/* * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. */ package controls; import com.sun.javafx.perf.PerformanceTracker; import fx.bm.FXTest; import fx.bm.util.counters.CounterType; import javafx.animation.AnimationTimer; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.animation.TimelineBuilder; import javafx.application.Application; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.KeyCode; import javafx.scene.control.*; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.stage.Stage; import javafx.stage.StageBuilder; import javafx.stage.WindowEvent; import javafx.util.Duration; import jrockit.bm.util.TestResult; import java.util.EnumSet; import java.util.Set; import java.util.List; import java.util.ArrayList; import java.awt.*; /** * TableView scroll test. * * Measure FPS rate of scrolling the TableView UI component. */ public class TreeViewTest extends Application implements FXTest, EventHandler , Runnable { // Fix for RT-11807 - AWT should be initialized before FX Application.launch . static { java.awt.Toolkit.getDefaultToolkit(); } private static final EnumSet DEFAULT_COUNTERS = EnumSet.of(CounterType.JFX_FPS, CounterType.JFX_PULSES); private static final String CSS_FILENAME = "css/treeview.css"; private enum CellTypes { REGULAR("regular cell"), CSS("CSS-featured cell, look for "+CSS_FILENAME), BUTTON("button control in cell"), CHECKBOX("checkbox control in cell"), HYPERLINK("hyperlinks in cell"); public String descr; CellTypes(String descr) { this.descr = descr; } } private enum TestModes { KEYBOARD ("walking vertical by arrow keys", false), SCROLL_DRAG ("dragging the scroll thumb by mouse", true); public String descr; public boolean useMouseEvents; TestModes(String descr, boolean useMouseEvents) { this.descr = descr; this.useMouseEvents = useMouseEvents; } } private enum ScrollMode { TOP("scroll through first 100 items"), BOTTOM("scroll through last 100 items"), TOP10("scroll through first 10 items"), BOTTOM10("scroll through last 10 items"), ALL("scroll through all items"); public String descr; ScrollMode(String descr) { this.descr = descr; } } // parameters below can be configured via command line, see output of printHelp() method for details. private static double viewWidth = 400.0; // width of visible area private static double viewHeight = 600.0; // height of visible area private static int numColumns = 30; private static int numRows = 1000; // items in list private static int warmupTime = 10; // warm-up phase duration private static int runTime = 20; // measurement phase duration private static boolean debugStatus = false; // turn off debug output private static int keysPerInjection = 1; // amount of keys, per single injection private static CellTypes cellType = CellTypes.REGULAR; private static TestModes testMode = TestModes.SCROLL_DRAG; private static ScrollMode scrollMode = ScrollMode.ALL; private TreeViewtreeView; private Stage stage; private Timeline fpsTimeline; private boolean isActive = false; private PerformanceTracker tracker; private final AnimationTimer timer = new AnimationTimer() { @Override public void handle(long l) { run(); } }; private KeyEvent ke_vk_down, ke_vk_up; private KeyEvent ke_vk_page_down, ke_vk_page_up; private int lastIdx, firstIdx; private boolean firstMove = true; private KeyEvent ke; private boolean first = true; private int lastY; private int direction = 1; private int thumb_top = 12; ///////////////////////////////////////////////////////////////// @Override public void startWarmup() {} @Override public void stopWarmup() {} @Override public boolean hasWarmup() { return true; } @Override public void startScoring() { } @Override public TestResult getScore() { return null; } @Override public void stopTest() { if(isActive) { isActive = false; timer.stop(); if (fpsTimeline != null) { fpsTimeline.stop(); } PerformanceTracker.releaseSceneTracker(stage.getScene()); treeView = null; stage = null; } } @Override public void parseArgs(String[] args) { parseCommandLine(args); } @Override public Stage createTestGUI() { // Note: workaround, trying to avoid affecting of mouse hover try { new Robot().mouseMove(0,0); } catch (AWTException awte) { //throw new RuntimeException (awte); } TreeItem root = new TreeItem("Root"); treeView = new TreeView(root); root.setExpanded(true); treeView.setPrefSize(viewWidth, viewHeight); for (int i = 0; i < numRows; i++) { TreeItem item = new TreeItem(new Label("Elem " + i)); root.getChildren().add(item); if(cellType == CellTypes.CSS) { ((Label)item.getValue()).getStyleClass().add("cell-heading"); } List> newChildren = new ArrayList>(); for (int j = 0; j < numColumns; j++) { String text = "Parent "+ i + " Subelement " + j; Object obj = text; switch(cellType) { case BUTTON: { obj = new Button(text); } break; case CHECKBOX: { obj = new CheckBox(text); } break; case HYPERLINK: { obj = new Hyperlink(text); } break; } newChildren.add(new TreeItem(obj)); } item.getChildren().addAll(newChildren); item.setExpanded(true); } final Scene scene = new Scene(new Group(treeView), viewWidth, viewHeight); if(cellType == CellTypes.CSS) { scene.getStylesheets().add( getClass().getResource(CSS_FILENAME).toString() ); } stage = StageBuilder.create() .title("TreeView") // workaround: terminate active test when window is closed .onHidden(new EventHandler() { @Override public void handle(WindowEvent event) { if (isActive) { stopTest(); System.out.println("Application window has been suddenly closed. Program terminated."); System.exit(-1); } } }) .scene(scene) .build(); stage.sizeToScene(); tracker = PerformanceTracker.getSceneTracker(stage.getScene()); if(debugStatus) { // init measurement timeline if(fpsTimeline == null) { fpsTimeline = TimelineBuilder.create() .cycleCount(Timeline.INDEFINITE) .keyFrames(new KeyFrame(Duration.seconds(1), new EventHandler() { @Override public void handle(ActionEvent e) { debug(" instant FPS: " + tracker.getInstantFPS() + ", average FPS: " + tracker.getAverageFPS()); debug(" instant pulses: " + tracker.getInstantPulses() + ", average pulses: " + tracker.getAveragePulses()); } }) ).build(); } fpsTimeline.playFromStart(); } return stage; } /* * Setup and run the Timeline for standalone mode */ private void doAutoTimeline() { // prepare timeline for standalone notifications TimelineBuilder.create().keyFrames( new KeyFrame(Duration.ZERO, new EventHandler() { @Override public void handle(ActionEvent e) { debug("Warm-up duration: " + warmupTime + " sec"); debug("Measurement duration: " + runTime + " sec"); System.out.println("Starting warmup for " + warmupTime + " sec..."); } }), new KeyFrame(new Duration(warmupTime * 1000), new EventHandler() { @Override public void handle(ActionEvent e) { debug("Resetting average fps counter"); tracker.resetAverageFPS(); tracker.resetAveragePulses(); System.out.println("Measurement phase for " + runTime + " sec... "); } }), new KeyFrame(new Duration((runTime + warmupTime) * 1000), new EventHandler() { @Override public void handle(ActionEvent e) { System.out.println("\n Score: " + tracker.getAverageFPS() + " Average FPS over " + runTime + "s "); System.out.println("\tinstant pulses: " + tracker.getInstantPulses() + ", average pulses: " + tracker.getAveragePulses()); System.exit(0); } }) ).build().playFromStart(); } @Override public void runTest(){ if (!treeView.isFocused()) { treeView.requestFocus(); treeView.toFront(); } switch (testMode) { case KEYBOARD: { ke = ke_vk_down = new KeyEvent(null, treeView, KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED, KeyCode.DOWN.getName(), KeyCode.DOWN.impl_getCode(), false, false, false, false); ke_vk_up = new KeyEvent(null, treeView, KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED, KeyCode.UP.getName(), KeyCode.UP.impl_getCode(), false, false, false, false); ke_vk_page_down = new KeyEvent(null, treeView, KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED, KeyCode.PAGE_DOWN.getName(), KeyCode.PAGE_DOWN.impl_getCode(), false, false, false, false); ke_vk_page_up = new KeyEvent(null, treeView, KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED, KeyCode.PAGE_UP.getName(), KeyCode.PAGE_UP.impl_getCode(), false, false, false, false); firstIdx = 0; lastIdx = (numRows * (numColumns+1)); switch (scrollMode ) { case TOP: lastIdx = Math.min(lastIdx, firstIdx + 100); break; case BOTTOM: firstIdx = Math.max(0, lastIdx - 100); break; case TOP10: lastIdx = Math.min(lastIdx, firstIdx + 10); break; case BOTTOM10: firstIdx = Math.max(0, lastIdx - 10); break; } treeView.getSelectionModel().select(firstIdx); if (debugStatus) { System.out.println("firstIdx=" + firstIdx); System.out.println("lastIdx=" + lastIdx); System.out.println("Start at: " + treeView.getSelectionModel().getSelectedIndex()); } } break; case SCROLL_DRAG: { first = true; lastY = thumb_top; direction = 1; } break; default: { throw new UnsupportedOperationException("the test doesn't support " + testMode ); } } timer.start(); isActive = true; } @Override public void run() { if(isActive) { switch(testMode) { case KEYBOARD: { process_keyboard(); } break; case SCROLL_DRAG: { process_mouse(); } break; } } } public void process_keyboard() { int position = treeView.getSelectionModel().getSelectedIndex(); if (debugStatus) { System.out.println("position=" + position); } for (int i=0; i= lastIdx) { position --; if (scrollMode != ScrollMode.TOP10 && scrollMode != ScrollMode.BOTTOM10) { if (!firstMove) { if (debugStatus) { System.out.println(" go PG_UP"); } stage.getScene().impl_processKeyEvent(ke_vk_page_up); } } ke = ke_vk_up; } if (debugStatus) { System.out.println(" go " + ke.getCode().getName()); } stage.getScene().impl_processKeyEvent(ke); firstMove = false; } } public void process_keyboard_old() { // check for selection int gap = treeView.getSelectionModel().getSelectedIndex(); int size = numRows*numColumns; if(gap < 0) { treeView.getSelectionModel().select(0); gap = 0; } // get left cells if(ke == ke_vk_down) { gap = size - 1 - gap; } // moving forward int amount = Math.min(keysPerInjection, gap); for( int i=0; i < amount; i++) { stage.getScene().impl_processKeyEvent(ke); } // bouncing the rest of the way amount = keysPerInjection - amount; while (amount > 0) { int range = Math.min(amount, size); amount -= range; ke = ( ke == ke_vk_up ? ke_vk_down : ke_vk_up ); for( int i=0; i < range; i++) { stage.getScene().impl_processKeyEvent(ke); } } } public void process_mouse() { if (first) { // Workaround of case when Control is not layouted yet if (treeView.getWidth() == 0 || treeView.getHeight() == 0) { return; } MouseEvent me_pressed = MouseEvent.impl_mouseEvent( treeView.getWidth() - 6, /* x */ thumb_top, /* y */ treeView.getWidth() - 6 + stage.getX() + stage.getScene().getX(), /* screenx */ thumb_top + stage.getY() + stage.getScene().getY(), /* screeny -- ignored */ MouseButton.PRIMARY, /* button */ 1, /* clickCount */ false, /* shiftDown */ false, /* controlDown */ false, /* altDown */ false, /* metaDown */ false, /* popupTrigger */ true, /* primaryButtonDown */ false, /* middleButtonDown */ false, /* secondaryButtonDown */ false, /* synthesized */ MouseEvent.MOUSE_PRESSED /* event id */ ); stage.getScene().impl_processMouseEvent(me_pressed); first = false; } /* fixed pixel margins of thumb movement */ if (lastY > treeView.getHeight() - 40 ) { direction = -1; } else if (lastY < thumb_top) { direction = 1; } lastY += 6 * direction; MouseEvent me_moved = MouseEvent.impl_mouseEvent( treeView.getWidth() - 6, /* x */ lastY, /* y */ treeView.getWidth() - 6 + stage.getX() + stage.getScene().getX(), /* screenx */ lastY + stage.getY() + stage.getScene().getY(), /* screeny -- ignored */ MouseButton.PRIMARY, /* button */ 1, /* clickCount */ false, /* shiftDown */ false, /* controlDown */ false, /* altDown */ false, /* metaDown */ false, /* popupTrigger */ true, /* primaryButtonDown */ false, /* middleButtonDown */ false, /* secondaryButtonDown */ false, /* synthesized */ MouseEvent.MOUSE_DRAGGED /* event id */ ); try { stage.getScene().impl_processMouseEvent(me_moved); } catch (NullPointerException npe) { // Workaround of NPE. Occurs when Scene mouse listener loses MOUSE_PRESSED. first = true; debug(npe); } } ///////////////////////////////////////////////////////////////// /** * Debug output. * @param msg message for output */ static void debug(String msg) { if (debugStatus) { System.out.println("DEBUG: " + msg); } } /** * Debug output. * @param th exception instance. */ static void debug(Throwable th) { if (debugStatus) { System.out.println("\nDEBUG: " + th.getClass().getCanonicalName() + ": " +th.getMessage()); for(StackTraceElement s: th.getStackTrace()) { System.out.println("DEBUG: " + s.toString()); } } } @Override public Set getDefaultCounters() { return DEFAULT_COUNTERS; } @Override public void handle(ActionEvent e) { Platform.runLater( this ); } private static void parseCommandLine(String[] args) { for(int i=0; i < args.length; i++) { if(i + 1 == args.length) { printHelp(); } final String opt = args[i].toLowerCase(); final String arg = args[++i]; if ("-viewsize".equals(opt)) { viewWidth = Integer.parseInt(arg); //viewHeight is optional if (i + 1 < args.length) { viewHeight = Integer.parseInt(args[++i]); } } else if ("-cells".equals(opt)) { numRows = Integer.parseInt(arg); //numColumns is optional if (i + 1 < args.length) { numColumns = Integer.parseInt(args[++i]); } } else if ("-mode".equals(opt)) { testMode = TestModes.valueOf(arg.toUpperCase()); } else if ("-celltype".equals(opt)) { cellType = CellTypes.valueOf(arg.toUpperCase()); } else if ("-keysperinjection".equals(opt)) { keysPerInjection = Math.max(Integer.valueOf(arg),1); } else if("-debug".equals(opt)) { debugStatus = Boolean.parseBoolean(arg); } else if("-warmup".equals(opt)) { warmupTime = Math.max(Integer.valueOf(arg), 0); } else if("-duration".equals(opt)) { runTime = Math.max(Integer.valueOf(arg), 5); } else if ("-scrollmode".equals(opt)) { scrollMode = ScrollMode.valueOf(arg.toUpperCase()); } else { printHelp(); } } System.out.println( " TestMode : " + testMode + "\n" + " View area : " + viewWidth + " x " + viewHeight+"\n" + " Total cells : " + numRows + "(toplevel) x " + numColumns + "(nested)\n" + " Cell type : " + cellType + "\n" + " Scroll mode : " + scrollMode + "\n" + "Keys per injection: " + keysPerInjection + "\n" + "Debug status : " + (debugStatus ? "print messages":"silent") + "\n" ); } private static void printHelp() { StringBuilder testModes = new StringBuilder(); for(TestModes mode : TestModes.values()) { testModes.append("\t\t").append(mode.toString().toLowerCase()).append("\t\t\t\t: ").append(mode.descr).append("\n"); } StringBuilder cellTypes = new StringBuilder(); for(CellTypes cell : CellTypes.values()) { cellTypes.append("\t\t").append(cell.toString().toLowerCase()).append("\t\t\t\t: ").append(cell.descr).append("\n"); } StringBuilder scrollModes = new StringBuilder(); for(ScrollMode mode : ScrollMode.values()) { scrollModes.append("\t\t").append(mode.toString().toLowerCase()).append("\t\t\t\t: ").append(mode.descr).append("\n"); } System.out.println("Command-line args:\n" +"\t-viewsize : set width and height of ListView component (default: "+viewWidth+"x"+viewHeight+")\n" +"\t-warmup : warm-up time, seconds (default: "+warmupTime+" sec)\n" +"\t-duration : measurement time, seconds (default: "+runTime+" sec)\n" +"\t-keysPerInjection : amount of key presses per injection (default:"+keysPerInjection+")\n" +"\t-mode {...} : select appropriate test mode, default is '"+testMode.toString().toLowerCase()+"'\n" +testModes.toString() +"\t-cells : set size of cells matrix (default: " + numRows + "x" + numColumns + ")\n" +"\t-celltype {...} : select desired shape of cell (default: '"+cellType.toString().toLowerCase()+"')\n" +cellTypes.toString() +"\t-scrollmode {...} : select appropriate scroll mode, default is '"+scrollMode.toString().toLowerCase()+"'\n" +scrollModes.toString() +"\t-debug true : enable debug messages (default " + debugStatus + ")\n" +"\n" +"\tExample:\n" +"\t\tjava -jar TreeView.jar -mode scroll_drag "); System.exit(0); } /* * Application class for running standalone. */ @Override public void start(Stage primaryStage) { Stage stage = createTestGUI(); stage.show(); doAutoTimeline(); runTest(); } /** * Standalone entry point. * @param args Commandline arguments */ public static void main(String[] args) { parseCommandLine(args); Application.launch(TreeViewTest.class, args); } }