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     /**
  64      * ALARM: must be called before pushing key combination, from code, which
  65      * knows, how to understand, which area is visible now. And this visible
  66      * area must be passed as argument.
  67      *
  68      * @param area - range [begin, end] - current fully visible range of lines.
  69      * Checks, that begin <= end.
  70      */
  71     public void setVisibleRange(Range area) {
  72         Assert.assertTrue(area.begin <= area.end);
  73         this.topVisible = area.begin;
  74         this.bottomVisible = area.end;
  75         this.pageHeight = this.bottomVisible - this.topVisible + 1;
  76     }
  77 
  78     public void setPageHeight(int height) {
  79         this.pageHeight = height;
  80     }
  81 
  82     public void setPageWidth(int width) {
  83         this.pageWidth = width;
  84     }
  85 
  86     public void setMultiple(boolean multiple) {
  87         this.multiple = multiple;
  88     }
  89 
  90     public void setSingleCell(boolean singleCell) {
  91         if (this.singleCell != singleCell) {
  92             if (singleCell) {
  93                 focus = new Point(0, 0);
  94             } else {
  95                 focus = new Point(-1, 0);
  96             }
  97             this.singleCell = singleCell;
  98             selectedSet.clear();
  99             anchor = null;
 100         }
 101     }
 102 
 103     public void moveFocusTo(int row, int column) {
 104         focus = new Point(row, column);
 105     }
 106 
 107     public void moveAnchorTo(int row, int column) {
 108         anchor = new Point(row, column);
 109     }
 110 
 111     public Collection<Point> getSelected() {
 112         return new HashSet<Point>(selectedSet);
 113     }
 114 
 115     public void push(KeyboardButtons btn, KeyboardModifiers... modifiers) {
 116         ctrlA = false;
 117         switch (modifiers.length) {
 118             case 0:
 119                 push(btn);
 120                 break;
 121             case 1:
 122                 switch (modifiers[0]) {
 123                     case META_DOWN_MASK:
 124                     case CTRL_DOWN_MASK:
 125                         ctrl(btn);
 126                         break;
 127                     case SHIFT_DOWN_MASK:
 128                         shift(btn);
 129                         break;
 130                     default:
 131                         throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 132                 }
 133                 break;
 134             case 2:
 135                 Set<KeyboardModifiers> set = new HashSet<KeyboardModifiers>();
 136                 Collections.addAll(set, modifiers);
 137                 if (set.contains(KeyboardModifiers.SHIFT_DOWN_MASK)
 138                         && (set.contains(KeyboardModifiers.CTRL_DOWN_MASK)
 139                         || set.contains(KeyboardModifiers.META_DOWN_MASK))) {
 140                     ctrlShift(btn);
 141                     break;
 142                 }
 143                 if (set.contains(KeyboardModifiers.CTRL_DOWN_MASK)
 144                         && set.contains(KeyboardModifiers.META_DOWN_MASK)) {
 145                     metaCtrl(btn);
 146                     break;
 147                 }
 148                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 149             default:
 150                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 151         }
 152     }
 153 
 154     public void push(KeyboardButtons btn) {
 155         switch (btn) {
 156             case PAGE_DOWN:
 157                 checkPageHeight();
 158                 moveFocusOnePageDown();
 159                 anchor = new Point(focus);
 160                 select(selectedSet, focus, focus, true);
 161                 return;
 162             case PAGE_UP:
 163                 checkPageHeight();
 164                 moveFocusOnePageUp();
 165                 anchor = new Point(focus);
 166                 select(selectedSet, focus, focus, true);
 167                 return;
 168             case SPACE:
 169                 anchor = new Point(focus);
 170                 select(selectedSet, focus, false, false);
 171                 return;
 172         }
 173         switch (btn) {
 174             case HOME:
 175                 focus.y = 0;
 176                 anchor = new Point(focus);
 177                 break;
 178             case END:
 179                 focus.y = rows - 1;
 180                 anchor = new Point(focus);
 181                 break;
 182             case UP:
 183                 if (focus.y > 0) {
 184                     focus.y--;
 185                 }
 186                 anchor = new Point(focus);
 187                 break;
 188             case DOWN:
 189                 if (focus.y < rows - 1) {
 190                     focus.y++;
 191                 }
 192                 anchor = new Point(focus);
 193                 break;
 194             case LEFT:
 195                 if (singleCell && (focus.x > 0)) {
 196                     focus.x--;
 197                 }
 198                 anchor = new Point(focus);
 199                 break;
 200             case RIGHT:
 201                 if (singleCell && (focus.x < columns - 1)) {
 202                     focus.x++;
 203                 }
 204                 anchor = new Point(focus);
 205                 break;
 206             default:
 207                 throw new UnsupportedOperationException(this.getClass().getSimpleName() + ": unexpected button is pressed");
 208         }
 209         select(selectedSet, focus, true, false);
 210     }
 211 
 212     public void ctrl(KeyboardButtons btn) {
 213         switch (btn) {
 214             case A:
 215                 select(selectedSet, new Point(0, 0), new Point(columns - 1, rows - 1), true);
 216                 //focus.y = rows - 1;//We dont change focus, it stays at the same place.
 217                 ctrlA = true;
 218                 break;
 219             case HOME:
 220                 focus.y = 0;
 221                 break;
 222             case END:
 223                 focus.y = rows - 1;
 224                 break;
 225             case PAGE_DOWN:
 226                 checkPageHeight();
 227                 moveFocusOnePageDown();
 228                 break;
 229             case PAGE_UP:
 230                 checkPageHeight();
 231                 moveFocusOnePageUp();
 232                 break;
 233             case SPACE:
 234                 anchor = new Point(focus);
 235                 select(selectedSet, focus, false, true);
 236                 return;
 237             case UP:
 238                 if (focus.y > 0) {
 239                     focus.y--;
 240                 }
 241                 break;
 242             case DOWN:
 243                 if (focus.y < rows - 1) {
 244                     focus.y++;
 245                 }
 246                 break;
 247             case LEFT:
 248                 if (focus.x > 0) {
 249                     focus.x--;
 250                 }
 251                 break;
 252             case RIGHT:
 253                 if (focus.x < columns - 1) {
 254                     focus.x++;
 255                 }
 256                 break;
 257         }
 258     }
 259 
 260     //See also RT-34619
 261     public void shift(KeyboardButtons btn) {
 262         if (anchor == null) {
 263             anchor = new Point(focus);
 264         }
 265 
 266         switch (btn) {
 267             case HOME:
 268                 focus.y = 0;
 269                 select(selectedSet, anchor, focus, true);
 270                 break;
 271             case END:
 272                 focus.y = rows - 1;
 273                 select(selectedSet, anchor, focus, true);
 274                 break;
 275             case UP:
 276                 if (focus.y > 0) {
 277                     focus.y--;
 278                     if (multiple) {
 279                         select(selectedSet, anchor, focus, true);
 280                     } else {
 281                         select(selectedSet, focus, focus, true);
 282                     }
 283                 }
 284                 break;
 285             case DOWN:
 286                 if (focus.y < rows - 1) {
 287                     focus.y++;
 288                     if (multiple) {
 289                         select(selectedSet, anchor, focus, true);
 290                     } else {
 291                         select(selectedSet, focus, focus, true);
 292                     }
 293                 }
 294                 break;
 295             case PAGE_UP:
 296                 if (!singleCell) {
 297                     checkPageHeight();
 298                     moveFocusOnePageUp();
 299                     select(selectedSet, anchor, focus, true);
 300                 }
 301                 break;
 302             case PAGE_DOWN:
 303                 if (!singleCell) {
 304                     checkPageHeight();
 305                     moveFocusOnePageDown();
 306                     select(selectedSet, anchor, focus, true);
 307                 }
 308                 break;
 309             case LEFT:
 310                 if (focus.x > 0) {
 311                     focus.x--;
 312                 }
 313                 if (multiple) {
 314                     select(selectedSet, anchor, focus, true);
 315                 } else {
 316                     select(selectedSet, focus, focus, true);
 317                 }
 318                 break;
 319             case RIGHT:
 320                 if (focus.x < columns - 1) {
 321                     focus.x++;
 322                 }
 323                 if (multiple) {
 324                     select(selectedSet, anchor, focus, true);
 325                 } else {
 326                     select(selectedSet, focus, focus, true);
 327                 }
 328                 break;
 329             case SPACE:
 330                 if (multiple) {
 331                     select(selectedSet, anchor, focus, true);
 332                 } else {
 333                     select(selectedSet, focus, focus, true);
 334                 }
 335                 break;
 336         }
 337     }
 338 
 339     public void ctrlShift(KeyboardButtons btn) {
 340         if (anchor == null) {
 341             anchor = new Point(focus);
 342         }
 343         switch (btn) {
 344             case HOME:
 345                 focus.y = 0;
 346                 if (multiple) {
 347                     select(selectedSet, anchor, focus, false);
 348                 } else {
 349                     select(selectedSet, focus, focus, true);
 350                 }
 351                 break;
 352             case END:
 353                 focus.y = rows - 1;
 354                 if (multiple) {
 355                     select(selectedSet, anchor, focus, false);
 356                 } else {
 357                     select(selectedSet, focus, focus, true);
 358                 }
 359                 break;
 360             case UP:
 361                 if (focus.y > 0) {
 362                     focus.y--;
 363                     if (multiple) {
 364                         select(selectedSet, anchor, focus, false);
 365                     } else {
 366                         select(selectedSet, focus, focus, true);
 367                     }
 368                 }
 369                 break;
 370             case DOWN:
 371                 if (focus.y < rows - 1) {
 372                     focus.y++;
 373                     if (multiple) {
 374                         select(selectedSet, anchor, focus, false);
 375                     } else {
 376                         select(selectedSet, focus, focus, true);
 377                     }
 378                 }
 379                 break;
 380             case PAGE_UP:
 381                 if (focus.y > 0) {
 382                     moveFocusOnePageUp();
 383                     if (multiple) {
 384                         select(selectedSet, anchor, focus, false);
 385                     } else {
 386                         select(selectedSet, focus, focus, true);
 387                     }
 388                 }
 389                 break;
 390             case PAGE_DOWN:
 391                 if (focus.y < rows - 1) {
 392                     moveFocusOnePageDown();
 393                     if (multiple) {
 394                         select(selectedSet, anchor, focus, false);
 395                     } else {
 396                         select(selectedSet, focus, focus, true);
 397                     }
 398                 }
 399                 break;
 400             case LEFT:
 401                 if (focus.x > 0) {
 402                     focus.x--;
 403                     if (multiple) {
 404                         select(selectedSet, anchor, focus, false);
 405                     } else {
 406                         select(selectedSet, focus, focus, true);
 407                     }
 408                 }
 409                 break;
 410             case RIGHT:
 411                 if (focus.x < columns - 1) {
 412                     focus.x++;
 413                     if (multiple) {
 414                         select(selectedSet, anchor, focus, false);
 415                     } else {
 416                         select(selectedSet, focus, focus, true);
 417                     }
 418                 }
 419                 break;
 420             case SPACE:
 421                 if (multiple) {
 422                     select(selectedSet, anchor, focus, false);
 423                 } else {
 424                     select(selectedSet, focus, focus, true);
 425                 }
 426                 break;
 427         }
 428     }
 429 
 430     public void metaCtrl(KeyboardButtons btn) {
 431         if (anchor == null) {
 432             anchor = new Point(focus);
 433         }
 434         switch (btn) {
 435             case SPACE:
 436                 anchor = new Point(focus);
 437                 select(selectedSet, focus, false, true);
 438                 return;
 439             default:
 440                 throw new UnsupportedOperationException("Unsupported : Meta + Ctrl + " + btn);
 441         }
 442     }
 443 
 444     public void click(int column, int row, KeyboardButtons modifier) {
 445         if (modifier != null) {
 446             switch (modifier) {
 447                 case CONTROL:
 448                 case META:
 449                     ctrlClick(column, row);
 450                     return;
 451                 case SHIFT:
 452                     shiftClick(column, row);
 453                     return;
 454             }
 455         }
 456         click(column, row);
 457     }
 458 
 459     public void click(int column, int row) {
 460         if (!singleCell) {
 461             column = -1;
 462         }
 463         focus = new Point(column, row);
 464         anchor = new Point(focus);
 465         selectedSet.clear();
 466         selectedSet.add(new Point(column, row));
 467     }
 468 
 469     protected void shiftClick(int column, int row) {
 470         if (multiple) {
 471             if (anchor == null) {
 472                 anchor = new Point(focus);
 473             }
 474             if (!singleCell) {
 475                 column = -1;
 476             }
 477             focus.y = row;
 478             focus.x = column;
 479             select(selectedSet, anchor, focus, true);
 480         } else {
 481             click(column, row);
 482         }
 483     }
 484 
 485     protected void ctrlClick(int column, int row) {
 486         //ctrl creates anchor at (column,row).
 487         if (multiple) {
 488             if (!singleCell) {
 489                 column = -1;
 490             }
 491             focus.y = row;
 492             focus.x = column;
 493             anchor = new Point(focus);
 494             select(selectedSet, focus, false, true);
 495         } else {
 496             if (!singleCell) {
 497                 column = -1;
 498             }
 499             focus = new Point(column, row);
 500             anchor = new Point(focus);
 501             //See RT-34649
 502             if (selectedSet.contains(focus)) {
 503                 selectedSet.clear();
 504             } else {
 505                 selectedSet.clear();
 506                 selectedSet.add(new Point(column, row));
 507             }
 508         }
 509     }
 510 
 511     protected void select(Collection<Point> set, Point p, boolean clear, boolean invert) {
 512         if (clear) {
 513             set.clear();
 514         }
 515         if (invert && selectedSet.contains(p)) {
 516             selectedSet.remove(p);
 517         } else {
 518             if (!selectedSet.contains(p)) {
 519                 selectedSet.add(new Point(p));
 520             }
 521         }
 522     }
 523 
 524     protected void select(Collection<Point> set, Point p1, Point p2, boolean clear) {
 525         if (clear) {
 526             set.clear();
 527         }
 528         select(set, p1, p2);
 529     }
 530 
 531     protected void select(Collection<Point> set, Point p1, Point p2) {
 532         if (singleCell) {
 533             for (int j = Math.min(p1.x, p2.x); j <= Math.max(p1.x, p2.x); j++) {
 534                 for (int i = Math.min(p1.y, p2.y); i <= Math.max(p1.y, p2.y); i++) {
 535                     set.add(new Point(j, i));
 536                 }
 537             }
 538         } else {
 539             for (int i = Math.min(p1.y, p2.y); i <= Math.max(p1.y, p2.y); i++) {
 540                 set.add(new Point(-1, i));
 541             }
 542         }
 543     }
 544 
 545     protected void checkPageHeight() {
 546         if (pageHeight < 0 || bottomVisible < 0 || topVisible < 0) {
 547             throw new IndexOutOfBoundsException(this.getClass().getSimpleName() + ": incorrect page parameters are set");
 548         }
 549     }
 550 
 551     protected void checkPageWidth() {
 552         if (pageWidth < 0) {
 553             throw new IndexOutOfBoundsException(this.getClass().getSimpleName() + ": incorrect page height is set");
 554         }
 555     }
 556 
 557     private void moveFocusOnePageDown() {
 558         if (focus.y < bottomVisible) {
 559             focus.y = bottomVisible;
 560         } else {
 561             focus.y += (pageHeight - 1);
 562             if (focus.y > rows - 1) {
 563                 focus.y = rows - 1;
 564             }
 565         }
 566     }
 567 
 568     private void moveFocusOnePageUp() {
 569         if (focus.y > topVisible) {
 570             focus.y = topVisible;
 571         } else {
 572             focus.y -= (pageHeight - 1);
 573             if (focus.y < 0) {
 574                 focus.y = 0;
 575             }
 576         }
 577     }
 578 
 579     public static class Range {
 580 
 581         public final int begin;
 582         public final int end;
 583 
 584         public Range(int begin, int end) {
 585             this.begin = begin;
 586             this.end = end;
 587         }
 588 
 589         @Override
 590         public String toString() {
 591             return "Range : begin <" + begin + ">, end <" + end + ">.";
 592         }
 593     }
 594 
 595     public static class ListViewMultipleSelectionHelper extends MultipleSelectionHelper {
 596 
 597         public ListViewMultipleSelectionHelper(int columns, int rows) {
 598             super(columns, rows);
 599         }
 600 
 601         /* Due to the https://javafx-jira.kenai.com/browse/RT-34204
 602          * we don't need to remove the focused cell from selection
 603          * in single cell selection mode.
 604          */
 605         @Override
 606         protected void ctrlClick(int column, int row) {
 607             //ctrl creates anchor at (column,row).
 608             column = -1;
 609             if (multiple) {
 610                 focus.y = row;
 611                 focus.x = column;
 612                 anchor = new Point(focus);
 613                 select(selectedSet, focus, false, true);
 614             } else {
 615                 focus = new Point(column, row);
 616                 anchor = new Point(focus);
 617                 if (!selectedSet.contains(focus)) {
 618 
 619                     selectedSet.clear();
 620                     selectedSet.add(new Point(column, row));
 621                 }
 622             }
 623         }
 624     }
 625 }