View Javadoc

1   /**
2    * Copyright 2009 Timothy Johnston Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
3    * file except in compliance with the License. You may obtain a copy of the License at
4    * 
5    * http://www.apache.org/licenses/LICENSE-2.0
6    * 
7    * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
8    * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
9    * specific language governing permissions and limitations under the License.
10   */
11  package com.timjohnstondev.unitconverter.view;
12  
13  import java.awt.Color;
14  import java.awt.Component;
15  import java.awt.Font;
16  import java.awt.Point;
17  import java.awt.event.MouseAdapter;
18  import java.awt.event.MouseEvent;
19  import java.awt.event.MouseListener;
20  import java.awt.event.MouseMotionListener;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.ResourceBundle;
24  import javax.swing.DefaultListModel;
25  import javax.swing.JList;
26  import javax.swing.JPanel;
27  import javax.swing.ListModel;
28  import javax.swing.ListSelectionModel;
29  import javax.swing.UIManager;
30  import javax.swing.border.LineBorder;
31  import com.timjohnstondev.unitconverter.controller.LogicController;
32  import com.timjohnstondev.unitconverter.controller.ModelController;
33  import com.timjohnstondev.unitconverter.view.listener.FactorChangeEvent;
34  import com.timjohnstondev.unitconverter.view.listener.FactorChangeListener;
35  import com.timjohnstondev.unitconverter.view.listener.ReorderAdapter;
36  
37  /**
38   * This panel contains all of the {@link com.timjohnstondev.unitconverter.model.Property Property}s with their
39   * respective units. It provides a user interface to select the {@code Property}, From Unit and To Unit which are then
40   * sent to all FactorChangeListeners.
41   */
42  public class UnitPanel extends JPanel implements MouseMotionListener, MouseListener, Configuration
43  {
44    private LogicController logicController;
45    private ModelController modelController;
46    private JList propertyList;
47    private JList fromUnitList;
48    private JList toUnitList;
49    private MolarMassPanel molarMassPanel;
50    private List <FactorChangeListener> listeners;
51    private int cellHeight;
52    private String propertyListName;
53    private String fromUnitListName;
54    private String toUnitListName;
55    private String toolTipHtmlPrefix;
56    private String toolTipHtmlSuffix;
57    private boolean isMolUnitDivisor;
58    private boolean isPropertySelected;
59    private boolean isFromUnitSelected;
60    private boolean isToUnitSelected;
61  
62    /**
63     * Constructs a {@code UnitPanel} with a {@link ModelController} that provides access to the model layer.
64     * 
65     * @param newController the {@code Controller} to access to the model layer
66     */
67    public UnitPanel(final ModelController newController)
68    {
69      logicController = new LogicController();
70      modelController = newController;
71      loadResources(ResourceBundle.getBundle("com.timjohnstondev.unitconverter.view.View"));
72      listeners = new ArrayList <FactorChangeListener>();
73  
74      setLayout(null);
75  
76      propertyList = new JList();
77      fromUnitList = new JList();
78      toUnitList = new JList();
79      molarMassPanel = new MolarMassPanel(this);
80  
81      configureList(propertyList, propertyListName);
82      configureList(fromUnitList, fromUnitListName);
83      configureList(toUnitList, toUnitListName);
84      setPropertyListData();
85      setReorderListener(propertyList);
86  
87      propertyList.setBounds(10, 0, 170, 500);
88      fromUnitList.setBounds(180, 0, 105, 500);
89      toUnitList.setBounds(285, 0, 105, 500);
90      molarMassPanel.setBounds(390, 0, 105, 150);
91      molarMassPanel.setVisible(false);
92  
93      add(propertyList);
94      add(fromUnitList);
95      add(toUnitList);
96      add(molarMassPanel);
97  
98      addMouseListener(this);
99    }
100 
101   private void setReorderListener(final JList list)
102   {
103     final MouseAdapter listener = new ReorderAdapter(modelController, list);
104     list.addMouseListener(listener);
105     list.addMouseMotionListener(listener);
106   }
107 
108   private void setPropertyListData()
109   {
110     final DefaultListModel model = new DefaultListModel();
111     for (String propertyName : modelController.getPropertyNames())
112     {
113       model.addElement(propertyName);
114     }
115     propertyList.setModel(model);
116   }
117 
118   private void loadResources(final ResourceBundle resources)
119   {
120     cellHeight = Integer.parseInt(resources.getString("UnitPanel.cellHeight"));
121     propertyListName = resources.getString("UnitPanel.propertyListName");
122     fromUnitListName = resources.getString("UnitPanel.fromUnitListName");
123     toUnitListName = resources.getString("UnitPanel.toUnitListName");
124     toolTipHtmlPrefix = resources.getString("UnitPanel.toolTipHtmlPrefix");
125     toolTipHtmlSuffix = resources.getString("UnitPanel.toolTipHtmlSuffix");
126   }
127 
128   private void configureList(final JList list, final String listName)
129   {
130     list.setName(listName);
131     list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
132     list.addMouseMotionListener(this);
133     list.addMouseListener(this);
134     list.setFixedCellHeight(cellHeight);
135     list.setBackground(getBackground());
136   }
137 
138   @Override
139   public final void mouseDragged(final MouseEvent event)
140   {}
141 
142   @Override
143   public final void mouseMoved(final MouseEvent event)
144   {
145     final String originatorName = event.getComponent().getName();
146     final Point point = new Point(event.getX(), event.getY());
147 
148     if (originatorName.equals(propertyListName) && !isPropertySelected && hasChangedSelection(propertyList, point))
149     {
150       newPropertySelected(point);
151     }
152     else if (originatorName.equals(fromUnitListName) && !isFromUnitSelected && hasChangedSelection(fromUnitList, point))
153     {
154       newFromUnitSelected(point);
155     }
156     else if (originatorName.equals(toUnitListName) && !isToUnitSelected && hasChangedSelection(toUnitList, point))
157     {
158       newToUnitSelected(point);
159     }
160   }
161 
162   /**
163    * 
164    */
165   final void updateMassToMoleLabels()
166   {
167     insertMol(toUnitList);
168     removeMol(fromUnitList);
169     setMolUnitPosition(fromUnitList);
170   }
171 
172   /**
173    * 
174    */
175   final void updateMoleToMassLabels()
176   {
177     insertMol(fromUnitList);
178     removeMol(toUnitList);
179     setMolUnitPosition(fromUnitList);
180   }
181 
182   private void setMolUnitPosition(final JList list)
183   {
184     isMolUnitDivisor = logicController.isMolUnitDivisor(list.getModel());
185   }
186 
187   /**
188    * @return {@code true} if the mole unit will/was appended to the divisor, else {@code false}
189    */
190   final boolean isMolUnitDivisor()
191   {
192     return isMolUnitDivisor;
193   }
194 
195   /**
196    * 
197    */
198   final void clearMassMoleLabels()
199   {
200     removeMol(fromUnitList);
201     removeMol(toUnitList);
202   }
203 
204   private void removeMol(final JList list)
205   {
206     final int selectedIndex = list.getSelectedIndex();
207     final ListModel model = list.getModel();
208     final String[] newData = new String[model.getSize()];
209     for (int i = 0; i < model.getSize(); i++)
210     {
211       newData[i] = removeMol(model.getElementAt(i));
212     }
213     list.setListData(newData);
214     list.setSelectedIndex(selectedIndex);
215   }
216 
217   private void insertMol(final JList list)
218   {
219     logicController.insertMol(list, modelController.getUnitSeparator());
220   }
221 
222   private void positionMolarMassPanel()
223   {
224     if (modelController.usesMoles(getSelectedValue(propertyList)))
225     {
226       final Point pointFromIndex = toUnitList.indexToLocation(toUnitList.getSelectedIndex());
227       final int y = getY(pointFromIndex.getY() + toUnitList.getY(), molarMassPanel);
228       molarMassPanel.setBounds(molarMassPanel.getX(), y, molarMassPanel.getWidth(), molarMassPanel.getHeight());
229     }
230   }
231 
232   private void positionToUnitList()
233   {
234     final int y = getY(fromUnitList.getY(), toUnitList);
235     toUnitList.setBounds(toUnitList.getX(), y, toUnitList.getWidth(), toUnitList.getModel().getSize() * cellHeight);
236   }
237 
238   private void populateToUnitList(final String propertyName, final String fromUnitName)
239   {
240     final boolean hasMoles = logicController.hasMoles(toUnitList.getModel());
241     toUnitList.setListData(modelController.getSymbols(propertyName, fromUnitName).toArray());
242     if (hasMoles)
243     {
244       insertMol(toUnitList);
245     }
246   }
247 
248   private void showMolarMassPanel()
249   {
250     if (modelController.usesMoles(getSelectedValue(propertyList)))
251     {
252       molarMassPanel.showPanel(fromUnitList.getY());
253     }
254     else
255     {
256       molarMassPanel.hidePanel();
257     }
258   }
259 
260   private void positionFromUnitList()
261   {
262     fromUnitList.setListData(modelController.getSymbols(getSelectedValue(propertyList)).toArray());
263     final Point pointFromIndex = propertyList.indexToLocation(propertyList.getSelectedIndex());
264     final int y = getY(pointFromIndex.getY(), fromUnitList);
265     fromUnitList.setBounds(fromUnitList.getX(), y, fromUnitList.getWidth(), fromUnitList.getModel().getSize()
266         * cellHeight);
267   }
268 
269   private int getY(final double yOfEvent, final int componentHeight)
270   {
271     final int panelHeight = componentHeight;
272     final int frameHeight = getHeight();
273     final int y = (int) ((yOfEvent + panelHeight) < frameHeight ? yOfEvent : (frameHeight - panelHeight));
274     return y;
275   }
276 
277   private int getY(final double yOfEvent, final JPanel panel)
278   {
279     return getY(yOfEvent, panel.getHeight());
280   }
281 
282   private int getY(final double yOfEvent, final JList list)
283   {
284     final int listHeight = list.getModel().getSize() * cellHeight;
285     return getY(yOfEvent, listHeight);
286   }
287 
288   private void clearFactor()
289   {
290     for (FactorChangeListener listener : listeners)
291     {
292       listener.factorChanged(new FactorChangeEvent(this, FactorChangeEvent.CLEAR_FACTOR_ACTION));
293     }
294   }
295 
296   /**
297    * Notifies {@link com.timjohnstondev.unitconverter.view.listener.FactorChangeListener}s that a
298    * {@link com.timjohnstondev.unitconverter.view.listener.FactorChangeEvent} has occurred
299    */
300   final void updateFields()
301   {
302     final String[] conversionParts = loadConversionFactorParts();
303     if (conversionParts != null)
304     {
305       final String conversion = modelController.getConversion(conversionParts[0], conversionParts[1],
306           conversionParts[2]);
307       for (FactorChangeListener listener : listeners)
308       {
309         listener.factorChanged(new FactorChangeEvent(this, conversion, molarMassPanel.getMolecularWeightCorrection()));
310       }
311     }
312   }
313 
314   private String[] loadConversionFactorParts()
315   {
316     String[] conversionParts = null;
317     if (propertyList.getSelectedValue() != null && fromUnitList.getSelectedValue() != null
318         && toUnitList.getSelectedValue() != null)
319     {
320       conversionParts = new String[3];
321       conversionParts[0] = (String) propertyList.getSelectedValue();
322       conversionParts[1] = removeMol(fromUnitList.getSelectedValue());
323       conversionParts[2] = removeMol(toUnitList.getSelectedValue());
324     }
325     return conversionParts;
326   }
327 
328   private String removeMol(final Object unit)
329   {
330     return ((String) unit).replaceAll(modelController.getUnitSeparator() + "mol", "");
331   }
332 
333   private String getSelectedValue(final JList list)
334   {
335     return list.getSelectedValue().toString();
336   }
337 
338   private void setSelectedIndex(final JList list, final Point point)
339   {
340     final int index = list.locationToIndex(point);
341     list.setSelectedIndex(index);
342   }
343 
344   private boolean hasChangedSelection(final JList list, final Point point)
345   {
346     boolean changed = false;
347     final int index = list.locationToIndex(point);
348     if (index != list.getSelectedIndex())
349     {
350       list.setSelectedIndex(index);
351       changed = true;
352     }
353     return changed;
354   }
355 
356   /**
357    * Add a new FactorChangeListener to the listeners to notify in the event of any changes.
358    * 
359    * @param listener the listener to add
360    */
361   public final void addFactorChangeListener(final FactorChangeListener listener)
362   {
363     if (!listeners.contains(listener))
364     {
365       listeners.add(listener);
366     }
367   }
368 
369   @Override
370   public final void setBackgroundColor(final Color color)
371   {
372     setBackground(color);
373     molarMassPanel.setBackground(color);
374     final Component[] children = getComponents();
375     for (Component child : children)
376     {
377       if (child instanceof JList)
378       {
379         ((JList) child).setBackground(color);
380         ((JList) child).setSelectionBackground(color.brighter());
381       }
382     }
383     UIManager.put("ToolTip.background", color.brighter());
384     UIManager.put("ToolTip.border", new LineBorder(color.darker(), 1, true));
385     updateUI();
386   }
387 
388   @Override
389   public final void setForegroundColor(final Color color)
390   {
391     setForeground(color);
392     molarMassPanel.setForegroundColor(color);
393     final Component[] children = getComponents();
394     for (Component child : children)
395     {
396       if (child instanceof JList)
397       {
398         ((JList) child).setForeground(color);
399         ((JList) child).setSelectionForeground(color);
400       }
401     }
402     UIManager.put("ToolTip.foreground", color);
403     updateUI();
404   }
405 
406   @Override
407   public final void setFontSize(final float fontSize)
408   {
409     final Font font = getFont().deriveFont(fontSize);
410     setFont(font);
411     molarMassPanel.setFontSize(fontSize);
412     final Component[] children = getComponents();
413     for (Component child : children)
414     {
415       child.setFont(font);
416     }
417     if (fontSize < 13)
418     {
419       toolTipHtmlPrefix = toolTipHtmlPrefix.replace('4', '3');
420     }
421     if (fontSize >= 13)
422     {
423       toolTipHtmlPrefix = toolTipHtmlPrefix.replace('3', '4');
424     }
425   }
426 
427   public void mousePressed(final MouseEvent event)
428   {}
429 
430   public final void mouseReleased(final MouseEvent event)
431   {
432     final String originatorName = event.getComponent().getName();
433     final Point point = new Point(event.getX(), event.getY());
434 
435     if (originatorName == null)
436     {
437       isPropertySelected = false;
438       isFromUnitSelected = false;
439       isToUnitSelected = false;
440     }
441     else if (originatorName.equals(propertyListName))
442     {
443       newPropertySelected(point);
444       isPropertySelected = true;
445       isFromUnitSelected = false;
446       isToUnitSelected = false;
447     }
448     else if (originatorName.equals(fromUnitListName))
449     {
450       newFromUnitSelected(point);
451       isPropertySelected = true;
452       isFromUnitSelected = true;
453       isToUnitSelected = false;
454     }
455     else if (originatorName.equals(toUnitListName) && toUnitList.getModel().getSize() > 0)
456     {
457       newToUnitSelected(point);
458       isPropertySelected = true;
459       isFromUnitSelected = true;
460       isToUnitSelected = true;
461     }
462   }
463 
464   public void mouseEntered(final MouseEvent event)
465   {}
466 
467   public void mouseExited(final MouseEvent event)
468   {}
469 
470   public void mouseClicked(final MouseEvent event)
471   {}
472 
473   private void newPropertySelected(final Point point)
474   {
475     setSelectedIndex(propertyList, point);
476     clearFactor();
477     toUnitList.setListData(new Object[0]);
478     toUnitList.setToolTipText("");
479     positionFromUnitList();
480     showMolarMassPanel();
481   }
482 
483   private void newFromUnitSelected(final Point point)
484   {
485     setSelectedIndex(fromUnitList, point);
486     clearFactor();
487     final String propertyName = getSelectedValue(propertyList);
488     final String fromUnitName = removeMol(getSelectedValue(fromUnitList));
489     fromUnitList.setToolTipText(toolTipHtmlPrefix + modelController.getUnitName(propertyName, fromUnitName)
490         + toolTipHtmlSuffix);
491     populateToUnitList(propertyName, fromUnitName);
492     positionToUnitList();
493   }
494 
495   private void newToUnitSelected(final Point point)
496   {
497     setSelectedIndex(toUnitList, point);
498     updateFields();
499     final String propertyName = getSelectedValue(propertyList);
500     final String toUnitName = removeMol(getSelectedValue(toUnitList));
501     toUnitList.setToolTipText(toolTipHtmlPrefix + modelController.getUnitName(propertyName, toUnitName)
502         + toolTipHtmlSuffix);
503     positionMolarMassPanel();
504   }
505 
506 }