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.custom.CCombo;
     15 import org.eclipse.swt.events.DisposeEvent;
     16 import org.eclipse.swt.events.DisposeListener;
     17 import org.eclipse.swt.events.SelectionListener;
     18 import org.eclipse.swt.graphics.Color;
     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.Combo;
     25 import org.eclipse.swt.widgets.Composite;
     26 import org.eclipse.swt.widgets.Control;
     27 import org.eclipse.swt.widgets.Display;
     28 import org.eclipse.swt.widgets.Event;
     29 import org.eclipse.swt.widgets.Listener;
     30 import org.eclipse.swt.widgets.Shell;
     31 import org.eclipse.swt.widgets.Table;
     32 import org.eclipse.swt.widgets.TableColumn;
     33 import org.eclipse.swt.widgets.TableItem;
     34 import org.eclipse.swt.widgets.TypedListener;
     35 
     36 import java.util.Locale;
     37 
     38 /**
     39  * {@link Control} like {@link Combo} or {@link CCombo} that shows {@link Table} with image/text as
     40  * drop-down.
     41  *
     42  * @author mitin_aa
     43  * @author scheglov_ke
     44  * @coverage core.control
     45  */
     46 public class CTableCombo extends Composite {
     47   protected Button m_arrow;
     48   protected CImageLabel m_text;
     49   protected Shell m_popup;
     50   protected Table m_table;
     51   protected boolean hasFocus;
     52 
     53   //
     54   public CTableCombo(Composite parent, int style) {
     55     super(parent, style = checkStyle(style));
     56     init(parent, style);
     57   }
     58 
     59   static int checkStyle(int style) {
     60     int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT;
     61     return style & mask;
     62   }
     63 
     64   private void init(Composite parent, int style) {
     65     m_arrow = new Button(this, SWT.ARROW | SWT.DOWN | SWT.NO_FOCUS);
     66     m_text = new CImageLabel(this, style & ~SWT.BORDER);
     67     m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
     68     final Shell shell = getShell();
     69     m_popup = new Shell(shell, SWT.NONE);
     70     m_table = new Table(m_popup, SWT.FULL_SELECTION);
     71     new TableColumn(m_table, SWT.NONE);
     72     Listener listener = new Listener() {
     73       public void handleEvent(Event event) {
     74         if (m_popup == event.widget) {
     75           handlePopupEvent(event);
     76           return;
     77         }
     78         if (m_text == event.widget) {
     79           handleTextEvent(event);
     80           return;
     81         }
     82         if (m_table == event.widget) {
     83           handleTableEvent(event);
     84           return;
     85         }
     86         if (m_arrow == event.widget) {
     87           handleArrowEvent(event);
     88           return;
     89         }
     90         if (CTableCombo.this == event.widget) {
     91           handleComboEvent(event);
     92           return;
     93         }
     94       }
     95     };
     96     final Listener shellListener = new Listener() {
     97       public void handleEvent(Event event) {
     98         switch (event.type) {
     99           case SWT.Dispose :
    100           case SWT.Move :
    101           case SWT.Resize :
    102             if (!isDisposed()) {
    103               dropDown(false);
    104             }
    105             break;
    106         }
    107       }
    108     };
    109     final int[] comboEvents = {SWT.Dispose, SWT.Move, SWT.Resize};
    110     for (int i = 0; i < comboEvents.length; i++) {
    111       addListener(comboEvents[i], listener);
    112       // HACK: hide popup when parent changed
    113       shell.addListener(comboEvents[i], shellListener);
    114     }
    115     addDisposeListener(new DisposeListener() {
    116       public void widgetDisposed(DisposeEvent e) {
    117         for (int i = 0; i < comboEvents.length; i++) {
    118           shell.removeListener(comboEvents[i], shellListener);
    119         }
    120       }
    121     });
    122     int[] popupEvents = {SWT.Close, SWT.Paint, SWT.Deactivate};
    123     for (int i = 0; i < popupEvents.length; i++) {
    124       m_popup.addListener(popupEvents[i], listener);
    125     }
    126     int[] textEvents =
    127         {
    128             SWT.KeyDown,
    129             SWT.KeyUp,
    130             SWT.Modify,
    131             SWT.MouseDown,
    132             SWT.MouseUp,
    133             SWT.MouseDoubleClick,
    134             SWT.Traverse,
    135             SWT.FocusIn,
    136             SWT.FocusOut};
    137     for (int i = 0; i < textEvents.length; i++) {
    138       m_text.addListener(textEvents[i], listener);
    139     }
    140     int[] tableEvents =
    141         {
    142             SWT.MouseUp,
    143             SWT.Selection,
    144             SWT.Traverse,
    145             SWT.KeyDown,
    146             SWT.KeyUp,
    147             SWT.FocusIn,
    148             SWT.FocusOut};
    149     for (int i = 0; i < tableEvents.length; i++) {
    150       m_table.addListener(tableEvents[i], listener);
    151     }
    152     int[] arrowEvents = {SWT.Selection, SWT.FocusIn, SWT.FocusOut};
    153     for (int i = 0; i < arrowEvents.length; i++) {
    154       m_arrow.addListener(arrowEvents[i], listener);
    155     }
    156   }
    157 
    158   protected void handleTableEvent(Event event) {
    159     switch (event.type) {
    160       case SWT.FocusIn : {
    161         if (hasFocus) {
    162           return;
    163         }
    164         hasFocus = true;
    165         Event e = new Event();
    166         e.time = event.time;
    167         notifyListeners(SWT.FocusIn, e);
    168         break;
    169       }
    170       case SWT.FocusOut : {
    171         final int time = event.time;
    172         event.display.asyncExec(new Runnable() {
    173           public void run() {
    174             if (CTableCombo.this.isDisposed()) {
    175               return;
    176             }
    177             Control focusControl = getDisplay().getFocusControl();
    178             if (focusControl == m_text || focusControl == m_arrow) {
    179               return;
    180             }
    181             hasFocus = false;
    182             Event e = new Event();
    183             e.time = time;
    184             notifyListeners(SWT.FocusOut, e);
    185           }
    186         });
    187         break;
    188       }
    189       case SWT.MouseUp : {
    190         if (event.button != 1) {
    191           return;
    192         }
    193         dropDown(false);
    194         Event e = new Event();
    195         e.time = event.time;
    196         notifyListeners(SWT.DefaultSelection, e);
    197         break;
    198       }
    199       case SWT.Selection : {
    200         int index = m_table.getSelectionIndex();
    201         if (index == -1) {
    202           return;
    203         }
    204         TableItem item = m_table.getItem(index);
    205         m_text.setText(item.getText());
    206         m_text.setImage(item.getImage());
    207         //m_text.selectAll();
    208         m_table.setSelection(index);
    209         Event e = new Event();
    210         e.time = event.time;
    211         e.stateMask = event.stateMask;
    212         e.doit = event.doit;
    213         notifyListeners(SWT.Selection, e);
    214         event.doit = e.doit;
    215         dropDown(false);
    216         break;
    217       }
    218       case SWT.Traverse : {
    219         switch (event.detail) {
    220           case SWT.TRAVERSE_TAB_NEXT :
    221           case SWT.TRAVERSE_RETURN :
    222           case SWT.TRAVERSE_ESCAPE :
    223           case SWT.TRAVERSE_ARROW_PREVIOUS :
    224           case SWT.TRAVERSE_ARROW_NEXT :
    225             event.doit = false;
    226             break;
    227         }
    228         Event e = new Event();
    229         e.time = event.time;
    230         e.detail = event.detail;
    231         e.doit = event.doit;
    232         e.keyCode = event.keyCode;
    233         notifyListeners(SWT.Traverse, e);
    234         event.doit = e.doit;
    235         break;
    236       }
    237       case SWT.KeyUp : {
    238         Event e = new Event();
    239         e.time = event.time;
    240         e.character = event.character;
    241         e.keyCode = event.keyCode;
    242         e.stateMask = event.stateMask;
    243         notifyListeners(SWT.KeyUp, e);
    244         break;
    245       }
    246       case SWT.KeyDown : {
    247         if (event.character == SWT.ESC) {
    248           // escape key cancels popups
    249           dropDown(false);
    250         }
    251         if (event.character == SWT.CR || event.character == '\t') {
    252           // Enter and Tab cause default selection
    253           dropDown(false);
    254           Event e = new Event();
    255           e.time = event.time;
    256           e.stateMask = event.stateMask;
    257           notifyListeners(SWT.DefaultSelection, e);
    258         }
    259         // At this point the widget may have been disposed.
    260         // If so, do not continue.
    261         if (isDisposed()) {
    262           break;
    263         }
    264         Event e = new Event();
    265         e.time = event.time;
    266         e.character = event.character;
    267         e.keyCode = event.keyCode;
    268         e.stateMask = event.stateMask;
    269         notifyListeners(SWT.KeyDown, e);
    270         break;
    271       }
    272     }
    273   }
    274 
    275   protected void handlePopupEvent(Event event) {
    276     switch (event.type) {
    277       case SWT.Paint :
    278         // draw black rectangle around list
    279         Rectangle listRect = m_table.getBounds();
    280         Color black = getDisplay().getSystemColor(SWT.COLOR_BLACK);
    281         event.gc.setForeground(black);
    282         event.gc.drawRectangle(0, 0, listRect.width + 1, listRect.height + 1);
    283         break;
    284       case SWT.Close :
    285         event.doit = false;
    286         dropDown(false);
    287         break;
    288     }
    289   }
    290 
    291   protected void handleComboEvent(Event event) {
    292     switch (event.type) {
    293       case SWT.Dispose :
    294         if (m_popup != null && !m_popup.isDisposed()) {
    295           m_popup.dispose();
    296         }
    297         m_popup = null;
    298         m_text = null;
    299         m_arrow = null;
    300         break;
    301       case SWT.Move :
    302         dropDown(false);
    303         break;
    304       case SWT.Resize :
    305         internalLayout();
    306         break;
    307     }
    308   }
    309 
    310   protected void handleArrowEvent(Event event) {
    311     switch (event.type) {
    312       case SWT.FocusIn : {
    313         if (hasFocus) {
    314           return;
    315         }
    316         hasFocus = true;
    317         Event e = new Event();
    318         e.time = event.time;
    319         notifyListeners(SWT.FocusIn, e);
    320         break;
    321       }
    322       case SWT.Selection : {
    323         boolean wasDropped = isDropped();
    324         dropDown(!wasDropped);
    325         if (wasDropped) {
    326           m_text.forceFocus();
    327         }
    328         break;
    329       }
    330     }
    331   }
    332 
    333   protected void handleTextEvent(Event event) {
    334     switch (event.type) {
    335       case SWT.FocusIn : {
    336         if (hasFocus) {
    337           return;
    338         }
    339         hasFocus = true;
    340         //if (getEditable())
    341         Event e = new Event();
    342         e.time = event.time;
    343         notifyListeners(SWT.FocusIn, e);
    344         break;
    345       }
    346       case SWT.FocusOut : {
    347         final int time = event.time;
    348         event.display.asyncExec(new Runnable() {
    349           public void run() {
    350             if (CTableCombo.this.isDisposed()) {
    351               return;
    352             }
    353             Control focusControl = getDisplay().getFocusControl();
    354             if (focusControl == m_table
    355                 || focusControl == m_arrow
    356                 || focusControl != null
    357                 && focusControl.getParent() == CTableCombo.this) {
    358               return;
    359             }
    360             hasFocus = false;
    361             Event e = new Event();
    362             e.time = time;
    363             notifyListeners(SWT.FocusOut, e);
    364           }
    365         });
    366         break;
    367       }
    368       case SWT.KeyDown : {
    369         if (event.character == SWT.ESC) { // escape key cancels popup
    370           dropDown(false);
    371         }
    372         if (event.character == SWT.CR) {
    373           dropDown(false);
    374           Event e = new Event();
    375           e.time = event.time;
    376           e.stateMask = event.stateMask;
    377           notifyListeners(SWT.DefaultSelection, e);
    378         }
    379         // At this point the widget may have been disposed.
    380         // If so, do not continue.
    381         if (isDisposed()) {
    382           break;
    383         }
    384         if (event.character == '+') {
    385           dropDown(true);
    386         }
    387         if (isDropped()) {
    388           if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN) {
    389             int oldIndex = getSelectionIndex();
    390             if (event.keyCode == SWT.ARROW_UP) {
    391               select(Math.max(oldIndex - 1, 0));
    392             } else {
    393               select(Math.min(oldIndex + 1, getItemCount() - 1));
    394             }
    395             if (oldIndex != getSelectionIndex()) {
    396               Event e = new Event();
    397               e.time = event.time;
    398               e.stateMask = event.stateMask;
    399               notifyListeners(SWT.Selection, e);
    400             }
    401             // At this point the widget may have been disposed.
    402             // If so, do not continue.
    403             if (isDisposed()) {
    404               break;
    405             }
    406           }
    407         }
    408         if (Character.isLetter(event.character)) {
    409           int oldIndex = getSelectionIndex();
    410           int index = -1;
    411           for (int i = 0; i < getItemCount(); i++) {
    412             String item = getItem(i).toUpperCase(Locale.ENGLISH);
    413             if (item.length() != 0 && item.charAt(0) == Character.toUpperCase(event.character)) {
    414               index = i;
    415               break;
    416             }
    417           }
    418           if (index != -1) {
    419             select(Math.max(index, 0));
    420             if (oldIndex != getSelectionIndex()) {
    421               Event e = new Event();
    422               e.time = event.time;
    423               e.stateMask = event.stateMask;
    424               notifyListeners(SWT.Selection, e);
    425             }
    426           }
    427         }
    428         Event e = new Event();
    429         e.time = event.time;
    430         e.character = event.character;
    431         e.keyCode = event.keyCode;
    432         e.stateMask = event.stateMask;
    433         if (m_text != null && !m_text.isDisposed()) {
    434           notifyListeners(SWT.KeyDown, e);
    435         }
    436         break;
    437       }
    438       case SWT.KeyUp : {
    439         Event e = new Event();
    440         e.time = event.time;
    441         e.character = event.character;
    442         e.keyCode = event.keyCode;
    443         e.stateMask = event.stateMask;
    444         notifyListeners(SWT.KeyUp, e);
    445         break;
    446       }
    447       case SWT.Modify : {
    448         m_table.deselectAll();
    449         Event e = new Event();
    450         e.time = event.time;
    451         notifyListeners(SWT.Modify, e);
    452         break;
    453       }
    454       case SWT.MouseDown : {
    455         if (event.button != 1) {
    456           return;
    457         }
    458         m_text.forceFocus();
    459         boolean dropped = isDropped();
    460         dropDown(!dropped);
    461         if (!dropped) {
    462           m_text.forceFocus();
    463         }
    464         break;
    465       }
    466       case SWT.MouseDoubleClick : {
    467         notifyListeners(SWT.MouseDoubleClick, event);
    468         break;
    469       }
    470       case SWT.Traverse : {
    471         switch (event.detail) {
    472           case SWT.TRAVERSE_RETURN :
    473           case SWT.TRAVERSE_ARROW_PREVIOUS :
    474           case SWT.TRAVERSE_ARROW_NEXT :
    475             // The enter causes default selection and
    476             // the arrow keys are used to manipulate the list contents so
    477             // do not use them for traversal.
    478             event.doit = false;
    479             break;
    480           case SWT.TRAVERSE_TAB_NEXT :
    481           case SWT.TRAVERSE_TAB_PREVIOUS :
    482             event.doit = true;
    483             break;
    484         }
    485         Event e = new Event();
    486         e.time = event.time;
    487         e.detail = event.detail;
    488         e.doit = event.doit;
    489         e.keyCode = event.keyCode;
    490         notifyListeners(SWT.Traverse, e);
    491         event.doit = e.doit;
    492         break;
    493       }
    494     }
    495   }
    496 
    497   private void dropDown(boolean drop) {
    498     if (drop == isDropped()) {
    499       return;
    500     }
    501     if (!drop) {
    502       m_popup.setVisible(false);
    503       m_text.setFocus();
    504       return;
    505     }
    506     int index = m_table.getSelectionIndex();
    507     if (index != -1) {
    508       m_table.setTopIndex(index);
    509       m_table.setSelection(index);
    510     }
    511     m_table.pack();
    512     Point point = getParent().toDisplay(getLocation());
    513     Point comboSize = getSize();
    514     //Rectangle tableRect = m_table.getBounds();
    515     //int width = Math.max(comboSize.x, tableRect.width + 2);
    516     int width = comboSize.x - 1;
    517     // only one column
    518     m_table.getColumn(0).setWidth(width - 5);
    519     if (!(m_popup.getLayout() instanceof FillLayout)) {
    520       m_popup.setLayout(new FillLayout());
    521     }
    522     int itemCount = m_table.getItemCount();
    523     if (itemCount > 20) {
    524       itemCount = 20;
    525     }
    526     int height =
    527         Math.min(
    528             m_table.getItemHeight() * itemCount + 5,
    529             Display.getCurrent().getClientArea().height - point.y - 20);
    530     m_popup.setBounds(point.x, point.y + comboSize.y, width, height);
    531     m_popup.layout();
    532     m_popup.setVisible(true);
    533     m_text.setFocus();
    534     if (index != -1) {
    535       m_table.setTopIndex(index);
    536       m_table.setSelection(index);
    537     }
    538   }
    539 
    540   @Override
    541   public Point computeSize(int wHint, int hHint, boolean changed) {
    542     checkWidget();
    543     int width = 0, height = 0;
    544     Point textSize = m_text.computeSize(wHint, SWT.DEFAULT, changed);
    545     Point arrowSize = m_arrow.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
    546     int tableWidth;
    547     {
    548       TableColumn column = m_table.getColumn(0);
    549       column.pack();
    550       tableWidth = column.getWidth();
    551     }
    552     //
    553     int borderWidth = getBorderWidth();
    554     height = Math.max(hHint, Math.max(textSize.y, arrowSize.y) + 2 * borderWidth);
    555     width = Math.max(wHint, Math.max(textSize.x + arrowSize.x, tableWidth) + 2 * borderWidth);
    556     //
    557     return new Point(width, height);
    558   }
    559 
    560   private void internalLayout() {
    561     if (isDropped()) {
    562       dropDown(false);
    563     }
    564     Rectangle rect = getClientArea();
    565     int width = rect.width;
    566     int height = rect.height;
    567     Point arrowSize = m_arrow.computeSize(SWT.DEFAULT, height);
    568     m_text.setBounds(rect.x, rect.y, width - arrowSize.x, height);
    569     m_arrow.setBounds(rect.x + width - arrowSize.x, rect.y, arrowSize.x, arrowSize.y);
    570   }
    571 
    572   private boolean isDropped() {
    573     return m_popup.isVisible();
    574   }
    575 
    576   @Override
    577   public boolean isFocusControl() {
    578     checkWidget();
    579     if (m_text.isFocusControl()
    580         || m_arrow.isFocusControl()
    581         || m_table.isFocusControl()
    582         || m_popup.isFocusControl()) {
    583       return true;
    584     }
    585     return super.isFocusControl();
    586   }
    587 
    588   public void select(int index) {
    589     checkWidget();
    590     if (index == -1) {
    591       m_table.deselectAll();
    592       m_text.setText(""); //$NON-NLS-1$
    593       m_text.setImage(null);
    594       return;
    595     }
    596     if (0 <= index && index < m_table.getItemCount()) {
    597       if (index != getSelectionIndex()) {
    598         TableItem item = m_table.getItem(index);
    599         m_text.setText(item.getText());
    600         m_text.setImage(item.getImage());
    601         m_table.select(index);
    602         m_table.showSelection();
    603       }
    604     }
    605   }
    606 
    607   @Override
    608   public void setEnabled(boolean enabled) {
    609     super.setEnabled(enabled);
    610     if (enabled) {
    611       m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
    612     } else {
    613       m_text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
    614     }
    615   }
    616 
    617   public String getItem(int index) {
    618     checkWidget();
    619     return m_table.getItem(index).getText();
    620   }
    621 
    622   public int getSelectionIndex() {
    623     checkWidget();
    624     return m_table.getSelectionIndex();
    625   }
    626 
    627   public void removeAll() {
    628     checkWidget();
    629     m_text.setText(""); //$NON-NLS-1$
    630     m_text.setImage(null);
    631     m_table.removeAll();
    632   }
    633 
    634   public int indexOf(String string) {
    635     return indexOf(string, 0);
    636   }
    637 
    638   public int indexOf(String string, int start) {
    639     checkWidget();
    640     if (string == null) {
    641       return -1;
    642     }
    643     TableItem[] items = m_table.getItems();
    644     for (int i = start; i < items.length; i++) {
    645       TableItem item = items[i];
    646       if (item.getText().equalsIgnoreCase(string)) {
    647         return i;
    648       }
    649     }
    650     return -1;
    651   }
    652 
    653   public String getText() {
    654     return m_text.getText();
    655   }
    656 
    657   public int getItemCount() {
    658     checkWidget();
    659     return m_table.getItemCount();
    660   }
    661 
    662   protected void setText(String string) {
    663     m_text.setText(string);
    664   }
    665 
    666   protected void setImage(Image image) {
    667     m_text.setImage(image);
    668   }
    669 
    670   public void add(String text) {
    671     add(text, null);
    672   }
    673 
    674   public void add(String text, Image image) {
    675     checkWidget();
    676     TableItem item = new TableItem(m_table, SWT.NONE);
    677     item.setText(text);
    678     item.setImage(image);
    679   }
    680 
    681   public void addSelectionListener(SelectionListener listener) {
    682     checkWidget();
    683     if (listener == null) {
    684       SWT.error(SWT.ERROR_NULL_ARGUMENT);
    685     }
    686     TypedListener typedListener = new TypedListener(listener);
    687     addListener(SWT.Selection, typedListener);
    688     addListener(SWT.DefaultSelection, typedListener);
    689   }
    690 }
    691