1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.jmeter.gui; 20 21 import java.awt.BorderLayout; 22 import java.awt.Component; 23 import java.awt.Dimension; 24 import java.awt.Font; 25 import java.awt.Insets; 26 import java.awt.event.ActionEvent; 27 import java.awt.event.ActionListener; 28 import java.awt.event.MouseEvent; 29 import java.awt.event.WindowAdapter; 30 import java.awt.event.WindowEvent; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 import javax.swing.BorderFactory; 35 import javax.swing.Box; 36 import javax.swing.BoxLayout; 37 import javax.swing.ImageIcon; 38 import javax.swing.JButton; 39 import javax.swing.JComponent; 40 import javax.swing.JDialog; 41 import javax.swing.JFrame; 42 import javax.swing.JLabel; 43 import javax.swing.JMenu; 44 import javax.swing.JPanel; 45 import javax.swing.JPopupMenu; 46 import javax.swing.JScrollPane; 47 import javax.swing.JSplitPane; 48 import javax.swing.JTree; 49 import javax.swing.MenuElement; 50 import javax.swing.SwingUtilities; 51 import javax.swing.tree.DefaultMutableTreeNode; 52 import javax.swing.tree.DefaultTreeCellRenderer; 53 import javax.swing.tree.TreeCellRenderer; 54 import javax.swing.tree.TreeModel; 55 import javax.swing.tree.TreePath; 56 57 import org.apache.jmeter.engine.event.LoopIterationEvent; 58 import org.apache.jmeter.gui.action.ActionNames; 59 import org.apache.jmeter.gui.action.ActionRouter; 60 import org.apache.jmeter.gui.tree.JMeterCellRenderer; 61 import org.apache.jmeter.gui.tree.JMeterTreeListener; 62 import org.apache.jmeter.gui.util.JMeterMenuBar; 63 import org.apache.jmeter.samplers.Remoteable; 64 import org.apache.jmeter.testelement.TestElement; 65 import org.apache.jmeter.testelement.TestListener; 66 import org.apache.jmeter.threads.JMeterContextService; 67 import org.apache.jmeter.util.JMeterUtils; 68 import org.apache.jorphan.gui.ComponentUtil; 69 70 /** 71 * The main JMeter frame, containing the menu bar, test tree, and an area for 72 * JMeter component GUIs. 73 * 74 */ 75 public class MainFrame extends JFrame implements TestListener, Remoteable { 76 77 // This is used to keep track of local (non-remote) tests 78 // The name is chosen to be an unlikely host-name 79 private static final String LOCAL = "*local*"; // $NON-NLS-1$ 80 81 // The default title for the Menu bar 82 private static final String DEFAULT_TITLE = 83 "Apache JMeter ("+JMeterUtils.getJMeterVersion()+")"; // $NON-NLS-1$ $NON-NLS-2$ 84 85 /** The menu bar. */ 86 private JMeterMenuBar menuBar; 87 88 /** The main panel where components display their GUIs. */ 89 private JScrollPane mainPanel; 90 91 /** The panel where the test tree is shown. */ 92 private JScrollPane treePanel; 93 94 /** The test tree. */ 95 private JTree tree; 96 97 /** An image which is displayed when a test is running. */ 98 private ImageIcon runningIcon = JMeterUtils.getImage("thread.enabled.gif");// $NON-NLS-1$ 99 100 /** An image which is displayed when a test is not currently running. */ 101 private ImageIcon stoppedIcon = JMeterUtils.getImage("thread.disabled.gif");// $NON-NLS-1$ 102 103 /** The button used to display the running/stopped image. */ 104 private JButton runningIndicator; 105 106 /** The x coordinate of the last location where a component was dragged. */ 107 private int previousDragXLocation = 0; 108 109 /** The y coordinate of the last location where a component was dragged. */ 110 private int previousDragYLocation = 0; 111 112 /** The set of currently running hosts. */ 113 private Set hosts = new HashSet(); 114 115 /** A message dialog shown while JMeter threads are stopping. */ 116 private JDialog stoppingMessage; 117 118 private JLabel totalThreads; 119 private JLabel activeThreads; 120 121 /** 122 * Create a new JMeter frame. 123 * 124 * @param actionHandler 125 * this parameter is not used 126 * @param treeModel 127 * the model for the test tree 128 * @param treeListener 129 * the listener for the test tree 130 */ 131 public MainFrame(ActionListener actionHandler, TreeModel treeModel, JMeterTreeListener treeListener) { 132 // TODO: actionHandler isn't used -- remove it from the parameter list 133 // this.actionHandler = actionHandler; 134 135 // TODO: Make the running indicator its own class instead of a JButton 136 runningIndicator = new JButton(stoppedIcon); 137 runningIndicator.setMargin(new Insets(0, 0, 0, 0)); 138 runningIndicator.setBorder(BorderFactory.createEmptyBorder()); 139 140 totalThreads = new JLabel("0"); // $NON-NLS-1$ 141 activeThreads = new JLabel("0"); // $NON-NLS-1$ 142 143 tree = makeTree(treeModel, treeListener); 144 145 GuiPackage.getInstance().setMainFrame(this); 146 init(); 147 148 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 149 } 150 151 /** 152 * Default constructor for the JMeter frame. This constructor will not 153 * properly initialize the tree, so don't use it. 154 * 155 * @deprecated Do not use - only needed for JUnit tests 156 */ 157 public MainFrame() { 158 } 159 160 // MenuBar related methods 161 // TODO: Do we really need to have all these menubar methods duplicated 162 // here? Perhaps we can make the menu bar accessible through GuiPackage? 163 164 /** 165 * Specify whether or not the File|Load menu item should be enabled. 166 * 167 * @param enabled 168 * true if the menu item should be enabled, false otherwise 169 */ 170 public void setFileLoadEnabled(boolean enabled) { 171 menuBar.setFileLoadEnabled(enabled); 172 } 173 174 /** 175 * Specify whether or not the File|Save menu item should be enabled. 176 * 177 * @param enabled 178 * true if the menu item should be enabled, false otherwise 179 */ 180 public void setFileSaveEnabled(boolean enabled) { 181 menuBar.setFileSaveEnabled(enabled); 182 } 183 184 /** 185 * Specify whether or not the File|Revert item should be enabled. 186 * 187 * @param enabled 188 * true if the menu item should be enabled, false otherwise 189 */ 190 public void setFileRevertEnabled(boolean enabled) { 191 menuBar.setFileRevertEnabled(enabled); 192 } 193 194 /** 195 * Specify the project file that was just loaded 196 * 197 * @param file - the full path to the file that was loaded 198 */ 199 public void setProjectFileLoaded(String file) { 200 menuBar.setProjectFileLoaded(file); 201 } 202 203 /** 204 * Set the menu that should be used for the Edit menu. 205 * 206 * @param menu 207 * the new Edit menu 208 */ 209 public void setEditMenu(JPopupMenu menu) { 210 menuBar.setEditMenu(menu); 211 } 212 213 /** 214 * Specify whether or not the Edit menu item should be enabled. 215 * 216 * @param enabled 217 * true if the menu item should be enabled, false otherwise 218 */ 219 public void setEditEnabled(boolean enabled) { 220 menuBar.setEditEnabled(enabled); 221 } 222 223 /** 224 * Set the menu that should be used for the Edit|Add menu. 225 * 226 * @param menu 227 * the new Edit|Add menu 228 */ 229 public void setEditAddMenu(JMenu menu) { 230 menuBar.setEditAddMenu(menu); 231 } 232 233 /** 234 * Specify whether or not the Edit|Add menu item should be enabled. 235 * 236 * @param enabled 237 * true if the menu item should be enabled, false otherwise 238 */ 239 public void setEditAddEnabled(boolean enabled) { 240 menuBar.setEditAddEnabled(enabled); 241 } 242 243 /** 244 * Specify whether or not the Edit|Remove menu item should be enabled. 245 * 246 * @param enabled 247 * true if the menu item should be enabled, false otherwise 248 */ 249 public void setEditRemoveEnabled(boolean enabled) { 250 menuBar.setEditRemoveEnabled(enabled); 251 } 252 253 /** 254 * Close the currently selected menu. 255 */ 256 public void closeMenu() { 257 if (menuBar.isSelected()) { 258 MenuElement[] menuElement = menuBar.getSubElements(); 259 if (menuElement != null) { 260 for (int i = 0; i < menuElement.length; i++) { 261 JMenu menu = (JMenu) menuElement[i]; 262 if (menu.isSelected()) { 263 menu.setPopupMenuVisible(false); 264 menu.setSelected(false); 265 break; 266 } 267 } 268 } 269 } 270 } 271 272 /** 273 * Show a dialog indicating that JMeter threads are stopping on a particular 274 * host. 275 * 276 * @param host 277 * the host where JMeter threads are stopping 278 */ 279 public void showStoppingMessage(String host) { 280 if (stoppingMessage != null){ 281 stoppingMessage.dispose(); 282 } 283 stoppingMessage = new JDialog(this, JMeterUtils.getResString("stopping_test_title"), true); //$NON-NLS-1$ 284 JLabel stopLabel = new JLabel(JMeterUtils.getResString("stopping_test") + ": " + host); //$NON-NLS-1$$NON-NLS-2$ 285 stopLabel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); 286 stoppingMessage.getContentPane().add(stopLabel); 287 stoppingMessage.pack(); 288 ComponentUtil.centerComponentInComponent(this, stoppingMessage); 289 SwingUtilities.invokeLater(new Runnable() { 290 public void run() { 291 if (stoppingMessage != null) {// TODO - how can this be null? 292 stoppingMessage.show(); 293 } 294 } 295 }); 296 } 297 298 public void updateCounts() { 299 SwingUtilities.invokeLater(new Runnable() { 300 public void run() { 301 activeThreads.setText(Integer.toString(JMeterContextService.getNumberOfThreads())); 302 totalThreads.setText(Integer.toString(JMeterContextService.getTotalThreads())); 303 } 304 }); 305 } 306 307 public void setMainPanel(JComponent comp) { 308 mainPanel.setViewportView(comp); 309 } 310 311 public JTree getTree() { 312 return tree; 313 } 314 315 // TestListener implementation 316 317 /** 318 * Called when a test is started on the local system. This implementation 319 * sets the running indicator and ensures that the menubar is enabled and in 320 * the running state. 321 */ 322 public void testStarted() { 323 testStarted(LOCAL); 324 menuBar.setEnabled(true); 325 } 326 327 /** 328 * Called when a test is started on a specific host. This implementation 329 * sets the running indicator and ensures that the menubar is in the running 330 * state. 331 * 332 * @param host 333 * the host where the test is starting 334 */ 335 public void testStarted(String host) { 336 hosts.add(host); 337 runningIndicator.setIcon(runningIcon); 338 activeThreads.setText("0"); // $NON-NLS-1$ 339 totalThreads.setText("0"); // $NON-NLS-1$ 340 menuBar.setRunning(true, host); 341 } 342 343 /** 344 * Called when a test is ended on the local system. This implementation 345 * disables the menubar, stops the running indicator, and closes the 346 * stopping message dialog. 347 */ 348 public void testEnded() { 349 testEnded(LOCAL); 350 menuBar.setEnabled(false); 351 } 352 353 /** 354 * Called when a test is ended on the remote system. This implementation 355 * stops the running indicator and closes the stopping message dialog. 356 * 357 * @param host 358 * the host where the test is ending 359 */ 360 public void testEnded(String host) { 361 hosts.remove(host); 362 if (hosts.size() == 0) { 363 runningIndicator.setIcon(stoppedIcon); 364 JMeterContextService.endTest(); 365 } 366 menuBar.setRunning(false, host); 367 if (stoppingMessage != null) { 368 stoppingMessage.dispose(); 369 stoppingMessage = null; 370 } 371 } 372 373 /* Implements TestListener#testIterationStart(LoopIterationEvent) */ 374 public void testIterationStart(LoopIterationEvent event) { 375 } 376 377 /** 378 * Create the GUI components and layout. 379 */ 380 private void init() { 381 menuBar = new JMeterMenuBar(); 382 setJMenuBar(menuBar); 383 384 JPanel all = new JPanel(new BorderLayout()); 385 all.add(createToolBar(), BorderLayout.NORTH); 386 387 JSplitPane treeAndMain = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); 388 389 treePanel = createTreePanel(); 390 treeAndMain.setLeftComponent(treePanel); 391 392 mainPanel = createMainPanel(); 393 treeAndMain.setRightComponent(mainPanel); 394 395 treeAndMain.setResizeWeight(.2); 396 treeAndMain.setContinuousLayout(true); 397 all.add(treeAndMain, BorderLayout.CENTER); 398 399 getContentPane().add(all); 400 401 tree.setSelectionRow(1); 402 addWindowListener(new WindowHappenings()); 403 404 setTitle(DEFAULT_TITLE); 405 setIconImage(JMeterUtils.getImage("jmeter.jpg").getImage());// $NON-NLS-1$ 406 } 407 408 public void setExtendedFrameTitle(String fname) { 409 // file New operation may set to null, so just return app name 410 if (fname == null) { 411 setTitle(DEFAULT_TITLE); 412 return; 413 } 414 415 // allow for windows / chars in filename 416 String temp = fname.replace('\\', '/'); // $NON-NLS-1$ // $NON-NLS-2$ 417 String simpleName = temp.substring(temp.lastIndexOf("/") + 1);// $NON-NLS-1$ 418 setTitle(simpleName + " (" + fname + ") - " + DEFAULT_TITLE); // $NON-NLS-1$ // $NON-NLS-2$ 419 } 420 421 /** 422 * Create the JMeter tool bar pane containing the running indicator. 423 * 424 * @return a panel containing the running indicator 425 */ 426 private Component createToolBar() { 427 Box toolPanel = new Box(BoxLayout.X_AXIS); 428 toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); 429 toolPanel.add(Box.createGlue()); 430 toolPanel.add(activeThreads); 431 toolPanel.add(new JLabel(" / ")); 432 toolPanel.add(totalThreads); 433 toolPanel.add(Box.createRigidArea(new Dimension(10, 15))); 434 toolPanel.add(runningIndicator); 435 return toolPanel; 436 } 437 438 /** 439 * Create the panel where the GUI representation of the test tree is 440 * displayed. The tree should already be created before calling this method. 441 * 442 * @return a scroll pane containing the test tree GUI 443 */ 444 private JScrollPane createTreePanel() { 445 JScrollPane treeP = new JScrollPane(tree); 446 treeP.setMinimumSize(new Dimension(100, 0)); 447 return treeP; 448 } 449 450 /** 451 * Create the main panel where components can display their GUIs. 452 * 453 * @return the main scroll pane 454 */ 455 private JScrollPane createMainPanel() { 456 return new JScrollPane(); 457 } 458 459 /** 460 * Create and initialize the GUI representation of the test tree. 461 * 462 * @param treeModel 463 * the test tree model 464 * @param treeListener 465 * the test tree listener 466 * 467 * @return the initialized test tree GUI 468 */ 469 private JTree makeTree(TreeModel treeModel, JMeterTreeListener treeListener) { 470 JTree treevar = new JTree(treeModel) { 471 public String getToolTipText(MouseEvent event) { 472 TreePath path = this.getPathForLocation(event.getX(), event.getY()); 473 if (path != null) { 474 Object treeNode = path.getLastPathComponent(); 475 if (treeNode instanceof DefaultMutableTreeNode) { 476 Object testElement = ((DefaultMutableTreeNode) treeNode).getUserObject(); 477 if (testElement instanceof TestElement) { 478 String comment = ((TestElement) testElement).getComment(); 479 if (comment != null && comment.length() > 0) { 480 return comment; 481 } 482 } 483 } 484 } 485 return null; 486 } 487 }; 488 treevar.setToolTipText(""); 489 treevar.setCellRenderer(getCellRenderer()); 490 treevar.setRootVisible(false); 491 treevar.setShowsRootHandles(true); 492 493 treeListener.setJTree(treevar); 494 treevar.addTreeSelectionListener(treeListener); 495 treevar.addMouseListener(treeListener); 496 treevar.addMouseMotionListener(treeListener); 497 treevar.addKeyListener(treeListener); 498 499 return treevar; 500 } 501 502 /** 503 * Create the tree cell renderer used to draw the nodes in the test tree. 504 * 505 * @return a renderer to draw the test tree nodes 506 */ 507 private TreeCellRenderer getCellRenderer() { 508 DefaultTreeCellRenderer rend = new JMeterCellRenderer(); 509 rend.setFont(new Font("Dialog", Font.PLAIN, 11)); 510 return rend; 511 } 512 513 /** 514 * Repaint pieces of the GUI as needed while dragging. This method should 515 * only be called from the Swing event thread. 516 * 517 * @param dragIcon 518 * the component being dragged 519 * @param x 520 * the current mouse x coordinate 521 * @param y 522 * the current mouse y coordinate 523 */ 524 public void drawDraggedComponent(Component dragIcon, int x, int y) { 525 Dimension size = dragIcon.getPreferredSize(); 526 treePanel.paintImmediately(previousDragXLocation, previousDragYLocation, size.width, size.height); 527 this.getLayeredPane().setLayer(dragIcon, 400); 528 SwingUtilities.paintComponent(treePanel.getGraphics(), dragIcon, treePanel, x, y, size.width, size.height); 529 previousDragXLocation = x; 530 previousDragYLocation = y; 531 } 532 533 /** 534 * A window adapter used to detect when the main JMeter frame is being 535 * closed. 536 */ 537 private static class WindowHappenings extends WindowAdapter { 538 /** 539 * Called when the main JMeter frame is being closed. Sends a 540 * notification so that JMeter can react appropriately. 541 * 542 * @param event 543 * the WindowEvent to handle 544 */ 545 public void windowClosing(WindowEvent event) { 546 ActionRouter.getInstance().actionPerformed(new ActionEvent(this, event.getID(), ActionNames.EXIT)); 547 } 548 } 549 }