Mozilla/mozilla/editor/libeditor/html/nsHTMLObjectResizer.cpp
kin%netscape.com dcb54fbdfe Fix for bug 199490: A crash occurs after clicking in (HTML tags )document - Trunk [@
nsHTMLEditor::SetFinalSize]

  - A one line fix in HideResizers() which prevents the
    reported crash by setting mIsResizing to false. I assume
    we can't be resizing if the resize handles are being hidden.
    The assertion and null check in SetFinalSize() are just because
    I'm paranoid, but they should never be triggered if HideResizers()
    is called first.

  - The changes in StartResizing() makes it so that we don't create
    a new mouse motion listener if we already have one. We were
    creating a new listener each time we clicked on the handles, and
    never unregistering the old one ... this meant that the old
    listeners could be triggered if the editor were ever destroyed
    and the document left in tact ... allowing us to crash because the
    listeners keep an un-addref'd pointer to the HTMLEditor. Note that
    this crash is not likely to be hit in Mozilla since we always
    destroy the document and editor in Composer and MailCompose, but
    it can happen in an embedding context.

r=glazman@netscape.com  sr=sfraser@netscape.com


git-svn-id: svn://10.0.0.236/trunk@140921 18797224-902f-48f8-a5cc-f745e15eee43
2003-04-09 21:10:58 +00:00

