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 com.google.common.collect.Lists;
     14 
     15 import org.eclipse.jface.viewers.IBaseLabelProvider;
     16 import org.eclipse.jface.viewers.IContentProvider;
     17 import org.eclipse.jface.viewers.IStructuredContentProvider;
     18 import org.eclipse.jface.viewers.LabelProvider;
     19 import org.eclipse.jface.viewers.TableViewer;
     20 import org.eclipse.jface.viewers.TableViewerColumn;
     21 import org.eclipse.jface.viewers.Viewer;
     22 import org.eclipse.jface.viewers.ViewerFilter;
     23 import org.eclipse.swt.SWT;
     24 import org.eclipse.swt.events.ControlAdapter;
     25 import org.eclipse.swt.events.ControlEvent;
     26 import org.eclipse.swt.events.DisposeEvent;
     27 import org.eclipse.swt.events.DisposeListener;
     28 import org.eclipse.swt.events.KeyAdapter;
     29 import org.eclipse.swt.events.KeyEvent;
     30 import org.eclipse.swt.events.ModifyEvent;
     31 import org.eclipse.swt.events.ModifyListener;
     32 import org.eclipse.swt.events.PaintEvent;
     33 import org.eclipse.swt.events.PaintListener;
     34 import org.eclipse.swt.events.SelectionAdapter;
     35 import org.eclipse.swt.events.SelectionEvent;
     36 import org.eclipse.swt.events.SelectionListener;
     37 import org.eclipse.swt.events.TypedEvent;
     38 import org.eclipse.swt.graphics.Image;
     39 import org.eclipse.swt.graphics.Point;
     40 import org.eclipse.swt.graphics.Rectangle;
     41 import org.eclipse.swt.layout.FillLayout;
     42 import org.eclipse.swt.widgets.Button;
     43 import org.eclipse.swt.widgets.Canvas;
     44 import org.eclipse.swt.widgets.Composite;
     45 import org.eclipse.swt.widgets.Display;
     46 import org.eclipse.swt.widgets.Event;
     47 import org.eclipse.swt.widgets.Listener;
     48 import org.eclipse.swt.widgets.Shell;
     49 import org.eclipse.swt.widgets.Table;
     50 import org.eclipse.swt.widgets.TableColumn;
     51 import org.eclipse.swt.widgets.TableItem;
     52 import org.eclipse.swt.widgets.Text;
     53 import org.eclipse.swt.widgets.TypedListener;
     54 import org.eclipse.wb.internal.core.model.property.editor.TextControlActionsManager;
     55 import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
     56 import org.eclipse.wb.internal.core.utils.check.Assert;
     57 
     58 import java.util.ArrayList;
     59 
     60 /**
     61  * Extended ComboBox control for {@link PropertyTable} and combo property editors.
     62  *
     63  * @author sablin_aa
     64  * @coverage core.control
     65  */
     66 public class CComboBox extends Composite {
     67   private Text m_text;
     68   private Button m_button;
     69   private Canvas m_canvas;
     70   private Shell m_popup;
     71   private TableViewer m_table;
     72   private boolean m_fullDropdownTableWidth = false;
     73   private boolean m_wasFocused;
     74 
     75   ////////////////////////////////////////////////////////////////////////////
     76   //
     77   // Constructor
     78   //
     79   ////////////////////////////////////////////////////////////////////////////
     80   public CComboBox(Composite parent, int style) {
     81     super(parent, style);
     82     createContents(this);
     83     m_wasFocused = isComboFocused();
     84     // add display hook
     85     final Listener displayFocusInHook = new Listener() {
     86       @Override
     87     public void handleEvent(Event event) {
     88         boolean focused = isComboFocused();
     89         if (m_wasFocused && !focused) {
     90           // close DropDown on focus out ComboBox
     91           comboDropDown(false);
     92         }
     93         if (event.widget != CComboBox.this) {
     94           // forward to ComboBox listeners
     95           if (!m_wasFocused && focused) {
     96             event.widget = CComboBox.this;
     97             notifyListeners(SWT.FocusIn, event);
     98           }
     99           if (m_wasFocused && !focused) {
    100             event.widget = CComboBox.this;
    101             notifyListeners(SWT.FocusOut, event);
    102           }
    103         }
    104         m_wasFocused = focused;
    105       }
    106     };
    107     final Listener displayFocusOutHook = new Listener() {
    108       @Override
    109     public void handleEvent(Event event) {
    110         m_wasFocused = isComboFocused();
    111       }
    112     };
    113     {
    114       Display display = getDisplay();
    115       display.addFilter(SWT.FocusIn, displayFocusInHook);
    116       display.addFilter(SWT.FocusOut, displayFocusOutHook);
    117     }
    118     // combo listeners
    119     addControlListener(new ControlAdapter() {
    120       @Override
    121       public void controlResized(ControlEvent e) {
    122         resizeInner();
    123       }
    124     });
    125     addDisposeListener(new DisposeListener() {
    126       @Override
    127     public void widgetDisposed(DisposeEvent e) {
    128         {
    129           // remove Display hooks
    130           Display display = getDisplay();
    131           display.removeFilter(SWT.FocusIn, displayFocusInHook);
    132           display.removeFilter(SWT.FocusOut, displayFocusOutHook);
    133         }
    134         disposeInner();
    135       }
    136     });
    137   }
    138 
    139   ////////////////////////////////////////////////////////////////////////////
    140   //
    141   // Contents
    142   //
    143   ////////////////////////////////////////////////////////////////////////////
    144   protected void createContents(Composite parent) {
    145     createText(parent);
    146     createButton(parent);
    147     createImage(parent);
    148     createPopup(parent);
    149   }
    150 
    151   /**
    152    * Create Text widget.
    153    */
    154   protected void createText(Composite parent) {
    155     m_text = new Text(parent, SWT.NONE);
    156     new TextControlActionsManager(m_text);
    157     // key press processing
    158     m_text.addKeyListener(new KeyAdapter() {
    159       @Override
    160       public void keyPressed(KeyEvent e) {
    161         switch (e.keyCode) {
    162           case SWT.ESC :
    163             if (isDroppedDown()) {
    164               // close dropdown
    165               comboDropDown(false);
    166               e.doit = false;
    167             } else {
    168               // forward to ComboBox listeners
    169               notifyListeners(SWT.KeyDown, convert2event(e));
    170             }
    171             break;
    172           case SWT.ARROW_UP :
    173             if (isDroppedDown()) {
    174               // prev item in dropdown list
    175               Table table = m_table.getTable();
    176               int index = table.getSelectionIndex() - 1;
    177               table.setSelection(index < 0 ? table.getItemCount() - 1 : index);
    178               e.doit = false;
    179             } else {
    180               // forward to ComboBox listeners
    181               notifyListeners(SWT.KeyDown, convert2event(e));
    182             }
    183             break;
    184           case SWT.ARROW_DOWN :
    185             if (isDroppedDown()) {
    186               // next item in dropdown list
    187               Table table = m_table.getTable();
    188               int index = table.getSelectionIndex() + 1;
    189               table.setSelection(index == table.getItemCount() ? 0 : index);
    190               e.doit = false;
    191             } else if ((e.stateMask & SWT.ALT) != 0) {
    192               // force drop down combo
    193               comboDropDown(true);
    194               e.doit = false;
    195               // return focus to text
    196               setFocus2Text(false);
    197             } else {
    198               // forward to ComboBox listeners
    199               notifyListeners(SWT.KeyDown, convert2event(e));
    200             }
    201             break;
    202           case '\r' :
    203             Table table = m_table.getTable();
    204             if (isDroppedDown() && table.getSelectionIndex() != -1) {
    205               // forward to Table listeners
    206               table.notifyListeners(SWT.Selection, convert2event(e));
    207             } else {
    208               m_text.selectAll();
    209               setSelectionText(getEditText());
    210               // forward to ComboBox listeners
    211               notifyListeners(SWT.Selection, convert2event(e));
    212             }
    213             break;
    214         }
    215       }
    216     });
    217     // modifications processing
    218     m_text.addModifyListener(new ModifyListener() {
    219       @Override
    220     public void modifyText(ModifyEvent e) {
    221         if (isDroppedDown()) {
    222           m_table.refresh();
    223         } else {
    224           // force drop down combo
    225           if (m_text.isFocusControl()) {
    226             comboDropDown(true);
    227             // return focus to text
    228             setFocus2Text(false);
    229           }
    230         }
    231       }
    232     });
    233   }
    234 
    235   /**
    236    * Create arrow button.
    237    */
    238   protected void createButton(Composite parent) {
    239     m_button = new Button(parent, SWT.ARROW | SWT.DOWN);
    240     m_button.addSelectionListener(new SelectionAdapter() {
    241       @Override
    242       public void widgetSelected(SelectionEvent e) {
    243         comboDropDown(!isDroppedDown());
    244         // return focus to text
    245         setFocus2Text(true);
    246       }
    247     });
    248   }
    249 
    250   /**
    251    * Create image canvas.
    252    */
    253   protected void createImage(Composite parent) {
    254     m_canvas = new Canvas(parent, SWT.BORDER);
    255     m_canvas.addPaintListener(new PaintListener() {
    256       @Override
    257     public void paintControl(PaintEvent e) {
    258         Image selectionImage = getSelectionImage();
    259         if (selectionImage != null) {
    260           e.gc.drawImage(selectionImage, 0, 0);
    261         } else {
    262           e.gc.fillRectangle(m_canvas.getClientArea());
    263         }
    264       }
    265     });
    266   }
    267 
    268   /**
    269    * Create popup shell with table.
    270    */
    271   protected void createPopup(Composite parent) {
    272     m_popup = new Shell(getShell(), SWT.BORDER);
    273     m_popup.setLayout(new FillLayout());
    274     createTable(m_popup);
    275   }
    276 
    277   /**
    278    * Create table.
    279    */
    280   protected void createTable(Composite parent) {
    281     m_table = new TableViewer(parent, SWT.FULL_SELECTION);
    282     new TableViewerColumn(m_table, SWT.LEFT);
    283     m_table.getTable().addSelectionListener(new SelectionAdapter() {
    284       @Override
    285       public void widgetSelected(SelectionEvent e) {
    286         int selectionIndex = m_table.getTable().getSelectionIndex();
    287         setSelectionIndex(selectionIndex);
    288         comboDropDown(false);
    289         // forward to ComboBox listeners
    290         notifyListeners(SWT.Selection, convert2event(e));
    291       }
    292     });
    293     m_table.setContentProvider(getContentProvider());
    294     m_table.setLabelProvider(getLabelProvider());
    295     m_table.addFilter(getFilterProvider());
    296   }
    297 
    298   /**
    299    * Placement inner widgets.
    300    */
    301   protected void resizeInner() {
    302     Rectangle clientArea = getClientArea();
    303     int rightOccupied = 0;
    304     int leftOccupied = 0;
    305     {
    306       // button
    307       m_button.setBounds(
    308           clientArea.width - clientArea.height,
    309           0,
    310           clientArea.height,
    311           clientArea.height);
    312       rightOccupied = clientArea.height;
    313     }
    314     {
    315       Image selectionImage = getSelectionImage();
    316       if (selectionImage != null) {
    317         // image
    318         m_canvas.setSize(clientArea.height, clientArea.height);
    319         leftOccupied = clientArea.height;
    320       } else {
    321         m_canvas.setSize(1, clientArea.height);
    322         leftOccupied = 1;
    323       }
    324     }
    325     {
    326       // text
    327       m_text.setBounds(
    328           leftOccupied,
    329           0,
    330           clientArea.width - rightOccupied - leftOccupied,
    331           clientArea.height);
    332     }
    333   }
    334 
    335   /**
    336    * Dispose inner widgets.
    337    */
    338   protected void disposeInner() {
    339     if (!m_popup.isDisposed()) {
    340       m_popup.dispose();
    341     }
    342   }
    343 
    344   ////////////////////////////////////////////////////////////////////////////
    345   //
    346   // Providers
    347   //
    348   ////////////////////////////////////////////////////////////////////////////
    349   protected IContentProvider getContentProvider() {
    350     return new IStructuredContentProvider() {
    351       @Override
    352     public Object[] getElements(Object inputElement) {
    353         return m_items.toArray(new ComboBoxItem[m_items.size()]);
    354       }
    355 
    356       @Override
    357     public void dispose() {
    358       }
    359 
    360       @Override
    361     public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    362       }
    363     };
    364   }
    365 
    366   protected IBaseLabelProvider getLabelProvider() {
    367     return new LabelProvider() {
    368       @Override
    369       public Image getImage(Object element) {
    370         ComboBoxItem item = (ComboBoxItem) element;
    371         return item.m_image;
    372       }
    373 
    374       @Override
    375       public String getText(Object element) {
    376         ComboBoxItem item = (ComboBoxItem) element;
    377         return item.m_label;
    378       }
    379     };
    380   }
    381 
    382   protected ViewerFilter getFilterProvider() {
    383     return new ViewerFilter() {
    384       @Override
    385       public boolean select(Viewer viewer, Object parentElement, Object element) {
    386         String lookingString = m_text.getText().toLowerCase();
    387         if (isDroppedDown() && lookingString.length() > 0) {
    388           ComboBoxItem item = (ComboBoxItem) element;
    389           return item.m_label.toLowerCase().indexOf(lookingString) != -1;
    390         }
    391         return true;
    392       }
    393     };
    394   }
    395 
    396   ////////////////////////////////////////////////////////////////////////////
    397   //
    398   // Items
    399   //
    400   ////////////////////////////////////////////////////////////////////////////
    401   protected static class ComboBoxItem {
    402     public final String m_label;
    403     public final Image m_image;
    404 
    405     public ComboBoxItem(String label, Image image) {
    406       m_label = label;
    407       m_image = image;
    408     }
    409   }
    410 
    411   ArrayList<ComboBoxItem> m_items = Lists.newArrayList();
    412 
    413   /**
    414    * Add new item.
    415    */
    416   public void addItem(String label, Image image) {
    417     Assert.isTrue(!isDroppedDown());
    418     m_items.add(new ComboBoxItem(label, image));
    419   }
    420 
    421   public void addItem(String label) {
    422     addItem(label, null);
    423   }
    424 
    425   public void removeAll() {
    426     m_items.clear();
    427   }
    428 
    429   public int getItemCount() {
    430     return m_items.size();
    431   }
    432 
    433   public String getItemLabel(int index) {
    434     return m_items.get(index).m_label;
    435   }
    436 
    437   ////////////////////////////////////////////////////////////////////////////
    438   //
    439   // Access
    440   //
    441   ////////////////////////////////////////////////////////////////////////////
    442   public boolean isComboFocused() {
    443     return isFocusControl()
    444         || m_text.isFocusControl()
    445         || m_button.isFocusControl()
    446         || m_canvas.isFocusControl()
    447         || m_popup.isFocusControl()
    448         || m_table.getTable().isFocusControl();
    449   }
    450 
    451   /**
    452    * Edit text.
    453    */
    454   public String getEditText() {
    455     return m_text.getText();
    456   }
    457 
    458   public void setEditText(String text) {
    459     m_text.setText(text == null ? "" : text);
    460     m_text.selectAll();
    461   }
    462 
    463   public void setEditSelection(int start, int end) {
    464     m_text.setSelection(start, end);
    465   }
    466 
    467   /**
    468    * Read only.
    469    */
    470   public void setReadOnly(boolean value) {
    471     m_text.setEditable(!value);
    472     m_button.setEnabled(!value);
    473   }
    474 
    475   /**
    476    * Drop down width.
    477    */
    478   public boolean isFullDropdownTableWidth() {
    479     return m_fullDropdownTableWidth;
    480   }
    481 
    482   public void setFullDropdownTableWidth(boolean value) {
    483     Assert.isTrue(!isDroppedDown());
    484     m_fullDropdownTableWidth = value;
    485   }
    486 
    487   ////////////////////////////////////////////////////////////////////////////
    488   //
    489   // Selection
    490   //
    491   ////////////////////////////////////////////////////////////////////////////
    492   private int m_selectionIndex = -1;
    493 
    494   /**
    495    * Selection index.
    496    */
    497   public int getSelectionIndex() {
    498     return m_selectionIndex;
    499   }
    500 
    501   public void setSelectionIndex(int index) {
    502     m_selectionIndex = index;
    503     if (isDroppedDown()) {
    504       m_table.getTable().setSelection(m_selectionIndex);
    505     }
    506     setEditText(getSelectionText());
    507   }
    508 
    509   /**
    510    * Selection text.
    511    */
    512   private String getSelectionText() {
    513     if (m_selectionIndex != -1 && isDroppedDown()) {
    514       Object itemData = m_table.getTable().getItem(m_selectionIndex).getData();
    515       return ((ComboBoxItem) itemData).m_label;
    516     }
    517     return null;
    518   }
    519 
    520   /**
    521    * Selection image.
    522    */
    523   private Image getSelectionImage() {
    524     return m_selectionIndex != -1 ? m_items.get(m_selectionIndex).m_image : null;
    525   }
    526 
    527   public void setSelectionText(String label) {
    528     TableItem[] items = m_table.getTable().getItems();
    529     for (int i = 0; i < items.length; i++) {
    530       TableItem item = items[i];
    531       if (item.getText().equals(label)) {
    532         setSelectionIndex(i);
    533         return;
    534       }
    535     }
    536     // no such item
    537     setSelectionIndex(-1);
    538     setEditText(label);
    539   }
    540 
    541   /**
    542    * Adds the listener to receive events.
    543    */
    544   public void addSelectionListener(SelectionListener listener) {
    545     checkWidget();
    546     if (listener == null) {
    547       SWT.error(SWT.ERROR_NULL_ARGUMENT);
    548     }
    549     TypedListener typedListener = new TypedListener(listener);
    550     addListener(SWT.Selection, typedListener);
    551     addListener(SWT.DefaultSelection, typedListener);
    552   }
    553 
    554   ////////////////////////////////////////////////////////////////////////////
    555   //
    556   // Popup
    557   //
    558   ////////////////////////////////////////////////////////////////////////////
    559   public boolean isDroppedDown() {
    560     return m_popup.isVisible();
    561   }
    562 
    563   public void comboDropDown(boolean dropdown) {
    564     // check, may be we already in this drop state
    565     if (dropdown == isDroppedDown()) {
    566       return;
    567     }
    568     // close combo
    569     if (dropdown) {
    570       // initialize
    571       m_table.setInput(m_items);
    572       Table table = m_table.getTable();
    573       TableColumn column = table.getColumn(0);
    574       column.pack();
    575       table.pack();
    576       m_popup.pack();
    577       // compute table size
    578       Rectangle tableBounds = table.getBounds();
    579       tableBounds.height = Math.min(tableBounds.height, table.getItemHeight() * 15);// max 15 items without scrolling
    580       table.setBounds(tableBounds);
    581       // prepare popup point
    582       Point comboLocation = toDisplay(new Point(0, 0));
    583       Point comboSize = getSize();
    584       // compute popup size
    585       Display display = getDisplay();
    586       Rectangle clientArea = display.getClientArea();
    587       int remainingDisplayHeight = clientArea.height - comboLocation.y - comboSize.y - 10;
    588       int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
    589       int remainingDisplayWidth = clientArea.width - comboLocation.x - 10;
    590       int preferredWidth =
    591           isFullDropdownTableWidth()
    592               ? Math.min(tableBounds.width, remainingDisplayWidth)
    593               : comboSize.x;
    594       Rectangle popupBounds =
    595           new Rectangle(comboLocation.x,
    596               comboLocation.y + comboSize.y,
    597               preferredWidth,
    598               preferredHeight);
    599       Rectangle trimBounds =
    600           m_popup.computeTrim(popupBounds.x, popupBounds.y, popupBounds.width, popupBounds.height);
    601       m_popup.setBounds(popupBounds.x, popupBounds.y, 2 * popupBounds.width - trimBounds.width, 2
    602           * popupBounds.height
    603           - trimBounds.height);
    604       // adjust column size
    605       column.setWidth(table.getClientArea().width);
    606       // show popup
    607       m_popup.setVisible(true);
    608       table.setSelection(getSelectionIndex());
    609     } else {
    610       // hide popup
    611       m_popup.setVisible(false);
    612     }
    613   }
    614 
    615   protected final void setFocus2Text(final boolean selectAll) {
    616     getDisplay().asyncExec(new Runnable() {
    617       final boolean m_selectAll = selectAll;
    618 
    619       @Override
    620     public void run() {
    621         if (!m_text.isDisposed()) {
    622           m_text.setFocus();
    623           if (m_selectAll) {
    624             m_text.selectAll();
    625           }
    626         }
    627       }
    628     });
    629   }
    630 
    631   ////////////////////////////////////////////////////////////////////////////
    632   //
    633   // Utilities
    634   //
    635   ////////////////////////////////////////////////////////////////////////////
    636   protected static Event convert2event(TypedEvent tEvent) {
    637     Event event = new Event();
    638     event.widget = tEvent.widget;
    639     event.display = tEvent.display;
    640     event.widget = tEvent.widget;
    641     event.time = tEvent.time;
    642     event.data = tEvent.data;
    643     if (tEvent instanceof KeyEvent) {
    644       KeyEvent kEvent = (KeyEvent) tEvent;
    645       event.character = kEvent.character;
    646       event.keyCode = kEvent.keyCode;
    647       event.stateMask = kEvent.stateMask;
    648       event.doit = kEvent.doit;
    649     }
    650     if (tEvent instanceof SelectionEvent) {
    651       SelectionEvent sEvent = (SelectionEvent) tEvent;
    652       event.item = sEvent.item;
    653       event.x = sEvent.x;
    654       event.y = sEvent.y;
    655       event.width = sEvent.width;
    656       event.height = sEvent.height;
    657       event.detail = sEvent.detail;
    658       event.stateMask = sEvent.stateMask;
    659       event.text = sEvent.text;
    660       event.doit = sEvent.doit;
    661     }
    662     return event;
    663   }
    664 }
    665