/********************************************** * Copyright (C) 2010 Lukas Laag * This file is part of svgreal. * * svgreal 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, either version 3 of the License, or * (at your option) any later version. * * svgreal is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with svgreal. If not, see http://www.gnu.org/licenses/ **********************************************/ package org.vectomatic.svg.edit.client; import java.util.HashMap; import java.util.Map; import org.vectomatic.dom.svg.OMSVGSVGElement; import org.vectomatic.dom.svg.ui.SVGImage; import org.vectomatic.dom.svg.utils.SVGConstants; import org.vectomatic.svg.edit.client.command.EditTitleCommandFactory; import org.vectomatic.svg.edit.client.engine.SVGModel; import org.vectomatic.svg.edit.client.event.RotationEvent; import org.vectomatic.svg.edit.client.event.RotationHandler; import org.vectomatic.svg.edit.client.gxt.layout.AbsoluteLayerLayout; import org.vectomatic.svg.edit.client.gxt.layout.AbsoluteLayerLayoutData; import org.vectomatic.svg.edit.client.gxt.widget.Compass; import org.vectomatic.svg.edit.client.gxt.widget.KeyNavExt; import org.vectomatic.svg.edit.client.gxt.widget.SVGTreePanelDragSource; import org.vectomatic.svg.edit.client.gxt.widget.SVGTreePanelDropTarget; import org.vectomatic.svg.edit.client.gxt.widget.TreePanelExt; import org.vectomatic.svg.edit.client.model.ModelConstants; import org.vectomatic.svg.edit.client.model.ValidationError.Severity; import org.vectomatic.svg.edit.client.model.svg.SVGElementModel; import org.vectomatic.svg.edit.client.utils.DecoratedImageCache.HAlign; import org.vectomatic.svg.edit.client.utils.DecoratedImageCache.VAlign; import com.extjs.gxt.ui.client.Style; import com.extjs.gxt.ui.client.Style.LayoutRegion; import com.extjs.gxt.ui.client.data.ChangeEvent; import com.extjs.gxt.ui.client.data.ChangeListener; import com.extjs.gxt.ui.client.data.ModelData; import com.extjs.gxt.ui.client.data.ModelIconProvider; import com.extjs.gxt.ui.client.dnd.DND.Feedback; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.DragEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.SelectionChangedEvent; import com.extjs.gxt.ui.client.event.SelectionProvider; import com.extjs.gxt.ui.client.event.SelectionService; import com.extjs.gxt.ui.client.event.SliderEvent; import com.extjs.gxt.ui.client.event.TreePanelEvent; import com.extjs.gxt.ui.client.store.Record; import com.extjs.gxt.ui.client.store.TreeStore; import com.extjs.gxt.ui.client.util.Margins; import com.extjs.gxt.ui.client.util.Rectangle; import com.extjs.gxt.ui.client.util.Size; import com.extjs.gxt.ui.client.widget.Component; import com.extjs.gxt.ui.client.widget.Editor; import com.extjs.gxt.ui.client.widget.LayoutContainer; import com.extjs.gxt.ui.client.widget.Slider; import com.extjs.gxt.ui.client.widget.Window; import com.extjs.gxt.ui.client.widget.form.TextField; import com.extjs.gxt.ui.client.widget.layout.BorderLayout; import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData; import com.extjs.gxt.ui.client.widget.layout.FitData; import com.extjs.gxt.ui.client.widget.layout.FitLayout; import com.extjs.gxt.ui.client.widget.menu.Menu; import com.extjs.gxt.ui.client.widget.treepanel.TreePanel.TreeNode; import com.extjs.gxt.ui.client.widget.treepanel.TreePanelSelectionModel; import com.extjs.gxt.ui.client.widget.treepanel.TreePanelView; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.ui.AbstractImagePrototype; /** * GXT window class dedicated to displaying and editing * a single SVG image. The window has several layers: the * bottom layer contains the SVG image itself and the top * layer contains widgets to manipulate it (rotation compass * and scale slider). * @author laaglu */ public class SVGWindow extends Window { public static final String WINDOW_ACTIVE_STYLE = "x-window-active"; /** * The SVG model backing this window */ private SVGModel svgModel; /** * The SVG rotation compass */ protected Compass compass; /** * The SVG scale slider */ protected Slider scaleSlider; /** * The navigation tree */ protected TreePanelExt<SVGElementModel> tree; /** * The contextual menu */ protected Menu contextMenu; /** * True when the context menu is displayed */ protected boolean displaysContextMenu; /** * To control keyboard input */ protected KeyNavExt<ComponentEvent> keyNav; /** * The drag'n'drop source */ protected SVGTreePanelDragSource dndSource; /** * The drag'n'drop source */ protected SVGTreePanelDropTarget dndTarget; /** * Constructor * @param svgModel * The SVG model to display */ public SVGWindow(final SVGModel svgModel) { super(); keyNav = new KeyNavExt<ComponentEvent>(this) { @Override public void onKeyPress(ComponentEvent ce) { svgModel.onKeyPress(ce); } @Override public void onKeyUp(ComponentEvent ce) { svgModel.onKeyUp(ce); } }; this.svgModel = svgModel; setPlain(true); setMaximizable(true); setMinWidth(200); setMinHeight(170); // Listen to model name changes svgModel.getRoot().addChangeListener(new ChangeListener() { @Override public void modelChanged(ChangeEvent event) { String title = event.getSource().get(SVGConstants.SVG_TITLE_ATTRIBUTE); String heading = getHeading(); if (!heading.equals(title)) { setHeading(title); } } }); ///////////////////////////////////////////////// // A CSS multi-layer container // The container hierarchy is as follows: // splitterPanel (LayoutContainer + BorderLayout) // tree (TreePanel) // layersContainer (LayoutContainer + AbsoluteLayerLayout) // svgContainer (LayoutContainer) // image (SVGImage) // compass (SVGImage) // scaleSlider (Slider) ///////////////////////////////////////////////// LayoutContainer splitterPanel = new LayoutContainer(); splitterPanel.setLayout(new BorderLayout()); LayoutContainer layersContainer = new LayoutContainer(); GWT.log("borders: " + getBorders()); layersContainer.setLayout(new AbsoluteLayerLayout()); // Create the contextual menu contextMenu = new Menu(); // Create the SVG view LayoutContainer svgContainer = new LayoutContainer() { @Override protected void onShowContextMenu(int x, int y) { GWT.log("SVGWindow.onShowContextMenu"); displaysContextMenu = true; svgModel.updateContextMenu(contextMenu); super.onShowContextMenu(x, y); } @Override protected void onHideContextMenu() { GWT.log("SVGWindow.onHideContextMenu"); displaysContextMenu = false; } }; svgContainer.setLayout(new FitLayout() { @Override protected void setItemSize(Component item, Size size) { GWT.log("setItemSize(" + size + ")"); // super.setItemSize(item, size); svgModel.setWindowRect(size.width, size.height); } }); svgContainer.setContextMenu(contextMenu); svgContainer.setScrollMode(Style.Scroll.AUTO); svgContainer.setStyleAttribute("background-color", SVGConstants.CSS_WHITE_VALUE); OMSVGSVGElement svg = svgModel.getSvgElement(); SVGImage image = new SVGImage(svg) { protected void onAttach() { GWT.log("onAttach"); svgModel.onAttach(); } }; svgContainer.add(image); layersContainer.add(svgContainer, new AbsoluteLayerLayoutData( AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_LEFT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP, 0, 0, 0, 0, 10)); // Trap mouse events to update grid rulers svgContainer.addDomHandler(svgModel.getGrid().getMouseMoveHandler(), MouseMoveEvent.getType()); // Create the tree view TreeStore<SVGElementModel> treeStore = svgModel.getStore(); tree = new TreePanelExt<SVGElementModel>(treeStore) { @Override protected void onShowContextMenu(int x, int y) { displaysContextMenu = true; svgModel.updateContextMenu(contextMenu); super.onShowContextMenu(x, y); } @Override protected void onHideContextMenu() { displaysContextMenu = false; } @Override protected void onDoubleClick(TreePanelEvent tpe) { TreeNode treeNode = tree.findNode(tpe.getTarget()); SVGElementModel model = treeNode.getModel(); renameModel(model); } }; tree.setView(new TreePanelView<SVGElementModel>() { @Override public void onSelectChange(SVGElementModel model, boolean select) { super.onSelectChange(model, select); if (svgModel.isHighlightingMode()) { svgModel.displayTwin(model, select); } } }); tree.setSelectionModel(svgModel.getSelectionModel()); tree.setContextMenu(contextMenu); tree.setIconProvider(new ModelIconProvider<SVGElementModel>() { Map<Severity, ImageResource> severityToResource = new HashMap<Severity, ImageResource>(); { severityToResource.put(Severity.WARNING, AppBundle.INSTANCE.warning()); severityToResource.put(Severity.ERROR, AppBundle.INSTANCE.error()); severityToResource.put(null, null); } @Override public AbstractImagePrototype getIcon(SVGElementModel model) { Severity severity = model.getSeverity(); return SvgrealApp.getApp().getImageCache().getImageWithDecoration(model.getMetaModel().getIcon(), severityToResource.get(severity), HAlign.LEFT, VAlign.BOTTOM); } }); // tree.setCheckable(true); tree.setWidth(150); tree.setDisplayProperty(SVGConstants.SVG_TITLE_TAG); tree.setTrackMouseOver(true); tree.setStyleAttribute("background-color", SVGConstants.CSS_WHITE_VALUE); tree.setAutoExpand(true); // ToolTipConfig tooltipConfig = new ToolTipConfig(); // tooltipConfig.setTrackMouse(true); // tooltipConfig.setDismissDelay(0); // tree.setToolTip(tooltipConfig); ///////////// Configure highlighting svg.addMouseOverHandler(new MouseOverHandler() { @Override public void onMouseOver(MouseOverEvent event) { if (!displaysContextMenu) { svgModel.setHighlightingMode(true); } } }); svg.addMouseOutHandler(new MouseOutHandler() { @Override public void onMouseOut(MouseOutEvent event) { if (!displaysContextMenu) { svgModel.setHighlightingMode(false); } } }); tree.addListener(Events.OnMouseOver, new Listener<TreePanelEvent<SVGElementModel>>() { public void handleEvent(TreePanelEvent<SVGElementModel> be) { // ToolTipConfig tooltipConfig = tree.getToolTip().getToolTipConfig(); // String desc = null; if (!displaysContextMenu) { svgModel.setHighlightingMode(true); SVGElementModel model = be.getItem(); if (model != null) { svgModel.highlightModel(model); // if (model instanceof SVGNamedElementModel) { // desc = model.get(SVGConstants.SVG_DESC_TAG); // } } } // if (desc != null) { // if (!desc.equals(tooltipConfig.getTitle())) { // tooltipConfig.setTitle(desc); // tree.getToolTip().show(); // } // } else { // tree.getToolTip().hide(); // } } }); tree.addListener(Events.OnMouseOut, new Listener<TreePanelEvent<SVGElementModel>>() { public void handleEvent(TreePanelEvent<SVGElementModel> be) { if (!displaysContextMenu) { svgModel.setHighlightingMode(false); // tree.setToolTip((String)null); } } }); ///////////// Configure drag'n'drop dndSource = new SVGTreePanelDragSource(this); dndTarget = new SVGTreePanelDropTarget(this); dndTarget.setAllowSelfAsSource(true); dndTarget.setFeedback(Feedback.BOTH); ///////////// Top-level layout BorderLayoutData layoutData = new BorderLayoutData(LayoutRegion.WEST, 150, 100, 250); layoutData.setMargins(new Margins(0, 5, 0, 0)); layoutData.setSplit(true); layoutData.setCollapsible(true); splitterPanel.add(tree, layoutData); splitterPanel.add(layersContainer, new BorderLayoutData(LayoutRegion.CENTER)); ///////////////////////////////////////////////// // Populate the higher layer ///////////////////////////////////////////////// // Create the compass compass = GWT.create(Compass.class); final OMSVGSVGElement compassSvg = compass.getSvgElement(); compassSvg.getStyle().setWidth(100, Unit.PX); compassSvg.getStyle().setHeight(100, Unit.PX); compass.addRotationHandler(new RotationHandler() { @Override public void onRotate(RotationEvent event) { svgModel.setRotation(event.getAngle()); } }); LayoutContainer compassContainer = new LayoutContainer(); AppCss css = AppBundle.INSTANCE.css(); compassContainer.addStyleName(css.compassContainer()); SVGImage compassImage = new SVGImage(compassSvg); compassImage.addClassNameBaseVal(css.compass()); compassContainer.add(compassImage); layersContainer.add(compassContainer, new AbsoluteLayerLayoutData( AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_RIGHT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP, 0, 0, 0, 0, 20)); // Create the scale slider LayoutContainer sliderContainer = new LayoutContainer(); sliderContainer.addStyleName(css.scaleSliderContainer()); scaleSlider = new Slider() { @Override protected String onFormatValue(int value) { return Integer.toString((int)(svgModel.getScale() * 100)) + "%"; } }; scaleSlider.addStyleName(css.scaleSlider()); sliderContainer.add(scaleSlider); scaleSlider.setHeight(100); scaleSlider.setMinValue(0); scaleSlider.setMaxValue(100); scaleSlider.setIncrement(1); scaleSlider.setValue(50); scaleSlider.setVertical(true); layersContainer.add(sliderContainer, new AbsoluteLayerLayoutData( AbsoluteLayerLayoutData.HORIZONTAL_ATTACH_RIGHT | AbsoluteLayerLayoutData.VERTICAL_ATTACH_TOP, 0, 0, 0, 0, 20)); scaleSlider.addListener(Events.Change, new Listener<SliderEvent>() { @Override public void handleEvent(SliderEvent be) { // Convert from slider unit to transform unit int value = be.getNewValue(); float scale; if (value >= 50) { scale = 1f + (value - 50f) / 10f * 4 / 5; } else { scale = 1f / (1f + (49 - value) / 10f * 4 / 5); } svgModel.setScale(scale); } }); // ToolBar toolBar = new ToolBar(); // ToggleButton selectButton = new ToggleButton(); // selectButton.setIcon(AbstractImagePrototype.create(AppBundle.INSTANCE.cursor())); // selectButton.addSelectionListener(new SelectionListener<ButtonEvent>() { // @Override // public void componentSelected(ButtonEvent ce) { // model.setSelectionMode(((ToggleButton)ce.getButton()).isPressed()); // } // }); // toolBar.add(selectButton); // setTopComponent(toolBar); setLayout(new FitLayout()); add(splitterPanel, new FitData(4)); /*addListener(Events.Activate, new Listener<WindowEvent>() { @Override public void handleEvent(WindowEvent we) { activate(); } });*/ } @Override protected void moveDrag(DragEvent de) { int windowBarHeight = SvgrealApp.getWindowBarHeight(); if (de.getY() < windowBarHeight) { de.setY(windowBarHeight); } } public SVGModel getSvgModel() { return svgModel; } public TreePanelExt<SVGElementModel> getTree() { return tree; } /** * Sets the scaling of the main image through the scale slider. * @param scale * The scale (50 means scale 1:1) */ public void setScaleSlider(int value) { scaleSlider.setValue(value); } /** * Sets the rotation of the main image through the * compass widget. * @param angleDeg * The angle (in degrees) */ public void setRotationCompass(int angleDeg) { compass.setRotation(angleDeg); } /* GWT bug ? * line 234: The method endDrag(DragEvent) in the type Window is not applicable for the arguments (DragEvent, boolean)*/ // protected void endDrag(DragEvent de, boolean canceled) { // GWT.log("endDrag" + de.getX() + " " + de.getY()); // int windowBarHeight = VectomaticApp2.getWindowBarHeight(); // if (de.getY() < windowBarHeight) { // de.setY(windowBarHeight); // } // super.endDrag(de, canceled); // } /*@Override protected void onHide() { GWT.log("SVGWindow(" + getHeading() + ").onHide"); TreePanelSelectionModel<SVGElementModel> selection = tree.getSelectionModel(); selection.deselectAll(); super.onHide(); }*/ public void activate() { GWT.log("SVGWindow(" + getHeading() + ").activate"); el().addStyleName(WINDOW_ACTIVE_STYLE); TreePanelSelectionModel<SVGElementModel> selection = tree.getSelectionModel(); SelectionService.get().register(selection); updateSelectionListeners(); } public void updateSelectionListeners() { TreePanelSelectionModel<SVGElementModel> selection = tree.getSelectionModel(); selection.fireEvent(Events.SelectionChange, new SelectionChangedEvent<SVGElementModel>(selection, selection.getSelectedItems())); } public void updateIcon(SVGElementModel model) { tree.getView().onIconStyleChange(tree.findTreeNode(model), tree.getIconProvider().getIcon(model)); } public void deactivate() { GWT.log("SVGWindow(" + getHeading() + ").deactivate"); Object selection = tree.getSelectionModel(); SelectionService.get().unregister((SelectionProvider<ModelData>)selection); el().removeStyleName(WINDOW_ACTIVE_STYLE); } @Override protected void fitContainer() { Rectangle rect = SvgrealApp.getApp().getRectangle(); setPosition(rect.x, rect.y); setSize(rect.width, rect.height); } public void renameModel(final SVGElementModel model) { final EditTitleCommandFactory commandFactory = (EditTitleCommandFactory) EditTitleCommandFactory.INSTANTIATOR.create(); commandFactory.start(this); commandFactory.updateStatus(ModelConstants.INSTANCE.renameElementCmdFactory2()); final TextField<String> nameField = new TextField<String>(); Editor nameEditor = new Editor(nameField); nameEditor.setAutoHeight(true); nameEditor.setAutoWidth(true); nameEditor.setCompleteOnEnter(true); Listener<BaseEvent> editorListener = new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { GWT.log("Editor: " + be.getType()); if (be.getType() == Events.Complete) { Record record = svgModel.getStore().getRecord(model); record.set(SVGConstants.SVG_TITLE_TAG, nameField.getValue()); record.commit(false); commandFactory.stop(); } if (be.getType() == Events.CancelEdit) { commandFactory.stop(); } } }; nameEditor.addListener(Events.Complete, editorListener); nameEditor.addListener(Events.CancelEdit, editorListener); TreeNode treeNode = tree.findTreeNode(model); nameEditor.startEdit(treeNode.getElement(), model.get(SVGConstants.SVG_TITLE_TAG)); } }