package org.greenstone.gatherer.gui.table; /** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ import java.awt.*; import java.io.File; import java.util.*; import javax.swing.*; import javax.swing.table.*; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.file.FileNode; import org.greenstone.gatherer.gui.WarningDialog; import org.greenstone.gatherer.msm.ElementWrapper; import org.greenstone.gatherer.msm.Metadata; import org.greenstone.gatherer.msm.MSMAction; import org.greenstone.gatherer.msm.MSMEvent; import org.greenstone.gatherer.msm.MSMListener; import org.greenstone.gatherer.msm.MSMUtils; import org.greenstone.gatherer.util.ArrayTools; /** 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. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3b */ public class GTableModel extends AbstractTableModel implements MSMListener { /** 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. */ private ArrayList current_metadata = new ArrayList(); /** The metadata currently assigned at the file level. */ private ArrayList file_metadata = null; // new ArrayList(); /** The metadata currently assigned at the folder level. */ private ArrayList inherited_metadata = null; // new ArrayList(); /** The metadata currently unassigned. */ private ArrayList unassigned_metadata = null; // new ArrayList(); /** The file nodes this model is built upon. */ private FileNode[] file_nodes; /** The current view. */ //private int current_view; //private JToggleButton assigned_metadata_view; //private JToggleButton unassigned_metadata_view; /** An icon showing the current state of the table build. */ //private JProgressBar activity_bar; /** The table this model is being displayed in. */ private JTable table; private ModelBuilder builder; static final public int SHOW_ALL = 0; static final public int SHOW_ASSIGNED = 1; static final public int SHOW_FILE = 2; static final public int SHOW_INHERITED = 3; static final public int SHOW_UNASSIGNED = 4; static final private String[] COLUMN_NAMES = {"", Gatherer.dictionary.get("Metadata.Element"), Gatherer.dictionary.get("Metadata.Value")}; public GTableModel(JTable table) { this.builder = null; this.file_metadata = current_metadata; this.inherited_metadata = current_metadata; this.table = table; this.unassigned_metadata = current_metadata; //changeView(); // Register this model with the msm manager. if(Gatherer.c_man.ready()) { Gatherer.c_man.getCollection().msm.addMSMListener(this); } } public GTableModel(JTable table, FileNode[] file_nodes) { this(table); this.file_nodes = file_nodes; if(file_nodes != null && file_nodes.length > 0) { // Create model builder builder = new ModelBuilder(); builder.start(); } } /** The default constructor creates a new empty model with the current view set to an emtpy 'all metadata'. */ /* public GTableModel(JTable table, JToggleButton assigned_metadata_view, JToggleButton unassigned_metadata_view, JProgressBar activity_bar) { this.activity_bar = activity_bar; this.assigned_metadata_view = assigned_metadata_view; this.builder = null; this.table = table; this.unassigned_metadata_view = unassigned_metadata_view; changeView(); // Register this model with the msm manager. if(Gatherer.c_man.ready()) { Gatherer.c_man.getCollection().msm.addMSMListener(this); } } */ /** This constuctor starts by creating an empty model then, using a separate model builder thread, builds a model from the given files. */ /* public GTableModel(JTable table, JToggleButton assigned_metadata_view, JToggleButton unassigned_metadata_view, JProgressBar activity_bar, FileNode[] file_nodes) { this(table, assigned_metadata_view, unassigned_metadata_view, activity_bar); this.file_nodes = file_nodes; if(file_nodes != null && file_nodes.length > 0) { // Create model builder builder = new ModelBuilder(); builder.start(); } } */ public void changeView() { changeView(SHOW_ALL); } /* public void changeView() { boolean show_assigned = assigned_metadata_view.isSelected(); boolean show_unassigned = unassigned_metadata_view.isSelected(); if(show_assigned && show_unassigned) { changeView(SHOW_ALL); } else if(show_assigned) { changeView(SHOW_ASSIGNED); } else { // Show unassigned changeView(SHOW_UNASSIGNED); } } */ /** Change the current view by changing the model that the table is currently based on. */ public void changeView(int view) { //current_view = view; /* switch(view) { case SHOW_ASSIGNED: ///ystem.err.println("Show assigned"); current_metadata = new ArrayList(); current_metadata.addAll(file_metadata); current_metadata.addAll(inherited_metadata); Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR); break; case SHOW_FILE: ///ystem.err.println("Show file"); current_metadata = file_metadata; break; case SHOW_INHERITED: ///ystem.err.println("Show inherited"); current_metadata = inherited_metadata; break; case SHOW_UNASSIGNED: ///ystem.err.println("Show unassigned"); current_metadata = unassigned_metadata; break; default: // SHOW_ALL System.err.println("Show all"); // Create a new array from the three other arrays. current_metadata = new ArrayList(); current_metadata.addAll(file_metadata); current_metadata.addAll(inherited_metadata); current_metadata.addAll(unassigned_metadata); Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR); } // Inform everyone that the model has changed. fireTableDataChanged(); */ } /** 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. */ public void elementChanged(MSMEvent event) { MSMAction profile = event.getProfile(); if(profile != null) { // Retrieve the elements new name (thats hopefully what will be returned by getValue()). String name = profile.getTarget(); int row = 0; for( ; row < current_metadata.size(); row++) { if(getValueAt(row, 0).equals(name)) { fireTableCellUpdated(row, 0); } } name = null; } profile = null; event = null; } /** Returns the number of columns in this table. */ public int getColumnCount() { return COLUMN_NAMES.length; } /** Returns the data class of the specified column. */ public Class getColumnClass(int col) { if(col == 2) { return JButton.class; } return String.class; } /** Retrieves the name of the specified column. */ public String getColumnName(int col) { return COLUMN_NAMES[col]; } /* 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. */ public synchronized Metadata getMetadataAtRow(int row) { if(0 <= row && row < current_metadata.size()) { return (Metadata) current_metadata.get(row); } return null; } /** Returns the number of rows in this table. */ public int getRowCount() { return current_metadata.size(); } /** Returns the cell value at a given row and column as an Object. */ public Object getValueAt(int row, int col) { Object result = ""; if(0 <= row && row < current_metadata.size()) { Metadata m = (Metadata) current_metadata.get(row); if(0 <= col && col < COLUMN_NAMES.length) { switch(col) { case 0: if(!m.isFileLevel()) { result = m.getFile(); } break; case 1: ElementWrapper element = m.getElement(); if(element != null) { result = element.toString(); } break; case 2: result = m.getValue(); break; case 3: result = (m.isFileLevel() ? "true" : "false"); break; } } } return result; } /** Allows access to this models current view. */ public int getView() { return SHOW_ALL; //current_view; } /** Determine if the given metadata is common to all selected file nodes given the context of the current view. */ public boolean isCommon(int row) { if(0 <= row && row < current_metadata.size()) { Metadata entry = (Metadata) current_metadata.get(row); // System.err.println("Of the " + file_nodes.length + " selected files, " + entry + " is attached to " + entry.getCount() + " of them."); // System.err.println("There are " + file_nodes.length + " selected files."); // System.err.println("This metadata is attached to " + entry.getCount() + " of them."); if(entry.getCount() == file_nodes.length) { return true; } } return false; } /** 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. */ public void metadataChanged(MSMEvent event) { FileNode file_node = event.getRecord(); ///ystem.err.println("File node effected"); // First check whether this record is one of those that we have showing. if(file_nodes != null && file_node != null && ArrayTools.contains(file_nodes, file_node)) { // Brute force solution... rebuild the table. if(file_nodes != null && file_nodes.length > 0) { current_metadata.clear(); // Create model builder builder = new ModelBuilder(); builder.start(); } } } /** Called whenever a set is added or removed from the metadata set manager. */ public void setChanged(MSMEvent event) { // No, don't care. Still don't care. Couldn't care less. } public Rectangle setSelectedMetadata(Metadata data) { Rectangle bounds = null; if (builder != null) { if(builder.complete) { for(int i = 0; i < getRowCount(); i++) { if(getMetadataAtRow(i).equals(data)) { // System.err.println("Found matching metadata at row " + i + " (of " + getRowCount() + " rows)"); // bounds = table.getCellRect(i, 0, true); // table.scrollRectToVisible(bounds); SelectionTask task = new SelectionTask(i); SwingUtilities.invokeLater(task); break; } } } else { builder.selected_metadata = data; } } return bounds; } private class SelectionTask extends Thread { private int selected_row; SelectionTask(int selected_row) { this.selected_row = selected_row; } public void run() { Rectangle bounds = table.getCellRect(selected_row, 0, true); table.scrollRectToVisible(bounds); table.setRowSelectionInterval(selected_row, selected_row); ///ystem.err.println("Done setRowSelectionInterval..."); } } public int size() { return file_nodes.length; } /** 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. */ public void valueChanged(MSMEvent event) { // Don't care. Tra-la-la-la-la. } /** Alphabetically inserts the new_metadata in the target vector. */ private int add(ArrayList target, Metadata new_metadata) { int i = 0; while(i < target.size()) { Metadata current = (Metadata)target.get(i); if(current.compareTo(new_metadata) > 0) { target.add(i, new_metadata); return i; } i++; } target.add(new_metadata); return i; } /** Remove a certain piece of data from a certain vector. */ private void remove(ArrayList target, Metadata old_metadata) { if(target != null) { for(int i = 0; i < target.size(); i++) { Metadata current_metadata = (Metadata) target.get(i); if(current_metadata.equals(old_metadata)) { target.remove(current_metadata); return; } } } } private class ModelBuilder extends Thread { public boolean complete = false; public Metadata selected_metadata = null;; private boolean had_warning = false; public void run() { if (!Gatherer.c_man.ready()) { System.err.println("Not ready!"); return; } // System.err.println("Building metadata model..."); Vector elements = Gatherer.c_man.getCollection().msm.getElements(); ArrayList known_elements = new ArrayList(); current_metadata.clear(); for (int i = 0; i < file_nodes.length; i++) { File current_file = file_nodes[i].getFile(); // System.err.println("File: " + current_file); // Get the currently assigned metadata for this file ArrayList metadata = Gatherer.c_man.getCollection().gdm.getAllMetadata(current_file); for (int j = 0; j < metadata.size(); j++) { Metadata metadatum = (Metadata) metadata.get(j); ElementWrapper element = metadatum.getElement(); // System.err.println(" Metadatum: " + metadatum + " (" + metadatum.getCount() + ")"); // Ignore hidden metadata if (!element.toString().startsWith("hidden.")) { // If a piece of metadata is at folder level, display the warning if (!metadatum.isFileLevel()) { showInheritedMetadataWarning(); } // If the piece of metadata is in the list, increment its count int index = current_metadata.indexOf(metadatum); if (index >= 0) { Metadata existing = (Metadata) current_metadata.get(index); existing.inc(); } // Otherwise add it to the list else { metadatum.setCount(1); index = add(current_metadata, metadatum); fireTableRowsInserted(index, index); // Remember we have seen this element if (known_elements.contains(element) == false) { known_elements.add(element); } } } } } // Make sure each element has a piece of metadata (even if blank) for (int i = 0; i < elements.size(); i++) { ElementWrapper element = (ElementWrapper) elements.get(i); // System.err.println("Element: " + element); // If we haven't seen this metadata element, add it now if (!known_elements.contains(element)) { Metadata metadatum = new Metadata(element); metadatum.setCount(file_nodes.length); // Add it to the table int index = add(current_metadata, metadatum); fireTableRowsInserted(index, index); } } // Sort the metadata and update the table Gatherer.g_man.metaedit_pane.validateMetadataTable(); Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR); fireTableDataChanged(); // Processing complete complete = true; // If while processing we've been asked to select a certain metadata, do it now if (selected_metadata != null) { setSelectedMetadata(selected_metadata); } } private void showInheritedMetadataWarning() { if (!had_warning) { had_warning = true; Runnable task = new Runnable() { public void run() { WarningDialog dialog = new WarningDialog("warning.InheritedMetadata", false); dialog.display(); dialog.dispose(); dialog = null; } }; SwingUtilities.invokeLater(task); } } } private class OldModelBuilder extends Thread { public boolean had_warning = false; public boolean complete = false; public Metadata selected_metadata = null;; public void run() { if(Gatherer.c_man != null && Gatherer.c_man.ready()) { Vector elements = Gatherer.c_man.getCollection().msm.getElements(); // Show some visual indication that building is occuring. //assigned_metadata_view.setEnabled(false); //unassigned_metadata_view.setEnabled(false); //activity_bar.setMaximum(file_nodes.length + elements.size()); //activity_bar.setValue(0); //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Building")); //long start = System.currentTimeMillis(); ArrayList known_elements = new ArrayList(); // Elements that have metadata assigned. for(int i = 0; i < file_nodes.length; i++) { File current_file = file_nodes[i].getFile(); // The current assigned metadata for this file. ArrayList metadatum = Gatherer.c_man.getCollection().gdm.getAllMetadata(current_file); for(int j = 0; j < metadatum.size(); j++) { Metadata data = (Metadata) metadatum.get(j); // Determine the target list by the metadata level ArrayList modified_metadata; if(data.isFileLevel()) { modified_metadata = file_metadata; } else { showInheritedMetadataWarning(); modified_metadata = inherited_metadata; } int index = -1; // Don't show hidden metadata // If the given metadata already exists in our list of modified metadata then increment its commonality count. if((index = modified_metadata.indexOf(data)) != -1) { data = (Metadata) modified_metadata.get(index); ///ystem.err.println("Increasing count:" + element); data.inc(); // If the table shown is stale, refresh it. //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) { // fireTableRowsUpdated(index, index); //} // We may have to update a compound list //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) { if((index = current_metadata.indexOf(data)) == -1) { fireTableRowsUpdated(index, index); } //} } // Ensure the metadata's element is in our list of showable elements. else if(contains(elements, data.getElement())) { ///ystem.err.println("Setting count to one: " + element); data.setCount(1); index = add(modified_metadata, data); // Add to assigned metadata. known_elements.add(data.getElement()); // If the table shown is stale, refresh it. //if((modified_metadata == file_metadata && current_view == SHOW_FILE) || (modified_metadata == inherited_metadata && current_view == SHOW_INHERITED)) { // fireTableRowsInserted(index, index); //} // We may have to update a compound list //else if(current_view == SHOW_ALL || current_view == SHOW_ASSIGNED) { if((index = current_metadata.indexOf(data)) == -1) { index = add(current_metadata, data); fireTableRowsInserted(index, index); } else { fireTableRowsUpdated(index, index); } //} } else { ///ystem.err.println("No.\n***** Cannot match element so it must be hidden, right? *****"); } } //activity_bar.setValue(activity_bar.getValue() + 1); Gatherer.g_man.metaedit_pane.validateMetadataTable(); } // Add entries for the currently unassigned metadata. You can determine these as the difference between elements and known_elements. // System.err.println("Number of elements: " + elements.size()); // System.err.println("Number of known elements: " + known_elements.size()); for(int k = 0; k < elements.size(); k++) { // For each key. ElementWrapper element = (ElementWrapper) elements.get(k); // System.err.println("Element " + k + ": " + element); if(!known_elements.contains(element)) { Metadata data = new Metadata(element); int index = add(unassigned_metadata, data); // Inform everyone that the model has changed, but only if it affects the current view. //if(current_view == SHOW_UNASSIGNED) { // fireTableRowsInserted(index, index); //} //else if(current_view == SHOW_ALL) { if((index = current_metadata.indexOf(data)) == -1) { // System.err.println("Adding " + data + " to " + current_metadata); index = add(current_metadata, data); fireTableRowsInserted(index, index); } else { fireTableRowsUpdated(index, index); } //} } //activity_bar.setValue(activity_bar.getValue() + 1); Gatherer.g_man.metaedit_pane.validateMetadataTable(); } //long end = System.currentTimeMillis(); ///ystem.err.println("Took " + (end - start) + "ms to build table."); //assigned_metadata_view.setEnabled(true); //unassigned_metadata_view.setEnabled(true); //activity_bar.setValue(activity_bar.getMaximum()); //activity_bar.setString(Gatherer.dictionary.get("MetaEdit.Ready")); Collections.sort(current_metadata, MSMUtils.METADATA_COMPARATOR); fireTableDataChanged(); } else { } // Finally complete (even if we didn't do anything). complete = true; // If in the in between we've been asked to select a certain metadata, we action that now. if(getRowCount() > 0 && selected_metadata != null) { setSelectedMetadata(selected_metadata); } } /** 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. */ private boolean contains(Vector elements, ElementWrapper element) { if(element != null) { for(int i = 0; i < elements.size(); i++) { if(element.equals(elements.get(i))) { return true; } } } return false; } private void showInheritedMetadataWarning() { if(!had_warning) { had_warning = true; Runnable task = new Runnable() { public void run() { WarningDialog dialog = new WarningDialog("warning.InheritedMetadata", false); dialog.display(); dialog.dispose(); dialog = null; } }; SwingUtilities.invokeLater(task); } } } }