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

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

Many more tooltips and improvements to the Dictionary. Still more to come.

  • Property svn:keywords set to Author Date Id Revision
File size: 22.1 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 Date date;
478 private long last_modified;
479 private String display;
480 private String filename;
481 private String filepath;
482
483 public FileEntry(String filename, String filepath) {
484 this.date = null;
485 this.display = null;
486 this.filename = filename;
487 this.filepath = filepath;
488 this.last_modified = 0L;
489 }
490
491 /** returns 0 if the dates are the same, -ve number if the current FileEntry is earlier than the fe FileEntry ...*/
492 public int compareTo(FileEntry file_entry) {
493 Date our_date = getDate();
494 Date other_date = file_entry.getDate();
495 return our_date.compareTo(other_date);
496 }
497
498 public Date getDate() {
499 if(date == null) {
500 // Need to exclude first '.'
501 int first_index = filename.indexOf(".") + 1;
502 // Need to exclude the last '.'
503 int last_index = filename.lastIndexOf(".");
504 if(first_index > 0 && last_index > 0 && first_index < last_index) {
505 String date_string = filename.substring(first_index, last_index);
506 date = new Date(Long.parseLong(date_string));
507 }
508 else {
509 date = new Date(); // Current date
510 }
511 }
512 return date;
513 }
514
515 public AppendLineOnlyFileDocument getDocument() {
516 return new AppendLineOnlyFileDocument(filepath);
517 }
518
519 public void reset() {
520 display = null;
521 }
522
523 /** we only want the date out of the file name, not the whole path */
524 public String toString() {
525 File file = new File(filename);
526 if(display == null) {
527 last_modified = file.lastModified();
528 StringBuffer d = new StringBuffer();
529 Date date = getDate();
530 d.append(date.toString());
531 char success = UNKNOWN;
532 try {
533 FileInputStream in = new FileInputStream(new File(filepath));
534 success = (char) in.read();
535 in.close();
536 in = null;
537 }
538 catch(Exception error) {
539 error.printStackTrace();
540 }
541 switch (success) {
542 case SUCCESSFUL:
543 d.append(Dictionary.newget("OptionsPane.Successful"));
544 break;
545 case UNSUCCESSFUL:
546 d.append(Dictionary.newget("OptionsPane.Unsuccessful"));
547 break;
548 case CANCELLED:
549 d.append(Dictionary.newget("OptionsPane.Cancelled"));
550 break;
551 default:
552 d.append(Dictionary.newget("OptionsPane.Unknown"));
553 }
554 display = d.toString();
555 }
556 return display;
557 }
558 }
559
560 /** a ListSelectionListener that triggers the load of a newly selected log */
561 private class LogListListener implements ListSelectionListener {
562
563 public void valueChanged(ListSelectionEvent e) {
564 if (!e.getValueIsAdjusting()) { // we get two events for one change in list selection - use the false one ( the second one)
565 JList source = (JList)e.getSource();
566 file_entry = (FileEntry) source.getSelectedValue();
567 // First we determine if the old log has been completely written to file
568 Document document = log_textarea.getDocument();
569 if(document instanceof AppendLineOnlyFileDocument) {
570 AppendLineOnlyFileDocument append_line_only_file_document = (AppendLineOnlyFileDocument) document;
571 if(append_line_only_file_document.isStillWriting()) {
572 writing_documents.add(append_line_only_file_document); // We have to maintain a reference until they are all done.
573 append_line_only_file_document.setOwner(OptionsPane.this);
574 append_line_only_file_document.setExit();
575 }
576 }
577 // Load the new log
578 log_textarea.setDocument(file_entry.getDocument());
579 }
580 }
581 }
582
583 /** Listener that sets the tooltip associated to a combobox to the tooltip relevant to the selected item. */
584 private class ToolTipUpdater
585 implements ActionListener {
586 private HashMap arg_list;
587 public ToolTipUpdater(HashMap arg_list) {
588 this.arg_list = arg_list;
589 }
590 /** 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.
591 * @param event An <strong>ActionEvent</strong> containing information about the action that fired this call.
592 */
593 public void actionPerformed(ActionEvent event) {
594 JComboBox source = (JComboBox)event.getSource();
595 String key = (String) source.getSelectedItem();
596 if(arg_list != null) {
597 String description = (String) arg_list.get(key);
598 if(description != null) {
599 description = Utility.formatHTMLWidth(DESCRIPTION_SEP + description, 60);
600 String original = source.getToolTipText();
601 if(original == null) {
602 original = "";
603 }
604 // Remove any existing extra description.
605 if(original.indexOf(DESCRIPTION_SEP) != -1) {
606 original = original.substring(0, original.indexOf(DESCRIPTION_SEP));
607 }
608 source.setToolTipText(original + description);
609 }
610 }
611 }
612 }
613}
Note: See TracBrowser for help on using the repository browser.