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

Last change on this file since 7143 was 7143, checked in by kjdon, 20 years ago

added in a left click on a folder handler: if the folder is closed, then move the focus to the parent folder. Note that this was a bit trickier than expected because there is another mysterious handler that does left and right clicks to close and open folders. I needed to test for left click on a closed folder on the key pressed, but actually action it on the key release other wise the other handler comes into play and you get two actions happening on a single click.

  • Property svn:keywords set to Author Date Id Revision
File size: 37.3 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.Dictionary;
48import org.greenstone.gatherer.Gatherer;
49import org.greenstone.gatherer.file.FileNode;
50import org.greenstone.gatherer.file.FileOpenActionListener;
51import org.greenstone.gatherer.file.FileQueue;
52import org.greenstone.gatherer.file.FileSystemModel;
53import org.greenstone.gatherer.gui.Filter;
54import org.greenstone.gatherer.gui.GComboBox;
55import org.greenstone.gatherer.gui.GLIButton;
56import org.greenstone.gatherer.gui.tree.DragTree;
57import org.greenstone.gatherer.gui.tree.WorkspaceTree;
58import org.greenstone.gatherer.undo.UndoManager;
59import org.greenstone.gatherer.util.DragComponent;
60import org.greenstone.gatherer.util.DragGroup;
61import org.greenstone.gatherer.util.TreeSynchronizer;
62import org.greenstone.gatherer.util.Utility;
63
64/** 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.
65 * @author John Thompson, Greenstone Digital Library, University of Waikato
66 * @version 2.3
67 */
68public class GatherPane
69 extends JPanel
70 implements ActionListener, FocusListener {
71 /** 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. */
72 private DragGroup group = null;
73 /** The tree showing the files within the collection. */
74 private DragTree collection_tree = null;
75 /** The threaded queue that handles the actually movement of files, so that the gui remains responsive. */
76 private FileQueue file_queue = null;
77 /** The filter currently applied to the collection tree. */
78 private Filter collection_filter = null;
79 /** The filter currently applied to the workspace tree. */
80 private Filter workspace_filter = null;
81 /** The collection model which is used to build, and hold the data of, the collection tree. */
82 private TreeModel collection = null;
83 /** The Tree model used as the data source for the workspace tree. */
84 private TreeModel workspace = null;
85 /** The button used to cancel all pending file queue jobs. */
86 private JButton stop_action = null;
87 /** The button used to create a new folder in the collection tree. */
88 private JButton new_folder = null;
89 /** The label shown at the top of the collection tree. */
90 private JLabel collection_label = null;
91 /** The label shown in the status area explaining the file apon which action is taking place. */
92 private JLabel filename_label = null;
93 /** The label shown explaining the current state of the file queue thread. */
94 private JLabel status_label = null;
95 /** The label at the top of the workspace tree. */
96 private JLabel workspace_label = null;
97 /** The panel that contains the collection tree. */
98 private JPanel collection_pane = null;
99 /** The panel that contains the various controls including the status area. */
100 private JPanel control_pane = null;
101 /** The panel that contains the workspace tree. */
102 private JPanel workspace_pane = null;
103 /** The scrollable area into which the collection tree is placed. */
104 private JScrollPane collection_scroll = null;
105 /** The scrollable area into which the workspace tree is placed. */
106 private JScrollPane workspace_scroll = null;
107 /** A split pane seperating the two trees, allowing for the screen real-estate for each to be changed. */
108 private JSplitPane tree_pane = null;
109 /** Text fragment arguments used to fill in phrases returned from the dictionary. */
110 private String args[] = null;
111 /** Ensures that expansion and selection events between collection trees based on the same model are synchronized. */
112 private TreeSynchronizer collection_tree_sync = null;
113 /** Ensures that expansion and selection events between workspace trees based on the same model are synchronized. */
114 private TreeSynchronizer workspace_tree_sync = null;
115 /** The button used to delete files, which also doubles as a drop target for files from the Trees. */
116 private UndoManager bin_button = null;
117 /** The default size of a label in the interface. */
118 static final private Dimension LABEL_SIZE = new Dimension(100,30);
119 /** The default size of a special mapping dialog. */
120 static final Dimension DIALOG_SIZE = new Dimension(400, 120);
121 /** The minimum size a gui component can become. */
122 static private Dimension MIN_SIZE = new Dimension( 90, 90);
123 /** The default size of the status area. */
124 static private Dimension STATUS_SIZE = new Dimension(450, 120);
125 /** The initial size of the trees. */
126 static private Dimension TREE_SIZE = new Dimension(400, 430);
127
128 /** The tree showing the available source workspace. */
129 public WorkspaceTree workspace_tree = null;
130
131 /* Constructor.
132 * @param tree_sync Ensures that expansion events between like trees are synchronized.
133 * @see org.greenstone.gatherer.file.FileManager
134 * @see org.greenstone.gatherer.file.FileQueue
135 */
136 public GatherPane(TreeSynchronizer workspace_tree_sync, TreeSynchronizer collection_tree_sync) {
137 this.group = new DragGroup();
138 this.file_queue = Gatherer.f_man.getQueue();
139 this.collection_tree_sync = collection_tree_sync;
140 this.workspace_tree_sync = workspace_tree_sync;
141
142 // Create components.
143 stop_action = new GLIButton();
144 stop_action.addActionListener(this);
145 stop_action.setEnabled(false);
146 stop_action.setMnemonic(KeyEvent.VK_S);
147 file_queue.registerStopButton(stop_action);
148 Dictionary.registerBoth(stop_action, "Collection.Stop", "Collection.Stop_Tooltip");
149
150 new_folder = new GLIButton(Utility.getImage("folder.gif"));
151 new_folder.addActionListener(this);
152 new_folder.setEnabled(false);
153 new_folder.setMinimumSize(MIN_SIZE);
154 new_folder.setMnemonic(KeyEvent.VK_N);
155 new_folder.setPreferredSize(MIN_SIZE);
156 Dictionary.registerTooltip(new_folder, "Collection.New_Folder_Tooltip");
157 }
158
159 /** 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. */
160 public void actionPerformed(ActionEvent event) {
161 // If a user has clicked on the bin button directly remove whatever
162 // files are selected in the active tree.
163 if(event.getSource() == bin_button) {
164 if(!bin_button.ignore()) {
165 // Find the active tree (you've made selections in).
166 DragTree tree = (DragTree) group.getActive();
167 // Fudge things a bit
168 group.setSource(tree);
169 // Determine the selection.
170 TreePath paths[] = tree.getSelectionPaths();
171 if(paths != null) {
172 FileNode[] source_nodes = new FileNode[paths.length];
173 for(int i = 0; i < paths.length; i++) {
174 source_nodes[i] = (FileNode)(paths[i].getLastPathComponent());
175 }
176 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
177 }
178 }
179 }
180 // If a user has clicked on new_folder create a new folder under
181 // whatever node is selected.
182 else if(event.getSource() == new_folder && collection_tree != null) {
183 int count = collection_tree.getSelectionCount();
184 boolean error = false;
185 if(count == 1) {
186 TreePath path = collection_tree.getSelectionPath();
187 FileNode node = (FileNode) path.getLastPathComponent();
188 if(node.getAllowsChildren()) {
189 Gatherer.f_man.newFolder(collection_tree, node);
190 }
191 else {
192 // try the parent
193 FileNode parent = (FileNode)node.getParent();
194 if (parent!=null && parent.getAllowsChildren()) {
195 Gatherer.f_man.newFolder(collection_tree, parent);
196 } else {
197 error = true;
198 }
199 }
200 }
201 else {
202 error = true;
203 }
204 if(error) {
205 // instead of an error, we now create a new folder at the root
206 FileNode node = (FileNode) collection_tree.getModel().getRoot();
207 Gatherer.f_man.newFolder(collection_tree, node);
208 //JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("FileActions.No_Parent_For_New_Folder"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
209 }
210 }
211 else if(event.getSource() == stop_action) {
212 file_queue.cancelAction();
213 }
214 }
215
216 /** Called whenever a significant change occurs in the current collections state, such as a new collection being loaded or the current one being closed. Several actions must occur in the GUI to indicate this change to the user, such as en/disabling the collection tree.
217 * @param ready <i>true</i> if a collection is loaded and ready to be modified, <i>false</i> otherwise.
218 * @see org.greenstone.gatherer.Configuration
219 * @see org.greenstone.gatherer.Gatherer
220 * @see org.greenstone.gatherer.collection.CollectionManager
221 * @see org.greenstone.gatherer.gui.Coloring
222 * @see org.greenstone.gatherer.gui.Filter
223 * @see org.greenstone.gatherer.util.TreeSynchronizer
224 */
225 public void collectionChanged(boolean ready) {
226 // Try to retrieve the collections record set.
227 collection = Gatherer.c_man.getRecordSet();
228 if(collection != null) {
229 //args = new String[1];
230 //args[0] = Gatherer.c_man.getCollection().getName();
231 Dictionary.registerText(collection_label, "Collection.Collection");
232 collection_tree.setModel(collection);
233 collection_tree.repaint();
234 collection_filter.setBackground(Gatherer.config.getColor("coloring.collection_heading_background", false));
235 }
236 else {
237 String args[] = new String[1];
238 args[0] = Dictionary.get("Collection.No_Collection");
239 Dictionary.registerText(collection_label, "Collection.Collection", args);
240 args = null;
241 collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("Error")));
242 collection_filter.setBackground(Color.lightGray);
243 }
244 collection_tree.setEnabled(ready);
245 collection_filter.setEnabled(ready);
246 collection_filter.setEditable(Gatherer.config.getMode() > Configuration.LIBRARIAN_MODE);
247 Dictionary.registerTooltip(collection_filter.getComboBox(), "Collection.Filter_Tooltip");
248
249 // Change the label at the top of collection tree.
250 setEnabled(collection_label, ready, Gatherer.config.getColor("coloring.collection_heading_foreground", false), Gatherer.config.getColor("coloring.collection_heading_background", false));
251 // Ensure that this tree view of the collection record set is synchronized with any others.
252 collection_tree_sync.add(collection_tree);
253
254 refreshWorkspaceTree(DragTree.LOADED_COLLECTION_CHANGED);
255 refreshCollectionTree(DragTree.LOADED_COLLECTION_CHANGED);
256
257 // Enable or disable the control buttons
258 bin_button.setEnabled(ready);
259 new_folder.setEnabled(ready);
260 }
261
262 /** Generates the pane on controls used to 'collect' files into the collection. Resposible for creating, connecting and laying out these controls. */
263 public void display() {
264 // Create Components.
265 KeyListenerImpl key_listener = new KeyListenerImpl();
266 MouseListenerImpl mouse_listener = new MouseListenerImpl();
267 this.addKeyListener(key_listener);
268
269 // Workspace Tree
270 workspace_pane = new JPanel();
271 workspace_pane.setMinimumSize(MIN_SIZE);
272 workspace_pane.setPreferredSize(TREE_SIZE);
273 workspace_pane.setSize(TREE_SIZE);
274
275 workspace_label = new JLabel();
276 workspace_label.setOpaque(true);
277 workspace_label.setBackground(Gatherer.config.getColor("coloring.workspace_heading_background", false));
278 workspace_label.setForeground(Gatherer.config.getColor("coloring.workspace_heading_foreground", false));
279 Dictionary.registerText(workspace_label, "Collection.Workspace");
280
281 workspace_tree = new WorkspaceTree(Utility.WORKSPACE_TREE);
282 group.add(workspace_tree);
283 workspace_tree.addFocusListener(this);
284 workspace_tree.addKeyListener(key_listener);
285 workspace_tree.addMouseListener(mouse_listener);
286 workspace_tree.addMouseListener(Gatherer.g_man.foa_listener);
287 workspace_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
288 workspace_tree.addTreeSelectionListener(file_queue);
289 workspace_tree.putClientProperty("JTree.lineStyle", "Angled");
290 workspace_tree.setBackgroundNonSelectionColor(Gatherer.config.getColor("coloring.workspace_tree_background", false));
291 workspace_tree.setTextNonSelectionColor(Gatherer.config.getColor("coloring.workspace_tree_foreground", false));
292 workspace_tree.setBackgroundSelectionColor(Gatherer.config.getColor("coloring.workspace_selection_background", false));
293 workspace_tree.setTextSelectionColor(Gatherer.config.getColor("coloring.workspace_selection_foreground", false));
294 workspace_tree.setRootVisible(false);
295
296 workspace_scroll = new JScrollPane(workspace_tree);
297
298 workspace_filter = Gatherer.g_man.getFilter(workspace_tree);
299 workspace_filter.setBackground(Gatherer.config.getColor("coloring.workspace_heading_background", false));
300 workspace_filter.setEditable(Gatherer.config.getMode() > Configuration.LIBRARIAN_MODE);
301 Dictionary.registerTooltip(workspace_filter.getComboBox(), "Collection.Filter_Tooltip");
302
303 // Collection Tree
304 collection_pane = new JPanel();
305 collection_pane.setMinimumSize(MIN_SIZE);
306 collection_pane.setPreferredSize(TREE_SIZE);
307 collection_pane.setSize(TREE_SIZE);
308
309 //args = new String[1];
310 //args[0] = Dictionary.get("Collection.No_Collection");
311 collection_label = new JLabel();
312 collection_label.setOpaque(true);
313 Dictionary.registerText(collection_label, "Collection.No_Collection");
314
315 collection = Gatherer.c_man.getRecordSet();
316 if(collection != null) {
317 collection_tree = new DragTree(Utility.COLLECTION_TREE, collection, null, true);
318 collection_tree.setEnabled(true);
319 }
320 else {
321 collection_tree = new DragTree(Utility.COLLECTION_TREE, null, true);
322 collection_tree.setEnabled(false);
323 }
324 group.add(collection_tree);
325 collection_tree.addFocusListener(this);
326 collection_tree.addKeyListener(key_listener);
327 collection_tree.addMouseListener(mouse_listener);
328 collection_tree.addMouseListener(Gatherer.g_man.foa_listener);
329 collection_tree.addTreeSelectionListener(file_queue);
330 collection_tree.addTreeExpansionListener(Gatherer.g_man.foa_listener);
331 collection_tree.putClientProperty("JTree.lineStyle", "Angled");
332 collection_tree.setBackgroundNonSelectionColor(Gatherer.config.getColor("coloring.collection_tree_background", false));
333 collection_tree.setTextNonSelectionColor(Gatherer.config.getColor("coloring.collection_tree_foreground", false));
334 collection_tree.setBackgroundSelectionColor(Gatherer.config.getColor("coloring.collection_selection_background", false));
335 collection_tree.setTextSelectionColor(Gatherer.config.getColor("coloring.collection_selection_foreground", false));
336 collection_tree.setRootVisible(false);
337 //collection_tree.setRootVisible(true);
338
339 collection_scroll = new JScrollPane(collection_tree);
340
341 collection_filter = Gatherer.g_man.getFilter(collection_tree);
342 if(collection != null) {
343 collection_filter.setBackground(Gatherer.config.getColor("coloring.collection_heading_background", false));
344 }
345 else {
346 collection_filter.setBackground(Color.lightGray);
347 }
348 // Add a tool tip
349 Dictionary.registerTooltip(collection_filter.getComboBox(), "Collection.Filter_Tooltip");
350
351 tree_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
352
353 // Status pane
354 control_pane = new JPanel();
355
356 JPanel inner_pane = new JPanel();
357 inner_pane.setSize(STATUS_SIZE);
358
359 JPanel file_pane = new JPanel();
360 //file_pane.setBackground(Color.white);
361 //JPanel job_pane = new JPanel();
362 //job_pane.setBackground(Color.white);
363 JPanel progress_pane = new JPanel();
364 //progress_pane.setBackground(Color.white);
365 JLabel file_status = file_queue.getFileStatus();
366 //JLabel job_status = job_queue.getJobStatus();
367
368 JProgressBar progress = file_queue.getProgress();
369
370 JPanel button_pane = new JPanel();
371
372 bin_button = Gatherer.c_man.undo;
373 bin_button.addActionListener(this);
374 bin_button.setEnabled(false);
375 bin_button.setMinimumSize(MIN_SIZE);
376 bin_button.setPreferredSize(MIN_SIZE);
377 Dictionary.registerTooltip(bin_button, "Collection.Delete_Tooltip");
378 group.add(bin_button);
379
380 // Layout Components.
381 workspace_pane.setLayout(new BorderLayout());
382 workspace_pane.add(workspace_label, BorderLayout.NORTH);
383 workspace_pane.add(workspace_scroll, BorderLayout.CENTER);
384 workspace_pane.add(workspace_filter, BorderLayout.SOUTH);
385
386 collection_pane.setLayout(new BorderLayout());
387 collection_pane.add(collection_label, BorderLayout.NORTH);
388 collection_pane.add(collection_scroll, BorderLayout.CENTER);
389 collection_pane.add(collection_filter, BorderLayout.SOUTH);
390
391 tree_pane.add(workspace_pane, JSplitPane.LEFT);
392 tree_pane.add(collection_pane, JSplitPane.RIGHT);
393 tree_pane.setDividerLocation(TREE_SIZE.width - 10);
394
395 //job_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
396 //job_pane.setLayout(new BorderLayout());
397 //job_pane.add(job_status, BorderLayout.CENTER);
398
399 file_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
400 file_pane.setLayout(new BorderLayout());
401 file_pane.add(file_status, BorderLayout.CENTER);
402 file_pane.add(stop_action, BorderLayout.EAST);
403
404 progress_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
405 progress_pane.setLayout(new BorderLayout());
406 progress_pane.add(progress, BorderLayout.CENTER);
407
408 inner_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(10,10,10,10), BorderFactory.createLoweredBevelBorder()));
409 inner_pane.setLayout(new GridLayout(2,1));
410 //inner_pane.add(job_pane);
411 inner_pane.add(file_pane);
412 inner_pane.add(progress_pane);
413
414 button_pane.add(new_folder);
415 button_pane.add(bin_button);
416
417 control_pane.setLayout(new BorderLayout());
418 //control_pane.add(new_folder, BorderLayout.WEST);
419 control_pane.add(inner_pane, BorderLayout.CENTER);
420 control_pane.add(button_pane, BorderLayout.EAST);
421
422 this.setLayout(new BorderLayout());
423 this.add(tree_pane, BorderLayout.CENTER);
424 this.add(control_pane, BorderLayout.SOUTH);
425 }
426 /** 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. */
427 public Rectangle expandPath(TreePath path) {
428 collection_tree.setImmediate(true);
429 collection_tree.scrollPathToVisible(path);
430 collection_tree.setSelectionPath(path);
431 collection_tree.setImmediate(false);
432 return collection_tree.getRowBounds(collection_tree.getRowForPath(path));
433 }
434 /** 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).
435 * @param event A <strong>FocusEvent</strong> containing details about the focus action performed.
436 */
437 public void focusGained(FocusEvent event) {
438 DefaultTreeCellRenderer def = new DefaultTreeCellRenderer();
439 DefaultTreeCellRenderer w = (DefaultTreeCellRenderer)workspace_tree.getCellRenderer();
440 DefaultTreeCellRenderer c = (DefaultTreeCellRenderer)collection_tree.getCellRenderer();
441 if(event.getSource() == workspace_tree) {
442 w.setBackgroundSelectionColor(def.getBackgroundSelectionColor());
443 c.setBackgroundSelectionColor(Color.lightGray);
444 }
445 else if(event.getSource() == collection_tree) {
446 c.setBackgroundSelectionColor(def.getBackgroundSelectionColor());
447 w.setBackgroundSelectionColor(Color.lightGray);
448 }
449 repaint();
450 }
451 /** Implementation side-effect, not used in any way.
452 * @param event A <strong>FocusEvent</strong> containing details about the focus action performed.
453 */
454 public void focusLost(FocusEvent event) {
455 }
456
457 /** Called to inform this control panel that it has just gained focus as an effect of the user clicking on its tab.
458 * @see org.greenstone.gatherer.gui.tree.DragTree
459 */
460 public void gainFocus() {
461 // Update the menubar's idea of whats been selected
462 if (collection_tree != null) {
463 if (collection_tree.isSelectionEmpty()) {
464 Gatherer.g_man.menu_bar.setMetaAuditSuffix(null);
465 }
466 else {
467 Gatherer.g_man.menu_bar.setMetaAuditSuffix(collection_tree.getSelectionDetails());
468 }
469 }
470 // Update the meta-audit view to show the current selection, if any.
471 Gatherer.g_man.meta_audit.setRecords(getSelected());
472 }
473
474 /** Retrieve a list of the currently selected file records in the active tree. */
475 public FileNode[] getSelected() {
476 TreePath paths[] = collection_tree.getSelectionPaths();
477 FileNode records[] = null;
478 if(paths != null) {
479 records = new FileNode[paths.length];
480 for(int i = 0; i < records.length; i++) {
481 records[i] = (FileNode) paths[i].getLastPathComponent();
482 }
483 }
484 return records;
485 }
486
487 public String getSelectionDetails() {
488 return collection_tree.getSelectionDetails();
489 }
490
491 /** 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)
492 * @param mode the mode level as an int
493 */
494 public void modeChanged(int mode) {
495 collection_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
496 workspace_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
497 }
498
499 public void refreshCollectionTree(int refresh_reason) {
500 collection_tree.refresh(null);
501 }
502
503
504 public void refreshWorkspaceTree(int refresh_reason) {
505 workspace_tree.refresh(refresh_reason);
506 }
507
508
509 /** Used to set the enabled state, and hence the colouring, of the two tree labels.
510 * @param label The <strong>JLabel</strong> to be affected.
511 * @param state <i>true</i> for enabled, i.e. when a collection is ready, <i>false</i> otherwise.
512 * @param foreground The <strong>Color</strong> to make the foreground text of the label when enabled.
513 * @param background The <strong>Color</strong> to make the background of the label when enabled.
514 */
515 private void setEnabled(JLabel label, boolean state, Color foreground, Color background) {
516 ///ystem.err.println("Setting the label color to state " + state);
517 if(state) {
518 label.setBackground(background);
519 label.setForeground(foreground);
520 }
521 else {
522 label.setBackground(Color.lightGray);
523 label.setForeground(Color.black);
524 }
525 label.repaint();
526 ///ystem.err.println("Color is now " + label.getBackground());
527 }
528
529 /** When a user right-clicks within the trees on the collection pane view they are presented with a small popup menu of context based options. This class provides such functionality.
530 */
531 private class GPopupMenu
532 extends JPopupMenu
533 implements ActionListener {
534
535 /** The tree over which the right click action occured. */
536 private DragTree tree = null;
537 /** The file record over which the right click action occured, if any. */
538 private FileNode node = null;
539 private JMenuItem collapse_expand_folder_menuitem;
540 /** A menu item enabled if a delete action is appropriate in the menus current context. */
541 private JMenuItem delete = null;
542 /** A menu item enabled if a special directory mapping is appropriate in the menus current context. */
543 private JMenuItem map = null;
544 /** A menu item enabled if a new folder action is appropriate in the menus current context. */
545 private JMenuItem new_folder = null;
546 /** A menu item enabled if a show meta-audit dialog action is appropriate in the menus current context. */
547 private JMenuItem show_metaaudit = null;
548 /** A menu item allowing a user to unmap a special directory, if and only if a special directory is selected. */
549 private JMenuItem unmap = null;
550 private TreePath path = null;
551 /** Constructor. */
552 public GPopupMenu(DragTree tree, MouseEvent event) {
553 super();
554 this.tree = tree;
555 this.path = tree.getClosestPathForLocation(event.getX(), event.getY());
556 if(path != null) {
557 node = (FileNode)path.getLastPathComponent();
558 // Any folder node gets a menu item allowing you to collapse or expand it depending on its current status
559 if(!node.isLeaf()) {
560 // Collapse
561 if(tree.isExpanded(path)) {
562 collapse_expand_folder_menuitem = new JMenuItem(Dictionary.get("Menu.Collapse"), KeyEvent.VK_C);
563 }
564 // Expand
565 else {
566 collapse_expand_folder_menuitem = new JMenuItem(Dictionary.get("Menu.Expand"), KeyEvent.VK_O);
567 }
568 collapse_expand_folder_menuitem.addActionListener(this);
569 add(collapse_expand_folder_menuitem);
570 }
571 // Set Options based on selection and tree
572 if(tree.getSelectionCount() != 0 && tree == collection_tree) {
573 String[] args = new String[1];
574 args[0] = collection_tree.getSelectionDetails();
575 show_metaaudit = new JMenuItem(Dictionary.get("Menu.Metadata_View", args), KeyEvent.VK_V);
576 show_metaaudit.addActionListener(this);
577 add(show_metaaudit);
578 }
579 if(tree == collection_tree && node != null && node.getFile() != null && node.getFile().isDirectory() && !node.isReadOnly()) {
580 new_folder = new JMenuItem(Dictionary.get("CollectionPopupMenu.New_Folder"), KeyEvent.VK_N);
581 new_folder.addActionListener(this);
582 add(new_folder);
583 add(new JSeparator());
584 }
585 if(tree == collection_tree && tree.getSelectionCount() != 0 && node != null && !node.isReadOnly()) {
586 delete = new JMenuItem(Dictionary.get("CollectionPopupMenu.Delete"), KeyEvent.VK_D);
587 delete.addActionListener(this);
588 add(delete);
589 }
590 if(tree == workspace_tree && node != null && !node.isLeaf()) {
591 String node_name = node.toString();
592 FileNode root = (FileNode) tree.getModel().getRoot();
593 if(!node_name.equals(Dictionary.get("Tree.World")) && !node_name.equals(Dictionary.get("Tree.Root")) && !node_name.equals(Dictionary.get("Tree.Public")) && !node_name.equals(Dictionary.get("Tree.Private"))) {
594 // You can unmap 1st level nodes.
595 if(root.getIndex(node) != -1) {
596 unmap = new JMenuItem(Dictionary.get("MappingPrompt.Unmap"), KeyEvent.VK_U);
597 unmap.addActionListener(this);
598 add(unmap);
599 }
600 // Or map any other level directories.
601 else {
602 map = new JMenuItem(Dictionary.get("MappingPrompt.Map"), KeyEvent.VK_M);
603 map.addActionListener(this);
604 add(map);
605 }
606 }
607 }
608 show(tree, event.getX(), event.getY());
609 }
610 }
611
612 /** Called whenever one of the menu items is actioned apon, this method then causes the appropriate effect. */
613 public void actionPerformed(ActionEvent event) {
614 Object source = event.getSource();
615 if(source == delete) {
616 // Retrieve the selection. Of course this gets a bit tricky as the user may have right clicked over a node not in the current selection, in which case we remove only that node.
617 TreePath[] selection_paths = tree.getSelectionPaths();
618 if(selection_paths != null) {
619 FileNode[] source_nodes = new FileNode[selection_paths.length];
620 boolean found = false;
621 for(int i = 0; i < selection_paths.length; i++) {
622 source_nodes[i] = (FileNode) selection_paths[i].getLastPathComponent();
623 if(node != null) {
624 found = found || source_nodes[i].equals(node);
625 }
626 }
627 if(node != null && !found) {
628 source_nodes = null;
629 source_nodes = new FileNode[1];
630 source_nodes[0] = node;
631 }
632 // Fire a delete action
633 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
634 source_nodes = null;
635 }
636 selection_paths = null;
637 }
638 else if(source == map && node != null) {
639 MappingPrompt mp = new MappingPrompt(node.getFile());
640 mp.destroy();
641 mp = null;
642 }
643 else if(source == new_folder && node != null) {
644 Gatherer.f_man.newFolder(tree, node);
645 }
646 else if(source == show_metaaudit) {
647 Gatherer.g_man.showMetaAuditBox();
648 }
649 else if(source == unmap && node != null) {
650 Gatherer.c_man.removeDirectoryMapping(node);
651 }
652 else if(source == collapse_expand_folder_menuitem && path != null && node != null && !node.isLeaf()) {
653 // Collapse
654 if(tree.isExpanded(path)) {
655 tree.collapsePath(path);
656 }
657 // Expand
658 else {
659 tree.expandPath(path);
660 }
661 }
662 }
663 }
664
665 /** This class listens for certain key presses, such as [Enter] or [Delete], and responds appropriately. */
666 private class KeyListenerImpl
667 extends KeyAdapter {
668 private boolean vk_left_pressed = false;
669 /** 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). */
670 public void keyReleased(KeyEvent event) {
671 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
672 if(event.getKeyCode() == KeyEvent.VK_DELETE) {
673 // Get the selected files from the tree and removal them using the default dnd removal method.
674 // Find the active tree (you've made selections in).
675 DragTree tree = (DragTree) group.getActive();
676 // Fudge things a bit
677 group.setSource(tree);
678 // Determine the selection.
679 TreePath paths[] = tree.getSelectionPaths();
680 if(paths != null) {
681 FileNode[] source_nodes = new FileNode[paths.length];
682 for(int i = 0; i < source_nodes.length; i++) {
683 source_nodes[i] = (FileNode) paths[i].getLastPathComponent();
684 }
685 Gatherer.f_man.action(tree, source_nodes, bin_button, null);
686 source_nodes = null;
687 }
688 }
689 else if(event.getKeyCode() == KeyEvent.VK_ENTER) {
690 // Get the first selected file.
691 DragTree tree = (DragTree)event.getSource();
692 TreePath path = tree.getSelectionPath();
693 if(path != null) {
694 File file = ((FileNode)path.getLastPathComponent()).getFile();
695 if(file != null && file.isFile()) {
696 Gatherer.self.spawnApplication(file);
697 }
698 else {
699 if(!tree.isExpanded(path)) {
700 tree.expandPath(path);
701 }
702 else {
703 tree.collapsePath(path);
704 }
705 }
706 }
707 } else if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
708 DragTree tree = (DragTree)event.getSource();
709 // we need to manually do the up and down selections
710 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
711 int current_row = tree.getLeadSelectionRow();
712 tree.setImmediate(true);
713 if (up) {
714 if (current_row > 0) {
715 tree.clearSelection();
716 tree.setSelectionRow(current_row-1);
717 }
718 } else {
719 if (current_row < tree.getRowCount()-1){
720 tree.clearSelection();
721 tree.setSelectionRow(current_row+1);
722 }
723 }
724 tree.setImmediate(false);
725 } else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
726 // left key on a file shifts the selection to the parent folder
727 DragTree tree = (DragTree)event.getSource();
728 TreePath path = tree.getLeadSelectionPath();
729 if(path != null) {
730 File file = ((FileNode)path.getLastPathComponent()).getFile();
731 if(file != null) {
732 if (file.isFile() || vk_left_pressed) {
733 vk_left_pressed = false;
734 TreePath parent_path = path.getParentPath();
735 if (parent_path != null && parent_path.getParentPath() != null) {
736 // if this file is in the top level folder, don't move the focus
737 tree.setImmediate(true);
738 tree.clearSelection();
739 tree.setSelectionPath(parent_path);
740 tree.setImmediate(false);
741 }
742 }
743 }
744 }
745 }
746 }
747
748 // 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.
749 public void keyPressed(KeyEvent event) {
750 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
751 // left key on closed directory shifts the selection to the parent folder
752 DragTree tree = (DragTree)event.getSource();
753 TreePath path = tree.getLeadSelectionPath();
754 if(path == null) return;
755 File file = ((FileNode)path.getLastPathComponent()).getFile();
756 if(file == null) return;
757
758 if (file.isDirectory() && tree.isCollapsed(path)) {
759 vk_left_pressed = true;
760 }
761 }
762 }
763 }
764
765 /** This provides a small prompt for gathering addition details about a special directory mapping such as its symbolic name. */
766 private class MappingPrompt
767 extends JDialog
768 implements ActionListener, KeyListener {
769 private boolean cancelled = false;
770 private JButton cancel_button = null;
771 private JButton ok_button = null;
772 private JTextField name_field = null;
773 public MappingPrompt(File file) {
774 super(Gatherer.g_man);
775 setModal(true);
776 setSize(DIALOG_SIZE);
777 Dictionary.setText(this, "MappingPrompt.Title");
778
779 // Creation
780 JPanel content_pane = (JPanel) getContentPane();
781 JPanel center_pane = new JPanel();
782 JPanel file_pane = new JPanel();
783 JLabel file_label = new JLabel();
784 file_label.setPreferredSize(LABEL_SIZE);
785 Dictionary.setText(file_label, "MappingPrompt.File");
786 JLabel file_field = new JLabel(file.getAbsolutePath());
787 JPanel name_pane = new JPanel();
788 JLabel name_label = new JLabel();
789 name_label.setPreferredSize(LABEL_SIZE);
790 Dictionary.setText(name_label, "MappingPrompt.Name");
791 name_field = new JTextField(file.getName());
792 JPanel button_pane = new JPanel();
793 ok_button = new GLIButton();
794 ok_button.setEnabled(name_field.getText().length() > 0);
795 ok_button.setMnemonic(KeyEvent.VK_O);
796 Dictionary.setBoth(ok_button, "General.OK", "General.OK_Tooltip");
797 cancel_button = new GLIButton();
798 cancel_button.setMnemonic(KeyEvent.VK_C);
799 Dictionary.setBoth(cancel_button, "General.Cancel", "General.Cancel_Tooltip");
800
801 // Connection
802 cancel_button.addActionListener(this);
803 ok_button.addActionListener(this);
804 name_field.addKeyListener(this);
805 // Layout
806 file_pane.setLayout(new BorderLayout());
807 file_pane.add(file_label, BorderLayout.WEST);
808 file_pane.add(file_field, BorderLayout.CENTER);
809
810 name_pane.setLayout(new BorderLayout());
811 name_pane.add(name_label, BorderLayout.WEST);
812 name_pane.add(name_field, BorderLayout.CENTER);
813
814 center_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
815 center_pane.setLayout(new GridLayout(2,1,5,5));
816 center_pane.add(file_pane);
817 center_pane.add(name_pane);
818
819 button_pane.setLayout(new GridLayout(1,2,5,5));
820 button_pane.add(ok_button);
821 button_pane.add(cancel_button);
822
823 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
824 content_pane.setLayout(new BorderLayout());
825 content_pane.add(center_pane, BorderLayout.CENTER);
826 content_pane.add(button_pane, BorderLayout.SOUTH);
827 // Display
828 Dimension screen_size = Gatherer.config.screen_size;
829 setLocation((screen_size.width - DIALOG_SIZE.width) / 2, (screen_size.height - DIALOG_SIZE.height) / 2);
830 show();
831 // If not cancelled create mapping.
832 if(!cancelled) {
833 Gatherer.c_man.addDirectoryMapping(name_field.getText(), file);
834 }
835 }
836 public void actionPerformed(ActionEvent event) {
837 if(event.getSource() == cancel_button) {
838 cancelled = true;
839 }
840 dispose();
841 }
842 public void destroy() {
843 cancel_button = null;
844 ok_button = null;
845 name_field = null;
846 }
847 public void keyPressed(KeyEvent event) {
848 }
849 public void keyReleased(KeyEvent event) {
850 ok_button.setEnabled(name_field.getText().length() > 0);
851
852 }
853 public void keyTyped(KeyEvent event) {
854 }
855 }
856
857 /** This class listens for mouse clicks and responds right mouse button clicks (popup menu). */
858 private class MouseListenerImpl
859 extends MouseAdapter {
860 /** 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. */
861 public void mouseClicked(MouseEvent event) {
862 if(SwingUtilities.isRightMouseButton(event)) {
863 new GPopupMenu((DragTree)event.getSource(), event);
864 }
865 }
866 }
867}
Note: See TracBrowser for help on using the repository browser.