package org.geogebra.web.web.gui.dialog;
import java.util.ArrayList;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.gui.dialog.ToolCreationDialogModel;
import org.geogebra.common.gui.dialog.ToolInputOutputListener;
import org.geogebra.common.javax.swing.GOptionPane;
import org.geogebra.common.kernel.Macro;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.kernelND.GeoElementND;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Feature;
import org.geogebra.common.main.GeoElementSelectionListener;
import org.geogebra.common.main.Localization;
import org.geogebra.common.util.AsyncOperation;
import org.geogebra.web.html5.gui.tooltip.ToolTipManagerW;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.web.gui.ToolNameIconPanelW;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TabPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* Dialog to create a new user defined tool
*/
public class ToolCreationDialogW extends DialogBoxW implements
GeoElementSelectionListener, ClickHandler, ToolInputOutputListener {
private AppW app;
/**
* The underlying ToolModel, managing all input and output lists
*/
ToolCreationDialogModel toolModel;
// Widgets
private Button btBack;
private Button btNext;
private Button btCancel;
private VerticalPanel mainWidget;
private FlowPanel bottomWidget;
private TabPanel tabPanel;
private ListBox outputAddLB, outputLB;
private ListBox inputAddLB, inputLB;
private ToolNameIconPanelW toolNameIconPanel;
private AsyncOperation<Macro> returnHandler;
private Localization loc;
/**
* Creates new tool creation dialog, if in macro-editing mode,
*
* @param app
* Aplication to which this dialog belongs
*/
public ToolCreationDialogW(App app) {
super(false, false, null, ((AppW) app).getPanel());
if (app.has(Feature.DIALOGS_OVERLAP_KEYBOARD)) {
setOverlapFeature(true);
}
this.app = (AppW) app;
this.loc = app.getLocalization();
createGUI();
toolModel = new ToolCreationDialogModel(app, this);
Macro appMacro = app.getMacro();
if (appMacro != null) {
this.setFromMacro(appMacro); // TODO
}
}
private void setFromMacro(Macro macro) {
toolNameIconPanel.setMacro(macro);
toolModel.setFromMacro(macro);
}
/**
* Creates new tool creation dialog, if in macro-editing mode, if launched
* from another Dialog this can be use to return to that dialog again. The
* returnHandler is passed the newly created {@link Macro} if successful or
* null if unsuccessful.
*
* @param app
* Application to which this dialog belongs
* @param returnHandler
* the {@link AsyncOperation} handling the resulting
* {@link Macro}
*
*/
public ToolCreationDialogW(AppW app, AsyncOperation<Macro> returnHandler) {
this(app);
this.returnHandler = returnHandler;
}
@Override
public void setVisible(boolean flag) {
super.setVisible(flag);
if (flag) {
// add all possible output and input elements to add Lists
toolModel.initAddLists();
// add all currently selected geos to output list
toolModel.addSelectedGeosToOutput();
app.setMoveMode();
app.getSelectionManager().addSelectionListener(this);
} else {
app.getSelectionManager().removeSelectionListener(this);
}
}
private void createGUI() {
addStyleName("toolCreationDialog");
addStyleName("GeoGebraPopup");
getCaption().setText(loc.getMenu("Tool.CreateNew"));
setWidget(mainWidget = new VerticalPanel());
mainWidget.add(tabPanel = new TabPanel());
// Create panel with ListBoxes for input and output objects and
// add ChangeHandler
outputAddLB = new ListBox();
outputAddLB.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
// added empty option at top, so use index-1
toolModel.addToOutput(outputAddLB.getSelectedIndex() - 1);
}
});
outputLB = new ListBox();
VerticalPanel outputObjectPanel = createInputOutputPanel(outputAddLB,
outputLB);
inputAddLB = new ListBox();
inputAddLB.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
// added empty option at top, so use index-1
toolModel.addToInput(inputAddLB.getSelectedIndex() - 1);
}
});
inputLB = new ListBox();
VerticalPanel inputObjectPanel = createInputOutputPanel(inputAddLB,
inputLB);
toolNameIconPanel = new ToolNameIconPanelW(app);
// Create tabPanel and add Selectionhandler
tabPanel.add(outputObjectPanel, loc.getMenu("OutputObjects"));
tabPanel.selectTab(0);
tabPanel.add(inputObjectPanel, loc.getMenu("InputObjects"));
tabPanel.add(toolNameIconPanel, loc.getMenu("NameIcon"));
SelectionHandler<Integer> handler = getSelectionHandler();
tabPanel.addSelectionHandler(handler);
// Create button navigation
createNavigation();
}
/**
* Assembles the input or output listboxes in a VerticalPanel adding the
* up/down/remove buttons on the right
*
* @param addLB
* dropdown Listbox for adding the inputs or outputs
* @param lB
* multiselect, multiline Listbox with the used input and output
* elements
* @return the panel containing the listboxes and controls
*/
VerticalPanel createInputOutputPanel(ListBox addLB, ListBox lB) {
lB.setVisibleItemCount(7);
lB.setMultipleSelect(true);
VerticalPanel inputPanel = new VerticalPanel();
Label labelInputAdd = new Label(loc.getMenu("Tool.SelectObjects"));
inputPanel.add(labelInputAdd);
FlowPanel addListUpDownPanel = new FlowPanel();
addListUpDownPanel.add(addLB);
HorizontalPanel upDownRemovePanel = new HorizontalPanel();
upDownRemovePanel.add(lB);
upDownRemovePanel.add(createListUpDownRemovePanel());
upDownRemovePanel.setCellWidth(lB, "80%"); // TODO
addListUpDownPanel.add(upDownRemovePanel);
inputPanel.add(addListUpDownPanel);
return inputPanel;
}
private VerticalPanel createListUpDownRemovePanel() {
Button btUp = new Button("\u25b2");
btUp.setTitle(loc.getMenu("Up"));
btUp.addClickHandler(this);
btUp.getElement().getStyle().setMargin(3, Style.Unit.PX);
Button btDown = new Button("\u25bc");
btDown.setTitle(loc.getMenu("Down"));
btDown.addClickHandler(this);
btDown.getElement().getStyle().setMargin(3, Style.Unit.PX);
Button btRemove = new Button("\u2718");
btRemove.setTitle(loc.getMenu("Remove"));
btRemove.addClickHandler(this);
btRemove.getElement().getStyle().setMargin(3, Style.Unit.PX);
VerticalPanel upDownRemovePanel = new VerticalPanel();
upDownRemovePanel.add(btUp);
upDownRemovePanel.add(btDown);
upDownRemovePanel.add(btRemove);
return upDownRemovePanel;
}
private SelectionHandler<Integer> getSelectionHandler() {
SelectionHandler<Integer> handler = new SelectionHandler<Integer>() {
@Override
public void onSelection(SelectionEvent<Integer> event) {
int tab = event.getSelectedItem();
updateBackNextButtons(tab);
}
};
return handler;
}
/**
* @param tab
* selected tab
*/
@SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "OK to fall through here" })
protected void updateBackNextButtons(int tab) {
btBack.setEnabled(tab > 0);
switch (tab) {
case 1: // input objects
toolModel.updateInputList();
// fall through
case 0: // output objects
btNext.setText(loc.getMenu("Next") + " >");
btNext.setEnabled(true);
break;
case 2: // name panel (finish)
if (toolModel.createTool()) {
btNext.setText(loc.getMenu("Finish"));
btNext.setEnabled(true);
} else {
btNext.setEnabled(false);
}
break;
default:
break;
}
}
private void createNavigation() {
mainWidget.add(bottomWidget = new FlowPanel());
bottomWidget.setStyleName("DialogButtonPanel");
// buttons
btBack = new Button("< " + loc.getMenu("Back"));
btBack.addClickHandler(this);
btBack.setEnabled(false);
btBack.getElement().getStyle().setMargin(3, Style.Unit.PX);
btNext = new Button(loc.getMenu("Next") + " >");
btNext.addClickHandler(this);
btNext.getElement().getStyle().setMargin(3, Style.Unit.PX);
btCancel = new Button(loc.getMenu("Cancel"));
btCancel.addStyleName("cancelBtn");
btCancel.addClickHandler(this);
btCancel.getElement().getStyle().setMargin(3, Style.Unit.PX);
bottomWidget.add(btBack);
bottomWidget.add(btNext);
bottomWidget.add(btCancel);
}
@Override
public void geoElementSelected(GeoElement geo, boolean addToSelection) {
int selectedTab = tabPanel.getTabBar().getSelectedTab();
switch (selectedTab) {
case 0: // output objects
toolModel.addToOutput(geo);
break;
case 1: // input objects
toolModel.addToInput(geo);
break;
default:
}
}
@Override
public void onClick(ClickEvent e) {
Element target = e.getNativeEvent().getEventTarget().cast();
int selectedTab = tabPanel.getTabBar().getSelectedTab();
if (target == btBack.getElement()) {
tabPanel.selectTab(selectedTab - 1);
} else if (target == btNext.getElement()) {
if (selectedTab == tabPanel.getTabBar().getTabCount() - 1) {
finish();
} else {
tabPanel.selectTab(selectedTab + 1);
}
} else if (target == btCancel.getElement()) {
setVisible(false);
callHandler();
hide();
} else {
ArrayList<Integer> selIndices = new ArrayList<Integer>();
switch (selectedTab) {
default:
// do nothing
break;
case 0:
if (outputLB.getSelectedIndex() >= 0) {
for (int i = 0; i < outputLB.getItemCount(); i++) {
if (outputLB.isItemSelected(i)) {
selIndices.add(i);
}
}
if (target.getTitle().equals(loc.getMenu("Down"))) {
toolModel.moveOutputDown(selIndices);
} else if (target.getTitle().equals(loc.getMenu("Up"))) {
toolModel.moveOutputUp(selIndices);
} else if (target.getTitle().equals(loc.getMenu("Remove"))) {
toolModel.removeFromOutput(selIndices);
}
}
break;
case 1:
selIndices = new ArrayList<Integer>();
if (inputLB.getSelectedIndex() >= 0) {
for (int i = 0; i < inputLB.getItemCount(); i++) {
if (inputLB.isItemSelected(i)) {
selIndices.add(i);
}
}
if (target.getTitle().equals(loc.getMenu("Down"))) {
toolModel.moveInputDown(selIndices);
} else if (target.getTitle().equals(loc.getMenu("Up"))) {
toolModel.moveInputUp(selIndices);
} else if (target.getTitle().equals(loc.getMenu("Remove"))) {
toolModel.removeFromInput(selIndices);
}
}
}
}
}
private void finish() {
final App appToSave;
if (app.getMacro() != null) {
appToSave = app.getMacro().getKernel().getApplication();
} else {
appToSave = app;
}
final String commandName = toolNameIconPanel.getCommandName();
if (appToSave.getKernel().getMacro(commandName) != null) {
String[] options = { loc.getMenu("Tool.Replace"),
loc.getMenu("Tool.DontReplace") };
app.getGuiManager()
.getOptionPane()
.showOptionDialog(
app,
app.getLocalization().getPlain(
"Tool.ReplaceQuestion", commandName),
loc.getMenu("Question"),
0,
GOptionPane.QUESTION_MESSAGE, null, options,
new AsyncOperation<String[]>() {
@Override
public void callback(String[] dialogResult) {
if ("0".equals(dialogResult[0])) {
saveMacro(appToSave);
}
}
});
} else {
saveMacro(appToSave);
}
}
/**
* Finish creation of user defined tool. Overwrites an existing macro with
* the macro (without warning) if macros are compatible
*
* @param appToSave
* application
*/
void saveMacro(final App appToSave) {
final String commandName = toolNameIconPanel.getCommandName();
final String toolName = toolNameIconPanel.getToolName();
final String toolHelp = toolNameIconPanel.getToolHelp();
final boolean showInToolBar = toolNameIconPanel.getShowTool();
final String iconFileName = toolNameIconPanel.getIconFileName();
boolean success = toolModel.finish(appToSave, commandName, toolName,
toolHelp, showInToolBar, iconFileName);
if (success) {
if (returnHandler == null) {
ToolTipManagerW.sharedInstance().showBottomMessage(
loc.getMenu("Tool.CreationSuccess"), true, app);
}
} else {
app.getGuiManager()
.getOptionPane()
.showConfirmDialog(app, loc.getMenu("Tool.NotCompatible"),
app.getLocalization().getError("Error"),
GOptionPane.OK_OPTION, GOptionPane.ERROR_MESSAGE,
null);
}
if (app.isToolLoadedFromStorage()) {
app.storeMacro(app.getMacro(), true);
}
if (success) {
setVisible(false);
callHandler();
hide();
}
}
private void callHandler() {
if (returnHandler != null) {
returnHandler.callback(toolModel.getNewTool());
returnHandler = null;
} else {
app.getActiveEuclidianView().requestFocusInWindow();
}
}
@Override
public void updateLists() {
updateListBox(outputAddLB, toolModel.getOutputAddList(), true);
updateListBox(outputLB, toolModel.getOutputList(), false);
updateListBox(inputAddLB, toolModel.getInputAddList(), true);
updateListBox(inputLB, toolModel.getInputList(), false);
}
private static void updateListBox(ListBox lb, GeoElementND[] geos,
boolean addList) {
lb.clear();
if (addList) {
lb.addItem(" ");
}
for (int i = 0; i < geos.length; i++) {
lb.addItem(geos[i].getLongDescription());
SelectElement selectElement = SelectElement.as(lb.getElement());
NodeList<OptionElement> options = selectElement.getOptions();
options.getItem(options.getLength() - 1).getStyle()
.setColor(GColor.getColorString(geos[i].getAlgebraColor()));
}
}
}