Java tutorial
/* Copyright 2012 Fabian Gebert and Michael Gorski. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.akanoo.client.views; import java.util.ArrayList; import java.util.List; import com.akanoo.client.dto.UserInfo; import com.akanoo.client.messages.Languages; import com.akanoo.client.presenters.CanvasPresenter; import com.akanoo.client.presenters.CanvasUiHandlers; import com.akanoo.client.presenters.Note; import com.allen_sauer.gwt.dnd.client.DragContext; import com.allen_sauer.gwt.dnd.client.DragHandlerAdapter; import com.allen_sauer.gwt.dnd.client.DragStartEvent; import com.allen_sauer.gwt.dnd.client.PickupDragController; import com.allen_sauer.gwt.dnd.client.drop.AbsolutePositionDropController; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.logical.shared.AttachEvent; import com.google.gwt.event.logical.shared.ResizeEvent; import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.resources.client.ImageResource.ImageOptions; import com.google.gwt.resources.client.ImageResource.RepeatStyle; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.PushButton; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextBoxBase; import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; import com.gwtplatform.mvp.client.ViewWithUiHandlers; public class CanvasView extends ViewWithUiHandlers<CanvasUiHandlers> implements CanvasPresenter.MyView { private final class TextBlurHandler implements BlurHandler { private final TextBoxBase textBox; private final Label bodyLabel; private final Note note; private boolean isBackBody; private TextBlurHandler(TextBoxBase textBox, Label bodyLabel, Note note, boolean isBackBody) { this.textBox = textBox; this.bodyLabel = bodyLabel; this.note = note; this.isBackBody = isBackBody; } @Override public void onBlur(BlurEvent event) { GWT.log("Textbox blurred"); textBox.removeFromParent(); String text = textBox.getText(); if (!text.isEmpty()) { bodyLabel.setVisible(true); getUiHandlers().updateNoteBody(note, text, isBackBody); } else { getUiHandlers().removeNote(note); } Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { GWT.log("Editing finished"); editing = false; } }); } } public class NoteRepresentation { public Note note; public HTML bodyLabel; public HTML backBodyLabel; public FlowPanel notePanel; } private static class NoteClickHandler implements ClickHandler { private Note note; public NoteClickHandler(Note note) { this.note = note; } @Override public void onClick(ClickEvent event) { event.stopPropagation(); this.noteClicked(note); } protected void noteClicked(Note note) { } } /** * Resources that match the GWT standard style theme. */ public interface Resources extends ClientBundle { @Source("background.jpg") @ImageOptions(repeatStyle = RepeatStyle.Both) ImageResource lightbackground(); @Source("tour-create-note.png") ImageResource tourCreateNote(); @Source("trash3.png") ImageResource trash(); @Source("trash3-hover.png") ImageResource trashHover(); @Source("resizeImage.png") ImageResource resize(); /** * The styles used in this widget. */ @Source(Style.DEFAULT_CSS) Style canvasStyle(); } public interface Style extends CssResource { /** * The path to the default CSS styles used by this resource. */ String DEFAULT_CSS = "com/akanoo/client/views/CanvasView.css"; int pixelSensitivity(); int sizePadding(); int activeUserPanelHeight(); String boundaryPanel(); String createNotePanel(); String focusPanel(); String canvas(); String note(); String noteFocusPanel(); String deleteNoteButton(); String resizeNoteButton(); String bodyLabelPosition(); String bodyLabel(); String bodyBox(); String activeUserLabel(); String activeUserPanel(); } @UiField(provided = true) Resources resources; private final Widget widget; public interface Binder extends UiBinder<Widget, CanvasView> { } @UiField AbsolutePanel boundaryPanel; @UiField AbsolutePanel canvas; @UiField FocusPanel canvasFocus; private PickupDragController dragController; private Point startPosition; private AbsolutePositionDropController dropController; @Inject private Languages lang; private boolean ignoreClick; protected boolean editing; protected boolean ignoreNext; private boolean enabled; @UiField Widget createNotePanel; @UiField(provided = true) Languages messages; private List<NoteRepresentation> representations; @UiField ScrollPanel scrollPanel; @UiField Panel activeUserPanel; private static class Point implements CanvasPresenter.Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public int getX() { return x; } @Override public int getY() { return y; } @Override public String toString() { return "[x=" + x + ",y=" + y + "]"; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof CanvasPresenter.Point)) return false; return getX() == ((CanvasPresenter.Point) obj).getX() && getY() == ((CanvasPresenter.Point) obj).getY(); } } @Inject public CanvasView(final Binder binder, Resources resources, Languages messages) { this.resources = resources; resources.canvasStyle().ensureInjected(); this.messages = messages; representations = new ArrayList<CanvasView.NoteRepresentation>(); widget = binder.createAndBindUi(this); scrollPanel.addAttachHandler(new AttachEvent.Handler() { @Override public void onAttachOrDetach(AttachEvent event) { if (event.isAttached()) { updateCanvasSize(); } } }); Window.addResizeHandler(new ResizeHandler() { @Override public void onResize(ResizeEvent event) { updateCanvasSize(); } }); // create drag controller dragController = new PickupDragController(boundaryPanel, true); dragController.setBehaviorDragStartSensitivity(resources.canvasStyle().pixelSensitivity()); dragController.setBehaviorBoundaryPanelDrop(false); dragController.addDragHandler(new DragHandlerAdapter() { @Override public void onDragStart(DragStartEvent event) { Widget note = event.getContext().draggable; startPosition = new Point(canvas.getWidgetLeft(note), canvas.getWidgetTop(note)); GWT.log("Starting at: " + startPosition); ignoreClick = true; } }); dropController = new AbsolutePositionDropController(canvas) { @Override public void onDrop(DragContext context) { super.onDrop(context); final Widget notePanel = context.draggable; Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { Point newPosition = new Point(canvas.getWidgetLeft(notePanel), canvas.getWidgetTop(notePanel)); if (!newPosition.equals(startPosition)) { GWT.log("Now got: " + newPosition); Note note = findByNotePanel(notePanel).note; getUiHandlers().moveNote(note, newPosition); } ignoreClick = false; } }); } }; dragController.registerDropController(dropController); canvasFocus.addFocusHandler(new FocusHandler() { @Override public void onFocus(FocusEvent event) { GWT.log("Canvas focussed!"); } }); canvasFocus.addMouseDownHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { GWT.log("Canvas mouse down!"); if (editing) ignoreNext = true; } }); canvasFocus.addTouchStartHandler(new TouchStartHandler() { @Override public void onTouchStart(TouchStartEvent event) { GWT.log("Canvas touch down!"); if (editing) ignoreNext = true; } }); canvasFocus.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { canvasClick(event); } }); } private NoteRepresentation findByNotePanel(Widget notePanel) { for (NoteRepresentation noteRepresentation : representations) { if (noteRepresentation.notePanel == notePanel) { return noteRepresentation; } } return null; } private NoteRepresentation findByNote(Note note) { for (NoteRepresentation noteRepresentation : representations) { if (noteRepresentation.note == note) { return noteRepresentation; } } return null; } @Override public Widget asWidget() { return widget; } @Override public void clearNotes() { canvas.clear(); canvas.add(createNotePanel); createNotePanel.setVisible(enabled); representations.clear(); } private static ClickHandler clickStopPropagationHandler = new ClickHandler() { @Override public void onClick(ClickEvent event) { event.stopPropagation(); } }; @Override public void addNote(Note note) { // hide tour panel createNotePanel.setVisible(false); FlowPanel notePanel = new FlowPanel(); notePanel.addStyleName(resources.canvasStyle().note()); // note drag and drop FocusPanel noteFocusPanel = new FocusPanel(); noteFocusPanel.addStyleName(resources.canvasStyle().noteFocusPanel()); notePanel.add(noteFocusPanel); // don't pass click events to the canvas noteFocusPanel.addClickHandler(clickStopPropagationHandler); // flow panel for all note elements Panel noteFlowPanel = new FlowPanel(); noteFocusPanel.add(noteFlowPanel); // note body label HTML bodyLabel = new HTML(); bodyLabel.addStyleName(resources.canvasStyle().bodyLabelPosition()); bodyLabel.addStyleName(resources.canvasStyle().bodyLabel()); bodyLabel.addClickHandler(new NoteClickHandler(note) { @Override protected void noteClicked(Note note) { CanvasView.this.noteClicked(note, false); } }); noteFlowPanel.add(bodyLabel); // note back body label HTML backBodyLabel = new HTML(); backBodyLabel.addStyleName(resources.canvasStyle().bodyLabelPosition()); backBodyLabel.addStyleName(resources.canvasStyle().bodyLabel()); backBodyLabel.addClickHandler(new NoteClickHandler(note) { @Override protected void noteClicked(Note note) { CanvasView.this.noteClicked(note, true); } }); backBodyLabel.setVisible(false); noteFlowPanel.add(backBodyLabel); // delete note button final PushButton deleteNoteButton = new PushButton(); deleteNoteButton.setStyleName(resources.canvasStyle().deleteNoteButton()); deleteNoteButton.getUpFace().setImage(new Image(resources.trash())); deleteNoteButton.getUpHoveringFace().setImage(new Image(resources.trashHover())); deleteNoteButton.setTitle(messages.deleteNoteVerb()); deleteNoteButton.addClickHandler(new NoteClickHandler(note) { @Override protected void noteClicked(Note note) { getUiHandlers().removeNote(note); } }); notePanel.add(deleteNoteButton); /* * // add resize button PushButton resizeNoteButton = new PushButton(); * resizeNoteButton.setStyleName(resources.canvasStyle() * .resizeNoteButton()); * resizeNoteButton.setTitle(messages.resizeNoteVerb()); * notePanel.add(resizeNoteButton); */ canvas.add(notePanel, note.getX(), note.getY()); // drag and drop notes enabled dragController.makeDraggable(notePanel, noteFocusPanel); NoteRepresentation representation = new NoteRepresentation(); representation.note = note; representation.notePanel = notePanel; representation.bodyLabel = bodyLabel; representation.backBodyLabel = backBodyLabel; representations.add(representation); updateNoteLabels(representation); updateCanvasSize(); } private void updateCanvasSize() { Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { int maxX = scrollPanel.getOffsetWidth(); int maxY = scrollPanel.getOffsetHeight(); for (NoteRepresentation noteRepresentation : representations) { maxX = Math.max(noteRepresentation.note.getX() + 180 + resources.canvasStyle().sizePadding(), maxX); maxY = Math.max(noteRepresentation.note.getY() + 260 + resources.canvasStyle().sizePadding(), maxY); } boundaryPanel.setPixelSize(maxX, maxY); } }); } // create new note on click void canvasClick(ClickEvent event) { if (ignoreClick || editing || !enabled) return; if (ignoreNext) { ignoreNext = false; return; } GWT.log("Creating new note"); Element canvasElement = canvas.getElement(); Point position = new Point(event.getRelativeX(canvasElement), event.getRelativeY(canvasElement)); getUiHandlers().createNote(position, ""); } // edit note public void noteClicked(final Note note, final boolean isBackBody) { NoteRepresentation representation = findByNote(note); final FlowPanel notePanel = representation.notePanel; final Label bodyLabel = isBackBody ? representation.backBodyLabel : representation.bodyLabel; if (note != null) { final TextBoxBase textBox = new TextArea(); textBox.setText(isBackBody ? note.getBackBody() : note.getBody()); textBox.addStyleName(resources.canvasStyle().bodyLabelPosition()); textBox.addStyleName(resources.canvasStyle().bodyBox()); notePanel.insert(textBox, 0); bodyLabel.setVisible(false); editing = true; GWT.log("Editing started"); textBox.addBlurHandler(new TextBlurHandler(textBox, bodyLabel, note, isBackBody)); textBox.setFocus(true); } } @Override public void updateNoteBody(Note note) { NoteRepresentation representation = findByNote(note); if (representation != null) { updateNoteLabels(representation); GWT.log("updated label body to " + note.getBody()); } } /** * Update note labels of the given representation with the body of the * corresponding note * * @param representation */ private void updateNoteLabels(NoteRepresentation representation) { Note note = representation.note; // escape HTML chars SafeHtml bodyHtml = SafeHtmlUtils.fromString(note.getBody() != null ? note.getBody() : ""); SafeHtml backBodyHtml = SafeHtmlUtils.fromString(note.getBackBody() != null ? note.getBackBody() : ""); // replace urls with anchors bodyHtml = SafeHtmlUtils.fromTrustedString(replaceUrlsWithLinks(bodyHtml.asString())); backBodyHtml = SafeHtmlUtils.fromTrustedString(replaceUrlsWithLinks(backBodyHtml.asString())); // update labels representation.bodyLabel.setHTML(bodyHtml); representation.backBodyLabel.setHTML(backBodyHtml); } /** * Replace URLs in the given string with anchors that will open in a new * window * * @param html * @return */ private String replaceUrlsWithLinks(String html) { RegExp r = RegExp.compile("(https?://[^ ]+)", "gim"); return r.replace(html, "<a href=\"$1\" target=\"_blank\">$1</a>"); } @Override public void updateNotePosition(Note note) { NoteRepresentation representation = findByNote(note); if (representation != null) { canvas.setWidgetPosition(representation.notePanel, note.getX(), note.getY()); GWT.log("updated note position to " + note.getX() + ", " + note.getY()); } updateCanvasSize(); } @Override public void focusNote(final Note note) { // do not focus note if we're currently in edit mode if (editing) return; // wait for the current event loop to finish and then focus the note // body label Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { // call click handler noteClicked(note, false); } }); } @Override public void removeNote(Note note) { NoteRepresentation representation = findByNote(note); if (representation != null) { representations.remove(representation); representation.notePanel.removeFromParent(); } } @Override public String getSampleText() { return lang.sampleBody(); } @Override public void setEnabled(boolean b) { this.enabled = b; } @Override public void populateActiveUsers(List<UserInfo> collaborators) { activeUserPanel.clear(); activeUserPanel.setVisible(collaborators.size() > 0); for (UserInfo userInfo : collaborators) { Label activeUserLabel = new Label(userInfo.name); activeUserLabel.setTitle(userInfo.email); activeUserLabel.addStyleName(resources.canvasStyle().activeUserLabel()); activeUserPanel.add(activeUserLabel); } } }