summaryrefslogtreecommitdiff
path: root/editor/libeditor/HTMLEditorObjectResizer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/HTMLEditorObjectResizer.cpp')
-rw-r--r--editor/libeditor/HTMLEditorObjectResizer.cpp1059
1 files changed, 1059 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditorObjectResizer.cpp b/editor/libeditor/HTMLEditorObjectResizer.cpp
new file mode 100644
index 0000000000..111a3f9751
--- /dev/null
+++ b/editor/libeditor/HTMLEditorObjectResizer.cpp
@@ -0,0 +1,1059 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/HTMLEditor.h"
+#include "HTMLEditorObjectResizerUtils.h"
+
+#include "HTMLEditUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/EditorUtils.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/mozalloc.h"
+#include "nsAString.h"
+#include "nsAlgorithm.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsIAtom.h"
+#include "nsIContent.h"
+#include "nsID.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMMouseEvent.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMText.h"
+#include "nsIDocument.h"
+#include "nsIEditor.h"
+#include "nsIHTMLObjectResizeListener.h"
+#include "nsIHTMLObjectResizer.h"
+#include "nsIPresShell.h"
+#include "nsISupportsUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsSubstringTuple.h"
+#include "nscore.h"
+#include <algorithm>
+
+class nsISelection;
+
+namespace mozilla {
+
+using namespace dom;
+
+/******************************************************************************
+ * mozilla::DocumentResizeEventListener
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(DocumentResizeEventListener, nsIDOMEventListener)
+
+DocumentResizeEventListener::DocumentResizeEventListener(nsIHTMLEditor* aEditor)
+{
+ mEditor = do_GetWeakReference(aEditor);
+}
+
+NS_IMETHODIMP
+DocumentResizeEventListener::HandleEvent(nsIDOMEvent* aMouseEvent)
+{
+ nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryReferent(mEditor);
+ if (objectResizer) {
+ return objectResizer->RefreshResizers();
+ }
+ return NS_OK;
+}
+
+/******************************************************************************
+ * mozilla::ResizerSelectionListener
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(ResizerSelectionListener, nsISelectionListener)
+
+ResizerSelectionListener::ResizerSelectionListener(nsIHTMLEditor* aEditor)
+{
+ mEditor = do_GetWeakReference(aEditor);
+}
+
+NS_IMETHODIMP
+ResizerSelectionListener::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
+ nsISelection* aSelection,
+ int16_t aReason)
+{
+ if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
+ nsISelectionListener::KEYPRESS_REASON |
+ nsISelectionListener::SELECTALL_REASON)) && aSelection) {
+ // the selection changed and we need to check if we have to
+ // hide and/or redisplay resizing handles
+ nsCOMPtr<nsIHTMLEditor> editor = do_QueryReferent(mEditor);
+ if (editor) {
+ editor->CheckSelectionStateForAnonymousButtons(aSelection);
+ }
+ }
+
+ return NS_OK;
+}
+
+/******************************************************************************
+ * mozilla::ResizerMouseMotionListener
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(ResizerMouseMotionListener, nsIDOMEventListener)
+
+ResizerMouseMotionListener::ResizerMouseMotionListener(nsIHTMLEditor* aEditor)
+{
+ mEditor = do_GetWeakReference(aEditor);
+}
+
+NS_IMETHODIMP
+ResizerMouseMotionListener::HandleEvent(nsIDOMEvent* aMouseEvent)
+{
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
+ if (!mouseEvent) {
+ //non-ui event passed in. bad things.
+ return NS_OK;
+ }
+
+ // Don't do anything special if not an HTML object resizer editor
+ nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryReferent(mEditor);
+ if (objectResizer) {
+ // check if we have to redisplay a resizing shadow
+ objectResizer->MouseMove(aMouseEvent);
+ }
+
+ return NS_OK;
+}
+
+/******************************************************************************
+ * mozilla::HTMLEditor
+ ******************************************************************************/
+
+already_AddRefed<Element>
+HTMLEditor::CreateResizer(int16_t aLocation,
+ nsIDOMNode* aParentNode)
+{
+ nsCOMPtr<nsIDOMElement> retDOM;
+ nsresult rv = CreateAnonymousElement(NS_LITERAL_STRING("span"),
+ aParentNode,
+ NS_LITERAL_STRING("mozResizer"),
+ false,
+ getter_AddRefs(retDOM));
+
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ NS_ENSURE_TRUE(retDOM, nullptr);
+
+ // add the mouse listener so we can detect a click on a resizer
+ nsCOMPtr<nsIDOMEventTarget> evtTarget = do_QueryInterface(retDOM);
+ evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mEventListener,
+ true);
+
+ nsAutoString locationStr;
+ switch (aLocation) {
+ case nsIHTMLObjectResizer::eTopLeft:
+ locationStr = kTopLeft;
+ break;
+ case nsIHTMLObjectResizer::eTop:
+ locationStr = kTop;
+ break;
+ case nsIHTMLObjectResizer::eTopRight:
+ locationStr = kTopRight;
+ break;
+
+ case nsIHTMLObjectResizer::eLeft:
+ locationStr = kLeft;
+ break;
+ case nsIHTMLObjectResizer::eRight:
+ locationStr = kRight;
+ break;
+
+ case nsIHTMLObjectResizer::eBottomLeft:
+ locationStr = kBottomLeft;
+ break;
+ case nsIHTMLObjectResizer::eBottom:
+ locationStr = kBottom;
+ break;
+ case nsIHTMLObjectResizer::eBottomRight:
+ locationStr = kBottomRight;
+ break;
+ }
+
+ nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
+ rv = ret->SetAttr(kNameSpaceID_None, nsGkAtoms::anonlocation, locationStr,
+ true);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+HTMLEditor::CreateShadow(nsIDOMNode* aParentNode,
+ nsIDOMElement* aOriginalObject)
+{
+ // let's create an image through the element factory
+ nsAutoString name;
+ if (HTMLEditUtils::IsImage(aOriginalObject)) {
+ name.AssignLiteral("img");
+ } else {
+ name.AssignLiteral("span");
+ }
+ nsCOMPtr<nsIDOMElement> retDOM;
+ CreateAnonymousElement(name, aParentNode,
+ NS_LITERAL_STRING("mozResizingShadow"), true,
+ getter_AddRefs(retDOM));
+
+ NS_ENSURE_TRUE(retDOM, nullptr);
+
+ nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
+ return ret.forget();
+}
+
+already_AddRefed<Element>
+HTMLEditor::CreateResizingInfo(nsIDOMNode* aParentNode)
+{
+ // let's create an info box through the element factory
+ nsCOMPtr<nsIDOMElement> retDOM;
+ CreateAnonymousElement(NS_LITERAL_STRING("span"), aParentNode,
+ NS_LITERAL_STRING("mozResizingInfo"), true,
+ getter_AddRefs(retDOM));
+
+ nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
+ return ret.forget();
+}
+
+nsresult
+HTMLEditor::SetAllResizersPosition()
+{
+ NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
+
+ int32_t x = mResizedObjectX;
+ int32_t y = mResizedObjectY;
+ int32_t w = mResizedObjectWidth;
+ int32_t h = mResizedObjectHeight;
+
+ // now let's place all the resizers around the image
+
+ // get the size of resizers
+ nsAutoString value;
+ float resizerWidth, resizerHeight;
+ nsCOMPtr<nsIAtom> dummyUnit;
+ mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::width,
+ value);
+ mCSSEditUtils->ParseLength(value, &resizerWidth, getter_AddRefs(dummyUnit));
+ mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::height,
+ value);
+ mCSSEditUtils->ParseLength(value, &resizerHeight, getter_AddRefs(dummyUnit));
+
+ int32_t rw = (int32_t)((resizerWidth + 1) / 2);
+ int32_t rh = (int32_t)((resizerHeight+ 1) / 2);
+
+ SetAnonymousElementPosition(x-rw, y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopLeftHandle)));
+ SetAnonymousElementPosition(x+w/2-rw, y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopHandle)));
+ SetAnonymousElementPosition(x+w-rw-1, y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopRightHandle)));
+
+ SetAnonymousElementPosition(x-rw, y+h/2-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mLeftHandle)));
+ SetAnonymousElementPosition(x+w-rw-1, y+h/2-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mRightHandle)));
+
+ SetAnonymousElementPosition(x-rw, y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomLeftHandle)));
+ SetAnonymousElementPosition(x+w/2-rw, y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomHandle)));
+ SetAnonymousElementPosition(x+w-rw-1, y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomRightHandle)));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::RefreshResizers()
+{
+ // nothing to do if resizers are not displayed...
+ NS_ENSURE_TRUE(mResizedObject, NS_OK);
+
+ nsresult rv =
+ GetPositionAndDimensions(
+ static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)),
+ mResizedObjectX,
+ mResizedObjectY,
+ mResizedObjectWidth,
+ mResizedObjectHeight,
+ mResizedObjectBorderLeft,
+ mResizedObjectBorderTop,
+ mResizedObjectMarginLeft,
+ mResizedObjectMarginTop);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetAllResizersPosition();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetShadowPosition(mResizingShadow, mResizedObject,
+ mResizedObjectX, mResizedObjectY);
+}
+
+NS_IMETHODIMP
+HTMLEditor::ShowResizers(nsIDOMElement* aResizedElement)
+{
+ nsresult rv = ShowResizersInner(aResizedElement);
+ if (NS_FAILED(rv)) {
+ HideResizers();
+ }
+ return rv;
+}
+
+nsresult
+HTMLEditor::ShowResizersInner(nsIDOMElement* aResizedElement)
+{
+ NS_ENSURE_ARG_POINTER(aResizedElement);
+
+ nsCOMPtr<nsIDOMNode> parentNode;
+ nsresult rv = aResizedElement->GetParentNode(getter_AddRefs(parentNode));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mResizedObject) {
+ NS_ERROR("call HideResizers first");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsCOMPtr<nsINode> resizedNode = do_QueryInterface(aResizedElement);
+ if (NS_WARN_IF(!IsDescendantOfEditorRoot(resizedNode))) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mResizedObject = do_QueryInterface(aResizedElement);
+ NS_ENSURE_STATE(mResizedObject);
+
+ // The resizers and the shadow will be anonymous siblings of the element.
+ mTopLeftHandle = CreateResizer(nsIHTMLObjectResizer::eTopLeft, parentNode);
+ NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
+ mTopHandle = CreateResizer(nsIHTMLObjectResizer::eTop, parentNode);
+ NS_ENSURE_TRUE(mTopHandle, NS_ERROR_FAILURE);
+ mTopRightHandle = CreateResizer(nsIHTMLObjectResizer::eTopRight, parentNode);
+ NS_ENSURE_TRUE(mTopRightHandle, NS_ERROR_FAILURE);
+
+ mLeftHandle = CreateResizer(nsIHTMLObjectResizer::eLeft, parentNode);
+ NS_ENSURE_TRUE(mLeftHandle, NS_ERROR_FAILURE);
+ mRightHandle = CreateResizer(nsIHTMLObjectResizer::eRight, parentNode);
+ NS_ENSURE_TRUE(mRightHandle, NS_ERROR_FAILURE);
+
+ mBottomLeftHandle = CreateResizer(nsIHTMLObjectResizer::eBottomLeft, parentNode);
+ NS_ENSURE_TRUE(mBottomLeftHandle, NS_ERROR_FAILURE);
+ mBottomHandle = CreateResizer(nsIHTMLObjectResizer::eBottom, parentNode);
+ NS_ENSURE_TRUE(mBottomHandle, NS_ERROR_FAILURE);
+ mBottomRightHandle = CreateResizer(nsIHTMLObjectResizer::eBottomRight, parentNode);
+ NS_ENSURE_TRUE(mBottomRightHandle, NS_ERROR_FAILURE);
+
+ rv = GetPositionAndDimensions(aResizedElement,
+ mResizedObjectX,
+ mResizedObjectY,
+ mResizedObjectWidth,
+ mResizedObjectHeight,
+ mResizedObjectBorderLeft,
+ mResizedObjectBorderTop,
+ mResizedObjectMarginLeft,
+ mResizedObjectMarginTop);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // and let's set their absolute positions in the document
+ rv = SetAllResizersPosition();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now, let's create the resizing shadow
+ mResizingShadow = CreateShadow(parentNode, aResizedElement);
+ NS_ENSURE_TRUE(mResizingShadow, NS_ERROR_FAILURE);
+ // and set its position
+ rv = SetShadowPosition(mResizingShadow, mResizedObject,
+ mResizedObjectX, mResizedObjectY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // and then the resizing info tooltip
+ mResizingInfo = CreateResizingInfo(parentNode);
+ NS_ENSURE_TRUE(mResizingInfo, NS_ERROR_FAILURE);
+
+ // and listen to the "resize" event on the window first, get the
+ // window from the document...
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(doc->GetWindow());
+ if (!target) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ mResizeEventListenerP = new DocumentResizeEventListener(this);
+ if (!mResizeEventListenerP) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ rv = target->AddEventListener(NS_LITERAL_STRING("resize"),
+ mResizeEventListenerP, false);
+ // XXX Even when it failed to add event listener, should we need to set
+ // _moz_resizing attribute?
+ aResizedElement->SetAttribute(NS_LITERAL_STRING("_moz_resizing"), NS_LITERAL_STRING("true"));
+ return rv;
+}
+
+NS_IMETHODIMP
+HTMLEditor::HideResizers()
+{
+ NS_ENSURE_TRUE(mResizedObject, NS_OK);
+
+ // get the presshell's document observer interface.
+ nsCOMPtr<nsIPresShell> ps = GetPresShell();
+ // We allow the pres shell to be null; when it is, we presume there
+ // are no document observers to notify, but we still want to
+ // UnbindFromTree.
+
+ nsCOMPtr<nsIContent> parentContent;
+
+ if (mTopLeftHandle) {
+ parentContent = mTopLeftHandle->GetParent();
+ }
+
+ NS_NAMED_LITERAL_STRING(mousedown, "mousedown");
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mTopLeftHandle, parentContent, ps);
+ mTopLeftHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mTopHandle, parentContent, ps);
+ mTopHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mTopRightHandle, parentContent, ps);
+ mTopRightHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mLeftHandle, parentContent, ps);
+ mLeftHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mRightHandle, parentContent, ps);
+ mRightHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mBottomLeftHandle, parentContent, ps);
+ mBottomLeftHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mBottomHandle, parentContent, ps);
+ mBottomHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mBottomRightHandle, parentContent, ps);
+ mBottomRightHandle = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mResizingShadow, parentContent, ps);
+ mResizingShadow = nullptr;
+
+ RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
+ mResizingInfo, parentContent, ps);
+ mResizingInfo = nullptr;
+
+ if (mActivatedHandle) {
+ mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
+ true);
+ mActivatedHandle = nullptr;
+ }
+
+ // don't forget to remove the listeners !
+
+ nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
+
+ if (target && mMouseMotionListenerP) {
+ DebugOnly<nsresult> rv =
+ target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
+ mMouseMotionListenerP, true);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove mouse motion listener");
+ }
+ mMouseMotionListenerP = nullptr;
+
+ nsCOMPtr<nsIDocument> doc = GetDocument();
+ if (!doc) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ target = do_QueryInterface(doc->GetWindow());
+ if (!target) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (mResizeEventListenerP) {
+ DebugOnly<nsresult> rv =
+ target->RemoveEventListener(NS_LITERAL_STRING("resize"),
+ mResizeEventListenerP, false);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove resize event listener");
+ }
+ mResizeEventListenerP = nullptr;
+
+ mResizedObject->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_resizing, true);
+ mResizedObject = nullptr;
+
+ return NS_OK;
+}
+
+void
+HTMLEditor::HideShadowAndInfo()
+{
+ if (mResizingShadow) {
+ mResizingShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ NS_LITERAL_STRING("hidden"), true);
+ }
+ if (mResizingInfo) {
+ mResizingInfo->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ NS_LITERAL_STRING("hidden"), true);
+ }
+}
+
+nsresult
+HTMLEditor::StartResizing(nsIDOMElement* aHandle)
+{
+ // First notify the listeners if any
+ for (auto& listener : mObjectResizeEventListeners) {
+ listener->OnStartResizing(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)));
+ }
+
+ mIsResizing = true;
+ mActivatedHandle = do_QueryInterface(aHandle);
+ NS_ENSURE_STATE(mActivatedHandle || !aHandle);
+ mActivatedHandle->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
+ NS_LITERAL_STRING("true"), true);
+
+ // do we want to preserve ratio or not?
+ bool preserveRatio = HTMLEditUtils::IsImage(mResizedObject) &&
+ Preferences::GetBool("editor.resizing.preserve_ratio", true);
+
+ // the way we change the position/size of the shadow depends on
+ // the handle
+ nsAutoString locationStr;
+ aHandle->GetAttribute(NS_LITERAL_STRING("anonlocation"), locationStr);
+ if (locationStr.Equals(kTopLeft)) {
+ SetResizeIncrements(1, 1, -1, -1, preserveRatio);
+ } else if (locationStr.Equals(kTop)) {
+ SetResizeIncrements(0, 1, 0, -1, false);
+ } else if (locationStr.Equals(kTopRight)) {
+ SetResizeIncrements(0, 1, 1, -1, preserveRatio);
+ } else if (locationStr.Equals(kLeft)) {
+ SetResizeIncrements(1, 0, -1, 0, false);
+ } else if (locationStr.Equals(kRight)) {
+ SetResizeIncrements(0, 0, 1, 0, false);
+ } else if (locationStr.Equals(kBottomLeft)) {
+ SetResizeIncrements(1, 0, -1, 1, preserveRatio);
+ } else if (locationStr.Equals(kBottom)) {
+ SetResizeIncrements(0, 0, 0, 1, false);
+ } else if (locationStr.Equals(kBottomRight)) {
+ SetResizeIncrements(0, 0, 1, 1, preserveRatio);
+ }
+
+ // make the shadow appear
+ mResizingShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
+
+ // position it
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
+ mResizedObjectWidth);
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
+ mResizedObjectHeight);
+
+ // add a mouse move listener to the editor
+ nsresult result = NS_OK;
+ if (!mMouseMotionListenerP) {
+ mMouseMotionListenerP = new ResizerMouseMotionListener(this);
+ if (!mMouseMotionListenerP) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+
+ result = target->AddEventListener(NS_LITERAL_STRING("mousemove"),
+ mMouseMotionListenerP, true);
+ NS_ASSERTION(NS_SUCCEEDED(result),
+ "failed to register mouse motion listener");
+ }
+ return result;
+}
+
+NS_IMETHODIMP
+HTMLEditor::MouseDown(int32_t aClientX,
+ int32_t aClientY,
+ nsIDOMElement* aTarget,
+ nsIDOMEvent* aEvent)
+{
+ bool anonElement = false;
+ if (aTarget && NS_SUCCEEDED(aTarget->HasAttribute(NS_LITERAL_STRING("_moz_anonclass"), &anonElement)))
+ // we caught a click on an anonymous element
+ if (anonElement) {
+ nsAutoString anonclass;
+ nsresult rv =
+ aTarget->GetAttribute(NS_LITERAL_STRING("_moz_anonclass"), anonclass);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (anonclass.EqualsLiteral("mozResizer")) {
+ // and that element is a resizer, let's start resizing!
+ aEvent->PreventDefault();
+
+ mOriginalX = aClientX;
+ mOriginalY = aClientY;
+ return StartResizing(aTarget);
+ }
+ if (anonclass.EqualsLiteral("mozGrabber")) {
+ // and that element is a grabber, let's start moving the element!
+ mOriginalX = aClientX;
+ mOriginalY = aClientY;
+ return GrabberClicked();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::MouseUp(int32_t aClientX,
+ int32_t aClientY,
+ nsIDOMElement* aTarget)
+{
+ if (mIsResizing) {
+ // we are resizing and release the mouse button, so let's
+ // end the resizing process
+ mIsResizing = false;
+ HideShadowAndInfo();
+ SetFinalSize(aClientX, aClientY);
+ } else if (mIsMoving || mGrabberClicked) {
+ if (mIsMoving) {
+ mPositioningShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ NS_LITERAL_STRING("hidden"), true);
+ SetFinalPosition(aClientX, aClientY);
+ }
+ if (mGrabberClicked) {
+ EndMoving();
+ }
+ }
+ return NS_OK;
+}
+
+void
+HTMLEditor::SetResizeIncrements(int32_t aX,
+ int32_t aY,
+ int32_t aW,
+ int32_t aH,
+ bool aPreserveRatio)
+{
+ mXIncrementFactor = aX;
+ mYIncrementFactor = aY;
+ mWidthIncrementFactor = aW;
+ mHeightIncrementFactor = aH;
+ mPreserveRatio = aPreserveRatio;
+}
+
+nsresult
+HTMLEditor::SetResizingInfoPosition(int32_t aX,
+ int32_t aY,
+ int32_t aW,
+ int32_t aH)
+{
+ nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
+
+ // Determine the position of the resizing info box based upon the new
+ // position and size of the element (aX, aY, aW, aH), and which
+ // resizer is the "activated handle". For example, place the resizing
+ // info box at the bottom-right corner of the new element, if the element
+ // is being resized by the bottom-right resizer.
+ int32_t infoXPosition;
+ int32_t infoYPosition;
+
+ if (mActivatedHandle == mTopLeftHandle ||
+ mActivatedHandle == mLeftHandle ||
+ mActivatedHandle == mBottomLeftHandle) {
+ infoXPosition = aX;
+ } else if (mActivatedHandle == mTopHandle ||
+ mActivatedHandle == mBottomHandle) {
+ infoXPosition = aX + (aW / 2);
+ } else {
+ // should only occur when mActivatedHandle is one of the 3 right-side
+ // handles, but this is a reasonable default if it isn't any of them (?)
+ infoXPosition = aX + aW;
+ }
+
+ if (mActivatedHandle == mTopLeftHandle ||
+ mActivatedHandle == mTopHandle ||
+ mActivatedHandle == mTopRightHandle) {
+ infoYPosition = aY;
+ } else if (mActivatedHandle == mLeftHandle ||
+ mActivatedHandle == mRightHandle) {
+ infoYPosition = aY + (aH / 2);
+ } else {
+ // should only occur when mActivatedHandle is one of the 3 bottom-side
+ // handles, but this is a reasonable default if it isn't any of them (?)
+ infoYPosition = aY + aH;
+ }
+
+ // Offset info box by 20 so it's not directly under the mouse cursor.
+ const int mouseCursorOffset = 20;
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::left,
+ infoXPosition + mouseCursorOffset);
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::top,
+ infoYPosition + mouseCursorOffset);
+
+ nsCOMPtr<nsIContent> textInfo = mResizingInfo->GetFirstChild();
+ ErrorResult erv;
+ if (textInfo) {
+ mResizingInfo->RemoveChild(*textInfo, erv);
+ NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
+ textInfo = nullptr;
+ }
+
+ nsAutoString widthStr, heightStr, diffWidthStr, diffHeightStr;
+ widthStr.AppendInt(aW);
+ heightStr.AppendInt(aH);
+ int32_t diffWidth = aW - mResizedObjectWidth;
+ int32_t diffHeight = aH - mResizedObjectHeight;
+ if (diffWidth > 0) {
+ diffWidthStr.Assign('+');
+ }
+ if (diffHeight > 0) {
+ diffHeightStr.Assign('+');
+ }
+ diffWidthStr.AppendInt(diffWidth);
+ diffHeightStr.AppendInt(diffHeight);
+
+ nsAutoString info(widthStr + NS_LITERAL_STRING(" x ") + heightStr +
+ NS_LITERAL_STRING(" (") + diffWidthStr +
+ NS_LITERAL_STRING(", ") + diffHeightStr +
+ NS_LITERAL_STRING(")"));
+
+ nsCOMPtr<nsIDOMText> nodeAsText;
+ nsresult rv = domdoc->CreateTextNode(info, getter_AddRefs(nodeAsText));
+ NS_ENSURE_SUCCESS(rv, rv);
+ textInfo = do_QueryInterface(nodeAsText);
+ mResizingInfo->AppendChild(*textInfo, erv);
+ NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
+
+ return mResizingInfo->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
+}
+
+nsresult
+HTMLEditor::SetShadowPosition(Element* aShadow,
+ Element* aOriginalObject,
+ int32_t aOriginalObjectX,
+ int32_t aOriginalObjectY)
+{
+ SetAnonymousElementPosition(aOriginalObjectX, aOriginalObjectY, static_cast<nsIDOMElement*>(GetAsDOMNode(aShadow)));
+
+ if (HTMLEditUtils::IsImage(aOriginalObject)) {
+ nsAutoString imageSource;
+ aOriginalObject->GetAttr(kNameSpaceID_None, nsGkAtoms::src, imageSource);
+ nsresult rv = aShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::src,
+ imageSource, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+int32_t
+HTMLEditor::GetNewResizingIncrement(int32_t aX,
+ int32_t aY,
+ int32_t aID)
+{
+ int32_t result = 0;
+ if (!mPreserveRatio) {
+ switch (aID) {
+ case kX:
+ case kWidth:
+ result = aX - mOriginalX;
+ break;
+ case kY:
+ case kHeight:
+ result = aY - mOriginalY;
+ break;
+ }
+ return result;
+ }
+
+ int32_t xi = (aX - mOriginalX) * mWidthIncrementFactor;
+ int32_t yi = (aY - mOriginalY) * mHeightIncrementFactor;
+ float objectSizeRatio =
+ ((float)mResizedObjectWidth) / ((float)mResizedObjectHeight);
+ result = (xi > yi) ? xi : yi;
+ switch (aID) {
+ case kX:
+ case kWidth:
+ if (result == yi)
+ result = (int32_t) (((float) result) * objectSizeRatio);
+ result = (int32_t) (((float) result) * mWidthIncrementFactor);
+ break;
+ case kY:
+ case kHeight:
+ if (result == xi)
+ result = (int32_t) (((float) result) / objectSizeRatio);
+ result = (int32_t) (((float) result) * mHeightIncrementFactor);
+ break;
+ }
+ return result;
+}
+
+int32_t
+HTMLEditor::GetNewResizingX(int32_t aX,
+ int32_t aY)
+{
+ int32_t resized = mResizedObjectX +
+ GetNewResizingIncrement(aX, aY, kX) * mXIncrementFactor;
+ int32_t max = mResizedObjectX + mResizedObjectWidth;
+ return std::min(resized, max);
+}
+
+int32_t
+HTMLEditor::GetNewResizingY(int32_t aX,
+ int32_t aY)
+{
+ int32_t resized = mResizedObjectY +
+ GetNewResizingIncrement(aX, aY, kY) * mYIncrementFactor;
+ int32_t max = mResizedObjectY + mResizedObjectHeight;
+ return std::min(resized, max);
+}
+
+int32_t
+HTMLEditor::GetNewResizingWidth(int32_t aX,
+ int32_t aY)
+{
+ int32_t resized = mResizedObjectWidth +
+ GetNewResizingIncrement(aX, aY, kWidth) *
+ mWidthIncrementFactor;
+ return std::max(resized, 1);
+}
+
+int32_t
+HTMLEditor::GetNewResizingHeight(int32_t aX,
+ int32_t aY)
+{
+ int32_t resized = mResizedObjectHeight +
+ GetNewResizingIncrement(aX, aY, kHeight) *
+ mHeightIncrementFactor;
+ return std::max(resized, 1);
+}
+
+NS_IMETHODIMP
+HTMLEditor::MouseMove(nsIDOMEvent* aMouseEvent)
+{
+ NS_NAMED_LITERAL_STRING(leftStr, "left");
+ NS_NAMED_LITERAL_STRING(topStr, "top");
+
+ if (mIsResizing) {
+ // we are resizing and the mouse pointer's position has changed
+ // we have to resdisplay the shadow
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
+ int32_t clientX, clientY;
+ mouseEvent->GetClientX(&clientX);
+ mouseEvent->GetClientY(&clientY);
+
+ int32_t newX = GetNewResizingX(clientX, clientY);
+ int32_t newY = GetNewResizingY(clientX, clientY);
+ int32_t newWidth = GetNewResizingWidth(clientX, clientY);
+ int32_t newHeight = GetNewResizingHeight(clientX, clientY);
+
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::left,
+ newX);
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::top,
+ newY);
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
+ newWidth);
+ mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
+ newHeight);
+
+ return SetResizingInfoPosition(newX, newY, newWidth, newHeight);
+ }
+
+ if (mGrabberClicked) {
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
+ int32_t clientX, clientY;
+ mouseEvent->GetClientX(&clientX);
+ mouseEvent->GetClientY(&clientY);
+
+ int32_t xThreshold =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdX, 1);
+ int32_t yThreshold =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 1);
+
+ if (DeprecatedAbs(clientX - mOriginalX) * 2 >= xThreshold ||
+ DeprecatedAbs(clientY - mOriginalY) * 2 >= yThreshold) {
+ mGrabberClicked = false;
+ StartMoving(nullptr);
+ }
+ }
+ if (mIsMoving) {
+ nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
+ int32_t clientX, clientY;
+ mouseEvent->GetClientX(&clientX);
+ mouseEvent->GetClientY(&clientY);
+
+ int32_t newX = mPositionedObjectX + clientX - mOriginalX;
+ int32_t newY = mPositionedObjectY + clientY - mOriginalY;
+
+ SnapToGrid(newX, newY);
+
+ mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::left,
+ newX);
+ mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::top,
+ newY);
+ }
+ return NS_OK;
+}
+
+void
+HTMLEditor::SetFinalSize(int32_t aX,
+ int32_t aY)
+{
+ if (!mResizedObject) {
+ // paranoia
+ return;
+ }
+
+ if (mActivatedHandle) {
+ mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated, true);
+ mActivatedHandle = nullptr;
+ }
+
+ // we have now to set the new width and height of the resized object
+ // we don't set the x and y position because we don't control that in
+ // a normal HTML layout
+ int32_t left = GetNewResizingX(aX, aY);
+ int32_t top = GetNewResizingY(aX, aY);
+ int32_t width = GetNewResizingWidth(aX, aY);
+ int32_t height = GetNewResizingHeight(aX, aY);
+ bool setWidth = !mResizedObjectIsAbsolutelyPositioned || (width != mResizedObjectWidth);
+ bool setHeight = !mResizedObjectIsAbsolutelyPositioned || (height != mResizedObjectHeight);
+
+ int32_t x, y;
+ x = left - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderLeft+mResizedObjectMarginLeft : 0);
+ y = top - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderTop+mResizedObjectMarginTop : 0);
+
+ // we want one transaction only from a user's point of view
+ AutoEditBatch batchIt(this);
+
+ NS_NAMED_LITERAL_STRING(widthStr, "width");
+ NS_NAMED_LITERAL_STRING(heightStr, "height");
+
+ nsCOMPtr<Element> resizedObject = do_QueryInterface(mResizedObject);
+ NS_ENSURE_TRUE(resizedObject, );
+ if (mResizedObjectIsAbsolutelyPositioned) {
+ if (setHeight) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::top, y);
+ }
+ if (setWidth) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::left, x);
+ }
+ }
+ if (IsCSSEnabled() || mResizedObjectIsAbsolutelyPositioned) {
+ if (setWidth && mResizedObject->HasAttr(kNameSpaceID_None, nsGkAtoms::width)) {
+ RemoveAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), widthStr);
+ }
+
+ if (setHeight && mResizedObject->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::height)) {
+ RemoveAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), heightStr);
+ }
+
+ if (setWidth) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::width,
+ width);
+ }
+ if (setHeight) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::height,
+ height);
+ }
+ } else {
+ // we use HTML size and remove all equivalent CSS properties
+
+ // we set the CSS width and height to remove it later,
+ // triggering an immediate reflow; otherwise, we have problems
+ // with asynchronous reflow
+ if (setWidth) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::width,
+ width);
+ }
+ if (setHeight) {
+ mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::height,
+ height);
+ }
+ if (setWidth) {
+ nsAutoString w;
+ w.AppendInt(width);
+ SetAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), widthStr, w);
+ }
+ if (setHeight) {
+ nsAutoString h;
+ h.AppendInt(height);
+ SetAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), heightStr, h);
+ }
+
+ if (setWidth) {
+ mCSSEditUtils->RemoveCSSProperty(*resizedObject, *nsGkAtoms::width,
+ EmptyString());
+ }
+ if (setHeight) {
+ mCSSEditUtils->RemoveCSSProperty(*resizedObject, *nsGkAtoms::height,
+ EmptyString());
+ }
+ }
+ // finally notify the listeners if any
+ for (auto& listener : mObjectResizeEventListeners) {
+ listener->OnEndResizing(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)),
+ mResizedObjectWidth, mResizedObjectHeight, width,
+ height);
+ }
+
+ // keep track of that size
+ mResizedObjectWidth = width;
+ mResizedObjectHeight = height;
+
+ RefreshResizers();
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetResizedObject(nsIDOMElement** aResizedObject)
+{
+ nsCOMPtr<nsIDOMElement> ret = static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject));
+ ret.forget(aResizedObject);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::GetObjectResizingEnabled(bool* aIsObjectResizingEnabled)
+{
+ *aIsObjectResizingEnabled = mIsObjectResizingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::SetObjectResizingEnabled(bool aObjectResizingEnabled)
+{
+ mIsObjectResizingEnabled = aObjectResizingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::AddObjectResizeEventListener(nsIHTMLObjectResizeListener* aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ if (mObjectResizeEventListeners.Contains(aListener)) {
+ /* listener already registered */
+ NS_ASSERTION(false,
+ "trying to register an already registered object resize event listener");
+ return NS_OK;
+ }
+ mObjectResizeEventListeners.AppendElement(*aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLEditor::RemoveObjectResizeEventListener(
+ nsIHTMLObjectResizeListener* aListener)
+{
+ NS_ENSURE_ARG_POINTER(aListener);
+ if (!mObjectResizeEventListeners.Contains(aListener)) {
+ /* listener was not registered */
+ NS_ASSERTION(false,
+ "trying to remove an object resize event listener that was not already registered");
+ return NS_OK;
+ }
+ mObjectResizeEventListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+} // namespace mozilla