source: trunk/gli/src/org/greenstone/gatherer/gui/GatherPane.java@ 9888

Last change on this file since 9888 was 9855, checked in by mdewsnip, 19 years ago

Minor changes.

  • Property svn:keywords set to Author Date Id Revision
File size: 37.1 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.gui;
38
39import java.awt.*;
40import java.awt.event.*;
41import java.io.*;
42import java.util.*;
43import javax.swing.*;
44import javax.swing.event.*;
45import javax.swing.tree.*;
46import org.greenstone.gatherer.Configuration;
47import org.greenstone.gatherer.DebugStream;
48import org.greenstone.gatherer.Dictionary;
49import org.greenstone.gatherer.Gatherer;
50import org.greenstone.gatherer.collection.CollectionTree;
51import org.greenstone.gatherer.collection.CollectionTreeNode;
52import org.greenstone.gatherer.file.FileNode;
53import org.greenstone.gatherer.file.FileOpenActionListener;
54import org.greenstone.gatherer.file.FileQueue;
55import org.greenstone.gatherer.file.FileSystemModel;
56import org.greenstone.gatherer.file.RecycleBin;
57import org.greenstone.gatherer.file.WorkspaceTree;
58import org.greenstone.gatherer.file.WorkspaceTreeNode;
59import org.greenstone.gatherer.gui.tree.DragTree;
60import org.greenstone.gatherer.gui.ExplodeMetadataPrompt;
61import org.greenstone.gatherer.util.DragComponent;
62import org.greenstone.gatherer.util.DragGroup;
63import org.greenstone.gatherer.util.TreeSynchronizer;
64import org.greenstone.gatherer.util.Utility;
65
66/** The collection pane is analogous with a file manager. It is there that the user chooses which files to include in their collection and what structure the file hierarchy should take. The later aspect is not important for the Greenstone Suite, but is usefull for grouping files for ease of metadata markup. The view essentially consists of two file trees, one denoting the entire source workspace and the other the files within your collection. The trees themselves have a title bar at the top, a filter control at the bottom, and are coloured to indicate activity (grey for disabled). The remainder of the screen is taken by a status area, to indicate current file job progress during copying etc, and three buttons for controlling features of the view.
67 * @author John Thompson, Greenstone Digital Library, University of Waikato
68 * @version 2.3
69 */
70public class GatherPane
71 extends JPanel
72 implements ActionListener, FocusListener {
73 /** The group encompassing all of the components available as drop targets for drag and drop actions. Required so that only one component renders the ghost and higlights itself as a target, which the other members are restored to their original, pristine, condition. */
74 private DragGroup group = null;
75 /** The tree showing the files within the collection. */
76 private CollectionTree collection_tree = null;
77 /** The threaded queue that handles the actually movement of files, so that the gui remains responsive. */
78 private FileQueue file_queue = null;
79 /** The filter currently applied to the collection tree. */
80 private Filter collection_filter = null;
81 /** The filter currently applied to the workspace tree. */
82 private Filter workspace_filter = null;
83 /** The button used to cancel all pending file queue jobs. */
84 private JButton stop_action = null;
85 /** The button used to create a new folder in the collection tree. */
86 private JButton new_folder = null;
87 /** The label shown at the top of the collection tree. */
88 private JLabel collection_label = null;
89 /** The label shown in the status area explaining the file apon which action is taking place. */
90 private JLabel filename_label = null;
91 /** The label shown explaining the current state of the file queue thread. */
92 private JLabel status_label = null;
93 /** The label at the top of the workspace tree. */
94 private JLabel workspace_label = null;
95 /** The panel that contains the collection tree. */
96 private JPanel collection_pane = null;
97 /** The panel that contains the various controls including the status area. */
98 private JPanel control_pane = null;
99 /** The panel that contains the workspace tree. */
100 private JPanel workspace_pane = null;
101 /** The scrollable area into which the collection tree is placed. */
102 private JScrollPane collection_scroll = null;
103 /** The scrollable area into which the workspace tree is placed. */
104 private JScrollPane workspace_scroll = null;
105 /** A split pane seperating the two trees, allowing for the screen real-estate for each to be changed. */
106 private JSplitPane tree_pane = null;
107 /** Text fragment arguments used to fill in phrases returned from the dictionary. */
108 private String args[] = null;
109 /** Ensures that expansion and selection events between collection trees based on the same model are synchronized. */
110 private TreeSynchronizer collection_tree_sync = null;
111 /** The button used to delete files, which also doubles as a drop target for files from the Trees. */
112 private RecycleBin bin_button = null;
113 /** The default size of a special mapping dialog. */
114 static final Dimension DIALOG_SIZE = new Dimension(400, 120);
115 /** The minimum size a gui component can become. */
116 static private Dimension MIN_SIZE = new Dimension( 90, 90);
117 /** The default size of the status area. */
118 static private Dimension STATUS_SIZE = new Dimension(450, 120);
119 /** The initial size of the trees. */
120 static private Dimension TREE_SIZE = new Dimension(400, 430);
121
122 /** The tree showing the available source workspace. */
123 private WorkspaceTree workspace_tree = null;
124
125
126 /* Constructor.
127 * @param tree_sync Ensures that expansion events between like trees are synchronized.
128 * @see org.greenstone.gatherer.file.FileManager
129 * @see org.greenstone.gatherer.file.FileQueue
130 */
131 public GatherPane(TreeSynchronizer collection_tree_sync) {
132 this.group = new DragGroup();
133 this.file_queue = Gatherer.f_man.getQueue();
134 this.collection_tree_sync = collection_tree_sync;
135
136 // Create components.
137 stop_action = new GLIButton();
138 stop_action.addActionListener(this);
139 stop_action.setEnabled(false);
140 stop_action.setMnemonic(KeyEvent.VK_S);
141 file_queue.registerStopButton(stop_action);
142 Dictionary.registerBoth(stop_action, "Collection.Stop", "Collection.Stop_Tooltip");
143
144 new_folder = new GLIButton(Utility.getImage("folder.gif"));
145 new_folder.addActionListener(this);
146 new_folder.setEnabled(false);
147 new_folder.setMinimumSize(MIN_SIZE);
148 new_folder.setMnemonic(KeyEvent.VK_N);
149 new_folder.setPreferredSize(MIN_SIZE);
150 Dictionary.registerTooltip(new_folder, "Collection.New_Folder_Tooltip");
151 }
152
153 /** Any implementation of ActionListener requires this method so that when an action is performed the appropriate effect can occur. In this case there are three valid possibilities. If the action occured on the recycle bin, then delete the current selection from the collection tree. If the action instead occured on the new folder button, then create a new folder under the current (single) selection in the collection tree. And finally if the cancel button was pressed, cancel the current, and remaining, jobs on the file queue. */
154 public void actionPerformed(ActionEvent event) {
155 // If a user has clicked on the bin button directly remove whatever
156 // files are selected in the active tree.
157 if(event.getSource() == bin_button) {
158 if(!bin_button.ignore()) {
159 // Find the active tree (you've made selections in).
160 DragTree tree = (DragTree) group.getActive();
161 // Fudge things a bit
162 group.setSource(tree);
163 // Determine the selection.
164 TreePath paths[] = tree.getSelectionPaths();
165 if(paths != null) {
166 FileNode[] source_nodes = new FileNode[paths.length];
167 for(int i = 0; i < paths.length; i++) {
168 source_nodes[i] = (FileNode)(paths[i].getLastPathComponent());
169 }
170 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
171 }
172 }
173 }
174 // If a user has clicked on new_folder create a new folder under
175 // whatever node is selected.
176 else if(event.getSource() == new_folder && collection_tree != null) {
177 int count = collection_tree.getSelectionCount();
178 boolean error = false;
179 if(count == 1) {
180 TreePath path = collection_tree.getSelectionPath();
181 CollectionTreeNode node = (CollectionTreeNode) path.getLastPathComponent();
182 if (node.getAllowsChildren()) {
183 Gatherer.f_man.newFolder(collection_tree, node);
184 }
185 else {
186 // try the parent
187 CollectionTreeNode parent = (CollectionTreeNode) node.getParent();
188 if (parent!=null && parent.getAllowsChildren()) {
189 Gatherer.f_man.newFolder(collection_tree, parent);
190 } else {
191 error = true;
192 }
193 }
194 }
195 else {
196 error = true;
197 }
198 if(error) {
199 // instead of an error, we now create a new folder at the root
200 CollectionTreeNode node = (CollectionTreeNode) collection_tree.getModel().getRoot();
201 Gatherer.f_man.newFolder(collection_tree, node);
202 }
203 }
204 else if(event.getSource() == stop_action) {
205 file_queue.cancelAction();
206 }
207 }
208
209
210 /** Generates the pane on controls used to 'collect' files into the collection. Resposible for creating, connecting and laying out these controls. */
211 public void display() {
212 // Create Components.
213 KeyListenerImpl key_listener = new KeyListenerImpl();
214 MouseListenerImpl mouse_listener = new MouseListenerImpl();
215 this.addKeyListener(key_listener);
216
217 // Workspace Tree
218 workspace_pane = new JPanel();
219 workspace_pane.setMinimumSize(MIN_SIZE);
220 workspace_pane.setPreferredSize(TREE_SIZE);
221 workspace_pane.setSize(TREE_SIZE);
222
223 workspace_label = new JLabel();
224 workspace_label.setOpaque(true);
225 workspace_label.setBackground(Configuration.getColor("coloring.workspace_heading_background", false));
226 workspace_label.setForeground(Configuration.getColor("coloring.workspace_heading_foreground", false));
227 Dictionary.registerText(workspace_label, "Collection.Workspace");
228
229 workspace_tree = new WorkspaceTree(Utility.WORKSPACE_TREE);
230 group.add(workspace_tree);
231 workspace_tree.addFocusListener(this);
232 workspace_tree.addKeyListener(key_listener);
233 workspace_tree.addMouseListener(mouse_listener);
234 workspace_tree.addMouseListener(Gatherer.g_man.foa_listener);
235 workspace_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
236 workspace_tree.putClientProperty("JTree.lineStyle", "Angled");
237 workspace_tree.setBackgroundNonSelectionColor(Configuration.getColor("coloring.workspace_tree_background", false));
238 workspace_tree.setTextNonSelectionColor(Configuration.getColor("coloring.workspace_tree_foreground", false));
239 workspace_tree.setBackgroundSelectionColor(Configuration.getColor("coloring.workspace_selection_background", false));
240 workspace_tree.setTextSelectionColor(Configuration.getColor("coloring.workspace_selection_foreground", false));
241 workspace_tree.setRootVisible(false);
242
243 workspace_scroll = new JScrollPane(workspace_tree);
244
245 workspace_filter = Gatherer.g_man.getFilter(workspace_tree);
246 workspace_filter.setBackground(Configuration.getColor("coloring.workspace_heading_background", false));
247 workspace_filter.setEditable(Configuration.getMode() > Configuration.LIBRARIAN_MODE);
248 Dictionary.registerTooltip(workspace_filter.getComboBox(), "Collection.Filter_Tooltip");
249
250 // Collection Tree
251 collection_pane = new JPanel();
252 collection_pane.setMinimumSize(MIN_SIZE);
253 collection_pane.setPreferredSize(TREE_SIZE);
254 collection_pane.setSize(TREE_SIZE);
255
256 collection_label = new JLabel();
257 collection_label.setOpaque(true);
258 Dictionary.registerText(collection_label, "Collection.No_Collection");
259
260 collection_tree = new CollectionTree(Utility.COLLECTION_TREE, Gatherer.c_man.getCollectionTreeModel(), true);
261 collection_tree.setEnabled(Gatherer.c_man.getCollectionTreeModel() != null);
262 group.add(collection_tree);
263 collection_tree.addFocusListener(this);
264 collection_tree.addKeyListener(key_listener);
265 collection_tree.addMouseListener(mouse_listener);
266 collection_tree.addMouseListener(Gatherer.g_man.foa_listener);
267 collection_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
268 collection_tree.putClientProperty("JTree.lineStyle", "Angled");
269 collection_tree.setBackgroundNonSelectionColor(Configuration.getColor("coloring.collection_tree_background", false));
270 collection_tree.setTextNonSelectionColor(Configuration.getColor("coloring.collection_tree_foreground", false));
271 collection_tree.setBackgroundSelectionColor(Configuration.getColor("coloring.collection_selection_background", false));
272 collection_tree.setTextSelectionColor(Configuration.getColor("coloring.collection_selection_foreground", false));
273 collection_tree.setRootVisible(false);
274
275 collection_scroll = new JScrollPane(collection_tree);
276
277 collection_filter = Gatherer.g_man.getFilter(collection_tree);
278 collection_filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
279 collection_filter.setEditable(Configuration.getMode() > Configuration.LIBRARIAN_MODE);
280 Dictionary.registerTooltip(collection_filter.getComboBox(), "Collection.Filter_Tooltip");
281
282 tree_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
283
284 // Status pane
285 control_pane = new JPanel();
286
287 JPanel inner_pane = new JPanel();
288 inner_pane.setSize(STATUS_SIZE);
289
290 JPanel file_pane = new JPanel();
291 JPanel progress_pane = new JPanel();
292 JLabel file_status = file_queue.getFileStatus();
293
294 JProgressBar progress_bar = file_queue.getProgressBar();
295
296 JPanel button_pane = new JPanel();
297
298 bin_button = new RecycleBin();
299 bin_button.addActionListener(this);
300 bin_button.setEnabled(false);
301 bin_button.setMinimumSize(MIN_SIZE);
302 bin_button.setPreferredSize(MIN_SIZE);
303 Dictionary.registerTooltip(bin_button, "Collection.Delete_Tooltip");
304 group.add(bin_button);
305
306 // Layout Components.
307 workspace_pane.setLayout(new BorderLayout());
308 workspace_pane.add(workspace_label, BorderLayout.NORTH);
309 workspace_pane.add(workspace_scroll, BorderLayout.CENTER);
310 workspace_pane.add(workspace_filter, BorderLayout.SOUTH);
311
312 collection_pane.setLayout(new BorderLayout());
313 collection_pane.add(collection_label, BorderLayout.NORTH);
314 collection_pane.add(collection_scroll, BorderLayout.CENTER);
315 collection_pane.add(collection_filter, BorderLayout.SOUTH);
316
317 tree_pane.add(workspace_pane, JSplitPane.LEFT);
318 tree_pane.add(collection_pane, JSplitPane.RIGHT);
319 tree_pane.setDividerLocation(TREE_SIZE.width - 10);
320
321 file_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
322 file_pane.setLayout(new BorderLayout());
323 file_pane.add(file_status, BorderLayout.CENTER);
324 file_pane.add(stop_action, BorderLayout.EAST);
325
326 progress_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
327 progress_pane.setLayout(new BorderLayout());
328 progress_pane.add(progress_bar, BorderLayout.CENTER);
329
330 inner_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(10,10,10,10), BorderFactory.createLoweredBevelBorder()));
331 inner_pane.setLayout(new GridLayout(2,1));
332 inner_pane.add(file_pane);
333 inner_pane.add(progress_pane);
334
335 button_pane.add(new_folder);
336 button_pane.add(bin_button);
337
338 control_pane.setLayout(new BorderLayout());
339 control_pane.add(inner_pane, BorderLayout.CENTER);
340 control_pane.add(button_pane, BorderLayout.EAST);
341
342 this.setLayout(new BorderLayout());
343 this.add(tree_pane, BorderLayout.CENTER);
344 this.add(control_pane, BorderLayout.SOUTH);
345 }
346 /** This method ensures that a certain tree path is visible and selected within the collection tree, expanding nodes if necessary. If the method is successful the bounds of the new selection are returned. */
347 public Rectangle expandPath(TreePath path) {
348 collection_tree.setImmediate(true);
349 collection_tree.scrollPathToVisible(path);
350 collection_tree.setSelectionPath(path);
351 collection_tree.setImmediate(false);
352 return collection_tree.getRowBounds(collection_tree.getRowForPath(path));
353 }
354 /** Called whenever this pane gains focus, this method ensures that the various tree renderers are correctly colouring the tree (as these settings sometimes get lost).
355 * @param event A <strong>FocusEvent</strong> containing details about the focus action performed.
356 */
357 public void focusGained(FocusEvent event) {
358 DefaultTreeCellRenderer def = new DefaultTreeCellRenderer();
359 DefaultTreeCellRenderer w = (DefaultTreeCellRenderer)workspace_tree.getCellRenderer();
360 DefaultTreeCellRenderer c = (DefaultTreeCellRenderer)collection_tree.getCellRenderer();
361 if(event.getSource() == workspace_tree) {
362 w.setBackgroundSelectionColor(def.getBackgroundSelectionColor());
363 c.setBackgroundSelectionColor(Color.lightGray);
364 }
365 else if(event.getSource() == collection_tree) {
366 c.setBackgroundSelectionColor(def.getBackgroundSelectionColor());
367 w.setBackgroundSelectionColor(Color.lightGray);
368 }
369 repaint();
370 }
371 /** Implementation side-effect, not used in any way.
372 * @param event A <strong>FocusEvent</strong> containing details about the focus action performed.
373 */
374 public void focusLost(FocusEvent event) {
375 }
376
377 /** Called to inform this control panel that it has just gained focus as an effect of the user clicking on its tab.
378 */
379 public void gainFocus() {
380 // Update the meta-audit view to show the current selection, if any.
381 Gatherer.g_man.meta_audit.setRecords(getCollectionTreeSelection());
382 }
383
384 /** Retrieve a list of the currently selected file records in the collection tree. */
385 private CollectionTreeNode[] getCollectionTreeSelection()
386 {
387 TreePath paths[] = collection_tree.getSelectionPaths();
388 CollectionTreeNode records[] = null;
389 if (paths != null) {
390 records = new CollectionTreeNode[paths.length];
391 for (int i = 0; i < records.length; i++) {
392 records[i] = (CollectionTreeNode) paths[i].getLastPathComponent();
393 }
394 }
395 return records;
396 }
397
398
399 /** Called whenever the detail mode changes to ensure the filters are at an appropriate level (ie only editable by those that understand regular expression matching)
400 * @param mode the mode level as an int
401 */
402 public void modeChanged(int mode) {
403 collection_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
404 workspace_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
405 }
406
407
408 /** Refresh this pane, depending on what has just happened (refresh_reason). */
409 public void refresh(int refresh_reason, boolean collection_loaded)
410 {
411 if (collection_loaded) {
412 // Update collection label
413 Dictionary.registerText(collection_label, "Collection.Collection");
414 collection_label.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
415 collection_label.setForeground(Configuration.getColor("coloring.collection_heading_foreground", false));
416
417 // Update collection tree
418 if (refresh_reason == Gatherer.COLLECTION_OPENED) {
419 collection_tree.setModel(Gatherer.c_man.getCollectionTreeModel());
420 }
421
422 // Update collection filter
423 collection_filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
424 }
425 else {
426 // Update collection label
427 String[] args = new String[1];
428 args[0] = Dictionary.get("Collection.No_Collection");
429 Dictionary.registerText(collection_label, "Collection.Collection", args);
430 collection_label.setBackground(Color.lightGray);
431 collection_label.setForeground(Color.black);
432
433 // Update collection tree
434 collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("Error")));
435
436 // Update collection filter
437 collection_filter.setBackground(Color.lightGray);
438 }
439
440 // Enable or disable the controls
441 workspace_tree.setEnabled(true);
442 collection_tree.setEnabled(collection_loaded);
443 collection_filter.setEnabled(collection_loaded);
444 bin_button.setEnabled(collection_loaded);
445 new_folder.setEnabled(collection_loaded);
446
447 // Ensure that this collection tree view is synchronized with all others
448 collection_tree_sync.add(collection_tree);
449 }
450
451
452 public void refreshCollectionTree(int refresh_reason) {
453 collection_tree.refresh(null);
454 }
455
456
457 public void refreshWorkspaceTree(int refresh_reason) {
458 workspace_tree.refresh(refresh_reason);
459 }
460
461
462 /** When a user right-clicks within the workspace and collection trees they are presented with a small popup menu of context based options. This class provides such functionality.
463 */
464 private class RightClickMenu
465 extends JPopupMenu
466 implements ActionListener {
467
468 /** The tree over which the right click action occurred. */
469 private DragTree tree = null;
470 /** The tree nodes selected when the right click action occurred. */
471 private TreePath[] selection_paths = null;
472 /** The file record over which the right click action occurred. */
473 private FileNode node = null;
474
475 private JMenuItem create_shortcut = null;
476 private JMenuItem delete_shortcut = null;
477 private JMenuItem collapse_folder = null;
478 private JMenuItem expand_folder = null;
479 private JMenuItem explode_metadata_database = null;
480 private JMenuItem delete = null;
481 private JMenuItem metaaudit = null;
482 private JMenuItem new_folder = null;
483 private JMenuItem new_dummy_doc = null;
484 private JMenuItem open_externally = null;
485
486
487 private RightClickMenu(DragTree tree, MouseEvent event)
488 {
489 super();
490 this.tree = tree;
491
492 // Note we have to use setImmediate() with the set selction paths
493 // otherwise the selection doesn't get updated until after the
494 // popup comes up.
495
496 // the right click position
497 TreePath right_click_path = tree.getPathForLocation(event.getX(), event.getY());
498 if (right_click_path == null) {
499 // user has clicked outside of the tree, clear the selection
500 selection_paths = null;
501 tree.setImmediate(true);
502 tree.clearSelection();
503 tree.setImmediate(false);
504 } else {
505 // Get the paths currently selected in the tree
506 selection_paths = tree.getSelectionPaths();
507 if (selection_paths == null) {
508 // nothing currently selected - we shift the selection to
509 // the node that was right clicked on
510 selection_paths = new TreePath[1];
511 selection_paths[0] = right_click_path;
512 tree.setImmediate(true);
513 tree.setSelectionPath(right_click_path);
514 tree.setImmediate(false);
515 } else if (selection_paths.length == 1 && ! selection_paths[0].equals( right_click_path)) {
516 tree.setImmediate(true);
517 tree.clearSelection();
518 tree.setSelectionPath(right_click_path);
519 tree.setImmediate(false);
520 selection_paths[0] = right_click_path;
521 } else {
522 // we had multiply selected paths in the tree.
523 // if we clicked on one of those paths, then use all the
524 // current selection, otherwise clear the selection and
525 // select the one we right clicked on
526 boolean clicked_in_selection = false;
527 for (int i=0; i<selection_paths.length; i++) {
528 if (selection_paths[i].equals(right_click_path)) {
529 clicked_in_selection = true;
530 break;
531 }
532 }
533 if (!clicked_in_selection) {
534 // want the tree to update right away
535 tree.setImmediate(true);
536 tree.clearSelection();
537 tree.setSelectionPath(right_click_path);
538 tree.setImmediate(false);
539 selection_paths = new TreePath[1];
540 selection_paths[0] = right_click_path;
541 }
542 }
543 }
544 // finally we have the correct selection paths!
545
546 // Create an appropriate context menu, based on what is selected
547 buildContextMenu(selection_paths);
548
549 // Show the popup menu on screen
550 show(tree, event.getX(), event.getY());
551 }
552
553
554 private void buildContextMenu(TreePath[] selection_paths)
555 {
556 // If nothing is selected, only the new folder/dummy doc options are available...
557 if (selection_paths == null) {
558 // ... but only in the collection tree!
559 if (tree == collection_tree) {
560 new_folder = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Folder"), KeyEvent.VK_N);
561 new_folder.addActionListener(this);
562 add(new_folder);
563
564 new_dummy_doc = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Dummy_Doc"));
565 new_dummy_doc.addActionListener(this);
566 add(new_dummy_doc);
567
568 node = (CollectionTreeNode) tree.getModel().getRoot();
569 }
570
571 return;
572 }
573
574 DebugStream.println("Number of files/folders selected: " + selection_paths.length);
575
576 // Collection tree: display meta-audit option and delete options
577 if (tree == collection_tree) {
578 String[] args = new String[1];
579 args[0] = collection_tree.getSelectionDetails();
580 metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", args), KeyEvent.VK_A);
581 metaaudit.addActionListener(this);
582 add(metaaudit);
583
584 delete = new JMenuItem(Dictionary.get("CollectionPopupMenu.Delete"), KeyEvent.VK_D);
585 delete.addActionListener(this);
586 add(delete);
587 }
588
589 // Only meta-audit and delete are available if multiple items are selected...
590 if (selection_paths.length > 1) {
591 return;
592 }
593
594 TreePath path = selection_paths[0];
595 node = (FileNode) path.getLastPathComponent();
596
597 // ---- Options for file nodes ----
598 if (node.isLeaf()) {
599 // Explode metadata databases, for collection tree only, and for explodable files only
600 if (tree == collection_tree && ((CollectionTreeNode) node).isExplodable()) {
601 explode_metadata_database = new JMenuItem(Dictionary.get("Menu.Explode_Metadata_Database"), KeyEvent.VK_E);
602 explode_metadata_database.addActionListener(this);
603 add(explode_metadata_database);
604 }
605
606 // Open the file in an external program
607 open_externally = new JMenuItem(Dictionary.get("Menu.Open_Externally"), KeyEvent.VK_O);
608 open_externally.addActionListener(this);
609 add(open_externally);
610
611 return;
612 }
613
614 // ---- Options for folder nodes ----
615 // Collapse or expand, depending on current status
616 if (tree.isExpanded(path)) {
617 collapse_folder = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
618 collapse_folder.addActionListener(this);
619 add(collapse_folder);
620 }
621 else {
622 expand_folder = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
623 expand_folder.addActionListener(this);
624 add(expand_folder);
625 }
626
627 // New folder/dummy doc options, for collection tree only
628 if (tree == collection_tree && !node.isReadOnly()) {
629 new_folder = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Folder"), KeyEvent.VK_N);
630 new_folder.addActionListener(this);
631 add(new_folder);
632 new_dummy_doc = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Dummy_Doc"));
633 new_dummy_doc.addActionListener(this);
634 add(new_dummy_doc);
635 }
636
637 // Create/remove shortcut option, for workspace tree only
638 if (tree == workspace_tree) {
639 // The "built-in" folders can't be modified
640 String node_name = node.toString();
641 if (node_name.equals(Dictionary.get("Tree.World")) || node_name.equals(Dictionary.get("Tree.Root")) || node_name.equals(Dictionary.get("Tree.DownloadedFiles"))) {
642 return;
643 }
644
645 // You can unmap 1st level nodes
646 WorkspaceTreeNode root = (WorkspaceTreeNode) tree.getModel().getRoot();
647 if (root.getIndex(node) != -1) {
648 delete_shortcut = new JMenuItem(Dictionary.get("MappingPrompt.Unmap"), KeyEvent.VK_R);
649 delete_shortcut.addActionListener(this);
650 add(delete_shortcut);
651 }
652 // Or map any other level directories
653 else {
654 create_shortcut = new JMenuItem(Dictionary.get("MappingPrompt.Map"), KeyEvent.VK_S);
655 create_shortcut.addActionListener(this);
656 add(create_shortcut);
657 }
658 }
659 }
660
661
662 /** Called whenever one of the menu items is clicked, this method then causes the appropriate effect. */
663 public void actionPerformed(ActionEvent event)
664 {
665 Object source = event.getSource();
666
667 // Create shortcut
668 if (source == create_shortcut) {
669 MappingPrompt mp = new MappingPrompt(node.getFile());
670 mp.destroy();
671 }
672
673 // Delete shortcut
674 else if (source == delete_shortcut) {
675 workspace_tree.removeDirectoryMapping((WorkspaceTreeNode) node);
676 }
677
678 // Collapse folder
679 else if (source == collapse_folder) {
680 tree.collapsePath(selection_paths[0]);
681 }
682
683 // Expand folder
684 else if (source == expand_folder) {
685 tree.expandPath(selection_paths[0]);
686 }
687
688 // Explode metadata database
689 else if (source == explode_metadata_database) {
690 ExplodeMetadataPrompt emp = new ExplodeMetadataPrompt(node.getFile());
691 //emp.destroy();
692 }
693
694 // Delete
695 else if (source == delete) {
696 FileNode[] source_nodes = new FileNode[selection_paths.length];
697 for (int i = 0; i < selection_paths.length; i++) {
698 source_nodes[i] = (FileNode) selection_paths[i].getLastPathComponent();
699 }
700
701 // Fire a delete action
702 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
703 }
704
705 // Meta-audit
706 else if (source == metaaudit) {
707 Gatherer.g_man.showMetaAuditBox();
708 }
709
710 // New folder
711 else if (source == new_folder) {
712 Gatherer.f_man.newFolder(tree, (CollectionTreeNode) node);
713 }
714 // New dummy doc
715 else if (source == new_dummy_doc) {
716 Gatherer.f_man.newDummyDoc(tree, (CollectionTreeNode) node);
717 }
718 // Open in external program
719 else if (source == open_externally) {
720 Gatherer.self.spawnApplication(node.getFile());
721 }
722 }
723 }
724
725
726 /** This class listens for certain key presses, such as [Enter] or [Delete], and responds appropriately. */
727 private class KeyListenerImpl
728 extends KeyAdapter {
729 private boolean vk_left_pressed = false;
730 /** Called whenever a key that was pressed is released, it is this action that will cause the desired effects (this allows for the key event itself to be processed prior to this listener dealing with it). */
731 public void keyReleased(KeyEvent event) {
732 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
733 if(event.getKeyCode() == KeyEvent.VK_DELETE) {
734 // Get the selected files from the tree and removal them using the default dnd removal method.
735 // Find the active tree (you've made selections in).
736 DragTree tree = (DragTree) group.getActive();
737 // Fudge things a bit
738 group.setSource(tree);
739 // Determine the selection.
740 TreePath paths[] = tree.getSelectionPaths();
741 if(paths != null) {
742 FileNode[] source_nodes = new FileNode[paths.length];
743 for(int i = 0; i < source_nodes.length; i++) {
744 source_nodes[i] = (FileNode) paths[i].getLastPathComponent();
745 }
746 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
747 source_nodes = null;
748 }
749 }
750 else if(event.getKeyCode() == KeyEvent.VK_ENTER) {
751 // Get the first selected file.
752 DragTree tree = (DragTree)event.getSource();
753 TreePath path = tree.getSelectionPath();
754 if(path != null) {
755 File file = ((FileNode)path.getLastPathComponent()).getFile();
756 if(file != null && file.isFile()) {
757 Gatherer.self.spawnApplication(file);
758 }
759 else {
760 if(!tree.isExpanded(path)) {
761 tree.expandPath(path);
762 }
763 else {
764 tree.collapsePath(path);
765 }
766 }
767 }
768 } else if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
769 DragTree tree = (DragTree)event.getSource();
770 // we need to manually do the up and down selections
771 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
772 int current_row = tree.getLeadSelectionRow();
773 tree.setImmediate(true);
774 if (up) {
775 if (current_row > 0) {
776 tree.clearSelection();
777 tree.setSelectionRow(current_row-1);
778 }
779 } else {
780 if (current_row < tree.getRowCount()-1){
781 tree.clearSelection();
782 tree.setSelectionRow(current_row+1);
783 }
784 }
785 tree.setImmediate(false);
786 } else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
787 // left key on a file shifts the selection to the parent folder
788 DragTree tree = (DragTree)event.getSource();
789 TreePath path = tree.getLeadSelectionPath();
790 if(path != null) {
791 File file = ((FileNode)path.getLastPathComponent()).getFile();
792 if(file != null) {
793 if (file.isFile() || vk_left_pressed) {
794 vk_left_pressed = false;
795 TreePath parent_path = path.getParentPath();
796 if (parent_path != null && parent_path.getParentPath() != null) {
797 // if this file is in the top level folder, don't move the focus
798 tree.setImmediate(true);
799 tree.clearSelection();
800 tree.setSelectionPath(parent_path);
801 tree.setImmediate(false);
802 }
803 }
804 }
805 }
806 }
807 }
808
809 // we need to watch for left clicks on an unopened folder - should shift the focus to teh parent folder. But because there is some other mysterious key listener that does opening and closing folders on right and left clicks, we must detect the situation before the other handler has done its job, and process it after.
810 public void keyPressed(KeyEvent event) {
811 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
812 // left key on closed directory shifts the selection to the parent folder
813 DragTree tree = (DragTree)event.getSource();
814 TreePath path = tree.getLeadSelectionPath();
815 if(path == null) return;
816 File file = ((FileNode)path.getLastPathComponent()).getFile();
817 if(file == null) return;
818
819 if (file.isDirectory() && tree.isCollapsed(path)) {
820 vk_left_pressed = true;
821 }
822 }
823 }
824 }
825
826 /** This provides a small prompt for gathering addition details about a special directory mapping such as its symbolic name.
827 * Used when creating a new shortcut to a folder */
828 private class MappingPrompt
829 extends JDialog
830 implements ActionListener, KeyListener {
831 private boolean cancelled = false;
832 private JButton cancel_button = null;
833 private JButton ok_button = null;
834 private JTextField name_field = null;
835 public MappingPrompt(File file) {
836 super(Gatherer.g_man);
837 setModal(true);
838 setSize(DIALOG_SIZE);
839 Dictionary.setText(this, "MappingPrompt.Title");
840
841 // Creation
842 JPanel content_pane = (JPanel) getContentPane();
843 JPanel center_pane = new JPanel();
844 JPanel labels_pane = new JPanel();
845 JPanel fields_pane = new JPanel();
846
847 JLabel file_label = new JLabel();
848 Dictionary.setText(file_label, "MappingPrompt.File");
849 JLabel file_field = new JLabel(file.getAbsolutePath());
850
851 JLabel name_label = new JLabel();
852 Dictionary.setText(name_label, "MappingPrompt.Name");
853 name_field = new JTextField(file.getName());
854
855 JPanel button_pane = new JPanel();
856 ok_button = new GLIButton();
857 ok_button.setEnabled(name_field.getText().length() > 0);
858 ok_button.setMnemonic(KeyEvent.VK_O);
859 Dictionary.setBoth(ok_button, "General.OK", "General.OK_Tooltip");
860 cancel_button = new GLIButton();
861 cancel_button.setMnemonic(KeyEvent.VK_C);
862 Dictionary.setBoth(cancel_button, "General.Cancel", "General.Cancel_Tooltip");
863
864 // Connection
865 cancel_button.addActionListener(this);
866 ok_button.addActionListener(this);
867 name_field.addKeyListener(this);
868
869 // Layout
870 labels_pane.setLayout(new GridLayout(2,1,5,0));
871 labels_pane.add(file_label);
872 labels_pane.add(name_label);
873
874 fields_pane.setLayout(new GridLayout(2,1,5,0));
875 fields_pane.add(file_field);
876 fields_pane.add(name_field);
877
878 center_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
879 center_pane.setLayout(new BorderLayout(5,0));
880 center_pane.add(labels_pane, BorderLayout.WEST);
881 center_pane.add(fields_pane, BorderLayout.CENTER);
882
883 button_pane.setLayout(new GridLayout(1,2,5,5));
884 button_pane.add(ok_button);
885 button_pane.add(cancel_button);
886
887 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
888 content_pane.setLayout(new BorderLayout());
889 content_pane.add(center_pane, BorderLayout.CENTER);
890 content_pane.add(button_pane, BorderLayout.SOUTH);
891 // Display
892 Dimension screen_size = Configuration.screen_size;
893 setLocation((screen_size.width - DIALOG_SIZE.width) / 2, (screen_size.height - DIALOG_SIZE.height) / 2);
894 setVisible(true);
895 // If not cancelled create mapping.
896 if (!cancelled) {
897 workspace_tree.addDirectoryMapping(name_field.getText(), file);
898 }
899 }
900 public void actionPerformed(ActionEvent event) {
901 if(event.getSource() == cancel_button) {
902 cancelled = true;
903 }
904 dispose();
905 }
906 public void destroy() {
907 cancel_button = null;
908 ok_button = null;
909 name_field = null;
910 }
911 public void keyPressed(KeyEvent event) {
912 }
913 public void keyReleased(KeyEvent event) {
914 ok_button.setEnabled(name_field.getText().length() > 0);
915
916 }
917 public void keyTyped(KeyEvent event) {
918 }
919 }
920
921 /** This class listens for mouse clicks and responds right mouse button clicks (popup menu). */
922 private class MouseListenerImpl
923 extends MouseAdapter {
924 /** Any subclass of MouseAdapter can override this method to respond to mouse click events. In this case we want to open a pop-up menu if we detect a right mouse click over one of our registered components, and start an external application if someone double clicks on a certain file record. */
925 public void mouseClicked(MouseEvent event) {
926 if (SwingUtilities.isRightMouseButton(event)) {
927 new RightClickMenu((DragTree) event.getSource(), event);
928 }
929 }
930 }
931}
Note: See TracBrowser for help on using the repository browser.