source: trunk/gli/src/org/greenstone/gatherer/gui/metaaudit/MetaAuditFrame.java@ 4421

Last change on this file since 4421 was 4421, checked in by kjdon, 21 years ago

the modal dialog now is one of our special ModalDialogs which only block the parent, enabling the use of help files while the dialog is open. A SimpleMenuBar with help on it has been added to the dialog.

  • Property svn:keywords set to Author Date Id Revision
File size: 11.9 KB
Line 
1package org.greenstone.gatherer.gui.metaaudit;
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 * <BR><BR>
10 *
11 * Author: John Thompson, Greenstone Digital Library, University of Waikato
12 *
13 * <BR><BR>
14 *
15 * Copyright (C) 1999 New Zealand Digital Library Project
16 *
17 * <BR><BR>
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * <BR><BR>
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
30 *
31 * <BR><BR>
32 *
33 * You should have received a copy of the GNU General Public License
34 * along with this program; if not, write to the Free Software
35 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
36 *########################################################################
37 */
38import java.awt.*;
39import java.awt.event.*;
40import javax.swing.*;
41import javax.swing.event.*;
42import javax.swing.tree.*;
43import org.greenstone.gatherer.Gatherer;
44import org.greenstone.gatherer.collection.Collection;
45import org.greenstone.gatherer.file.FileNode;
46import org.greenstone.gatherer.util.TreeSynchronizer;
47import org.greenstone.gatherer.gui.SimpleMenuBar;
48import org.greenstone.gatherer.gui.ModalDialog;
49
50/** The MetaAuditFrame provides a table view of all of the metadata assigned to a selection of FileNodes. All values for a certain file and a certain metadata element appear in the same cell. This table can be sorted by any column, and also has a MS Excel-like AutoFilter allowing you to restrict the rows visible to only those that match a certain set of criteria (applied to each column, and then 'ANDED', or cojoined, to determine the filter). Finally this dialog does not block the Gatherer tool, so the file selection can be changed and the dialog will just generate a new table dynamically.<BR>
51 * Much effort has gone into optimizing this table, as it quickly becomes slow and unresponsive to build/filter/sort when the number of records selected is high. However its performance is still nowhere as good as the Excel spreadsheet, and selections of 1000+ records can cause some serious waiting problems. Performance progression, shown in terms of time taken to perform action, are shown below. Note that building includes both the creation of the data model, and/or the time taken to lay out row and column sizes (due to this using multiple entry cells). Also all tables, by default, must be sorted by one column, which is initially the first column in ascending order:<BR><BR>
52 * <TABLE border=1 cellspacing=0 cellpadding=5>
53 * <TR><TD align=center colspan=5>Time Taken with 1000 records (ms)</TD></TR>
54 * <TR><TD>Action</TD><TD>Original</TD><TD>Revision 1</TD><TD>Revision 2</TD><TD>Revision 3</TD></TR>
55 * <TR><TD>Build</TD><TD align=right>113013</TD><TD align=right>1500</TD><TD align=right>1280</TD><TD align=right>665</TD></TR>
56 * <TR><TD>Sort</TD><TD align=right>34228</TD><TD align=right>353</TD><TD align=right>341</TD><TD align=right>338</TD></TR>
57 * <TR><TD>Filter</TD><TD align=right>57110</TD><TD align=right>485</TD><TD align=right>485</TD><TD align=right>485</TD></TR>
58 * </TABLE>
59 * <BR><BR>
60 * <TABLE border=1 cellspacing=0 cellpadding=5>
61 * <TR><TD align=center colspan=5>Time Taken with 10,000 records (ms) [hdlsub]</TD></TR>
62 * <TR><TD>Action</TD><TD>Original</TD><TD>Revision 1</TD><TD>Revision 2</TD><TD>Revision 3</TD></TR>
63 * <TR><TD>Build</TD><TD align=right>DNF</TD><TD align=right>8667</TD><TD align=right>6784</TD><TD align=right>3081</TD></TR>
64 * <TR><TD>Sort</TD><TD align=right>DNF</TD><TD align=right>236</TD><TD align=right>241</TD><TD align=right>228</TD></TR>
65 * <TR><TD>Filter</TD><TD align=right>DNF</TD><TD align=right>59272</TD><TD align=right>59767</TD><TD align=right>59614</TD></TR>
66 * </TABLE>
67 * <BR><BR>
68 * <TABLE border=1 cellspacing=0 cellpadding=5>
69 * <TR><TD align=center colspan=5>Time Taken with 30,000 records (ms) [hdl]</TD></TR>
70 * <TR><TD>Action</TD><TD>Original</TD><TD>Revision 1</TD><TD>Revision 2</TD><TD>Revision 3</TD></TR>
71 * <TR><TD>Build</TD><TD align=right>DNF</TD><TD align=right>19037</TD><TD align=right>16478</TD><TD align=right>8111</TD></TR>
72 * <TR><TD>Sort</TD><TD align=right>DNF</TD><TD align=right>314</TD><TD align=right>301</TD><TD align=right>385</TD></TR>
73 * <TR><TD>Filter</TD><TD align=right>DNF</TD><TD align=right>[300000]</TD><TD align=right>[300000]</TD><TD align=right>[300000]</TD></TR>
74 * </TABLE><BR><BR>
75 * It is easy to see that while building and sorting have become quite fast and reasonable, sorting is still unacceptable in terms of performance. The difficulty is this, that while sorting is easy (and fast) to implement via the Decorator pattern (making the overall table model a MDVC one) it requires the entire contents of a column to be available. Thus no matter the speed of your sort algorithm you will end up making N calls to getValueAt() in the table data model, for N records. This is not a problem for the initial model, hence the small sort times. However when you attempt to place another deocrator model (or modify the existing one) in order to filter only selected rows, even if the filtering itself takes little time, this little time quickly grows to unacceptable levels. In fact testing has shown it to grow much faster than linear time (though I'm not sure where). Doubling the number of records to filter increases the processing time by 5-7 times.<BR>
76 * In attempting to solve this problem, I have found many resources that mention a similar problem when trying to build visual representations of database tables, especially over networks or other temporally-non-deterministic connections. The common solution is to 'page' only those records necessary into memory on demand, thus making initial response significantly faster, while paying a small price for subsequent page seeks. There are even studies and algorithms for determining pages loads etc, I would imagine garnered from the memory management community. However none of these really help me usless I can get around the problem of having to have the entire columns population immeditaly accessable for sorting purposes.<BR>
77 * I believe with more time is would be possible to make a hybrid table model with properties such as you would require for the aforemenetioned database application, but which also provides fast methods for determining the highest and lowest records perhaps by building traversable heaps in the original model. Such a solution would incur a build penalty, and could consume significant memory, but would provide ordered and sequential access to the data within.<BR>
78 * The discussion aside, I have run out of time to do anything further on this, so will include as is, and provide a warning dialog whenever the number of records might suffer slow processing (1000+ records).
79 * @author John Thompson, Greenstone Digital Library, University of Waikato
80 * @version 2.3
81 */
82public class MetaAuditFrame
83 extends ModalDialog
84 implements TreeSelectionListener {
85 public AutofilterDialog autofilter_dialog;
86 /** Whether the selection has changed since the last time we built the model (because it has been hidden and theres no point refreshing a model you can't see!). */
87 private boolean invalid = true;
88 /** An array holding the most recent list of paths selected (plus some nulls for those paths removed). */
89 private FileNode records[];
90 /** A reference to ourselves so that inner classes can interact with us. */
91 private MetaAuditFrame self;
92 /** The table in which the metadata is shown. */
93 private MetaAuditTable table;
94 /** The default size for the metaaudit dialog. */
95 static final private Dimension SIZE = new Dimension(640,505);
96 /** The tolerance used to determine between a column resize, and a request for an AutoFilterDialog. */
97 static final private int TOLERANCE = 3;
98 /** Constructor.*/
99 public MetaAuditFrame(TreeSynchronizer tree_sync, FileNode records[]) {
100 super(Gatherer.g_man);
101 // Arguments
102 this.autofilter_dialog = new AutofilterDialog(this);
103 this.records = records;
104 this.self = this;
105 this.table = new MetaAuditTable(this);
106 // Creation
107 setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
108 setSize(SIZE);
109 setTitle(get("Title"));
110 setJMenuBar(new SimpleMenuBar("6.7"));
111 JPanel content_pane = (JPanel) getContentPane();
112 JPanel button_pane = new JPanel();
113
114 JButton close_button = new JButton(get("Close"));
115 close_button.setMnemonic(KeyEvent.VK_C);
116 // Connection
117 close_button.addActionListener(new CloseListener());
118 tree_sync.addTreeSelectionListener(this);
119 // Layout
120 button_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
121 button_pane.setLayout(new BorderLayout());
122 button_pane.add(close_button, BorderLayout.CENTER);
123
124 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
125 content_pane.setLayout(new BorderLayout());
126 content_pane.add(new JScrollPane(table), BorderLayout.CENTER);
127 content_pane.add(button_pane, BorderLayout.SOUTH);
128
129 Dimension screen_size = Gatherer.config.screen_size;
130 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
131 screen_size = null;
132 close_button = null;
133 button_pane = null;
134 content_pane = null;
135 }
136 /** Destructor. */
137 public void destroy() {
138 records = null;
139 self = null;
140 table = null;
141 }
142 /** Display the dialog on screen. */
143 public void display() {
144 if(invalid) {
145 rebuildModel();
146 }
147 setVisible(true);
148 }
149 /** This method is called whenever the selection within the collection tree changes. This causes the table to be rebuilt with a new model.
150 * @param event A <strong>TreeSelectionEvent</strong> containing information about the event.
151 */
152 public void valueChanged(TreeSelectionEvent event) {
153 if(event.getSource() instanceof JTree) {
154 TreePath paths[] = Gatherer.g_man.metaedit_pane.collection_tree.getSelectionPaths();
155 if(paths != null) {
156 records = new FileNode[paths.length];
157 for(int i = 0; i < paths.length; i++) {
158 records[i] = (FileNode) paths[i].getLastPathComponent();
159 }
160 if(isVisible()) {
161 rebuildModel();
162 }
163 else {
164 invalid = true;
165 }
166 }
167 }
168 }
169
170 public void wait(boolean waiting) {
171 Component glass_pane = getGlassPane();
172 if(waiting) {
173 // Show wait cursor.
174 glass_pane.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
175 glass_pane.setVisible(true);
176 }
177 else {
178 // Hide wait cursor.
179 glass_pane.setVisible(false);
180 glass_pane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
181 }
182 glass_pane = null;
183 }
184
185 /** Retrieve a phrase from the Dictionary.
186 * @param key A <strong>String</strong> which uniquely maps to a phrase.
187 * @return The desired phrase as a </strong>String</strong>.
188 * @see org.greenstone.gatherer.Dictionary
189 */
190 private String get(String key) {
191 return Gatherer.dictionary.get("MetaAudit." + key);
192 }
193 /** Rebuild the metaaudit table model using the current collection tree selection.
194 */
195 private void rebuildModel() {
196 // Build and set model
197 table.newModel(records);
198 // Done.
199 invalid = false;
200 }
201 /** Listens for actions apon the close button, and if detected closes the dialog (storing its current size first). */
202 private class CloseListener
203 implements ActionListener {
204 /** Any implementation of ActionListener must include this method so that we can be informed when an action has been performed on our target control(s).
205 * @param event An <strong>ActionEvent</strong> containing information about the event.
206 */
207 public void actionPerformed(ActionEvent event) {
208 self.setVisible(false);
209 }
210 }
211}
212
213
214
215
216
217
218
219
220
221
Note: See TracBrowser for help on using the repository browser.