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