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