package org.geogebra.web.web.gui.util; import java.util.HashMap; import org.geogebra.common.euclidian.event.PointerEventType; import org.geogebra.common.gui.util.SelectionTable; import org.geogebra.common.main.App; import org.geogebra.common.main.Feature; import org.geogebra.common.util.debug.Log; import org.geogebra.web.html5.gui.GPopupPanel; import org.geogebra.web.html5.gui.util.ClickStartHandler; import org.geogebra.web.html5.gui.util.Slider; import org.geogebra.web.html5.gui.util.SliderInputHandler; import org.geogebra.web.html5.main.AppW; import org.geogebra.web.web.css.GuiResources; import org.geogebra.web.web.euclidian.EuclidianStyleBarW; import org.geogebra.web.web.gui.images.ImgResourceHelper; 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.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEndHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.PopupPanel; /** * Table popup for selecting properties of objects * */ public class PopupMenuButtonW extends MyCJButton implements ChangeHandler, SliderInputHandler { /** * App */ protected AppW app; /** * Icons / texts in the table */ protected ImageOrText[] data; private ButtonPopupMenu myPopup; private PopupMenuHandler popupHandler; private Slider mySlider; private Label sliderLabel; private SelectionTableW myTable; private boolean hasTable; /** flag to determine if the popup should persist after a mouse click */ private boolean keepVisible = true; private boolean isIniting = true; private ImageOrText fixedIcon; private boolean isFixedIcon = false; private boolean multiselectionEnabled = false; private StyleBarW2 changeEventHandler; protected HashMap<Integer, Integer> lineStyleMap; protected FlowPanel sliderPanel; /** * @param app * {@link AppW} * @param data * {@link ImageOrText} * @param rows * {@code Integer} * @param columns * {@code Integer} * @param mode * {@link SelectionTableW} */ public PopupMenuButtonW(App app, ImageOrText[] data, Integer rows, Integer columns, SelectionTable mode) { this(app, data, rows, columns, mode, true, false, null); } /** * @param app * {@link AppW} * @param data * {@link ImageOrText} * @param rows * {@code Integer} * @param columns * {@code Integer} * @param mode * {@link SelectionTableW} * @param hasTable * {@code boolean} * @param hasSlider * {@code boolean} * @param lineStyleMap0 * maps item index to line style */ public PopupMenuButtonW(App app, ImageOrText[] data, Integer rows, Integer columns, SelectionTable mode, final boolean hasTable, boolean hasSlider, HashMap<Integer, Integer> lineStyleMap0) { this(app, data, rows, columns, mode, hasTable, hasSlider, null, lineStyleMap0); } /** * @param app * {@link AppW} * @param data * {@link ImageOrText} * @param rows * {@code Integer} * @param columns * {@code Integer} * @param mode * {@link SelectionTableW} * @param hasTable * {@code boolean} * @param hasSlider * {@code boolean} * @param selected * which items are selected * @param lineStyleMap0 * maps item index to line style */ public PopupMenuButtonW(App app, ImageOrText[] data, Integer rows, Integer columns, SelectionTable mode, final boolean hasTable, boolean hasSlider, boolean[] selected, HashMap<Integer, Integer> lineStyleMap0) { super(); this.app = (AppW) app; this.hasTable = hasTable; this.lineStyleMap = lineStyleMap0; if (selected != null) { multiselectionEnabled = true; } createPopup(); // add a mouse listener to our button that triggers the popup addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { event.stopPropagation(); } }); // merge mousedown + touchstart ClickStartHandler.init(this, new ClickStartHandler(true, true) { @Override public void onClickStart(int x, int y, PointerEventType type) { if (!PopupMenuButtonW.this.isEnabled()) { return; } handleClick(); } }); addDomHandler(new TouchEndHandler() { @Override public void onTouchEnd(TouchEndEvent event) { event.stopPropagation(); } }, TouchEndEvent.getType()); if (hasTable) { createSelectionTable(data, rows, columns, mode, selected); } if (selected != null) { // myTable.initSelectedItems(selected); // TODO remove? } // create slider if (hasSlider) { getMySlider(); } isIniting = false; } /** * creates a new {@link ButtonPopupMenu} */ private void createPopup() { myPopup = new ButtonPopupMenu(app.getPanel()) { @Override public void setVisible(boolean visible) { super.setVisible(visible); // if another button is pressed only the visibility is changed, // by firing the event we can react as if it was closed CloseEvent.fire(this, this, false); } @Override public void hide() { super.hide(); if (EuclidianStyleBarW.getCurrentPopup().equals(this)) { EuclidianStyleBarW.setCurrentPopup(null); } } }; myPopup.setAutoHideEnabled(true); } /** * handle click on {@link PopupMenuButtonW this button} */ void handleClick() { onClickAction(); if (EuclidianStyleBarW.getCurrentPopup() != myPopup) { if (EuclidianStyleBarW.getCurrentPopup() != null) { EuclidianStyleBarW.getCurrentPopup().hide(); } EuclidianStyleBarW.setCurrentPopup(myPopup); app.registerPopup(myPopup); myPopup.showRelativeTo(getWidget()); myPopup.getFocusPanel().getElement().focus(); } else { myPopup.setVisible(false); EuclidianStyleBarW.setCurrentPopup(null); } } /** * @param newData * icons * @param rows * number of rows * @param columns * number of columns * @param mode * selection mode */ private void createSelectionTable(ImageOrText[] newData, Integer rows, Integer columns, SelectionTable mode, boolean[] selected) { this.data = newData; myTable = new SelectionTableW(newData, rows, columns, mode, multiselectionEnabled); if (!multiselectionEnabled) { setSelectedIndex(0); } else { myTable.initSelectedItems(selected); } myTable.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { handlePopupActionEvent(); } }); myPopup.getPanel().add(myTable); } /** * Show or hide item at given position * * @param col * item column * @param show * visibility flag */ protected void showTableItem(int col, boolean show) { myTable.getWidget(0, col).setVisible(show); } /** * @param popupMenuHandler * {@link PopupMenuHandler} */ public void addPopupHandler(PopupMenuHandler popupMenuHandler) { this.popupHandler = popupMenuHandler; } /** * @return {@link PopupPanel} */ public GPopupPanel getMyPopup() { return myPopup; } /** * @return {@link SelectionTableW} */ public SelectionTableW getMyTable() { return myTable; } /** * called by click on button */ protected void onClickAction() { // called by click on button // overridden in (e.g.) EuclidianStayleBar3DW } /** * Pass a popup action event up to the button invoker. If the first button * click triggered our popup (the click was in the triangle region), then we * must pass action events from the popup to the invoker */ public void handlePopupActionEvent(){ if (popupHandler != null) { popupHandler.fireActionPerformed(this); } else { Log.debug("PopupMenubutton has null popupHandler"); } updateGUI(); if(!keepVisible) { myPopup.hide(); } } private void updateGUI(){ if(isIniting) { return; } if (hasTable && !multiselectionEnabled) { setIcon(getButtonIcon()); } } // ============================================== // Icon Handling // ============================================== /** * @return {@link ImageOrText} */ public ImageOrText getButtonIcon() { return data[getSelectedIndex()]; } /** * Append a downward triangle image to the right hand side of an input icon. */ @Override public void setIcon(ImageOrText icon) { if (isFixedIcon) { super.setIcon(fixedIcon); return; } // add a down_triangle image to the left of the icon if (icon != null) { super.setIcon(icon); } else { ImgResourceHelper.setIcon(GuiResources.INSTANCE.toolbar_further_tools(), this); } } /** * @param icon * {@link ImageOrText} */ public void setFixedIcon(ImageOrText icon){ isFixedIcon = true; this.fixedIcon = icon; setIcon(icon); } /** * @param selectedIndex * {@code Integer} */ public void setSelectedIndex(Integer selectedIndex) { myTable.setSelectedIndex(selectedIndex == null ? -1 :selectedIndex.intValue()); updateGUI(); } /** * @param index * index to be changed * @param selected * target value for that index */ public void changeMultiSelection(int index, boolean selected) { myTable.changeMultiSelection(index, selected); updateGUI(); } @Override public void onChange(ChangeEvent event) { Log.debug("onchange"); onSliderInput(); } @Override public void onSliderInput() { if(mySlider != null) { setSliderValue(mySlider.getValue()); } if (changeEventHandler != null) { changeEventHandler.fireActionPerformed(this); } else { if (app.has(Feature.DYNAMIC_STYLEBAR)) { // needed checking if stylebar exists: don't create EV stylebar if (app.getActiveEuclidianView().hasStyleBar()) { ((EuclidianStyleBarW) app.getActiveEuclidianView().getStyleBar()).fireActionPerformed(this); } if (app.getActiveEuclidianView().hasDynamicStyleBar()) { ((EuclidianStyleBarW) app.getActiveEuclidianView().getDynamicStyleBar()).fireActionPerformed(this); } } else { ((EuclidianStyleBarW) app.getActiveEuclidianView().getStyleBar()).fireActionPerformed(this); } } fireActionPerformed(); updateGUI(); } /** * Fires on index change */ protected void fireActionPerformed() { //implemented in subclass } /** * @return {@link Slider} */ public Slider getMySlider() { if (mySlider == null) { initSlider(); } return mySlider; } public void showSlider(boolean show) { if (show) { getMySlider().setVisible(show); sliderLabel.setVisible(show); } else if (mySlider != null) { mySlider.setVisible(false); sliderLabel.setVisible(false); } } private void initSlider() { mySlider = new Slider(0,100); mySlider.setMajorTickSpacing(25); mySlider.setMinorTickSpacing(5); mySlider.addChangeHandler(this); Slider.addInputHandler(mySlider.getElement(), this); sliderLabel = new Label(); sliderPanel = new FlowPanel(); sliderPanel.add(mySlider); sliderPanel.add(sliderLabel); sliderLabel.addStyleName("popupSliderLabel"); sliderPanel.addStyleName("panelRow"); if (app.has(Feature.COLORPOPUP_IMPROVEMENTS)) { sliderPanel.addStyleName("panelRow2"); } myPopup.getPanel().add(sliderPanel); if (!app.has(Feature.COLORPOPUP_IMPROVEMENTS)) { Label placeholder = new Label(); placeholder.setHeight("8px"); myPopup.getPanel().add(placeholder); } } /** * @param value * {@code int} */ public void setSliderValue(int value) { if (mySlider == null) { return; } mySlider.setValue(value); setSliderText(value + getSliderPostfix()); updateGUI(); } /** * Sets the value next to the slider. * * @param text * the value string. */ protected void setSliderText(String text) { sliderLabel.setText(text); } /** * * @return The postix string after the value of the slider. */ protected String getSliderPostfix() { return ""; } /** * @return selected index of the table */ public int getSelectedIndex() { return myTable.getSelectedIndex(); } /** * @param index * index * @return whether item with given index is selected */ public boolean isSelected(int index) { return myTable.isSelected(index); } /** * @param array * elements (usually GeoElements) whose state is displayed in * this table * @return */ public void update(Object[] array) { // will be overwritten from instances } /** * @return selected Object of the {@link SelectionTableW table} */ public Object getSelectedValue() { return myTable.getSelectedValue(); } /** * @return {@code int} or {@code -1} if the {@link Slider slider} is null */ public int getSliderValue() { return mySlider == null ? -1 : mySlider.getValue(); } /** * @param keepVisible * {@code boolean} */ public void setKeepVisible(boolean keepVisible) { this.keepVisible = keepVisible; } /** * @return {@code true} if it has a slider, {@code false} otherwise */ protected boolean hasSlider() { return mySlider != null; } /** * explicitly sets who should receive the change events * * @param handler * change handler */ public void setChangeEventHandler(StyleBarW2 handler){ this.changeEventHandler = handler; } }