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 // CTableCombo(Composite parent, int style)54 public CTableCombo(Composite parent, int style) { 55 super(parent, style = checkStyle(style)); 56 init(parent, style); 57 } 58 checkStyle(int style)59 static int checkStyle(int style) { 60 int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT; 61 return style & mask; 62 } 63 init(Composite parent, int style)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 handleTableEvent(Event event)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 handlePopupEvent(Event event)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 handleComboEvent(Event event)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 handleArrowEvent(Event event)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 handleTextEvent(Event event)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 dropDown(boolean drop)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 computeSize(int wHint, int hHint, boolean changed)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 internalLayout()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 isDropped()572 private boolean isDropped() { 573 return m_popup.isVisible(); 574 } 575 576 @Override isFocusControl()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 select(int index)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 setEnabled(boolean enabled)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 getItem(int index)617 public String getItem(int index) { 618 checkWidget(); 619 return m_table.getItem(index).getText(); 620 } 621 getSelectionIndex()622 public int getSelectionIndex() { 623 checkWidget(); 624 return m_table.getSelectionIndex(); 625 } 626 removeAll()627 public void removeAll() { 628 checkWidget(); 629 m_text.setText(""); //$NON-NLS-1$ 630 m_text.setImage(null); 631 m_table.removeAll(); 632 } 633 indexOf(String string)634 public int indexOf(String string) { 635 return indexOf(string, 0); 636 } 637 indexOf(String string, int start)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 getText()653 public String getText() { 654 return m_text.getText(); 655 } 656 getItemCount()657 public int getItemCount() { 658 checkWidget(); 659 return m_table.getItemCount(); 660 } 661 setText(String string)662 protected void setText(String string) { 663 m_text.setText(string); 664 } 665 setImage(Image image)666 protected void setImage(Image image) { 667 m_text.setImage(image); 668 } 669 add(String text)670 public void add(String text) { 671 add(text, null); 672 } 673 add(String text, Image image)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 addSelectionListener(SelectionListener listener)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