1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.test.jemmy.misc.wrappers; 34 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 40 import org.eclipse.swt.graphics.Image; 41 import org.eclipse.swt.graphics.Rectangle; 42 import org.eclipse.swt.widgets.Display; 43 import org.eclipse.swt.widgets.Shell; 44 import org.eclipse.swt.widgets.Table; 45 import org.eclipse.swt.widgets.TableColumn; 46 import org.eclipse.swt.widgets.TableItem; 47 import org.jemmy.Point; 48 import org.jemmy.control.Wrap; 49 import org.jemmy.input.StringPopupOwner; 50 import org.jemmy.input.StringPopupSelectableOwner; 51 import org.jemmy.interfaces.Keyboard.KeyboardButtons; 52 import org.jemmy.interfaces.Parent; 53 import org.jemmy.interfaces.Selectable; 54 import org.jemmy.lookup.Lookup; 55 import org.jemmy.resources.StringComparePolicy; 56 import org.jemmy.swt.ItemWrap; 57 import org.jemmy.swt.TableWrap; 58 import org.jemmy.swt.lookup.ByName; 59 import org.junit.Assert; 60 61 import org.openjdk.jmc.test.jemmy.misc.base.wrappers.MCJemmyBase; 62 import org.openjdk.jmc.test.jemmy.misc.fetchers.Fetcher; 63 64 /** 65 * The Jemmy base wrapper for tables 66 */ 67 public class MCTable extends MCJemmyBase { 68 69 /** 70 * A small representation of a row in a table, contains both the row text and a list of strings 71 * representing all cells in the row. If no tests actually require this, we should change the 72 * scope of this inner class to private or, at least, package private. 73 */ 74 public class TableRow { 75 76 private final String text; 77 private final List<String> columnTexts; 78 private Map<String, Integer> columnNameMap; 79 80 TableRow(String text, List<String> columns, Map<String, Integer> columnNameMap) { 81 this.text = text; 82 columnTexts = columns; 83 this.columnNameMap = columnNameMap; 84 } 85 86 /** 87 * @param text 88 * The text, separate from any column texts, to match 89 * @return {@code true} if the text matches that of this TableRow 90 */ 91 boolean hasText(String text) { 92 return policy.compare(text, this.text); 93 } 94 95 /** 96 * @param text 97 * the text, separate from any column texts, to match 98 * @param policy 99 * the policy to use when matching 100 * @return {@code true} if the text matches that of this {@link TableRow} 101 */ 102 boolean hasText(String text, StringComparePolicy policy) { 103 return policy.compare(text, this.text); 104 } 105 106 /** 107 * @param text 108 * the text to be found 109 * @return whether or not the text has been found in any column 110 */ 111 boolean hasColumnText(String text) { 112 return hasColumnText(text, policy); 113 } 114 115 /** 116 * @param text 117 * the text to be found 118 * @param policy 119 * the policy to use when matching 120 * @return whether or not the text has been found in any column 121 */ 122 boolean hasColumnText(String text, StringComparePolicy policy) { 123 for (String col : columnTexts) { 124 if (policy.compare(text, col)) { 125 return true; 126 } 127 } 128 return false; 129 } 130 131 /** 132 * @return the text of a row. 133 */ 134 public String getText() { 135 return text; 136 } 137 138 /** 139 * Returns the row text for the provided column index 140 * 141 * @param columnIndex 142 * the column index 143 * @return the text of the field of the provided column 144 */ 145 public String getText(int columnIndex) { 146 return columnTexts.get(columnIndex); 147 } 148 149 /** 150 * Returns the row text for the provided column header 151 * 152 * @param columnHeader 153 * the string header of the column 154 * @return the text of the field of the provided column 155 */ 156 public String getText(String columnHeader) { 157 return columnTexts.get(columnNameMap.get(columnHeader)); 158 } 159 160 /** 161 * @return the texts in the columns of a row 162 */ 163 public List<String> getColumns() { 164 return columnTexts; 165 } 166 167 @Override 168 public String toString() { 169 StringBuilder sb = new StringBuilder(); 170 sb.append(text); 171 sb.append(":["); 172 for (String col : columnTexts) { 173 sb.append(col); 174 sb.append(' '); 175 } 176 sb.append("]"); 177 return sb.toString(); 178 } 179 180 @Override 181 public boolean equals(Object o) { 182 if (!(o instanceof TableRow)) { 183 return false; 184 } 185 return toString().equals(((TableRow) o).toString()); 186 } 187 188 public Map<String, Integer> getColumnNameMap() { 189 return columnNameMap; 190 } 191 } 192 193 /** 194 * The policy used in comparisons in McTables 195 */ 196 public static StringComparePolicy policy = StringComparePolicy.SUBSTRING; 197 198 private MCTable(Wrap<? extends Table> tableWrap) { 199 this.control = tableWrap; 200 } 201 202 /** 203 * @return a list of all the tables in the default shell. 204 */ 205 public static List<MCTable> getAll() { 206 return getAll(getShell()); 207 } 208 209 /** 210 * Returns all currently visible tables as McTables in a list. 211 * 212 * @param shell 213 * the shell to search for tables. 214 * @return a {@link List} of {@link MCTable} 215 */ 216 public static List<MCTable> getAll(Wrap<? extends Shell> shell) { 217 return getAll(shell, true); 218 } 219 220 /** 221 * Returns all currently visible tables as McTables in a list. 222 * 223 * @param shell 224 * the shell to search for tables. 225 * @param waitForIdle 226 * {@code true} if supposed to wait for the UI to be idle before performing the 227 * lookup 228 * @return a {@link List} of {@link MCTable} 229 */ 230 @SuppressWarnings("unchecked") 231 public static List<MCTable> getAll(Wrap<? extends Shell> shell, boolean waitForIdle) { 232 List<Wrap<? extends Table>> list = getVisible(shell.as(Parent.class, Table.class).lookup(Table.class), 233 waitForIdle, false); 234 List<MCTable> tables = new ArrayList<>(); 235 for (int i = 0; i < list.size(); i++) { 236 tables.add(new MCTable(list.get(i))); 237 } 238 return tables; 239 } 240 241 /** 242 * Returns all currently visible tables as {@link MCTable} in a list. 243 * 244 * @param dialog 245 * the {@link MCDialog} to search for tables. 246 * @return a {@link List} of {@link MCTable} 247 */ 248 public static List<MCTable> getAll(MCDialog dialog) { 249 return getAll(dialog.getDialogShell()); 250 } 251 252 /** 253 * Finds tables by index, generally you should not use this method, but rather get all tables 254 * and keep the list up-to-date. 255 * 256 * @param shell 257 * the shell to search 258 * @param index 259 * the index in the list of tables 260 * @return the {@link MCTable} representing the table at the specified index, or {@code null} 261 * if index is out of range 262 */ 263 @SuppressWarnings("unchecked") 264 static MCTable getByIndex(Wrap<? extends Shell> shell, int index) { 265 Lookup<Table> lookup = shell.as(Parent.class, Table.class).lookup(Table.class); 266 return (index < lookup.size()) ? new MCTable(lookup.wrap(index)) : null; 267 } 268 269 /** 270 * Finds tables by column header (first match only) 271 * 272 * @param headerName 273 * the name of the column header 274 * @return a {@link MCTable} 275 */ 276 public static MCTable getByColumnHeader(String headerName) { 277 return getByColumnHeader(getShell(), headerName); 278 } 279 280 /** 281 * Finds tables by column header (first match only) 282 * 283 * @param shell 284 * the shell in which to look for the table 285 * @param headerName 286 * the name of the column header 287 * @return a {@link MCTable} 288 */ 289 public static MCTable getByColumnHeader(Wrap<? extends Shell> shell, String headerName) { 290 for (MCTable table : getAll(shell)) { 291 if (table.getColumnIndex(headerName) != null) { 292 return table; 293 } 294 } 295 return null; 296 } 297 298 /** 299 * Finds a table by name (data set by the key "name") 300 * 301 * @param name 302 * the name of the table 303 * @return a {@link MCTable} 304 */ 305 public static MCTable getByName(String name) { 306 return getByName(getShell(), name); 307 } 308 309 /** 310 * Finds a table by name (data set by the key "name") that is child of the provided dialog 311 * 312 * @param dialog 313 * the dialog from where to start the search (ancestor) 314 * @param name 315 * the name of the table 316 * @return a {@link MCTable} 317 */ 318 public static MCTable getByName(MCDialog dialog, String name) { 319 return getByName(dialog.getDialogShell(), name); 320 } 321 322 /** 323 * Finds a table by name (data set by the key "name") that is child of the provided shell 324 * 325 * @param shell 326 * the shell from where to start the search (ancestor) 327 * @param name 328 * the name of the table 329 * @return a {@link MCTable} 330 */ 331 @SuppressWarnings("unchecked") 332 public static MCTable getByName(Wrap<? extends Shell> shell, String name) { 333 return new MCTable(shell.as(Parent.class, Table.class) 334 .lookup(Table.class, new ByName<>(name, StringComparePolicy.EXACT)).wrap()); 335 } 336 337 /** 338 * Returns a List of string lists containing the table's complete table item text values. 339 * 340 * @return a {@link List} of {@link List} of {@link String} 341 */ 342 public List<List<String>> getAllColumnItemTexts() { 343 List<List<String>> result = new ArrayList<>(); 344 for (TableRow tableRow : getRows()) { 345 result.add(tableRow.getColumns()); 346 } 347 return result; 348 } 349 350 /** 351 * Returns a column from a table 352 * 353 * @param columnId 354 * the column to get 355 * @return the requested column's text value(s) 356 */ 357 public List<String> getColumnItemTexts(int columnId) { 358 List<String> column = new ArrayList<>(); 359 for (TableRow row : getRows()) { 360 column.add(row.getText(columnId)); 361 } 362 return column; 363 } 364 365 /** 366 * Returns a column from a table 367 * 368 * @param columnHeader 369 * the column to get 370 * @return the requested column's text value(s) 371 */ 372 public List<String> getColumnItemTexts(String columnHeader) { 373 List<String> column = new ArrayList<>(); 374 for (TableRow row : getRows()) { 375 column.add(row.getText(columnHeader)); 376 } 377 return column; 378 } 379 380 /** 381 * @param columnHeader 382 * the header of the column 383 * @return the index of the column 384 */ 385 public Integer getColumnIndex(String columnHeader) { 386 return getColumnNameMap().get(columnHeader); 387 } 388 389 private Map<String, Integer> getColumnNameMap() { 390 final Table table = getWrap().getControl(); 391 Fetcher<Map<String, Integer>> fetcher = new Fetcher<Map<String, Integer>>() { 392 @Override 393 public void run() { 394 TableColumn[] tableColumns = table.getColumns(); 395 Map<String, Integer> columnNameMap = new HashMap<>(); 396 int columnIndex = 0; 397 for (TableColumn tc : tableColumns) { 398 columnNameMap.put(tc.getText(), columnIndex); 399 columnIndex++; 400 } 401 setOutput(columnNameMap); 402 } 403 }; 404 Display.getDefault().syncExec(fetcher); 405 return fetcher.getOutput(); 406 } 407 408 /** 409 * Returns a list of strings for the table item of the specified index. 410 * 411 * @param rowIndex 412 * the index of the item to get the text for 413 * @return a {@link List} of {@link String} 414 */ 415 public List<String> getItemTexts(int rowIndex) { 416 TableRow row = getRow(rowIndex); 417 return row.getColumns(); 418 } 419 420 /** 421 * Gets a TableRow for the row index provided. 422 * 423 * @param index 424 * the index of the row to get data from 425 * @return a {@link TableRow} with the data from the table row 426 */ 427 public TableRow getRow(int index) { 428 return getRow(index, getColumnNameMap()); 429 } 430 431 /** 432 * Gets a TableRow for the row index provided. 433 * 434 * @param index 435 * the index of the row to get data from 436 * @param columnNameMap 437 * a map of the columns' headers and indexes 438 * @return a {@link TableRow} with the data from the table row 439 */ 440 public TableRow getRow(int index, Map<String, Integer> columnNameMap) { 441 final Table table = getWrap().getControl(); 442 Fetcher<TableRow> fetcher = new Fetcher<TableRow>() { 443 @Override 444 public void run() { 445 int columns = columnNameMap.size(); 446 TableRow output; 447 TableItem item = table.getItem(index); 448 String text = item.getText(); 449 List<String> texts = new ArrayList<>(); 450 for (int i = 0; i < columns; i++) { 451 texts.add(item.getText(i)); 452 } 453 output = new TableRow(text, texts, columnNameMap); 454 setOutput(output); 455 } 456 }; 457 Display.getDefault().syncExec(fetcher); 458 return fetcher.getOutput(); 459 } 460 461 /** 462 * Gets all the row and column data of the table 463 * 464 * @return a {@link List} of {@link TableRow} 465 */ 466 public List<TableRow> getRows() { 467 int numberOfItems = this.getItemCount(); 468 List<TableRow> allRows = new ArrayList<>(); 469 470 Map<String, Integer> columnNameMap = getColumnNameMap(); 471 for (int i = 0; i < numberOfItems; i++) { 472 allRows.add(getRow(i, columnNameMap)); 473 } 474 475 return allRows; 476 } 477 478 /** 479 * Gets an Image for a specific row of the table 480 * 481 * @param rowIndex 482 * index of the row to get 483 * @return an {@link Image} 484 */ 485 public Image getItemImage(int rowIndex) { 486 final Table table = getWrap().getControl(); 487 Fetcher<Image> fetcher = new Fetcher<Image>() { 488 @Override 489 public void run() { 490 TableItem item = table.getItem(rowIndex); 491 Image icon = item.getImage(); 492 setOutput(icon); 493 } 494 }; 495 Display.getDefault().syncExec(fetcher); 496 return fetcher.getOutput(); 497 } 498 499 /** 500 * Gets the number of items in the table 501 * 502 * @return the number of items in the table 503 */ 504 public int getItemCount() { 505 final Table table = getWrap().getControl(); 506 Fetcher<Integer> fetcher = new Fetcher<Integer>() { 507 @Override 508 public void run() { 509 int count = table.getItemCount(); 510 setOutput(count); 511 } 512 }; 513 Display.getDefault().syncExec(fetcher); 514 return fetcher.getOutput().intValue(); 515 } 516 517 /** 518 * Whether or not the table contains the text given 519 * 520 * @param item 521 * the text 522 * @return {@code true} if found. 523 */ 524 public boolean hasItem(String item) { 525 return (getItemIndex(item) != -1) ? true : false; 526 } 527 528 /** 529 * Returns the number of (exactly) matching table items 530 * 531 * @param itemText 532 * the text 533 * @return the number of matching items in the table 534 */ 535 public int numberOfMatchingItems(String itemText) { 536 return numberOfMatchingItems(itemText, StringComparePolicy.EXACT); 537 } 538 539 /** 540 * Returns the number of matching table items 541 * 542 * @param itemText 543 * the text of the items to match 544 * @param policy 545 * the policy to use when matching 546 * @return the number of matching items in the table 547 */ 548 public int numberOfMatchingItems(String itemText, StringComparePolicy policy) { 549 return getItemIndexes(itemText, policy).size(); 550 } 551 552 /** 553 * Returns the indexes of matching table items (Exact matching) 554 * 555 * @param itemText 556 * the text of the items to match 557 * @return a {@link List} of {@link Integer} of the matching indexes 558 */ 559 public List<Integer> getItemIndexes(String itemText) { 560 return getItemIndexes(itemText, StringComparePolicy.EXACT); 561 } 562 563 /** 564 * Returns the indexes of matching table items 565 * 566 * @param itemText 567 * the text of the matching table item 568 * @param policy 569 * the matching policy to use 570 * @return a {@link List} of {@link Integer} of the matching indexes 571 */ 572 public List<Integer> getItemIndexes(String itemText, StringComparePolicy policy) { 573 List<TableRow> rows = getRows(); 574 List<Integer> index = new ArrayList<>(); 575 for (int i = 0; i < rows.size(); i++) { 576 TableRow row = rows.get(i); 577 if (row.hasColumnText(itemText, policy) || row.hasText(itemText, policy)) { 578 index.add(i); 579 } 580 } 581 return index; 582 } 583 584 /** 585 * Selects the given item (if found). This could also be done using the Selector of the Table 586 * (like "tableWrap.as(Selectable.class).selector().select(goalIndex)") but there seems to be an 587 * issue with TableItem.getBounds() on OS X where we run into some nasty ArrayIndexOutOfBounds 588 * exceptions because that code relies on mouse().click(). Another drawback with that approach 589 * is that we might actually be trying to click outside of what's visible. Keyboard navigation 590 * is safer so the Jemmy IndexItemSelector class (as well as TextItemSelector) should be fixed 591 * to do that instead 592 * 593 * @param item 594 * the item to select 595 */ 596 public void select(String item) { 597 Assert.assertTrue("Unable to select " + item + ".", select(getItemIndex(item))); 598 } 599 600 /** 601 * Selects the given item (if found). This could also be done using the Selector of the Table 602 * (like "tableWrap.as(Selectable.class).selector().select(goalIndex)") but there seems to be an 603 * issue with TableItem.getBounds() on OS X where we run into some nasty ArrayIndexOutOfBounds 604 * exceptions because that code relies on mouse().click(). Another drawback with that approach 605 * is that we might actually be trying to click outside of what's visible. Keyboard navigation 606 * is safer so the Jemmy IndexItemSelector class (as well as TextItemSelector) should be fixed 607 * to do that instead 608 * 609 * @param item 610 * the item to select 611 * @param columnIndex 612 * the column index to select 613 */ 614 public void select(String item, int columnIndex) { 615 Assert.assertTrue("Unable to select " + item + ".", select(getItemIndex(item), columnIndex)); 616 } 617 618 /** 619 * Performs a mouse click at a specified column index of an item 620 * 621 * @param item 622 * the item to click 623 * @param columnIndex 624 * the column index where to click 625 */ 626 public void clickItem(String item, int columnIndex) { 627 select(getItemIndex(item), columnIndex); 628 scrollbarSafeSelection(); 629 control.mouse().click(1, getRelativeClickPoint(getSelectedItem(), columnIndex)); 630 } 631 632 /** 633 * Performs a mouse click at a specified column header's index of an item 634 * 635 * @param item 636 * the item to click 637 * @param columnHeader 638 * the column header 639 */ 640 public void clickItem(String item, String columnHeader) { 641 clickItem(item, getColumnIndex(columnHeader)); 642 } 643 644 /** 645 * Selects the given item (if found). This could also be done using the Selector of the Table 646 * (like "tableWrap.as(Selectable.class).selector().select(goalIndex)") but there seems to be an 647 * issue with TableItem.getBounds() on OS X where we run into ArrayIndexOutOfBounds exceptions 648 * because that code relies on mouse().click(). Another drawback with that approach is that we 649 * might actually be trying to click outside of what's visible. Keyboard navigation is safer so 650 * the Jemmy IndexItemSelector class (as well as TextItemSelector) should be fixed to do that 651 * instead 652 * 653 * @param item 654 * the item to select 655 * @param columnHeader 656 * the column header to select 657 */ 658 public void select(String item, String columnHeader) { 659 Assert.assertTrue("Unable to select " + item + ".", select(getItemIndex(item), getColumnIndex(columnHeader))); 660 } 661 662 /** 663 * Selects the given item (if found). This could also be done using the Selector of the Table 664 * (like "tableWrap.as(Selectable.class).selector().select(goalIndex)") but there seems to be an 665 * issue with TableItem.getBounds() on OS X where we run into ArrayIndexOutOfBounds exceptions 666 * because that code relies on mouse().click(). Another drawback with that approach is that we 667 * might actually be trying to click outside of what's visible. Keyboard navigation is safer so 668 * the Jemmy IndexItemSelector class (as well as TextItemSelector) should be fixed to do that 669 * instead 670 * 671 * @param item 672 * the item to select 673 * @param exactMatching 674 * if {@code true} {@link StringComparePolicy.EXACT} is used. Otherwise 675 * {@link StringComparePolicy.SUBSTRING} will be used 676 */ 677 public void select(String item, boolean exactMatching) { 678 StringComparePolicy thisPolicy = (exactMatching) ? StringComparePolicy.EXACT : StringComparePolicy.SUBSTRING; 679 Assert.assertTrue("Unable to select " + item + ".", select(getItemIndex(item, thisPolicy))); 680 } 681 682 /** 683 * Selects the item at the given index (if not -1)). Will retry the selection a maximum number 684 * of three times just to make sure that lost and regained focus doesn't break things 685 * 686 * @param index 687 * the index of the item 688 * @param columnIndex 689 * the column index of the item to select 690 * @return {@code true} if selected index is the same as the provided. {@code false} otherwise 691 */ 692 public boolean select(int index, int columnIndex) { 693 if (index != -1) { 694 ensureFocus(); 695 int maxRetries = 3; 696 while (control.getProperty(Integer.class, Selectable.STATE_PROP_NAME) != index && maxRetries > 0) { 697 maxRetries--; 698 int startIndex = control.getProperty(Integer.class, Selectable.STATE_PROP_NAME); 699 if (startIndex == -1) { 700 control.keyboard().pushKey(KeyboardButtons.DOWN); 701 control.keyboard().pushKey(KeyboardButtons.UP); 702 startIndex = control.getProperty(Integer.class, Selectable.STATE_PROP_NAME); 703 } 704 if (startIndex != -1) { 705 int steps = index - startIndex; 706 KeyboardButtons stepButton = (index > startIndex) ? KeyboardButtons.DOWN : KeyboardButtons.UP; 707 for (int i = 0; i < Math.abs(steps); i++) { 708 control.keyboard().pushKey(stepButton); 709 } 710 // if we have a column > 0 do some side stepping 711 for (int i = 0; i < columnIndex; i++) { 712 control.keyboard().pushKey(KeyboardButtons.RIGHT); 713 } 714 } 715 } 716 return (control.getProperty(Integer.class, Selectable.STATE_PROP_NAME) == index && index != -1); 717 } else { 718 return false; 719 } 720 } 721 722 /** 723 * Selects the item at the given index (if not -1)) 724 * 725 * @param index 726 * the index of the item 727 * @return {@code true} if selected index is the same as the provided. {@code false} otherwise 728 */ 729 public boolean select(int index) { 730 return select(index, 0); 731 } 732 733 /** 734 * Context clicks the currently selected table item and chooses the supplied option 735 * 736 * @param desiredState 737 * the selection state to which the context choice is to be to set to 738 * @param choice 739 * the context menu path to the option 740 */ 741 @SuppressWarnings("unchecked") 742 public void contextChoose(boolean desiredState, String ... choice) { 743 scrollbarSafeSelection(); 744 StringPopupSelectableOwner<Table> spo = control.as(StringPopupSelectableOwner.class); 745 spo.setPolicy(policy); 746 spo.push(desiredState, getRelativeClickPoint(getSelectedItem()), choice); 747 } 748 749 /** 750 * Context clicks the currently selected table item and finds out the selection status of the 751 * supplied option 752 * 753 * @param choice 754 * the context menu path to the option 755 * @return the selection status of the item 756 */ 757 @SuppressWarnings("unchecked") 758 public boolean getContextOptionState(String ... choice) { 759 scrollbarSafeSelection(); 760 StringPopupSelectableOwner<Table> spo = control.as(StringPopupSelectableOwner.class); 761 spo.setPolicy(policy); 762 return spo.getState(getRelativeClickPoint(getSelectedItem()), choice); 763 } 764 765 /** 766 * Context clicks the currently selected table item and chooses the supplied option 767 * 768 * @param choice 769 * the context menu path to the option 770 */ 771 @SuppressWarnings("unchecked") 772 public void contextChoose(String ... choice) { 773 scrollbarSafeSelection(); 774 StringPopupOwner<Table> spo = control.as(StringPopupOwner.class); 775 spo.setPolicy(policy); 776 spo.push(getRelativeClickPoint(getSelectedItem()), choice); 777 } 778 779 private Wrap<? extends TableItem> getSelectedItem() { 780 Fetcher<TableItem> fetcher = new Fetcher<TableItem>() { 781 @Override 782 public void run() { 783 setOutput(getWrap().getControl().getSelection()[0]); 784 } 785 }; 786 Display.getDefault().syncExec(fetcher); 787 return new ItemWrap<>(getWrap(), fetcher.getOutput()); 788 } 789 790 /** 791 * Calculates the click point of the child relative to the parent provided. Uses a rather 792 * cumbersome way of getting the bounds because {@link ArrayIndexOutOfBoundsException} in some 793 * cases getting thrown on Mac OS X. 794 * 795 * @param child 796 * the wrapped child control 797 * @return the {@link Point} of the child relative to the parent 798 */ 799 private Point getRelativeClickPoint(final Wrap<? extends TableItem> child) { 800 return getRelativeClickPoint(child, null); 801 } 802 803 /** 804 * Calculates the click point of the child relative to the parent. Uses a rather cumbersome way 805 * of getting the bounds because {@link ArrayIndexOutOfBoundsException} in some cases getting 806 * thrown on Mac OS X. 807 * 808 * @param child 809 * the wrapped child control 810 * @param columnIndex 811 * the column index of the table item for which to get the click point. May be null 812 * if no column 813 * @return the {@link Point} of the child relative to the parent 814 */ 815 private Point getRelativeClickPoint(final Wrap<? extends TableItem> child, final Integer columnIndex) { 816 Fetcher<Point> fetcher = new Fetcher<Point>() { 817 @Override 818 public void run() { 819 Rectangle childRect = null; 820 if (columnIndex != null) { 821 childRect = child.getControl().getBounds(columnIndex); 822 } else { 823 try { 824 childRect = child.getControl().getBounds(); 825 } catch (ArrayIndexOutOfBoundsException e) { 826 childRect = child.getControl().getBounds(0); 827 } 828 } 829 setOutput(new Point(childRect.x + childRect.width / 2, childRect.y + childRect.height / 2)); 830 } 831 }; 832 Display.getDefault().syncExec(fetcher); 833 return fetcher.getOutput(); 834 } 835 836 private int getItemIndex(String itemText) { 837 return getItemIndex(itemText, policy); 838 } 839 840 private int getItemIndex(String itemText, StringComparePolicy policy) { 841 List<TableRow> rows = getRows(); 842 int index = -1; 843 for (int i = 0; i < rows.size(); i++) { 844 TableRow row = rows.get(i); 845 if (row.hasColumnText(itemText, policy) || row.hasText(itemText, policy)) { 846 index = i; 847 break; 848 } 849 } 850 return index; 851 } 852 853 @SuppressWarnings("unchecked") 854 private Wrap<? extends Table> getWrap() { 855 return control.as(TableWrap.class); 856 } 857 858 private void scrollbarSafeSelection() { 859 int index = control.getProperty(Integer.class, Selectable.STATE_PROP_NAME); 860 control.keyboard().pushKey(KeyboardButtons.DOWN); 861 control.keyboard().pushKey(KeyboardButtons.UP); 862 select(index); 863 } 864 }