/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.desktop.gui.view.functioninspector;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.ArrayList;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.gui.util.SelectionTable;
import org.geogebra.common.gui.view.functioninspector.FunctionInspector;
import org.geogebra.common.gui.view.functioninspector.FunctionInspectorModel.Colors;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.main.App;
import org.geogebra.common.main.GeoGebraColorConstants;
import org.geogebra.common.util.debug.Log;
import org.geogebra.desktop.awt.GColorD;
import org.geogebra.desktop.gui.GuiManagerD;
import org.geogebra.desktop.gui.inputfield.MyTextFieldD;
import org.geogebra.desktop.gui.util.GeoGebraIconD;
import org.geogebra.desktop.gui.util.PopupMenuButtonD;
import org.geogebra.desktop.gui.util.SpecialNumberFormat;
import org.geogebra.desktop.gui.util.SpecialNumberFormatInterface;
import org.geogebra.desktop.main.AppD;
import org.geogebra.desktop.main.LocalizationD;
import org.geogebra.desktop.util.GuiResourcesD;
/**
* View for inspecting selected GeoFunc } else {tions
*
* @author G. Sturr, 2011-2-12
*
*/
public class FunctionInspectorD extends FunctionInspector
implements
ListSelectionListener, KeyListener,
SpecialNumberFormatInterface, ActionListener, WindowFocusListener,
FocusListener {
// ggb fields
private JDialog wrappedDialog;
// color constants
private static final Color DISPLAY_GEO_COLOR = Color.RED;
private static final Color DISPLAY_GEO2_COLOR = Color.RED;
private static final Color EVEN_ROW_COLOR = new Color(241, 245, 250);
private static final Color TABLE_GRID_COLOR = GColorD
.getAwtColor(GeoGebraColorConstants.TABLE_GRID_COLOR);
// table fields
private InspectorTable tableXY, tableInterval;
private DefaultTableModel modelXY, modelInterval;
private static final int minRows = 12;
// GUI
private JLabel lblGeoName, lblStep, lblInterval;
private MyTextFieldD fldStep, fldLow, fldHigh;
private JButton btnRemoveColumn, btnHelp;
private JToggleButton btnOscCircle, btnTangent, btnXYSegments, btnTable;
private PopupMenuButtonD btnAddColumn, btnOptions;
private JTabbedPane tabPanel;
private JPanel intervalTabPanel, pointTabPanel, headerPanel, helpPanel;
private boolean isChangingValue;
private int pointCount = 9;
private SpecialNumberFormat nf;
/***************************************************************
* Constructs a FunctionInspecor
*
* @param app
* @param selectedGeo
*/
public FunctionInspectorD(AppD app, GeoFunction selectedGeo) {
super(app, selectedGeo);
this.app = app;
}
private AppD getAppD() {
return (AppD) app;
}
// ======================================================
// GUI
// ======================================================
@Override
protected void createGUI() {
wrappedDialog = new JDialog(getAppD().getFrame(), false) {
/**
* } else {
*
*/
private static final long serialVersionUID = 1L;
@Override
public void setVisible(boolean isVisible) {
setInspectorVisible(isVisible);
super.setVisible(isVisible);
}
};
nf = new SpecialNumberFormat(getAppD(), this);
super.createGUI();
// add GUI to contentPane
wrappedDialog.getContentPane().add(headerPanel, BorderLayout.NORTH);
wrappedDialog.getContentPane().add(tabPanel, BorderLayout.CENTER);
// prepare the dialog to be displayed in the center
wrappedDialog.setResizable(true);
wrappedDialog.pack();
wrappedDialog.setLocationRelativeTo(getAppD().getMainComponent());
updateFonts();
setLabels();
}
@Override
protected void createTabIntervalPanel() {
JToolBar intervalTB = new JToolBar(); // JPanel(new
// FlowLayout(FlowLayout.LEFT));
intervalTB.setFloatable(false);
intervalTB.add(fldLow);
intervalTB.add(lblInterval);
intervalTB.add(fldHigh);
intervalTabPanel = new JPanel(new BorderLayout(5, 5));
intervalTabPanel.add(new JScrollPane(tableInterval),
BorderLayout.CENTER);
intervalTabPanel.add(intervalTB, BorderLayout.SOUTH);
}
@Override
protected void createTabPointPanel() {
// create step toolbar
JToolBar tb1 = new JToolBar();
tb1.setFloatable(false);
tb1.add(lblStep);
tb1.add(fldStep);
// create add/remove column toolbar
JToolBar tb2 = new JToolBar();
tb2.setFloatable(false);
tb2.add(btnAddColumn);
tb2.add(btnRemoveColumn);
// create toggle graphics panel
FlowLayout flow = new FlowLayout(FlowLayout.CENTER);
flow.setHgap(4);
JPanel tb3 = new JPanel(flow);
// JToolBar tb3 = new JToolBar();
// tb3.setFloatable(false);
tb3.add(btnTable);
tb3.add(btnXYSegments);
tb3.add(btnTangent);
tb3.add(btnOscCircle);
JPanel toggleGraphicsPanel = new JPanel(new BorderLayout());
toggleGraphicsPanel.add(tb3, BorderLayout.CENTER);
// create the panel
LocalizationD loc = getAppD().getLocalization();
JPanel northPanel = new JPanel(new BorderLayout());
northPanel.add(tb1, loc.borderWest());
northPanel.add(tb2, loc.borderEast());
JPanel southPanel = new JPanel(new BorderLayout());
southPanel.add(toggleGraphicsPanel, BorderLayout.CENTER);
JScrollPane scroller = new JScrollPane(tableXY);
pointTabPanel = new JPanel(new BorderLayout(2, 2));
pointTabPanel.add(northPanel, BorderLayout.NORTH);
pointTabPanel.add(scroller, BorderLayout.CENTER);
pointTabPanel.add(southPanel, BorderLayout.SOUTH);
}
@Override
protected void createGUIElements() {
// create XY table
tableXY = new InspectorTable(getAppD(), this, minRows,
InspectorTable.TYPE_XY);
modelXY = new DefaultTableModel();
modelXY.addColumn("x");
modelXY.addColumn("y(x)");
// modelXY.addRow(new String[] { "", "" });
modelXY.setRowCount(pointCount);
tableXY.setModel(modelXY);
tableXY.getSelectionModel().addListSelectionListener(this);
// tableXY.addKeyListener(this);
tableXY.setMyCellEditor(0);
// create interval table
tableInterval = new InspectorTable(getAppD(), this, minRows,
InspectorTable.TYPE_INTERVAL);
modelInterval = new DefaultTableModel();
modelInterval.setColumnCount(2);
modelInterval.setRowCount(pointCount);
tableInterval.setModel(modelInterval);
tableInterval.getSelectionModel()
.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
getModel().updateIntervalGeoVisiblity();
}
});
lblGeoName = new JLabel(getModel().getTitleString());
lblGeoName.setFont(getAppD().getBoldFont());
lblStep = new JLabel();
fldStep = makeTextField(app);
fldStep.addActionListener(this);
fldStep.addFocusListener(this);
fldStep.setColumns(6);
lblInterval = new JLabel();
fldLow = makeTextField(app);
fldLow.addActionListener(this);
fldLow.addFocusListener(this);
fldLow.setColumns(6);
fldHigh = makeTextField(app);
fldHigh.addActionListener(this);
fldHigh.addFocusListener(this);
fldHigh.setColumns(6);
btnOscCircle = new JToggleButton();
btnTangent = new JToggleButton();
btnXYSegments = new JToggleButton();
btnTable = new JToggleButton();
btnOscCircle.addActionListener(this);
btnTangent.addActionListener(this);
btnXYSegments.addActionListener(this);
btnTable.addActionListener(this);
// btnOscCircle.setPreferredSize(new Dimension(24,24));
// btnTangent.setPreferredSize(new Dimension(24,24));
// btnXYSegments.setPreferredSize(new Dimension(24,24));
// btnTable.setPreferredSize(new Dimension(24,24));
btnXYSegments.setSelected(true);
btnRemoveColumn = new JButton();
btnRemoveColumn.addActionListener(this);
btnHelp = new JButton();
btnHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Thread runner = new Thread() {
@Override
public void run() {
((GuiManagerD) app.getGuiManager())
.openHelp("Function_Inspector_Tool");
}
};
runner.start();
}
});
btnHelp.setFocusable(false);
updateIcons();
createBtnAddColumn();
}
private void createBtnAddColumn() {
btnAddColumn = new PopupMenuButtonD(getAppD(),
getModel().getColumnNames(), -1, 1, new Dimension(0, 18),
SelectionTable.MODE_TEXT);
btnAddColumn.setKeepVisible(false);
btnAddColumn.setStandardButton(true);
btnAddColumn.setFixedIcon(GeoGebraIconD.createEmptyIcon(1, 1));
btnAddColumn.setText("\u271A");
btnAddColumn.addActionListener(this);
}
@Override
public void setLabels() {
LocalizationD loc = getAppD().getLocalization();
wrappedDialog.setTitle(loc.getMenu("FunctionInspector"));
lblStep.setText(loc.getMenu("Step") + ":");
lblInterval.setText(" \u2264 x \u2264 "); // <= x <=
// header text
modelInterval.setColumnIdentifiers(getModel().getIntervalColumnNames());
tabPanel.setTitleAt(1, loc.getPlain("fncInspector.Points"));
tabPanel.setTitleAt(0, loc.getPlain("fncInspector.Interval"));
lblGeoName.setText(getModel().getTitleString());
// tool tips
btnHelp.setToolTipText(loc.getPlain("ShowOnlineHelp"));
btnOscCircle.setToolTipText(
loc.getPlainTooltip("fncInspector.showOscCircle"));
btnXYSegments.setToolTipText(
loc.getPlainTooltip("fncInspector.showXYLines"));
btnTable.setToolTipText(loc.getPlainTooltip("fncInspector.showTable"));
btnTangent.setToolTipText(
loc.getPlainTooltip("fncInspector.showTangent"));
btnAddColumn
.setToolTipText(loc.getPlainTooltip("fncInspector.addColumn"));
btnRemoveColumn.setToolTipText(
loc.getPlainTooltip("fncInspector.removeColumn"));
fldStep.setToolTipText(loc.getPlainTooltip("fncInspector.step"));
lblStep.setToolTipText(loc.getPlainTooltip("fncInspector.step"));
// add/remove extra column buttons
btnRemoveColumn.setText("\u2718");
// btnAddColumn.setText("\u271A");
Container c = btnAddColumn.getParent();
c.removeAll();
createBtnAddColumn();
c.add(btnAddColumn);
c.add(btnRemoveColumn);
createOptionsButton();
}
// =====================================
// Update
// =====================================
@Override
protected void updateIntervalFields() {
if (tabPanel.getSelectedComponent() == intervalTabPanel) {
double[] coords = new double[3];
getModel().getLowPoint().getCoords(coords);
fldLow.setText(getModel().format(coords[0]));
getModel().getHighPoint().getCoords(coords);
fldHigh.setText(getModel().format(coords[0]));
updateIntervalTable();
}
}
/**
* Updates the interval table. The max, min, roots, area etc. for the
* current interval are calculated and put into the IntervalTable model.
*/
@Override
protected void updateIntervalTable() {
isChangingValue = true;
getModel().updateIntervalTable();
isChangingValue = false;
}
/**
* Updates the XYTable with the coordinates of the current sample points and
* any related values (e.g. derivative, difference)
*/
@Override
protected void updateXYTable() {
isChangingValue = true;
getModel().updateXYTable(modelXY.getRowCount(), btnTable.isSelected());
isChangingValue = false;
}
@Override
public void addTableColumn(String name) {
modelXY.addColumn(name);
tableXY.setMyCellEditor(0);
updateXYTable();
}
@Override
protected void removeColumn() {
int count = tableXY.getColumnCount();
if (count <= 2) {
return;
}
getModel().removeColumn();
modelXY.setColumnCount(modelXY.getColumnCount() - 1);
tableXY.setMyCellEditor(0);
updateXYTable();
}
// ========================================================
// Event Handlers
// ========================================================
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source instanceof JTextField) {
doTextFieldActionPerformed((JTextField) source);
} else if (source == btnAddColumn) {
getModel().addColumn(btnAddColumn.getSelectedIndex());
}
else if (source == btnRemoveColumn) {
removeColumn();
}
else if (source == btnOscCircle || source == btnTangent
|| source == btnTable || source == btnXYSegments) {
updateGUI();
}
}
private void doTextFieldActionPerformed(JTextField source) {
try {
String inputText = source.getText().trim();
// allow input such as sqrt(2)
NumberValue nv;
nv = getKernel().getAlgebraProcessor().evaluateToNumeric(inputText,
false);
double value = nv.getDouble();
if (source == fldStep) {
getModel().applyStep(value);
updateXYTable();
} else if (source == fldLow) {
isChangingValue = true;
getModel().applyLow(value);
isChangingValue = false;
updateIntervalTable();
} else if (source == fldHigh) {
isChangingValue = true;
getModel().applyHigh(value);
isChangingValue = false;
updateIntervalTable();
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
@Override
public void focusGained(FocusEvent e) {
if (e.getSource() instanceof MyTextFieldD) {
((MyTextFieldD) e.getSource()).selectAll();
}
}
@Override
public void focusLost(FocusEvent e) {
doTextFieldActionPerformed((JTextField) (e.getSource()));
}
public void show() {
wrappedDialog.setVisible(true);
}
public void hide() {
wrappedDialog.setVisible(false);
}
@Override
public void reset() {
wrappedDialog.setVisible(false);
}
// ====================================================
// Table Selection Listener
// ====================================================
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() || isChangingValue) {
return;
}
tableXY.getSelectionModel().removeListSelectionListener(this);
if (e.getSource() == tableXY.getSelectionModel()) {
// row selection changed
updateTestPoint();
}
tableXY.getSelectionModel().addListSelectionListener(this);
}
// ====================================================
// Key Listeners
// ====================================================
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
tableXY.getSelectionModel().removeListSelectionListener(this);
switch (key) {
default:
// do nothing
break;
case KeyEvent.VK_UP:
if (tableXY.getSelectedRow() == 0) {
getModel().stepStartBackward();
updateXYTable();
updateTestPoint();
}
break;
case KeyEvent.VK_DOWN:
if (tableXY.getSelectedRow() == tableXY.getRowCount() - 1) {
getModel().stepStartForward();
updateXYTable();
tableXY.changeSelection(tableXY.getRowCount() - 1, 0, false,
false);
updateTestPoint();
}
break;
}
tableXY.getSelectionModel().addListSelectionListener(this);
}
@Override
public void keyReleased(KeyEvent arg0) {
// only handle key pressed
}
@Override
public void keyTyped(KeyEvent arg0) {
// only handle key pressed
}
// Mouse Listeners
// =========================================
@Override
public void updateFonts() {
Font font = getAppD().getPlainFont();
wrappedDialog.setFont(font);
tableXY.setFont(font);
tableInterval.setFont(font);
MyTextFieldD dummyField = makeTextField(app);
tableXY.setRowHeight(dummyField.getPreferredSize().height);
tableInterval.setRowHeight(dummyField.getPreferredSize().height);
updateIcons();
GuiManagerD.setFontRecursive(wrappedDialog, font);
}
private static MyTextFieldD makeTextField(App app) {
return new MyTextFieldD((AppD) app);
}
@Override
public void windowGainedFocus(WindowEvent arg0) {
//
}
@Override
public void changeStart(double x) {
tableXY.getSelectionModel().removeListSelectionListener(this);
setStart(x);
tableXY.getSelectionModel().addListSelectionListener(this);
}
private SpecialNumberFormat getMyNumberFormat() {
return nf;
}
@Override
protected void createOptionsButton() {
if (btnOptions == null) {
btnOptions = new PopupMenuButtonD(getAppD());
btnOptions.setKeepVisible(true);
btnOptions.setStandardButton(true);
btnOptions
.setFixedIcon(getAppD().getScaledIcon(GuiResourcesD.TOOL));
btnOptions.setDownwardPopup(true);
}
btnOptions.removeAllMenuItems();
LocalizationD loc = getAppD().getLocalization();
btnOptions.setToolTipText(loc.getMenu("Options"));
// copy to spreadsheet
JMenuItem mi = new JMenuItem(loc.getMenu("CopyToSpreadsheet"));
mi.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doCopyToSpreadsheet();
}
});
mi.setEnabled(((GuiManagerD) app.getGuiManager()).hasSpreadsheetView());
btnOptions.addPopupMenuItem(mi);
// rounding
btnOptions.addPopupMenuItem(
getMyNumberFormat().createMenuDecimalPlaces());
}
@Override
protected void doCopyToSpreadsheet() {
if (tabPanel.getSelectedComponent() == pointTabPanel) {
getModel().copyPointsToSpreadsheet(tableXY.getColumnCount(),
tableXY.getRowCount());
} else {
getModel().copyIntervalsToSpreadsheet(
tableInterval.getColumnCount(),
tableInterval.getRowCount());
}
}
@Override
public void updateXYTable(boolean isTable) {
// reset table model and update the XYtable
tableXY.setCellEditable(-1, -1);
if (isTable) {
modelXY.setRowCount(pointCount);
tableXY.setCellEditable((pointCount - 1) / 2, 0);
// tableXY.setRowSelectionAllowed(true);
tableXY.changeSelection((pointCount - 1) / 2, 0, false, false);
} else {
modelXY.setRowCount(1);
tableXY.setCellEditable(0, 0);
tableXY.changeSelection(0, 0, false, false);
// tableXY.setRowSelectionAllowed(false);
}
updateXYTable();
updateTestPoint();
}
@Override
public void updateInterval(ArrayList<String> property,
ArrayList<String> value) {
// load the model with these pairs
modelInterval.setRowCount(property.size());
for (int i = 0; i < property.size(); i++) {
modelInterval.setValueAt(property.get(i), i, 0);
modelInterval.setValueAt(value.get(i), i, 1);
}
}
@Override
public void setXYValueAt(Double value, int row, int col) {
if (col < modelXY.getColumnCount() && row < modelXY.getRowCount()) {
modelXY.setValueAt(value == null ? null : getModel().format(value),
row, col);
} else {
Log.debug("[FI] Outside of range: " + modelXY.getRowCount() + ", "
+ modelXY.getRowCount());
}
}
@Override
public Object getXYValueAt(int row, int col) {
return modelXY.getValueAt(row, col);
}
@Override
public void setGeoName(String name) {
lblGeoName.setText(name);
}
@Override
public void changeTableSelection() {
updateXYTable();
tableXY.getSelectionModel().removeListSelectionListener(this);
if (btnTable.isSelected() && tableXY.getSelectedRow() != 4) {
tableXY.changeSelection(4, 0, false, false);
} else if (!btnTable.isSelected() && tableXY.getSelectedRow() != 0) {
tableXY.changeSelection(0, 0, false, false);
}
tableXY.getSelectionModel().addListSelectionListener(this);
}
@Override
public void updateHighAndLow(boolean isAscending, boolean isLowSelected) {
if (isAscending) {
if (isLowSelected) {
doTextFieldActionPerformed(fldLow);
} else {
doTextFieldActionPerformed(fldHigh);
}
}
updateIntervalFields();
}
@Override
public void setStepText(String text) {
fldStep.removeActionListener(this);
fldStep.setText(text);
fldStep.addActionListener(this);
}
@Override
public GColor getColor(Colors id) {
Color color;
switch (id) {
case EVEN_ROW:
color = EVEN_ROW_COLOR;
break;
case GEO:
color = DISPLAY_GEO_COLOR;
break;
case GEO2:
color = DISPLAY_GEO2_COLOR;
break;
case GRID:
color = TABLE_GRID_COLOR;
break;
default:
color = Color.black;
break;
}
return GColorD.newColor(color);
}
@Override
public int getSelectedXYRow() {
return tableXY.getSelectedRow();
}
@Override
public void setStepVisible(boolean isVisible) {
lblStep.setVisible(isVisible);
fldStep.setVisible(isVisible);
}
@Override
protected void buildTabPanel() {
// build tab panel
tabPanel = new JTabbedPane();
tabPanel.addTab("Interval", intervalTabPanel);
tabPanel.addTab("Point", pointTabPanel);
tabPanel.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent evt) {
updateTabPanels();
}
});
}
@Override
protected void buildHelpPanel() {
helpPanel = new JPanel(new FlowLayout());
helpPanel.add(btnHelp);
helpPanel.add(btnOptions);
}
@Override
protected void buildHeaderPanel() {
LocalizationD loc = getAppD().getLocalization();
headerPanel = new JPanel(new BorderLayout());
headerPanel.add(lblGeoName, BorderLayout.CENTER);
headerPanel.add(helpPanel, loc.borderEast());
headerPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 2));
}
@Override
protected void updatePointsTab() {
tableXY.getSelectionModel().removeListSelectionListener(this);
getModel().updatePoints(btnTangent.isSelected(),
btnOscCircle.isSelected(), btnXYSegments.isSelected(),
btnTable.isSelected());
tableXY.getSelectionModel().addListSelectionListener(this);
}
@Override
protected boolean isIntervalTabSelected() {
return tabPanel.getSelectedComponent() == intervalTabPanel;
}
@Override
public boolean hasFocus() {
// TODO Auto-generated method stub
return false;
}
@Override
public void windowLostFocus(WindowEvent e) {
// TODO Auto-generated method stub
}
@Override
public void changedNumberFormat() {
getModel().setPrintDecimals(nf.getPrintDecimals());
getModel().setPrintFigures(nf.getPrintFigures());
super.changedNumberFormat();
}
@Override
public boolean suggestRepaint() {
return false;
// only for web
}
public void updateIcons() {
if (app == null || btnOscCircle == null) {
return;
}
getScaledIcon(btnOscCircle, GuiResourcesD.OSCULATING_CIRCLE);
getScaledIcon(btnTangent, GuiResourcesD.TANGENT_LINE);
getScaledIcon(btnXYSegments, GuiResourcesD.XY_SEGMENTS);
getScaledIcon(btnTable, GuiResourcesD.XY_TABLE);
getScaledIcon(btnHelp, GuiResourcesD.HELP);
if (btnOptions != null) {
btnOptions
.setFixedIcon(getAppD().getScaledIcon(GuiResourcesD.TOOL));
}
}
private void getScaledIcon(AbstractButton target, GuiResourcesD tool) {
target.setIcon(getAppD().getScaledIcon(tool));
}
}