package com.project.website.canvas.client.canvastools.sitecrop;
import com.google.common.base.Strings;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.IsWidget;
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.MouseButtonDownHandler;
import com.project.shared.client.handlers.RegistrationsManager;
import com.project.shared.client.handlers.SpecificKeyPressHandler;
import com.project.shared.client.utils.ElementUtils;
import com.project.shared.client.utils.SchedulerUtils.OneTimeScheduler;
import com.project.shared.client.utils.UrlUtils;
import com.project.shared.client.utils.WindowUtils;
import com.project.shared.client.utils.widgets.WidgetUtils;
import com.project.shared.data.MouseButtons;
import com.project.shared.data.Point2D;
import com.project.shared.data.Rectangle;
import com.project.website.canvas.client.canvastools.base.CanvasToolEvents;
import com.project.website.canvas.client.canvastools.base.ResizeMode;
import com.project.website.canvas.client.canvastools.base.interfaces.CanvasTool;
import com.project.website.canvas.client.canvastools.base.interfaces.ICanvasToolEvents;
import com.project.website.canvas.client.resources.CanvasResources;
import com.project.website.canvas.client.worksheet.ElementDragManagerImpl;
import com.project.website.canvas.client.worksheet.interfaces.ElementDragManager.StopCondition;
import com.project.website.canvas.client.worksheet.interfaces.MouseMoveOperationHandler;
import com.project.website.canvas.shared.data.ElementData;
import com.project.website.canvas.shared.data.SiteCropElementData;
//TODO:
//8. Add "Reset" button
//Chrome Problems:
//Sometimes when dragging the inner frame an exception is thrown due to null mouse position
//IE9 Problems (2):
//Apparently in IE9 they've changed the way IFrames are rendered so now they are rendered using the same engine as
//the parent page. so if the parent page defines a doctype of HTML5, the child page will also be renderd in the same engine.
//which causes problems in some sites (e.g.: ynet.co.il) since they are not supposed to work with that rendering engine.
//http://www.sitepoint.com/forums/showthread.php?743000-IE9-Iframes-DOCTYPES-and-You/page3
public class SiteCropTool extends Composite implements CanvasTool<SiteCropElementData>{
//#region UiBinder Declarations
private static SiteCropToolUiBinder uiBinder = GWT.create(SiteCropToolUiBinder.class);
interface SiteCropToolUiBinder extends UiBinder<Widget, SiteCropTool> {
}
//#endregion
//#region UiFields
@UiField
FocusPanel rootPanel;
@UiField
Frame siteFrame;
@UiField
FlowPanel frameContainer;
@UiField
HTMLPanel blockPanel;
@UiField
HTMLPanel selectionPanel;
@UiField
HTMLPanel dragPanel;
//#endregion
private static final int MOUSE_SCROLL_PIXELS = 5;
private CanvasToolEvents _toolEvents = new CanvasToolEvents(this);
private SiteCropElementData _data = null;
private final SimpleEvent<Void> _stopMouseOperationEvent = new SimpleEvent<Void>();
private ElementDragManagerImpl _frameDragManager = null;
private SiteFrameSelectionManager _frameSelectionManager = null;
private RegistrationsManager _registrationManager = new RegistrationsManager();
private RegistrationsManager _modeRegistrations = new RegistrationsManager();
private RegistrationsManager _loadedRegistrations = new RegistrationsManager();
private RegistrationsManager _moveRegistrationManager = new RegistrationsManager();
private RegistrationsManager _cropRegistrationManager = new RegistrationsManager();
private Rectangle _minimalRectangle = Rectangle.empty;
private boolean _isViewMode = false;
private boolean _isActive = false;
private boolean _isLoaded = false;
//TODO: Make singleton and update according to data when displayed.
private final SiteCropToolbar _toolbar = new SiteCropToolbar();
private final ScheduledCommand _refreshUrlCommand = new ScheduledCommand() {
@Override
public void execute() {
loadUrl();
}};
public SiteCropTool() {
initWidget(uiBinder.createAndBindUi(this));
this.initializeFrame();
this.addStyleName(CanvasResources.INSTANCE.main().cropSiteToolEmpty());
this.selectionPanel.setVisible(false);
this.dragPanel.setVisible(false);
this.initializeToolbar();
WidgetUtils.disableContextMenu(this.blockPanel);
this._frameDragManager = new ElementDragManagerImpl(
this.frameContainer, this.dragPanel, 0, this._stopMouseOperationEvent);
this._frameSelectionManager = new SiteFrameSelectionManager(
this.frameContainer, this.dragPanel, this.selectionPanel, this._stopMouseOperationEvent);
this.setEditMode();
}
@Override
public ICanvasToolEvents getToolEvents()
{
return this._toolEvents;
}
private void initializeFrame()
{
this.siteFrame.getElement().setPropertyString("scrolling", "no");
this.siteFrame.getElement().setPropertyString("frameborder", "0");
}
private void initializeToolbar()
{
this._toolbar.enableCrop(false);
this._toolbar.enableDrag(false);
this._toolbar.enableBrowse(false);
this._toolbar.setAcceptCropVisibility(false);
}
private void registerGlobalHandlers()
{
this._registrationManager.clear();
this._registrationManager.add(
this.siteFrame.addLoadHandler(new LoadHandler() {
@Override
public void onLoad(LoadEvent event) {
handleFrameLoaded(event);
}
}));
}
private void registerEditModeHandlers()
{
this._modeRegistrations.clear();
this._modeRegistrations.add(this._loadedRegistrations.asSingleRegistration());
this._modeRegistrations.add(this._cropRegistrationManager.asSingleRegistration());
this._modeRegistrations.add(this._moveRegistrationManager.asSingleRegistration());
this._modeRegistrations.add(this.rootPanel.addKeyPressHandler(
new SpecificKeyPressHandler(KeyCodes.KEY_ESCAPE) {
@Override
public void onSpecificKeyPress(KeyPressEvent event) {
clearSelection();
}
}));
this._modeRegistrations.add(
this._toolbar.addUrlChangedHandler(new Handler<String>() {
@Override
public void onFire(String arg) {
updateUrl(arg);
}
}));
this._modeRegistrations.add(
this._toolbar.addBrowseRequestHandler(new Handler<Void>() {
@Override
public void onFire(Void arg) {
WindowUtils.openNewTab(siteFrame.getUrl());
}
}));
this._modeRegistrations.add(
this._toolbar.addIsInteractiveChangedHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
_data.isInteractive = event.getValue();
}
}));
if (this._isLoaded)
{
this.registerLoadedEditModeHandlers();
}
// ONLY FOR DEBUG
this._modeRegistrations.add(
this._toolbar.addDebugClickRequestHandler(new Handler<Void>() {
@Override
public void onFire(Void arg) {
updateUrl("http://www.google.com");
}
}));
}
private void registerViewModeHandlers()
{
this._modeRegistrations.clear();
}
private void registerLoadedEditModeHandlers()
{
this._loadedRegistrations.clear();
this._loadedRegistrations.add(
this.addDomHandler(new MouseWheelHandler() {
@Override
public void onMouseWheel(MouseWheelEvent event) {
handleMouseScroll(event);
}
}, MouseWheelEvent.getType()));;
this._loadedRegistrations.add(
this._toolbar.addToggleDragRequestHandler(new Handler<Boolean>() {
@Override
public void onFire(Boolean arg) {
if (arg) {
enableSiteMove();
}
else {
disableSiteMove();
}
}
}));
this._loadedRegistrations.add(
this._toolbar.addToggleCropModeRequestHandler(new Handler<Boolean>() {
@Override
public void onFire(Boolean arg) {
if (arg) {
enableSiteCrop();
}
else {
disableSiteCrop();
}
}
}));
}
private void clearSelection()
{
_stopMouseOperationEvent.dispatch(null);
this._frameSelectionManager.clearSelection();
}
private void handleFrameLoaded(LoadEvent event)
{
if (Strings.isNullOrEmpty(this.siteFrame.getUrl()))
{
return;
}
this.onLoadEnded();
}
private void setDefaultMode()
{
this._toolbar.toggleDrag();
//We don't know if all the handlers are already registered and therefore we can't count on the toolbar
//to raise the appropriate event which will enable the actual behavior.
this.enableSiteMove();
}
private void cropSelectedFrame()
{
Rectangle selectionRect = this._frameSelectionManager.getSelectedRectangle();
Rectangle frameRect = ElementUtils.getElementOffsetRectangle(this.siteFrame.getElement());
frameRect = frameRect.move(new Point2D(
frameRect.getLeft() - selectionRect.getLeft(), frameRect.getTop() - selectionRect.getTop()));
this.updateFrameDimensions(frameRect);
this._toolEvents.dispatchSelfMoveRequestEvent(new Point2D(
selectionRect.getLeft(), selectionRect.getTop()));
ElementUtils.setElementSize(this.getElement(), selectionRect.getSize());
this.setMinimalRectangle(frameRect);
this.clearSelection();
this.setDefaultMode();
}
private void enableSiteMove()
{
this._moveRegistrationManager.clear();
this._moveRegistrationManager.add(
this.blockPanel.addDomHandler(new MouseButtonDownHandler(MouseButtons.Left) {
@Override
public void onMouseButtonDown(MouseDownEvent event) {
MouseMoveOperationHandler handler = new MouseMoveOperationHandler() {
private Rectangle initialFrameRectangle = null;
private Point2D lastMousePos = null;
@Override public void onStop(Point2D pos) { }
@Override public void onStart() {
initialFrameRectangle = ElementUtils.getElementOffsetRectangle(siteFrame.getElement());
lastMousePos = Point2D.zero;
}
@Override public void onCancel() {
updateFrameDimensions(initialFrameRectangle);
}
@Override
public void onMouseMove(Point2D pos)
{
Point2D delta = pos.minus(lastMousePos);
lastMousePos = pos;
updateFrameDimensions(moveFrame(
ElementUtils.getElementOffsetRectangle(siteFrame.getElement()), delta));
}
};
_frameDragManager.startMouseMoveOperation(blockPanel.getElement(),
ElementUtils.getRelativePosition(event, blockPanel.getElement()),
handler, StopCondition.STOP_CONDITION_MOVEMENT_STOP);
}
}, MouseDownEvent.getType()));
}
private void handleMouseScroll(MouseWheelEvent event)
{
this.clearSelection();
Rectangle frameRect = ElementUtils.getElementOffsetRectangle(siteFrame.getElement());
this.updateFrameDimensions(this.moveFrame(frameRect, new Point2D(0, event.getDeltaY() * -(MOUSE_SCROLL_PIXELS))));
}
private Rectangle moveFrame(Rectangle frameRectangle, Point2D delta)
{
return new Rectangle(
Math.min(0, frameRectangle.getLeft() + delta.getX()),
Math.min(0, frameRectangle.getTop() + delta.getY()),
frameRectangle.getRight(),
frameRectangle.getBottom());
}
private void disableSiteMove()
{
this._moveRegistrationManager.clear();
}
private void enableSiteCrop()
{
this._cropRegistrationManager.clear();
this._cropRegistrationManager.add(
this.blockPanel.addDomHandler(new MouseButtonDownHandler(MouseButtons.Left) {
@Override
public void onMouseButtonDown(MouseDownEvent event) {
_toolbar.setAcceptCropVisibility(false);
_frameSelectionManager.startSelectionDrag(event, new Handler<Void>() {
@Override
public void onFire(Void arg) {
_toolbar.setAcceptCropVisibility(true);
}
});
}
}, MouseDownEvent.getType()));
this._cropRegistrationManager.add(
_toolbar.addAcceptCropRequestHandler(new Handler<Void>() {
@Override
public void onFire(Void arg) {
cropSelectedFrame();
}
}));
}
private void disableSiteCrop()
{
this._cropRegistrationManager.clear();
this.clearSelection();
this._toolbar.setAcceptCropVisibility(false);
}
private void setMinimalRectangle(Rectangle rectangle)
{
this._minimalRectangle = new Rectangle(rectangle);
}
private void updateFrameDimensions(Rectangle rectangle)
{
this._data.frameRectangle = new Rectangle(rectangle);
this.setFrameParameters();
}
private void setFrameParameters()
{
if (this._data.frameRectangle.equals(Rectangle.empty))
{
return;
}
ElementUtils.setElementRectangle(this.siteFrame.getElement(), this._data.frameRectangle);
}
private void updateUrl(String url) {
if (false == this._data.url.equalsIgnoreCase(url))
{
this.resetFramePosition();
this._data.url = UrlUtils.ensureProtocol(url);
}
this.loadUrl();
}
private void loadUrl()
{
if (false == this.isValidUrl(this._data.url))
{
return;
}
this.onLoadStarted();
this.siteFrame.setUrl(this._data.url);
ElementUtils.setElementSize(this.getElement(),
ElementUtils.getElementOffsetSize(this.getElement()));
this.removeStyleName(CanvasResources.INSTANCE.main().cropSiteToolEmpty());
this.addStyleName(CanvasResources.INSTANCE.main().cropSiteToolSet());
this._toolbar.setUrl(this._data.url);
this._toolbar.enableBrowse(true);
}
private void onLoadStarted()
{
this._isLoaded = false;
this._toolEvents.dispatchLoadStartedEvent();
this._toolbar.enableCrop(false);
this._toolbar.enableDrag(false);
this._loadedRegistrations.clear();
}
private void onLoadEnded()
{
this._isLoaded = true;
this._toolEvents.dispatchLoadEndedEvent();
this._toolbar.enableCrop(true);
this._toolbar.enableDrag(true);
if (this._isActive)
{
this.reRegisterModeHandlers();
}
}
private void resetFramePosition()
{
this.updateFrameDimensions(ElementUtils.getElementOffsetRectangle(
this.siteFrame.getElement()).move(new Point2D(0, 0)));
this.setMinimalRectangle(Rectangle.empty);
this.onResize();
}
private boolean isValidUrl(String url)
{
if (Strings.isNullOrEmpty(url))
{
return false;
}
return true;
}
@Override
public void setValue(SiteCropElementData value) {
//Currently, after saving the worksheet, all the tools are updated with the saved element data.
//so we want to avoid unnecessary load of the url in case nothing was actually changed.
if ((null != this._data) && (this._data.url.equalsIgnoreCase(value.url)))
{
this._data = value;
}
else
{
this._data = value;
this.loadUrl();
}
this.setMinimalRectangle(this._data.frameRectangle);
this.setToolbarData(value);
this.setFrameParameters();
}
private void setToolbarData(SiteCropElementData data)
{
this._toolbar.setIsInteractive(data.isInteractive);
}
@Override
public SiteCropElementData getValue() {
return this._data;
}
@Override
public void setElementData(ElementData data) {
this.setValue((SiteCropElementData)data);
}
@Override
public void setActive(boolean isActive) {
if (isActive == this._isActive)
{
return;
}
this._isActive = isActive;
if (false == isActive)
{
this._modeRegistrations.clear();
return;
}
this.reRegisterModeHandlers();
}
private void reRegisterModeHandlers()
{
if (this._isViewMode)
{
this.registerViewModeHandlers();
}
else
{
this.registerEditModeHandlers();
this.setDefaultMode();
}
}
@Override
public void bind() {
this.registerGlobalHandlers();
}
@Override
public ResizeMode getResizeMode() {
return ResizeMode.BOTH;
}
@Override
public boolean canRotate() {
return true;
}
@Override
public void setViewMode(boolean isViewMode) {
if (isViewMode) {
this.setViewMode();
}
else {
this.setEditMode();
}
}
//Don't register any new event handlers here since we only register them in the setActive method so that all the
//specific handlers (edit mode/view mode) will get called only when the tool is active.
private void setViewMode()
{
this._isViewMode = true;
this._modeRegistrations.clear();
this.clearSelection();
this.setFrameInteractive(this._data.isInteractive);
}
//Don't register any new event handlers here since we only register them in the setActive method so that all the
//specific handlers (edit mode/view mode) will get called only when the tool is active.
private void setEditMode()
{
this._isViewMode = false;
this._modeRegistrations.clear();
this.setFrameInteractive(false);
//Must use Scheduler since it appears that the setEditMode is called during an event and
//for some reason nothing happens when settings the url of the iframe during that event.
OneTimeScheduler.get().scheduleDeferredOnce(this._refreshUrlCommand);
}
private void setFrameInteractive(Boolean isInteractive)
{
blockPanel.setVisible(isInteractive ? false : true);
}
@Override
public void onResize() {
//Only resize the frame if the tool is larger than the minimal rectangle which is set when cropping the frame.
//this is done to prevent unwanted movement in the frame during resize due to the fact that after crop the frame
//size might be bigger than the actual tool.
Rectangle frameRect = ElementUtils.getElementOffsetRectangle(this.siteFrame.getElement());
Rectangle toolRect = ElementUtils.getElementOffsetRectangle(this.getElement());
if (toolRect.getSize().getX() >= this._minimalRectangle.getRight())
{
frameRect.setRight(toolRect.getRight());
}
if (toolRect.getSize().getY() >= this._minimalRectangle.getBottom())
{
frameRect.setBottom(toolRect.getBottom());
}
this.updateFrameDimensions(frameRect);
}
@Override
public IsWidget getToolbar()
{
return this._toolbar;
}
}