• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   ////////////////////////////////////////////////////////////////////////////
CComboBox(Composite parent, int style)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   ////////////////////////////////////////////////////////////////////////////
createContents(Composite parent)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    */
createText(Composite parent)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    */
createButton(Composite parent)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    */
createImage(Composite parent)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    */
createPopup(Composite parent)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    */
createTable(Composite parent)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    */
resizeInner()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    */
disposeInner()338   protected void disposeInner() {
339     if (!m_popup.isDisposed()) {
340       m_popup.dispose();
341     }
342   }
343 
344   ////////////////////////////////////////////////////////////////////////////
345   //
346   // Providers
347   //
348   ////////////////////////////////////////////////////////////////////////////
getContentProvider()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