1 /* 2 * $Id$ 3 * 4 * Copyright (c) 2003, 2009, Oracle and/or its affiliates. All rights reserved. 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * This code is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU General Public License version 2 only, as 9 * published by the Free Software Foundation. Oracle designates this 10 * particular file as subject to the "Classpath" exception as provided 11 * by Oracle in the LICENSE file that accompanied this code. 12 * 13 * This code is distributed in the hope that it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16 * version 2 for more details (a copy is included in the LICENSE file that 17 * accompanied this code). 18 * 19 * You should have received a copy of the GNU General Public License version 20 * 2 along with this work; if not, write to the Free Software Foundation, 21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 22 * 23 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 24 * or visit www.oracle.com if you need additional information or have any 25 * questions. 26 */ 27 package com.sun.javatest.tool; 28 29 import java.awt.*; 30 import java.awt.event.ActionEvent; 31 import java.awt.event.WindowAdapter; 32 import java.awt.event.WindowEvent; 33 import java.beans.PropertyChangeEvent; 34 import java.beans.PropertyChangeListener; 35 import java.io.*; 36 import java.nio.charset.StandardCharsets; 37 import java.util.Iterator; 38 import java.util.Set; 39 import java.util.Vector; 40 import java.beans.PropertyChangeListener; 41 import javax.accessibility.AccessibleContext; 42 import javax.swing.AbstractAction; 43 import javax.swing.Action; 44 import javax.swing.ActionMap; 45 import javax.swing.BorderFactory; 46 import javax.swing.InputMap; 47 import javax.swing.JButton; 48 import javax.swing.JCheckBoxMenuItem; 49 import javax.swing.JComponent; 50 import javax.swing.JDialog; 51 import javax.swing.JEditorPane; 52 import javax.swing.JFrame; 53 import javax.swing.JLabel; 54 import javax.swing.JList; 55 import javax.swing.JMenu; 56 import javax.swing.JMenuBar; 57 import javax.swing.JPanel; 58 import javax.swing.JRootPane; 59 import javax.swing.JScrollBar; 60 import javax.swing.JTextField; 61 import javax.swing.JTree; 62 import javax.swing.KeyStroke; 63 import javax.swing.SwingUtilities; 64 import javax.swing.event.ChangeEvent; 65 import javax.swing.event.ChangeListener; 66 import javax.swing.event.MenuEvent; 67 import javax.swing.event.MenuListener; 68 import javax.swing.text.Document; 69 import javax.swing.text.JTextComponent; 70 import com.sun.javatest.tool.jthelp.JHelpContentViewer; 71 72 class FocusMonitor 73 { 74 public static FocusMonitor access() { 75 if (focusMonitor == null) 76 focusMonitor = new FocusMonitor(); 77 78 return focusMonitor; 79 } 80 81 public void setOptions(String[] opts) { 82 for (int i = 0; i < opts.length; i++) { 83 if (opts[i].equals("-open")) 84 setVisible(true); 85 else if (opts[i].equals("-bg")) 86 setHighlightEnabled(true); 87 else 88 System.err.println("Warning: bad option for FocusMonitor: " + opts[i]); 89 } 90 } 91 92 public void setActivateKey(String key) { 93 activateKey = KeyStroke.getKeyStroke(key); 94 } 95 96 public void setReportKey(String key) { 97 reportKey = KeyStroke.getKeyStroke(key); 98 } 99 100 public void setReportFile(String file) { 101 reportFile = file; 102 } 103 104 public void monitor(Component c) { 105 if (c == null 106 || (frame != null && (frame == c || frame.isAncestorOf(c)))) 107 return; 108 109 if (activateKey != null || reportKey != null) { 110 Window w = (Window) (c instanceof Window ? c 111 : SwingUtilities.getAncestorOfClass(Window.class, c)); 112 if (w == null) 113 return; 114 115 JRootPane root; 116 if (w instanceof JFrame) 117 root = ((JFrame) w).getRootPane(); 118 else if (w instanceof JDialog) 119 root = ((JDialog) w).getRootPane(); 120 else 121 return; 122 123 if (root == null) 124 return; 125 126 InputMap inputMap = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 127 inputMap.put(activateKey, "focusMonitor.activate"); 128 inputMap.put(reportKey, "focusMonitor.report"); 129 130 ActionMap actionMap = root.getActionMap(); 131 actionMap.put("focusMonitor.activate", activateAction); 132 actionMap.put("focusMonitor.report", reportAction); 133 } 134 } 135 136 public void report() { 137 try { 138 Writer out; 139 if (reportFile == null) { 140 out = new OutputStreamWriter(System.out) { 141 public void close() throws IOException { 142 flush(); // don't close System.out 143 } 144 }; 145 } 146 else 147 out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile, true), StandardCharsets.UTF_8)); 148 149 out.write("---------------------------------------"); 150 out.write(NEWLINE); 151 report(out); 152 out.close(); 153 } 154 catch (IOException e) { 155 System.err.println(e); 156 } 157 } 158 159 public void setVisible(boolean b) { 160 if (b) { 161 if (frame == null) 162 initGUI(); 163 KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 164 focusMonitor.monitor(fm.getFocusOwner()); 165 frame.setVisible(true); 166 focusMonitor.update(); 167 } 168 else if (frame != null) 169 frame.setVisible(false); 170 } 171 172 private FocusMonitor() { 173 KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 174 fm.addPropertyChangeListener(new PropertyChangeListener() { 175 public void propertyChange(PropertyChangeEvent e) { 176 if (e.getPropertyName().equals("focusOwner")) 177 update(); 178 } 179 }); 180 } 181 182 private void deactivate() { 183 frame.setVisible(false); 184 frame.dispose(); 185 frame = null; 186 } 187 188 private void update() { 189 if (frame == null || !frame.isVisible()) 190 return; 191 192 KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 193 Component c = fm.getFocusOwner(); 194 195 if (c == null || frame == c || frame.isAncestorOf(c)) { 196 prevFocusPanel.setEnabled(false); 197 currFocusPanel.setEnabled(false); 198 nextFocusPanel.setEnabled(false); 199 } 200 else { 201 if (highlighting) { 202 setHighlight(currentComponent, false); 203 setHighlight(c, true); 204 } 205 206 currentComponent = c; 207 208 prevFocusPanel.setComponent(getPreviousFocus(c)); 209 currFocusPanel.setComponent(c); 210 nextFocusPanel.setComponent(getNextFocus(c)); 211 212 Window w = fm.getFocusedWindow(); 213 while (w != null && !(w instanceof Frame)) 214 w = w.getOwner(); 215 216 String title = "Focus Monitor"; 217 if (w instanceof Frame) 218 title += " - " + ((Frame)w).getTitle(); 219 frame.setTitle(title); 220 } 221 } 222 223 private void setHighlightEnabled(boolean b) { 224 if (b != highlighting) { 225 highlighting = b; 226 KeyboardFocusManager fm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); 227 Component c = fm.getFocusOwner(); 228 if (frame == null || !frame.isAncestorOf(c)) 229 setHighlight(c, highlighting); 230 } 231 } 232 233 private void setHighlight(Component c, boolean b) { 234 if (c == null) 235 return; 236 if (b) { 237 savedOpaque = c.isOpaque(); 238 savedBackground = c.getBackground(); 239 if (c instanceof JComponent) 240 ((JComponent) c).setOpaque(true); 241 c.setBackground(HILITE_COLOR); 242 } 243 else { 244 if (c instanceof JComponent) 245 ((JComponent) c).setOpaque(savedOpaque); 246 c.setBackground(savedBackground); 247 } 248 } 249 250 private void report(Writer out) throws IOException { 251 if (currFocusPanel == null) { 252 System.err.println("focus monitor not open: no component selected"); 253 return; 254 } 255 256 currFocusPanel.write(out); 257 } 258 259 private void initGUI() { 260 JMenuBar menuBar = new JMenuBar(); 261 JMenu viewMenu = new JMenu("View"); 262 final JCheckBoxMenuItem showBackgroundMenuItem = new JCheckBoxMenuItem("background"); 263 viewMenu.addMenuListener(new MenuListener() { 264 public void menuSelected(MenuEvent e) { 265 showBackgroundMenuItem.setSelected(highlighting); 266 } 267 public void menuDeselected(MenuEvent e) { } 268 public void menuCanceled(MenuEvent e) { } 269 }); 270 showBackgroundMenuItem.addChangeListener(new ChangeListener() { 271 public void stateChanged(ChangeEvent e) { 272 setHighlightEnabled(showBackgroundMenuItem.isSelected()); 273 } 274 }); 275 viewMenu.add(showBackgroundMenuItem); 276 menuBar.add(viewMenu); 277 278 prevFocusPanel = new SummaryPanel(); 279 prevFocusPanel.setBorder(BorderFactory.createTitledBorder("previous focus")); 280 281 currFocusPanel = new DetailPanel(); 282 currFocusPanel.setBorder(BorderFactory.createTitledBorder("current focus")); 283 284 nextFocusPanel = new SummaryPanel(); 285 nextFocusPanel.setBorder(BorderFactory.createTitledBorder("next focus")); 286 287 JPanel main = new JPanel(new GridBagLayout()); 288 GridBagConstraints c = new GridBagConstraints(); 289 c.fill = GridBagConstraints.BOTH; 290 c.gridwidth = GridBagConstraints.REMAINDER; 291 c.weightx = 1; 292 main.add(prevFocusPanel, c); 293 c.insets.top = 10; 294 main.add(currFocusPanel, c); 295 main.add(nextFocusPanel, c); 296 297 JFrame f = new JFrame("Focus Monitor"); 298 f.setJMenuBar(menuBar); 299 f.setContentPane(main); 300 f.pack(); 301 f.addWindowListener(new WindowAdapter() { 302 public void windowClosed(WindowEvent e) { 303 deactivate(); 304 } 305 }); 306 307 frame = f; 308 } 309 310 private Component getNextFocus(Component c) { 311 if (c == null) 312 return null; 313 314 Container rootAncestor = c.getFocusCycleRootAncestor(); 315 Component comp = c; 316 while (rootAncestor != null && 317 !(rootAncestor.isShowing() && 318 rootAncestor.isFocusable() && 319 rootAncestor.isEnabled())) 320 { 321 comp = rootAncestor; 322 rootAncestor = comp.getFocusCycleRootAncestor(); 323 } 324 325 if (rootAncestor == null) 326 return null; 327 328 FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); 329 Component toFocus = policy.getComponentAfter(rootAncestor, comp); 330 if (toFocus == null) 331 toFocus = policy.getDefaultComponent(rootAncestor); 332 333 return toFocus; 334 } 335 336 private Component getPreviousFocus(Component c) { 337 if (c == null) 338 return null; 339 340 Container rootAncestor = c.getFocusCycleRootAncestor(); 341 Component comp = c; 342 while (rootAncestor != null && 343 !(rootAncestor.isShowing() && 344 rootAncestor.isFocusable() && 345 rootAncestor.isEnabled())) 346 { 347 comp = rootAncestor; 348 rootAncestor = comp.getFocusCycleRootAncestor(); 349 } 350 351 if (rootAncestor == null) 352 return null; 353 354 FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); 355 Component toFocus = policy.getComponentBefore(rootAncestor, comp); 356 if (toFocus == null) 357 toFocus = policy.getDefaultComponent(rootAncestor); 358 359 return toFocus; 360 } 361 362 private Component getUpFocus(Component c) { 363 if (c == null) 364 return null; 365 366 Container rootAncestor; 367 for (rootAncestor = c.getFocusCycleRootAncestor(); 368 rootAncestor != null && !(rootAncestor.isShowing() && 369 rootAncestor.isFocusable() && 370 rootAncestor.isEnabled()); 371 rootAncestor = rootAncestor.getFocusCycleRootAncestor()) { 372 } 373 374 if (rootAncestor != null) 375 return rootAncestor; 376 377 Container window = 378 (c instanceof Container) ? ((Container)c) : c.getParent(); 379 while (window != null && !(window instanceof Window)) { 380 window = window.getParent(); 381 } 382 if (window == null) 383 return null; 384 385 Component toFocus = window.getFocusTraversalPolicy().getDefaultComponent(window); 386 return toFocus; 387 } 388 389 private static String getKeysString(Component c, int mode) { 390 if (c == null) 391 return null; 392 393 Set<AWTKeyStroke> s = c.getFocusTraversalKeys(mode); 394 StringBuffer sb = new StringBuffer(); 395 for (Iterator<AWTKeyStroke> iter = s.iterator(); iter.hasNext(); ) { 396 if (sb.length() > 0) 397 sb.append(", "); 398 sb.append(iter.next()); 399 } 400 if (!c.areFocusTraversalKeysSet(mode)) 401 sb.append(" (inherited)"); 402 return sb.toString(); 403 } 404 405 private static String getPath(Component c) { 406 StringBuffer sb = new StringBuffer(); 407 appendPath(sb, c); 408 return sb.toString(); 409 } 410 411 private static void appendPath(StringBuffer sb, Component c) { 412 Container p = c.getParent(); 413 if (p != null) 414 appendPath(sb, p); 415 sb.append('/'); 416 String name = c.getName(); 417 if (name == null || name.length() == 0) { 418 if (p == null) // special case, root component, no name 419 sb.append("(Root component)" ); 420 else 421 for (int i = 0; i < p.getComponentCount(); i++) { 422 if (p.getComponent(i) == c) { 423 sb.append(i); 424 break; 425 } 426 } // for 427 } 428 else 429 sb.append(name); 430 } 431 432 private Action activateAction = new AbstractAction() { 433 public void actionPerformed(ActionEvent e) { 434 setVisible(true); 435 } 436 }; 437 438 private Action reportAction = new AbstractAction() { 439 public void actionPerformed(ActionEvent e) { 440 report(); 441 } 442 }; 443 444 445 private KeyStroke activateKey; 446 private KeyStroke reportKey; 447 private String reportFile; 448 private JFrame frame; 449 private SummaryPanel prevFocusPanel; 450 private DetailPanel currFocusPanel; 451 private SummaryPanel nextFocusPanel; 452 453 private Component currentComponent; 454 private boolean highlighting; 455 private boolean savedOpaque; 456 private Color savedBackground; 457 458 private static FocusMonitor focusMonitor; 459 460 private static final Color STD_COLOR = Color.black; 461 private static final Color ERR_COLOR = Color.red; 462 private static final Color WARN_COLOR = Color.yellow; 463 private static final Color HILITE_COLOR = new Color(255, 255, 200); 464 private static final String NEWLINE = System.getProperty("line.separator"); 465 466 private class Entry { 467 Entry(String name) { 468 label = new JLabel(name + ": "); 469 label.setName(name); 470 field = new JTextField(60); 471 field.setName(name + ".value"); 472 field.setEditable(false); 473 label.setLabelFor(field); 474 } 475 476 void setParentEnabled(boolean parentEnabled) { 477 label.setEnabled(parentEnabled && enabled); 478 field.setEnabled(parentEnabled && enabled); 479 } 480 481 void setEnabled(boolean enabled) { 482 this.enabled = enabled; 483 label.setEnabled(enabled); 484 field.setEnabled(enabled); 485 field.setText(""); 486 } 487 488 void setText(String s) { 489 setEnabled(true); 490 field.setText(s); 491 field.setForeground(STD_COLOR); 492 } 493 494 void setText(String s, boolean ok) { 495 setText(s, ok, STD_COLOR, ERR_COLOR); 496 } 497 498 void setText(String s, boolean ok, Color okColor, Color notOKColor) { 499 setEnabled(true); 500 field.setText(s); 501 field.setForeground(ok ? okColor : notOKColor); 502 } 503 504 void setText(String s, String err) { 505 if (s == null || s.length() == 0) 506 setText(err, false); 507 else 508 setText(s, true); 509 } 510 511 void setText(String s, String err, Color okColor, Color notOKColor) { 512 if (s == null || s.length() == 0) 513 setText(err, false, okColor, notOKColor); 514 else 515 setText(s, true, okColor, notOKColor); 516 } 517 518 void write(Writer out) throws IOException { 519 if (field.isEnabled()) { 520 out.write(field.getForeground() == ERR_COLOR ? "** " : " "); 521 out.write(label.getText()); 522 out.write(field.getText()); 523 out.write(NEWLINE); 524 } 525 } 526 527 JLabel label; 528 JTextField field; 529 boolean enabled; 530 } 531 532 private class SummaryPanel extends JPanel { 533 SummaryPanel() { 534 setName("summary"); 535 setLayout(new GridBagLayout()); 536 add(type = new Entry("type")); 537 add(name = new Entry("name")); 538 add(path = new Entry("path")); 539 } 540 541 void setComponent(Component c) { 542 if (c == null) { 543 setEnabled(false); 544 type.setEnabled(false); 545 name.setEnabled(false); 546 path.setEnabled(false); 547 } 548 else { 549 setEnabled(true); 550 type.setText(c.getClass().getName()); 551 name.setText(c.getName(), "no name", STD_COLOR, WARN_COLOR); 552 path.setText(getPath(c)); 553 } 554 } 555 556 void write(Writer out) throws IOException { 557 for (int i = 0; i <entries.size(); i++) { 558 Entry e = entries.elementAt(i); 559 e.write(out); 560 } 561 } 562 563 public void setEnabled(boolean enabled) { 564 super.setEnabled(enabled); 565 for (int i = 0; i <entries.size(); i++) { 566 Entry e = entries.elementAt(i); 567 e.setParentEnabled(enabled); 568 } 569 } 570 571 protected void add(Entry entry) { 572 GridBagConstraints lc = new GridBagConstraints(); 573 add(entry.label, lc); 574 575 GridBagConstraints fc = new GridBagConstraints(); 576 fc.fill = GridBagConstraints.HORIZONTAL; 577 fc.gridwidth = GridBagConstraints.REMAINDER; 578 fc.weightx = 1; 579 add(entry.field, fc); 580 581 entries.add(entry); 582 } 583 584 private Vector<Entry> entries = new Vector<>(); 585 private Entry name; 586 private Entry path; 587 private Entry type; 588 } 589 590 private class DetailPanel extends SummaryPanel { 591 DetailPanel() { 592 // is accessible 593 add(accName = new Entry("acc. name")); 594 add(accDesc = new Entry("acc. desc")); 595 add(toolTip = new Entry("tooltip")); 596 add(text = new Entry("text")); 597 // labelled by 598 add(labelFor = new Entry("label for")); 599 add(mnemonic = new Entry("mnemonic")); 600 add(fwdKeys = new Entry("> keys")); 601 add(bwdKeys = new Entry("< keys")); 602 add(upKeys = new Entry("^ keys")); 603 add(downKeys = new Entry("v keys")); 604 } 605 606 void setComponent(Component c) { 607 // summary info (type, name) 608 super.setComponent(c); 609 610 // accessible info (name, description) 611 AccessibleContext ac = (c == null ? null : c.getAccessibleContext()); 612 if (ac == null) { 613 accName.setText(null, "not accessible"); 614 accDesc.setText(null, "not accessible"); 615 } 616 else { 617 boolean sb = (c instanceof JScrollBar); 618 String an = ac.getAccessibleName(); 619 accName.setText((an != null ? an : "no accessible name"), (an != null || sb)); 620 String ad = ac.getAccessibleDescription(); 621 accDesc.setText((ad != null ? ad : "no accessible description"), (ad != null || sb)); 622 } 623 624 if (c != null && c instanceof JComponent) { 625 String ttText = ((JComponent)c).getToolTipText(); 626 boolean ttEmpty = (ttText == null || ttText.length() == 0); 627 boolean toolTipOK = !ttEmpty 628 || c instanceof JTree 629 || c instanceof JEditorPane 630 || c instanceof JHelpContentViewer 631 || c instanceof JList 632 || c instanceof JRootPane 633 || c instanceof JScrollBar 634 || (c instanceof JTextComponent && !((JTextComponent) c).isEditable()); 635 toolTip.setText((ttEmpty ? "no tooltip" : ttText), toolTipOK); 636 } 637 else 638 toolTip.setEnabled(false); 639 640 // what the text content might be 641 if (c instanceof JButton) 642 text.setText(((JButton) c).getText()); 643 else if (c instanceof JLabel) 644 text.setText(((JLabel) c).getText()); 645 else if (c instanceof JTextComponent) { 646 JTextComponent tc = (JTextComponent) c; 647 Document d = tc.getDocument(); 648 try { 649 text.setText(d.getText(0, Math.min(80, d.getLength()))); 650 } 651 catch (Exception e) { 652 text.setText(null, e.toString()); 653 } 654 } 655 else 656 text.setEnabled(false); 657 658 // what it might be a label for 659 if (c != null && c instanceof JLabel) 660 labelFor.setText(c.getClass().getName() + " " + c.getName()); 661 else 662 labelFor.setEnabled(false); 663 664 // what the mnemonic might be 665 if (c instanceof JLabel) { 666 JLabel l = (JLabel) c; 667 int mne = l.getDisplayedMnemonic(); 668 boolean mnemonicOK = (mne != 0 669 || l.getLabelFor() == null); 670 mnemonic.setText((mne == 0 ? "no mnemonic" : String.valueOf((char)mne)), mnemonicOK); 671 } 672 else if (c instanceof JButton) { 673 JButton b = (JButton) c; 674 int mne = b.getMnemonic(); 675 String cmd = b.getActionCommand(); 676 boolean mnemonicOK = (mne != 0 677 || (cmd != null && cmd.equals(UIFactory.CANCEL))); 678 mnemonic.setText(mne == 0 ? "no mnemonic" : String.valueOf((char)mne), mnemonicOK); 679 } 680 else 681 mnemonic.setEnabled(false); 682 683 // what the traversal keys are 684 fwdKeys.setText(getKeysString(c, KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); 685 bwdKeys.setText(getKeysString(c, KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)); 686 upKeys.setText(getKeysString(c, KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS)); 687 downKeys.setText(getKeysString(c, KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS)); 688 } 689 690 private Entry accName; 691 private Entry accDesc; 692 private Entry toolTip; 693 private Entry text; 694 private Entry labelFor; 695 private Entry mnemonic; 696 private Entry fwdKeys; 697 private Entry bwdKeys; 698 private Entry upKeys; 699 private Entry downKeys; 700 701 } 702 }