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

Last change on this file since 4329 was 4329, checked in by jmt12, 21 years ago

Metadata changes update appropriately - John

  • Property svn:keywords set to Author Date Id Revision
File size: 24.8 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.complete) {
424 for(int i = 0; i < getRowCount(); i++) {
425 if(getMetadataAtRow(i).equals(data)) {
426 ///ystem.err.println("Found matching metadata at row " + i + " (of " + getRowCount() + " rows)");
427 ///ystem.err.println("Table shows " + table.getRowCount() + " rows!");
428 bounds = table.getCellRect(i, 0, true);
429 table.scrollRectToVisible(bounds);
430 table.setRowSelectionInterval(i, i);
431 }
432 }
433 }
434 else {
435 builder.selected_metadata = data;
436 }
437 return bounds;
438 }
439
440 public int size() {
441 return file_nodes.length;
442 }
443
444 /** 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. */
445 public void valueChanged(MSMEvent event) {
446 // Don't care. Tra-la-la-la-la.
447 }
448
449 /** Alphabetically inserts the new_metadata in the target vector. */
450 private int add(ArrayList target, Metadata new_metadata) {
451 int i = 0;
452 while(i < target.size()) {
453 Metadata current = (Metadata)target.get(i);
454 if(current.compareTo(new_metadata) > 0) {
455 target.add(i, new_metadata);
456 return i;
457 }
458 i++;
459 }
460 target.add(new_metadata);
461 return i;
462 }
463
464 /** Remove a certain piece of data from a certain vector. */
465 private void remove(ArrayList target, Metadata old_metadata) {
466 if(target != null) {
467 for(int i = 0; i < target.size(); i++) {
468 Metadata current_metadata = (Metadata) target.get(i);
469 if(current_metadata.equals(old_metadata)) {
470 target.remove(current_metadata);
471 return;
472 }
473 }
474 }
475 }
476
477 private class ModelBuilder
478 extends Thread {
479
480 public boolean had_warning = false;
481
482 public boolean complete = false;
483
484 public Metadata selected_metadata = null;;
485
486 public void run() {
487 Vector elements = Gatherer.c_man.getCollection().msm.getElements();
488 // Show some visual indication that building is occuring.
489 //assigned_metadata_view.setEnabled(false);
490 //unassigned_metadata_view.setEnabled(false);
491 //activity_bar.setMaximum(file_nodes.length + elements.size());
492 //activity_bar.setValue(0);
493 //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Building"));
494 //long start = System.currentTimeMillis();
495
496 ArrayList known_elements = new ArrayList(); // Elements that have metadata assigned.
497 for(int i = 0; i < file_nodes.length; i++) {
498 File current_file = file_nodes[i].getFile();
499 // The current assigned metadata for this file.
500 ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getAllMetadata(current_file);
501 for(int j = 0; j < metadatum.size(); j++) {
502 Metadata data = (Metadata) metadatum.get(j);
503 // Determine the target list by the metadata level
504 ArrayList modified_metadata;
505 if(data.isFileLevel()) {
506 modified_metadata = file_metadata;
507 }
508 else {
509 showInheritedMetadataWarning();
510 modified_metadata = inherited_metadata;
511 }
512 int index = -1;
513 // Don't show hidden metadata
514 // If the given metadata already exists in our list of modified metadata then increment its commonality count.
515 if((index = modified_metadata.indexOf(data)) != -1) {
516 data = (Metadata) modified_metadata.get(index);
517 ///ystem.err.println("Increasing count:" + element);
518 data.inc();
519 // If the table shown is stale, refresh it.
520 //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) {
521 // fireTableRowsUpdated(index, index);
522 //}
523 // We may have to update a compound list
524 //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) {
525 if((index = current_metadata.indexOf(data)) == -1) {
526 fireTableRowsUpdated(index, index);
527 }
528 //}
529 }
530 // Ensure the metadata's element is in our list of showable elements.
531 else if(contains(elements, data.getElement())) {
532 ///ystem.err.println("Setting count to one: " + element);
533 data.setCount(1);
534 index = add(modified_metadata, data); // Add to assigned metadata.
535 known_elements.add(data.getElement());
536 // If the table shown is stale, refresh it.
537 //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) {
538 // fireTableRowsInserted(index, index);
539 //}
540 // We may have to update a compound list
541 //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) {
542 if((index = current_metadata.indexOf(data)) == -1) {
543 index = add(current_metadata, data);
544 fireTableRowsInserted(index, index);
545 }
546 else {
547 fireTableRowsUpdated(index, index);
548 }
549 //}
550 }
551 else {
552 ///ystem.err.println("No.\n***** Cannot match element so it must be hidden, right? *****");
553 }
554 }
555 //activity_bar.setValue(activity_bar.getValue() + 1);
556 Gatherer.g_man.metaedit_pane.validateMetadataTable();
557 }
558 // Add entries for the currently unassigned metadata. You can determine these as the difference between elements and known_elements.
559 for(int k = 0; k < elements.size(); k++) {
560 // For each key.
561 ElementWrapper element = (ElementWrapper) elements.get(k);
562 if(!known_elements.contains(element)) {
563 Metadata data = new Metadata(element);
564 int index = add(unassigned_metadata, data);
565 // Inform everyone that the model has changed, but only if it affects the current view.
566 //if(current_view == SHOW_UNASSIGNED) {
567 // fireTableRowsInserted(index, index);
568 //}
569 //else if(current_view == SHOW_ALL) {
570 if((index = current_metadata.indexOf(data)) == -1) {
571 index = add(current_metadata, data);
572 fireTableRowsInserted(index, index);
573 }
574 else {
575 fireTableRowsUpdated(index, index);
576 }
577 //}
578 }
579 //activity_bar.setValue(activity_bar.getValue() + 1);
580 Gatherer.g_man.metaedit_pane.validateMetadataTable();
581 }
582 //long end = System.currentTimeMillis();
583 ///ystem.err.println("Took " + (end - start) + "ms to build table.");
584 //assigned_metadata_view.setEnabled(true);
585 //unassigned_metadata_view.setEnabled(true);
586 //activity_bar.setValue(activity_bar.getMaximum());
587 //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Ready"));
588 Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR);
589 fireTableDataChanged();
590 // Finally complete
591 complete = true;
592 // If in the in between we've been asked to select a certain metadata, we action that now.
593 if(selected_metadata != null) {
594 setSelectedMetadata(selected_metadata);
595 }
596 }
597
598 /** 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. */
599 private boolean contains(Vector elements, ElementWrapper element) {
600 if(element != null) {
601 for(int i = 0; i < elements.size(); i++) {
602 if(element.equals(elements.get(i))) {
603 return true;
604 }
605 }
606 }
607 return false;
608 }
609
610 private void showInheritedMetadataWarning() {
611 if(!had_warning) {
612 had_warning = true;
613 Runnable task = new Runnable() {
614 public void run() {
615 WarningDialog dialog = new WarningDialog("warning.InheritedMetadata", false);
616 dialog.display();
617 dialog.dispose();
618 dialog = null;
619 }
620 };
621 SwingUtilities.invokeLater(task);
622 }
623 }
624 }
625}
Note: See TracBrowser for help on using the repository browser.