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