1135 lines
37 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.org.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corp.
* Portions created by the Initial Developer are Copyright (C) 2003
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Daniel Glazman (glazman@netscape.com) (Original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsHTMLObjectResizer.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMNSHTMLElement.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMText.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIDocumentObserver.h"
#include "nsIEditor.h"
#include "nsIPresShell.h"
#include "nsIScriptGlobalObject.h"
#include "nsHTMLEditor.h"
#include "nsEditor.h"
#include "nsEditorUtils.h"
#include "nsHTMLEditUtils.h"
#include "nsPoint.h"
class nsHTMLEditUtils;
// ==================================================================
// ResizeEventListener
// ==================================================================
NS_IMPL_ADDREF(ResizeEventListener)
NS_IMPL_RELEASE(ResizeEventListener)
NS_INTERFACE_MAP_BEGIN(ResizeEventListener)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
ResizeEventListener::ResizeEventListener(nsIHTMLEditor * aEditor) :
mEditor(aEditor)
{
}
ResizeEventListener::~ResizeEventListener()
{
}
NS_IMETHODIMP
ResizeEventListener::HandleEvent(nsIDOMEvent* aMouseEvent)
{
nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryInterface(mEditor);
objectResizer->RefreshResizers();
return NS_OK;
}
// ==================================================================
// ResizerSelectionListener
// ==================================================================
NS_IMPL_ADDREF(ResizerSelectionListener)
NS_IMPL_RELEASE(ResizerSelectionListener)
NS_INTERFACE_MAP_BEGIN(ResizerSelectionListener)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsISelectionListener)
NS_INTERFACE_MAP_END
ResizerSelectionListener::ResizerSelectionListener(nsIHTMLEditor * aEditor) :
mEditor(aEditor)
{
}
ResizerSelectionListener::~ResizerSelectionListener()
{
}
NS_IMETHODIMP
ResizerSelectionListener::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aSelection,short 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<nsIHTMLObjectResizer> objectResizer = do_QueryInterface(mEditor);
objectResizer->CheckResizingState(aSelection);
}
return NS_OK;
}
// ==================================================================
// ResizerMouseMotionListener
// ==================================================================
NS_IMPL_ADDREF(ResizerMouseMotionListener)
NS_IMPL_RELEASE(ResizerMouseMotionListener)
ResizerMouseMotionListener::ResizerMouseMotionListener(nsIHTMLEditor * aEditor):
mEditor(aEditor)
{
}
ResizerMouseMotionListener::~ResizerMouseMotionListener()
{
}
NS_IMETHODIMP
ResizerMouseMotionListener::MouseMove(nsIDOMEvent* aMouseEvent)
{
NS_ENSURE_ARG_POINTER(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 editor
nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
if (htmlEditor)
{
// check if we have to redisplay a resizing shadow
nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryInterface(htmlEditor);
objectResizer->MouseMove(aMouseEvent);
}
return NS_OK;
}
NS_IMETHODIMP
ResizerMouseMotionListener::HandleEvent(nsIDOMEvent* aMouseEvent)
{
return NS_OK;
}
NS_IMETHODIMP
ResizerMouseMotionListener::DragMove(nsIDOMEvent* aMouseEvent)
{
return NS_OK;
}
NS_INTERFACE_MAP_BEGIN(ResizerMouseMotionListener)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMMouseMotionListener)
NS_INTERFACE_MAP_END
// ==================================================================
// nsHTMLEditor
// ==================================================================
nsresult
nsHTMLEditor::CreateResizer(nsIDOMElement ** aReturn, PRInt16 aLocation, nsISupportsArray * aArray)
{
// let's create a span through the element factory
nsCOMPtr<nsIContent> newContent;
nsresult res = CreateHTMLContent(NS_LITERAL_STRING("span"), getter_AddRefs(newContent));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent);
if (!newElement)
return NS_ERROR_FAILURE;
*aReturn = newElement;
NS_ADDREF(*aReturn);
// add the mouse listener so we can detect a click on a resizer
nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(*aReturn));
evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mMouseListenerP, PR_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;
}
res = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"),
NS_LITERAL_STRING("mozResizer"));
if (NS_FAILED(res)) return res;
res = newElement->SetAttribute(NS_LITERAL_STRING("anonlocation"),
locationStr);
if (NS_FAILED(res)) return res;
return aArray->AppendElement(newContent);
}
nsresult
nsHTMLEditor::CreateShadow(nsIDOMElement ** aReturn, nsISupportsArray * aArray)
{
// let's create an image through the element factory
nsCOMPtr<nsIContent> newContent;
nsAutoString name;
if (NodeIsType(mResizedObject, NS_LITERAL_STRING("img")))
name = NS_LITERAL_STRING("img");
else
name = NS_LITERAL_STRING("span");
nsresult res = CreateHTMLContent(name, getter_AddRefs(newContent));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent);
if (!newElement)
return NS_ERROR_FAILURE;
*aReturn = newElement;
NS_ADDREF(*aReturn);
return aArray->AppendElement(newContent);
}
nsresult
nsHTMLEditor::CreateResizingInfo(nsIDOMElement ** aReturn, nsISupportsArray * aArray)
{
// let's create an image through the element factory
nsCOMPtr<nsIContent> newContent;
nsresult res = CreateHTMLContent(NS_LITERAL_STRING("span"), getter_AddRefs(newContent));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newContent);
if (NS_FAILED(res) || !newElement)
return NS_ERROR_FAILURE;
*aReturn = newElement;
NS_ADDREF(*aReturn);
res = newElement->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"),
NS_LITERAL_STRING("mozResizingInfo"));
if (NS_FAILED(res)) return res;
res = newElement->SetAttribute(NS_LITERAL_STRING("class"),
NS_LITERAL_STRING("hidden"));
if (NS_FAILED(res)) return res;
return aArray->AppendElement(newContent);
}
void
nsHTMLEditor::SetResizerPosition(PRInt32 aX, PRInt32 aY, nsIDOMElement *aResizer)
{
nsAutoString x, y;
x.AppendInt(aX);
y.AppendInt(aY);
mHTMLCSSUtils->SetCSSProperty(aResizer,
nsIEditProperty::cssLeft,
x + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(aResizer,
nsIEditProperty::cssTop,
y + NS_LITERAL_STRING("px"),
PR_TRUE);
}
nsresult
nsHTMLEditor::SetAllResizersPosition(nsIDOMElement * aResizedElement, PRInt32 & aX, PRInt32 & aY)
{
nsCOMPtr<nsIDOMNSHTMLElement> nsElement = do_QueryInterface(aResizedElement);
if (!nsElement) {return NS_ERROR_NULL_POINTER; }
GetElementOrigin(aResizedElement, aX, aY);
mResizedObjectX = aX;
mResizedObjectY = aY;
nsresult res = nsElement->GetOffsetWidth(&mResizedObjectWidth );
if (NS_FAILED(res)) return res;
res = nsElement->GetOffsetHeight(&mResizedObjectHeight);
if (NS_FAILED(res)) return res;
PRInt32 w = mResizedObjectWidth;
PRInt32 h = mResizedObjectHeight;
// now let's place all the resizers around the image
SetResizerPosition(aX-4, aY-4, mTopLeftHandle);
SetResizerPosition(aX+w/2-3, aY-4, mTopHandle);
SetResizerPosition(aX+w-2, aY-4, mTopRightHandle);
SetResizerPosition(aX-4, aY+h/2-3, mLeftHandle);
SetResizerPosition(aX+w-2, aY+h/2-3, mRightHandle);
SetResizerPosition(aX-4, aY+h-2, mBottomLeftHandle);
SetResizerPosition(aX+w/2-3, aY+h-2, mBottomHandle);
SetResizerPosition(aX+w-2, aY+h-2, mBottomRightHandle);
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::RefreshResizers()
{
PRInt32 x, y;
nsresult res = SetAllResizersPosition(mResizedObject, x, y);
if (NS_FAILED(res)) return res;
return SetShadowPosition(mResizedObject, x, y);
}
NS_IMETHODIMP
nsHTMLEditor::ShowResizers(nsIDOMElement *aResizedElement)
{
// we are going to need the PresShell
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsISupportsArray> anonymousItems;
NS_NewISupportsArray(getter_AddRefs(anonymousItems));
if (!anonymousItems) return NS_ERROR_NULL_POINTER;
mResizedObject = aResizedElement;
// the resizers and the shadow will be anonymous children of the body
nsCOMPtr<nsIDOMElement> bodyElement;
nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIContent> bodyContent( do_QueryInterface(bodyElement) );
// let's create the resizers
res = CreateResizer(getter_AddRefs(mTopLeftHandle),
nsIHTMLObjectResizer::eTopLeft, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mTopHandle),
nsIHTMLObjectResizer::eTop, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mTopRightHandle),
nsIHTMLObjectResizer::eTopRight, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mLeftHandle),
nsIHTMLObjectResizer::eLeft, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mRightHandle),
nsIHTMLObjectResizer::eRight, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mBottomLeftHandle),
nsIHTMLObjectResizer::eBottomLeft, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mBottomHandle),
nsIHTMLObjectResizer::eBottom, anonymousItems);
if (NS_FAILED(res)) return res;
res = CreateResizer(getter_AddRefs(mBottomRightHandle),
nsIHTMLObjectResizer::eBottomRight, anonymousItems);
if (NS_FAILED(res)) return res;
// and let's set their absolute positions in the document
PRInt32 x, y;
res = SetAllResizersPosition(aResizedElement, x, y);
if (NS_FAILED(res)) return res;
// now, let's create the resizing shadow
res = CreateShadow(getter_AddRefs(mResizingShadow), anonymousItems);
if (NS_FAILED(res)) return res;
// and set its position
res = SetShadowPosition(aResizedElement, x, y);
if (NS_FAILED(res)) return res;
mResizingShadow->SetAttribute(NS_LITERAL_STRING("class"),
NS_LITERAL_STRING("hidden"));
// and then the resizing info tooltip
res = CreateResizingInfo(getter_AddRefs(mResizingInfo), anonymousItems);
if (NS_FAILED(res)) return res;
PRUint32 count = 0;
anonymousItems->Count(&count);
// we are going to need the document too
nsCOMPtr<nsIDOMDocument> domDoc;
GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (!doc) return NS_ERROR_NULL_POINTER;
for (PRUint32 i=0; i < count; i++) {
// for each anonymous node we created, set what's needed to make it work
// and create its frames
nsCOMPtr<nsIContent> content;
if (NS_FAILED(anonymousItems->QueryElementAt(i, NS_GET_IID(nsIContent), getter_AddRefs(content))))
continue;
content->SetNativeAnonymous(PR_TRUE);
content->SetParent(bodyContent);
content->SetDocument(doc, PR_TRUE, PR_TRUE);
content->SetBindingParent(content);
ps->RecreateFramesFor(content);
}
// and listen to the "resize" event on the window
// first, get the script global object from the document...
nsCOMPtr<nsIScriptGlobalObject> global;
res = doc->GetScriptGlobalObject(getter_AddRefs(global));
if (NS_FAILED(res)) return res;
if (!global) { return NS_ERROR_NULL_POINTER; }
mResizeEventListenerP = new ResizeEventListener(this);
if (!mResizeEventListenerP) { return NS_ERROR_NULL_POINTER; }
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(global);
res = target->AddEventListener(NS_LITERAL_STRING("resize"), mResizeEventListenerP, PR_FALSE);
return res;
}
void
nsHTMLEditor::DeleteRefToAnonymousNode(nsIDOMElement* aElement,
nsIContent * aParentContent,
nsIDocumentObserver * aDocObserver)
{
// call ContentRemoved() for the anonymous content
// node so its references get removed from the frame manager's
// undisplay map, and its layout frames get destroyed!
nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
if (content) {
aDocObserver->ContentRemoved(nsnull, aParentContent, content, -1);
content->SetDocument(nsnull, PR_TRUE, PR_TRUE);
content->SetParent(nsnull);
}
}
NS_IMETHODIMP
nsHTMLEditor::HideResizers(void)
{
if (!mIsShowingResizeHandles || !mResizedObject)
return NS_OK;
// get the presshell's document observer interface.
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDocumentObserver> docObserver(do_QueryInterface(ps));
if (!docObserver) return NS_ERROR_FAILURE;
// get the root content node.
nsCOMPtr<nsIDOMElement> bodyElement;
nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIContent> bodyContent( do_QueryInterface(bodyElement) );
if (!bodyContent) return NS_ERROR_FAILURE;
DeleteRefToAnonymousNode(mTopLeftHandle, bodyContent, docObserver);
mTopLeftHandle = nsnull;
DeleteRefToAnonymousNode(mTopHandle, bodyContent, docObserver);
mTopHandle = nsnull;
DeleteRefToAnonymousNode(mTopRightHandle, bodyContent, docObserver);
mTopRightHandle = nsnull;
DeleteRefToAnonymousNode(mLeftHandle, bodyContent, docObserver);
mLeftHandle = nsnull;
DeleteRefToAnonymousNode(mRightHandle, bodyContent, docObserver);
mRightHandle = nsnull;
DeleteRefToAnonymousNode(mBottomLeftHandle, bodyContent, docObserver);
mBottomLeftHandle = nsnull;
DeleteRefToAnonymousNode(mBottomHandle, bodyContent, docObserver);
mBottomHandle = nsnull;
DeleteRefToAnonymousNode(mBottomRightHandle, bodyContent, docObserver);
mBottomRightHandle = nsnull;
DeleteRefToAnonymousNode(mResizingShadow, bodyContent, docObserver);
mResizingShadow = nsnull;
DeleteRefToAnonymousNode(mResizingInfo, bodyContent, docObserver);
mResizingInfo = nsnull;
// don't forget to remove the listeners !
nsCOMPtr<nsIDOMEventReceiver> erP;
res = GetDOMEventReceiver(getter_AddRefs(erP));
if (NS_SUCCEEDED(res) && erP && mMouseMotionListenerP)
{
res = erP->RemoveEventListener(NS_LITERAL_STRING("mousemove"), mMouseMotionListenerP, PR_TRUE);
NS_ASSERTION(NS_SUCCEEDED(res), "failed to remove mouse motion listener");
}
mMouseMotionListenerP = nsnull;
nsCOMPtr<nsIDOMDocument> domDoc;
GetDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (!doc) { return NS_ERROR_NULL_POINTER; }
nsCOMPtr<nsIScriptGlobalObject> global;
res = doc->GetScriptGlobalObject(getter_AddRefs(global));
if (NS_FAILED(res)) return res;
if (!global) { return NS_ERROR_NULL_POINTER; }
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(global);
if (target && mResizeEventListenerP) {
res = target->RemoveEventListener(NS_LITERAL_STRING("resize"), mResizeEventListenerP, PR_FALSE);
NS_ASSERTION(NS_SUCCEEDED(res), "failed to remove resize event listener");
}
mResizeEventListenerP = nsnull;
mIsShowingResizeHandles = PR_FALSE;
mResizedObject = nsnull;
mIsResizing = PR_FALSE;
return NS_OK;
}
void
nsHTMLEditor::HideShadowAndInfo()
{
if (mResizingShadow)
mResizingShadow->SetAttribute(NS_LITERAL_STRING("class"), NS_LITERAL_STRING("hidden"));
if (mResizingInfo)
mResizingInfo->SetAttribute(NS_LITERAL_STRING("class"), NS_LITERAL_STRING("hidden"));
}
nsresult
nsHTMLEditor::StartResizing(nsIDOMElement *aHandle)
{
mIsResizing = PR_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, PR_TRUE);
else if (locationStr.Equals(kTop))
SetResizeIncrements(0, 1, 0, -1, PR_FALSE);
else if (locationStr.Equals(kTopRight))
SetResizeIncrements(0, 1, 1, -1, PR_TRUE);
else if (locationStr.Equals(kLeft))
SetResizeIncrements(1, 0, -1, 0, PR_FALSE);
else if (locationStr.Equals(kRight))
SetResizeIncrements(0, 0, 1, 0, PR_FALSE);
else if (locationStr.Equals(kBottomLeft))
SetResizeIncrements(1, 0, -1, 1, PR_TRUE);
else if (locationStr.Equals(kBottom))
SetResizeIncrements(0, 0, 0, 1, PR_FALSE);
else if (locationStr.Equals(kBottomRight))
SetResizeIncrements(0, 0, 1, 1, PR_TRUE);
// make the shadow appear
mResizingShadow->RemoveAttribute(NS_LITERAL_STRING("class"));
// position it
nsAutoString w, h;
w.AppendInt(mResizedObjectWidth);
h.AppendInt(mResizedObjectHeight);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssWidth,
w + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssHeight,
h + NS_LITERAL_STRING("px"),
PR_TRUE);
nsresult result = NS_OK;
if (!mMouseMotionListenerP)
{
// add a mouse move listener to the editor
mMouseMotionListenerP = new ResizerMouseMotionListener(this);
if (!mMouseMotionListenerP) {return NS_ERROR_NULL_POINTER;}
nsCOMPtr<nsIDOMEventReceiver> erP;
result = GetDOMEventReceiver(getter_AddRefs(erP));
if (NS_SUCCEEDED(result) && erP)
{
result = erP->AddEventListener(NS_LITERAL_STRING("mousemove"), mMouseMotionListenerP, PR_TRUE);
NS_ASSERTION(NS_SUCCEEDED(result), "failed to register mouse motion listener");
}
else
HandleEventListenerError();
}
return result;
}
NS_IMETHODIMP
nsHTMLEditor::MouseDown(PRInt32 aClientX, PRInt32 aClientY,
nsIDOMElement *aTarget)
{
PRBool anonElement = PR_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 res = aTarget->GetAttribute(NS_LITERAL_STRING("_moz_anonclass"), anonclass);
if (NS_FAILED(res)) return res;
if (anonclass.Equals(NS_LITERAL_STRING("mozResizer"))) {
// and that element is a resizer, let's start resizing!
mOriginalX = aClientX;
mOriginalY = aClientY;
res = StartResizing(aTarget);
if (NS_FAILED(res)) return res;
}
return NS_OK;
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::MouseUp(PRInt32 aClientX, PRInt32 aClientY,
nsIDOMElement *aResizedObject)
{
if (mIsResizing) {
// we are resizing and release the mouse button, so let's
// end the resizing process
mIsResizing = PR_FALSE;
HideShadowAndInfo();
SetFinalSize(aClientX, aClientY);
}
return NS_OK;
}
void
nsHTMLEditor::SetResizeIncrements(PRInt32 aX, PRInt32 aY,
PRInt32 aW, PRInt32 aH,
PRBool aPreserveRatio)
{
mXIncrementFactor = aX;
mYIncrementFactor = aY;
mWidthIncrementFactor = aW;
mHeightIncrementFactor = aH;
mPreserveRatio = aPreserveRatio;
}
nsresult
nsHTMLEditor::SetResizingInfoPosition(PRInt32 aX, PRInt32 aY, PRInt32 aW, PRInt32 aH)
{
nsAutoString x, y;
x.AppendInt(aX + 20);
y.AppendInt(aY + 20);
mHTMLCSSUtils->SetCSSProperty(mResizingInfo,
nsIEditProperty::cssLeft,
x + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingInfo,
nsIEditProperty::cssTop,
y + NS_LITERAL_STRING("px"),
PR_TRUE);
nsCOMPtr<nsIDOMNode> textInfo;
nsresult res = mResizingInfo->GetFirstChild(getter_AddRefs(textInfo));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode> junk;
if (textInfo) {
res = mResizingInfo->RemoveChild(textInfo, getter_AddRefs(junk));
if (NS_FAILED(res)) return res;
textInfo = nsnull;
junk = nsnull;
}
nsCOMPtr<nsIDOMDocument> domDoc;
GetDocument(getter_AddRefs(domDoc));
nsAutoString widthStr, heightStr, diffWidthStr, diffHeightStr;
widthStr.AppendInt(aW);
heightStr.AppendInt(aH);
PRInt32 diffWidth = aW - mResizedObjectWidth;
PRInt32 diffHeight = aH - mResizedObjectHeight;
if (diffWidth > 0)
diffWidthStr = NS_LITERAL_STRING("+");
if (diffHeight > 0)
diffHeightStr = NS_LITERAL_STRING("+");
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;
res = domDoc->CreateTextNode(info, getter_AddRefs(nodeAsText));
if (NS_FAILED(res)) return res;
textInfo = do_QueryInterface(nodeAsText);
res = mResizingInfo->AppendChild(textInfo, getter_AddRefs(junk));
if (NS_FAILED(res)) return res;
PRBool hasClass = PR_FALSE;
if (NS_SUCCEEDED(mResizingInfo->HasAttribute(NS_LITERAL_STRING("class"), &hasClass )) && hasClass)
res = mResizingInfo->RemoveAttribute(NS_LITERAL_STRING("class"));
return res;
}
nsresult
nsHTMLEditor::SetShadowPosition(nsIDOMElement *aResizedObject,
PRInt32 aX, PRInt32 aY)
{
nsAutoString x, y;
x.AppendInt(aX);
y.AppendInt(aY);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssLeft,
x + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssTop,
y + NS_LITERAL_STRING("px"),
PR_TRUE);
if(NodeIsType(mResizedObject, NS_LITERAL_STRING("img"))) {
nsAutoString imageSource;
nsresult res = aResizedObject->GetAttribute(NS_LITERAL_STRING("src"),
imageSource);
if (NS_FAILED(res)) return res;
res = mResizingShadow->SetAttribute(NS_LITERAL_STRING("src"), imageSource);
if (NS_FAILED(res)) return res;
}
return mResizingShadow->SetAttribute(NS_LITERAL_STRING("_moz_anonclass"),
NS_LITERAL_STRING("mozResizingShadow"));
}
PRInt32
nsHTMLEditor::GetNewResizingIncrement(PRInt32 aX, PRInt32 aY, PRInt32 aID)
{
PRInt32 result = 0;
if (!mPreserveRatio) {
switch (aID) {
case kX:
case kWidth:
result = aX - mOriginalX;
break;
case kY:
case kHeight:
result = aY - mOriginalY;
break;
}
return result;
}
PRInt32 xi = (aX - mOriginalX) * mWidthIncrementFactor;
PRInt32 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 *= objectSizeRatio;
result *= mWidthIncrementFactor;
break;
case kY:
case kHeight:
if (result == xi)
result /= objectSizeRatio;
result *= mHeightIncrementFactor;
break;
}
return result;
}
PRInt32
nsHTMLEditor::GetNewResizingX(PRInt32 aX, PRInt32 aY)
{
PRInt32 resized = mResizedObjectX +
GetNewResizingIncrement(aX, aY, kX) * mXIncrementFactor;
PRInt32 max = mResizedObjectX + mResizedObjectWidth;
return PR_MIN(resized, max);
}
PRInt32
nsHTMLEditor::GetNewResizingY(PRInt32 aX, PRInt32 aY)
{
PRInt32 resized = mResizedObjectY +
GetNewResizingIncrement(aX, aY, kY) * mYIncrementFactor;
PRInt32 max = mResizedObjectY + mResizedObjectHeight;
return PR_MIN(resized, max);
}
PRInt32
nsHTMLEditor::GetNewResizingWidth(PRInt32 aX, PRInt32 aY)
{
PRInt32 resized = mResizedObjectWidth +
GetNewResizingIncrement(aX, aY, kWidth) *
mWidthIncrementFactor;
return PR_MAX(resized, 1);
}
PRInt32
nsHTMLEditor::GetNewResizingHeight(PRInt32 aX, PRInt32 aY)
{
PRInt32 resized = mResizedObjectHeight +
GetNewResizingIncrement(aX, aY, kHeight) *
mHeightIncrementFactor;
return PR_MAX(resized, 1);
}
NS_IMETHODIMP
nsHTMLEditor::MouseMove(nsIDOMEvent* aMouseEvent)
{
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) );
PRInt32 clientX, clientY;
mouseEvent->GetClientX(&clientX);
mouseEvent->GetClientY(&clientY);
nsAutoString x, y, w, h;
PRInt32 newWidth = GetNewResizingWidth(clientX, clientY);
PRInt32 newHeight = GetNewResizingHeight(clientX, clientY);
x.AppendInt(GetNewResizingX(clientX, clientY));
y.AppendInt(GetNewResizingY(clientX, clientY));
w.AppendInt(newWidth);
h.AppendInt(newHeight);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssLeft,
x + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssTop,
y + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssWidth,
w + NS_LITERAL_STRING("px"),
PR_TRUE);
mHTMLCSSUtils->SetCSSProperty(mResizingShadow,
nsIEditProperty::cssHeight,
h + NS_LITERAL_STRING("px"),
PR_TRUE);
SetResizingInfoPosition(clientX, clientY, newWidth, newHeight);
}
return NS_OK;
}
void
nsHTMLEditor::SetFinalSize(PRInt32 aX, PRInt32 aY)
{
NS_ASSERTION(mResizedObject, "SetFinalSize() called with null mResizedObject ptr!");
if (!mResizedObject) return;
// 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
PRInt32 width = GetNewResizingWidth(aX, aY);
PRInt32 height = GetNewResizingHeight(aX, aY);
nsAutoString w, h;
w.AppendInt(width);
h.AppendInt(height);
// keep track of that size
mResizedObjectWidth = width;
mResizedObjectHeight = height;
// we need to know if we're in a CSS-enabled editor or not
PRBool useCSS;
GetIsCSSEnabled(&useCSS);
// we want one transaction only from a user's point of view
nsAutoEditBatch batchIt(this);
NS_NAMED_LITERAL_STRING(widthStr, "width");
NS_NAMED_LITERAL_STRING(heightStr, "height");
PRBool hasAttr = PR_FALSE;
if (useCSS) {
if (NS_SUCCEEDED(mResizedObject->HasAttribute(widthStr, &hasAttr)) && hasAttr)
RemoveAttribute(mResizedObject, widthStr);
hasAttr = PR_FALSE;
if (NS_SUCCEEDED(mResizedObject->HasAttribute(heightStr, &hasAttr)) && hasAttr)
RemoveAttribute(mResizedObject, heightStr);
mHTMLCSSUtils->SetCSSProperty(mResizedObject,
nsIEditProperty::cssWidth,
w + NS_LITERAL_STRING("px"),
PR_FALSE);
mHTMLCSSUtils->SetCSSProperty(mResizedObject,
nsIEditProperty::cssHeight,
h + NS_LITERAL_STRING("px"),
PR_FALSE);
}
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
mHTMLCSSUtils->SetCSSProperty(mResizedObject,
nsIEditProperty::cssWidth,
w + NS_LITERAL_STRING("px"),
PR_FALSE);
mHTMLCSSUtils->SetCSSProperty(mResizedObject,
nsIEditProperty::cssHeight,
h + NS_LITERAL_STRING("px"),
PR_FALSE);
SetAttribute(mResizedObject, widthStr, w);
SetAttribute(mResizedObject, heightStr, h);
mHTMLCSSUtils->RemoveCSSProperty(mResizedObject,
nsIEditProperty::cssWidth,
NS_LITERAL_STRING(""),
PR_FALSE);
mHTMLCSSUtils->RemoveCSSProperty(mResizedObject,
nsIEditProperty::cssHeight,
NS_LITERAL_STRING(""),
PR_FALSE);
}
RefreshResizers();
}
NS_IMETHODIMP
nsHTMLEditor::CheckResizingState(nsISelection *aSelection)
{
NS_ENSURE_ARG_POINTER(aSelection);
if (!mIsImageResizingEnabled)
return NS_OK;
PRBool bCollapsed;
nsresult res = aSelection->GetIsCollapsed(&bCollapsed);
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode> focusNode;
if (bCollapsed) {
res = aSelection->GetFocusNode(getter_AddRefs(focusNode));
if (NS_FAILED(res)) return res;
}
else {
PRInt32 rangeCount;
res = aSelection->GetRangeCount(&rangeCount);
if (NS_FAILED(res)) return res;
if (rangeCount == 1) {
nsCOMPtr<nsIDOMRange> range;
res = aSelection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(res)) return res;
if (!range) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> startContainer, endContainer;
res = range->GetStartContainer(getter_AddRefs(startContainer));
if (NS_FAILED(res)) return res;
res = range->GetEndContainer(getter_AddRefs(endContainer));
if (NS_FAILED(res)) return res;
PRInt32 startOffset, endOffset;
res = range->GetStartOffset(&startOffset);
if (NS_FAILED(res)) return res;
res = range->GetEndOffset(&endOffset);
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> focusElement;
if (startContainer == endContainer && startOffset + 1 == endOffset) {
res = GetSelectedElement(NS_LITERAL_STRING(""), getter_AddRefs(focusElement));
if (NS_FAILED(res)) return res;
if (focusElement)
focusNode = do_QueryInterface(focusElement);
}
if (!focusNode) {
res = range->GetCommonAncestorContainer(getter_AddRefs(focusNode));
if (NS_FAILED(res)) return res;
}
}
else {
PRInt32 i;
nsCOMPtr<nsIDOMRange> range;
for (i = 0; i < rangeCount; i++)
{
res = aSelection->GetRangeAt(i, getter_AddRefs(range));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode> startContainer;
range->GetStartContainer(getter_AddRefs(startContainer));
if (!focusNode)
focusNode = startContainer;
else if (focusNode != startContainer) {
res = startContainer->GetParentNode(getter_AddRefs(focusNode));
if (NS_FAILED(res)) return res;
break;
}
}
}
}
PRUint16 nodeType;
focusNode->GetNodeType(&nodeType);
if (nsIDOMNode::TEXT_NODE == nodeType) {
nsCOMPtr<nsIDOMNode> parent;
res = focusNode->GetParentNode(getter_AddRefs(parent));
if (NS_FAILED(res)) return res;
focusNode = parent;
}
if (focusNode) {
nsCOMPtr<nsIDOMElement>element;
element = do_QueryInterface(focusNode);
nsAutoString tagName;
res = element->GetTagName(tagName);
if (NS_FAILED(res)) return res;
ToLowerCase(tagName);
nsCOMPtr<nsIAtom> tagAtom = getter_AddRefs(NS_NewAtom(tagName));
if (!tagAtom) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> tableContainer = GetEnclosingTable(focusNode);
if (nsIEditProperty::img == tagAtom ||
tableContainer) {
mResizedObjectIsAnImage = PR_FALSE;
if (nsIEditProperty::img == tagAtom)
mResizedObjectIsAnImage = PR_TRUE;
else {
focusNode = tableContainer;
}
// that node is an image, we have to show resizers
element = do_QueryInterface(focusNode);
if (mIsShowingResizeHandles) {
// we were already displaying resizers and we only have to
// refresh the position
nsCOMPtr<nsIDOMNode> resizedNode = do_QueryInterface(mResizedObject);
if (!NodesSameType(focusNode, resizedNode)) {
res = HideResizers();
if (NS_FAILED(res)) return res;
mResizedObject = element;
res = ShowResizers(element);
}
else {
mResizedObject = element;
res = RefreshResizers();
}
}
else {
// run through the whole thing
res = ShowResizers(element);
}
if (NS_FAILED(res)) return res;
mIsShowingResizeHandles = PR_TRUE;
return NS_OK;
}
}
if (mIsShowingResizeHandles) {
// we were showing resizers but the selection is not an image any longer
res = HideResizers();
// just in case we were resizing... (bug 195412)
mIsResizing = PR_FALSE;
if (NS_FAILED(res)) return res;
// paranoia...
// mIsResizing = PR_FALSE;
}
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::GetResizedObject(nsIDOMElement * *aResizedObject)
{
NS_IF_ADDREF(*aResizedObject = mResizedObject);
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::GetResizedObjectSize(PRInt32 *aWidth, PRInt32 *aHeight)
{
*aWidth = mResizedObjectWidth;
*aHeight = mResizedObjectHeight;
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::GetIsImageResizingEnabled(PRBool *aIsImageResizingEnabled)
{
*aIsImageResizingEnabled = mIsImageResizingEnabled;
return NS_OK;
}
NS_IMETHODIMP
nsHTMLEditor::SetIsImageResizingEnabled(PRBool aIsImageResizingEnabled)
{
mIsImageResizingEnabled = aIsImageResizingEnabled;
return NS_OK;
}