001package jmri.jmrit.catalog;
002
003import java.awt.datatransfer.DataFlavor;
004import java.awt.event.ActionEvent;
005import java.awt.event.MouseEvent;
006import java.util.Enumeration;
007import java.util.Set;
008import javax.swing.BoxLayout;
009import javax.swing.JLabel;
010import javax.swing.JMenu;
011import javax.swing.JMenuBar;
012import javax.swing.JMenuItem;
013import javax.swing.JOptionPane;
014import javax.swing.JPanel;
015import javax.swing.JPopupMenu;
016import javax.swing.JSplitPane;
017import javax.swing.JTree;
018import javax.swing.SwingConstants;
019import javax.swing.tree.*;
020
021import jmri.CatalogTreeManager;
022import jmri.CatalogTreeNode;
023import jmri.InstanceInitializer;
024import jmri.InstanceManager;
025import jmri.implementation.AbstractInstanceInitializer;
026import jmri.util.FileUtil;
027import jmri.util.JmriJFrame;
028import org.openide.util.lookup.ServiceProvider;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * A JFrame for creating and editing an Image Index.
034 *
035 * @author Pete Cressman Copyright 2009
036 */
037public final class ImageIndexEditor extends JmriJFrame {
038
039    private static final String RESOURCES = "resources";
040    private static final String MESSAGE_TITLE = "MessageTitle";
041    private CatalogPanel _index;
042    private JTree _indexTree;
043
044    public static final String IconDataFlavorMime = DataFlavor.javaJVMLocalObjectMimeType
045            + ";class=jmri.jmrit.catalog.NamedIcon";
046
047    /**
048     * Ctor for an unnamed ImageIndexEditor.
049     */
050    private ImageIndexEditor() {
051        super();
052    }
053
054    /**
055     * Ctor for a named ImageIndexEditor.
056     *
057     * @param name title to display on the editor frame
058     */
059    private ImageIndexEditor(String name) {
060        super(name);
061    }
062
063    private void init() {
064        JMenuBar menuBar = new JMenuBar();
065        JMenu findIcon = new JMenu(Bundle.getMessage("MenuFile"));
066        menuBar.add(findIcon);
067        JMenuItem storeItem = new JMenuItem(Bundle.getMessage("MIStoreImageIndex"));
068        findIcon.add(storeItem);
069        storeItem.addActionListener((ActionEvent event) ->
070                InstanceManager.getDefault(CatalogTreeManager.class).storeImageIndex());
071
072        findIcon.addSeparator();
073        JMenuItem openItem = new JMenuItem(Bundle.getMessage("openDirMenu"));
074        openItem.addActionListener((ActionEvent e) ->
075                InstanceManager.getDefault(DirectorySearcher.class).openDirectory());
076        findIcon.add(openItem);
077
078        JMenuItem searchItem = new JMenuItem(Bundle.getMessage("searchFSMenu"));
079        searchItem.addActionListener((ActionEvent e) ->
080                InstanceManager.getDefault(DirectorySearcher.class).searchFS());
081        findIcon.add(searchItem);
082
083        JMenu editMenu = new JMenu(Bundle.getMessage("MenuEdit"));
084        menuBar.add(editMenu);
085        addMenuItems(editMenu, null);
086        setJMenuBar(menuBar);
087
088        addHelpMenu("package.jmri.jmrit.catalog.ImageIndex", true);
089
090        JPanel mainPanel = new JPanel();
091        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
092        JPanel labelPanel = new JPanel();
093        labelPanel.add(new JLabel(Bundle.getMessage("dragIconsInstr"), SwingConstants.LEFT));
094        mainPanel.add(labelPanel);
095        JPanel catalogsPanel = new JPanel();
096        catalogsPanel.setLayout(new BoxLayout(catalogsPanel, BoxLayout.X_AXIS));
097        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
098                makeCatalogPanel(), makeIndexPanel());
099        splitPane.setContinuousLayout(true);
100        splitPane.setOneTouchExpandable(true);
101        catalogsPanel.add(splitPane);
102        mainPanel.add(catalogsPanel);
103        getContentPane().add(mainPanel);
104
105        addWindowListener(new java.awt.event.WindowAdapter() {
106            @Override
107            public void windowClosing(java.awt.event.WindowEvent e) {
108                InstanceManager.getDefault(DirectorySearcher.class).close();
109            }
110        });
111        setLocation(10, 200);
112        pack();
113        setVisible(true);
114    }
115
116    private JPanel makeCatalogPanel() {
117        CatalogPanel _catalog = CatalogPanel.makeCatalog("defaultCatalog", "selectNode", true, false, true); // make sure both these properties keys exist
118         log.debug("init the new CatalogPanel for ImageIndexEditor.makeCatalogPanel()");
119        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
120
121        for (jmri.CatalogTree tree : manager.getNamedBeanSet()) {
122            if (tree.getSystemName().startsWith("IF")) {
123                _catalog.addTree(tree);
124                log.debug("added item to tree");
125            }
126        }
127
128        _catalog.createNewBranch("IFJAR", "Program Directory", RESOURCES);
129        FileUtil.createDirectory(FileUtil.getUserFilesPath() + RESOURCES);
130        _catalog.createNewBranch("IFPREF", "Preferences Directory", FileUtil.getUserFilesPath() + RESOURCES);
131        return _catalog;
132    }
133
134    /*
135     * Provide node editing.
136     *
137     * @param evt mouse event providing x and y position in frame
138     */
139    private void showTreePopUp(MouseEvent evt) {
140        int row = _indexTree.getRowForLocation(evt.getX(), evt.getY());
141        if (row <= 0) {
142            return;
143        }
144        TreePath path = _indexTree.getPathForLocation(evt.getX(), evt.getY());
145        String nodeName = path.getLastPathComponent().toString();
146        log.debug("showTreePopUp node= {}", nodeName);
147        JPopupMenu popup = new JPopupMenu();
148        popup.add(new JMenuItem(nodeName));
149        popup.add(new javax.swing.JPopupMenu.Separator());
150        
151        addMenuItems(popup, (CatalogTreeNode) path.getLastPathComponent());
152        popup.show(evt.getComponent(), evt.getX(), evt.getY());
153    }
154
155    private void addMenuItems(javax.swing.JComponent editMenu, CatalogTreeNode node) {
156        JMenuItem addItem = new JMenuItem(Bundle.getMessage("addNode"));
157        addItem.addActionListener((ActionEvent e) -> addNode(node));
158        editMenu.add(addItem);
159        JMenuItem renameItem = new JMenuItem(Bundle.getMessage("renameNode"));
160        renameItem.addActionListener((ActionEvent e) -> renameNode(node));
161        editMenu.add(renameItem);
162        JMenuItem deleteItem = new JMenuItem(Bundle.getMessage("deleteNode"));
163        deleteItem.addActionListener((ActionEvent e) -> deleteNode(node));
164        editMenu.add(deleteItem);
165    }
166
167    private JPanel makeIndexPanel() {
168        _index = CatalogPanel.makeCatalog("ImageIndex", "selectIndexNode", true, true, false); // make sure both these properties keys exist
169        log.debug("init the new CatalogPanel for ImageIndexEditor.makeIndexPanel()");
170        // activate dragNdrop init(true,true) allows drop into same panel as the drag - i.e. duplicates icon in node
171
172        boolean found = false;
173        CatalogTreeManager manager = InstanceManager.getDefault(jmri.CatalogTreeManager.class);
174        for (jmri.CatalogTree tree : manager.getNamedBeanSet()) {
175            if (tree.getSystemName().startsWith("IX")) {
176                _index.addTree(tree);
177                found = true;
178            }
179        }
180
181        if (!found) {
182            _index.createNewBranch("IXII", Bundle.getMessage("ImageIndexRoot"), "ImageIndexRoot");
183        }
184        _indexTree = _index.getTree();
185        _indexTree.setSelectionRow(0);   // begin with ImageIndexRoot selected
186        _indexTree.addMouseListener(new java.awt.event.MouseAdapter () {
187            @Override
188            public void mouseReleased(MouseEvent e) {
189                if (e.isPopupTrigger() || e.isMetaDown() || e.isAltDown()) {
190                    showTreePopUp(e);
191                }
192            }
193        });
194        return _index;
195    }
196
197    void addNode(CatalogTreeNode selectedNode) {
198        if (selectedNode == null) {
199            selectedNode = _index.getSelectedNode();            
200        }
201        if (selectedNode == null) {
202            JOptionPane.showMessageDialog(this, Bundle.getMessage("selectAddNode"),
203                    Bundle.getMessage(MESSAGE_TITLE), JOptionPane.INFORMATION_MESSAGE);
204        } else {
205            String name = JOptionPane.showInputDialog(this, Bundle.getMessage("nameAddNode"),
206                    Bundle.getMessage("QuestionTitle"), JOptionPane.QUESTION_MESSAGE);
207            if (name != null) {
208                if (!_index.insertNodeIntoModel(name, selectedNode)) {
209                    JOptionPane.showMessageDialog(this, Bundle.getMessage("duplicateNodeName", name),
210                            Bundle.getMessage("ErrorTitle"), JOptionPane.ERROR_MESSAGE);
211                }
212            }
213        }
214        invalidate();
215    }
216
217    private void renameNode(CatalogTreeNode selectedNode) {
218        if (selectedNode == null) {
219            selectedNode = _index.getSelectedNode();            
220        }
221        if (selectedNode == null) {
222            JOptionPane.showMessageDialog(this, Bundle.getMessage("selectRenameNode"),
223                    Bundle.getMessage(MESSAGE_TITLE), JOptionPane.INFORMATION_MESSAGE);
224        } else {
225            String name = JOptionPane.showInputDialog(this, Bundle.getMessage("newNameNode"),
226                    selectedNode.getUserObject());
227            if (name != null) {
228                if (!_index.renameNode(selectedNode, name)) {
229                    JOptionPane.showMessageDialog(this, Bundle.getMessage("duplicateNodeName", name),
230                            Bundle.getMessage("ErrorTitle"), JOptionPane.ERROR_MESSAGE);
231                }
232
233            }
234        }
235        invalidate();
236    }
237
238    private void deleteNode(CatalogTreeNode selectedNode) {
239        if (selectedNode == null) {
240            selectedNode = _index.getSelectedNode();            
241        }
242        if (selectedNode == null) {
243            JOptionPane.showMessageDialog(this, Bundle.getMessage("selectDeleteNode"),
244                    Bundle.getMessage(MESSAGE_TITLE), JOptionPane.INFORMATION_MESSAGE);
245            return;
246        }
247        if (log.isDebugEnabled()) {
248            log.debug("delete node \"{}\" level= {}.", selectedNode, selectedNode.getLevel());
249        }
250        if (selectedNode.getLevel() <= 1) {
251            JOptionPane.showMessageDialog(this, Bundle.getMessage("deleteRootNode"),
252                    Bundle.getMessage(MESSAGE_TITLE), JOptionPane.INFORMATION_MESSAGE);
253        } else {
254            int numNodes = countSubNodes(selectedNode);
255            int numIcons = countIcons(selectedNode);
256            int response = JOptionPane.showConfirmDialog(this,
257                    Bundle.getMessage("confirmDeleteNode", selectedNode.getUserObject(), numNodes, numIcons),
258                    Bundle.getMessage("QuestionTitle"), JOptionPane.YES_NO_OPTION,
259                    JOptionPane.QUESTION_MESSAGE);
260            if (response == JOptionPane.YES_OPTION) {
261                CatalogTreeNode parent = (CatalogTreeNode)selectedNode.getParent();
262                _index.removeNodeFromModel(selectedNode);
263                _index.setSelectedNode(parent);
264            }
265        }
266    }
267
268    int countSubNodes(CatalogTreeNode node) {
269        int cnt = 0;
270        Enumeration<TreeNode> e = node.children();
271        while (e.hasMoreElements()) {
272            CatalogTreeNode n = (CatalogTreeNode)e.nextElement();
273            cnt += countSubNodes(n) + 1;
274        }
275        return cnt;
276    }
277
278    private int countIcons(CatalogTreeNode node) {
279        int cnt = 0;
280        Enumeration<TreeNode> e = node.children();
281        while (e.hasMoreElements()) {
282            CatalogTreeNode n =(CatalogTreeNode)e.nextElement();
283            cnt += countIcons(n);
284        }
285        cnt += node.getNumLeaves();
286        return cnt;
287    }
288
289    @ServiceProvider(service = InstanceInitializer.class)
290    public static class Initializer extends AbstractInstanceInitializer {
291
292        @Override
293        public <T> Object getDefault(Class<T> type) {
294            if (type.equals(ImageIndexEditor.class)) {
295                ImageIndexEditor instance = new ImageIndexEditor(Bundle.getMessage("editIndexFrame"));
296                instance.init();
297                return instance;
298            }
299            return super.getDefault(type);
300        }
301
302        @Override
303        public Set<Class<?>> getInitalizes() {
304            Set<Class<?>> set = super.getInitalizes();
305            set.add(ImageIndexEditor.class);
306            return set;
307        }
308    }
309
310    private static final Logger log = LoggerFactory.getLogger(ImageIndexEditor.class);
311
312}