source: trunk/gli/src/org/greenstone/gatherer/gui/OptionsPane.java@ 5726

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

Investigated why spinners don't remember their value if focus is lost before focus is shifted. Will take a lot more to solve

  • Property svn:keywords set to Author Date Id Revision
File size: 22.5 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.text.*;
46import org.greenstone.gatherer.Dictionary;
47import org.greenstone.gatherer.Gatherer;
48import org.greenstone.gatherer.cdm.Argument;
49import org.greenstone.gatherer.checklist.CheckList;
50import org.greenstone.gatherer.collection.BuildOptions;
51import org.greenstone.gatherer.collection.Collection;
52import org.greenstone.gatherer.collection.CollectionManager;
53import org.greenstone.gatherer.msm.ElementWrapper;
54import org.greenstone.gatherer.util.AppendLineOnlyFileDocument;
55import org.greenstone.gatherer.util.AppendLineOnlyFileDocumentOwner;
56import org.greenstone.gatherer.util.Utility;
57
58/** This class serves as the data holder for all subclasses of option panes, such as Import options or All options. It also contains methods for creating each of the option lines as they would appear in the subpane. Futhermore it has a method for considering all the arguments and generating a <strong>String[]</strong> to allow you to pass them to the <strong>GShell</strong>.
59 * @author John Thompson, Greenstone Digital Library, University of Waikato
60 * @version 2.2
61 */
62public class OptionsPane
63 extends JPanel
64 implements AppendLineOnlyFileDocumentOwner {
65
66 static final public char SUCCESSFUL = 's';
67 static final public char UNSUCCESSFUL = 'u';
68 static final public char CANCELLED = 'c';
69 static final public char UNKNOWN = 'x';
70
71 /** All process messages are written to this log text area. */
72 public JTextArea log_textarea = null;
73
74 /** The <strong>BuildOptions</strong> data object contains all the option settings we wish to persist between Gatherer sessions (and thus is stored in <strong>Collection</strong>). */
75 private BuildOptions build_options = null;
76
77 private FileEntry file_entry = null;
78 /** the log pane - we only create it once now, not each time */
79 private JPanel log_pane = null;
80 /** the list of previous log messages */
81 private JList log_list = null;
82 private Vector writing_documents;
83
84 static private int BUILD = 0;
85 static private int IMPORT = 1;
86 static private Dimension LABEL_SIZE = new Dimension(180, 25);
87 static private Dimension LOG_SIZE = new Dimension(610, 360);
88 /** The minimum size of a options pane, to prevent refresh problems around the bottom of shorter panes. */
89 static private Dimension PANE_SIZE = new Dimension(610, 350);
90 static private Dimension ROW_SIZE = new Dimension(610, 25);
91 static private Dimension SPINNER_SIZE = new Dimension(100, 25);
92 static private String DESCRIPTION_SEP = " + ";
93
94
95 /** The default constructor creates the few session length options, but either retrieves the rest from the current collection, or creates a default set of options. */
96 public OptionsPane(BuildOptions build_options) {
97 this.build_options = build_options;
98 this.writing_documents = new Vector();
99
100 // Have to do this here, not in display, as the message log view may not have been displayed yet.
101 log_textarea = new JTextArea();
102 log_textarea.setEditable(false);
103 }
104
105 /** This method creates the panel with all the build only options on it.
106 * @return A <strong>JPanel</strong> which can be used to display all the build only options.
107 */
108 public JPanel buildBuild() {
109 JPanel pane = new JPanel();
110 pane.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
111 pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
112 int build_argument_count = build_options.getBuildArgumentCount();
113 pane.setPreferredSize(new Dimension(ROW_SIZE.width, ROW_SIZE.height * build_argument_count));
114 for(int i = 0; i < build_argument_count; i++) {
115 // Retrieve the argument so we know how to format the control.
116 Argument argument = build_options.getBuildArgument(i);
117 // Now attempt to retrieve any existing value for this argument.
118 boolean enabled = build_options.getBuildValueEnabled(argument.getName());
119 String value = build_options.getBuildValue(argument.getName());
120 ArgumentControl argument_control = new ArgumentControl(BUILD, argument, enabled, value);
121 pane.add(argument_control);
122 }
123 return pane;
124 }
125 /** This method creates the panel with all the import only options on it.
126 * @return A <strong>JPanel</strong> which can be used to display all the import only options.
127 */
128 public JPanel buildImport() {
129 JPanel pane = new JPanel();
130 pane.setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
131 pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
132 int import_argument_count = build_options.getImportArgumentCount();
133 pane.setPreferredSize(new Dimension(ROW_SIZE.width, ROW_SIZE.height * import_argument_count));
134 for(int i = 0; i < import_argument_count; i++) {
135 // Retrieve the argument so we know how to format the control.
136 Argument argument = build_options.getImportArgument(i);
137 // Now attempt to retrieve any existing value for this argument.
138 boolean enabled = build_options.getImportValueEnabled(argument.getName());
139 String value = build_options.getImportValue(argument.getName());
140 ArgumentControl argument_control = new ArgumentControl(IMPORT, argument, enabled, value);
141 pane.add(argument_control);
142 }
143 return pane;
144 }
145 /** This method is used to build a panel based on the message log, which is nothing like any of the other panels.
146 * @return A <strong>JPanel</strong> containing a scrollable text area which represents the shell process message log.
147 */
148 public JPanel buildLog() {
149 // we now save the log pane
150 if (log_pane == null) {
151 log_pane = new JPanel(new BorderLayout());
152
153 // Build a list of the log files available, ordering by last modified. Log files are like build_log.date.txt
154 DefaultListModel contents = new DefaultListModel();
155 File log_directory = new File(Gatherer.c_man.getCollectionLog());
156 File children[] = log_directory.listFiles();
157 for(int i = 0; children != null && i < children.length; i++) {
158 if(children[i].getName().startsWith("build_log") && children[i].getName().endsWith(".txt") ) {
159 FileEntry entry = new FileEntry(children[i].getName(), children[i].getAbsolutePath());
160 // We are about to insert it. But where.
161 boolean found = false;
162 for(int j = 0; !found && j < contents.size(); j++) {
163 FileEntry sibling = (FileEntry) contents.getElementAt(j);
164 int order = entry.compareTo(sibling);
165 if(order > 0) {
166 contents.insertElementAt(entry, j);
167 found = true;
168 }
169 }
170 if(!found) {
171 contents.addElement(entry);
172 }
173 }
174 }
175
176 log_list = new JList(contents);
177 log_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
178 log_list.setLayoutOrientation(JList.VERTICAL);
179 log_list.setPreferredSize(new Dimension(600, 100));
180 log_list.setVisibleRowCount(3);
181 log_list.addListSelectionListener(new LogListListener());
182
183 JLabel log_history_label = new JLabel();
184 Dictionary.registerText(log_history_label, "OptionsPane.LogHistory");
185 JPanel log_history_pane = new JPanel();
186 log_history_pane.setPreferredSize(new Dimension(600, 100));
187 log_history_pane.setLayout(new BorderLayout());
188 log_history_pane.add(log_history_label, BorderLayout.NORTH);
189 log_history_pane.add(new JScrollPane(log_list), BorderLayout.CENTER);
190
191 log_pane.add(new JScrollPane(log_textarea), BorderLayout.CENTER);
192 log_pane.add(log_history_pane, BorderLayout.SOUTH);
193 }
194 return log_pane;
195 }
196
197 public AppendLineOnlyFileDocument createNewLogDocument() {
198 long time = System.currentTimeMillis();
199 StringBuffer name = new StringBuffer();
200 name.append("build_log.");
201 name.append(time);
202 name.append(".txt");
203 // just in case there is no log directory
204 File file = new File(Gatherer.c_man.getCollectionLog() + name.toString());
205 File parent_file = file.getParentFile();
206 parent_file.mkdirs();
207 parent_file = null;
208 // create the file entry and add it to the list at pos 0 - it will always be the newest one created
209 file_entry = new FileEntry(name.toString(), file.getAbsolutePath());
210 ((DefaultListModel)log_list.getModel()).add(0, file_entry);
211 log_list.setSelectedIndex(0);
212 // Finally retrieve and return the document associated with this file entry
213 return file_entry.getDocument();
214 }
215
216
217 /** Attempts to discover the latest document count.
218 * @return An <strong>int</strong> detailing the number of documents in this collection.
219 */
220 public int getDocumentCount() {
221 if(Gatherer.c_man.ready()) {
222 int count = Gatherer.c_man.getCollection().getDocumentCount();
223 if(count != 0) {
224 return count;
225 }
226 }
227 return 1;
228 }
229
230 /** Called by our magic log documents after they have finished writing themselves to file, whereapon it is no longer necessary to hold a reference to them. */
231 public void remove(AppendLineOnlyFileDocument document) {
232 writing_documents.remove(document);
233 }
234
235 public void resetFileEntry() {
236 if(file_entry != null) {
237 file_entry.reset();
238 }
239 }
240
241 /** Given a panel containing ArgumentControls, update the values associated with them. */
242 public void update(JPanel panel) {
243 if(panel == log_pane) {
244 return;
245 }
246
247 for(int i = 0; i < panel.getComponentCount(); i++) {
248 Component component = panel.getComponent(i);
249 if(component instanceof ArgumentControl) {
250 ((ArgumentControl)component).update();
251 }
252 }
253 }
254
255 private class ArgumentControl
256 extends JPanel {
257 private Argument argument;
258 private int type;
259 private JComponent value_control;
260 private JCheckBox enabled;
261 public ArgumentControl(int type, Argument argument, boolean enable, String value) {
262 super();
263 this.argument = argument;
264 this.type = type;
265 String tooltip = Utility.formatHTMLWidth("<html>" + argument.getDescription() + "</html>", 60);
266 // Because of the dynamic order of component creation/connection/layout, we can't really follow that pattern here.
267 setBackground(Gatherer.config.getColor("coloring.collection_tree_background", false));
268 setBorder(BorderFactory.createEmptyBorder(2,0,2,0));
269 setLayout(new BorderLayout());
270 setPreferredSize(ROW_SIZE);
271
272 // Try to determine what the value_controls value should be.
273 if(value == null) {
274 value = argument.getDefaultValue();
275 }
276 // Create a correct value control based on the argument provided.
277 switch(argument.getType()) {
278 case Argument.ENUM:
279 JComboBox combobox = new JComboBox();
280 combobox.setEnabled(enable);
281 combobox.setToolTipText(tooltip);
282 // Set enabled
283 if(enable) {
284 combobox.setBackground(Color.white);
285 }
286 else {
287 combobox.setBackground(Color.lightGray);
288 }
289 // Build an option model, wrapping each entry of the list table.
290 HashMap arg_list = argument.getOptions();
291 Iterator it = arg_list.keySet().iterator();
292 while(it.hasNext()) {
293 combobox.addItem((String) it.next());
294 }
295 // Connect this up first, so that if a value is selected the tooltip updates accordingly.
296 combobox.addActionListener(new ToolTipUpdater(arg_list));
297 if(value != null) {
298 // Set the selected string. However since they are all strings we had best iterate ourselves.
299 for(int i = 0; i < combobox.getItemCount(); i++) {
300 if(combobox.getItemAt(i).toString().equals(value)) {
301 combobox.setSelectedIndex(i);
302 }
303 }
304 }
305 // Layout
306 add(combobox, BorderLayout.CENTER);
307 // And remember
308 value_control = combobox;
309 break;
310 case Argument.FLAG:
311 // Only need the check box.
312 value_control = null;
313 break;
314 case Argument.INTEGER:
315 // Build a spinner
316 JSpinner spinner = new JSpinner();
317 spinner.setEnabled(enable);
318 spinner.setPreferredSize(SPINNER_SIZE);
319 spinner.setToolTipText(tooltip);
320 // Set enabled
321 JComponent c = spinner.getEditor();
322 if ( c instanceof JSpinner.DefaultEditor ) {
323 JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) c;
324 JFormattedTextField field = editor.getTextField();
325 field.setEditable(enable);
326 if(enable) {
327 field.setBackground(Color.white);
328 }
329 else {
330 field.setBackground(Color.lightGray);
331 }
332 }
333 // If there was an original value, set it.
334 if(value != null) {
335 try {
336 spinner.setValue(new Integer(value));
337 }
338 catch (Exception error) {
339 }
340 }
341 // Layout
342 add(new JLabel(), BorderLayout.CENTER);
343 add(spinner, BorderLayout.EAST);
344 // And remember it
345 value_control = spinner;
346 break;
347 case Argument.STRING:
348 // Use a standard text field
349 JTextField textfield = new JTextField();
350 textfield.setEnabled(enable);
351 textfield.setToolTipText(tooltip);
352 // Set enabled
353 if(enable) {
354 textfield.setBackground(Color.white);
355 }
356 else {
357 textfield.setBackground(Color.lightGray);
358 }
359 // If there was an original value, set it.
360 if(value != null) {
361 textfield.setText(value);
362 }
363 // Layout
364 add(textfield, BorderLayout.CENTER);
365 // And remember it
366 value_control = textfield;
367 break;
368 }
369
370 // If the argument is required, then you don't get a choice of whether it is enabled.
371 if(argument.isRequired()) {
372 JLabel label = new JLabel(argument.getName());
373 label.setOpaque(false);
374 label.setPreferredSize(LABEL_SIZE);
375 label.setToolTipText(tooltip);
376 add(label, BorderLayout.WEST);
377 }
378 else {
379 enabled = new JCheckBox(argument.getName(), enable);
380 enabled.setOpaque(false);
381 enabled.setPreferredSize(LABEL_SIZE);
382 enabled.setToolTipText(tooltip);
383 // Connect
384 enabled.addActionListener(new EnabledListener(value_control));
385 // Layout
386 add(enabled, BorderLayout.WEST);
387 }
388 }
389
390 /** Update the values stored in the collection so as to rememebr the current state of this argument. */
391 public void update() {
392 String name = argument.getName();
393 boolean enable = true;
394 if(enabled != null) {
395 enable = enabled.isSelected();
396 }
397 String value = null;
398 if(value_control == null) {
399 // Flag value, nothing to do.
400 }
401 else if(value_control instanceof JTextField) {
402 value = ((JTextField)value_control).getText();
403 }
404 else if(value_control instanceof JSpinner) {
405 value = ((JSpinner)value_control).getValue().toString();
406 }
407 else if(value_control instanceof JComboBox) {
408 value = (String) ((JComboBox)value_control).getSelectedItem();
409 }
410 // If this argument was a flag, but is now disabled, remove from the build options altogether
411 if(!enable && value == null) {
412 if(type == BUILD) {
413 build_options.removeBuildValue(name);
414 }
415 else {
416 build_options.removeImportValue(name);
417 }
418 }
419 // Otherwise update the argument value
420 else {
421 if(type == BUILD) {
422 build_options.setBuildValue(name, enable, value);
423 }
424 else {
425 build_options.setImportValue(name, enable, value);
426 }
427 }
428 }
429 }
430
431 /** Listens for actions apon the enable checkbox, and if detected enables or diables control appropriately. */
432 private class EnabledListener
433 implements ActionListener {
434 /** An editor component, such as a JComboBox or JTextField, that might have its enabled state changed by this listener. */
435 private JComponent target = null;
436 /** Constructor. */
437 public EnabledListener(JComponent target) {
438 this.target = target;
439 }
440 /** Any implementation of ActionListener must include this method so that we can be informed when an action has been performed on or registered check box, prompting us to change the state of the other controls as per the users request.
441 * @param event An <strong>ActionEvent</strong> containing information about the click.
442 */
443 public void actionPerformed(ActionEvent event) {
444 JCheckBox source = (JCheckBox)event.getSource();
445 if(target != null) {
446 if(source.isSelected()) {
447 target.setBackground(Color.white);
448 target.setEnabled(true);
449 }
450 else {
451 target.setBackground(Color.lightGray);
452 target.setEnabled(false);
453 }
454 // Special case of stupid JSpinners who don't let their backgrounds change properly.
455 if(target instanceof JSpinner) {
456 JSpinner spinner = (JSpinner) target;
457 JComponent c = spinner.getEditor();
458 if ( c instanceof JSpinner.DefaultEditor ) {
459 JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) c;
460 JFormattedTextField field = editor.getTextField();
461 field.setEditable(source.isSelected());
462 if(source.isSelected()) {
463 field.setBackground(Color.white);
464 }
465 else {
466 field.setBackground(Color.lightGray);
467 }
468 }
469 }
470 }
471 }
472 }
473
474 /** Holds a File which has a particular naming convention build_log.date.txt also keeps a Date corresponding to the date in its name*/
475 private class FileEntry {
476
477 private AppendLineOnlyFileDocument current_document;
478 private Date date;
479 private long last_modified;
480 private String display;
481 private String filename;
482 private String filepath;
483
484 public FileEntry(String filename, String filepath) {
485 this.date = null;
486 this.display = null;
487 this.filename = filename;
488 this.filepath = filepath;
489 this.last_modified = 0L;
490 }
491
492 /** returns 0 if the dates are the same, -ve number if the current FileEntry is earlier than the fe FileEntry ...*/
493 public int compareTo(FileEntry file_entry) {
494 Date our_date = getDate();
495 Date other_date = file_entry.getDate();
496 return our_date.compareTo(other_date);
497 }
498
499 public Date getDate() {
500 if(date == null) {
501 // Need to exclude first '.'
502 int first_index = filename.indexOf(".") + 1;
503 // Need to exclude the last '.'
504 int last_index = filename.lastIndexOf(".");
505 if(first_index > 0 && last_index > 0 && first_index < last_index) {
506 String date_string = filename.substring(first_index, last_index);
507 date = new Date(Long.parseLong(date_string));
508 }
509 else {
510 date = new Date(); // Current date
511 }
512 }
513 return date;
514 }
515
516 public AppendLineOnlyFileDocument getDocument() {
517 if(current_document == null) {
518 current_document = new AppendLineOnlyFileDocument(filepath);
519 }
520 return current_document;
521 }
522
523 public void reset() {
524 display = null;
525 }
526
527 /** we only want the date out of the file name, not the whole path */
528 public String toString() {
529 File file = new File(filename);
530 if(display == null) {
531 last_modified = file.lastModified();
532 StringBuffer d = new StringBuffer();
533 Date date = getDate();
534 d.append(date.toString());
535 char success = UNKNOWN;
536 File the_file = new File(filepath);
537 if(the_file.exists()) {
538 try {
539 FileInputStream in = new FileInputStream(the_file);
540 success = (char) in.read();
541 in.close();
542 in = null;
543 }
544 catch(Exception error) {
545 ///ystem.err.println("Log '" + filepath + "' not found!");
546 ///atherer.printStackTrace(error);
547 }
548 }
549 the_file = null;
550 switch (success) {
551 case SUCCESSFUL:
552 d.append(Dictionary.get("OptionsPane.Successful"));
553 break;
554 case UNSUCCESSFUL:
555 d.append(Dictionary.get("OptionsPane.Unsuccessful"));
556 break;
557 case CANCELLED:
558 d.append(Dictionary.get("OptionsPane.Cancelled"));
559 break;
560 default:
561 d.append(Dictionary.get("OptionsPane.Unknown"));
562 }
563 display = d.toString();
564 }
565 return display;
566 }
567 }
568
569 /** a ListSelectionListener that triggers the load of a newly selected log */
570 private class LogListListener implements ListSelectionListener {
571
572 public void valueChanged(ListSelectionEvent e) {
573 if (!e.getValueIsAdjusting()) { // we get two events for one change in list selection - use the false one ( the second one)
574 JList source = (JList)e.getSource();
575 file_entry = (FileEntry) source.getSelectedValue();
576 // First we determine if the old log has been completely written to file
577 Document document = log_textarea.getDocument();
578 if(document instanceof AppendLineOnlyFileDocument) {
579 AppendLineOnlyFileDocument append_line_only_file_document = (AppendLineOnlyFileDocument) document;
580 if(append_line_only_file_document.isStillWriting()) {
581 writing_documents.add(append_line_only_file_document); // We have to maintain a reference until they are all done.
582 append_line_only_file_document.setOwner(OptionsPane.this);
583 append_line_only_file_document.setExit();
584 }
585 }
586 // Load the new log
587 log_textarea.setDocument(file_entry.getDocument());
588 }
589 }
590 }
591
592 /** Listener that sets the tooltip associated to a combobox to the tooltip relevant to the selected item. */
593 private class ToolTipUpdater
594 implements ActionListener {
595 private HashMap arg_list;
596 public ToolTipUpdater(HashMap arg_list) {
597 this.arg_list = arg_list;
598 }
599 /** Any implementation of an ActionListener must include this method so that we can be informed when the selection in a combobox has changed and update the tooltip accordingly.
600 * @param event An <strong>ActionEvent</strong> containing information about the action that fired this call.
601 */
602 public void actionPerformed(ActionEvent event) {
603 JComboBox source = (JComboBox)event.getSource();
604 String key = (String) source.getSelectedItem();
605 if(arg_list != null) {
606 String description = (String) arg_list.get(key);
607 if(description != null) {
608 description = Utility.formatHTMLWidth(DESCRIPTION_SEP + description, 60);
609 String original = source.getToolTipText();
610 if(original == null) {
611 original = "";
612 }
613 // Remove any existing extra description.
614 if(original.indexOf(DESCRIPTION_SEP) != -1) {
615 original = original.substring(0, original.indexOf(DESCRIPTION_SEP));
616 }
617 source.setToolTipText(original + description);
618 }
619 }
620 }
621 }
622}
Note: See TracBrowser for help on using the repository browser.