Home | History | Annotate | Download | only in controls
      1 /*******************************************************************************
      2  * Copyright (c) 2011 Google, Inc.
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *    Google, Inc. - initial API and implementation
     10  *******************************************************************************/
     11 package org.eclipse.wb.core.controls;
     12 
     13 import org.eclipse.swt.SWT;
     14 import org.eclipse.swt.events.FocusAdapter;
     15 import org.eclipse.swt.events.FocusEvent;
     16 import org.eclipse.swt.events.KeyAdapter;
     17 import org.eclipse.swt.events.KeyEvent;
     18 import org.eclipse.swt.events.SelectionAdapter;
     19 import org.eclipse.swt.events.SelectionEvent;
     20 import org.eclipse.swt.graphics.Color;
     21 import org.eclipse.swt.graphics.Point;
     22 import org.eclipse.swt.graphics.Rectangle;
     23 import org.eclipse.swt.widgets.Button;
     24 import org.eclipse.swt.widgets.Composite;
     25 import org.eclipse.swt.widgets.Display;
     26 import org.eclipse.swt.widgets.Event;
     27 import org.eclipse.swt.widgets.Layout;
     28 import org.eclipse.swt.widgets.Spinner;
     29 import org.eclipse.swt.widgets.Text;
     30 
     31 import java.text.DecimalFormat;
     32 import java.text.MessageFormat;
     33 import java.text.ParseException;
     34 
     35 /**
     36  * Custom implementation of {@link Spinner}.
     37  *
     38  * @author scheglov_ke
     39  * @coverage core.control
     40  */
     41 public class CSpinner extends Composite {
     42   private static final Color COLOR_VALID = Display.getCurrent().getSystemColor(
     43       SWT.COLOR_LIST_BACKGROUND);
     44   private static final Color COLOR_INVALID = new Color(null, 255, 230, 230);
     45   private int m_minimum = 0;
     46   private int m_maximum = 100;
     47   private int m_increment = 1;
     48   private int m_value = 0;
     49   private int m_multiplier = 1;
     50   private String m_formatPattern = "0";
     51   private DecimalFormat m_format = new DecimalFormat(m_formatPattern);
     52   ////////////////////////////////////////////////////////////////////////////
     53   //
     54   // GUI fields
     55   //
     56   ////////////////////////////////////////////////////////////////////////////
     57   private final Button m_button;
     58   private final Text m_text;
     59   private final Spinner m_spinner;
     60   private Composite win32Hack;
     61 
     62   ////////////////////////////////////////////////////////////////////////////
     63   //
     64   // Constructor
     65   //
     66   ////////////////////////////////////////////////////////////////////////////
     67   public CSpinner(Composite parent, int style) {
     68     super(parent, style);
     69     m_button = new Button(this, SWT.ARROW | SWT.DOWN);
     70     {
     71       int textStyle = SWT.SINGLE | SWT.RIGHT;
     72       if (IS_OS_MAC_OSX_COCOA) {
     73         textStyle |= SWT.BORDER;
     74       }
     75       m_text = new Text(this, textStyle);
     76       m_text.setText("" + m_value);
     77       m_text.addKeyListener(new KeyAdapter() {
     78         @Override
     79         public void keyPressed(KeyEvent e) {
     80           if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
     81             e.doit = false;
     82             updateValue(e.keyCode);
     83           }
     84         }
     85 
     86         @Override
     87         public void keyReleased(KeyEvent e) {
     88           try {
     89             m_value = (int) (m_format.parse(m_text.getText()).doubleValue() * m_multiplier);
     90             if (m_value < m_minimum || m_value > m_maximum) {
     91               m_text.setBackground(COLOR_INVALID);
     92               setState(MessageFormat.format(
     93                   Messages.CSpinner_outOfRange,
     94                   m_value,
     95                   m_minimum,
     96                   m_maximum));
     97               notifySelectionListeners(false);
     98             } else {
     99               setState(null);
    100               notifySelectionListeners(true);
    101             }
    102           } catch (ParseException ex) {
    103             setState(MessageFormat.format(
    104                 Messages.CSpinner_canNotParse,
    105                 m_text.getText(),
    106                 m_formatPattern));
    107             notifySelectionListeners(false);
    108           }
    109         }
    110       });
    111     }
    112     if (!IS_OS_MAC_OSX) {
    113       win32Hack = new Composite(this, SWT.NONE);
    114       win32Hack.setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
    115       win32Hack.moveAbove(null);
    116       win32Hack.moveBelow(m_text);
    117     }
    118     {
    119       m_spinner = new Spinner(this, SWT.VERTICAL);
    120       m_spinner.setMinimum(0);
    121       m_spinner.setMaximum(50);
    122       m_spinner.setIncrement(1);
    123       m_spinner.setPageIncrement(1);
    124       m_spinner.setSelection(25);
    125       m_spinner.addFocusListener(new FocusAdapter() {
    126         @Override
    127         public void focusGained(FocusEvent e) {
    128           setFocus();
    129         }
    130       });
    131       m_spinner.addSelectionListener(new SelectionAdapter() {
    132         @Override
    133         public void widgetSelected(SelectionEvent e) {
    134           m_text.forceFocus();
    135           if (m_spinner.getSelection() > 25) {
    136             updateValue(SWT.ARROW_UP);
    137           } else {
    138             updateValue(SWT.ARROW_DOWN);
    139           }
    140           m_spinner.setSelection(25);
    141         }
    142       });
    143       setBackground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
    144       if (IS_OS_WINDOWS_XP || IS_OS_WINDOWS_2003) {
    145         setLayout(new WindowsXpLayout());
    146       } else if (IS_OS_WINDOWS_VISTA || IS_OS_WINDOWS_7) {
    147         setLayout(new WindowsVistaLayout());
    148       } else if (IS_OS_LINUX) {
    149         setLayout(new LinuxLayout());
    150       } else if (IS_OS_MAC_OSX) {
    151         if (IS_OS_MAC_OSX_COCOA) {
    152           setLayout(new MacCocoaLayout());
    153         } else {
    154           setLayout(new MacLayout());
    155         }
    156       } else {
    157         setLayout(new WindowsXpLayout());
    158       }
    159     }
    160   }
    161 
    162   ////////////////////////////////////////////////////////////////////////////
    163   //
    164   // Access
    165   //
    166   ////////////////////////////////////////////////////////////////////////////
    167   @Override
    168   public void setEnabled(boolean enabled) {
    169     super.setEnabled(enabled);
    170     m_text.setEnabled(enabled);
    171     m_spinner.setEnabled(enabled);
    172   }
    173 
    174   /**
    175    * Sets the number of decimal places used by the receiver.
    176    * <p>
    177    * See {@link Spinner#setDigits(int)}.
    178    */
    179   public void setDigits(int digits) {
    180     m_formatPattern = "0.";
    181     m_multiplier = 1;
    182     for (int i = 0; i < digits; i++) {
    183       m_formatPattern += "0";
    184       m_multiplier *= 10;
    185     }
    186     m_format = new DecimalFormat(m_formatPattern);
    187     updateText();
    188   }
    189 
    190   /**
    191    * Sets minimum and maximum using single invocation.
    192    */
    193   public void setRange(int minimum, int maximum) {
    194     setMinimum(minimum);
    195     setMaximum(maximum);
    196   }
    197 
    198   /**
    199    * @return the minimum value that the receiver will allow.
    200    */
    201   public int getMinimum() {
    202     return m_minimum;
    203   }
    204 
    205   /**
    206    * Sets the minimum value that the receiver will allow.
    207    */
    208   public void setMinimum(int minimum) {
    209     m_minimum = minimum;
    210     setSelection(Math.max(m_value, m_minimum));
    211   }
    212 
    213   /**
    214    * Sets the maximum value that the receiver will allow.
    215    */
    216   public void setMaximum(int maximum) {
    217     m_maximum = maximum;
    218     setSelection(Math.min(m_value, m_maximum));
    219   }
    220 
    221   /**
    222    * Sets the amount that the receiver's value will be modified by when the up/down arrows are
    223    * pressed to the argument, which must be at least one.
    224    */
    225   public void setIncrement(int increment) {
    226     m_increment = increment;
    227   }
    228 
    229   /**
    230    * Sets the <em>value</em>, which is the receiver's position, to the argument. If the argument is
    231    * not within the range specified by minimum and maximum, it will be adjusted to fall within this
    232    * range.
    233    */
    234   public void setSelection(int newValue) {
    235     newValue = Math.min(Math.max(m_minimum, newValue), m_maximum);
    236     if (newValue != m_value) {
    237       m_value = newValue;
    238       updateText();
    239       // set valid state
    240       setState(null);
    241     }
    242   }
    243 
    244   private void updateText() {
    245     String text = m_format.format((double) m_value / m_multiplier);
    246     m_text.setText(text);
    247     m_text.selectAll();
    248   }
    249 
    250   /**
    251    * @return the <em>selection</em>, which is the receiver's position.
    252    */
    253   public int getSelection() {
    254     return m_value;
    255   }
    256 
    257   ////////////////////////////////////////////////////////////////////////////
    258   //
    259   // Update
    260   //
    261   ////////////////////////////////////////////////////////////////////////////
    262   /**
    263    * Updates {@link #m_value} into given direction.
    264    */
    265   private void updateValue(int direction) {
    266     // prepare new value
    267     int newValue;
    268     {
    269       newValue = m_value;
    270       if (direction == SWT.ARROW_UP) {
    271         newValue += m_increment;
    272       }
    273       if (direction == SWT.ARROW_DOWN) {
    274         newValue -= m_increment;
    275       }
    276     }
    277     // update value
    278     setSelection(newValue);
    279     notifySelectionListeners(true);
    280   }
    281 
    282   /**
    283    * Sets the valid/invalid state.
    284    *
    285    * @param message
    286    *          the message to show, or <code>null</code> if valid.
    287    */
    288   private void setState(String message) {
    289     m_text.setToolTipText(message);
    290     if (message == null) {
    291       m_text.setBackground(COLOR_VALID);
    292     } else {
    293       m_text.setBackground(COLOR_INVALID);
    294     }
    295   }
    296 
    297   /**
    298    * Notifies {@link SWT#Selection} listeners with value and state.
    299    */
    300   private void notifySelectionListeners(boolean valid) {
    301     Event event = new Event();
    302     event.detail = m_value;
    303     event.doit = valid;
    304     notifyListeners(SWT.Selection, event);
    305   }
    306 
    307   ////////////////////////////////////////////////////////////////////////////
    308   //
    309   // Windows XP
    310   //
    311   ////////////////////////////////////////////////////////////////////////////
    312   /**
    313    * Implementation of {@link Layout} for Windows XP.
    314    */
    315   private class WindowsXpLayout extends Layout {
    316     @Override
    317     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
    318       Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    319       size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
    320       // add Text widget margin
    321       size.y += 2;
    322       // apply hints
    323       if (wHint != SWT.DEFAULT) {
    324         size.x = Math.min(size.x, wHint);
    325       }
    326       if (hHint != SWT.DEFAULT) {
    327         size.y = Math.min(size.y, hHint);
    328       }
    329       // OK, final size
    330       return size;
    331     }
    332 
    333     @Override
    334     protected void layout(Composite composite, boolean flushCache) {
    335       Rectangle cRect = composite.getClientArea();
    336       if (cRect.isEmpty()) {
    337         return;
    338       }
    339       // prepare size of Text
    340       Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    341       // prepare size of Spinner
    342       Point sSize;
    343       sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
    344       sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
    345       sSize.x = Math.min(sSize.x, cRect.width);
    346       // prepare width of arrows part of Spinner
    347       int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
    348       // set bounds for Spinner and Text
    349       m_spinner.setBounds(
    350           cRect.x + cRect.width - sSize.x + 1,
    351           cRect.y - 1,
    352           sSize.x,
    353           cRect.height + 2);
    354       m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y);
    355       win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y);
    356     }
    357   }
    358   ////////////////////////////////////////////////////////////////////////////
    359   //
    360   // Windows Vista
    361   //
    362   ////////////////////////////////////////////////////////////////////////////
    363   /**
    364    * Implementation of {@link Layout} for Windows Vista.
    365    */
    366   private class WindowsVistaLayout extends Layout {
    367     @Override
    368     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
    369       Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    370       size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
    371       // add Text widget margin
    372       size.y += 3;
    373       // apply hints
    374       if (wHint != SWT.DEFAULT) {
    375         size.x = Math.min(size.x, wHint);
    376       }
    377       if (hHint != SWT.DEFAULT) {
    378         size.y = Math.min(size.y, hHint);
    379       }
    380       // OK, final size
    381       return size;
    382     }
    383 
    384     @Override
    385     protected void layout(Composite composite, boolean flushCache) {
    386       Rectangle cRect = composite.getClientArea();
    387       if (cRect.isEmpty()) {
    388         return;
    389       }
    390       // prepare size of Text
    391       Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    392       // prepare size of Spinner
    393       Point sSize;
    394       sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
    395       sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
    396       sSize.x = Math.min(sSize.x, cRect.width);
    397       // prepare width of arrows part of Spinner
    398       int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
    399       // set bounds for Spinner and Text
    400       m_spinner.setBounds(
    401           cRect.x + cRect.width - sSize.x + 1,
    402           cRect.y - 1,
    403           sSize.x,
    404           cRect.height + 2);
    405       m_text.setBounds(cRect.x, cRect.y + 1, cRect.width - arrowWidth, tSize.y);
    406       win32Hack.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, sSize.y);
    407     }
    408   }
    409   ////////////////////////////////////////////////////////////////////////////
    410   //
    411   // Linux
    412   //
    413   ////////////////////////////////////////////////////////////////////////////
    414   /**
    415    * Implementation of {@link Layout} for Linux.
    416    */
    417   private class LinuxLayout extends Layout {
    418     @Override
    419     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
    420       Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    421       size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
    422       // apply hints
    423       if (wHint != SWT.DEFAULT) {
    424         size.x = Math.min(size.x, wHint);
    425       }
    426       if (hHint != SWT.DEFAULT) {
    427         size.y = Math.min(size.y, hHint);
    428       }
    429       // OK, final size
    430       return size;
    431     }
    432 
    433     @Override
    434     protected void layout(Composite composite, boolean flushCache) {
    435       Rectangle cRect = composite.getClientArea();
    436       if (cRect.isEmpty()) {
    437         return;
    438       }
    439       // prepare size of Text
    440       Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    441       // prepare size of Spinner
    442       Point sSize;
    443       sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
    444       sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
    445       sSize.x = Math.min(sSize.x, cRect.width);
    446       // prepare width of arrows part of Spinner
    447       int arrowWidth;
    448       {
    449         m_spinner.setSize(sSize);
    450         arrowWidth = sSize.x - m_spinner.getClientArea().width;
    451       }
    452       // set bounds for Spinner and Text
    453       m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y - 2, sSize.x, cRect.height + 4);
    454       m_text.setBounds(cRect.x, cRect.y, cRect.width - arrowWidth, tSize.y);
    455     }
    456   }
    457   ////////////////////////////////////////////////////////////////////////////
    458   //
    459   // MacOSX
    460   //
    461   ////////////////////////////////////////////////////////////////////////////
    462   /**
    463    * Implementation of {@link Layout} for MacOSX.
    464    */
    465   private class MacLayout extends Layout {
    466     @Override
    467     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
    468       Point size = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    469       size.x += m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT).x - m_spinner.getClientArea().width;
    470       // add Text widget margin
    471       size.y += 4;
    472       // apply hints
    473       if (wHint != SWT.DEFAULT) {
    474         size.x = Math.min(size.x, wHint);
    475       }
    476       if (hHint != SWT.DEFAULT) {
    477         size.y = Math.min(size.y, hHint);
    478       }
    479       // OK, final size
    480       return size;
    481     }
    482 
    483     @Override
    484     protected void layout(Composite composite, boolean flushCache) {
    485       Rectangle cRect = composite.getClientArea();
    486       if (cRect.isEmpty()) {
    487         return;
    488       }
    489       // prepare size of Text
    490       Point tSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    491       tSize.y += 4;
    492       // prepare size of Spinner
    493       Point sSize;
    494       sSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
    495       sSize.y = Math.min(sSize.y, Math.min(tSize.y, cRect.height));
    496       sSize.x = Math.min(sSize.x, cRect.width);
    497       // prepare width of arrows part of Spinner
    498       int arrowWidth = m_button.computeSize(-1, -1).x;
    499       // set bounds for Spinner and Text
    500       m_spinner.setBounds(cRect.x + cRect.width - sSize.x, cRect.y, sSize.x, cRect.height);
    501       m_text.setBounds(cRect.x, cRect.y + 2, cRect.width - arrowWidth - 2, tSize.y);
    502     }
    503   }
    504   /**
    505    * Implementation of {@link Layout} for MacOSX Cocoa.
    506    */
    507   private class MacCocoaLayout extends Layout {
    508     @Override
    509     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
    510       Point textSize = m_text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    511       Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT);
    512       int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
    513       int width = textSize.x + arrowWidth;
    514       int height = Math.max(spinnerSize.y, textSize.y);
    515       // apply hints
    516       if (wHint != SWT.DEFAULT) {
    517         width = Math.min(width, wHint);
    518       }
    519       if (hHint != SWT.DEFAULT) {
    520         height = Math.min(height, hHint);
    521       }
    522       return new Point(width, height);
    523     }
    524 
    525     @Override
    526     protected void layout(Composite composite, boolean flushCache) {
    527       Rectangle clientArea = composite.getClientArea();
    528       if (clientArea.isEmpty()) {
    529         return;
    530       }
    531       // prepare size of Spinner
    532       Point spinnerSize = m_spinner.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache);
    533       // prepare width of arrows part of Spinner
    534       int arrowWidth = m_button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
    535       m_spinner.setBounds(clientArea.x + clientArea.width - arrowWidth - 1, clientArea.y
    536           + clientArea.height
    537           - spinnerSize.y, arrowWidth + 2, spinnerSize.y);
    538       m_text.setBounds(
    539           clientArea.x + 2,
    540           clientArea.y + 2,
    541           clientArea.width - arrowWidth - 5,
    542           clientArea.y + clientArea.height - 4);
    543     }
    544   }
    545 
    546   ////////////////////////////////////////////////////////////////////////////
    547   //
    548   // System utils
    549   //
    550   ////////////////////////////////////////////////////////////////////////////
    551   private static final String OS_NAME = System.getProperty("os.name");
    552   private static final String OS_VERSION = System.getProperty("os.version");
    553   private static final String WS_TYPE = SWT.getPlatform();
    554   private static final boolean IS_OS_MAC_OSX = isOS("Mac OS X");
    555   private static final boolean IS_OS_MAC_OSX_COCOA = IS_OS_MAC_OSX && "cocoa".equals(WS_TYPE);
    556   private static final boolean IS_OS_LINUX = isOS("Linux") || isOS("LINUX");
    557   private static final boolean IS_OS_WINDOWS_XP = isWindowsVersion("5.1");
    558   private static final boolean IS_OS_WINDOWS_2003 = isWindowsVersion("5.2");
    559   private static final boolean IS_OS_WINDOWS_VISTA = isWindowsVersion("6.0");
    560   private static final boolean IS_OS_WINDOWS_7 = isWindowsVersion("6.1");
    561 
    562   private static boolean isOS(String osName) {
    563     return OS_NAME != null && OS_NAME.startsWith(osName);
    564   }
    565 
    566   private static boolean isWindowsVersion(String windowsVersion) {
    567     return isOS("Windows") && OS_VERSION != null && OS_VERSION.startsWith(windowsVersion);
    568   }
    569 }
    570