View Javadoc

1   package com.melloware.jukes.gui.view.editor;
2   
3   import java.awt.Color;
4   import java.awt.Component;
5   import java.awt.Container;
6   import java.awt.Cursor;
7   import java.awt.Font;
8   import java.awt.KeyboardFocusManager;
9   import java.beans.PropertyChangeEvent;
10  import java.beans.PropertyChangeListener;
11  import java.text.SimpleDateFormat;
12  import java.util.ArrayList;
13  import java.util.Collection;
14  import java.util.Iterator;
15  import java.util.Locale;
16  
17  import javax.swing.BorderFactory;
18  import javax.swing.Icon;
19  import javax.swing.JComboBox;
20  import javax.swing.JComponent;
21  import javax.swing.JLabel;
22  import javax.swing.JPanel;
23  import javax.swing.JTextArea;
24  import javax.swing.JTextField;
25  import javax.swing.JToolBar;
26  import javax.swing.ProgressMonitor;
27  import javax.swing.Timer;
28  import javax.swing.border.Border;
29  import javax.swing.text.JTextComponent;
30  
31  import com.jgoodies.forms.builder.PanelBuilder;
32  import com.jgoodies.forms.layout.CellConstraints;
33  import com.jgoodies.forms.layout.FormLayout;
34  import com.jgoodies.uif.action.ActionManager;
35  import com.jgoodies.uif.application.Application;
36  import com.jgoodies.uif.component.ToolBarButton;
37  import com.jgoodies.uifextras.util.UIFactory;
38  import com.jgoodies.validation.view.ValidationComponentUtils;
39  import com.jgoodies.validation.view.ValidationResultViewFactory;
40  import com.melloware.jukes.db.orm.AbstractJukesObject;
41  import com.melloware.jukes.gui.tool.Actions;
42  import com.melloware.jukes.gui.tool.MainModule;
43  import com.melloware.jukes.gui.tool.Resources;
44  import com.melloware.jukes.gui.tool.Settings;
45  import com.melloware.jukes.gui.view.MainFrame;
46  import com.melloware.jukes.gui.view.tasks.UpdateTagsTask;
47  import com.melloware.jukes.gui.view.validation.AbstractValidationModel;
48  import com.melloware.jukes.util.MessageUtil;
49  
50  /**
51   * The abstract superclass of all <code>Editor</code> implementations.
52   * <p>
53   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
54   * @author Emil A. Lefkof III <info@melloware.com>
55   * @version 4.0
56   */
57  @SuppressWarnings("PMD")
58  abstract public class AbstractEditor
59      extends JPanel
60      implements Editor {
61  
62      protected static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MMM dd yyyy h:mm a", Locale.US);
63      protected static final Font FONT_ENABLED = new JLabel().getFont();
64      protected static final Font FONT_DISABLED = FONT_ENABLED.deriveFont(FONT_ENABLED.getStyle() ^ Font.BOLD);
65      protected static final Border BORDER_ENABLED = new JTextField().getBorder();
66      protected static final Border BORDER_DISABLED = BorderFactory.createEmptyBorder();
67      protected static final String HINT = "HINT";
68      protected AbstractValidationModel validationModel;
69      protected final Icon icon;
70      protected JComponent hintAreaPane;
71      protected JLabel createdByLabel;
72      protected JLabel createdDateLabel;
73      protected JLabel hintLabel;
74      protected JLabel modifiedByLabel;
75      protected JLabel modifiedDateLabel;
76      protected JTextArea hintArea;
77      protected ProgressMonitor progressMonitor;
78      protected final String titlePrefix;
79      protected Timer timer;
80      protected UpdateTagsTask task;
81      private Object model;
82  
83      /**
84       * Constructs an <code>AbstractEditor</code> with the specified icon.
85       */
86      public AbstractEditor(Icon icon) {
87          this(icon, "");
88      }
89  
90      /**
91       * Constructs an <code>AbstractEditor</code> with the specified title
92       * prefix.
93       */
94      public AbstractEditor(String titlePrefix) {
95          this(null, titlePrefix);
96      }
97  
98      /**
99       * Constructs an <code>AbstractEditor</code> with the specified
100      * <code>Icon</code> and title prefix.
101      */
102     public AbstractEditor(Icon icon, String titlePrefix) {
103         this.icon = icon;
104         this.titlePrefix = titlePrefix;
105         build();
106     }
107 
108     /**
109      * Returns the class used to register this instance in the UpdateManager.
110      */
111     abstract public Class getDomainClass();
112 
113     /**
114      * Answers this <code>Editor</code>'s <code>JToolBar</code>.
115      * The default implementation specifies that no tool bar is used.
116      */
117     public JToolBar getHeaderToolBar() {
118         return null;
119     }
120 
121     /**
122      * Answers this <code>Editor</code>'s <code>Icon</code>.
123      */
124     public Icon getIcon() {
125         return icon;
126     }
127 
128     /**
129      * Returns this editor's underlying model.
130      */
131     public Object getModel() {
132         return model;
133     }
134 
135     /**
136      * Returns this editor's underlying model as an AbstractJukesObject.
137      */
138     public AbstractJukesObject getOrmObject() {
139         return (AbstractJukesObject)model;
140     }
141 
142     /**
143      * Answers this <code>Editor</code>'s title.
144      */
145     public String getTitle() {
146         return titlePrefix + ' ' + getTitleSuffix();
147     }
148 
149     /**
150      * Answers this <code>Editor</code>'s <code>JToolBar</code>.
151      * The default implementation specifies that no tool bar is used.
152      */
153     public JToolBar getToolBar() {
154         return null;
155     }
156 
157     /**
158      * Sets a new model. Does nothing if the old and new model are the same.
159      * If the model changes, invokes <code>#updateView</code>.
160      *
161      * @param newModel   the model to set
162      */
163     public void setModel(Object newModel) {
164         Object oldModel = getModel();
165         if ((oldModel != null) &&(oldModel.equals(newModel))) {
166         	this.lock();
167         	
168             return;
169         }
170         model = newModel;
171         updateView();
172         this.lock();
173     }
174 
175     /**
176      * Activates this viewer.
177      */
178     public void activate() {
179         // Do nothing by default; subclasses may override.
180     }
181 
182     /**
183      * Commits any changes made to this editor.
184      */
185     public void commit() {
186     	this.lock();
187     }
188     
189     /**
190      * Unlocks this viewer and updates all text fields and buttons.
191      */
192     public void unlock() {
193     	ActionManager.get(Actions.UNLOCK_ID).setEnabled(false);
194     	validationModel.updateButtonState(true);
195         final Collection components = getAllComponents(this);
196     	
197     	for (Iterator iter = components.iterator(); iter.hasNext();) {
198 			final Component component = (Component) iter.next();
199 			if (component instanceof JTextComponent) {
200 				final JTextComponent field = (JTextComponent)component;
201 				//skip if this is the hint area
202 				if (field.getClientProperty(HINT) != null) {
203 					continue;
204 				}
205 				field.setEnabled(true);
206 				field.setBorder(BORDER_ENABLED);
207 				field.setOpaque(true);
208 				field.setFont(FONT_ENABLED);
209 			} else if (component instanceof JComboBox) {
210 				final JComboBox field = (JComboBox)component;
211 				field.setEnabled(true);
212 				field.setVisible(true);
213 			} else if (component instanceof JLabel) {
214 				final JLabel field = (JLabel)component;
215 				if ("GENRE".equalsIgnoreCase(field.getName())) {
216 					field.setVisible(false);
217 				}
218 			} else if (component instanceof ToolBarButton) {
219 				final ToolBarButton button = (ToolBarButton)component;
220 				button.setVisible(true);
221 			}
222 		}
223     	
224     	this.updateUI();
225     }
226     
227     /**
228      * Locks this viewer and updates all text fields and buttons.
229      */
230     public void lock() {
231     	ActionManager.get(Actions.UNLOCK_ID).setEnabled(true);
232     	validationModel.updateButtonState(false);
233     	final Collection components = getAllComponents(this);
234     	
235     	for (Iterator iter = components.iterator(); iter.hasNext();) {
236 			final Component component = (Component) iter.next();
237 			if (component instanceof JTextComponent) {
238 				final JTextComponent field = (JTextComponent)component;
239 				//skip if this is the hint area
240 				if (field.getClientProperty(HINT) != null) {
241 					continue;
242 				}
243 				
244 				field.setEnabled(false);
245 				field.setDisabledTextColor(Color.BLACK);
246 				field.setBorder(BORDER_DISABLED);
247 				field.setOpaque(false);
248 				field.setFont(FONT_DISABLED);
249 			}  else if (component instanceof JComboBox) {
250 				final JComboBox field = (JComboBox)component;
251 				field.setEnabled(false);
252 				field.setVisible(false);
253 			} else if (component instanceof JLabel) {
254 				final JLabel field = (JLabel)component;
255 				if ("GENRE".equalsIgnoreCase(field.getName())) {
256 					final String text = field.getText().trim();
257 					field.setText("  " + text);
258 					field.setVisible(true);
259 					field.setFont(FONT_DISABLED);
260 				}
261 			} else if (component instanceof ToolBarButton) {
262 				final ToolBarButton button = (ToolBarButton)component;
263 				button.setVisible(false);
264 			}
265 			
266 		}
267     	
268     	this.updateUI();
269     }
270     
271     /**
272      * Recurse through a component and build a list of all the components 
273      * underneath of it.
274      * <p>
275      * @param aTop the top container
276      * @return the collection of all components under the container
277      */
278     private Collection getAllComponents(Container aTop) {
279     	final Component[] comp = aTop.getComponents();
280     	final ArrayList list = new ArrayList();
281     	
282     	if (comp.length == 0) {
283 			return list;
284 		}
285     	
286     	for (int i = 0; i < comp.length; i++) {
287     		list.add(comp[i]);
288     		if (comp[i] instanceof Container) {
289 				list.addAll(getAllComponents((Container)comp[i]));
290 			}
291 		}
292     	return list;
293     }
294 
295     /**
296      * Deactivates this viewer.
297      */
298     public void deactivate() {
299         // refresh the object if there are errors when leaving
300         if (this.hasErrors()) {
301             this.rollback();
302         } else {
303         	//if dirty prompt the user to save changes
304             if (validationModel.isDirty()) {
305                 if (MessageUtil.promptYesNo(getMainFrame(), Resources.getString("messages.promptSaveChanges"))) {
306                     this.commit();
307                 } else {
308                 	//if they don't want to save then refresh this object
309                 	this.rollback();
310                 }
311             }
312         }
313     }
314 
315     /**
316      * Delete the object and its descendants contained by this editor.
317      */
318     public void delete() {
319         // Do nothing by default; subclasses may override.
320     }
321 
322     /**
323      * Tries to find a new cover for the disc.
324      */
325     public void findCover() {
326         // Do nothing by default; subclasses may override.
327     }
328 
329     /**
330      * Renames any files this editor owns.
331      */
332     public void renameFiles() {
333         // Do nothing by default; subclasses may override.
334     }
335 
336     /**
337      * Rollback any changes made to this editor
338      */
339     public void rollback() {
340         // Do nothing by default; subclasses may override.
341     	this.lock();
342     }
343 
344     /**
345      * Perform the web service search to look for disc info.
346      */
347     public void webSearch() {
348         // Do nothing by default; subclasses may override.
349     }
350 
351     /**
352      * Returns a suffix for this editor's title.
353      *
354      * @return a suffix for this editor's title
355      */
356     abstract protected String getTitleSuffix();
357 
358     /**
359      * Builds this panel.
360      */
361     abstract protected void build();
362 
363     /**
364      * Writes the view contents to the underlying model.
365      */
366     abstract protected void updateModel();
367 
368     /**
369      * Reads the view contents from the underlying model.
370      */
371     abstract protected void updateView();
372 
373     /**
374      * Gets the MainFrame for the application.
375      * <p>
376      * @return the MainFrame object
377      */
378     protected MainFrame getMainFrame() {
379         return (MainFrame)Application.getDefaultParentFrame();
380     }
381 
382     /**
383      * Gets the MainModule for the application.
384      * <p>
385      * @return the MainModule object
386      */
387     protected MainModule getMainModule() {
388         return getMainFrame().getMainModule();
389     }
390 
391     /**
392      * Gets the settings for the application.
393      * <p>
394      * @return the Settings object of user defined settings
395      */
396     protected Settings getSettings() {
397         return MainModule.SETTINGS;
398     }
399 
400     /**
401      * Gets the validationModel.
402      * <p>
403      * @return Returns the validationModel.
404      */
405     protected AbstractValidationModel getValidationModel() {
406         return this.validationModel;
407     }
408 
409     /**
410      * Sets the cursor to hourglass for true and default for false.  Used for
411      * long operations such as saves.
412      * <p>
413      * @param aBusy true for busy cursor, false for default
414      */
415     protected void setBusyCursor(boolean aBusy) {
416         if (aBusy) {
417             getMainFrame().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
418         } else {
419             getMainFrame().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
420         }
421     }
422 
423     /**
424      * Builds the audit information panel which displays the created by, and
425      * modified by info of a ORM record.
426      * <p>
427      * @return the panel to display the audit info
428      */
429     protected JComponent buildAuditInfoPanel() {
430         // create the labels as BOLD
431         createdDateLabel = UIFactory.createBoldLabel("");
432         createdByLabel = UIFactory.createBoldLabel("");
433         modifiedDateLabel = UIFactory.createBoldLabel("");
434         modifiedByLabel = UIFactory.createBoldLabel("");
435 
436         FormLayout layout = new FormLayout("right:max(40dlu;pref), 3dlu, 100dlu, 7dlu, "
437                                            + "right:max(40dlu;pref), 3dlu, 100dlu", "p, 3dlu, p, 3dlu, p, 3dlu, p");
438         PanelBuilder builder = new PanelBuilder(layout);
439         CellConstraints cc = new CellConstraints();
440 
441         int row = 1;
442         builder.addLabel(Resources.getString("label.createdby") + ": ", cc.xy(1, row));
443         builder.add(createdByLabel, cc.xy(3, row));
444         builder.addLabel(Resources.getString("label.createddate") + ": ", cc.xy(5, row));
445         builder.add(createdDateLabel, cc.xy(7, row++));
446         row++;
447 
448         builder.addLabel(Resources.getString("label.modifiedby") + ": ", cc.xy(1, row));
449         builder.add(modifiedByLabel, cc.xy(3, row));
450         builder.addLabel(Resources.getString("label.modifieddate") + ": ", cc.xy(5, row));
451         builder.add(modifiedDateLabel, cc.xy(7, row++));
452         row++;
453         
454         JComponent component = builder.getPanel();
455         component.setVisible(this.getSettings().isAuditInfo());
456 
457         return component;
458     }
459 
460     /**
461      * Builds the hint area panel where validation hints are displayed.
462      * <p>
463      * @return the panel to display the hints
464      */
465     protected JComponent buildHintAreaPane() {
466         hintLabel = new JLabel(ValidationResultViewFactory.getInfoIcon());
467         hintArea = new JTextArea(1, 38);
468         hintArea.putClientProperty(HINT, HINT);
469         hintArea.setEditable(false);
470         hintArea.setOpaque(false);
471 
472         FormLayout layout = new FormLayout("pref, 2dlu, default", "pref");
473         PanelBuilder builder = new PanelBuilder(layout);
474         CellConstraints cc = new CellConstraints();
475         builder.add(hintLabel, cc.xy(1, 1));
476         builder.add(hintArea, cc.xy(3, 1));
477 
478         hintAreaPane = builder.getPanel();
479         hintAreaPane.setVisible(false);
480         return hintAreaPane;
481     }
482 
483     /**
484      * Does this editor pass validation right now. True if so false otherwise.
485      * <p>
486      * @return true if passes validation
487      */
488     protected boolean hasErrors() {
489         return getValidationModel().getValidationResultModel().getResult().hasErrors();
490     }
491 
492     /**
493      * Initializes any event handling.
494      */
495     protected void initEventHandling() {
496         KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new FocusChangeHandler());
497     }
498 
499 
500     /**
501      * Displays an input hint for components that get the focus permanently.
502      */
503     private final class FocusChangeHandler
504         implements PropertyChangeListener {
505 
506         public void propertyChange(PropertyChangeEvent evt) {
507             String propertyName = evt.getPropertyName();
508             if (!"permanentFocusOwner".equals(propertyName)) {
509                 return;
510             }
511             Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
512             String focusHint = (focusOwner instanceof JComponent)
513                                ? (String)ValidationComponentUtils.getInputHint((JComponent)focusOwner) : null;
514 
515             hintArea.setText(focusHint);
516             hintAreaPane.setVisible(focusHint != null);
517         }
518     }
519 
520 }