• 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 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   ////////////////////////////////////////////////////////////////////////////
CCombo3(Composite parent, int style)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    */
sendSelectionEvent(Event event)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    */
resendKeyEvent(Event event)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    */
addEvents(Widget widget, Listener listener, int[] events)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    */
addSelectionListener(SelectionListener listener)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    */
doDropDown(boolean drop)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    */
startDrag()370   public void startDrag() {
371     m_text.setCapture(true);
372   }
373 
374   ////////////////////////////////////////////////////////////////////////////
375   //
376   // Access
377   //
378   ////////////////////////////////////////////////////////////////////////////
setFullDropdownTableWidth(boolean freeTableSize)379   public void setFullDropdownTableWidth(boolean freeTableSize) {
380     m_fullDropdownTableSize = freeTableSize;
381   }
382 
isFullDropdownTableWidth()383   public boolean isFullDropdownTableWidth() {
384     return m_fullDropdownTableSize;
385   }
386 
isDropped()387   public boolean isDropped() {
388     return m_popup.isVisible();
389   }
390 
setQuickSearch(boolean value)391   public void setQuickSearch(boolean value) {
392     // TODO
393   }
394 
395   ////////////////////////////////////////////////////////////////////////////
396   //
397   // Access: items
398   //
399   ////////////////////////////////////////////////////////////////////////////
400   /**
401    * Removes all items.
402    */
removeAll()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    */
add(String text)414   public void add(String text) {
415     add(text, null);
416   }
417 
418   /**
419    * Adds new item with given text and image.
420    */
add(String text, Image image)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    */
getItem(int index)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    */
getItemCount()439   public int getItemCount() {
440     checkWidget();
441     return m_table.getItemCount();
442   }
443 
444   /**
445    * @return the index of the selected item
446    */
getSelectionIndex()447   public int getSelectionIndex() {
448     checkWidget();
449     return m_table.getSelectionIndex();
450   }
451 
452   /**
453    * Selects an item with given index.
454    */
select(int index)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    */
setText(String text)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   ////////////////////////////////////////////////////////////////////////////
doResize()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