/* * Copyright 2003-2010 Tufts University Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package tufts.vue.action; import tufts.Util; import tufts.vue.VUE; import tufts.vue.DEBUG; import tufts.vue.VueResources; import tufts.vue.VueTool; import tufts.vue.VueAction; import tufts.vue.Actions; import tufts.vue.VueConstants; import tufts.vue.VueToolbarController; import tufts.vue.gui.GUI; import tufts.vue.gui.DockWindow; import java.awt.Event; import java.awt.event.*; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import javax.swing.Action; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.KeyStroke; import javax.swing.JScrollPane; /** * Produce a shortcuts window. * * @version $Revision: 1.20 $ / $Date: 2010-02-03 19:13:45 $ / $Author: mike $ * @author Scott Fraize */ public class ShortcutsAction extends tufts.vue.VueAction { private static DockWindow window; public ShortcutsAction() { super(VueResources.getString("shortcutactions.keyboardshortcuts")); } @Override public boolean isUserEnabled() { return true; } private boolean wasDebug; private JComponent content; /** display the shortcuts DockWindow (create it if needed) */ public void act() { if (window == null) window = GUI.createDockWindow(VUE.getName() + " " + VueResources.getString("shortcutactions.shortcutkeys"), VueResources.getString("dockWindow.VUEShort-CutKeys.helpText")); if (content == null || (wasDebug != DEBUG.Enabled)) { wasDebug = DEBUG.Enabled; if (DEBUG.Enabled) tufts.vue.VueAction.checkForDupeStrokes(); content = buildShortcutsComponent(); window.setContent(content); } window.pack(); // fit to widest line GUI.makeVisibleOnScreen(content); // window.setVisible(true); window.raise(); } private static String keyCodeChar(int keyCode) { return keyCodeChar(keyCode, false); } private static String keyCodeChar(int keyCode, boolean lowerCase) { if (lowerCase && keyCode >= KeyEvent.VK_A && keyCode <= KeyEvent.VK_Z) { return String.valueOf((char)keyCode).toLowerCase(); } if (keyCode == KeyEvent.VK_OPEN_BRACKET) return "["; else if (keyCode == KeyEvent.VK_CLOSE_BRACKET) return "]"; else if (keyCode == 0) return ""; else if (Util.isMacLeopard()) { if (keyCode == KeyEvent.VK_SPACE) // override the little "u" glyph return "Space"; else if (keyCode == KeyEvent.VK_BACK_QUOTE) // override the hard to see '`' return "Back Quote"; } return KeyEvent.getKeyText(keyCode); } static StringBuffer html; private static int BOLD = 1; private static int ITAL = 2; private static int RIGHT = 4; private static int CENTER = 8; private static int SPAN2 = 16; private static int SPAN3 = 32; private static int NO_EAST_GAP = 64; private static int NO_WEST_GAP = 128; private static int NO_GAP = NO_EAST_GAP + NO_WEST_GAP; private static final String NBSP = " "; //private static final String NBSP = "X"; private static final String BIG_NBSP = " " + NBSP + " " + NBSP + " " + NBSP //+ " " + NBSP // these if tool lines are bold on Leopard //+ " " + NBSP //+ " " + NBSP ; ; private static void add(int bits, Object o, String... attr) { html.append("<td"); if ((bits & SPAN2) != 0) html.append(" colspan=2"); else if ((bits & SPAN3) != 0) html.append(" colspan=3"); if ((bits & CENTER) != 0) // CENTER takes priority over RIGHT html.append(" align=center"); else if ((bits & RIGHT) != 0) html.append(" align=right"); if (attr != null) { for (String s : attr) { html.append(' '); html.append(s); } } html.append('>'); if ((bits & NO_WEST_GAP) == 0) html.append(NBSP); if ((bits & BOLD) != 0) html.append("<b>"); if ((bits & ITAL) != 0) html.append("<i>"); html.append(o == null ? (DEBUG.Enabled?"null":"") : o.toString()); // if ((bits & BOLD) != 0) html.append("</b>"); // implied // if ((bits & ITAL) != 0) html.append("</i>"); // implied if ((bits & NO_EAST_GAP) == 0) html.append(NBSP); //MK : enable for file output // html.append("</td>"); // implied } private static void add(Object o) { add(0, o); } private static void addRow(int row) { addRow(row, false); } private static void addRow(int row, boolean debug) { // MK - user this comment rest for file output html.append("\n<tr"); if (debug) { html.append(" bgcolor=#FF0000"); } else if (row % 2 == 0) { if (Util.isMacPlatform()) { if (DEBUG.Enabled) html.append(" bgcolor=#DDDDFF"); else html.append(" bgcolor=#EEEEEE"); // VUE-1036 } else { html.append(" bgcolor=#FFFFFF"); } } else { html.append('>'); } } private static void addTable(String... attr) { html.append('\n'); html.append("<table"); html.append(" cellpadding=2"); if (DEBUG.Enabled) html.append(" border=1"); else html.append(" border=0"); //html.append(" rules=rows bordercolor=red"); // Java does not support these if (attr != null) { for (String s : attr) { html.append(' '); html.append(s); } } html.append('>'); html.append('\n'); } private static final int SHIFT = Event.SHIFT_MASK + InputEvent.SHIFT_DOWN_MASK; private static final int CTRL = Event.CTRL_MASK + InputEvent.CTRL_DOWN_MASK; private static final int ALT = Event.ALT_MASK + InputEvent.ALT_DOWN_MASK; private static boolean hasOnlyShift(int mods) { return (mods & SHIFT) == (mods | SHIFT); } private static boolean hasOnlyCtrl(int mods) { return (mods & CTRL) == (mods | CTRL); } private static boolean hasOnlyAlt(int mods) { return (mods & ALT) == (mods | ALT); } private static boolean hasOnlyOne(int mods) { return hasOnlyShift(mods) || hasOnlyAlt(mods) || hasOnlyCtrl(mods); } private static final String TitleColor = "#C5C5C5"; // As per VUE-1036 static JComponent buildShortcutsComponent() { if (html == null) { if (DEBUG.Enabled) html = new StringBuffer(65536); else html = new StringBuffer(8192); } html.setLength(0); html.append("<html>"); addTable("width=100%"); // fill to wider width of actions below //addTable(); int row = 0; if (DEBUG.Enabled) { //============================================================================= // DEBUG TOOLS Title/Header Line //============================================================================= html.append("<tr bgcolor=#00FFFF>"); add(BOLD+ITAL, "TOOL ID"); add(BOLD+ITAL, "ShortCut"); add(BOLD+ITAL, "DownKey"); add(BOLD+ITAL, "Name"); add(BOLD+ITAL, VueTool.class); html.append("</tr>"); } else { //============================================================================= // Production TOOLS Title/Header Line //============================================================================= html.append("<tr bgcolor=" + TitleColor + ">"); add(BOLD, VueResources.getString("shortcutactions.tool")); add(BOLD+CENTER, VueResources.getString("shortcutactions.key")); add(BOLD+CENTER, VueResources.getString("shortcutactions.auick-key")); html.append("</tr>"); } for (VueTool t : VueTool.getTools()) { if (t.getShortcutKey() == 0) continue; final char downKey = (char) t.getActiveWhileDownKeyCode(); addRow(row++); if (DEBUG.Enabled) { //------------------------------------------------------- // DEBUG TOOLS //------------------------------------------------------- add(t.getID()); add(BOLD+CENTER, t.getShortcutKey()); add(BOLD+CENTER, keyCodeChar(downKey)); //add(BOLD+CENTER, KeyStroke.getKeyStroke((char)downKey)); add(BOLD, t.getToolName()); add(t.getClass().getName()); //MK - enable for file output //html.append("</tr>"); } else { //======================================================= // Production TOOLS //======================================================= add(t.getToolName()); add(CENTER, t.getShortcutKey()); if (downKey == 0) add(""); else add(CENTER, keyCodeChar(downKey, true)); //add(BOLD+CENTER, t.getShortcutKey(), "bgcolor=black color=white"); //MK - enable for fileoutput //html.append("</tr>"); } } html.append("</table><br>"); addTable(); if (DEBUG.Enabled) { //------------------------------------------------------- // DEBUG ACTION Title/Header Line //------------------------------------------------------- // html.append("</table><p>"); // addTable(0); html.append("<tr bgcolor=#00FFFF>"); add(BOLD+ITAL, "row"); add(BOLD+ITAL, "mod bits"); add(BOLD+ITAL, "mod text"); if (Util.isMacLeopard()) add(BOLD, "<font size=-3>Leopard<br> Glyphs"); add(BOLD+ITAL+CENTER, "Key"); add(BOLD+ITAL, "ACTION NAME"); add(BOLD+ITAL, KeyStroke.class); add(BOLD+ITAL, VueAction.class); html.append("</tr>"); } else { //======================================================= // Production ACTION Title/Header Line //======================================================= html.append("<tr bgcolor=" + TitleColor + ">"); if (Util.isMacLeopard()) add(NO_GAP, " " + NBSP + " " + NBSP + " " + NBSP + " "); add(BOLD, VueResources.getString("shortcutactions.action")); if (Util.isMacLeopard()) add(BOLD+SPAN3+NO_WEST_GAP, VueResources.getString("shortcutactions.macshortcut")); else add(BOLD+SPAN2, VueResources.getString("shortcutactions.shortcutkey")); html.append("</tr>"); } //============================================================================= // Find all short-cuts //============================================================================= if (DEBUG.Enabled) { //------------------------------------------------------------------ // // Find all javax.swing.AbstractButton's in the AWT hierarchy (which // includes allJMenuItem's), and record any accelerator found on their // action. // // todo: don't just check action: check the actual accelerator keys on the // JMenuItems (could someday even check all input maps?) // //------------------------------------------------------------------ new tufts.vue.EventRaiser<AbstractButton> (ShortcutsAction.class, AbstractButton.class, tufts.vue.EventRaiser.INCLUDE_MENUS) { int row = 0; public void dispatch(AbstractButton b) { if (reportKeyStroke(b.getAction(), row)) row++; } } .raiseStartingAt(VUE.getRootWindow()); addRow(0, true); // todo: keep map of all found here, and below, if NOT found in // that hash map, debug-mode report the "dangling" vue-action // (not found in the UI) } //------------------------------------------------------------------ // Find accelerator keys set in any instaniated VueActions // This is the meat of everything we do here. //------------------------------------------------------------------ row = 0; for (VueAction a : VueAction.getAllActions()) if (reportKeyStroke(a, row)) row++; //============================================================================= // Load up the produced HTML text //============================================================================= final javax.swing.JLabel t = new javax.swing.JLabel(); // if (DEBUG.Enabled) // t.setFont(VueConstants.LargeFont); // else t.setFont(VueConstants.MediumFont); if (DEBUG.Enabled) Log.debug("HTML size: " + ShortcutsAction.html.length()); t.setText(html.toString()); /* Since this list can't be cut and pasted from the screen added this to dump it as a file as necessary so we can keep a list on the wiki FileOutputStream fos; try { fos = new FileOutputStream("/Users/mkorcy01/Desktop/mike2.html"); try { fos.write(html.toString().getBytes()); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } */ t.setOpaque(false); //t.setFocusable(false); return new JScrollPane(t, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); } /** if a keystroke is found on the action, add another HTML table-data line for it */ private static boolean reportKeyStroke(Action a, int row) { if (a == null) return false; KeyStroke k = (KeyStroke) a.getValue(Action.ACCELERATOR_KEY); if (k == null && !(DEBUG.Enabled && DEBUG.WORK)) return false; String modNames = ""; if (k != null) { modNames = KeyEvent.getKeyModifiersText(k.getModifiers()); //if (modNames != null && modNames.length() > 0) //modNames += " "; } if (DEBUG.Enabled) { if (tufts.vue.VueAction.isDupeStrokeAction(a)) addRow(row, true); else addRow(row, false); } else { addRow(row); } row++; final int mods = k == null ? 0 : k.getModifiers(); int goRight = hasOnlyOne(mods) ? RIGHT : 0; if (goRight != 0 && (mods & Actions.COMMAND) != 0) // not for the platform primary goRight = 0; if (DEBUG.Enabled) { //----------------------------------------------------------------------------- // DEBUG ACTIONS //----------------------------------------------------------------------------- add(RIGHT, row); if (k == null) { add(""); add(""); if (Util.isMacPlatform()) add(""); add(""); } else { add(RIGHT+BOLD, Integer.toBinaryString(mods)); if (Util.isMacLeopard()) { add(BOLD+goRight, get_MacOSX_Leopard_Modifier_Names(mods)); add(BOLD+goRight+(DEBUG.Enabled?0:CENTER), KeyEvent.getKeyModifiersText(mods)); } else { add(BOLD+goRight, KeyEvent.getKeyModifiersText(mods)); } add(BOLD+CENTER, keyCodeChar(k.getKeyCode())); } if (a instanceof VueAction) add(BOLD, ((VueAction)a).getPermanentActionName()); else add(BOLD+ITAL, (String) a.getValue(Action.NAME)); add(k == null ? "" : k); add(a.getClass().getName()); } else { //============================================================================= // Production ACTIONS //============================================================================= if (Util.isMacLeopard()) add(NO_GAP, ""); if (a instanceof VueAction) add(((VueAction)a).getPermanentActionName()); else add(ITAL, (String) a.getValue(Action.NAME)); if (Util.isMacLeopard()) { add(NO_GAP + goRight, get_MacOSX_Leopard_Modifier_Glyphs(mods)); add(NO_GAP, keyCodeChar(k.getKeyCode())); } else { add(goRight, KeyEvent.getKeyModifiersText(mods)); add(NO_WEST_GAP, keyCodeChar(k.getKeyCode())); } if (row == 1 && Util.isMacLeopard()) add(NO_GAP, BIG_NBSP); // for colspan 3 in header //============================================================================= } return true; } /** @return a standard, short and unqiue description of the given KeyStroke */ public static String getDescription(KeyStroke k) { if (Util.isMacLeopard()) { //return get_MacOSX_Leopard_Modifier_Names(k.getModifiers()) + "+" + keyCodeChar(k.getKeyCode()); // longest //return KeyEvent.getKeyModifiersText(k.getModifiers()) + "+" + keyCodeChar(k.getKeyCode()); // much shorter return get_MacOSX_Leopard_Modifier_Glyphs(k.getModifiers()) + keyCodeChar(k.getKeyCode()); // shortest } else { return KeyEvent.getKeyModifiersText(k.getModifiers()) + "+" + keyCodeChar(k.getKeyCode()); } } // The Mac OSX Leopard JVM impl changed KeyEvent.getKeyModifiersText(mods) to return the actual // special mac glyphs representing these keys. This replaces the old functionality // (swiped from the java source), in case we want to use it. private static String get_MacOSX_Leopard_Modifier_Names(int modifiers) { StringBuffer buf = new StringBuffer(); if ((modifiers & InputEvent.META_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.meta", "Meta")); //buf.append("Command"); buf.append("Apple"); buf.append("+"); } if ((modifiers & InputEvent.CTRL_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.control", "Ctrl")); buf.append("Ctrl"); buf.append("+"); } if ((modifiers & InputEvent.ALT_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.alt", "Alt")); buf.append("Alt"); buf.append("+"); } if ((modifiers & InputEvent.SHIFT_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.shift", "Shift")); buf.append("Shift"); buf.append("+"); } if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.altGraph", "Alt Graph")); buf.append("Alt Graph"); buf.append("+"); } if ((modifiers & InputEvent.BUTTON1_MASK) != 0) { //buf.append(Toolkit.getProperty("AWT.button1", "Button1")); buf.append("Button1"); buf.append("+"); } if (buf.length() > 0) { buf.setLength(buf.length()-1); // remove trailing '+' } return buf.toString(); } private static String get_MacOSX_Leopard_Modifier_Glyphs(int modifiers) { return KeyEvent.getKeyModifiersText(modifiers).replace('+', (char)0); } public static void main(String args[]) { VUE.init(args); // Ensure the tools are loaded to we can see their shortcuts: VueToolbarController.getController(); javax.swing.JFrame frame = new javax.swing.JFrame("vueParentWindow"); // Ensure that all the Actions are instantiated so we can see them: tufts.vue.Actions.Delete.toString(); // Let us see the actual menu bar: frame.setJMenuBar(new tufts.vue.gui.VueMenuBar()); frame.setVisible(true); // do this or we can't see the menu bar new ShortcutsAction().act(); // Log.info("creating..."); // DockWindow shortcuts = ShortcutsAction.createWindow(); // Log.info("showing..."); // shortcuts.pack(); // fit to HTML content // shortcuts.setVisible(true); if (args.length > 1) { System.out.println(ShortcutsAction.html); System.out.println("length=" + ShortcutsAction.html.length()); } } }