package org.geogebra.desktop.main; import java.awt.Component; import java.awt.KeyEventDispatcher; import java.awt.event.KeyEvent; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.TreeSet; import javax.swing.JRootPane; import javax.swing.JTable; import javax.swing.text.JTextComponent; import org.geogebra.common.gui.inputfield.AutoCompleteTextField; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.main.App; import org.geogebra.common.main.GlobalKeyDispatcher; import org.geogebra.common.main.GuiManagerInterface; import org.geogebra.common.main.KeyCodes; import org.geogebra.common.util.FileExtensions; import org.geogebra.desktop.euclidian.EuclidianViewD; import org.geogebra.desktop.gui.GuiManagerD; import org.geogebra.desktop.gui.app.GeoGebraFrame; import org.geogebra.desktop.gui.app.MyFileFilter; import org.geogebra.desktop.gui.inputbar.AlgebraInputD; import org.geogebra.desktop.gui.layout.LayoutD; import org.geogebra.desktop.gui.menubar.GeoGebraMenuBar; import org.geogebra.desktop.gui.util.OOMLConverter; import org.geogebra.desktop.util.UtilD; /** * Handles global keys like ESC, DELETE, and function keys. * * @author Markus Hohenwarter */ public class GlobalKeyDispatcherD extends GlobalKeyDispatcher implements KeyEventDispatcher { /** * @param app * application */ public GlobalKeyDispatcherD(AppD app) { super(app); } /** * This method is called by the current KeyboardFocusManager before they are * dispatched to their targets, allowing it to handle the key event and * consume it. */ @Override public boolean dispatchKeyEvent(KeyEvent event) { // ignore key events coming from text components (i.e. text fields and // text areas) // or key events coming from popups (source class = JRootPane) if (event.isConsumed() || event.getSource() instanceof JTextComponent || event.getSource() instanceof JRootPane) { return false; } boolean consumed = false; switch (event.getID()) { default: // do nothing break; case KeyEvent.KEY_PRESSED: consumed = handleKeyPressed(event); break; case KeyEvent.KEY_TYPED: consumed = handleKeyTyped(event); break; case KeyEvent.KEY_RELEASED: setNewWindowAllowed(true); break; } if (consumed) { event.consume(); } return consumed; } /** * The "key pressed" event is generated when a key is pushed down. * * @param event * event * @return if key was consumed */ protected boolean handleKeyPressed(KeyEvent event) { // GENERAL KEYS: // handle ESC, function keys, zooming with Ctrl +, Ctrl -, etc. if (handleGeneralKeys(event)) { return true; } // SELECTED GEOS: // handle function keys, arrow keys, +/- keys for selected geos, etc. if (handleSelectedGeosKeys(event, selection.getSelectedGeos())) { return true; } return false; } /** * "Key typed" events are higher-level and generally do not depend on the * platform or keyboard layout. They are generated when a Unicode character * is entered, and are the preferred way to find out about character input. */ private boolean handleKeyTyped(KeyEvent event) { // ignore key events coming from tables like the spreadsheet to // allow start editing if (event.getSource() instanceof JTable) { return false; } char ch = event.getKeyChar(); if (!event.isMetaDown() && !event.isAltDown() && !event.isControlDown()) { return renameStarted(ch); } return false; } /** * Handles key event by disassembling it into primitive types and handling * it using the mothod from common * * @param event * event * @return whether key was consumed */ public boolean handleGeneralKeys(KeyEvent event) { // use event.isAltDown rather than AppD.isControlDown(event) // as we need to distinguish <AltGr>2 and <Ctrl>2 // #2390 #908 return handleGeneralKeys(KeyCodes.translateJavacode(event.getKeyCode()), event.isShiftDown(), AppD.isControlDown(event), event.isAltDown(), event.getSource() instanceof JTable, event.getSource() instanceof EuclidianViewD); } private boolean handleSelectedGeosKeys(KeyEvent event, ArrayList<GeoElement> geos) { // use event.isAltDown rather than AppD.isAltDown(event) // as Ctrl-Arrow on OSX does something special // so we actually want to use Alt return handleSelectedGeosKeys( KeyCodes.translateJavacode(event.getKeyCode()), geos, event.isShiftDown(), AppD.isControlDown(event), event.isAltDown(), event.getSource() instanceof JTable); } @Override protected boolean handleEnter() { if (super.handleEnter()) { return true; } if (((AppD) app).isUsingFullGui() && ((GuiManagerD) app.getGuiManager()).noMenusOpen()) { if (app.showAlgebraInput() && !((GuiManagerD) app.getGuiManager()) .getAlgebraInput().hasFocus()) { // focus this frame (needed for external view windows) if (!app.isApplet() && ((AppD) app).getFrame() != null) { ((AppD) app).getFrame().toFront(); } ((GuiManagerD) app.getGuiManager()).getAlgebraInput() .requestFocus(); return true; } } return false; } @Override public boolean handleTab(boolean isControlDown, boolean isShiftDown, boolean cycle) { app.getActiveEuclidianView().closeDropdowns(); if (isControlDown && app.isUsingFullGui()) { GuiManagerInterface gui = app.getGuiManager(); ((LayoutD) gui.getLayout()).getDockManager() .moveFocus(!isShiftDown); return true; } boolean useTab = app.getActiveEuclidianView().hasFocus() || ((GuiManagerD) app.getGuiManager()).getAlgebraView() .hasFocus(); // make sure TAB works in Input Boxes but also in Spreadsheet, Input Bar Component owner = ((AppD) app).getFrame().getFocusOwner(); if (owner instanceof AutoCompleteTextField && ((AutoCompleteTextField) owner).usedForInputBox()) { useTab = true; } if (useTab) { super.handleTab(isControlDown, isShiftDown, cycle); return true; } return false; } @Override protected void handleCopyCut(boolean cut) { if (!(((GuiManagerD) app.getGuiManager()).getSpreadsheetView() .hasFocus()) && !(((AlgebraInputD) ((GuiManagerD) app.getGuiManager()) .getAlgebraInput()).getTextField().hasFocus())) { super.handleCopyCut(cut); } } @Override protected void handleCtrlV() { if (!(((GuiManagerD) app.getGuiManager()).getSpreadsheetView() .hasFocus()) && !(((AlgebraInputD) ((GuiManagerD) app.getGuiManager()) .getAlgebraInput()).getTextField().hasFocus())) { super.handleCtrlV(); tryPasteEquation(); } } protected void tryPasteEquation() { String html = ((GuiManagerD) app.getGuiManager()) .getStringFromClipboard(); if (html != null && html.indexOf("<m:oMath") > 0) { int blockBegin = html.indexOf("<m:oMathPara>"); int blockEnd; if (blockBegin == -1) { blockBegin = html.indexOf("<m:oMath>"); blockEnd = html.indexOf("</m:oMath>") + 10; } else { blockEnd = html.indexOf("</m:oMathPara>") + 14; } String mathml = OOMLConverter .oomlToMathml(html.substring(blockBegin, blockEnd) .replace('\n', ' ').replace('\r', ' ')); app.getGgbApi().evalCommand(mathml); } } @Override protected boolean handleCtrlShiftN(boolean isAltDown) { ArrayList<GeoGebraFrame> ggbInstances = GeoGebraFrame.getInstances(); int size = ggbInstances.size(); if (size == 1) { // load next file in folder // ask if OK to discard current file if (((AppD) app).isSaved() || ((AppD) app).saveCurrentFile()) { MyFileFilter fileFilter = new MyFileFilter(); fileFilter.addExtension(FileExtensions.GEOGEBRA); File[] options = ((AppD) app).getCurrentPath() .listFiles(fileFilter); if (options == null) { return false; } // no current file, just load the first file in the // folder if (((AppD) app).getCurrentFile() == null) { if (options.length > 0) { ((GuiManagerD) app.getGuiManager()).loadFile(options[0], false); return true; } return false; } TreeSet<File> sortedSet = new TreeSet<File>( UtilD.getFileComparator()); for (int i = 0; i < options.length; i++) { if (options[i].isFile()) { sortedSet.add(options[i]); } } String currentFile = ((AppD) app).getCurrentFile().getName(); Iterator<File> iterator = sortedSet.iterator(); File fileToLoad = null; while (iterator.hasNext() && fileToLoad == null) { if (iterator.next().getName().equals(currentFile)) { // check if we're at the end if (iterator.hasNext()) { fileToLoad = iterator.next(); } else { fileToLoad = options[0]; } } } ((GuiManagerD) app.getGuiManager()).loadFile(fileToLoad, false); return true; } for (int i = 0; i < size; i++) { GeoGebraFrame ggb = ggbInstances.get(i); AppD application = ggb.getApplication(); if (app == application) { int n = isAltDown ? ((i - 1 + size) % size) : ((i + 1) % size); ggb = ggbInstances.get(n); // next/last // instance ggb.toFront(); ggb.requestFocus(); break; // break from if loop } } return true; } return false; } @Override protected void copyDefinitionsToInputBarAsList(ArrayList<GeoElement> geos) { JTextComponent textComponent = ((AlgebraInputD) ((GuiManagerD) app .getGuiManager()).getAlgebraInput()).getTextField(); StringBuilder sb = new StringBuilder(); sb.append('{'); Iterator<GeoElement> it = geos.iterator(); while (it.hasNext()) { sb.append(it.next().getFormulaString(StringTemplate.defaultTemplate, false)); if (it.hasNext()) { sb.append(','); } } sb.append('}'); textComponent.setText(sb.toString()); } @Override protected void createNewWindow() { // no wait cursor needed here, that's taken care of before we call this if (app instanceof AppD) { ((AppD) app).createNewWindow(); } } @Override protected void showPrintPreview(App app2) { GeoGebraMenuBar.showPrintPreview((AppD) app); } }