package com.project.website.canvas.client.worksheet; import java.util.ArrayList; import java.util.HashSet; import com.google.common.base.Objects; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DataTransfer; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.dom.client.DragEnterEvent; import com.google.gwt.event.dom.client.DragEnterHandler; import com.google.gwt.event.dom.client.DragEvent; import com.google.gwt.event.dom.client.DragOverEvent; import com.google.gwt.event.dom.client.DragOverHandler; import com.google.gwt.event.dom.client.DropEvent; import com.google.gwt.event.dom.client.DropHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.HumanInputEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Hyperlink; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.project.shared.client.events.SimpleEvent; import com.project.shared.client.events.SimpleEvent.Handler; import com.project.shared.client.handlers.RegistrationsManager; import com.project.shared.client.utils.ElementUtils; import com.project.shared.client.utils.EventUtils; import com.project.shared.client.utils.HandlerUtils; import com.project.shared.client.utils.SchedulerUtils; import com.project.shared.client.utils.ZIndexAllocator; import com.project.shared.client.utils.widgets.DialogWithZIndex; import com.project.shared.client.utils.widgets.WidgetUtils; import com.project.shared.data.Point2D; import com.project.shared.data.funcs.Func; import com.project.shared.utils.ArrayUtils; import com.project.shared.utils.IterableUtils; import com.project.shared.utils.loggers.Logger; import com.project.website.canvas.client.canvastools.CursorToolboxItem; import com.project.website.canvas.client.canvastools.MoveToolboxItem; import com.project.website.canvas.client.canvastools.base.interfaces.CanvasTool; import com.project.website.canvas.client.canvastools.base.interfaces.CanvasToolFactory; import com.project.website.canvas.client.canvastools.base.interfaces.CanvasToolFrame; import com.project.website.canvas.client.canvastools.base.interfaces.ToolboxItem; import com.project.website.canvas.client.resources.CanvasResources; import com.project.website.canvas.client.shared.ImageInformationUtils; import com.project.website.canvas.client.shared.UndoManager; import com.project.website.canvas.client.shared.UndoManager.UndoRedoPair; import com.project.website.canvas.client.shared.dialogs.SelectImageDialog; import com.project.website.canvas.client.shared.searchProviders.SearchProviders; import com.project.website.canvas.client.worksheet.data.CanvasToolFrameInfo; import com.project.website.canvas.client.worksheet.interfaces.ElementDragManager; import com.project.website.canvas.client.worksheet.interfaces.MouseMoveOperationHandler; import com.project.website.canvas.client.worksheet.interfaces.ToolFrameTransformer; import com.project.website.canvas.client.worksheet.interfaces.WorksheetView; import com.project.website.canvas.shared.data.CanvasPageOptions; import com.project.website.canvas.shared.data.ElementData; import com.project.website.canvas.shared.data.ImageInformation; import com.project.website.canvas.shared.data.Transform2D; import com.project.website.shared.data.UserProfile; public class WorksheetViewImpl extends Composite implements WorksheetView { interface WorksheetViewImplUiBinder extends UiBinder<Widget, WorksheetViewImpl> { } private static WorksheetViewImplUiBinder uiBinder = GWT.create(WorksheetViewImplUiBinder.class); @UiField HTMLPanel dragPanel; @UiField Anchor optionsBackground; @UiField Anchor linkLogout; // @UiField // Anchor linkInvite; @UiField Anchor saveButton; @UiField Anchor newButton; @UiField Hyperlink viewButton; @UiField FlowPanel worksheetBackground; @UiField HTMLPanel worksheetContainer; @UiField HTMLPanel worksheetHeader; @UiField FocusPanel focusPanel; @UiField FlowPanel worksheetPanel; @UiField HTMLPanel selectionPanel; // @UiField // Label userWelcomeLabel; @UiField Anchor addSpaceButton; @UiField Label statusLabel; @UiField HTMLPanel dropTarget; @UiField CheckBox gridCheckBox; @UiField ToolFramesContainerImpl toolFramesContainer; private static final Point2D PAGE_SIZE_ADDITIONAL_AMOUNT = new Point2D(0, 300); private static final int PAGE_SIZE_ADD_ANIMATION_DURATION = 500; public static final String DEFAULT_PUBLIC_NAME = "Guest"; private static final String SAVE_PAGE_EDITABLE = "Save"; private static final String SAVE_PAGE_NON_EDITABLE = "Save as new page"; private ToolboxItem _activeToolboxItem; private Widget _floatingWidget; private CanvasPageOptions _pageOptions; private final SimpleEvent<Void> _floatingWidgetTerminated = new SimpleEvent<Void>(); private final WorksheetImageOptionsProvider _imageOptionsProvider = new WorksheetImageOptionsProvider(); private final ToolFrameTransformer _toolFrameTransformer; private final ToolFrameSelectionManager _toolFrameSelectionManager; private final ElementDragManagerImpl _floatingWidgetDragManager; private final DialogBox _optionsDialog = new DialogWithZIndex(false, true); private final SelectImageDialog _selectImageDialog = new SelectImageDialog(); private final RegistrationsManager _editModeRegistrations = new RegistrationsManager(); private final RegistrationsManager _allModesRegistrations = new RegistrationsManager(); private final SimpleEvent<CanvasPageOptions> _optionsUpdatedEvent = new SimpleEvent<CanvasPageOptions>(); private final SimpleEvent<Void> _stopOperationEvent = new SimpleEvent<Void>(); private final SimpleEvent<Void> _undoRequestEvent = new SimpleEvent<Void>(); private final SimpleEvent<ImageDropInfo> _imageDropEvent = new SimpleEvent<ImageDropInfo>(); private final SimpleEvent<ArrayList<CanvasToolFrame>> _removeToolsRequest = new SimpleEvent<ArrayList<CanvasToolFrame>>(); private final SimpleEvent<ArrayList<CanvasToolFrame>> _copyToolsRequest = new SimpleEvent<ArrayList<CanvasToolFrame>>(); private final SimpleEvent<Void> _pasteToolsRequest = new SimpleEvent<Void>(); private final SimpleEvent<ToolCreationRequest> _toolCreationRequestEvent = new SimpleEvent<ToolCreationRequest>(); private final SimpleEvent<CanvasToolFrame> _activeToolFrameChangedEvent = new SimpleEvent<CanvasToolFrame>(); private HashSet<CanvasToolFrame> _selectedTools = new HashSet<CanvasToolFrame>(); private final boolean _dragSupported = DragEvent.isSupported(); private boolean _viewMode; private boolean _modeInitialized = false; private boolean _pageEditable; public WorksheetViewImpl() { initWidget(uiBinder.createAndBindUi(this)); this._toolFrameTransformer = new ToolFrameTransformerImpl(worksheetPanel, dragPanel, _stopOperationEvent); this.dragPanel.setVisible(false); this.dropTarget.setVisible(false); this._toolFrameSelectionManager = new ToolFrameSelectionManager(this, worksheetPanel, dragPanel, selectionPanel, _stopOperationEvent); this.selectionPanel.setVisible(false); this._floatingWidgetDragManager = new ElementDragManagerImpl(this, this.dragPanel, 0, _stopOperationEvent); this._optionsDialog.setText("Worksheet options"); this._selectImageDialog.setImageOptionsProvider(this._imageOptionsProvider); this._selectImageDialog.setSearchProviders(SearchProviders.getDefaultImageSearchProviders()); this._optionsDialog.add(this._selectImageDialog); this.addRegistrations(); this.setViewMode(false); // TODO: remove when users feature is fully implemented this.linkLogout.setVisible(false); } @Override public HandlerRegistration addActiveToolFrameChangedHandler(Handler<CanvasToolFrame> handler) { return this._activeToolFrameChangedEvent.addHandler(handler); } @Override public HandlerRegistration addCopyToolHandler(Handler<ArrayList<CanvasToolFrame>> handler) { return this._copyToolsRequest.addHandler(handler); } @Override public HandlerRegistration addInviteHandler(Handler<Void> handler) { // return this.linkInvite.addClickHandler(HandlerUtils.asClickHandler(handler)); return null; } @Override public HandlerRegistration addLoadHandler(final Handler<String> handler) { // TODO: why is this 'return null' here? return null; // return loadButton.addClickHandler(new ClickHandler() { // @Override // public void onClick(ClickEvent event) { // load(handler); // } // }); } @Override public HandlerRegistration addLogoutHandler(Handler<Void> handler) { return this.linkLogout.addClickHandler(HandlerUtils.asClickHandler(handler)); } @Override public HandlerRegistration addOptionsUpdatedHandler(Handler<CanvasPageOptions> handler) { return _optionsUpdatedEvent.addHandler(handler); } @Override public HandlerRegistration addPasteToolHandler(Handler<Void> handler) { return this._pasteToolsRequest.addHandler(handler); } @Override public HandlerRegistration addRemoveToolsRequest(Handler<ArrayList<CanvasToolFrame>> handler) { return this._removeToolsRequest.addHandler(handler); } @Override public HandlerRegistration addAddSpaceHandler(Handler<Void> handler) { return this.addSpaceButton.addClickHandler(HandlerUtils.asClickHandler(handler)); } @Override public HandlerRegistration addImageDropHandler(Handler<ImageDropInfo> handler) { return this._imageDropEvent.addHandler(handler); } @Override public HandlerRegistration addSaveHandler(Handler<Void> handler) { return saveButton.addClickHandler(HandlerUtils.asClickHandler(handler)); } @Override public HandlerRegistration addNewPageHandler(Handler<Void> handler) { return newButton.addClickHandler(HandlerUtils.asClickHandler(handler)); } @Override public HandlerRegistration addStopOperationHandler(Handler<Void> handler) { return _stopOperationEvent.addHandler(handler); } @Override public HandlerRegistration addToolCreationRequestHandler(Handler<ToolCreationRequest> handler) { return _toolCreationRequestEvent.addHandler(handler); } @Override public void addToolInstanceWidget(final CanvasToolFrame toolFrame, final Transform2D transform, final Point2D additionalOffset, final boolean addFrameInnerOffset) { CanvasToolFrameInfo info = this.toolFramesContainer.addToolFrame(toolFrame); this.setToolFrameRegistrations(toolFrame, info.getRegistrations().asRegistrationsManager(this)); WidgetUtils.getOnAttachAsyncFunc(toolFrame.asWidget()) .then(SchedulerUtils.getDeferredAsyncFunc()) .then(new Func.VoidAction() { @Override public void exec() { final Point2D innerFrameOffset = addFrameInnerOffset ? toolFrame.getToolOffsetInFrame() : Point2D.zero; setToolFrameTransform(toolFrame, transform, additionalOffset.minus(innerFrameOffset)); }}) .run(null); } @Override public HandlerRegistration addUndoRequestHandler(Handler<Void> handler) { return this._undoRequestEvent.addHandler(handler); } @Override public void clearActiveToolboxItem() { clearFloatingWidget(); if (this._activeToolboxItem instanceof MoveToolboxItem) { // todo re-add in canvas? // for (CanvasToolFrame toolFrame : this._overToolFrames) // { // toolFrame.asWidget().removeStyleName(CanvasResources.INSTANCE.main().drag()); // } } if (null != this._activeToolboxItem) { this.worksheetPanel.removeStyleName(this._activeToolboxItem.getCanvasStyleInCreateMode()); this._activeToolboxItem = null; } } public void selectAllTools() { for (CanvasToolFrame toolFrame : this.getToolFrames()) this.selectToolFrame(toolFrame); } @Override public void clearToolFrameSelection() { ArrayList<CanvasToolFrame> framesToClear = new ArrayList<CanvasToolFrame>(this._selectedTools); for (CanvasToolFrame toolFrame : framesToClear) { this.unSelectToolFrame(toolFrame); } } @Override public ArrayList<CanvasToolFrame> getToolFrames() { return IterableUtils.toArrayList(this.toolFramesContainer.getToolFrames()); } @Override public boolean isToolFrameSelected(CanvasToolFrame toolFrame) { return this._selectedTools.contains(toolFrame); } @Override public void onLoadOperationChange(OperationStatus status, String reason) { switch (status) { case PENDING: this.statusLabel.setText("Loading..."); break; case SUCCESS: this.statusLabel.setText(""); break; case FAILURE: this.statusLabel.setText("Failed to load :("); break; } if (OperationStatus.FAILURE == status) { Window.alert("Load failed. Reason: " + reason); } } @Override public void onSaveOperationChange(OperationStatus status, String reason) { this.changeStatusLabel(saveButton, status, "Saving...", this.getSaveButtonText()); if (OperationStatus.FAILURE == status) { Window.alert("Save failed. Reason: " + reason); } } @Override public void removeToolInstanceWidget(CanvasToolFrame toolFrame) { this.toolFramesContainer.removeToolFrame(toolFrame); this._selectedTools.remove(toolFrame); } @Override public void selectToolFrame(CanvasToolFrame toolFrame) { this._selectedTools.add(toolFrame); toolFrame.asWidget().addStyleName(CanvasResources.INSTANCE.main().selected()); } @Override public void setActiveToolboxItem(final ToolboxItem toolboxItem) { this.clearActiveToolboxItem(); this._activeToolboxItem = toolboxItem; this.worksheetPanel.addStyleName(toolboxItem.getCanvasStyleInCreateMode()); // De-activate any active tools, except when entering Cursor mode if (false == (toolboxItem instanceof CursorToolboxItem)) { this._activeToolFrameChangedEvent.dispatch(null); } // also de-select any selected tools, unless a selector toolbox item was activated if (false == this.isSelectorActiveTool()) { this.clearToolFrameSelection(); } CanvasToolFactory<? extends CanvasTool<? extends ElementData>> factory = toolboxItem.getToolFactory(); if (null == factory) { return; } if (toolboxItem.createOnMouseDown()) { this.setActiveToolboxItemWithoutFloatingWidget(toolboxItem); } this.setFloatingWidgetForTool(factory); if (null == this._floatingWidget) { return; } this.startDraggingFloatingWidget(toolboxItem); } @Override public void setOptions(final CanvasPageOptions value) { this._pageOptions = value; this.worksheetBackground.addStyleName(CanvasResources.INSTANCE.main().imageLoadingStyle()); this.pageSizeUpdated(); WidgetUtils.setBackgroundImageAsync(this.worksheetBackground, value.backgroundImage.getUrl(), CanvasResources.INSTANCE.imageUnavailable().getSafeUri().asString(), false, CanvasResources.INSTANCE.main().imageLoadingStyle(), new SimpleEvent.Handler<Void>() { @Override public void onFire(Void arg) { handleBackgroundImageSet(); }}, HandlerUtils.<Void>emptyHandler()); } private void handleBackgroundImageSet() { ImageInformationUtils.setBackgroundStyle(this.worksheetBackground, this._pageOptions.backgroundImage); } @Override public void setToolFrameTransform(final CanvasToolFrame toolFrame, final Transform2D transform, final Point2D additionalOffset) { if (toolFrame.getTool().canRotate()) { ElementUtils.setRotation(toolFrame.asWidget().getElement(), transform.rotation); } if (null != transform.size) { toolFrame.setToolSize(transform.size); } _toolFrameTransformer.setToolFramePosition(toolFrame, transform.translation.plus(additionalOffset)); } @Override public void setUserProfile(UserProfile userProfile) { // boolean canInvite = false; // String publicName = ""; // String email = ""; // if (null != userProfile) { // canInvite = userProfile.canInvite; // publicName = userProfile.publicName; // email = userProfile.email; // } //this.linkInvite.setVisible(canInvite); // this.userWelcomeLabel.setText( // StringUtils.defaultIfNullOrEmpty(publicName, DEFAULT_PUBLIC_NAME)); // this.userWelcomeLabel.setTitle(email); } @Override public void setViewLinkTargetHistoryToken(String targetHistoryToken) { this.viewButton.setTargetHistoryToken(targetHistoryToken); } @Override public void setViewMode(boolean isViewMode) { if (this._modeInitialized && (this._viewMode == isViewMode)) { return; } this._viewMode = isViewMode; this._modeInitialized = true; for (CanvasToolFrame frame : this.toolFramesContainer.getToolFrames()) { frame.setViewMode(isViewMode); } this.toolFramesContainer.setIsEditMode(false == isViewMode); if (isViewMode) { this.clearEditModeRegistrations(); this.worksheetHeader.addStyleName(CanvasResources.INSTANCE.main().displayNone()); this.addStyleName(CanvasResources.INSTANCE.main().worksheetFullView()); this.addStyleName(CanvasResources.INSTANCE.main().worksheetModeViewOnly()); this.removeStyleName(CanvasResources.INSTANCE.main().worksheetModeEditable()); } else { this.setEditModeRegistrations(); this.worksheetHeader.removeStyleName(CanvasResources.INSTANCE.main().displayNone()); this.removeStyleName(CanvasResources.INSTANCE.main().worksheetFullView()); this.removeStyleName(CanvasResources.INSTANCE.main().worksheetModeViewOnly()); this.addStyleName(CanvasResources.INSTANCE.main().worksheetModeEditable()); } } @Override public void unSelectToolFrame(CanvasToolFrame toolFrame) { this._selectedTools.remove(toolFrame); toolFrame.asWidget().removeStyleName(CanvasResources.INSTANCE.main().selected()); } @Override protected void onLoad() { super.onLoad(); ElementUtils.setTextSelectionEnabled(this.getElement(), false); } @Override public void setPageEditable(boolean isEditable) { this._pageEditable = isEditable; this.saveButton.setText(this.getSaveButtonText()); } private String getSaveButtonText() { return this._pageEditable ? SAVE_PAGE_EDITABLE : SAVE_PAGE_NON_EDITABLE; } @Override public void pageSizeUpdated() { WidgetUtils.getOnAttachAsyncFunc(this.worksheetPanel) .then(SchedulerUtils.getDeferredAsyncFunc()) .then(new Func.VoidAction() { @Override public void exec() { performPageSizeUpdate(); }}) .run(null); } /*----------------------------------------------------------------------------------------*/ private void performPageSizeUpdate() { // Never make the page height less than what is currently visible on the screen // to prevent truncation of the background before the vertical edge of the window int currentHeight = this.focusPanel.getOffsetHeight(); Logger.info("Updating page size. Current height: " + String.valueOf(currentHeight)); this.worksheetPanel.setHeight(String.valueOf(Math.max(currentHeight, this._pageOptions.size.getY())) + "px"); this.worksheetBackground.setHeight(String.valueOf(Math.max(currentHeight, this._pageOptions.size.getY())) + "px"); } private void setEditModeRegistrations() { final WorksheetViewImpl that = this; this._editModeRegistrations.clear(); this._editModeRegistrations.add(this.worksheetPanel.addDomHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { Point2D posRelativeToWorksheet = ElementUtils.getMousePositionRelativeToElement(worksheetPanel.getElement()); if (null == posRelativeToWorksheet) { return; } if (posRelativeToWorksheet.getX() < 0 || posRelativeToWorksheet.getY() < 0) { return; } if (that.toolFramesContainer.getHoveredToolFrames().isEmpty()) { onClearAreaClicked(event); } else { onOverToolFrameAreaClicked(event); } } }, MouseDownEvent.getType())); this._editModeRegistrations.add(this.addSpaceButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { onAddSpaceRequest(); } })); if (this._dragSupported) { this._editModeRegistrations.add(this.addDomHandler(new DragEnterHandler() { @Override public void onDragEnter(DragEnterEvent event) { event.preventDefault(); event.stopPropagation(); Logger.info("drag enter"); that.dropTarget.setVisible(true); } }, DragEnterEvent.getType())); this._editModeRegistrations.add(this.addDomHandler(new DragOverHandler() { @Override public void onDragOver(DragOverEvent event) { event.preventDefault(); event.stopPropagation(); Logger.info("drag over"); that.dropTarget.setVisible(true); } }, DragOverEvent.getType())); this._editModeRegistrations.add(this.dropTarget.addDomHandler(new DropHandler() { @Override public void onDrop(DropEvent event) { event.preventDefault(); event.stopPropagation(); Logger.info("drop"); that.dropTarget.setVisible(false); that.onDropEvent(event); } }, DropEvent.getType())); } } protected void onDropEvent(DropEvent event) { DataTransfer dataTransfer = event.getDataTransfer(); Logger.info(dataTransfer); this.handleDrop(dataTransfer, ElementUtils.getMousePositionRelativeToElement(toolFramesContainer.getElement())); } protected void createImageFromDataUrl(String dataUrl, Point2D pos) { // TODO: Currently the image dataUrl is limited in size on the server side (google app engine limitation + the fact that we save base64 encoded data instead of binary) this._imageDropEvent.dispatch(new ImageDropInfo(dataUrl, pos)); } // see: http://www.html5rocks.com/en/tutorials/file/dndfiles/ protected native void handleDrop(DataTransfer dataTransfer, Point2D pos) /*-{ var _this = this; var _pos = pos; var files = dataTransfer.files; // Loop through the FileList and render image files as thumbnails. for (var i = 0, f; f = files[i]; i++) { $wnd.console.log(f.name + ', ' + (f.type || 'n/a') + ', ' + f.size + ' bytes, last modified: ' + (f.lastModifiedDate ? f.lastModifiedDate.toLocaleDateString() : 'n/a')); // Only process image files. if (!f.type.match('image.*')) { continue; } var reader = new FileReader(); // Closure to capture the file information. reader.onload = (function(theFile) { return function(e) { var dataUrl = e.target.result.toString(); _this.@com.project.website.canvas.client.worksheet.WorksheetViewImpl::createImageFromDataUrl(Ljava/lang/String;Lcom/project/shared/data/Point2D;)(dataUrl, _pos); }; })(f); // Read in the image file as a data URL. reader.readAsDataURL(f); } }-*/; protected void onAddSpaceRequest() { UndoManager.get().addAndRedo(this, new UndoRedoPair() { @Override public void undo() { performAddSpace(-1); } @Override public void redo() { performAddSpace(1); } }); } private void performAddSpace(int direction) { Point2D transformVector = PAGE_SIZE_ADDITIONAL_AMOUNT.mul(direction); int newHeight = this.worksheetPanel.getOffsetHeight() + transformVector.getY(); this.worksheetPanel.setHeight(String.valueOf(newHeight) + "px"); this._pageOptions.size = new Point2D(this._pageOptions.size.getX(), newHeight); for (CanvasToolFrame toolFrame : this.toolFramesContainer.getToolFrames()) { Point2D newPos = ElementUtils.getElementOffsetPosition(toolFrame.asWidget().getElement()) .plus(transformVector); this._toolFrameTransformer.setToolFramePosition(toolFrame, newPos, PAGE_SIZE_ADD_ANIMATION_DURATION); } this.pageSizeUpdated(); } private void addRegistrations() { this.setEditModeRegistrations(); final WorksheetViewImpl that = this; this._allModesRegistrations.add(this.optionsBackground.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { _selectImageDialog.setValue(_pageOptions.backgroundImage.getClone()); _optionsDialog.center(); } })); this._allModesRegistrations.add(this._selectImageDialog.addCancelHandler(new SimpleEvent.Handler<Void>() { @Override public void onFire(Void arg) { _optionsDialog.hide(); } })); this._allModesRegistrations.add(this._selectImageDialog.addDoneHandler(new SimpleEvent.Handler<ImageInformation>() { @Override public void onFire(ImageInformation arg) { _optionsDialog.hide(); onBackgroundImageSelected(arg); } })); this._allModesRegistrations.add(this.focusPanel.addKeyDownHandler(new KeyDownHandler(){ @Override public void onKeyDown(KeyDownEvent event) { that.onWorksheetFocusedKeyDown(event); }})); this._allModesRegistrations.add(Event.addNativePreviewHandler(new NativePreviewHandler() { @Override public void onPreviewNativeEvent(NativePreviewEvent event) { NativeEvent nativeEvent = null == event ? null : event.getNativeEvent(); if (null == nativeEvent) { return; } that.handleAllModesPreviewEvent(event); if (false == that._viewMode) { that.handleEditModePreviewEvent(event); } } })); this._allModesRegistrations.add(this.gridCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() { @Override public void onValueChange(ValueChangeEvent<Boolean> event) { that._toolFrameTransformer.setSnapToGrid(event.getValue()); } })); this._allModesRegistrations.add(Window.addResizeHandler(new ResizeHandler() { @Override public void onResize(ResizeEvent event) { that.pageSizeUpdated(); } })); } private void changeStatusLabel(Anchor button, OperationStatus status, String pendingText, String doneText) { switch (status) { case PENDING: button.setText(pendingText); button.setEnabled(false); break; case SUCCESS: case FAILURE: button.setText(doneText); button.setEnabled(true); } } private void clearEditModeRegistrations() { this._editModeRegistrations.clear(); } private void clearFloatingWidget() { if (null != this._floatingWidget) { this.worksheetPanel.remove(_floatingWidget); } this._floatingWidgetTerminated.dispatch(null); this._floatingWidgetTerminated.clearAllHandlers(); this._floatingWidget = null; } private void handleAllModesPreviewEvent(NativePreviewEvent event) { if (EventUtils.nativePreviewEventTypeEquals(event, KeyDownEvent.getType())) { final WorksheetViewImpl that = this; that.onPreviewKeyDown(event); } } private void handleEditModePreviewEvent(NativePreviewEvent event) { NativeEvent nativeEvent = event.getNativeEvent(); if (this._activeToolboxItem instanceof MoveToolboxItem) { if (EventUtils.nativePreviewEventTypeEquals(event, MouseDownEvent.getType())) { MouseDownEvent.fireNativeEvent(nativeEvent, this.worksheetPanel); } else if (EventUtils.nativePreviewEventTypeEquals(event, MouseUpEvent.getType())) { MouseUpEvent.fireNativeEvent(nativeEvent, this.worksheetPanel); } } } private boolean isSelectorActiveTool() { return (this._activeToolboxItem instanceof CursorToolboxItem) || (this._activeToolboxItem instanceof MoveToolboxItem); } private void onBackgroundImageSelected(ImageInformation arg) { if (Objects.equal(this._pageOptions.backgroundImage, arg)) { return; } _pageOptions.backgroundImage = arg; _optionsUpdatedEvent.dispatch(_pageOptions); } private void onClearAreaClicked(HumanInputEvent<?> event) { this._activeToolFrameChangedEvent.dispatch(null); if (null == this._activeToolboxItem) { return; } // TODO: should be handled by a tool outside the worksheet class? if (this.isSelectorActiveTool()) { this._toolFrameSelectionManager.startSelectionDrag(event); } } private void onCopyToolsRequest() { this._copyToolsRequest.dispatch(new ArrayList<CanvasToolFrame>(_selectedTools)); } private void onPreviewKeyDown(NativePreviewEvent event) { if (event.getNativeEvent().getCtrlKey()) { // TODO: When text is being edited, don't do this switch (event.getNativeEvent().getKeyCode()) { case (int)'Z': UndoManager.get().undo(); return; case (int)'Y': UndoManager.get().redo(); return; case (int)'A': //Must be in the Preview handler since we want to cancel the event after handling it //otherwise in some browsers the whole page is selected (e.g. firefox) and it interrupts dragging. this.selectAllTools(); event.getNativeEvent().preventDefault(); return; default: //do nothing break; } } switch (event.getNativeEvent().getKeyCode()) { case KeyCodes.KEY_ESCAPE: _stopOperationEvent.dispatch(null); //Prevent default otherwise all Animated Gifs (loading circle and user selected) are stopped. event.getNativeEvent().preventDefault(); break; default: break; } } private void onWorksheetFocusedKeyDown(KeyDownEvent event){ if (this._viewMode) { return; } if (event.isControlKeyDown()) { switch (event.getNativeKeyCode()) { case (int)'C': this.onCopyToolsRequest(); event.preventDefault(); return; case (int)'V': this.onPasteToolsRequest(); event.preventDefault(); return; default: //do nothing break; } } switch (event.getNativeKeyCode()) { case KeyCodes.KEY_DELETE: this._removeToolsRequest.dispatch(new ArrayList<CanvasToolFrame>(this._selectedTools)); event.preventDefault(); break; default: break; } } private void onOverToolFrameAreaClicked(DomEvent<?> event) { if (false == (this._activeToolboxItem instanceof MoveToolboxItem)) { return; } CanvasToolFrame highestToolUnderMouse = null; int highestZIndex = -1; for (CanvasToolFrame frame : this.toolFramesContainer.getHoveredToolFrames()) { int zIndex = ZIndexAllocator.getElementZIndex(frame.asWidget().getElement()); if (zIndex > highestZIndex) { highestToolUnderMouse = frame; highestZIndex = zIndex; } } this._toolFrameSelectionManager.forceToolFrameSelection(highestToolUnderMouse); this.startDraggingSelectedToolFrames(); event.stopPropagation(); event.preventDefault(); } private void onPasteToolsRequest() { this._pasteToolsRequest.dispatch(null); } private void setActiveToolboxItemWithoutFloatingWidget(final ToolboxItem toolboxItem) { final RegistrationsManager regs = new RegistrationsManager(); regs.add(WidgetUtils.addMovementStopHandler(this.worksheetPanel, new Handler<HumanInputEvent<?>>() { @Override public void onFire(HumanInputEvent<?> event) { dispatchToolCreationWithoutFloatingWidget(toolboxItem, event); event.stopPropagation(); event.preventDefault(); }})); this._floatingWidgetTerminated.addHandler(new Handler<Void>() { @Override public void onFire(Void arg) { regs.clear(); } }); } private void setFloatingWidgetForTool(CanvasToolFactory<? extends CanvasTool<? extends ElementData>> factory) { this._floatingWidget = factory.getFloatingWidget(); if (null == this._floatingWidget) { return; } this.worksheetPanel.add(_floatingWidget); _floatingWidget.addStyleName(CanvasResources.INSTANCE.main().floatingToolStyle()); Event event = Event.getCurrentEvent(); if (null != event) { Point2D relativeToWorksheet = new Point2D(event.getClientX(), event.getClientY()); Point2D worksheetPos = ElementUtils.getElementAbsolutePosition(worksheetPanel.getElement()); ElementUtils.setElementCSSPosition(_floatingWidget.getElement(), Point2D.max(Point2D.zero, relativeToWorksheet.minus(worksheetPos).plus(factory.getFloatingWidgetCreationOffset()))); } } private void setToolFrameRegistrations(final CanvasToolFrame toolFrame, RegistrationsManager regs) { final WorksheetViewImpl that = this; // In case we have already registered. regs.addRecurringMultiple(new Func<Void, Iterable<HandlerRegistration>>() { @Override public Iterable<HandlerRegistration> apply(Void arg) { return that.setEditModeToolFrameRegistrations(toolFrame); }}); } private void startDraggingFloatingWidget(final ToolboxItem toolboxItem) { final WorksheetViewImpl that = this; final CanvasToolFactory<? extends CanvasTool<? extends ElementData>> toolFactory = toolboxItem.getToolFactory(); final Point2D floatingWidgetCreationOffset = toolFactory.getFloatingWidgetCreationOffset(); MouseMoveOperationHandler handler = new MouseMoveOperationHandler() { @Override public void onStop(Point2D pos) { Point2D snappedPos = this.calcTargetPos(pos); Point2D creationPos = that.translateDragPanelPosToToolContainerPos(snappedPos); _toolCreationRequestEvent.dispatch(new ToolCreationRequest(creationPos, toolFactory)); } @Override public void onStart() { } @Override public void onMouseMove(Point2D pos) { ElementUtils.setElementCSSPosition(that._floatingWidget.getElement(), this.calcTargetPos(pos).plus(floatingWidgetCreationOffset)); } private Point2D calcTargetPos(Point2D pos) { return that._toolFrameTransformer.applySnapToGrid(pos); } @Override public void onCancel() { } }; this._floatingWidgetTerminated.addHandler(this._floatingWidgetDragManager.startMouseMoveOperation( null, this.dragPanel.getElement(), Point2D.zero, handler, ElementDragManager.StopCondition.STOP_CONDITION_MOVEMENT_STOP)); } private void startDraggingSelectedToolFrames() { _toolFrameTransformer.startDragCanvasToolFrames(IterableUtils.<CanvasToolFrame, CanvasToolFrame>upCast(_selectedTools)); } private void dispatchToolCreationWithoutFloatingWidget(final ToolboxItem toolboxItem, final HumanInputEvent<?> event) { Point2D position = ElementUtils.getMousePositionRelativeToElement(toolFramesContainer.getElement()); _toolCreationRequestEvent.dispatch(new ToolCreationRequest(position, toolboxItem.getToolFactory()) { @Override public void toolCreated(CanvasTool<? extends ElementData> tool) { super.toolCreated(tool); tool.asWidget().fireEvent(event); } }); } private Point2D translateDragPanelPosToToolContainerPos(Point2D toolFramePos) { // Transform the coordinates for creating a tool frame from a floating widget // transforms them from the drag panel coordinates to the toolsContainerPanel coordinates return toolFramePos.minus(ElementUtils.getElementOffsetPosition(this.toolFramesContainer.getElement())) .plus(ElementUtils.getElementOffsetPosition(this.dragPanel.getElement())); } private ArrayList<HandlerRegistration> setEditModeToolFrameRegistrations(final CanvasToolFrame toolFrame) { ArrayList<HandlerRegistration> res = ArrayUtils.toList(new HandlerRegistration[] { toolFrame.addMoveStartRequestHandler(new SimpleEvent.Handler<Void>() { @Override public void onFire(Void arg) { _toolFrameSelectionManager.forceToolFrameSelection(toolFrame); startDraggingSelectedToolFrames(); } }), toolFrame.addResizeStartRequestHandler(new SimpleEvent.Handler<Void>() { @Override public void onFire(Void arg) { _toolFrameSelectionManager.forceToolFrameSelection(toolFrame); _toolFrameTransformer.startResizeCanvasToolFrame(toolFrame); } }), toolFrame.addFocusHandler(new FocusHandler() { @Override public void onFocus(FocusEvent event) { _activeToolFrameChangedEvent.dispatch(toolFrame); } }), toolFrame.addMouseUpHandler(new MouseUpHandler() { @Override public void onMouseUp(MouseUpEvent event) { _toolFrameSelectionManager.handleToolFrameSelection(toolFrame); } }), }); if (toolFrame.getTool().canRotate()) { res.add(toolFrame.addRotateStartRequestHandler(new SimpleEvent.Handler<Void>() { @Override public void onFire(Void arg) { _toolFrameSelectionManager.forceToolFrameSelection(toolFrame); _toolFrameTransformer.startRotateCanvasToolFrame(toolFrame); } })); } return res; } }