1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation. Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javafx.scene.control.test.util;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collection;
  29 import java.util.Collections;
  30 import java.util.HashSet;
  31 import java.util.Set;
  32 import org.jemmy.Point;
  33 import org.jemmy.interfaces.Keyboard.KeyboardButtons;
  34 import org.jemmy.interfaces.Keyboard.KeyboardModifiers;
  35 import org.junit.Assert;
  36 
  37 /**
  38  * Helps to track actual state of selection in controls like TreeView, listView,
  39  * TableView and TreeTableView.
  40  *
  41  * @author Oleg Barbashov, Alexander Kirov
  42  */
  43 public class MultipleSelectionHelper {
  44 
  45     public Point focus = new Point(-1, 0);
  46     public Point anchor = null;
  47     public boolean multiple = false;
  48     public boolean singleCell = false;
  49     public Collection<Point> selectedSet = new ArrayList<Point>();
  50     public int rows;
  51     public int columns;
  52     public int pageHeight = -1;
  53     public int pageWidth = -1;
  54     public boolean ctrlA = false;
  55     public int topVisible = -1;
  56     public int bottomVisible = -1;
  57 
  58     public MultipleSelectionHelper(int columns, int rows) {
  59         this.rows = rows;
  60         this.columns = columns;
  61     }
  62 
  63     public void setColumnsNum(int columns) {
  64         this.columns = columns;
  65     }
  66 
  67     public void setRowsNum(int rows) {
  68         this.rows = rows;
  69     }
  70 
  71     /**
  72      * ALARM: must be called before pushing key combination, from code, which
  73      * knows, how to understand, which area is visible now. And this visible
  74      * area must be passed as argument.
  75      *
  76      * @param area - range [begin, end] - current fully visible range of lines.
  77      * Checks, that begin <= end.
  78      */
  79     public void setVisibleRange(Range area) {
  80         Assert.assertTrue(area.begin <= area.end);
  81         this.topVisible = area.begin;
  82         this.bottomVisible = area.end;
  83         this.pageHeight = this.bottomVisible - this.topVisible + 1;
  84     }
  85 
  86     public void setPageHeight(int height) {
  87         this.pageHeight = height;
  88     }
  89 
  90     public void setPageWidth(int width) {
  91         this.pageWidth = width;
  92     }
  93 
  94     public void setMultiple(boolean multiple) {
  95         this.multiple = multiple;
  96     }
  97 
  98     public void setSingleCell(boolean singleCell) {
  99         if (this.singleCell != singleCell) {
 100             if (singleCell) {
 101                 focus = new Point(0, 0);
 102             } else {
 103                 focus = new Point(-1, 0);
 104             }
 105             this.singleCell = singleCell;
 106             selectedSet.clear();
 107             anchor = null;
 108         }
 109     }
 110 
 111     public void moveFocusTo(int row, int column) {
 112         focus = new Point(row, column);
 113     }
 114 
 115     public void moveAnchorTo(int row, int column) {
 116         anchor = new Point(row, column);
 117     }
 118 
 119     public Collection<Point> getSelected() {
 120         return new HashSet<Point>(selectedSet);
 121     }
 122 
 123     public void push(KeyboardButtons btn, KeyboardModifiers... modifiers) {
 124         ctrlA = false;
 125         switch (modifiers.length) {
 126             case 0:
 127                 push(btn);
 128                 break;
 129             case 1:
 130                 switch (modifiers[0]) {
 131                     case META_DOWN_MASK:
 132                     case CTRL_DOWN_MASK:
 133                         ctrl(btn);
 134                         break;
 135                     case SHIFT_DOWN_MASK:
 136                         shift(btn);
 137                         break;
 138                     default:
 139                         throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 140                 }
 141                 break;
 142             case 2:
 143                 Set<KeyboardModifiers> set = new HashSet<KeyboardModifiers>();
 144                 Collections.addAll(set, modifiers);
 145                 if (set.contains(KeyboardModifiers.SHIFT_DOWN_MASK)
 146                         && (set.contains(KeyboardModifiers.CTRL_DOWN_MASK)
 147                         || set.contains(KeyboardModifiers.META_DOWN_MASK))) {
 148                     ctrlShift(btn);
 149                     break;
 150                 }
 151                 if (set.contains(KeyboardModifiers.CTRL_DOWN_MASK)
 152                         && set.contains(KeyboardModifiers.META_DOWN_MASK)) {
 153                     metaCtrl(btn);
 154                     break;
 155                 }
 156                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 157             default:
 158                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 159         }
 160     }
 161 
 162     public void push(KeyboardButtons btn) {
 163         switch (btn) {
 164             case PAGE_DOWN:
 165                 checkPageHeight();
 166                 moveFocusOnePageDown();
 167                 anchor = new Point(focus);
 168                 select(selectedSet, focus, focus, true);
 169                 return;
 170             case PAGE_UP:
 171                 checkPageHeight();
 172                 moveFocusOnePageUp();
 173                 anchor = new Point(focus);
 174                 select(selectedSet, focus, focus, true);
 175                 return;
 176             case SPACE:
 177                 anchor = new Point(focus);
 178                 select(selectedSet, focus, false, false);
 179                 return;
 180         }
 181         switch (btn) {
 182             case HOME:
 183                 focus.y = 0;
 184                 anchor = new Point(focus);
 185                 break;
 186             case END:
 187                 focus.y = rows - 1;
 188                 anchor = new Point(focus);
 189                 break;
 190             case UP:
 191                 if (focus.y > 0) {
 192                     focus.y--;
 193                 }
 194                 anchor = new Point(focus);
 195                 break;
 196             case DOWN:
 197                 if (focus.y < rows - 1) {
 198                     focus.y++;
 199                 }
 200                 anchor = new Point(focus);
 201                 break;
 202             case LEFT:
 203                 if (singleCell && (focus.x > 0)) {
 204                     focus.x--;
 205                 }
 206                 anchor = new Point(focus);
 207                 break;
 208             case RIGHT:
 209                 if (singleCell && (focus.x < columns - 1)) {
 210                     focus.x++;
 211                 }
 212                 anchor = new Point(focus);
 213                 break;
 214             default:
 215                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 216         }
 217         select(selectedSet, focus, true, false);
 218     }
 219 
 220     public void ctrl(KeyboardButtons btn) {
 221         switch (btn) {
 222             case A:
 223                 select(selectedSet, new Point(0, 0), new Point(columns - 1, rows - 1), true);
 224                 //focus.y = rows - 1;//We dont change focus, it stays at the same place.
 225                 ctrlA = true;
 226                 break;
 227             case HOME:
 228                 focus.y = 0;
 229                 break;
 230             case END:
 231                 focus.y = rows - 1;
 232                 break;
 233             case PAGE_DOWN:
 234                 checkPageHeight();
 235                 moveFocusOnePageDown();
 236                 break;
 237             case PAGE_UP:
 238                 checkPageHeight();
 239                 moveFocusOnePageUp();
 240                 break;
 241             case SPACE:
 242                 anchor = new Point(focus);
 243                 select(selectedSet, focus, false, true);
 244                 return;
 245             case UP:
 246                 if (focus.y > 0) {
 247                     focus.y--;
 248                 }
 249                 break;
 250             case DOWN:
 251                 if (focus.y < rows - 1) {
 252                     focus.y++;
 253                 }
 254                 break;
 255             case LEFT:
 256                 if (focus.x > 0) {
 257                     focus.x--;
 258                 }
 259                 break;
 260             case RIGHT:
 261                 if (focus.x < columns - 1) {
 262                     focus.x++;
 263                 }
 264                 break;
 265         }
 266     }
 267 
 268     //See also RT-34619
 269     public void shift(KeyboardButtons btn) {
 270         if (anchor == null) {
 271             anchor = new Point(focus);
 272         }
 273 
 274         switch (btn) {
 275             case HOME:
 276                 focus.y = 0;
 277                 select(selectedSet, anchor, focus, true);
 278                 break;
 279             case END:
 280                 focus.y = rows - 1;
 281                 select(selectedSet, anchor, focus, true);
 282                 break;
 283             case UP:
 284                 if (focus.y > 0) {
 285                     focus.y--;
 286                     if (multiple) {
 287                         select(selectedSet, anchor, focus, true);
 288                     } else {
 289                         select(selectedSet, focus, focus, true);
 290                     }
 291                 }
 292                 break;
 293             case DOWN:
 294                 if (focus.y < rows - 1) {
 295                     focus.y++;
 296                     if (multiple) {
 297                         select(selectedSet, anchor, focus, true);
 298                     } else {
 299                         select(selectedSet, focus, focus, true);
 300                     }
 301                 }
 302                 break;
 303             case PAGE_UP:
 304                 if (!singleCell) {
 305                     checkPageHeight();
 306                     moveFocusOnePageUp();
 307                     select(selectedSet, anchor, focus, true);
 308                 }
 309                 break;
 310             case PAGE_DOWN:
 311                 if (!singleCell) {
 312                     checkPageHeight();
 313                     moveFocusOnePageDown();
 314                     select(selectedSet, anchor, focus, true);
 315                 }
 316                 break;
 317             case LEFT:
 318                 if (focus.x > 0) {
 319                     focus.x--;
 320                 }
 321                 if (multiple) {
 322                     select(selectedSet, anchor, focus, true);
 323                 } else {
 324                     select(selectedSet, focus, focus, true);
 325                 }
 326                 break;
 327             case RIGHT:
 328                 if (focus.x < columns - 1) {
 329                     focus.x++;
 330                 }
 331                 if (multiple) {
 332                     select(selectedSet, anchor, focus, true);
 333                 } else {
 334                     select(selectedSet, focus, focus, true);
 335                 }
 336                 break;
 337             case SPACE:
 338                 if (multiple) {
 339                     select(selectedSet, anchor, focus, true);
 340                 } else {
 341                     select(selectedSet, focus, focus, true);
 342                 }
 343                 break;
 344         }
 345     }
 346 
 347     public void ctrlShift(KeyboardButtons btn) {
 348         if (anchor == null) {
 349             anchor = new Point(focus);
 350         }
 351         switch (btn) {
 352             case HOME:
 353                 focus.y = 0;
 354                 if (multiple) {
 355                     select(selectedSet, anchor, focus, false);
 356                 } else {
 357                     select(selectedSet, focus, focus, true);
 358                 }
 359                 break;
 360             case END:
 361                 focus.y = rows - 1;
 362                 if (multiple) {
 363                     select(selectedSet, anchor, focus, false);
 364                 } else {
 365                     select(selectedSet, focus, focus, true);
 366                 }
 367                 break;
 368             case UP:
 369                 if (focus.y > 0) {
 370                     focus.y--;
 371                     if (multiple) {
 372                         select(selectedSet, anchor, focus, false);
 373                     } else {
 374                         select(selectedSet, focus, focus, true);
 375                     }
 376                 }
 377                 break;
 378             case DOWN:
 379                 if (focus.y < rows - 1) {
 380                     focus.y++;
 381                     if (multiple) {
 382                         select(selectedSet, anchor, focus, false);
 383                     } else {
 384                         select(selectedSet, focus, focus, true);
 385                     }
 386                 }
 387                 break;
 388             case PAGE_UP:
 389                 if (focus.y > 0) {
 390                     moveFocusOnePageUp();
 391                     if (multiple) {
 392                         select(selectedSet, anchor, focus, false);
 393                     } else {
 394                         select(selectedSet, focus, focus, true);
 395                     }
 396                 }
 397                 break;
 398             case PAGE_DOWN:
 399                 if (focus.y < rows - 1) {
 400                     moveFocusOnePageDown();
 401                     if (multiple) {
 402                         select(selectedSet, anchor, focus, false);
 403                     } else {
 404                         select(selectedSet, focus, focus, true);
 405                     }
 406                 }
 407                 break;
 408             case LEFT:
 409                 if (focus.x > 0) {
 410                     focus.x--;
 411                     if (multiple) {
 412                         select(selectedSet, anchor, focus, false);
 413                     } else {
 414                         select(selectedSet, focus, focus, true);
 415                     }
 416                 }
 417                 break;
 418             case RIGHT:
 419                 if (focus.x < columns - 1) {
 420                     focus.x++;
 421                     if (multiple) {
 422                         select(selectedSet, anchor, focus, false);
 423                     } else {
 424                         select(selectedSet, focus, focus, true);
 425                     }
 426                 }
 427                 break;
 428             case SPACE:
 429                 if (multiple) {
 430                     select(selectedSet, anchor, focus, false);
 431                 } else {
 432                     select(selectedSet, focus, focus, true);
 433                 }
 434                 break;
 435         }
 436     }
 437 
 438     public void metaCtrl(KeyboardButtons btn) {
 439         if (anchor == null) {
 440             anchor = new Point(focus);
 441         }
 442         switch (btn) {
 443             case SPACE:
 444                 anchor = new Point(focus);
 445                 select(selectedSet, focus, false, true);
 446                 return;
 447             default:
 448                 throw new UnsupportedOperationException("Unsupported : Meta + Ctrl + " + btn);
 449         }
 450     }
 451 
 452     public void click(int column, int row, KeyboardButtons modifier) {
 453         if (modifier != null) {
 454             switch (modifier) {
 455                 case CONTROL:
 456                 case META:
 457                     ctrlClick(column, row);
 458                     return;
 459                 case SHIFT:
 460                     shiftClick(column, row);
 461                     return;
 462             }
 463         }
 464         click(column, row);
 465     }
 466 
 467     public void click(int column, int row) {
 468         if (!singleCell) {
 469             column = -1;
 470         }
 471         focus = new Point(column, row);
 472         anchor = new Point(focus);
 473         selectedSet.clear();
 474         selectedSet.add(new Point(column, row));
 475     }
 476 
 477     protected void shiftClick(int column, int row) {
 478         if (multiple) {
 479             if (anchor == null) {
 480                 anchor = new Point(focus);
 481             }
 482             if (!singleCell) {
 483                 column = -1;
 484             }
 485             focus.y = row;
 486             focus.x = column;
 487             select(selectedSet, anchor, focus, true);
 488         } else {
 489             click(column, row);
 490         }
 491     }
 492 
 493     protected void ctrlClick(int column, int row) {
 494         //ctrl creates anchor at (column,row).
 495         if (multiple) {
 496             if (!singleCell) {
 497                 column = -1;
 498             }
 499             focus.y = row;
 500             focus.x = column;
 501             anchor = new Point(focus);
 502             select(selectedSet, focus, false, true);
 503         } else {
 504             if (!singleCell) {
 505                 column = -1;
 506             }
 507             focus = new Point(column, row);
 508             anchor = new Point(focus);
 509             //See RT-34649
 510             if (selectedSet.contains(focus)) {
 511                 selectedSet.clear();
 512             } else {
 513                 selectedSet.clear();
 514                 selectedSet.add(new Point(column, row));
 515             }
 516         }
 517     }
 518 
 519     protected void select(Collection<Point> set, Point p, boolean clear, boolean invert) {
 520         if (clear) {
 521             set.clear();
 522         }
 523         if (invert && selectedSet.contains(p)) {
 524             selectedSet.remove(p);
 525         } else {
 526             if (!selectedSet.contains(p)) {
 527                 selectedSet.add(new Point(p));
 528             }
 529         }
 530     }
 531 
 532     protected void select(Collection<Point> set, Point p1, Point p2, boolean clear) {
 533         if (clear) {
 534             set.clear();
 535         }
 536         select(set, p1, p2);
 537     }
 538 
 539     protected void select(Collection<Point> set, Point p1, Point p2) {
 540         if (singleCell) {
 541             for (int j = Math.min(p1.x, p2.x); j <= Math.max(p1.x, p2.x); j++) {
 542                 for (int i = Math.min(p1.y, p2.y); i <= Math.max(p1.y, p2.y); i++) {
 543                     set.add(new Point(j, i));
 544                 }
 545             }
 546         } else {
 547             for (int i = Math.min(p1.y, p2.y); i <= Math.max(p1.y, p2.y); i++) {
 548                 set.add(new Point(-1, i));
 549             }
 550         }
 551     }
 552 
 553     protected void checkPageHeight() {
 554         if (pageHeight < 0 || bottomVisible < 0 || topVisible < 0) {
 555             throw new IndexOutOfBoundsException(this.getClass().getSimpleName() + ": incorrect page parameters are set");
 556         }
 557     }
 558 
 559     protected void checkPageWidth() {
 560         if (pageWidth < 0) {
 561             throw new IndexOutOfBoundsException(this.getClass().getSimpleName() + ": incorrect page height is set");
 562         }
 563     }
 564 
 565     private void moveFocusOnePageDown() {
 566         if (focus.y < bottomVisible) {
 567             focus.y = bottomVisible;
 568         } else {
 569             focus.y += (pageHeight - 1);
 570             if (focus.y > rows - 1) {
 571                 focus.y = rows - 1;
 572             }
 573         }
 574     }
 575 
 576     private void moveFocusOnePageUp() {
 577         if (focus.y > topVisible) {
 578             focus.y = topVisible;
 579         } else {
 580             focus.y -= (pageHeight - 1);
 581             if (focus.y < 0) {
 582                 focus.y = 0;
 583             }
 584         }
 585     }
 586 
 587     public static class Range {
 588 
 589         public final int begin;
 590         public final int end;
 591 
 592         public Range(int begin, int end) {
 593             this.begin = begin;
 594             this.end = end;
 595         }
 596 
 597         @Override
 598         public String toString() {
 599             return "Range : begin <" + begin + ">, end <" + end + ">.";
 600         }
 601     }
 602 
 603     public static class ListViewMultipleSelectionHelper extends MultipleSelectionHelper {
 604 
 605         public ListViewMultipleSelectionHelper(int columns, int rows) {
 606             super(columns, rows);
 607         }
 608 
 609         /* Due to the https://javafx-jira.kenai.com/browse/RT-34204
 610          * we don't need to remove the focused cell from selection
 611          * in single cell selection mode.
 612          */
 613         @Override
 614         protected void ctrlClick(int column, int row) {
 615             //ctrl creates anchor at (column,row).
 616             column = -1;
 617             if (multiple) {
 618                 focus.y = row;
 619                 focus.x = column;
 620                 anchor = new Point(focus);
 621                 select(selectedSet, focus, false, true);
 622             } else {
 623                 focus = new Point(column, row);
 624                 anchor = new Point(focus);
 625                 if (!selectedSet.contains(focus)) {
 626 
 627                     selectedSet.clear();
 628                     selectedSet.add(new Point(column, row));
 629                 }
 630             }
 631         }
 632     }
 633 }