001 package jigcell.compare.ui;
002
003 import java.awt.EventQueue;
004 import java.awt.event.ActionEvent;
005 import java.awt.event.ActionListener;
006 import java.util.ArrayList;
007 import java.util.List;
008 import javax.swing.JButton;
009 import javax.swing.JFileChooser;
010 import javax.swing.JMenu;
011 import javax.swing.JMenuItem;
012 import jigcell.compare.IMultipleDataSource;
013 import jigcell.compare.IPersistableDataSource;
014 import jigcell.compare.IReadableDataSource;
015 import jigcell.compare.IWriteableDataSource;
016 import jigcell.compare.impl.Compare;
017 import jigcell.compare.impl.Config;
018 import jigcell.compare.impl.DataSource;
019 import jigcell.compare.impl.ExceptionRecorder;
020 import jigcell.compare.impl.FileDataSource;
021 import jigcell.compare.impl.ProxyBuilder;
022
023 /**
024 * A default implementation of a tab providing some basic services.
025 *
026 * <p>
027 * This code is licensed under the DARPA BioCOMP Open Source License. See LICENSE for more details.
028 * </p>
029 *
030 * @author Nicholas Allen
031 */
032
033 public abstract class DataEditorPanelTab extends PanelTab {
034
035 /**
036 * Beginning of property key for recent source options
037 */
038
039 public final static String CONFIG_RECENTSOURCE = "DataEditorPanelTab.recentSource";
040
041 /**
042 * Property key for number of recent sources supported
043 */
044
045 public final static String CONFIG_RECENTSOURCECOUNT = "DataEditorPanelTab.recentSourceCount";
046
047 /**
048 * Name of menu containing recent sources to open
049 */
050
051 protected final static String MENU_OPENRECENT = "Recent Files";
052
053 /**
054 * Name of default menubar
055 */
056
057 protected final static String MENUBAR_DEFAULT = "default";
058
059 /**
060 * General error message for failed load
061 */
062
063 protected final static String MESSAGE_LOADERROR = "Unable to load: ";
064
065 /**
066 * Status message when loading
067 */
068
069 protected final static String MESSAGE_LOADING = "Loading from: ";
070
071 /**
072 * General error message for failed save
073 */
074
075 protected final static String MESSAGE_SAVEERROR = "Unable to save: ";
076
077 /**
078 * File chooser option when opening a file
079 */
080
081 protected final static String OPTION_CHOOSEROPEN = "Open";
082
083 /**
084 * File chooser option when saving a file
085 */
086
087 protected final static String OPTION_CHOOSERSAVE = "Save";
088
089 /**
090 * Property for storing recent sources within a menu item
091 */
092
093 protected final static String PROPERTY_RECENTSOURCETARGET = "jigcell_recentsourcetarget";
094
095 /**
096 * Name of default toolbar
097 */
098
099 protected final static String TOOLBAR_DEFAULT = "default";
100
101 /**
102 * The menus that this view is presenting
103 */
104
105 protected JMenu menus [];
106
107 /**
108 * Exception handler
109 */
110
111 protected ExceptionRecorder exception;
112
113 /**
114 * Provides interface help for the view
115 */
116
117 protected InterfaceBuilder manager;
118
119 /**
120 * Menu listing the recently used readable data sources
121 */
122
123 protected JMenu recentReadablesMenu;
124
125 /**
126 * Creates menus for the view
127 */
128
129 protected MenuBuilder menuManager;
130
131 /**
132 * Lock for temporary read result
133 */
134
135 protected final Object READ_LOCK = new Object ();
136
137 /**
138 * Data source the current content should be saved to
139 */
140
141 private IWriteableDataSource currentSource;
142
143 /**
144 * Helper class for performing read operations on other threads.
145 */
146
147 protected class ReadHelper {
148
149 /**
150 * Data source to read from
151 */
152
153 protected IReadableDataSource readSource;
154
155 /**
156 * Temporary read result
157 */
158
159 protected Object readResult;
160
161 /**
162 * Creates a new reading helper thread.
163 *
164 * @param readSource Data source to read from
165 */
166
167 protected ReadHelper (IReadableDataSource readSource) {
168 this.readSource = readSource;
169 new Thread (ProxyBuilder.proxyRunnable (this, "read")).start ();
170 }
171
172 /**
173 * Sends data read from some external source off for processing.
174 */
175
176 protected void finish () {
177 try {
178 setCurrentSource (null);
179 readInternal (readResult);
180 setCurrentSource (readSource instanceof IWriteableDataSource ? (IWriteableDataSource) readSource : null);
181 } catch (Exception e) {
182 Compare.throwUncheckedException (e);
183 } finally {
184 readNotify ();
185 }
186 }
187
188 /**
189 * Reads data from some external source.
190 */
191
192 protected void read () {
193 ProgressMonitor monitor = new ProgressMonitor (DataEditorPanelTab.this, MESSAGE_LOADING + readSource.getName ());
194 Throwable readError = null;
195 synchronized (READ_LOCK) {
196 try {
197 try {
198 readResult = readSource.read ();
199 } finally {
200 if (readSource instanceof IMultipleDataSource)
201 ((IMultipleDataSource) readSource).finish ();
202 }
203 if (monitor.isCancelled ())
204 return;
205 EventQueue.invokeAndWait (ProxyBuilder.proxyRunnable (this, "finish"));
206 } catch (Exception e) {
207 readError = e;
208 } finally {
209 monitor.close ();
210 if (readError != null)
211 compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_LOADERROR + readError.getMessage (), readError);
212 }
213 }
214 }
215 }
216
217 /**
218 * A panel tab with services for reading and writing data.
219 *
220 * @param compare Comparator backend to interface with
221 * @param configMarker Marker for retrieving configuration information from Comparator backend
222 */
223
224 public DataEditorPanelTab (Compare compare, String configMarker) {
225 super (compare, configMarker);
226 exception = new ExceptionRecorder ("Error encountered during file operation.", true, false, true);
227 setCurrentSource (null);
228 }
229
230 /**
231 * Adds a recently used data source. If the source is already in the list, it will be moved to the most recent position.
232 *
233 * @param source Source
234 */
235
236 public void addRecentSource (IPersistableDataSource source) {
237 int l = (int) getRecentSourceCount ();
238 if (l == 0)
239 return;
240 String location = source.getLocation ();
241 if (location == null)
242 return;
243 String _source = DataSource.persistSource (source);
244 if (_source == null)
245 return;
246 Config config = getConfigForView ();
247 String sources [] = new String [l];
248 sources [0] = _source;
249 int pos = 1;
250 for (int i = 1; i <= l && pos < l; i++) {
251 _source = config.getValue (CONFIG_RECENTSOURCE + i);
252 if (_source == null)
253 break;
254 if (!location.equals (DataSource.unpersistSource (_source).getLocation ()))
255 sources [pos++] = _source;
256 }
257 for (int i = 0; i < pos; i++)
258 config.setValue (CONFIG_RECENTSOURCE + (i + 1), sources [i]);
259 compare.firePropertyChange (Compare.PROPERTY_CONFIG_EDIT);
260 }
261
262 /**
263 * The current data source to work from.
264 */
265
266 public IWriteableDataSource getCurrentSource () {
267 return currentSource;
268 }
269
270 /**
271 * A recently used data source. If there is no such source, the return value is null.
272 *
273 * @param index The position of the source. The most recently used source is position 1 and older sources have higher numbers.
274 */
275
276 public IPersistableDataSource getRecentSource (long index) {
277 assert index > 0 && index <= getRecentSourceCount ();
278 return DataSource.unpersistSource (compare.getConfig ().findValue (configMarkers, CONFIG_RECENTSOURCE + index));
279 }
280
281 /**
282 * The number of recent data sources that can be remembered.
283 */
284
285 public long getRecentSourceCount () {
286 return Config.convertToInteger (compare.getConfig ().findValue (configMarkers, CONFIG_RECENTSOURCECOUNT), 0);
287 }
288
289 /**
290 * Loads the data for the view from some external source.
291 *
292 * @param source Data source to read from
293 */
294
295 public void load (IReadableDataSource source) {
296 if (source instanceof FileDataSource) {
297 FileDataSource fileSource = (FileDataSource) source;
298 fileSource.setExceptionListenerOption (exception);
299 fileSource.setFileChooserOption (createFileChooser ());
300 fileSource.setFileChooserSelectOption (OPTION_CHOOSEROPEN);
301 }
302 try {
303 if (!source.configure ())
304 return;
305 loadDirect (source);
306 } catch (Exception e) {
307 compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_LOADERROR + e.getMessage (), e);
308 }
309 }
310
311 /**
312 * Loads the data for the view from some external source without user prompting.
313 *
314 * @param source Data source to read from
315 */
316
317 public void loadDirect (IReadableDataSource source) {
318 new ReadHelper (source);
319 }
320
321 /**
322 * {@inheritDoc}
323 */
324
325 public void readConfiguration (String state) {
326 if (state == STATE_ENDINITIALIZE) {
327 JMenu openMenu = MenuBuilder.findParentMenu (menus, "open", false);
328 if (openMenu == null)
329 return;
330 recentReadablesMenu = MenuBuilder.createMenu (MENU_OPENRECENT, null, null, null);
331 for (int i = 0, l = openMenu.getItemCount (); i < l; i++)
332 if ("open".equals (openMenu.getItem (i).getActionCommand ())) {
333 openMenu.insert (recentReadablesMenu, i + 1);
334 break;
335 }
336 } else if (state != STATE_RUNNING)
337 return;
338 if (recentReadablesMenu == null)
339 return;
340 recentReadablesMenu.removeAll ();
341 JMenuItem items [] = createReadableRecentSourceMenuItems ();
342 if (items.length != 0) {
343 recentReadablesMenu.setEnabled (true);
344 for (int i = 0, l = items.length; i < l; i++)
345 recentReadablesMenu.add (items [i]);
346 } else
347 recentReadablesMenu.setEnabled (false);
348 }
349
350 /**
351 * Saves the data for the view to some external source.
352 *
353 * @param source Data source to write to
354 */
355
356 public void save (IWriteableDataSource source) {
357 if (source instanceof FileDataSource) {
358 FileDataSource fileSource = (FileDataSource) source;
359 fileSource.setExceptionListenerOption (exception);
360 fileSource.setFileChooserOption (createFileChooser ());
361 fileSource.setFileChooserSelectOption (OPTION_CHOOSERSAVE);
362 }
363 try {
364 if (!source.configure ())
365 return;
366 saveDirect (source);
367 } catch (Exception e) {
368 compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_SAVEERROR + e.getMessage (), e);
369 }
370 }
371
372 /**
373 * Saves the data for the view to some external source without user prompting.
374 *
375 * @param source Data source to write to
376 */
377
378 public void saveDirect (IWriteableDataSource source) {
379 try {
380 write (source);
381 setCurrentSource (source);
382 } catch (Exception e) {
383 compare.shellHandleException (Compare.MESSAGE_ERROR, MESSAGE_SAVEERROR + e.getMessage (), e);
384 } finally {
385 if (source instanceof IMultipleDataSource)
386 ((IMultipleDataSource) source).finish ();
387 }
388 }
389
390 /**
391 * Sets the current data source to work from.
392 *
393 * @param source Data source
394 */
395
396 public void setCurrentSource (IWriteableDataSource source) {
397 currentSource = source;
398 if (source instanceof IPersistableDataSource)
399 addRecentSource ((IPersistableDataSource) source);
400 }
401
402 /**
403 * A file chooser specialized for the view.
404 */
405
406 protected JFileChooser createFileChooser () {
407 return InterfaceBuilder.createFileChooser ();
408 }
409
410 /**
411 * {@inheritDoc}
412 */
413
414 protected JMenu [] createMenus () {
415 menus = menuManager.createMenus (MENUBAR_DEFAULT);
416 return menus;
417 }
418
419 /**
420 * Menu items that correspond to the most recently used readable data sources. Clicking on the menu item will load the source.
421 */
422
423 protected JMenuItem [] createReadableRecentSourceMenuItems () {
424 ActionListener listener = ProxyBuilder.proxyActionListener (this, "loadRecentSource");
425 List items = new ArrayList ();
426 for (int i = 1, l = (int) getRecentSourceCount (); i <= l; i++) {
427 IPersistableDataSource source = getRecentSource (i);
428 if (source == null)
429 break;
430 if (!(source instanceof IReadableDataSource))
431 continue;
432 JMenuItem item = MenuBuilder.createMenuItem (source.getLocation (), null, null, null, listener);
433 items.add (item);
434 item.putClientProperty (PROPERTY_RECENTSOURCETARGET, source);
435 }
436 return (JMenuItem []) items.toArray (new JMenuItem [0]);
437 }
438
439 /**
440 * {@inheritDoc}
441 */
442
443 protected JButton [] createTools () {
444 return menuManager.createTools (TOOLBAR_DEFAULT);
445 }
446
447 /**
448 * {@inheritDoc}
449 */
450
451 protected void initialize () {
452 super.initialize ();
453 manager = new InterfaceBuilder (this, this);
454 menuManager = new MenuBuilder (this, true);
455 }
456
457 /**
458 * Loads a recent source.
459 *
460 * @param e Action caused by the menu item that indicates the recent source
461 */
462
463 protected void loadRecentSource (ActionEvent e) {
464 loadDirect ((IReadableDataSource) ((JMenuItem) e.getSource ()).getClientProperty (PROPERTY_RECENTSOURCETARGET));
465 }
466
467 /**
468 * Processes data that has been read from some external source.
469 *
470 * @param readResult Data
471 */
472
473 protected void readInternal (Object readResult) throws Exception {}
474
475 /**
476 * Notifies listeners after a read operation has completed.
477 */
478
479 protected void readNotify () {}
480
481 /**
482 * Writes data to some external source.
483 *
484 * @param source Data source to write to
485 */
486
487 protected void write (IWriteableDataSource source) throws Exception {}
488 }