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 com.sun.javafx.geom.PickRay;
  28 import com.sun.javafx.geom.Vec3d;
  29 import com.sun.javafx.scene.input.PickResultChooser;
  30 import java.util.AbstractMap;
  31 import java.util.ArrayList;
  32 import java.util.Collection;
  33 import java.util.HashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Map.Entry;
  37 import javafx.event.ActionEvent;
  38 import javafx.event.EventHandler;
  39 import javafx.geometry.Orientation;
  40 import javafx.geometry.Point2D;
  41 import javafx.scene.Node;
  42 import javafx.scene.Scene;
  43 import javafx.scene.control.*;
  44 import javafx.scene.control.test.ControlsTestBase;
  45 import javafx.scene.control.test.utils.CommonPropertiesScene;
  46 import javafx.scene.control.test.utils.ComponentsFactory.MultipleIndexFormComponent;
  47 import javafx.scene.control.test.utils.ptables.AbstractApplicationPropertiesRegystry;
  48 import javafx.scene.control.test.utils.ptables.AbstractEventsCounter;
  49 import javafx.scene.control.test.utils.ptables.AbstractPropertiesTable;
  50 import static javafx.scene.control.test.utils.ptables.AbstractPropertiesTable.BIDIR_PREFIX;
  51 import static javafx.scene.control.test.utils.ptables.AbstractPropertiesTable.UNIDIR_PREFIX;
  52 import javafx.scene.control.test.utils.ptables.AbstractPropertyController.SettingType;
  53 import javafx.scene.control.test.utils.ptables.AbstractPropertyValueListener;
  54 import static javafx.scene.control.test.utils.ptables.AbstractPropertyValueSetter.CONTROLLER_SUFFIX;
  55 import static javafx.scene.control.test.utils.ptables.AbstractPropertyValueSetter.SET_PREFIX;
  56 import javafx.scene.control.test.utils.ptables.AbstractStateCheckable.StateChangedException;
  57 import static javafx.scene.control.test.utils.ptables.NodesChoserFactory.*;
  58 import javafx.scene.control.test.utils.ptables.PropertiesTable;
  59 import static javafx.scene.control.test.utils.ptables.PropertyValueListener.LISTENER_SUFFIX;
  60 import static javafx.scene.control.test.utils.ptables.TabPaneWithControl.TAB_CONTENT_ID;
  61 import static javafx.scene.control.test.utils.ptables.TabPaneWithControl.TAB_PANE_WITH_CONTROL_ID;
  62 import static javafx.scene.control.test.utils.ptables.TextFieldEventsCounter.COUNTER_SUFFIX;
  63 import static javafx.scene.control.test.utils.ptables.ToggleBindingSwitcher.BIND_BUTTON_SUFFIX;
  64 import javafx.scene.input.ScrollEvent;
  65 import javafx.scene.input.ScrollEvent.HorizontalTextScrollUnits;
  66 import javafx.scene.input.ScrollEvent.VerticalTextScrollUnits;
  67 import javafx.scene.text.Font;
  68 import org.jemmy.Point;
  69 import org.jemmy.Rectangle;
  70 import org.jemmy.action.Action;
  71 import org.jemmy.action.GetAction;
  72 import org.jemmy.control.Wrap;
  73 import org.jemmy.env.Timeout;
  74 import org.jemmy.fx.ByID;
  75 import org.jemmy.fx.NodeWrap;
  76 import org.jemmy.fx.Root;
  77 import org.jemmy.fx.control.TabPaneDock;
  78 import static org.jemmy.fx.control.TextControlWrap.SELECTED_PROP_NAME;
  79 import org.jemmy.input.AbstractScroll;
  80 import org.jemmy.interfaces.Keyboard.KeyboardModifiers;
  81 import org.jemmy.interfaces.Parent;
  82 import org.jemmy.interfaces.Selectable;
  83 import org.jemmy.interfaces.Text;
  84 import org.jemmy.lookup.LookupCriteria;
  85 import org.jemmy.timing.State;
  86 import org.jemmy.timing.Waiter;
  87 import static org.junit.Assert.*;
  88 import test.javaclient.shared.TestUtil;
  89 import test.javaclient.shared.Utils;
  90 
  91 import com.sun.javafx.scene.NodeHelper;
  92 
  93 /**
  94  * @author Alexander Kirov
  95  *
  96  * Used for many my tests. Don't touch it until you are sure, what you do.
  97  */
  98 public class UtilTestFunctions extends ControlsTestBase {
  99 
 100     protected static Parent<Node> parent;
 101     protected static KeyboardModifiers CTRL_DOWN_MASK_OS;
 102     protected static ChangingController defaultController;
 103     protected static final int ITERATIONS = 3;
 104     protected static final int SLEEP = 300;
 105     protected static double ASSERT_CMP_PRECISION = 3.5;
 106     protected static SettingOption currentSettingOption = SettingOption.PROGRAM;
 107     private static Parent<Node> propertiesTableScrollPane = null;
 108     private static final boolean useCaching = true;
 109     private static String currentTabName = AbstractApplicationPropertiesRegystry.DEFAULT_DOMAIN_NAME;
 110     private static final String CUSTOM_STYLE_CONST = "imagescomparator/custom_font.css";
 111     private String rememberedStylesheet;
 112 
 113     static {
 114         if (Utils.isMacOS()) {
 115             CTRL_DOWN_MASK_OS = KeyboardModifiers.META_DOWN_MASK;
 116         } else {
 117             CTRL_DOWN_MASK_OS = KeyboardModifiers.CTRL_DOWN_MASK;
 118         }
 119     }
 120 
 121     protected void initChangingController(Parent<Node> parent) {
 122         defaultController = new ChangingController(parent);
 123     }
 124 
 125     /**
 126      * Clicks toggle button with according id.
 127      *
 128      * @param toggleButtonId
 129      */
 130     protected static void clickToggleButton(String toggleButtonId) {
 131         findToggleButton(toggleButtonId).mouse().click();
 132     }
 133 
 134     /**
 135      * Checks, whether the toggle button has a toggled state as asked.
 136      *
 137      * @param toggleButtonId
 138      * @param selectedExpected
 139      */
 140     protected static void checkToggleButtonSelection(String toggleButtonId, boolean selectedExpected) {
 141         findToggleButton(toggleButtonId).waitProperty(SELECTED_PROP_NAME, selectedExpected);
 142     }
 143 
 144     /**
 145      * Change toggle button's state to value, with set id.
 146      *
 147      * @param buttonId
 148      */
 149     protected static void setToggleButtonSelectedStateForTestPurpose(final String buttonId, final Boolean newValue) {
 150         final Wrap<? extends ToggleButton> btnWrap = findToggleButton(buttonId);
 151         Boolean curValue = new GetAction<Boolean>() {
 152             @Override
 153             public void run(Object... os) throws Exception {
 154                 setResult(btnWrap.getControl().isSelected());
 155             }
 156         }.dispatch(Root.ROOT.getEnvironment());
 157 
 158         switch (currentSettingOption) {
 159             case MANUAL:
 160                 if (curValue != newValue) {
 161                     clickToggleButtonByID(buttonId);
 162                 }
 163                 break;
 164             case PROGRAM:
 165                 if (curValue != newValue) {
 166                     new GetAction() {
 167                         @Override
 168                         public void run(Object... os) throws Exception {
 169                             findToggleButton(buttonId).getControl().setSelected(newValue);
 170                         }
 171                     }.dispatch(Root.ROOT.getEnvironment());
 172                 }
 173                 break;
 174         }
 175     }
 176 
 177     /**
 178      * Clicks button with set id.
 179      *
 180      * @param buttonId
 181      */
 182     protected static void clickButtonForTestPurpose(final String buttonId) {
 183         switch (currentSettingOption) {
 184             case MANUAL:
 185                 clickButtonByID(buttonId);
 186                 break;
 187             case PROGRAM:
 188                 new GetAction() {
 189                     @Override
 190                     public void run(Object... os) throws Exception {
 191                         Button btn = findButton(buttonId).getControl();
 192                         EventHandler<ActionEvent> onAction = btn.getOnAction();
 193                         if (null != onAction) {
 194                             onAction.handle(null);
 195                         } else {
 196                             System.err.format("onAction event handler is not set for [%s]%n", buttonId);
 197                             btn.fire();
 198                         }
 199                     }
 200                 }.dispatch(Root.ROOT.getEnvironment());
 201                 break;
 202             default:
 203                 throw new IllegalStateException("Unknown option.");
 204         }
 205     }
 206 
 207     protected static void clickToggleButtonByID(String buttonId) {
 208         findToggleButton(buttonId).mouse().click();
 209     }
 210 
 211     protected static void clickButtonByID(String buttonId) {
 212         findButton(buttonId).mouse().click();
 213     }
 214 
 215     /**
 216      * Checks focused state of control.
 217      *
 218      * @param nodeId
 219      * @param isFocused
 220      */
 221     protected static void checkFocus(String nodeId, boolean isFocused) {
 222         findControl(nodeId).waitProperty("isFocused", isFocused);
 223     }
 224 
 225     // Two functions clicking arrows of the scrollBar.
 226     // This function make click over scrollBar (left or up arrow)
 227     protected static void clickLess(Wrap<? extends ScrollBar> wrap) {
 228         wrap.mouse().click(1, new Point(5, 5));
 229     }
 230 
 231     // This function make click over scrollBar (right or down arrow)
 232     protected static void clickMore(Wrap<? extends ScrollBar> wrap) {
 233         wrap.mouse().click(1, new Point(wrap.getScreenBounds().width - 5, wrap.getScreenBounds().height - 5));
 234     }
 235 
 236     /**
 237      * *******************************************************************
 238      *
 239      * SECTION OF MANUAL CONTROLING
 240      *
 241      ********************************************************************
 242      */
 243     //               DIRECT PROPERTIES CONTROL
 244     protected void requestFocusOnControl(final Wrap<? extends Node> control) {
 245         new GetAction() {
 246             @Override
 247             public void run(Object... os) throws Exception {
 248                 control.getControl().requestFocus();
 249             }
 250         }.dispatch(Root.ROOT.getEnvironment());
 251     }
 252 
 253     protected static void setSliderPosition(String id, final double toPosition, SettingOption option) throws InterruptedException {
 254         Thread.sleep(SLEEP);
 255         setSliderPosition(findSlider(id), toPosition, option);
 256     }
 257 
 258     protected static void setSliderPosition(final Wrap<? extends Slider> propertySliderWrap, final double toPosition, SettingOption option) throws InterruptedException {
 259         switch (option) {
 260             case MANUAL:
 261                 AbstractScroll abstrScroll = propertySliderWrap.as(AbstractScroll.class);
 262                 abstrScroll.allowError(ASSERT_CMP_PRECISION / 4);
 263                 abstrScroll.caret().to(toPosition);
 264                 break;
 265             case PROGRAM:
 266                 new GetAction<Point2D>() {
 267                     @Override
 268                     public void run(Object... os) throws Exception {
 269                         propertySliderWrap.getControl().setValue(toPosition);
 270                     }
 271                 }.dispatch(Root.ROOT.getEnvironment());
 272                 Thread.sleep(SLEEP);
 273         }
 274     }
 275 
 276     protected static void setChoiceBoxValue(String controlID, final Object value) {
 277         switch (currentSettingOption) {
 278             case MANUAL:
 279                 findChoiceBox(controlID).as(Selectable.class).selector().select(value);
 280                 //new org.jemmy.fx.control.ChoiceBoxDock(parent, controlID).asSelectable().selector().select(value);
 281                 //parent.lookup(ChoiceBox.class, new ByID<ChoiceBox>(getPrefix(type) + propType.toString().toUpperCase() + CHOICE_BOX_SUFFIX)).wrap().as(Selectable.class).selector().select(propValue.name());
 282                 break;
 283             case PROGRAM:
 284                 final Object actualValue = getChoiceBoxState(controlID);
 285                 if (!value.equals(actualValue)) {
 286                     final ChoiceBox cb = findChoiceBox(controlID).getControl();
 287                     new GetAction() {
 288                         @Override
 289                         public void run(Object... os) throws Exception {
 290                             cb.setValue(value);
 291                         }
 292                     }.dispatch(Root.ROOT.getEnvironment());
 293                 }
 294                 break;
 295         }
 296     }
 297 
 298     protected static void changePropertyControlBindingToggleButtonState(SettingType type, Enum propType) {
 299         changeToggleButtonState(getPrefix(type) + propType.toString().toUpperCase() + BIND_BUTTON_SUFFIX);
 300     }
 301 
 302     protected static void changeToggleButtonState(String controlID) {
 303         switch (currentSettingOption) {
 304             case MANUAL:
 305                 findToggleButton(controlID).mouse().click();
 306                 break;
 307             case PROGRAM:
 308                 final boolean selected = getToggleButtonState(controlID);
 309                 final ToggleButton tb = findToggleButton(controlID).getControl();
 310                 new GetAction() {
 311                     @Override
 312                     public void run(Object... os) throws Exception {
 313                         tb.setSelected(!selected);
 314                     }
 315                 }.dispatch(Root.ROOT.getEnvironment());
 316                 break;
 317         }
 318     }
 319 
 320     protected static void changeTextFieldText(String controlID, final String newText) {
 321         switch (currentSettingOption) {
 322             case MANUAL:
 323                 Text text = findTextField(controlID).as(Text.class);
 324                 text.clear();
 325                 text.type(newText);
 326                 break;
 327             case PROGRAM:
 328                 final TextField tf = findTextField(controlID).getControl();
 329                 new GetAction() {
 330                     @Override
 331                     public void run(Object... os) throws Exception {
 332                         tf.setText(newText);
 333                     }
 334                 }.dispatch(Root.ROOT.getEnvironment());
 335                 break;
 336         }
 337     }
 338 
 339     protected static void switchToPropertiesTab(final String tabName) {
 340         if (useCaching) {
 341             Cache.clear();
 342         }
 343         final Wrap<? extends TabPane> tabPane = parent.lookup(TabPane.class, new ByID<TabPane>(TAB_PANE_WITH_CONTROL_ID)).wrap();
 344         switch (currentSettingOption) {
 345             case MANUAL:
 346                 Tab tab = new GetAction<Tab>() {
 347                     @Override
 348                     public void run(Object... os) throws Exception {
 349                         for (Tab tab : tabPane.getControl().getTabs()) {
 350                             if (tab.getText().equals(tabName)) {
 351                                 setResult(tab);
 352                             }
 353                         }
 354                     }
 355                 }.dispatch(Root.ROOT.getEnvironment());
 356                 TabPaneDock dock = new TabPaneDock(tabPane);
 357                 dock.asSelectable().selector().select(tab);
 358                 break;
 359             case PROGRAM:
 360                 new GetAction() {
 361                     @Override
 362                     public void run(Object... os) throws Exception {
 363                         Tab tabToSelect = null;
 364                         for (Tab tab : tabPane.getControl().getTabs()) {
 365                             if (tab.getText().equals(tabName)) {
 366                                 tabToSelect = tab;
 367                             }
 368                         }
 369                         if (tabToSelect == null) {
 370                             throw new Exception("Tab with name <" + tabName + "> could not be found");
 371                         } else {
 372                             tabPane.getControl().getSelectionModel().select(tabToSelect);
 373                         }
 374                     }
 375                 }.dispatch(Root.ROOT.getEnvironment());
 376                 break;
 377         }
 378 
 379         //Wait, until content of according Tab (root item of tab is scrollPane,
 380         //with preset Id), will be findable on the scene.
 381         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 382             public Object reached() {
 383                 if (parent.lookup(ScrollPane.class, new ByID<ScrollPane>(tabName + TAB_CONTENT_ID)).size() == 1) {
 384                     return true;
 385                 } else {
 386                     return null;
 387                 }
 388             }
 389         });
 390 
 391         propertiesTableScrollPane = parent.lookup(ScrollPane.class, new ByID<ScrollPane>(tabName + javafx.scene.control.test.utils.ptables.TabPaneWithControl.TAB_CONTENT_ID)).wrap().as(Parent.class, Node.class);
 392         currentTabName = tabName;
 393     }
 394 
 395     //              WORKING WITH LISTENING TEXT FIELDS
 396     protected static void checkText(String textFieldId, String expectedText) {
 397         findTextField(textFieldId).waitProperty(Wrap.TEXT_PROP_NAME, expectedText);
 398     }
 399 
 400     protected static void checkTextFieldText(Enum propType, String expectedText) {
 401         findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX).waitProperty(Wrap.TEXT_PROP_NAME, expectedText);
 402         if (defaultController != null) {
 403             defaultController.removeProperty(currentTabName, propType);
 404         }
 405     }
 406 
 407     protected static void checkTextFieldTextContaining(final Enum propType, final String expectedContainedText) {
 408         final TextField tf = findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX).getControl();
 409         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 410             public Object reached() {
 411                 if (tf.getText().contains(expectedContainedText)) {
 412                     return true;
 413                 } else {
 414                     return null;
 415                 }
 416             }
 417         });
 418 
 419         if (defaultController != null) {
 420             defaultController.removeProperty(currentTabName, propType);
 421         }
 422     }
 423 
 424     protected static void checkTextFieldValue(Enum propType, final double expectedValue) {
 425         final Wrap<? extends TextField> tfwrap = findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX);
 426         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 427             public Object reached() {
 428                 if (Math.abs(Double.parseDouble(tfwrap.getControl().getText()) - expectedValue) < ASSERT_CMP_PRECISION) {
 429                     return true;
 430                 } else {
 431                     return null;
 432                 }
 433             }
 434         });
 435 
 436         if (defaultController != null) {
 437             defaultController.removeProperty(currentTabName, propType);
 438         }
 439     }
 440 
 441     protected static void checkTextFieldValue(Enum propType, final int expectedValue, final double COMPARISON_DELTA) {
 442         final Wrap<? extends TextField> tfwrap = findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX);
 443         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 444             double actual;
 445 
 446             public Object reached() {
 447                 actual = Double.parseDouble(tfwrap.getControl().getText());
 448                 if (Math.abs(actual - expectedValue) <= COMPARISON_DELTA) {
 449                     return true;
 450                 } else {
 451                     return null;
 452                 }
 453             }
 454 
 455             @Override
 456             public String toString() {
 457                 return String.format("[Expected:%d, got:%.6f, delta:%.6f]",
 458                         expectedValue, actual, COMPARISON_DELTA);
 459             }
 460         });
 461 
 462         if (defaultController != null) {
 463             defaultController.removeProperty(currentTabName, propType);
 464         }
 465     }
 466 
 467     protected static void checkTextFieldValue(Enum propType, int expectedValue) {//more strict for int values.
 468         checkTextFieldValue(propType, expectedValue, ASSERT_CMP_PRECISION);
 469     }
 470 
 471     protected static void checkSimpleListenerValue(Enum propType, final int expectedValue) {
 472         final Wrap<? extends TextField> tfwrap = findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX);
 473         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 474             public Object reached() {
 475                 if (Math.abs(Double.parseDouble(tfwrap.getControl().getText()) - expectedValue) == 0) {
 476                     return true;
 477                 } else {
 478                     System.err.println("Diff : tfwrap.getControl().getText() = " + tfwrap.getControl().getText() + "; expectedValue = " + expectedValue);
 479                     return null;
 480                 }
 481             }
 482         });
 483 
 484         if (defaultController != null) {
 485             defaultController.removeProperty(currentTabName, propType);
 486         }
 487     }
 488 
 489     protected static void checkFontSize(Enum property, Double expectedSize) {
 490         final Wrap<? extends TextField> tfwrap = findTextField(property.toString().toUpperCase() + LISTENER_SUFFIX);
 491         String fontDescription = new GetAction<String>() {
 492             @Override
 493             public void run(Object... os) throws Exception {
 494                 setResult(tfwrap.getControl().getText());
 495             }
 496         }.dispatch(Root.ROOT.getEnvironment());
 497 
 498         String fontSize = fontDescription.substring(fontDescription.indexOf("size=") + "size=".length(), fontDescription.indexOf(']', fontDescription.indexOf("size=")));
 499         assertEquals(Double.parseDouble(fontSize), expectedSize, 0.5);
 500     }
 501 
 502     protected static void checkSimpleListenerValue(Enum propType, String expectedText) {
 503         findTextField(propType.toString().toUpperCase() + LISTENER_SUFFIX).waitProperty(Wrap.TEXT_PROP_NAME, expectedText);
 504     }
 505 
 506     protected static void checkCounterValue(Enum propType, int expectedValue) {
 507         checkCounterValue(propType.toString().toUpperCase(), expectedValue);
 508     }
 509 
 510     protected static void checkCounterValue(String counterName, final int expectedValue) {
 511         final Wrap<? extends TextField> tfwrap = findTextField(counterName.toUpperCase() + COUNTER_SUFFIX);
 512         new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 5000)).ensureState(new State() {
 513             public Object reached() {
 514                 if (Math.abs(Double.parseDouble(tfwrap.getControl().getText()) - expectedValue) == 0) {
 515                     return true;
 516                 } else {
 517                     return null;
 518                 }
 519             }
 520 
 521             @Override
 522             public String toString() {
 523                 return String.format("Expected value: %d, actual: %f.2", expectedValue, Double.parseDouble(tfwrap.getControl().getText()));
 524             }
 525         });
 526 
 527         if (defaultController != null) {
 528             defaultController.removeCounter(currentTabName, counterName);
 529         }
 530     }
 531 
 532     //                            SEARCHERS
 533     protected static Wrap<? extends ScrollBar> findScrollBar(final Parent<Node> parentWrap, final Orientation orientation, final Boolean visible) {
 534         final LookupCriteria<ScrollBar> lc = new LookupCriteria<ScrollBar>() {
 535             public boolean check(ScrollBar cntrl) {
 536                 return ((cntrl.isVisible() == visible) && (cntrl.getOrientation() == orientation));
 537             }
 538         };
 539 
 540         try {
 541             new Waiter(new Timeout("", TestUtil.isEmbedded() ? 20000 : 2000)).ensureState(new State() {
 542                 public Object reached() {
 543                     if (parentWrap.lookup(ScrollBar.class, lc).size() > 0) {
 544                         return true;
 545                     } else {
 546                         return null;
 547                     }
 548                 }
 549             });
 550         } catch (Exception ex) {
 551             return null;
 552         }
 553 
 554         return parentWrap.lookup(ScrollBar.class, lc).wrap();
 555     }
 556 
 557     protected static Wrap<? extends ScrollBar> findScrollBar(Parent<Node> parentWrap, Orientation orientation) {
 558         return findScrollBar(parentWrap, orientation, true);
 559     }
 560 
 561     protected static Wrap<? extends ScrollBar> findScrollBar(Parent<Node> parentWrap) {
 562         Wrap<? extends ScrollBar> sb1, sb2;
 563         sb1 = findScrollBar(parentWrap, Orientation.VERTICAL);
 564         sb2 = findScrollBar(parentWrap, Orientation.HORIZONTAL);
 565         if (sb1 != null) {
 566             return sb1;
 567         } else {
 568             return sb2;
 569         }
 570     }
 571 
 572     //                                CONTROL
 573     protected static void setPropertyBySlider(SettingType type, Enum propType, double toPosition) throws InterruptedException {
 574         if (type == SettingType.SETTER) {
 575             switchOffBinding(SettingType.UNIDIRECTIONAL, propType);
 576             setSliderPosition(getPrefix(SettingType.UNIDIRECTIONAL) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, toPosition, currentSettingOption);
 577             clickButtonForTestPurpose(SET_PREFIX + propType.toString().toUpperCase());
 578         } else {
 579             switchOnBinding(type, propType);
 580             setSliderPosition(getPrefix(type) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, toPosition, currentSettingOption);
 581         }
 582 
 583         if (defaultController != null) {
 584             defaultController.removeProperty(currentTabName, propType);
 585         }
 586     }
 587 
 588     protected static void setPropertyByToggleClick(SettingType type, Enum propType) {
 589         if (type == SettingType.SETTER) {
 590             switchOffBinding(SettingType.UNIDIRECTIONAL, propType);
 591             changeToggleButtonState(getPrefix(SettingType.UNIDIRECTIONAL) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX);
 592             clickButtonForTestPurpose(SET_PREFIX + propType.toString().toUpperCase());
 593         } else {
 594             switchOnBinding(type, propType);
 595             changeToggleButtonState(getPrefix(type) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX);
 596         }
 597 
 598         if (defaultController != null) {
 599             defaultController.removeProperty(currentTabName, propType);
 600         }
 601     }
 602 
 603     protected static void setPropertyByToggleClick(SettingType type, Enum propType, Boolean newVal) {
 604 
 605         if (type == SettingType.SETTER) {
 606             String btnID = getPrefix(SettingType.UNIDIRECTIONAL) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX;
 607             final Wrap<? extends ToggleButton> btnWrap = findToggleButton(btnID);
 608 
 609             Boolean curVal = new GetAction<Boolean>() {
 610                 @Override
 611                 public void run(Object... os) throws Exception {
 612                     setResult(btnWrap.getControl().isSelected());
 613                 }
 614             }.dispatch(Root.ROOT.getEnvironment());
 615 
 616             switchOffBinding(SettingType.UNIDIRECTIONAL, propType);
 617             if (curVal != newVal) {
 618                 setToggleButtonSelectedStateForTestPurpose(btnID, newVal);
 619             }
 620             clickButtonForTestPurpose(SET_PREFIX + propType.toString().toUpperCase());
 621         } else {
 622             String btnID = getPrefix(type) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX;
 623             setToggleButtonSelectedStateForTestPurpose(btnID, newVal);
 624             switchOnBinding(type, propType);
 625         }
 626 
 627         if (defaultController != null) {
 628             defaultController.removeProperty(currentTabName, propType);
 629         }
 630     }
 631 
 632     protected static void setPropertyByChoiceBox(SettingType type, Object propValue, Enum propType) {
 633         if (type == SettingType.SETTER) {
 634             switchOffBinding(SettingType.UNIDIRECTIONAL, propType);
 635             setChoiceBoxValue(getPrefix(SettingType.UNIDIRECTIONAL) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, propValue);
 636             clickButtonForTestPurpose(SET_PREFIX + propType.toString().toUpperCase());
 637         } else {
 638             switchOnBinding(type, propType);
 639             setChoiceBoxValue(getPrefix(type) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, propValue);
 640         }
 641 
 642         if (defaultController != null) {
 643             defaultController.removeProperty(currentTabName, propType);
 644         }
 645     }
 646 
 647     protected static void setPropertyByTextField(SettingType type, Enum propType, String value) {
 648         if (type == SettingType.SETTER) {
 649             switchOffBinding(SettingType.UNIDIRECTIONAL, propType);
 650             changeTextFieldText(getPrefix(SettingType.UNIDIRECTIONAL) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, value);
 651             clickButtonForTestPurpose(SET_PREFIX + propType.toString().toUpperCase());
 652         } else {
 653             switchOnBinding(type, propType);
 654             changeTextFieldText(getPrefix(type) + propType.toString().toUpperCase() + CONTROLLER_SUFFIX, value);
 655         }
 656 
 657         if (defaultController != null) {
 658             defaultController.removeProperty(currentTabName, propType);
 659         }
 660     }
 661 
 662     protected void selectControlFromFactory(final int index) {
 663         final Wrap<? extends ChoiceBox> choice = (Wrap<? extends ChoiceBox>) findControl(NODE_CHOSER_CHOICE_BOX_ID);
 664         new GetAction() {
 665             @Override
 666             public void run(Object... os) throws Exception {
 667                 choice.getControl().getSelectionModel().select(index);
 668             }
 669         }.dispatch(Root.ROOT.getEnvironment());
 670     }
 671 
 672     protected void selectControlFromFactory(final Class controlClass) {
 673         final Wrap<? extends ChoiceBox> choice = (Wrap<? extends ChoiceBox>) findControl(NODE_CHOSER_CHOICE_BOX_ID);
 674         new GetAction() {
 675             @Override
 676             public void run(Object... os) throws Exception {
 677                 for (Object item : choice.getControl().getItems()) {
 678                     if (item.getClass() == controlClass) {
 679                         choice.getControl().getSelectionModel().select(item);
 680                     }
 681                 }
 682 
 683             }
 684         }.dispatch(Root.ROOT.getEnvironment());
 685     }
 686 
 687     protected int controlsFactorySize() {
 688         final Wrap<? extends ChoiceBox> choice = (Wrap<? extends ChoiceBox>) findControl(NODE_CHOSER_CHOICE_BOX_ID);
 689         return new GetAction<Integer>() {
 690             @Override
 691             public void run(Object... os) throws Exception {
 692                 setResult(choice.getControl().getItems().size());
 693             }
 694         }.dispatch(Root.ROOT.getEnvironment());
 695     }
 696 
 697     /*
 698      * This is complex method. It should be able to select different fonts, and
 699      * different objects at all. So it will be executed always in a way, like
 700      * "program" (not manual) execution done.
 701      */
 702     protected static void selectObjectFromChoiceBox(SettingType settingType, Enum property, Object whichToChose) {
 703         Wrap<? extends ChoiceBox> choice;
 704         if (settingType == SettingType.SETTER) {
 705             switchOffBinding(SettingType.UNIDIRECTIONAL, property);
 706             choice = findChoiceBox(getPrefix(SettingType.UNIDIRECTIONAL) + property.toString().toUpperCase() + CONTROLLER_SUFFIX);
 707         } else {
 708             choice = findChoiceBox(getPrefix(settingType) + property.toString().toUpperCase() + CONTROLLER_SUFFIX);
 709         }
 710 
 711         selectObjectFromChoiceBox(choice, whichToChose);
 712 
 713         if (settingType.equals(SettingType.SETTER)) {
 714             clickButtonForTestPurpose(SET_PREFIX + property.toString().toUpperCase());
 715         } else {
 716             switchOnBinding(settingType, property);
 717         }
 718 
 719         if (defaultController != null) {
 720             defaultController.removeProperty(currentTabName, property);
 721         }
 722     }
 723 
 724     protected static void selectObjectFromChoiceBox(Wrap<? extends ChoiceBox> choiceBox, final Object whatToChose) {
 725         switch (currentSettingOption) {
 726             case MANUAL:
 727                 choiceBox.as(Selectable.class).selector().select(whatToChose);
 728                 break;
 729             case PROGRAM:
 730                 new GetAction<Object>() {
 731                     @Override
 732                     public void run(Object... os) throws Exception {
 733                         ChoiceBox choiceBox = (ChoiceBox) os[0];
 734                         Collection allObjects = choiceBox.getItems();
 735                         Object actualToChose = null;
 736 
 737                         if (whatToChose instanceof Font) {
 738 
 739                             for (Object font : allObjects) {
 740                                 //Supposed - font are different in their color.
 741                                 if ((font instanceof Font) && (((Font) font).getSize() == ((Font) whatToChose).getSize())) {
 742                                     actualToChose = font;
 743                                 }
 744                             }
 745                         } else {
 746                             for (Object obj : allObjects) {
 747                                 if ((obj != null) && obj.getClass().equals(whatToChose)) {
 748                                     actualToChose = obj;
 749                                 }
 750                                 if ((obj != null) && obj.equals(whatToChose)) {
 751                                     actualToChose = obj;
 752                                 }
 753                             }
 754                             if (actualToChose == null) {
 755                                 System.out.println("Select NULL (Possibly, nothing found to select).");
 756                             }
 757                         }
 758 
 759                         ChoiceBox choice = (ChoiceBox) os[0];
 760                         if (actualToChose == null) {
 761                             choice.setValue(null);
 762                         } else {
 763                             choice.getSelectionModel().select(actualToChose);
 764                         }
 765                     }
 766                 }.dispatch(Root.ROOT.getEnvironment(), choiceBox.getControl());
 767                 break;
 768         }
 769     }
 770 
 771     /**
 772      * Send ScrollEvent in the center of the control
 773      *
 774      * @param wrap Wrap, which will receive event
 775      * @param scrollX Number of pixels to scroll by x coordinate
 776      * @param scrollY Number of pixels to scroll by y coordinate
 777      */
 778     protected static void sendScrollEvent(final Wrap<? extends Scene> scene, Wrap<? extends Node> wrap, double scrollX, double scrollY) {
 779         double x = wrap.getScreenBounds().width / 4;
 780         double y = wrap.getScreenBounds().height / 4;
 781         sendScrollEvent(scene, wrap, scrollX, scrollY, HorizontalTextScrollUnits.NONE, scrollX, VerticalTextScrollUnits.NONE, scrollY, x, y, wrap.getScreenBounds().x + x, wrap.getScreenBounds().y + y);
 782     }
 783 
 784     protected static void sendScrollEvent(final Wrap<? extends Scene> scene, final Wrap<? extends Node> wrap,
 785             double _scrollX, double _scrollY,
 786             HorizontalTextScrollUnits _scrollTextXUnits, double _scrollTextX,
 787             VerticalTextScrollUnits _scrollTextYUnits, double _scrollTextY,
 788             double _x, double _y,
 789             double _screenX, double _screenY) {
 790         //For 2.1.0 :
 791         //final ScrollEvent scrollEvent = ScrollEvent.impl_scrollEvent(_scrollX, _scrollY, _scrollTextXUnits, _scrollTextX, _scrollTextYUnits, _scrollTextY, _x, _y, _screenX, _screenY, false, false, false, false);
 792         //For 2.2.0 :
 793         //Interpretation: EventType<ScrollEvent> eventType, double _scrollX, double _scrollY, double _totalScrollX, double _totalScrollY, HorizontalTextScrollUnits _scrollTextXUnits, double _scrollTextX, VerticalTextScrollUnits _scrollTextYUnits, double _scrollTextY, int _touchPoints, double _x, double _y, double _screenX, double _screenY, boolean _shiftDown, boolean _controlDown, boolean _altDown, boolean _metaDown, boolean _direct, boolean _inertia)
 794         //For 8.0 before b64 and RT-9383
 795         //final ScrollEvent scrollEvent = new ScrollEvent.impl_scrollEvent(ScrollEvent.SCROLL, _scrollX, _scrollY, _scrollX, _scrollY, _scrollTextXUnits, _scrollTextX, _scrollTextYUnits, _scrollTextY, 0, _x, _y, _screenX, _screenY, false, false, false, false, false, false);
 796 
 797         //new ScrollEvent(EventType<ScrollEvent> eventType,
 798         //double x, double y, double screenX, double screenY,
 799         //boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown,
 800         //boolean direct, boolean inertia, double deltaX, double deltaY, double gestureDeltaX, double gestureDeltaY,
 801         //ScrollEvent.HorizontalTextScrollUnits textDeltaXUnits, double textDeltaX,
 802         //ScrollEvent.VerticalTextScrollUnits textDeltaYUnits, double textDeltaY, int touchCount)
 803         final ScrollEvent scrollEvent = new ScrollEvent(ScrollEvent.SCROLL,
 804                 _x, _y, _screenX, _screenY,
 805                 false, false, false, false,
 806                 false, false, _scrollX, _scrollY, 0, 0,
 807                 _scrollTextXUnits, _scrollTextX,
 808                 _scrollTextYUnits, _scrollTextY, 0, null /* PickResult?*/);
 809 
 810         wrap.getEnvironment().getExecutor().execute(wrap.getEnvironment(), true, new Action() {
 811             @Override
 812             public void run(Object... os) throws Exception {
 813                 Wrap<? extends Node> wrap = ((Wrap<? extends Node>) os[2]);
 814                 Point2D pointOnScene = new GetAction<Point2D>() {
 815                     @Override
 816                     public void run(Object... os) throws Exception {
 817                         setResult(((Node) os[0]).localToScene((Double) os[1], (Double) os[2]));
 818                     }
 819                 }.dispatch(Root.ROOT.getEnvironment(), wrap.getControl(), Double.valueOf(wrap.getScreenBounds().width / 4), Double.valueOf(wrap.getScreenBounds().height / 4));
 820 
 821                 // The following line requires --add-exports javafx.graphics/com.sun.javafx.scene.input=ALL-UNNAMED
 822                 final PickResultChooser result = new PickResultChooser();
 823 
 824                 // The following line requires --add-exports javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED
 825                 //public PickRay(Vec3d origin, Vec3d direction, double nearClip, double farClip) {
 826                 NodeHelper.pickNode(
 827                     (((Wrap<? extends Scene>) os[0]).getControl()).getRoot(),
 828                     new PickRay(new Vec3d(pointOnScene.getX(), pointOnScene.getY(), -10), new Vec3d(0, 0, 1), 1.0, 100),
 829                     result);
 830                 Node node = result.getIntersectedNode();
 831                 node.fireEvent(scrollEvent);
 832             }
 833         }, scene, scrollEvent, wrap);
 834     }
 835 
 836     protected static void switchOnBinding(SettingType type, Enum propType) {
 837         if (!getToggleButtonState(getPrefix(type) + propType.toString().toUpperCase() + BIND_BUTTON_SUFFIX)) {
 838             changePropertyControlBindingToggleButtonState(type, propType);
 839         }
 840     }
 841 
 842     protected static void switchOffBinding(SettingType type, Enum propType) {
 843         if (getToggleButtonState(getPrefix(type) + propType.toString().toUpperCase() + BIND_BUTTON_SUFFIX)) {
 844             changePropertyControlBindingToggleButtonState(type, propType);
 845         }
 846     }
 847 
 848     private static boolean getToggleButtonState(String controlID) {
 849         final ToggleButton tb = findToggleButton(controlID).getControl();
 850         return new GetAction<Boolean>() {
 851             @Override
 852             public void run(Object... os) throws Exception {
 853                 setResult(tb.selectedProperty().getValue());
 854             }
 855         }.dispatch(Root.ROOT.getEnvironment());
 856     }
 857 
 858     private static Object getChoiceBoxState(String controlID) {
 859         final ChoiceBox cb = findChoiceBox(controlID).getControl();
 860         return new GetAction<Object>() {
 861             @Override
 862             public void run(Object... os) throws Exception {
 863                 setResult(cb.valueProperty().getValue());
 864             }
 865         }.dispatch(Root.ROOT.getEnvironment());
 866     }
 867 
 868     private static String getPrefix(SettingType type) {
 869         if (type == SettingType.BIDIRECTIONAL) {
 870             return BIDIR_PREFIX;
 871         } else {
 872             return UNIDIR_PREFIX;
 873         }
 874     }
 875 
 876     protected static void setText(final Wrap<? extends TextField> tf, final int value) {
 877         setText(tf, String.valueOf(value));
 878     }
 879 
 880     protected static void setText(final Wrap<? extends TextField> tf, final String value) {
 881         new GetAction() {
 882             @Override
 883             public void run(Object... os) throws Exception {
 884                 tf.getControl().setText(value);
 885             }
 886         }.dispatch(Root.ROOT.getEnvironment());
 887     }
 888 
 889     protected static void setCheckBoxState(String controlId, final boolean checked) {
 890         final Wrap<? extends CheckBox> wrap = (Wrap<? extends CheckBox>) findControl(controlId);
 891         new GetAction() {
 892             @Override
 893             public void run(Object... os) throws Exception {
 894                 wrap.getControl().setSelected(checked);
 895             }
 896         }.dispatch(Root.ROOT.getEnvironment());
 897     }
 898 
 899     /*
 900      * FINDERS. Needed for ControlTabs. The problem is with TabPane. Its content
 901      * (tabs' content) is not removed from SceneGraph, but just made invisible.
 902      * So there are many controls with the same id, because they correspond to
 903      * the same properties (by name) of different controls of the same class.
 904      * So, sometimes, we need to find them in scrollPane, which is inside some
 905      * Tab and its ID corresponds to name of tab.
 906      */
 907     protected static Wrap<? extends Button> findButton(String id) {
 908         return (Wrap<? extends Button>) findControl(id);
 909     }
 910 
 911     protected static Wrap<? extends TextField> findTextField(String id) {
 912         return (Wrap<? extends TextField>) findControl(id);
 913     }
 914 
 915     protected static Wrap<? extends Slider> findSlider(String id) {
 916         return (Wrap<? extends Slider>) findControl(id);
 917     }
 918 
 919     protected static Wrap<? extends ToggleButton> findToggleButton(String id) {
 920         return (Wrap<? extends ToggleButton>) findControl(id);
 921     }
 922 
 923     protected static Wrap<? extends ChoiceBox> findChoiceBox(String id) {
 924         return (Wrap<? extends ChoiceBox>) findControl(id);
 925     }
 926 
 927     protected static Wrap<? extends CheckBox> findCheckBox(String id) {
 928         return (Wrap<? extends CheckBox>) findControl(id);
 929     }
 930 
 931     protected static Wrap<? extends Control> findControl(String id) {
 932         if (useCaching) {
 933             Wrap cached = Cache.search(id);
 934             if (cached != null) {
 935                 return cached;
 936             }
 937         }
 938         if ((propertiesTableScrollPane != null) && (propertiesTableScrollPane.lookup(Control.class, new ByID(id)).size() > 0)) {
 939             return propertiesTableScrollPane.lookup(Control.class, new ByID<Control>(id)).wrap();
 940         } else {
 941             if (parent.lookup(Control.class, new ByID(id)).size() == 1) {
 942                 Wrap found = parent.lookup(Control.class, new ByID<Control>(id)).wrap();
 943                 if (useCaching) {
 944                     if (Cache.search(id) != null) {
 945                         throw new IllegalStateException("We tried to lookup cached wrap. We shouldn't be here.");
 946                     } else {
 947                         Cache.add(id, found);
 948                     }
 949                 }
 950                 return found;
 951             } else {
 952                 if (parent.lookup(Control.class, new ByID(id)).size() > 1) {
 953                     /**
 954                      * If you are here, possibly, you didn't call
 955                      * switchToPropertiesTab() before specifying a property
 956                      * value, which appears on several tabs.
 957                      */
 958                     throw new RuntimeException("Not unique ID!!! (" + id + ").");
 959                 } else {
 960                     throw new RuntimeException("Not found!!! (" + id + ").");
 961                 }
 962             }
 963         }
 964     }
 965 
 966     protected double adjustValue(double min, double max, double value) {
 967         if (min > max) {
 968             throw new IllegalArgumentException("Min must be less then max: min=" + min + "; max=" + max);
 969         }
 970 
 971         if (value < min) {
 972             return min;
 973         }
 974 
 975         if (value > max) {
 976             return max;
 977         }
 978 
 979         return value;
 980     }
 981 
 982     protected void clearCache() {
 983         Cache.clear();
 984     }
 985 
 986     public static String convertMultiIndicesToString(int... indices) {
 987         StringBuilder builder = new StringBuilder();
 988         for (int i : indices) {
 989             builder.append(i);
 990             builder.append(MultipleIndexFormComponent.INDICES_DELIMITER);
 991         }
 992         builder.delete(builder.length() - MultipleIndexFormComponent.INDICES_DELIMITER.length(), builder.length());
 993         return builder.toString();
 994     }
 995 
 996     protected void provideSpaceForControl(int width, int height, boolean nonTestedControlsVisibility) {
 997         Wrap<? extends Scene> scene = Root.ROOT.lookup().wrap();
 998         ((CommonPropertiesScene) scene.getControl()).setNonTestedContentVisibility(nonTestedControlsVisibility);
 999         ((CommonPropertiesScene) scene.getControl()).setTestedControlContainerSize(width, height);
1000     }
1001 
1002     protected Wrap<? extends Node> getParentWrap(Wrap<? extends Node> node) {
1003         final Node control = node.getControl();
1004         return new NodeWrap(node.getEnvironment(), new GetAction<Node>(){
1005 
1006             @Override
1007             public void run(Object... os) throws Exception {
1008                 setResult(control.getParent());
1009             }
1010         }.dispatch(Root.ROOT.getEnvironment()));
1011     }
1012 
1013     /**
1014      * Checks, that rec1 rel rec2.
1015      */
1016     public static void checkRectanglesRelation(Rectangle rec1, RectanglesRelations rel, Rectangle rec2) {
1017         switch (rel) {
1018             case ABOVE:
1019                 assertTrue(rec1.y + rec1.height <= rec2.y);
1020                 break;
1021             case BELOW:
1022                 assertTrue(rec1.y >= rec2.y + rec2.height);
1023                 break;
1024             case CONTAINS:
1025                 assertTrue(rec1.contains(rec2));
1026                 break;
1027             case ISCONTAINED:
1028                 assertTrue(rec2.contains(rec1));
1029                 break;
1030             case RIGHTER:
1031                 assertTrue(rec1.x >= rec2.x + rec2.width);
1032                 break;
1033             case LEFTER:
1034                 assertTrue(rec1.x + rec1.width <= rec2.x);
1035                 break;
1036             case CENTERED_IN_HORIZONTAL:
1037                 //Centered, relative to horizontal dimension.
1038                 assertEquals(rec1.y + rec1.height / 2.0, rec2.y + rec2.height / 2.0, 1.0);
1039                 break;
1040             case CENTERED_IN_VERTICAL:
1041                 //Centered, relative to vertical dimension.
1042                 assertEquals(rec1.x + rec1.width / 2.0, rec2.x + rec2.width / 2.0, 1.0);
1043                 break;
1044             default:
1045                 throw new IllegalStateException("No case provided!");
1046         }
1047     }
1048 
1049     protected void removeStylesheet() {
1050         new GetAction() {
1051             @Override
1052             public void run(Object... os) throws Exception {
1053                 Scene scene = parent.lookup().wrap().getControl().getScene();
1054                 for (String str : scene.getStylesheets()) {
1055                     if (str.contains(CUSTOM_STYLE_CONST)) {
1056                         rememberedStylesheet = str;
1057                         scene.getStylesheets().remove(str);
1058                         return;
1059                     }
1060                 }
1061             }
1062         }.dispatch(Root.ROOT.getEnvironment());
1063     }
1064 
1065     protected void restoreStylesheet() {
1066         assertNotNull(rememberedStylesheet);
1067         new GetAction() {
1068             @Override
1069             public void run(Object... os) throws Exception {
1070                 Scene scene = parent.lookup().wrap().getControl().getScene();
1071                 scene.getStylesheets().add(rememberedStylesheet);
1072             }
1073         }.dispatch(Root.ROOT.getEnvironment());
1074         rememberedStylesheet = null;
1075     }
1076 
1077     private static class Cache {
1078 
1079         private static Map<String, Wrap> cache = new HashMap<String, Wrap>();
1080         private static Parent<Node> parentUsedForCaching = null;
1081 
1082         private static void add(String id, Wrap cachedValue) {
1083             if (parent.equals(parentUsedForCaching)) {
1084                 cache.put(id, cachedValue);
1085             } else {
1086                 cache.clear();
1087                 parentUsedForCaching = parent;
1088                 cache.put(id, cachedValue);
1089             }
1090         }
1091 
1092         private static Wrap search(String id) {
1093             if (parent.equals(parentUsedForCaching)) {
1094                 return cache.get(id);
1095             } else {
1096                 cache.clear();
1097                 parentUsedForCaching = parent;
1098                 return null;
1099             }
1100         }
1101 
1102         private static void clear() {
1103             cache.clear();
1104         }
1105     }
1106 
1107     /**
1108      * Use case:
1109      *
1110      * @Test public void testTest() throws InterruptedException {
1111      * initChangingController(parent);
1112      * defaultController.include().allTables().allProperties().allCounters().apply();
1113      * defaultController.fixCurrentState();
1114      * defaultController.provideInfoForCodeCompletion(TestBase.Properties.values(),
1115      * null);
1116      *
1117      * assertEquals((new Slider()).maxProperty().getValue(), 100,
1118      * ASSERT_CMP_PRECISION);//initial value
1119      *
1120      * setPropertyBySlider(AbstractPropertyController.SettingType.BIDIRECTIONAL,
1121      * TestBase.Properties.max, -10);
1122      * checkTextFieldValue(TestBase.Properties.value, -10);
1123      * checkTextFieldValue(TestBase.Properties.min, -10);
1124      * checkTextFieldValue(TestBase.Properties.max, -10);
1125      *
1126      * setPropertyBySlider(AbstractPropertyController.SettingType.BIDIRECTIONAL,
1127      * TestBase.Properties.max, 150); setSliderPosition(TESTED_SLIDER_ID, 130,
1128      * SettingOption.MANUAL); checkTextFieldValue(TestBase.Properties.value,
1129      * 130);
1130      *
1131      * testedControl.keyboard().pushKey(Keyboard.KeyboardButtons.END);
1132      * checkTextFieldValue(TestBase.Properties.value, 150);
1133      *
1134      * setPropertyBySlider(AbstractPropertyController.SettingType.UNIDIRECTIONAL,
1135      * TestBase.Properties.max, 30);
1136      * checkTextFieldValue(TestBase.Properties.min, -10);
1137      * checkTextFieldValue(TestBase.Properties.max, 30);
1138      * checkTextFieldValue(TestBase.Properties.value, 30);
1139      *
1140      * defaultController.check(); } Can be added into
1141      * javafx.scene.control.test.slider.SliderTest class.
1142      */
1143     protected static class ChangingController {
1144 
1145         //This variable can deactivate this class on failing tests, if occasionally it cannot be easily fixed.
1146         final private boolean FAIL_ON_ERROR_IN_NON_CHANGING_CHECKING = true;
1147         private Object[] propertiesEnum = null;
1148         private Object[] countersEnum = null;
1149         private Parent<Node> stageWithPropertiestables = null;
1150         private boolean codeCompletionInfoProvided = false;
1151         private boolean stateWasFixed = false;
1152         private boolean atLeastOneConfigDone = false;
1153         private String changingControllerVariableName;
1154         private List<Entry<AbstractPropertiesTable, String>> trackedProperties = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1155         private List<Entry<AbstractPropertiesTable, String>> trackedCounters = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1156 
1157         private ChangingController(Parent<Node> parent) {
1158             if (parent == null) {
1159                 throw new IllegalArgumentException("Stage for looking up the properties table cannot be null.");
1160             }
1161             this.stageWithPropertiestables = parent;
1162         }
1163 
1164         public void provideInfoForCodeCompletion(Object[] propertiesEnum, Object[] countersEnum) {
1165             this.changingControllerVariableName = "defaultController";
1166 //            if (changingControllerVariableName == null) {
1167 //                throw new IllegalArgumentException("Variable name cannot be null.");
1168 //            }
1169 
1170             if ((propertiesEnum == null) && (countersEnum == null)) {
1171                 throw new IllegalArgumentException("Both enums cannot be null.");
1172             }
1173             codeCompletionInfoProvided = true;
1174             this.propertiesEnum = propertiesEnum;
1175             this.countersEnum = countersEnum;
1176 //            this.changingControllerVariableName = changingControllerVariableName;
1177         }
1178 
1179         public void fixCurrentState() {
1180             Exception ex = new GetAction<Exception>() {
1181                 @Override
1182                 public void run(Object... os) throws Exception {
1183                     try {
1184                         for (Entry<AbstractPropertiesTable, String> entry : trackedProperties) {
1185                             for (AbstractPropertyValueListener listener : entry.getKey().getListeners()) {
1186                                 listener.rememberCurrentState();
1187                             }
1188                         }
1189 
1190                         for (Entry<AbstractPropertiesTable, String> entry : trackedCounters) {
1191                             for (AbstractEventsCounter counter : entry.getKey().getCounters()) {
1192                                 counter.rememberCurrentState();
1193                             }
1194                         }
1195                     } catch (Exception ex) {
1196                         setResult(ex);
1197                     }
1198                 }
1199             }.dispatch(Root.ROOT.getEnvironment());
1200 
1201             if (ex != null) {
1202                 System.err.println("Exception during state fixing happened : " + ex.getMessage());
1203                 ex.printStackTrace(System.err);
1204             }
1205 
1206             stateWasFixed = true;
1207         }
1208 
1209         public void check() {
1210             if (!atLeastOneConfigDone) {
1211                 throw new IllegalStateException("No configs were done.");
1212             }
1213             if (!stateWasFixed) {
1214                 throw new IllegalStateException("You need to call fixState() method, to fix the counters or properties.");
1215             }
1216 
1217             final List<FailedItem> failedItems = new ArrayList<FailedItem>();
1218 
1219             Throwable ex = new GetAction<Throwable>() {
1220                 @Override
1221                 public void run(Object... os) throws Exception {
1222                     try {
1223 
1224 
1225                         for (Entry<AbstractPropertiesTable, String> entry : trackedProperties) {
1226                             try {
1227                                 for (AbstractPropertyValueListener listener : entry.getKey().getListeners()) {
1228                                     if (listener.getPropertyName().toUpperCase().equals(entry.getValue().toUpperCase())) {
1229                                         listener.checkCurrentStateEquality();
1230                                     }
1231                                 }
1232                             } catch (StateChangedException exception) {
1233                                 failedItems.add(new FailedItem(FailedItem.FailedItemType.PROPERTY, entry.getKey().getDomainName(), entry.getValue(), exception));
1234                             }
1235                         }
1236 
1237                         for (Entry<AbstractPropertiesTable, String> entry : trackedCounters) {
1238                             try {
1239                                 for (AbstractEventsCounter counter : entry.getKey().getCounters()) {
1240                                     if (counter.getName().toUpperCase().equals(entry.getValue().toUpperCase())) {
1241                                         counter.checkCurrentStateEquality();
1242                                     }
1243                                 }
1244                             } catch (StateChangedException exception) {
1245                                 failedItems.add(new FailedItem(FailedItem.FailedItemType.COUNTER, entry.getKey().getDomainName(), entry.getValue(), exception));
1246                             }
1247                         }
1248                     } catch (Throwable ex) {
1249                         setResult(ex);
1250                     }
1251                 }
1252             }.dispatch(Root.ROOT.getEnvironment());
1253 
1254             if (ex != null) {
1255                 System.err.println("During checking an error occured : " + ex.getMessage());
1256                 ex.printStackTrace(System.err);
1257             }
1258 
1259             if (codeCompletionInfoProvided) {
1260                 generateCompletingCode(failedItems);
1261             }
1262 
1263             fail(failedItems);
1264         }
1265 
1266         public Config include() {
1267             if (stateWasFixed) {
1268                 throw new IllegalStateException("Inclusion cannot be done, because fixing has been done.");
1269             }
1270             return new Config(this, true);
1271         }
1272 
1273         public Config exclude() {
1274             return new Config(this, false);
1275         }
1276 
1277         protected void removeProperty(String domainName, Enum propertyName) {
1278             new Config(this, false).table(domainName).property(propertyName).apply();
1279         }
1280 
1281         protected void removeCounter(String domainName, String counterName) {
1282             new Config(this, false).table(domainName).counter(counterName).apply();
1283         }
1284 
1285         final public void setPropertiesEnum(Object[] propertiesEnum) {
1286             this.propertiesEnum = propertiesEnum;
1287         }
1288 
1289         final public void setCountersEnum(Object[] countersEnum) {
1290             this.countersEnum = countersEnum;
1291         }
1292 
1293         private void generateCompletingCode(List<FailedItem> failedItems) {
1294             if (!failedItems.isEmpty()) {
1295                 System.out.println("You can add this code to provide code completion : ");
1296             }
1297 
1298             try {
1299                 for (FailedItem failedItem : failedItems) {
1300                     if (failedItem.getFailedItemType().equals(FailedItem.FailedItemType.COUNTER)) {
1301                         if (countersEnum != null) {
1302                             int index = findItemIndex(countersEnum, failedItem.getName());
1303                             if (index != -1) {
1304                                 System.out.println(changingControllerVariableName + ".exclude().table(" + failedItem.getDomainName() + ").counter(Counters." + countersEnum[index].toString() + ").apply();");
1305                             }
1306                         }
1307                     }
1308 
1309                     if (failedItem.getFailedItemType().equals(FailedItem.FailedItemType.PROPERTY)) {
1310                         if (propertiesEnum != null) {
1311                             int index = findItemIndex(propertiesEnum, failedItem.getName());
1312                             if (index != -1) {
1313                                 System.out.println(changingControllerVariableName + ".exclude().table(" + failedItem.getDomainName() + ").property(Properties." + propertiesEnum[index].toString() + ").apply();");
1314                             }
1315                         }
1316                     }
1317                 }
1318             } catch (Exception ex) {
1319                 System.err.println("During code generating an error has occured : " + ex.getMessage());
1320                 ex.printStackTrace(System.err);
1321             }
1322         }
1323 
1324         private void fail(List<FailedItem> failedItems) {
1325             for (FailedItem failedItem : failedItems) {
1326                 System.err.println("Failed on checking : " + failedItem);
1327             }
1328 
1329             if (FAIL_ON_ERROR_IN_NON_CHANGING_CHECKING) {
1330                 if (!failedItems.isEmpty()) {
1331                     throw new IllegalStateException("Failures detected.");
1332                 }
1333             }
1334         }
1335 
1336         private int findItemIndex(Object[] enumm, String name) {
1337             for (int i = 0; i < enumm.length; i++) {
1338                 if (enumm[i].toString().toUpperCase().equals(name.toUpperCase())) {
1339                     return i;
1340                 }
1341             }
1342 
1343             return -1;
1344         }
1345 
1346         protected void applyConfig(final Config config) {
1347             Throwable ex = new GetAction<Throwable>() {
1348                 @Override
1349                 public void run(Object... os) throws Exception {
1350                     try {
1351                         Map<String, AbstractPropertiesTable> currentlyExistingTables = searchForTables();
1352 
1353                         List<String> listOfTables = new ArrayList<String>();
1354                         if (config.allTables) {
1355                             for (AbstractPropertiesTable table : currentlyExistingTables.values()) {
1356                                 listOfTables.add(table.getDomainName());
1357                             }
1358                         } else {
1359                             listOfTables = config.tableNames;
1360                         }
1361 
1362                         if (config.toInclude) {
1363                             for (String domainName : listOfTables) {
1364                                 if (config.allCounters) {
1365                                     for (AbstractEventsCounter counter : currentlyExistingTables.get(domainName).getCounters()) {
1366                                         trackedCounters.add(new AbstractMap.SimpleEntry<AbstractPropertiesTable, String>(currentlyExistingTables.get(domainName), counter.getName()));
1367                                     }
1368                                 } else {
1369                                     for (String counterName : config.counterNames) {
1370                                         trackedCounters.add(new AbstractMap.SimpleEntry<AbstractPropertiesTable, String>(currentlyExistingTables.get(domainName), counterName));
1371                                     }
1372                                 }
1373 
1374                                 if (config.allProperties) {
1375                                     for (AbstractPropertyValueListener listener : currentlyExistingTables.get(domainName).getListeners()) {
1376                                         trackedProperties.add(new AbstractMap.SimpleEntry<AbstractPropertiesTable, String>(currentlyExistingTables.get(domainName), listener.getPropertyName()));
1377                                     }
1378                                 } else {
1379                                     for (Enum property : config.propertiesNames) {
1380                                         trackedProperties.add(new AbstractMap.SimpleEntry<AbstractPropertiesTable, String>(currentlyExistingTables.get(domainName), property.name()));
1381                                     }
1382                                 }
1383                             }
1384                         } else { //ToExclude
1385                             for (String domainName : listOfTables) {
1386                                 if (config.allCounters) {
1387                                     List<Entry<AbstractPropertiesTable, String>> removeList = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1388                                     for (Entry<AbstractPropertiesTable, String> entry : trackedCounters) {
1389                                         if (entry.getKey().getDomainName().equals(domainName)) {
1390                                             removeList.add(entry);
1391                                         }
1392                                     }
1393                                     for (Entry<AbstractPropertiesTable, String> entry : removeList) {
1394                                         trackedCounters.remove(entry);
1395                                     }
1396                                 } else {
1397                                     List<Entry<AbstractPropertiesTable, String>> removeList = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1398                                     for (Entry<AbstractPropertiesTable, String> entry : trackedCounters) {
1399                                         for (String counterName : config.counterNames) {
1400                                             if (entry.getKey().getDomainName().equals(domainName) && entry.getValue().equalsIgnoreCase(counterName)) {
1401                                                 removeList.add(entry);
1402                                             }
1403                                         }
1404                                     }
1405                                     for (Entry<AbstractPropertiesTable, String> entry : removeList) {
1406                                         trackedCounters.remove(entry);
1407                                     }
1408                                 }
1409 
1410                                 if (config.allProperties) {
1411                                     List<Entry<AbstractPropertiesTable, String>> removeList = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1412                                     for (Entry<AbstractPropertiesTable, String> entry : trackedProperties) {
1413                                         if (entry.getKey().getDomainName().equals(domainName)) {
1414                                             removeList.add(entry);
1415                                         }
1416                                     }
1417                                     for (Entry<AbstractPropertiesTable, String> entry : removeList) {
1418                                         trackedProperties.remove(entry);
1419                                     }
1420                                 } else {
1421                                     List<Entry<AbstractPropertiesTable, String>> removeList = new ArrayList<Entry<AbstractPropertiesTable, String>>();
1422                                     for (Entry<AbstractPropertiesTable, String> entry : trackedProperties) {
1423                                         for (Enum propertyName : config.propertiesNames) {
1424                                             if (entry.getKey().getDomainName().equals(domainName) && entry.getValue().equalsIgnoreCase(propertyName.name())) {
1425                                                 removeList.add(entry);
1426                                             }
1427                                         }
1428                                     }
1429                                     for (Entry<AbstractPropertiesTable, String> entry : removeList) {
1430                                         trackedProperties.remove(entry);
1431                                     }
1432                                 }
1433                             }
1434                         }
1435                     } catch (Throwable th) {
1436                         setResult(th);
1437                     }
1438                 }
1439             }.dispatch(Root.ROOT.getEnvironment());
1440 
1441             if (ex != null) {
1442                 ex.printStackTrace(System.err);
1443             }
1444         }
1445 
1446         private Map<String, AbstractPropertiesTable> searchForTables() {
1447             List<AbstractPropertiesTable> foundTables = new ArrayList<AbstractPropertiesTable>();
1448 
1449             int found = stageWithPropertiestables.lookup(PropertiesTable.class).size();
1450             for (int i = 0; i < found; i++) {
1451                 foundTables.add(stageWithPropertiestables.lookup(PropertiesTable.class).wrap(i).getControl());
1452             }
1453 
1454             Map<String, AbstractPropertiesTable> tableToIdMathing = new HashMap<String, AbstractPropertiesTable>();
1455 
1456             for (final AbstractPropertiesTable table : foundTables) {
1457                 String domainName = new GetAction<String>() {
1458                     @Override
1459                     public void run(Object... os) throws Exception {
1460                         setResult(table.getDomainName());
1461                     }
1462                 }.dispatch(Root.ROOT.getEnvironment());
1463                 tableToIdMathing.put(domainName, table);
1464             }
1465 
1466             return tableToIdMathing;
1467         }
1468 
1469         protected enum CHECKING_SET {
1470 
1471             COUNTERS, PROPERTIES, STANDART_SET, ALL
1472         };
1473 
1474         private static class FailedItem {
1475 
1476             final private FailedItemType failedItemType;
1477             final private String name;
1478             final private String domainName;
1479             final private Exception exception;
1480 
1481             public FailedItem(FailedItemType failedItemType, String domainName, String name, Exception exception) {
1482                 if (failedItemType == null) {
1483                     throw new IllegalArgumentException("Failed item type cannot be null.");
1484                 }
1485                 if (name == null) {
1486                     throw new IllegalArgumentException("Name cannot be null.");
1487                 }
1488                 if (domainName == null) {
1489                     throw new IllegalArgumentException("Domain name cannot be null.");
1490                 }
1491                 if (exception == null) {
1492                     throw new IllegalArgumentException("Create an exception.");
1493                 }
1494 
1495                 this.failedItemType = failedItemType;
1496                 this.name = name;
1497                 this.domainName = domainName;
1498                 this.exception = exception;
1499             }
1500 
1501             public FailedItemType getFailedItemType() {
1502                 return failedItemType;
1503             }
1504 
1505             public String getName() {
1506                 return name;
1507             }
1508 
1509             public String getDomainName() {
1510                 return domainName;
1511             }
1512 
1513             public Exception getException() {
1514                 return exception;
1515             }
1516 
1517             public enum FailedItemType {
1518 
1519                 PROPERTY, COUNTER
1520             };
1521 
1522             @Override
1523             public String toString() {
1524                 return "Failed <" + failedItemType.name() + "> with name <" + name + "> on table <" + domainName + "> with error : \n" + exception.getMessage();
1525             }
1526         }
1527 
1528         /**
1529          * The only applicable way of doing initialisation is : Create an
1530          * instance -> set tables -> set properties and counters in any order ->
1531          * apply.
1532          */
1533         public static class Config {
1534 
1535             private ConfigStage currentStage = ConfigStage.NOT_STARTED;
1536             private ChangingController controller = null;
1537             private List<String> tableNames = new ArrayList<String>();
1538             private List<Enum> propertiesNames = new ArrayList<Enum>();
1539             private List<String> counterNames = new ArrayList<String>();
1540             private boolean allTables = false;
1541             private boolean allCounters = false;
1542             private boolean allProperties = false;
1543             private Boolean toInclude = null;
1544 
1545             /**
1546              * @param controller that controller, which will receive this
1547              * config.
1548              * @param toInclude - true - to include, false - to exclude.
1549              */
1550             private Config(ChangingController controller, boolean toInclude) {
1551                 if (controller == null) {
1552                     throw new IllegalArgumentException("Controller instance cannot be null.");
1553                 }
1554                 this.controller = controller;
1555                 this.toInclude = toInclude;
1556             }
1557 
1558             public Config table(String name) {
1559                 if (name == null) {
1560                     throw new IllegalStateException("Name cannot be null.");
1561                 }
1562                 checkNonInitialisedOrTablesSetStage();
1563                 tableNames.add(name);
1564                 currentStage = ConfigStage.DOMAIN_SET;
1565                 return this;
1566             }
1567 
1568             public Config tables(String... names) {
1569                 if (names == null) {
1570                     throw new IllegalStateException("Names cannot be null.");
1571                 }
1572                 checkNonInitialisedOrTablesSetStage();
1573                 for (String name : names) {
1574                     if (name == null) {
1575                         throw new IllegalStateException("No one name can be null.");
1576                     }
1577                     tableNames.add(name);
1578                 }
1579                 currentStage = ConfigStage.DOMAIN_SET;
1580                 return this;
1581             }
1582 
1583             /**
1584              * You can write: tables("treeItem-<i>", 1, 4) to decribe such list
1585              * of items: "treeItem-1", "treeItem-2", "treeItem-3", "treeItem-4"
1586              *
1587              * @param pattern should contain <i>
1588              * @param start initial index.
1589              * @param end final index
1590              * @return self
1591              */
1592             public Config tables(String pattern, int start, int end) {
1593                 if (pattern == null) {
1594                     throw new IllegalStateException("Names cannot be null.");
1595                 }
1596 
1597                 if (pattern.indexOf("<i>") < 0) {
1598                     throw new IllegalStateException("Patter should contain \"<i>\" substring.");
1599                 }
1600 
1601                 checkNonInitialisedOrTablesSetStage();
1602                 for (int i = start; i <= end; i++) {
1603                     this.table(pattern.replace("<i>", String.valueOf(i)));
1604                 }
1605                 currentStage = ConfigStage.DOMAIN_SET;
1606                 return this;
1607             }
1608 
1609             public Config allTables() {
1610                 checkNonInitialisedOrTablesSetStage();
1611                 allTables = true;
1612                 currentStage = ConfigStage.DOMAIN_SET;
1613                 return this;
1614             }
1615 
1616             public Config counter(String name) {
1617                 if (name == null) {
1618                     throw new IllegalStateException("Name cannot be null.");
1619                 }
1620                 checkCanSetPropertyOrCounterDetermined();
1621                 counterNames.add(name);
1622                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1623                 return this;
1624             }
1625 
1626             public Config property(Enum name) {
1627                 if (name == null) {
1628                     throw new IllegalStateException("Name cannot be null.");
1629                 }
1630                 checkCanSetPropertyOrCounterDetermined();
1631                 propertiesNames.add(name);
1632                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1633                 return this;
1634             }
1635 
1636             public Config counters(String... names) {
1637                 if (names == null) {
1638                     throw new IllegalStateException("Names cannot be null.");
1639                 }
1640                 checkCanSetPropertyOrCounterDetermined();
1641                 for (String name : names) {
1642                     if (name == null) {
1643                         throw new IllegalStateException("No one name can be null.");
1644                     }
1645                     counterNames.add(name);
1646                 }
1647                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1648                 return this;
1649             }
1650 
1651             public Config properties(Enum... names) {
1652                 if (names == null) {
1653                     throw new IllegalStateException("Names cannot be null.");
1654                 }
1655                 checkCanSetPropertyOrCounterDetermined();
1656                 for (Enum name : names) {
1657                     if (name == null) {
1658                         throw new IllegalStateException("No one name can be null.");
1659                     }
1660                     propertiesNames.add(name);
1661                 }
1662                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1663                 return this;
1664             }
1665 
1666             public Config allCounters() {
1667                 checkCanSetPropertyOrCounterDetermined();
1668                 allCounters = true;
1669                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1670                 return this;
1671             }
1672 
1673             public Config allProperties() {
1674                 checkCanSetPropertyOrCounterDetermined();
1675                 allProperties = true;
1676                 currentStage = ConfigStage.PROPERTIES_OR_COUNTERS_SET;
1677                 return this;
1678             }
1679 
1680             public void apply() {
1681                 if (currentStage != ConfigStage.PROPERTIES_OR_COUNTERS_SET) {
1682                     throw new IllegalStateException("Cannot be applied, when properties or counters are not determined.");
1683                 }
1684                 controller.atLeastOneConfigDone = true;
1685                 controller.applyConfig(this);
1686                 currentStage = ConfigStage.APPLIED;
1687             }
1688 
1689             private void checkNonInitialisedOrTablesSetStage() {
1690                 if (currentStage != ConfigStage.NOT_STARTED && currentStage != ConfigStage.DOMAIN_SET) {
1691                     throw new IllegalStateException("Should be not initialised, or only tables initialised.");
1692                 }
1693             }
1694 
1695             private void checkCanSetPropertyOrCounterDetermined() {
1696                 if (!((currentStage == ConfigStage.DOMAIN_SET) || (currentStage == ConfigStage.PROPERTIES_OR_COUNTERS_SET))) {
1697                     throw new IllegalStateException();
1698                 }
1699             }
1700 
1701             private static enum ConfigStage {
1702 
1703                 NOT_STARTED, DOMAIN_SET, PROPERTIES_OR_COUNTERS_SET, APPLIED
1704             };
1705         }
1706     }
1707 
1708     static protected enum SettingOption {
1709 
1710         MANUAL, PROGRAM
1711     };
1712 
1713     static public enum RectanglesRelations {
1714 
1715         BELOW, LEFTER, RIGHTER, ABOVE, ISCONTAINED, CONTAINS, CENTERED_IN_HORIZONTAL, CENTERED_IN_VERTICAL
1716     }
1717 }