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.wb.draw2d.IColorConstants;
     14 import org.eclipse.wb.internal.core.model.property.table.PropertyTable;
     15 import org.eclipse.wb.internal.core.utils.binding.editors.controls.DefaultControlActionsManager;
     16 
     17 import org.eclipse.swt.SWT;
     18 import org.eclipse.swt.events.SelectionListener;
     19 import org.eclipse.swt.graphics.Image;
     20 import org.eclipse.swt.graphics.Point;
     21 import org.eclipse.swt.graphics.Rectangle;
     22 import org.eclipse.swt.layout.FillLayout;
     23 import org.eclipse.swt.widgets.Button;
     24 import org.eclipse.swt.widgets.Composite;
     25 import org.eclipse.swt.widgets.Event;
     26 import org.eclipse.swt.widgets.Listener;
     27 import org.eclipse.swt.widgets.Shell;
     28 import org.eclipse.swt.widgets.Table;
     29 import org.eclipse.swt.widgets.TableColumn;
     30 import org.eclipse.swt.widgets.TableItem;
     31 import org.eclipse.swt.widgets.TypedListener;
     32 import org.eclipse.swt.widgets.Widget;
     33 
     34 /**
     35  * Combo control for {@link PropertyTable} and combo property editors.
     36  *
     37  * @author scheglov_ke
     38  * @coverage core.control
     39  */
     40 public class CCombo3 extends Composite {
     41   private final long m_createTime = System.currentTimeMillis();
     42   private final CImageLabel m_text;
     43   private final Button m_arrow;
     44   private final Shell m_popup;
     45   private final Table m_table;
     46   private boolean m_fullDropdownTableSize = false;
     47 
     48   ////////////////////////////////////////////////////////////////////////////
     49   //
     50   // Constructor
     51   //
     52   ////////////////////////////////////////////////////////////////////////////
     53   public CCombo3(Composite parent, int style) {
     54     super(parent, style);
     55     addEvents(this, m_comboListener, new int[]{SWT.Dispose, SWT.Move, SWT.Resize});
     56     // create label
     57     {
     58       m_text = new CImageLabel(this, SWT.NONE);
     59       new DefaultControlActionsManager(m_text);
     60       addEvents(m_text, m_textListener, new int[]{
     61           SWT.KeyDown,
     62           SWT.KeyUp,
     63           SWT.MouseDown,
     64           SWT.MouseUp,
     65           SWT.MouseMove,
     66           SWT.MouseDoubleClick,
     67           SWT.Traverse,
     68           SWT.FocusIn,
     69           SWT.FocusOut});
     70     }
     71     // create arrow
     72     {
     73       m_arrow = new Button(this, SWT.ARROW | SWT.DOWN);
     74       addEvents(m_arrow, m_arrowListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
     75     }
     76     // create popup Shell
     77     {
     78       Shell shell = getShell();
     79       m_popup = new Shell(shell, SWT.NONE);
     80       m_popup.setLayout(new FillLayout());
     81     }
     82     // create table for items
     83     {
     84       m_table = new Table(m_popup, SWT.FULL_SELECTION);
     85       addEvents(m_table, m_tableListener, new int[]{SWT.Selection, SWT.FocusIn, SWT.FocusOut});
     86       //
     87       new TableColumn(m_table, SWT.NONE);
     88     }
     89     // Focus tracking filter
     90     {
     91       final Listener filter = new Listener() {
     92         private boolean hasFocus;
     93 
     94         public void handleEvent(Event event) {
     95           boolean old_hasFocus = hasFocus;
     96           hasFocus =
     97               m_text.isFocusControl()
     98                   || m_arrow.isFocusControl()
     99                   || m_popup.isFocusControl()
    100                   || m_table.isFocusControl();
    101           // configure colors
    102           if (hasFocus) {
    103             m_text.setBackground(IColorConstants.listSelection);
    104             m_text.setForeground(IColorConstants.listSelectionText);
    105           } else {
    106             m_text.setBackground(IColorConstants.listBackground);
    107             m_text.setForeground(IColorConstants.listForeground);
    108           }
    109           // send FocusOut event
    110           if (old_hasFocus && !hasFocus) {
    111             Event e = new Event();
    112             e.widget = CCombo3.this;
    113             e.time = event.time;
    114             notifyListeners(SWT.FocusOut, e);
    115           }
    116         }
    117       };
    118       getDisplay().addFilter(SWT.FocusIn, filter);
    119       addListener(SWT.Dispose, new Listener() {
    120         public void handleEvent(Event event) {
    121           getDisplay().removeFilter(SWT.FocusIn, filter);
    122         }
    123       });
    124     }
    125   }
    126 
    127   ////////////////////////////////////////////////////////////////////////////
    128   //
    129   // Events handling
    130   //
    131   ////////////////////////////////////////////////////////////////////////////
    132   private final Listener m_comboListener = new Listener() {
    133     public void handleEvent(Event event) {
    134       switch (event.type) {
    135         case SWT.Dispose :
    136           if (!m_popup.isDisposed()) {
    137             m_popup.dispose();
    138           }
    139           break;
    140         case SWT.Move :
    141           doDropDown(false);
    142           break;
    143         case SWT.Resize :
    144           doResize();
    145           break;
    146       }
    147     }
    148   };
    149   private final Listener m_textListener = new Listener() {
    150     public void handleEvent(final Event event) {
    151       switch (event.type) {
    152         case SWT.MouseDown :
    153           if (System.currentTimeMillis() - m_createTime < 400) {
    154             // send "logical" double click for case when we just activated combo
    155             // and almost right away click second time (but first time on editor)
    156             event.detail = -1;
    157             notifyListeners(SWT.MouseDoubleClick, event);
    158             // when we use "auto drop on editor activation" option, this click is
    159             // is "logically" second one, so it should close combo
    160             if (!isDisposed()) {
    161               doDropDown(false);
    162             }
    163           } else {
    164             m_text.setCapture(true);
    165             doDropDown(!isDropped());
    166           }
    167           break;
    168         case SWT.MouseUp : {
    169           m_text.setCapture(false);
    170           TableItem item = getItemUnderCursor(event);
    171           if (item != null) {
    172             doDropDown(false);
    173             sendSelectionEvent(event);
    174           }
    175           break;
    176         }
    177         case SWT.MouseDoubleClick :
    178           // prevent resending MouseDoubleClick that we sent on fast MouseDown
    179           if (event.detail != -1) {
    180             notifyListeners(SWT.MouseDoubleClick, event);
    181           }
    182           break;
    183         case SWT.MouseMove : {
    184           TableItem item = getItemUnderCursor(event);
    185           if (item != null) {
    186             m_table.setSelection(new TableItem[]{item});
    187           }
    188           break;
    189         }
    190         case SWT.KeyDown : {
    191           // check for keyboard navigation and selection
    192           {
    193             int selectionIndex = m_table.getSelectionIndex();
    194             if (event.keyCode == SWT.ARROW_UP) {
    195               selectionIndex--;
    196               if (selectionIndex < 0) {
    197                 selectionIndex = m_table.getItemCount() - 1;
    198               }
    199               m_table.setSelection(selectionIndex);
    200               return;
    201             } else if (event.keyCode == SWT.ARROW_DOWN) {
    202               m_table.setSelection((selectionIndex + 1) % m_table.getItemCount());
    203               return;
    204             } else if (event.character == SWT.CR || event.character == ' ') {
    205               sendSelectionEvent(event);
    206               return;
    207             }
    208           }
    209           // be default just resend event
    210           resendKeyEvent(event);
    211           break;
    212         }
    213         case SWT.KeyUp :
    214           resendKeyEvent(event);
    215           break;
    216       }
    217     }
    218 
    219     private TableItem getItemUnderCursor(Event event) {
    220       Point displayLocation = m_text.toDisplay(new Point(event.x, event.y));
    221       Point tableLocation = m_table.toControl(displayLocation);
    222       return m_table.getItem(tableLocation);
    223     }
    224   };
    225   private final Listener m_arrowListener = new Listener() {
    226     public void handleEvent(Event event) {
    227       switch (event.type) {
    228       /*case SWT.FocusIn : {
    229        resendFocusEvent(event);
    230        break;
    231        }*/
    232         case SWT.Selection : {
    233           doDropDown(!isDropped());
    234           break;
    235         }
    236       }
    237     }
    238   };
    239   private final Listener m_tableListener = new Listener() {
    240     public void handleEvent(Event event) {
    241       switch (event.type) {
    242         case SWT.Selection : {
    243           doDropDown(false);
    244           // show selected item in text
    245           {
    246             int index = m_table.getSelectionIndex();
    247             select(index);
    248           }
    249           // send selection event
    250           sendSelectionEvent(event);
    251           break;
    252         }
    253       }
    254     }
    255   };
    256 
    257   ////////////////////////////////////////////////////////////////////////////
    258   //
    259   // Events utils
    260   //
    261   ////////////////////////////////////////////////////////////////////////////
    262   /**
    263    * Sends selection event.
    264    */
    265   private void sendSelectionEvent(Event event) {
    266     Event e = new Event();
    267     e.time = event.time;
    268     e.stateMask = event.stateMask;
    269     notifyListeners(SWT.Selection, e);
    270   }
    271 
    272   /**
    273    * Resends KeyDown/KeyUp events.
    274    */
    275   private void resendKeyEvent(Event event) {
    276     Event e = new Event();
    277     e.time = event.time;
    278     e.character = event.character;
    279     e.keyCode = event.keyCode;
    280     e.stateMask = event.stateMask;
    281     notifyListeners(event.type, e);
    282   }
    283 
    284   /**
    285    * Adds given listener as handler for events in given widget.
    286    */
    287   private void addEvents(Widget widget, Listener listener, int[] events) {
    288     for (int i = 0; i < events.length; i++) {
    289       widget.addListener(events[i], listener);
    290     }
    291   }
    292 
    293   /**
    294    * Adds the listener to receive events.
    295    */
    296   public void addSelectionListener(SelectionListener listener) {
    297     checkWidget();
    298     if (listener == null) {
    299       SWT.error(SWT.ERROR_NULL_ARGUMENT);
    300     }
    301     TypedListener typedListener = new TypedListener(listener);
    302     addListener(SWT.Selection, typedListener);
    303     addListener(SWT.DefaultSelection, typedListener);
    304   }
    305 
    306   ////////////////////////////////////////////////////////////////////////////
    307   //
    308   // Activity
    309   //
    310   ////////////////////////////////////////////////////////////////////////////
    311   /**
    312    * Sets drop state of combo.
    313    */
    314   public void doDropDown(boolean drop) {
    315     // check, may be we already in this drop state
    316     if (drop == isDropped()) {
    317       return;
    318     }
    319     // close combo
    320     if (!drop) {
    321       m_popup.setVisible(false);
    322       m_text.setFocus();
    323       return;
    324     }
    325     // open combo
    326     {
    327       // prepare popup location
    328       Point comboSize = getSize();
    329       Point popupLocation;
    330       {
    331         //popupLocation = getParent().toDisplay(getLocation());
    332         popupLocation = toDisplay(new Point(0, 0));
    333         popupLocation.y += comboSize.y;
    334       }
    335       // calculate and set popup location
    336       {
    337         TableColumn tableColumn = m_table.getColumn(0);
    338         // pack everything
    339         tableColumn.pack();
    340         m_table.pack();
    341         m_popup.pack();
    342         // calculate bounds
    343         Rectangle tableBounds = m_table.getBounds();
    344         tableBounds.height = Math.min(tableBounds.height, m_table.getItemHeight() * 20); // max 20 items without scrolling
    345         m_table.setBounds(tableBounds);
    346         // calculate size
    347         int remainingDisplayHeight = getDisplay().getClientArea().height - popupLocation.y - 10;
    348         int preferredHeight = Math.min(tableBounds.height, remainingDisplayHeight);
    349         int remainingDisplayWidth = getDisplay().getClientArea().width - popupLocation.x - 5;
    350         int preferredWidth =
    351             isFullDropdownTableWidth()
    352                 ? Math.min(tableBounds.width, remainingDisplayWidth)
    353                 : comboSize.x;
    354         // set popup bounds calculated as computeTrim basing on combo width and table height paying attention on remaining display space
    355         Rectangle popupBounds =
    356             m_popup.computeTrim(popupLocation.x, popupLocation.y, preferredWidth, preferredHeight);
    357         m_popup.setBounds(popupBounds);
    358         // adjust column size
    359         tableColumn.setWidth(m_table.getClientArea().width);
    360       }
    361       m_popup.setVisible(true);
    362       // scroll to selection if needed
    363       m_table.showSelection();
    364     }
    365   }
    366 
    367   /**
    368    * Initiates "press-hold-drag" sequence.
    369    */
    370   public void startDrag() {
    371     m_text.setCapture(true);
    372   }
    373 
    374   ////////////////////////////////////////////////////////////////////////////
    375   //
    376   // Access
    377   //
    378   ////////////////////////////////////////////////////////////////////////////
    379   public void setFullDropdownTableWidth(boolean freeTableSize) {
    380     m_fullDropdownTableSize = freeTableSize;
    381   }
    382 
    383   public boolean isFullDropdownTableWidth() {
    384     return m_fullDropdownTableSize;
    385   }
    386 
    387   public boolean isDropped() {
    388     return m_popup.isVisible();
    389   }
    390 
    391   public void setQuickSearch(boolean value) {
    392     // TODO
    393   }
    394 
    395   ////////////////////////////////////////////////////////////////////////////
    396   //
    397   // Access: items
    398   //
    399   ////////////////////////////////////////////////////////////////////////////
    400   /**
    401    * Removes all items.
    402    */
    403   public void removeAll() {
    404     TableItem[] items = m_table.getItems();
    405     for (int index = 0; index < items.length; index++) {
    406       TableItem item = items[index];
    407       item.dispose();
    408     }
    409   }
    410 
    411   /**
    412    * Adds new item with given text.
    413    */
    414   public void add(String text) {
    415     add(text, null);
    416   }
    417 
    418   /**
    419    * Adds new item with given text and image.
    420    */
    421   public void add(String text, Image image) {
    422     checkWidget();
    423     TableItem item = new TableItem(m_table, SWT.NONE);
    424     item.setText(text);
    425     item.setImage(image);
    426   }
    427 
    428   /**
    429    * @return an item at given index
    430    */
    431   public String getItem(int index) {
    432     checkWidget();
    433     return m_table.getItem(index).getText();
    434   }
    435 
    436   /**
    437    * @return the number of items
    438    */
    439   public int getItemCount() {
    440     checkWidget();
    441     return m_table.getItemCount();
    442   }
    443 
    444   /**
    445    * @return the index of the selected item
    446    */
    447   public int getSelectionIndex() {
    448     checkWidget();
    449     return m_table.getSelectionIndex();
    450   }
    451 
    452   /**
    453    * Selects an item with given index.
    454    */
    455   public void select(int index) {
    456     checkWidget();
    457     if (index == -1) {
    458       m_table.deselectAll();
    459       m_text.setText(null);
    460       m_text.setImage(null);
    461       return;
    462     } else {
    463       TableItem item = m_table.getItem(index);
    464       m_text.setText(item.getText());
    465       m_text.setImage(item.getImage());
    466       m_table.select(index);
    467     }
    468   }
    469 
    470   ////////////////////////////////////////////////////////////////////////////
    471   //
    472   // Access: text and image
    473   //
    474   ////////////////////////////////////////////////////////////////////////////
    475   /**
    476    * Selects item with given text.
    477    */
    478   public void setText(String text) {
    479     // try to find item with given text
    480     TableItem[] items = m_table.getItems();
    481     for (int index = 0; index < items.length; index++) {
    482       TableItem item = items[index];
    483       if (item.getText().equals(text)) {
    484         select(index);
    485         return;
    486       }
    487     }
    488     // not found, remove selection
    489     select(-1);
    490   }
    491 
    492   ////////////////////////////////////////////////////////////////////////////
    493   //
    494   // Resize support
    495   // TODO: computeSize
    496   //
    497   ////////////////////////////////////////////////////////////////////////////
    498   protected void doResize() {
    499     Rectangle clientArea = getClientArea();
    500     int areaWidth = clientArea.width;
    501     int areaHeight = clientArea.height;
    502     // compute sizes of controls
    503     Point buttonSize = m_arrow.computeSize(areaHeight, areaHeight);
    504     Point textSize = m_text.computeSize(areaWidth - buttonSize.x, areaHeight);
    505     // set controls location/size
    506     m_arrow.setLocation(areaWidth - buttonSize.x, 0);
    507     m_arrow.setSize(buttonSize);
    508     m_text.setSize(areaWidth - buttonSize.x, Math.max(textSize.y, areaHeight));
    509   }
    510 }
    511