Skip to content

Commit

Permalink
metadata refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalazscs committed Jan 17, 2024
1 parent 45e4a67 commit 87f50e4
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 181 deletions.
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,6 @@
</build>

<dependencies>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.19.0</version>
</dependency>

<dependency>
<groupId>org.swinglabs.swingx</groupId>
<artifactId>swingx-all</artifactId>
Expand Down
175 changes: 64 additions & 111 deletions src/main/java/pixelitor/menus/file/MetaDataPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,38 @@

package pixelitor.menus.file;

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import org.jdesktop.swingx.JXTreeTable;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import pixelitor.Composition;
import pixelitor.gui.GUIText;
import pixelitor.gui.utils.DialogBuilder;
import pixelitor.gui.utils.Dialogs;
import pixelitor.gui.utils.PAction;
import pixelitor.io.FileUtils;
import pixelitor.menus.file.MetaDataTreeTableModel.NameValue;
import pixelitor.utils.Messages;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.io.UncheckedIOException;
import java.util.Iterator;

import static java.awt.BorderLayout.CENTER;
import static java.awt.BorderLayout.EAST;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.WEST;
import static java.awt.FlowLayout.LEFT;
import static java.lang.String.format;
import static pixelitor.gui.GUIText.CLOSE_DIALOG;

