package org.geogebra.common.main;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.euclidian.EuclidianController;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon;
import org.geogebra.common.euclidian.draw.DrawDropDownList;
import org.geogebra.common.euclidian.draw.DrawInputBox;
import org.geogebra.common.kernel.ConstructionDefaults;
import org.geogebra.common.kernel.Kernel;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.Matrix.Coords;
import org.geogebra.common.kernel.algos.AlgoElement;
import org.geogebra.common.kernel.geos.Furniture;
import org.geogebra.common.kernel.geos.GeoAngle;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoInputBox;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.kernel.geos.PointProperties;
import org.geogebra.common.main.settings.EuclidianSettings;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.util.debug.Log;
/**
* Handles keyboard events. This class only dispatches
*/
public abstract class GlobalKeyDispatcher {
public GlobalKeyDispatcher(App app2) {
this.app = app2;
this.selection = app.getSelectionManager();
}
/**
* Handle Fx keys for input bar when geo is selected
*
* @param i
* x when Fx is pressed
* @param geo
* selected geo
*/
public void handleFunctionKeyForAlgebraInput(int fkey, GeoElement geo) {
if (!app.showAlgebraInput() || app.getGuiManager() == null) {
return;
}
switch (fkey) {
default:
// do nothing
break;
case 3: // F3 key: copy definition to input field
app.getGuiManager().setInputText(geo.getDefinitionForInputBar());
break;
case 4: // F4 key: copy value to input field
app.getGuiManager().replaceInputSelection(
" " + geo.getValueForInputBar() + " ");
break;
case 5: // F5 key: copy name to input field
app.getGuiManager().replaceInputSelection(
" " + geo.getLabel(StringTemplate.defaultTemplate) + " ");
break;
}
}
/** application */
protected final App app;
protected final SelectionManager selection;
private TreeSet<AlgoElement> tempSet;
/**
* @return temporary set of algos
*/
protected TreeSet<AlgoElement> getTempSet() {
if (tempSet == null) {
tempSet = new TreeSet<AlgoElement>();
}
return tempSet;
}
private Coords tempVec;
private boolean newWindowAllowed = true;
protected boolean renameStarted(char ch) {
GeoElement geo;
if (selection.selectedGeosSize() == 1) {
// selected geo
geo = selection.getSelectedGeos().get(0);
} else {
// last created geo
geo = app.getLastCreatedGeoElement();
}
// show RENAME dialog when a letter is typed
// or edit Textfield for any keypress
if ((Character.isLetter(ch)) || geo instanceof GeoInputBox) {
// open rename dialog
if (geo != null && geo.isRenameable()) {
if (geo instanceof GeoInputBox) {
DrawInputBox dt = (DrawInputBox) app
.getActiveEuclidianView().getDrawableFor(geo);
dt.setFocus(ch + "");
} else {
app.getDialogManager().showRenameDialog(geo, true,
Character.toString(ch), false);
}
return true;
}
}
return false;
}
private boolean handleUpDownArrowsForDropdown(ArrayList<GeoElement> geos,
boolean down, boolean canOpenDropDown) {
Log.debug("[KEYS] handleUpDownArrowsForDropdown");
if (geos.size() == 1 && geos.get(0).isGeoList()) {
DrawDropDownList dl = DrawDropDownList.asDrawable(app, geos.get(0));
if (dl == null || !((GeoList) geos.get(0)).drawAsComboBox()) {
return false;
}
if (canOpenDropDown && !dl.isOptionsVisible()) {
dl.toggleOptions();
} else {
dl.moveSelectorVertical(down);
}
return true;
}
return false;
}
private boolean handleLeftRightArrowsForDropdown(ArrayList<GeoElement> geos,
boolean left, boolean canOpenDropDown) {
Log.debug("[KEYS] handleLeftRightArrowsForDropdown");
if (geos.size() == 1 && geos.get(0).isGeoList()) {
DrawDropDownList dl = DrawDropDownList.asDrawable(app, geos.get(0));
if (canOpenDropDown && !dl.isOptionsVisible()) {
dl.toggleOptions();
}
if (dl.isMultiColumn()) {
if (dl.isOptionsVisible()) {
dl.moveSelectorHorizontal(left);
return true;
}
} else {
return handleUpDownArrowsForDropdown(geos, left,
canOpenDropDown);
}
}
return false;
}
/**
* Tries to move the given objects after pressing an arrow key on the
* keyboard.
*
* @param geos
* moved geos
* @param xdiff
* translation in x direction
* @param ydiff
* translation in y direction
* @param zdiff
* translation in z direction
*
* @return whether any object was moved
*/
protected boolean handleArrowKeyMovement(ArrayList<GeoElement> geos,
double xdiff, double ydiff, double zdiff) {
GeoElement geo = geos.get(0);
boolean allSliders = true;
for (int i = 0; i < geos.size(); i++) {
GeoElement geoi = geos.get(i);
if (!geoi.isGeoNumeric() || !geoi.isChangeable()) {
allSliders = false;
continue;
}
}
// don't move sliders, they will be handled later
if (allSliders) {
return false;
}
// set translation vector
if (tempVec == null)
{
tempVec = new Coords(4); // 4 coords for 3D
}
double xd = geo.getAnimationStep() * xdiff;
double yd = geo.getAnimationStep() * ydiff;
double zd = geo.getAnimationStep() * zdiff;
tempVec.setX(xd);
tempVec.setY(yd);
tempVec.setZ(zd);
// move objects
boolean moved = GeoElement.moveObjects(geos, tempVec, null, null,
app.getActiveEuclidianView());
// nothing moved
if (!moved) {
for (int i = 0; i < geos.size(); i++) {
geo = geos.get(i);
// toggle boolean value
if (geo.isChangeable() && geo.isGeoBoolean()) {
GeoBoolean bool = (GeoBoolean) geo;
bool.setValue(!bool.getBoolean());
bool.updateCascade();
moved = true;
}
}
}
if (moved) {
app.getKernel().notifyRepaint();
}
return moved;
}
public void setNewWindowAllowed(boolean rel) {
newWindowAllowed = rel;
}
/**
* Handles general keys like ESC and function keys that don't involved
* selected GeoElements.
*
* @param key
* key code
* @param isShiftDown
* whether shift is down
* @param isControlDown
* whether control is down
* @param isAltDown
* whether alt is down
* @param fromSpreadsheet
* whether this event comes from spreadsheet
* @param fromEuclidianView
* whether this event comes from EV
*
* @return if key was consumed
*/
protected boolean handleGeneralKeys(KeyCodes key, boolean isShiftDown,
boolean isControlDown, boolean isAltDown, boolean fromSpreadsheet,
boolean fromEuclidianView) {
// eventually make an undo point (e.g. after zooming)
app.storeUndoInfoIfSetCoordSystemOccured();
boolean consumed = false;
// ESC and function keys
switch (key) {
default:
// do nothing
break;
case ESCAPE:
// ESC: set move mode
handleEscForDropdown();
if (app.isApplet() && !app.showToolBar()) {
app.loseFocus();
} else {
app.setMoveMode();
app.getActiveEuclidianView().getEuclidianController()
.deletePastePreviewSelected();
}
consumed = true;
break;
case ENTER:
// check not spreadsheet
if (!fromSpreadsheet) {
// ENTER: set focus to input field
consumed = handleEnter();
}
break;
// toggle boolean or run script when Spacebar pressed
case SPACE:
// check not spreadsheet
if (!fromSpreadsheet) {
consumed = app.handleSpaceKey();
}
break;
case TAB:
consumed = handleTab(isControlDown, isShiftDown, true);
break;
// open Tool Help
case F1:
app.getDialogManager().openToolHelp();
return true;
// F9 updates construction
// cmd-f9 on Mac OS
case F9:
if (!app.isApplet() || app.isRightClickEnabled()) {
app.getKernel().updateConstruction();
app.setUnsaved();
consumed = true;
}
break;
}
/*
* // make sure Ctrl-1/2/3 works on the Numeric Keypad even with Numlock
* // off // **** NB if NumLock on, event.isShiftDown() always returns
* false with // Numlock on!!! (Win 7) if (event.getKeyLocation() ==
* KeyEvent.KEY_LOCATION_NUMPAD) { String keyText =
* KeyEvent.getKeyText(keyCode); if ("End".equals(keyText)) { keyCode =
* KeyEvent.VK_1; } else if ("Down".equals(keyText)) { keyCode =
* KeyEvent.VK_2; } else if ("Page Down".equals(keyText)) { keyCode =
* KeyEvent.VK_3; }
*
* }
*/
// Ctrl key down (and not Alt, so that AltGr works for special
// characters)
if (isControlDown && !isAltDown) {
switch (key) {
case K1:
case NUMPAD1:
// event.isShiftDown() doesn't work if NumLock on
// however .isAltDown() stops AltGr-1 from working (| on some
// keyboards)
if (isShiftDown && app.getGuiManager() != null) {// ||
// event.isAltDown())
// {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_EUCLIDIAN),
App.VIEW_EUCLIDIAN);
consumed = true;
} else if (!isAltDown) { // make sure not triggered on
// AltGr
// Ctrl-1: set objects back to the default size (for font
// size 12)
changeFontsAndGeoElements(app, 12, false, false);
consumed = true;
}
break;
case NUMPAD2:
case K2:
// event.isShiftDown() doesn't work if NumLock on
// however .isAltDown() stops AltGr-2 from working (superscript
// 2 on some keyboards)
if (isShiftDown && app.getGuiManager() != null) {// ||
// event.isAltDown())
// {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_EUCLIDIAN2),
App.VIEW_EUCLIDIAN2);
consumed = true;
} else if (!isAltDown) { // make sure not triggered on
// AltGr
// Ctrl-2: large font size and thicker lines for projectors
// etc
int fontSize = Math.min(32, app.getFontSize() + 4);
changeFontsAndGeoElements(app, fontSize, false, true);
consumed = true;
}
break;
case NUMPAD3:
case K3:
// event.isShiftDown() doesn't work if NumLock on
// however .isAltDown() stops AltGr-3 from working (^ on
// Croatian keyboard)
if (isShiftDown && app.getGuiManager() != null
&& app.supportsView(App.VIEW_EUCLIDIAN3D)) { // ||
// event.isAltDown())
// {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_EUCLIDIAN3D),
App.VIEW_EUCLIDIAN3D);
consumed = true;
} else if (!isAltDown) { // make sure not triggered on
// AltGr
// Ctrl-3: set black/white mode printing and visually
// impaired users
changeFontsAndGeoElements(app, app.getFontSize(), true,
true);
consumed = true;
}
break;
case A:
if (isShiftDown) {
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_ALGEBRA),
App.VIEW_ALGEBRA);
consumed = true;
}
} else {
selection.selectAll(-1);
consumed = true;
}
break;
case K:
if (isShiftDown) {
if (app.isUsingFullGui() && app.getGuiManager() != null
&& app.supportsView(App.VIEW_CAS)) {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_CAS),
App.VIEW_CAS);
consumed = true;
}
}
break;
case L:
if (isShiftDown) {
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(
App.VIEW_CONSTRUCTION_PROTOCOL),
App.VIEW_CONSTRUCTION_PROTOCOL);
consumed = true;
}
} else {
selection.selectAll(selection.getSelectedLayer());
consumed = true;
}
break;
case O: // File -> Open
if (!isShiftDown && app.getGuiManager() != null) {
app.getGuiManager().openFile();
consumed = true;
}
break;
case P:
if (isShiftDown) {
// toggle Probability View
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(
App.VIEW_PROBABILITY_CALCULATOR),
App.VIEW_PROBABILITY_CALCULATOR);
}
} else {
showPrintPreview(app);
}
consumed = true;
break;
case T: // File -> Export -> PSTricks
if (isShiftDown && app.getGuiManager() != null) {
app.getGuiManager().showPSTricksExport();
consumed = true;
}
break;
case W: // File -> Export -> Webpage
if (isShiftDown && app.getGuiManager() != null) {
app.getGuiManager().showWebpageExport();
consumed = true;
} else {
// File -> Close (under Mac: Command-W)
app.exitAll();
// Under Ubuntu/Unity this will close all windows.
consumed = true;
}
break;
case F4: // File -> Exit
if (!isShiftDown) {
app.exitAll();
consumed = true;
}
break;
case I: // Edit -> Invert Selection
if (!isShiftDown) {
selection.invertSelection();
consumed = true;
}
break;
case X:
// Ctrl-shift-c: copy graphics view to clipboard
// should also work in applets with no menubar
// check not spreadsheet
if (!fromSpreadsheet) {
handleCopyCut(true);
}
break;
case C:
// Ctrl-shift-c: copy graphics view to clipboard
// should also work in applets with no menubar
if (isShiftDown) {
app.copyGraphicsViewToClipboard();
consumed = true;
} else {
// check not spreadsheet
if (!fromSpreadsheet) {
handleCopyCut(false);
}
}
break;
case M:
if (isShiftDown) {
app.copyFullHTML5ExportToClipboard();
} else {
// Ctrl-M: standard view
app.setStandardView();
}
break;
case B:
// copy base64 string to clipboard
if (isShiftDown) {
app.copyBase64ToClipboard();
}
break;
// Ctrl + H / G: Show Hide objects (labels)
case G:
case H:
if (isShiftDown) {
selection.showHideSelectionLabels();
} else {
selection.showHideSelection();
}
consumed = true;
break;
// Ctrl + E: open object properties (needed here for spreadsheet)
case E:
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().setShowView(
!app.getGuiManager().showView(App.VIEW_PROPERTIES),
App.VIEW_PROPERTIES, false);
}
consumed = true;
break;
// Ctrl + F: refresh views
case F:
app.refreshViews();
consumed = true;
break;
/*
* send next instance to front (alt - last)
*/
case N:
if (isShiftDown) {
handleCtrlShiftN(isAltDown);
} else if (newWindowAllowed) {
app.setWaitCursor();
createNewWindow();
app.setDefaultCursor();
newWindowAllowed = false;
}
break;
// needed for detached views and MacOS
// Ctrl + Z: Undo
case Z:
if (app.getGuiManager() != null) {
if (isShiftDown) {
app.getGuiManager().redo();
} else {
app.getGuiManager().undo();
}
}
consumed = true;
break;
case U:
if (isShiftDown && app.getGuiManager() != null) {
app.getGuiManager().showGraphicExport();
consumed = true;
}
break;
case V:
// check not spreadsheet, not inputbar
if (!(fromSpreadsheet)) {
handleCtrlV();
}
break;
// ctrl-R updates construction
// make sure it works in applets without a menubar
case R:
if (!app.isApplet() || app.isRightClickEnabled()) {
app.getKernel().updateConstruction();
app.setUnsaved();
consumed = true;
}
break;
// ctrl-shift-s (toggle spreadsheet)
case S:
if (isShiftDown) {
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().setShowView(
!app.getGuiManager()
.showView(App.VIEW_SPREADSHEET),
App.VIEW_SPREADSHEET);
consumed = true;
}
} else if (app.getGuiManager() != null) {
app.getGuiManager().save();
consumed = true;
}
break;
case Y:
if (isShiftDown) {
// if (app.isUsingFullGui() && app.getGuiManager() != null)
// {
// app.getGuiManager().setShowView(
// !app.getGuiManager().showView(
// App.VIEW_PYTHON),
// App.VIEW_PYTHON);
// consumed = true;
// }
} else if (app.getGuiManager() != null) {
// needed for detached views and MacOS
// Cmd + Y: Redo
app.getGuiManager().redo();
consumed = true;
}
break;
// Ctrl-(shift)-Q (deprecated - doesn't work on MacOS)
// Ctrl-(shift)-J
case J:
case Q:
if (isShiftDown) {
selection.selectAllDescendants();
} else {
selection.selectAllPredecessors();
}
consumed = true;
break;
// Ctrl + "+", Ctrl + "-" zooms in or out in graphics view
case PLUS:
case ADD:
case SUBTRACT:
case MINUS:
case EQUALS:
// in Chrome and IE11, both the applet and the
// browser are zoomed
// even when the applet has focus
if (app.isHTML5Applet()) {
break;
}
// disable zooming in PEN mode
if (!EuclidianView
.isPenMode(app.getActiveEuclidianView().getMode())) {
boolean spanish = app.getLocalization().getLanguage()
.startsWith("es");
// AltGr+ on Spanish keyboard is ] so
// allow <Ctrl>+ (zoom) but not <Ctrl><Alt>+ (fast zoom)
// from eg Input Bar
if (!spanish || (fromEuclidianView)) {
(app.getActiveEuclidianView()).getEuclidianController()
.zoomInOut(isAltDown, key.equals(KeyCodes.MINUS)
|| key.equals(KeyCodes.SUBTRACT));
app.setUnsaved();
consumed = true;
}
}
break;
// Ctrl + D: toggles algebra style: value, definition, command
case D:
case BACK_QUOTE:
if (!isShiftDown) {
Kernel kernel = app.getKernel();
kernel.setAlgebraStyle((kernel.getAlgebraStyle() + 1) % 3);
kernel.setAlgebraStyleSpreadsheet(
(kernel.getAlgebraStyleSpreadsheet() + 1) % 3);
kernel.updateConstruction();
/*
* if (app.hasOptionsMenu()) {
* app.getOptionsMenu(null).updateMenuViewDescription(); }
*/
app.setUnsaved();
consumed = true;
} else {
// Ctrl-Shift-D
// toggle "selection allowed" for all objects
// except visible sliders, unfixed points, buttons,
// checkboxes, InputBoxes, drop-down lists
// what to set all objects to
boolean selectionAllowed = false;
// check if any geos already have selectionAllowed = false
// if so then we will set all to be true
TreeSet<GeoElement> objects = app.getKernel()
.getConstruction().getGeoSetConstructionOrder();
Iterator<GeoElement> it = objects.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
if (!geo.isSelectionAllowed(
app.getActiveEuclidianView())) {
selectionAllowed = true;
break;
}
}
it = objects.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
if (geo instanceof Furniture
|| (geo.isGeoNumeric() && geo.isIndependent())
|| (geo.isGeoList()
&& ((GeoList) geo).drawAsComboBox())
|| geo.isGeoBoolean()
|| (geo.isGeoPoint() && !geo.isLocked())) {
geo.setSelectionAllowed(true);
} else {
geo.setSelectionAllowed(selectionAllowed);
}
}
}
break;
}
}
return consumed;
}
private void handleEscForDropdown() {
Log.debug("handleEscForDropdown");
ArrayList<GeoElement> geos = selection.getSelectedGeos();
if (geos.size() == 1 && geos.get(0).isGeoList()) {
DrawDropDownList dl = DrawDropDownList.asDrawable(app, geos.get(0));
if (dl.isOptionsVisible()) {
dl.toggleOptions();
}
}
}
/**
* Creates new GGB window
*/
protected abstract void createNewWindow();
/**
* Opens print preview dialog
*
* @param app2
* application
*/
protected abstract void showPrintPreview(App app2);
/**
* Handles Ctrl+V; overridden in desktop Default implementation pastes from
* XML and returns true
*/
protected void handleCtrlV() {
app.setWaitCursor();
app.getCopyPaste().pasteFromXML(app, false);
app.setDefaultCursor();
}
/**
* @param isAltDown
* whether alt is down
* @return whether keys were consumed
*/
protected abstract boolean handleCtrlShiftN(boolean isAltDown);
/**
* overridden in desktop Default implementation copies into XML
*
* @param cut
* whether to cut (false = copy)
*/
protected void handleCopyCut(boolean cut) {
// Copy selected geos
app.setWaitCursor();
app.getCopyPaste().copyToXML(app, selection.getSelectedGeos(), false);
if (cut) {
app.deleteSelectedObjects(cut);
}
app.updateMenubar();
app.setDefaultCursor();
}
/**
* @param isControlDown
* whether control is down
* @param isShiftDown
* whether shift is down
* @param cycle
* whether to cycle back
* @return whether key was consumed
*/
public boolean handleTab(boolean isControlDown, boolean isShiftDown,
boolean cycle) {
app.getActiveEuclidianView().closeDropdowns();
if (isShiftDown) {
selection.selectLastGeo(app.getActiveEuclidianView());
} else {
selection.selectNextGeo(app.getActiveEuclidianView(), cycle);
}
return true;
}
/**
* Changes the font size of the user interface and construction element
* styles (thickness, size) for a given fontSize.
*
* @param app
* application
* @param fontSize
* 12-32pt
* @param blackWhiteMode
* whether only black should be used as a color
* @param makeAxesBold
* force bold / not bold
* @return whether change was performed
*/
public static boolean changeFontsAndGeoElements(App app, int fontSize,
boolean blackWhiteMode, boolean makeAxesBold) {
if (app.isApplet()) {
return false;
}
app.setWaitCursor();
// axes bold / not bold
for (int ev = 1; ev <= 2; ev++) {
EuclidianSettings settings = app.getSettings().getEuclidian(ev);
int style = settings.getAxesLineStyle();
// set bold
style = style | EuclidianStyleConstants.AXES_BOLD;
if (!makeAxesBold) {
// turn bold off again
style = style ^ EuclidianStyleConstants.AXES_BOLD;
}
settings.setAxesLineStyle(style);
}
// determine styles
// set new default line thickness
int oldFontSize = app.getFontSize();
int angleSizeIncr = fontSize - oldFontSize;
int incr = getPointSizeInc(oldFontSize, fontSize);
// construction defaults
ConstructionDefaults cd = app.getKernel().getConstruction()
.getConstructionDefaults();
cd.setDefaultLineThickness(cd.getDefaultLineThickness() + incr);
cd.setDefaultPointSize(cd.getDefaultPointSize() + incr,
cd.getDefaultDependentPointSize() + incr);
cd.setDefaultAngleSize(cd.getDefaultAngleSize() + angleSizeIncr);
// blackWhiteMode: set defaults for new GeoElements
cd.setBlackWhiteMode(blackWhiteMode);
// change application font size
app.setFontSize(fontSize, true);
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().updateSpreadsheetColumnWidths();
}
// apply styles to to selected or all geos
Iterator<GeoElement> it = null;
if (app.getSelectionManager().getSelectedGeos().size() == 0) {
// change all geos
it = app.getKernel().getConstruction().getGeoSetConstructionOrder()
.iterator();
} else {
// just change selected geos
it = app.getSelectionManager().getSelectedGeos().iterator();
}
while (it.hasNext()) {
GeoElement geo = it.next();
setGeoProperties(geo, incr, incr, angleSizeIncr, blackWhiteMode);
}
app.getKernel().updateConstruction();
app.setUnsaved();
app.storeUndoInfo();
app.setDefaultCursor();
return true;
}
private static int getPointSizeInc(int oldFontSize, int newFontSize) {
if (oldFontSize == newFontSize) {
return 0;
}
int step = newFontSize > oldFontSize ? 1 : -1;
int left = Math.min(oldFontSize, newFontSize);
int right = Math.max(oldFontSize, newFontSize);
int[] borders = { 16, 22, 28 };
int incr = 0;
for (int i = 0; i < borders.length; i++) {
if (left < borders[i] && borders[i] <= right) {
incr = incr + step;
}
}
return incr * 2;
}
private static void setGeoProperties(GeoElement geo, int lineThicknessIncr,
int pointSizeIncr, int angleSizeIncr, boolean blackWhiteMode) {
if (!geo.isGeoText() && !geo.isGeoImage() && !geo.isGeoPolygon()) { // affects
// bounding
// box
int geoLineThickness = geo.getLineThickness();
if (geoLineThickness != 0) {
int lineThickness = Math.max(2,
geoLineThickness + lineThicknessIncr);
geo.setLineThickness(lineThickness);
}
}
if (geo instanceof PointProperties) {
PointProperties p = (PointProperties) geo;
int pointSize = Math.max(2, p.getPointSize() + pointSizeIncr);
p.setPointSize(pointSize);
}
if (geo.isGeoAngle()) {
GeoAngle angle = (GeoAngle) geo;
int angleSize = Math.max(2, angle.getArcSize() + angleSizeIncr);
angle.setArcSize(angleSize);
}
if (blackWhiteMode) {
geo.setAlphaValue(0f);
geo.setObjColor(GColor.BLACK);
}
}
/**
* Handle pressed key for selected GeoElements
*
* @param key
* key code
* @param geos
* selected geos
* @param isShiftDown
* whether shift is down
* @param isControlDown
* whether control is down
* @param isAltDown
* whether alt is down
* @param fromSpreadsheet
* whether this event comes from spreadsheet
*
* @return if key was consumed
*/
protected boolean handleSelectedGeosKeys(KeyCodes key,
ArrayList<GeoElement> geos, boolean isShiftDown,
boolean isControlDown, boolean isAltDown, boolean fromSpreadsheet) {
// SPECIAL KEYS
double changeValX = 0; // later: changeVal = base or -base
double changeValY = 0; // later: changeVal = base or -base
double changeValZ = 0; // later: changeVal = base or -base
// Shift : base = 0.1
// Default : base = 1
// Ctrl : base = 10
// Alt : base = 100
double base = 1;
if (isShiftDown) {
base = 0.1;
}
if (isControlDown) {
base = 10;
}
if (isAltDown) {
base = 100;
}
// Log.debug("key pressed");
if (geos == null || geos.size() == 0) {
// Get the EuclidianView which has the focus
EuclidianViewInterfaceCommon ev = app.getActiveEuclidianView();
int width = ev.getWidth();
int height = ev.getHeight();
if (ev.hasFocus() && app.isShiftDragZoomEnabled()) {
switch (key) {
case PAGEUP:
ev.rememberOrigins();
ev.pageUpDownTranslateCoordSystem((int) (height * base));
return true;
case PAGEDOWN:
ev.rememberOrigins();
ev.pageUpDownTranslateCoordSystem(-(int) (height * base));
return true;
case INSERT:
ev.rememberOrigins();
ev.translateCoordSystemInPixels((int) (height * base), 0, 0,
EuclidianController.MOVE_VIEW);
return true;
case HOME:
ev.rememberOrigins();
ev.translateCoordSystemInPixels(-(int) (height * base), 0,
0, EuclidianController.MOVE_VIEW);
return true;
case DOWN:
if (app.isUsingFullGui() && app.getGuiManager() != null
&& app.getGuiManager().noMenusOpen()) {
if (isShiftDown) {
EuclidianViewInterfaceCommon view = app
.getActiveEuclidianView();
if (!view.isLockedAxesRatio()) {
view.setCoordSystem(view.getXZero(),
view.getYZero(), view.getXscale(),
view.getYscale() * 0.9);
}
} else {
ev.rememberOrigins();
ev.translateCoordSystemInPixels(0,
(int) (height / 100.0 * base), 0,
EuclidianController.MOVE_VIEW);
}
return true;
}
break;
case UP:
if (app.isUsingFullGui() && app.getGuiManager() != null
&& app.getGuiManager().noMenusOpen()) {
if (isShiftDown) {
EuclidianViewInterfaceCommon view = app
.getActiveEuclidianView();
if (!view.isLockedAxesRatio()) {
view.setCoordSystem(view.getXZero(),
view.getYZero(), view.getXscale(),
view.getYscale() / 0.9);
}
} else {
ev.rememberOrigins();
ev.translateCoordSystemInPixels(0,
-(int) (height / 100.0 * base), 0,
EuclidianController.MOVE_VIEW);
}
return true;
}
break;
case LEFT:
if (app.isUsingFullGui() && app.getGuiManager() != null
&& app.getGuiManager().noMenusOpen()) {
if (isShiftDown) {
EuclidianViewInterfaceCommon view = app
.getActiveEuclidianView();
if (!view.isLockedAxesRatio()) {
view.setCoordSystem(view.getXZero(),
view.getYZero(), view.getXscale() * 0.9,
view.getYscale());
}
} else {
ev.rememberOrigins();
ev.translateCoordSystemInPixels(
-(int) (width / 100.0 * base), 0, 0,
EuclidianController.MOVE_VIEW);
}
return true;
}
break;
case RIGHT:
if (app.isUsingFullGui() && app.getGuiManager() != null
&& app.getGuiManager().noMenusOpen()) {
if (isShiftDown) {
EuclidianViewInterfaceCommon view = app
.getActiveEuclidianView();
if (!view.isLockedAxesRatio()) {
view.setCoordSystem(view.getXZero(),
view.getYZero(), view.getXscale() / 0.9,
view.getYscale());
}
} else {
ev.rememberOrigins();
ev.translateCoordSystemInPixels(
(int) (width / 100.0 * base), 0, 0,
EuclidianController.MOVE_VIEW);
}
}
return true;
}
}
return false;
}
Iterator<GeoElement> it;
// FUNCTION and DELETE keys
switch (key) {
case PAGEUP:
// 3D handled later (move object up/down)
if (!app.getActiveEuclidianView().isEuclidianView3D()) {
it = geos.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
geo.setLayer(geo.getLayer() + 1);
}
}
break;
case PAGEDOWN:
// 3D handled later (move object up/down)
if (!app.getActiveEuclidianView().isEuclidianView3D()) {
it = geos.iterator();
while (it.hasNext()) {
GeoElement geo = it.next();
geo.setLayer(geo.getLayer() - 1);
}
}
break;
case F3:
// F3 key: copy definition to input field
if (geos.size() == 1) {
handleFunctionKeyForAlgebraInput(3, geos.get(0));
} else {
// F3 key: copy definitions to input field as list
copyDefinitionsToInputBarAsList(geos);
break;
}
return true;
case F1:
app.getDialogManager().openToolHelp();
return true;
case F4:
// F4 key: copy value to input field
handleFunctionKeyForAlgebraInput(4, geos.get(0));
return true;
case F5:
// F5 key: copy label to input field
handleFunctionKeyForAlgebraInput(5, geos.get(0));
return true;
case DELETE:
// G.Sturr 2010-5-2: let the spreadsheet handle delete
if (app.getGuiManager() != null
&& app.getGuiManager().hasSpreadsheetView()
&& app.getGuiManager().getSpreadsheetView().hasFocus()) {
return false;
}
// DELETE selected objects
if (!app.isApplet() || app.isRightClickEnabled()) {
app.deleteSelectedObjects(false);
return true;
}
case BACKSPACE:
// G.Sturr 2010-5-2: let the spreadsheet handle delete
if (app.getGuiManager() != null
&& app.getGuiManager().getSpreadsheetView().hasFocus()) {
return false;
}
// DELETE selected objects
// Note: ctrl-h generates a KeyEvent.VK_BACK_SPACE event, so check
// for ctrl too
if (!isControlDown
&& (!app.isApplet() || app.isRightClickEnabled())) {
app.deleteSelectedObjects(false);
return true;
}
break;
}
// ignore key events coming from tables like the spreadsheet to
// allow start editing, moving etc
if (fromSpreadsheet || (app.isUsingFullGui()
&& app.getGuiManager() != null
&& app.getGuiManager().hasSpreadsheetView()
&& app.getGuiManager().getSpreadsheetView().hasFocus())) {
return false;
}
// check for arrow keys: try to move objects accordingly
boolean moved = false;
switch (key) {
default:
// do nothing
break;
case UP:
// make sure arrow keys work in menus
if (app.getGuiManager() != null && app.isUsingFullGui()
&& !app.getGuiManager().noMenusOpen()) {
return false;
}
if (!fromSpreadsheet
&& handleUpDownArrowsForDropdown(geos, false, true)) {
return true;
}
changeValY = base;
break;
case DOWN:
// make sure arrow keys work in menus
if (app.getGuiManager() != null && app.isUsingFullGui()
&& !app.getGuiManager().noMenusOpen()) {
return false;
}
if (!fromSpreadsheet
&& handleUpDownArrowsForDropdown(geos, true, true)) {
return true;
}
changeValY = -base;
break;
case RIGHT:
// make sure arrow keys work in menus
if (app.getGuiManager() != null && app.isUsingFullGui()
&& !app.getGuiManager().noMenusOpen()) {
return false;
}
if (!fromSpreadsheet
&& handleLeftRightArrowsForDropdown(geos, true, true)) {
return true;
}
changeValX = base;
break;
case LEFT:
// make sure arrow keys work in menus
if (app.getGuiManager() != null && app.isUsingFullGui()
&& !app.getGuiManager().noMenusOpen()) {
return false;
}
if (!fromSpreadsheet
&& handleLeftRightArrowsForDropdown(geos, false, true)) {
return true;
}
changeValX = -base;
break;
case PAGEUP:
changeValZ = base;
break;
case PAGEDOWN:
changeValZ = -base;
break;
}
if (changeValX != 0 || changeValY != 0 || changeValZ != 0) {
moved = handleArrowKeyMovement(geos, changeValX, changeValY,
changeValZ);
}
if (moved) {
return true;
}
boolean vertical = true;
double changeVal = 0;
// F2, PLUS, MINUS keys
switch (key) {
default:
// do nothing
break;
case F2:
// handle F2 key to start editing first selected element
if (app.isUsingFullGui() && app.getGuiManager() != null) {
app.getGuiManager().startEditing(geos.get(0));
return true;
}
break;
case PLUS:
case ADD: // can be own key on some keyboard
case EQUALS: // same key as plus (on most keyboards)
case UP:
changeVal = base;
vertical = true;
break;
case RIGHT:
changeVal = base;
vertical = false;
break;
case MINUS:
case SUBTRACT:
case DOWN:
changeVal = -base;
vertical = true;
break;
case LEFT:
changeVal = -base;
vertical = false;
break;
// case ESCAPE:
// if (!fromSpreadsheet) {
// handleEscForDropdown();
// }
// break;
}
/*
* if (changeVal == 0) { char keyChar = event.getKeyChar(); if (keyChar
* == '+') changeVal = base; else if (keyChar == '-') changeVal = -base;
* }
*/
// change all geoelements
if (changeVal != 0) {
boolean twoSliders = geos.size() == 2 && geos.get(0).isGeoNumeric()
&& geos.get(1).isGeoNumeric();
for (int i = geos.size() - 1; i >= 0; i--) {
GeoElement geo = geos.get(i);
if (geo.isChangeable()) {
// update number
if (geo.isGeoNumeric()
&& (!twoSliders || ((vertical && i == 0)
|| (!vertical && i == 1)))) {
GeoNumeric num = (GeoNumeric) geo;
double newValue = num.getValue()
+ changeVal * num.getAnimationStep();
if (num.getAnimationStep() > Kernel.MIN_PRECISION) {
// round to decimal fraction, e.g. 2.800000000001 to
// 2.8
if (num.isGeoAngle()) {
newValue = Kernel.PI_180
* Kernel.checkDecimalFraction(
newValue * Kernel.CONST_180_PI,
1 / num.getAnimationStep());
} else {
newValue = Kernel.checkDecimalFraction(newValue,
1 / num.getAnimationStep());
}
}
num.setValue(newValue);
}
// update point on path
else if (geo.isGeoPoint() && !geo.isGeoElement3D()) {
GeoPoint p = (GeoPoint) geo;
if (p.isPointOnPath()) {
p.addToPathParameter(
changeVal * p.getAnimationStep());
}
}
}
// update parent algo of dependent geo to update randomNumbers
else if (!geo.isIndependent()) {
// update labeled random number
if (geo.isLabelSet() && geo.isGeoNumeric()) {
GeoNumeric num = (GeoNumeric) geo;
if (num.isRandomGeo()) {
num.updateRandomGeo();
}
}
// update parent algorithm for unlabeled random numbers
// and all other algorithms
geo.getParentAlgorithm().update();
}
}
// update all geos together
GeoElement.updateCascade(geos, getTempSet(), false);
app.getKernel().notifyRepaint();
return true;
}
return false;
}
/**
* Copies definitions of geos to input bar and wraps them in a list
*
* @param geos
* list of geos
*/
protected abstract void copyDefinitionsToInputBarAsList(
ArrayList<GeoElement> geos);
/**
* @return handles enter
*/
protected boolean handleEnter() {
if (selection.getSelectedGeos().size() == 1) {
GeoElement geo = selection.getSelectedGeos().get(0);
if (geo.isGeoList()) {
DrawDropDownList.asDrawable(app, geo).selectCurrentItem();
return true;
} else if (geo.isGeoInputBox()) {
app.getActiveEuclidianView()
.focusAndShowTextField((GeoInputBox) geo);
}
}
return false;
}
}