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;
008
009import javax.swing.BoxLayout;
010import javax.swing.JLabel;
011import javax.swing.JMenu;
012import javax.swing.JMenuBar;
013import javax.swing.JMenuItem;
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 jmri.util.swing.JmriJOptionPane;
029
030import org.openide.util.lookup.ServiceProvider;
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            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("selectAddNode"),
203                    Bundle.getMessage(MESSAGE_TITLE), JmriJOptionPane.INFORMATION_MESSAGE);
204        } else {
205            String name = (String)JmriJOptionPane.showInputDialog(this, Bundle.getMessage("nameAddNode"), Bundle.getMessage("QuestionTitle"),
206                    HEIGHT, null, null, "");
207            if (name != null) {
208                if (!_index.insertNodeIntoModel(name, selectedNode)) {
209                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("duplicateNodeName", name),
210                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.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            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("selectRenameNode"),
223                    Bundle.getMessage(MESSAGE_TITLE), JmriJOptionPane.INFORMATION_MESSAGE);
224        } else {
225            String name = JmriJOptionPane.showInputDialog(this, Bundle.getMessage("newNameNode"),
226                    selectedNode.getUserObject().toString());
227            if (name != null) {
228                if (!_index.renameNode(selectedNode, name)) {
229                    JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("duplicateNodeName", name),
230                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.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            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("selectDeleteNode"),
244                    Bundle.getMessage(MESSAGE_TITLE), JmriJOptionPane.INFORMATION_MESSAGE);
245            return;
246        }
247        log.debug("delete node \"{}\" level= {}.", selectedNode, selectedNode.getLevel());
248        if (selectedNode.getLevel() <= 1) {
249            JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("deleteRootNode"),
250                    Bundle.getMessage(MESSAGE_TITLE), JmriJOptionPane.INFORMATION_MESSAGE);
251        } else {
252            int numNodes = countSubNodes(selectedNode);
253            int numIcons = countIcons(selectedNode);
254            int response = JmriJOptionPane.showConfirmDialog(this,
255                    Bundle.getMessage("confirmDeleteNode", selectedNode.getUserObject(), numNodes, numIcons),
256                    Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION,
257                    JmriJOptionPane.QUESTION_MESSAGE);
258            if (response == JmriJOptionPane.YES_OPTION) {
259                CatalogTreeNode parent = (CatalogTreeNode)selectedNode.getParent();
260                _index.removeNodeFromModel(selectedNode);
261                _index.setSelectedNode(parent);
262            }
263        }
264    }
265
266    int countSubNodes(CatalogTreeNode node) {
267        int cnt = 0;
268        Enumeration<TreeNode> e = node.children();
269        while (e.hasMoreElements()) {
270            CatalogTreeNode n = (CatalogTreeNode)e.nextElement();
271            cnt += countSubNodes(n) + 1;
272        }
273        return cnt;
274    }
275
276    private int countIcons(CatalogTreeNode node) {
277        int cnt = 0;
278        Enumeration<TreeNode> e = node.children();
279        while (e.hasMoreElements()) {
280            CatalogTreeNode n =(CatalogTreeNode)e.nextElement();
281            cnt += countIcons(n);
282        }
283        cnt += node.getNumLeaves();
284        return cnt;
285    }
286
287    @ServiceProvider(service = InstanceInitializer.class)
288    public static class Initializer extends AbstractInstanceInitializer {
289
290        @Override
291        public <T> Object getDefault(Class<T> type) {
292            if (type.equals(ImageIndexEditor.class)) {
293                ImageIndexEditor instance = new ImageIndexEditor(Bundle.getMessage("editIndexFrame"));
294                instance.init();
295                return instance;
296            }
297            return super.getDefault(type);
298        }
299
300        @Override
301        public Set<Class<?>> getInitalizes() {
302            Set<Class<?>> set = super.getInitalizes();
303            set.add(ImageIndexEditor.class);
304            return set;
305        }
306    }
307
308    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ImageIndexEditor.class);
309
310}