public class MetaDataPanel extends JPanel implements DropTargetListener {
public class MetaDataPanel extends JPanel {
private final JXTreeTable treeTable;

private MetaDataPanel(MetaDataTreeTableModel model) {
Expand All @@ -62,8 +62,7 @@ private MetaDataPanel(MetaDataTreeTableModel model) {
JScrollPane sp = new JScrollPane(treeTable);
add(sp, CENTER);

JPanel northPanel = new JPanel(new BorderLayout());
JPanel northLeftPanel = new JPanel(new FlowLayout(LEFT));
JPanel northPanel = new JPanel(new FlowLayout(LEFT));

JButton expandButton = new JButton(new PAction(
"Expand All", treeTable::expandAll));
Expand All @@ -73,115 +72,69 @@ private MetaDataPanel(MetaDataTreeTableModel model) {
"Collapse All", treeTable::collapseAll));
collapseButton.setName("collapseButton");

northLeftPanel.add(expandButton);
northLeftPanel.add(collapseButton);
northPanel.add(northLeftPanel, WEST);
northPanel.add(expandButton);
northPanel.add(collapseButton);

JButton helpButton = new JButton(new PAction(
GUIText.HELP, this::showHelp));

JPanel northRightPanel = new JPanel();
northRightPanel.add(helpButton);
northPanel.add(northRightPanel, EAST);
add(northPanel, NORTH);

setupColumnsWidths();

new DropTarget(this, this);
}

private void showHelp() {
String txt = "<html>You can drag external multimedia files on the Metadata window " +
"to see their Exif, IPTC, etc. information." +
"<br>It can read a different set of files than the rest of Pixelitor." +
"<p><br>Supported file types: <b>JPEG, TIFF, WebP, PNG, BMP, GIF, HEIC, PSD, " +
"ICO, PCX, MP3, WAV, QuickTime, MP4, AVI</b>." +
"<br>Supported Camera Raw file types: <b>NEF</b> (Nikon), <b>CR2</b> (Canon), " +
"<b>ORF</b> (Olympus), <b>ARW</b> (Sony), <br><b>RW2</b> (Panasonic), " +
"<b>RWL</b> (Leica), <b>SRW</b> (Samsung).";
Dialogs.showInfoDialog(this, "Show Metadata Help", txt);
}

private void setupColumnsWidths() {
treeTable.getColumnModel().getColumn(0).setMinWidth(200);
treeTable.getColumnModel().getColumn(1).setMinWidth(200);
}

@Override
public void dragEnter(DropTargetDragEvent dtde) {
handleOngoingDrag(dtde);
}

@Override
public void dragOver(DropTargetDragEvent dtde) {
handleOngoingDrag(dtde);
}

@Override
public void dropActionChanged(DropTargetDragEvent dtde) {

}

@Override
public void dragExit(DropTargetEvent dte) {

}

@Override
public void drop(DropTargetDropEvent e) {
Transferable transferable = e.getTransferable();
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (DataFlavor flavor : flavors) {
if (flavor.isFlavorJavaFileListType()) {
// this is where we get after dropping a file or directory
e.acceptDrop(DnDConstants.ACTION_COPY);

try {
@SuppressWarnings("unchecked")
List<File> list = (List<File>) transferable.getTransferData(flavor);
File file = list.getFirst();
if (file.isFile()) {
changeFile(file);
}
} catch (UnsupportedFlavorException | IOException ex) {
e.rejectDrop();
}
e.dropComplete(true);
return;
private static TreeNode extractMetadata(File file) throws IOException {
IIOMetadata metadata = null;
try (ImageInputStream iis = ImageIO.createImageInputStream(file)) {
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
if (readers.hasNext()) {
// pick the first available ImageReader
ImageReader reader = readers.next();
// attach source to the reader
reader.setInput(iis, true);
// read metadata of first image
metadata = reader.getImageMetadata(0);
}
}

// DataFlavor not recognized
e.rejectDrop();
}

private static void handleOngoingDrag(DropTargetDragEvent dtde) {
if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
dtde.acceptDrag(DnDConstants.ACTION_COPY);
} else {
dtde.rejectDrag();
String[] formatNames = metadata.getMetadataFormatNames();
DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
for (String formatName : formatNames) {
Node node = metadata.getAsTree(formatName);
root.add(toSwingNode(node));
}
return root;
}

private void changeFile(File file) {
Metadata metadata = extractMetadata(file);
if (metadata != null) {
treeTable.setTreeTableModel(new MetaDataTreeTableModel(metadata));
setupColumnsWidths();
JDialog d = (JDialog) SwingUtilities.getWindowAncestor(this);
d.setTitle("Metadata for " + file.getName());
private static DefaultMutableTreeNode toSwingNode(Node node) {
DefaultMutableTreeNode swingNode;

swingNode = new DefaultMutableTreeNode(node.getNodeName());

NamedNodeMap map = node.getAttributes();
if (map != null) {
int length = map.getLength();
if (length == 1) {
NameValue nameValue = new NameValue(node.getNodeName(), map.item(0).getNodeValue());
return new DefaultMutableTreeNode(nameValue);
} else {
for (int i = 0; i < length; i++) {
Node attr = map.item(i);
String nodeName = attr.getNodeName();
String nodeValue = attr.getNodeValue();
NameValue nameValue = new NameValue(nodeName, nodeValue);
swingNode.add(new DefaultMutableTreeNode(nameValue));
}
}
}
}

private static Metadata extractMetadata(File file) {
Metadata metadata;
try {
metadata = ImageMetadataReader.readMetadata(file);
} catch (ImageProcessingException | IOException e) {
Messages.showException(e);
return null;
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
swingNode.add(toSwingNode(childNode));
}
return metadata;
return swingNode;
}

public static void showInDialog(Composition comp) {
Expand All @@ -200,13 +153,13 @@ public static void showInDialog(Composition comp) {
Messages.showError("File not found", msg, comp.getDialogParent());
return;
}
if (FileUtils.hasTGAExtension(file.getName())) {
String msg = "Metadata for TGA files isn't supported yet.";
Messages.showError("TGA File", msg, comp.getDialogParent());
return;
TreeNode metadataRoot;
try {
metadataRoot = extractMetadata(file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Metadata metadata = extractMetadata(file);
MetaDataPanel panel = new MetaDataPanel(new MetaDataTreeTableModel(metadata));
MetaDataPanel panel = new MetaDataPanel(new MetaDataTreeTableModel(metadataRoot));
new DialogBuilder()
.title("Metadata for " + file.getName())
.content(panel)
Expand Down
85 changes: 21 additions & 64 deletions src/main/java/pixelitor/menus/file/MetaDataTreeTableModel.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Laszlo Balazs-Csiki and Contributors
* Copyright 2024 Laszlo Balazs-Csiki and Contributors
*
* This file is part of Pixelitor. Pixelitor is free software: you
* can redistribute it and/or modify it under the terms of the GNU
Expand All @@ -17,66 +17,55 @@

package pixelitor.menus.file;

import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import org.jdesktop.swingx.treetable.AbstractTreeTableModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;

/**
* The model for the metadata tree-table
*/
public class MetaDataTreeTableModel extends AbstractTreeTableModel {
private static final String[] COLUMN_NAMES = {"Name", "Value"};
private final List<DirNode> dirNodes = new ArrayList<>();

public MetaDataTreeTableModel(Metadata metadata) {
super(new Object());
for (Directory directory : metadata.getDirectories()) {
dirNodes.add(new DirNode(directory));
}
public MetaDataTreeTableModel(TreeNode root) {
super(root);
}

@Override
public Object getValueAt(Object node, int column) {
if (node instanceof DirNode dir) {
return switch (column) {
case 0 -> dir.name();
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) node;
Object userObject = treeNode.getUserObject();
return switch (userObject) {
case String s -> switch (column) {
// a String means a "directory node" with no value
case 0 -> s;
case 1 -> null;
default -> throw new IllegalStateException("Unexpected column: " + column);
};
} else if (node instanceof TagNode tag) {
return switch (column) {
case 0 -> tag.name();
case 1 -> tag.value();
case NameValue nameValue -> switch (column) {
// a leaf node was found
case 0 -> nameValue.name();
case 1 -> nameValue.value();
default -> throw new IllegalStateException("Unexpected column: " + column);
};
}
return null;
default -> throw new IllegalStateException("Unexpected type: " + userObject.getClass().getName());
};
}

@Override
public Object getChild(Object parent, int index) {
if (parent instanceof DirNode dir) {
return dir.nodes.get(index);
}
return dirNodes.get(index);
return ((TreeNode) parent).getChildAt(index);
}

@Override
public int getChildCount(Object parent) {
if (parent instanceof DirNode dir) {
return dir.nodes.size();
}
return dirNodes.size();
return ((TreeNode) parent).getChildCount();
}

@Override
public int getIndexOfChild(Object parent, Object child) {
return ((TagNode) child).index();
return ((TreeNode) parent).getIndex((TreeNode) child);
}

@Override
Expand All @@ -89,38 +78,6 @@ public String getColumnName(int column) {
return COLUMN_NAMES[column];
}

@Override
public boolean isLeaf(Object node) {
return node instanceof TagNode;
}

static class DirNode {
private final Directory dir;
private final List<TagNode> nodes = new ArrayList<>();

DirNode(Directory dir) {
this.dir = dir;
Collection<Tag> tags = dir.getTags();
int tagIndex = 0;
for (Tag tag : tags) {
String tagName = tag.getTagName();
if (tagName.startsWith("Unknown")) {
continue;
}
String description = tag.getDescription();
if (description != null && description.startsWith("Unknown")) {
continue;
}
nodes.add(new TagNode(tagName, description, tagIndex));
tagIndex++;
}
}

public String name() {
return dir.getName();
}
}

private record TagNode(String name, String value, int index) {
public record NameValue(String name, String value) {
}
}

0 comments on commit 87f50e4

Please sign in to comment.