source: trunk/gli/src/org/greenstone/gatherer/gui/table/GTableModel.java@ 4350

Last change on this file since 4350 was 4350, checked in by mdewsnip, 21 years ago

Added check for null builder in setSelectedMetadata().

  • Property svn:keywords set to Author Date Id Revision
File size: 24.9 KB
Line 
1package org.greenstone.gatherer.gui.table;
2/**
3 *#########################################################################
4 *
5 * A component of the Gatherer application, part of the Greenstone digital
6 * library suite from the New Zealand Digital Library Project at the
7 * University of Waikato, New Zealand.
8 *
9 * Author: John Thompson, Greenstone Digital Library, University of Waikato
10 *
11 * Copyright (C) 1999 New Zealand Digital Library Project
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 *########################################################################
27 */
28import java.awt.*;
29import java.io.File;
30import java.util.*;
31import javax.swing.*;
32import javax.swing.table.*;
33import org.greenstone.gatherer.Gatherer;
34import org.greenstone.gatherer.file.FileNode;
35import org.greenstone.gatherer.gui.WarningDialog;
36import org.greenstone.gatherer.msm.ElementWrapper;
37import org.greenstone.gatherer.msm.Metadata;
38import org.greenstone.gatherer.msm.MSMAction;
39import org.greenstone.gatherer.msm.MSMEvent;
40import org.greenstone.gatherer.msm.MSMListener;
41import org.greenstone.gatherer.msm.MSMUtils;
42import org.greenstone.gatherer.util.ArrayTools;
43/** Provides the model for a GTable component, filling it with metadata values for the choosen files or folders. The model also provides several different view of this data; assigned folder metadata, assigned file metadata, all assigned metadata, unassigned metadata, and all metadata. It also differentiates between metadata that is common to all of the files or folders, and that which isn't. The building of the actual model is done on a separate thread so that the gui remains responsive, and the gui is intermitantly updated by this thread. Updating of the model is triggered by events recieved from the metadata set manager in terms of new or obsolete metadata. A new model is rebuilt whenever the user selects a different group of files or folders.
44 * @author John Thompson, Greenstone Digital Library, University of Waikato
45 * @version 2.3b
46 */
47public class GTableModel
48 extends AbstractTableModel
49 implements MSMListener {
50 /** The source for the data currently shown in the table. May be any of the other three metadata lists, or the union of all of them. */
51 private ArrayList current_metadata = new ArrayList();
52 /** The metadata currently assigned at the file level. */
53 private ArrayList file_metadata = null; // new ArrayList();
54 /** The metadata currently assigned at the folder level. */
55 private ArrayList inherited_metadata = null; // new ArrayList();
56 /** The metadata currently unassigned. */
57 private ArrayList unassigned_metadata = null; // new ArrayList();
58 /** The file nodes this model is built upon. */
59 private FileNode[] file_nodes;
60 /** The current view. */
61 //private int current_view;
62
63 //private JToggleButton assigned_metadata_view;
64 //private JToggleButton unassigned_metadata_view;
65
66 /** An icon showing the current state of the table build. */
67 //private JProgressBar activity_bar;
68 /** The table this model is being displayed in. */
69 private JTable table;
70
71 private ModelBuilder builder;
72
73 static final public int SHOW_ALL = 0;
74 static final public int SHOW_ASSIGNED = 1;
75 static final public int SHOW_FILE = 2;
76 static final public int SHOW_INHERITED = 3;
77 static final public int SHOW_UNASSIGNED = 4;
78 static final private String[] COLUMN_NAMES = {"", Gatherer.dictionary.get("Metadata.Element"), Gatherer.dictionary.get("Metadata.Value")};
79
80 public GTableModel(JTable table) {
81 this.builder = null;
82 this.file_metadata = current_metadata;
83 this.inherited_metadata = current_metadata;
84 this.table = table;
85 this.unassigned_metadata = current_metadata;
86 //changeView();
87 // Register this model with the msm manager.
88 if(Gatherer.c_man.ready()) {
89 Gatherer.c_man.getCollection().msm.addMSMListener(this);
90 }
91 }
92
93 public GTableModel(JTable table, FileNode[] file_nodes) {
94 this(table);
95 this.file_nodes = file_nodes;
96 if(file_nodes != null && file_nodes.length > 0) {
97 // Create model builder
98 builder = new ModelBuilder();
99 builder.start();
100 }
101 }
102
103 /** The default constructor creates a new empty model with the current view set to an emtpy 'all metadata'. */
104 /*
105 public GTableModel(JTable table, JToggleButton assigned_metadata_view, JToggleButton unassigned_metadata_view, JProgressBar activity_bar) {
106 this.activity_bar = activity_bar;
107 this.assigned_metadata_view = assigned_metadata_view;
108 this.builder = null;
109 this.table = table;
110 this.unassigned_metadata_view = unassigned_metadata_view;
111 changeView();
112 // Register this model with the msm manager.
113 if(Gatherer.c_man.ready()) {
114 Gatherer.c_man.getCollection().msm.addMSMListener(this);
115 }
116 }
117 */
118
119 /** This constuctor starts by creating an empty model then, using a separate model builder thread, builds a model from the given files. */
120 /*
121 public GTableModel(JTable table, JToggleButton assigned_metadata_view, JToggleButton unassigned_metadata_view, JProgressBar activity_bar, FileNode[] file_nodes) {
122 this(table, assigned_metadata_view, unassigned_metadata_view, activity_bar);
123 this.file_nodes = file_nodes;
124 if(file_nodes != null && file_nodes.length > 0) {
125 // Create model builder
126 builder = new ModelBuilder();
127 builder.start();
128 }
129 }
130 */
131
132 public void changeView() {
133 changeView(SHOW_ALL);
134 }
135
136 /*
137 public void changeView() {
138 boolean show_assigned = assigned_metadata_view.isSelected();
139 boolean show_unassigned = unassigned_metadata_view.isSelected();
140 if(show_assigned && show_unassigned) {
141 changeView(SHOW_ALL);
142 }
143 else if(show_assigned) {
144 changeView(SHOW_ASSIGNED);
145 }
146 else { // Show unassigned
147 changeView(SHOW_UNASSIGNED);
148 }
149 }
150 */
151
152 /** Change the current view by changing the model that the table is currently based on. */
153 public void changeView(int view) {
154 //current_view = view;
155 /*
156 switch(view) {
157 case SHOW_ASSIGNED:
158 ///ystem.err.println("Show assigned");
159 current_metadata = new ArrayList();
160 current_metadata.addAll(file_metadata);
161 current_metadata.addAll(inherited_metadata);
162 Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR);
163 break;
164 case SHOW_FILE:
165 ///ystem.err.println("Show file");
166 current_metadata = file_metadata;
167 break;
168 case SHOW_INHERITED:
169 ///ystem.err.println("Show inherited");
170 current_metadata = inherited_metadata;
171 break;
172 case SHOW_UNASSIGNED:
173 ///ystem.err.println("Show unassigned");
174 current_metadata = unassigned_metadata;
175 break;
176 default:
177 // SHOW_ALL
178 System.err.println("Show all");
179 // Create a new array from the three other arrays.
180 current_metadata = new ArrayList();
181 current_metadata.addAll(file_metadata);
182 current_metadata.addAll(inherited_metadata);
183 current_metadata.addAll(unassigned_metadata);
184 Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR);
185 }
186 // Inform everyone that the model has changed.
187 fireTableDataChanged();
188 */
189 }
190
191 /** Called whenever an element in a set is added, edited or removed from the metadata set manager. We listen for this just incase the name of one of the elements currently shown changes. After that the table will be up to date, as the element references are otherwise live. */
192 public void elementChanged(MSMEvent event) {
193 MSMAction profile = event.getProfile();
194 if(profile != null) {
195 // Retrieve the elements new name (thats hopefully what will be returned by getValue()).
196 String name = profile.getTarget();
197 int row = 0;
198 for( ; row < current_metadata.size(); row++) {
199 if(getValueAt(row, 0).equals(name)) {
200 fireTableCellUpdated(row, 0);
201 }
202 }
203 name = null;
204 }
205 profile = null;
206 event = null;
207 }
208
209 /** Returns the number of columns in this table. */
210 public int getColumnCount() {
211 return COLUMN_NAMES.length;
212 }
213
214 /** Returns the data class of the specified column. */
215 public Class getColumnClass(int col) {
216 if(col == 2) {
217 return JButton.class;
218 }
219 return String.class;
220 }
221
222 /** Retrieves the name of the specified column. */
223 public String getColumnName(int col) {
224 return COLUMN_NAMES[col];
225 }
226
227 /* Called to retrieve the Metadata record that matches a certain row. Usually caused by the user selectiong a row in the table. It synchronized so that the model doesn't up and change while we're trying to retrieve the indicated element. */
228 public synchronized Metadata getMetadataAtRow(int row) {
229 if(0 <= row && row < current_metadata.size()) {
230 return (Metadata) current_metadata.get(row);
231 }
232 return null;
233 }
234
235 /** Returns the number of rows in this table. */
236 public int getRowCount() {
237 return current_metadata.size();
238 }
239
240 /** Returns the cell value at a given row and column as an Object. */
241 public Object getValueAt(int row, int col) {
242 Object result = "";
243 if(0 <= row && row < current_metadata.size()) {
244 Metadata m = (Metadata) current_metadata.get(row);
245 if(0 <= col && col < COLUMN_NAMES.length) {
246 switch(col) {
247 case 0:
248 if(!m.isFileLevel()) {
249 result = m.getFile();
250 }
251 break;
252 case 1:
253 ElementWrapper element = m.getElement();
254 if(element != null) {
255 result = element.toString();
256 }
257 break;
258 case 2:
259 result = m.getValue();
260 break;
261 case 3:
262 result = (m.isFileLevel() ? "true" : "false");
263 break;
264 }
265 }
266 }
267 return result;
268 }
269
270 /** Allows access to this models current view. */
271 public int getView() {
272 return SHOW_ALL; //current_view;
273 }
274
275 /** Determine if the given metadata is common to all selected file nodes given the context of the current view. */
276 public boolean isCommon(int row) {
277 if(0 <= row && row < current_metadata.size()) {
278 Metadata entry = (Metadata) current_metadata.get(row);
279 ///ystem.err.println("There are " + file_nodes.length + " selected files.");
280 ///ystem.err.println("This metadata is attached to " + entry.getCount() + " of them.");
281 if(entry.getCount() == file_nodes.length) {
282 return true;
283 }
284 }
285 return false;
286 }
287
288 /** Whenever there is a metadata change we must determine if there are any changes to our various lists of metadata, and also if there is any need to refresh the table currently being viewed. */
289 public void metadataChanged(MSMEvent event) {
290 FileNode file_node = event.getRecord();
291 ///ystem.err.println("File node effected");
292 // First check whether this record is one of those that we have showing.
293 if(file_nodes != null && file_node != null && ArrayTools.contains(file_nodes, file_node)) {
294 // Brute force solution... rebuild the table.
295 if(file_nodes != null && file_nodes.length > 0) {
296 current_metadata.clear();
297 // Create model builder
298 builder = new ModelBuilder();
299 builder.start();
300 }
301
302 /*
303 // If old metadata has been removed then remove it. Update the unassigned list if necessary (first checking if there are other metadata entries with the same element, and furthermore if the were inherited metadata being supressed).
304 if(old_data != null) {
305 System.err.println("Remove: " + old_data);
306 // Determine the level of this metadata
307 ArrayList modified_metadata = null;
308 if(old_data.isFileLevel()) {
309 modified_metadata = file_metadata;
310 }
311 else {
312 modified_metadata = inherited_metadata;
313 }
314
315 int index = -1;
316 if((index = modified_metadata.indexOf(old_data)) != -1) {
317 Metadata data = (Metadata) modified_metadata.get(index);
318 data.dec();
319 // If we just removed the last occurance of this metadata, delete it
320 if(data.getCount() == 0) {
321 remove(modified_metadata, data);
322 // If there are no further occurances of the element of this entry, add entry to unassigned_metadata.
323 boolean found = false;
324 ElementWrapper element = data.getElement();
325 // Search the current metadata
326 for(int i = 0; i < modified_metadata.size(); i++) {
327 Metadata sibling = (Metadata) modified_metadata.get(i);
328 if(sibling.getElement().equals(element)) {
329 found = true;
330 }
331 }
332 // And the inherited metadata if we haven't already
333 if(modified_metadata != inherited_metadata) {
334 for(int i = 0; !found && i < inherited_metadata.size(); i++) {
335 Metadata sibling = (Metadata) inherited_metadata.get(i);
336 if(sibling.getElement().equals(element)) {
337 found = true;
338 }
339 }
340 }
341 // If we get this far and still haven't found a match then we do something very tricky. We retrieve all of the metadata for the parent file and notice any inherited nodes that were previously overwritten by the metadata entry we removed. The reason we use the parent is that we can't just assume that the GDMDocument changes occured before this method was called. Thus our about-to-be-removed overwritting metadata may still be in effect if we just called getMetadata for this file.
342 if(!found) {
343 ArrayList parent_metadatum = Gatherer.c_man.getCollection().gdm.getMetadata((file_node.getFile()).getParentFile());
344 // For each piece of parent metadata
345 for(int i = parent_metadatum.size(); i > 0; i--) {
346 Metadata parent_metadata = (Metadata) parent_metadatum.get(i - 1);
347 // If we found some previously suppressed metadata then add it and say we found something.
348 if(!inherited_metadata.contains(parent_metadata)) {
349 ///ystem.err.println("Restoring overwritten inherited metadata: " + parent_metadata);
350 found = true;
351 parent_metadata.setCount(1);
352 parent_metadata.setFileLevel(false); // Has to be folder level doesn't it.
353 add(inherited_metadata, parent_metadata);
354 }
355 }
356 // Run this getMetadata again to restore all of the file level and source file entries (they would have changed to reflect the parents call.
357 Gatherer.c_man.getCollection().gdm.getMetadata(file_node.getFile());
358 }
359 // And only then, if we haven't found a substitute, may we add a null metadata.
360 if(!found) {
361 add(unassigned_metadata, new Metadata(data.getElement()));
362 }
363 }
364 }
365 }
366 // If new metadata has been assigned, remove its entry from the unassigned metadata list. Furthermore add the new entry to either the file or folder metadata list depending on its level. Remember to rebuild the common metadata lists.
367 if(new_data != null) {
368 ///ystem.err.println("Add: " + new_data);
369 // Create dummy 'unassigned' metadata from the new metadata and remove it from the unassigned list (if its even there)
370 remove(unassigned_metadata, new Metadata(new_data.getElement()));
371 // Determine if we are adding the new node at the file or folder level.
372 ArrayList modified_metadata = null;
373 if(new_data.isFileLevel()) {
374 modified_metadata = file_metadata;
375 // If its file metadata, and its set to overwrite, then we need to remove any row it would overwrite.
376 if(new_data.accumulates()) {
377 // All good.
378 }
379 else {
380 // Remove any entries in the inherited_metadata that would be overwritten
381 ElementWrapper element = new_data.getElement();
382 for(int i = 0; i < inherited_metadata.size(); i++) {
383 Metadata sibling = (Metadata) inherited_metadata.get(i);
384 if(sibling.getElement().equals(element)) {
385 inherited_metadata.remove(sibling);
386 }
387 }
388 }
389 }
390 else {
391 modified_metadata = inherited_metadata;
392 }
393 // And add it.
394 int index = -1;
395 if((index = modified_metadata.indexOf(new_data)) == -1) {
396 add(modified_metadata, new_data);
397 new_data.setCount(1);
398 }
399 else {
400 Metadata data = (Metadata) modified_metadata.get(index);
401 data.inc();
402 data = null;
403 }
404 modified_metadata = null;
405 }
406 // As a current visible table is most likely affected, update the table component. We do this by calling changeView so as to also update any compound list.
407 //changeView(current_view);
408 */
409 }
410 else {
411 ///ystem.err.println("FileNodes = " + file_nodes);
412 ///ystem.err.println("FileNode = " + file_node);
413 }
414 }
415
416 /** Called whenever a set is added or removed from the metadata set manager. */
417 public void setChanged(MSMEvent event) {
418 // No, don't care. Still don't care. Couldn't care less.
419 }
420
421 public Rectangle setSelectedMetadata(Metadata data) {
422 Rectangle bounds = null;
423 if (builder != null) {
424 if(builder.complete) {
425 for(int i = 0; i < getRowCount(); i++) {
426 if(getMetadataAtRow(i).equals(data)) {
427 ///ystem.err.println("Found matching metadata at row " + i + " (of " + getRowCount() + " rows)");
428 ///ystem.err.println("Table shows " + table.getRowCount() + " rows!");
429 bounds = table.getCellRect(i, 0, true);
430 table.scrollRectToVisible(bounds);
431 table.setRowSelectionInterval(i, i);
432 }
433 }
434 }
435 else {
436 builder.selected_metadata = data;
437 }
438 }
439 return bounds;
440 }
441
442 public int size() {
443 return file_nodes.length;
444 }
445
446 /** Called when the value tree of a certain element has changed significantly but, although we display metadata values, we only care about those coming through metadataChanged() events. */
447 public void valueChanged(MSMEvent event) {
448 // Don't care. Tra-la-la-la-la.
449 }
450
451 /** Alphabetically inserts the new_metadata in the target vector. */
452 private int add(ArrayList target, Metadata new_metadata) {
453 int i = 0;
454 while(i < target.size()) {
455 Metadata current = (Metadata)target.get(i);
456 if(current.compareTo(new_metadata) > 0) {
457 target.add(i, new_metadata);
458 return i;
459 }
460 i++;
461 }
462 target.add(new_metadata);
463 return i;
464 }
465
466 /** Remove a certain piece of data from a certain vector. */
467 private void remove(ArrayList target, Metadata old_metadata) {
468 if(target != null) {
469 for(int i = 0; i < target.size(); i++) {
470 Metadata current_metadata = (Metadata) target.get(i);
471 if(current_metadata.equals(old_metadata)) {
472 target.remove(current_metadata);
473 return;
474 }
475 }
476 }
477 }
478
479 private class ModelBuilder
480 extends Thread {
481
482 public boolean had_warning = false;
483
484 public boolean complete = false;
485
486 public Metadata selected_metadata = null;;
487
488 public void run() {
489 Vector elements = Gatherer.c_man.getCollection().msm.getElements();
490 // Show some visual indication that building is occuring.
491 //assigned_metadata_view.setEnabled(false);
492 //unassigned_metadata_view.setEnabled(false);
493 //activity_bar.setMaximum(file_nodes.length + elements.size());
494 //activity_bar.setValue(0);
495 //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Building"));
496 //long start = System.currentTimeMillis();
497
498 ArrayList known_elements = new ArrayList(); // Elements that have metadata assigned.
499 for(int i = 0; i < file_nodes.length; i++) {
500 File current_file = file_nodes[i].getFile();
501 // The current assigned metadata for this file.
502 ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getAllMetadata(current_file);
503 for(int j = 0; j < metadatum.size(); j++) {
504 Metadata data = (Metadata) metadatum.get(j);
505 // Determine the target list by the metadata level
506 ArrayList modified_metadata;
507 if(data.isFileLevel()) {
508 modified_metadata = file_metadata;
509 }
510 else {
511 showInheritedMetadataWarning();
512 modified_metadata = inherited_metadata;
513 }
514 int index = -1;
515 // Don't show hidden metadata
516 // If the given metadata already exists in our list of modified metadata then increment its commonality count.
517 if((index = modified_metadata.indexOf(data)) != -1) {
518 data = (Metadata) modified_metadata.get(index);
519 ///ystem.err.println("Increasing count:" + element);
520 data.inc();
521 // If the table shown is stale, refresh it.
522 //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) {
523 // fireTableRowsUpdated(index, index);
524 //}
525 // We may have to update a compound list
526 //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) {
527 if((index = current_metadata.indexOf(data)) == -1) {
528 fireTableRowsUpdated(index, index);
529 }
530 //}
531 }
532 // Ensure the metadata's element is in our list of showable elements.
533 else if(contains(elements, data.getElement())) {
534 ///ystem.err.println("Setting count to one: " + element);
535 data.setCount(1);
536 index = add(modified_metadata, data); // Add to assigned metadata.
537 known_elements.add(data.getElement());
538 // If the table shown is stale, refresh it.
539 //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) {
540 // fireTableRowsInserted(index, index);
541 //}
542 // We may have to update a compound list
543 //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) {
544 if((index = current_metadata.indexOf(data)) == -1) {
545 index = add(current_metadata, data);
546 fireTableRowsInserted(index, index);
547 }
548 else {
549 fireTableRowsUpdated(index, index);
550 }
551 //}
552 }
553 else {
554 ///ystem.err.println("No.\n***** Cannot match element so it must be hidden, right? *****");
555 }
556 }
557 //activity_bar.setValue(activity_bar.getValue() + 1);
558 Gatherer.g_man.metaedit_pane.validateMetadataTable();
559 }
560 // Add entries for the currently unassigned metadata. You can determine these as the difference between elements and known_elements.
561 for(int k = 0; k < elements.size(); k++) {
562 // For each key.
563 ElementWrapper element = (ElementWrapper) elements.get(k);
564 if(!known_elements.contains(element)) {
565 Metadata data = new Metadata(element);
566 int index = add(unassigned_metadata, data);
567 // Inform everyone that the model has changed, but only if it affects the current view.
568 //if(current_view == SHOW_UNASSIGNED) {
569 // fireTableRowsInserted(index, index);
570 //}
571 //else if(current_view == SHOW_ALL) {
572 if((index = current_metadata.indexOf(data)) == -1) {
573 index = add(current_metadata, data);
574 fireTableRowsInserted(index, index);
575 }
576 else {
577 fireTableRowsUpdated(index, index);
578 }
579 //}
580 }
581 //activity_bar.setValue(activity_bar.getValue() + 1);
582 Gatherer.g_man.metaedit_pane.validateMetadataTable();
583 }
584 //long end = System.currentTimeMillis();
585 ///ystem.err.println("Took " + (end - start) + "ms to build table.");
586 //assigned_metadata_view.setEnabled(true);
587 //unassigned_metadata_view.setEnabled(true);
588 //activity_bar.setValue(activity_bar.getMaximum());
589 //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Ready"));
590 Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR);
591 fireTableDataChanged();
592 // Finally complete
593 complete = true;
594 // If in the in between we've been asked to select a certain metadata, we action that now.
595 if(selected_metadata != null) {
596 setSelectedMetadata(selected_metadata);
597 }
598 }
599
600 /** For some reason contains doesn't always work. It appear not to use equals() properly, as ElementWrappers need to compare themselves by their string content as other data members are nearly always different even between to ElementWrappers generated from the same DOM Element. */
601 private boolean contains(Vector elements, ElementWrapper element) {
602 if(element != null) {
603 for(int i = 0; i < elements.size(); i++) {
604 if(element.equals(elements.get(i))) {
605 return true;
606 }
607 }
608 }
609 return false;
610 }
611
612 private void showInheritedMetadataWarning() {
613 if(!had_warning) {
614 had_warning = true;
615 Runnable task = new Runnable() {
616 public void run() {
617 WarningDialog dialog = new WarningDialog("warning.InheritedMetadata", false);
618 dialog.display();
619 dialog.dispose();
620 dialog = null;
621 }
622 };
623 SwingUtilities.invokeLater(task);
624 }
625 }
626 }
627}
Note: See TracBrowser for help on using the repository browser.