Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/HTMLEditor.h"
7 : #include "HTMLEditorObjectResizerUtils.h"
8 :
9 : #include "HTMLEditUtils.h"
10 : #include "mozilla/DebugOnly.h"
11 : #include "mozilla/EditorUtils.h"
12 : #include "mozilla/LookAndFeel.h"
13 : #include "mozilla/MathAlgorithms.h"
14 : #include "mozilla/Preferences.h"
15 : #include "mozilla/mozalloc.h"
16 : #include "nsAString.h"
17 : #include "nsAlgorithm.h"
18 : #include "nsCOMPtr.h"
19 : #include "nsDebug.h"
20 : #include "nsError.h"
21 : #include "nsGkAtoms.h"
22 : #include "nsIAtom.h"
23 : #include "nsIContent.h"
24 : #include "nsID.h"
25 : #include "nsIDOMDocument.h"
26 : #include "nsIDOMElement.h"
27 : #include "nsIDOMEvent.h"
28 : #include "nsIDOMEventTarget.h"
29 : #include "nsIDOMMouseEvent.h"
30 : #include "nsIDOMNode.h"
31 : #include "nsIDocument.h"
32 : #include "nsIPresShell.h"
33 : #include "nsISupportsUtils.h"
34 : #include "nsPIDOMWindow.h"
35 : #include "nsReadableUtils.h"
36 : #include "nsString.h"
37 : #include "nsStringFwd.h"
38 : #include "nsSubstringTuple.h"
39 : #include "nscore.h"
40 : #include <algorithm>
41 :
42 : class nsISelection;
43 :
44 : namespace mozilla {
45 :
46 : using namespace dom;
47 :
48 : /******************************************************************************
49 : * mozilla::DocumentResizeEventListener
50 : ******************************************************************************/
51 :
52 0 : NS_IMPL_ISUPPORTS(DocumentResizeEventListener, nsIDOMEventListener)
53 :
54 0 : DocumentResizeEventListener::DocumentResizeEventListener(
55 0 : HTMLEditor& aHTMLEditor)
56 0 : : mHTMLEditorWeak(&aHTMLEditor)
57 : {
58 0 : }
59 :
60 : NS_IMETHODIMP
61 0 : DocumentResizeEventListener::HandleEvent(nsIDOMEvent* aMouseEvent)
62 : {
63 0 : RefPtr<HTMLEditor> htmlEditor = mHTMLEditorWeak.get();
64 0 : if (htmlEditor) {
65 0 : return htmlEditor->RefreshResizers();
66 : }
67 0 : return NS_OK;
68 : }
69 :
70 : /******************************************************************************
71 : * mozilla::ResizerSelectionListener
72 : ******************************************************************************/
73 :
74 0 : NS_IMPL_ISUPPORTS(ResizerSelectionListener, nsISelectionListener)
75 :
76 0 : ResizerSelectionListener::ResizerSelectionListener(HTMLEditor& aHTMLEditor)
77 0 : : mHTMLEditorWeak(&aHTMLEditor)
78 : {
79 0 : }
80 :
81 : NS_IMETHODIMP
82 0 : ResizerSelectionListener::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
83 : nsISelection* aSelection,
84 : int16_t aReason)
85 : {
86 0 : if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
87 : nsISelectionListener::KEYPRESS_REASON |
88 0 : nsISelectionListener::SELECTALL_REASON)) && aSelection) {
89 : // the selection changed and we need to check if we have to
90 : // hide and/or redisplay resizing handles
91 0 : RefPtr<HTMLEditor> htmlEditor = mHTMLEditorWeak.get();
92 0 : if (htmlEditor) {
93 0 : htmlEditor->CheckSelectionStateForAnonymousButtons(aSelection);
94 : }
95 : }
96 :
97 0 : return NS_OK;
98 : }
99 :
100 : /******************************************************************************
101 : * mozilla::ResizerMouseMotionListener
102 : ******************************************************************************/
103 :
104 0 : NS_IMPL_ISUPPORTS(ResizerMouseMotionListener, nsIDOMEventListener)
105 :
106 0 : ResizerMouseMotionListener::ResizerMouseMotionListener(HTMLEditor& aHTMLEditor)
107 0 : : mHTMLEditorWeak(&aHTMLEditor)
108 : {
109 0 : }
110 :
111 : NS_IMETHODIMP
112 0 : ResizerMouseMotionListener::HandleEvent(nsIDOMEvent* aMouseEvent)
113 : {
114 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
115 0 : if (!mouseEvent) {
116 : //non-ui event passed in. bad things.
117 0 : return NS_OK;
118 : }
119 :
120 : // Don't do anything special if not an HTML object resizer editor
121 0 : RefPtr<HTMLEditor> htmlEditor = mHTMLEditorWeak.get();
122 0 : if (htmlEditor) {
123 : // check if we have to redisplay a resizing shadow
124 0 : htmlEditor->MouseMove(mouseEvent);
125 : }
126 :
127 0 : return NS_OK;
128 : }
129 :
130 : /******************************************************************************
131 : * mozilla::HTMLEditor
132 : ******************************************************************************/
133 :
134 : already_AddRefed<Element>
135 0 : HTMLEditor::CreateResizer(int16_t aLocation,
136 : nsIDOMNode* aParentNode)
137 : {
138 : RefPtr<Element> ret =
139 0 : CreateAnonymousElement(nsGkAtoms::span,
140 : aParentNode,
141 0 : NS_LITERAL_STRING("mozResizer"),
142 0 : false);
143 0 : if (NS_WARN_IF(!ret)) {
144 0 : return nullptr;
145 : }
146 :
147 : // add the mouse listener so we can detect a click on a resizer
148 0 : nsCOMPtr<nsIDOMEventTarget> evtTarget = do_QueryInterface(ret);
149 0 : evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mEventListener,
150 0 : true);
151 :
152 0 : nsAutoString locationStr;
153 0 : switch (aLocation) {
154 : case nsIHTMLObjectResizer::eTopLeft:
155 0 : locationStr = kTopLeft;
156 0 : break;
157 : case nsIHTMLObjectResizer::eTop:
158 0 : locationStr = kTop;
159 0 : break;
160 : case nsIHTMLObjectResizer::eTopRight:
161 0 : locationStr = kTopRight;
162 0 : break;
163 :
164 : case nsIHTMLObjectResizer::eLeft:
165 0 : locationStr = kLeft;
166 0 : break;
167 : case nsIHTMLObjectResizer::eRight:
168 0 : locationStr = kRight;
169 0 : break;
170 :
171 : case nsIHTMLObjectResizer::eBottomLeft:
172 0 : locationStr = kBottomLeft;
173 0 : break;
174 : case nsIHTMLObjectResizer::eBottom:
175 0 : locationStr = kBottom;
176 0 : break;
177 : case nsIHTMLObjectResizer::eBottomRight:
178 0 : locationStr = kBottomRight;
179 0 : break;
180 : }
181 :
182 : nsresult rv =
183 0 : ret->SetAttr(kNameSpaceID_None, nsGkAtoms::anonlocation, locationStr, true);
184 0 : NS_ENSURE_SUCCESS(rv, nullptr);
185 0 : return ret.forget();
186 : }
187 :
188 : already_AddRefed<Element>
189 0 : HTMLEditor::CreateShadow(nsIDOMNode* aParentNode,
190 : nsIDOMElement* aOriginalObject)
191 : {
192 : // let's create an image through the element factory
193 0 : nsCOMPtr<nsIAtom> name;
194 0 : if (HTMLEditUtils::IsImage(aOriginalObject)) {
195 0 : name = nsGkAtoms::img;
196 : } else {
197 0 : name = nsGkAtoms::span;
198 : }
199 : RefPtr<Element> ret =
200 0 : CreateAnonymousElement(name, aParentNode,
201 0 : NS_LITERAL_STRING("mozResizingShadow"), true);
202 0 : return ret.forget();
203 : }
204 :
205 : already_AddRefed<Element>
206 0 : HTMLEditor::CreateResizingInfo(nsIDOMNode* aParentNode)
207 : {
208 : // let's create an info box through the element factory
209 : RefPtr<Element> ret =
210 0 : CreateAnonymousElement(nsGkAtoms::span, aParentNode,
211 0 : NS_LITERAL_STRING("mozResizingInfo"), true);
212 0 : return ret.forget();
213 : }
214 :
215 : nsresult
216 0 : HTMLEditor::SetAllResizersPosition()
217 : {
218 0 : NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
219 :
220 0 : int32_t x = mResizedObjectX;
221 0 : int32_t y = mResizedObjectY;
222 0 : int32_t w = mResizedObjectWidth;
223 0 : int32_t h = mResizedObjectHeight;
224 :
225 : // now let's place all the resizers around the image
226 :
227 : // get the size of resizers
228 0 : nsAutoString value;
229 : float resizerWidth, resizerHeight;
230 0 : nsCOMPtr<nsIAtom> dummyUnit;
231 0 : mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::width,
232 0 : value);
233 0 : mCSSEditUtils->ParseLength(value, &resizerWidth, getter_AddRefs(dummyUnit));
234 0 : mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::height,
235 0 : value);
236 0 : mCSSEditUtils->ParseLength(value, &resizerHeight, getter_AddRefs(dummyUnit));
237 :
238 0 : int32_t rw = (int32_t)((resizerWidth + 1) / 2);
239 0 : int32_t rh = (int32_t)((resizerHeight+ 1) / 2);
240 :
241 0 : SetAnonymousElementPosition(x-rw, y-rh, mTopLeftHandle);
242 0 : SetAnonymousElementPosition(x+w/2-rw, y-rh, mTopHandle);
243 0 : SetAnonymousElementPosition(x+w-rw-1, y-rh, mTopRightHandle);
244 :
245 0 : SetAnonymousElementPosition(x-rw, y+h/2-rh, mLeftHandle);
246 0 : SetAnonymousElementPosition(x+w-rw-1, y+h/2-rh, mRightHandle);
247 :
248 0 : SetAnonymousElementPosition(x-rw, y+h-rh-1, mBottomLeftHandle);
249 0 : SetAnonymousElementPosition(x+w/2-rw, y+h-rh-1, mBottomHandle);
250 0 : SetAnonymousElementPosition(x+w-rw-1, y+h-rh-1, mBottomRightHandle);
251 :
252 0 : return NS_OK;
253 : }
254 :
255 : NS_IMETHODIMP
256 0 : HTMLEditor::RefreshResizers()
257 : {
258 : // nothing to do if resizers are not displayed...
259 0 : NS_ENSURE_TRUE(mResizedObject, NS_OK);
260 :
261 : nsresult rv =
262 0 : GetPositionAndDimensions(
263 0 : static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)),
264 : mResizedObjectX,
265 : mResizedObjectY,
266 : mResizedObjectWidth,
267 : mResizedObjectHeight,
268 : mResizedObjectBorderLeft,
269 : mResizedObjectBorderTop,
270 : mResizedObjectMarginLeft,
271 0 : mResizedObjectMarginTop);
272 :
273 0 : NS_ENSURE_SUCCESS(rv, rv);
274 0 : rv = SetAllResizersPosition();
275 0 : NS_ENSURE_SUCCESS(rv, rv);
276 0 : return SetShadowPosition(mResizingShadow, mResizedObject,
277 0 : mResizedObjectX, mResizedObjectY);
278 : }
279 :
280 : NS_IMETHODIMP
281 0 : HTMLEditor::ShowResizers(nsIDOMElement* aResizedElement)
282 : {
283 0 : nsresult rv = ShowResizersInner(aResizedElement);
284 0 : if (NS_FAILED(rv)) {
285 0 : HideResizers();
286 : }
287 0 : return rv;
288 : }
289 :
290 : nsresult
291 0 : HTMLEditor::ShowResizersInner(nsIDOMElement* aResizedElement)
292 : {
293 0 : NS_ENSURE_ARG_POINTER(aResizedElement);
294 :
295 0 : nsCOMPtr<nsIDOMNode> parentNode;
296 0 : nsresult rv = aResizedElement->GetParentNode(getter_AddRefs(parentNode));
297 0 : NS_ENSURE_SUCCESS(rv, rv);
298 :
299 0 : if (mResizedObject) {
300 0 : NS_ERROR("call HideResizers first");
301 0 : return NS_ERROR_UNEXPECTED;
302 : }
303 0 : mResizedObject = do_QueryInterface(aResizedElement);
304 0 : NS_ENSURE_STATE(mResizedObject);
305 :
306 : // The resizers and the shadow will be anonymous siblings of the element.
307 0 : mTopLeftHandle = CreateResizer(nsIHTMLObjectResizer::eTopLeft, parentNode);
308 0 : NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
309 0 : mTopHandle = CreateResizer(nsIHTMLObjectResizer::eTop, parentNode);
310 0 : NS_ENSURE_TRUE(mTopHandle, NS_ERROR_FAILURE);
311 0 : mTopRightHandle = CreateResizer(nsIHTMLObjectResizer::eTopRight, parentNode);
312 0 : NS_ENSURE_TRUE(mTopRightHandle, NS_ERROR_FAILURE);
313 :
314 0 : mLeftHandle = CreateResizer(nsIHTMLObjectResizer::eLeft, parentNode);
315 0 : NS_ENSURE_TRUE(mLeftHandle, NS_ERROR_FAILURE);
316 0 : mRightHandle = CreateResizer(nsIHTMLObjectResizer::eRight, parentNode);
317 0 : NS_ENSURE_TRUE(mRightHandle, NS_ERROR_FAILURE);
318 :
319 0 : mBottomLeftHandle = CreateResizer(nsIHTMLObjectResizer::eBottomLeft, parentNode);
320 0 : NS_ENSURE_TRUE(mBottomLeftHandle, NS_ERROR_FAILURE);
321 0 : mBottomHandle = CreateResizer(nsIHTMLObjectResizer::eBottom, parentNode);
322 0 : NS_ENSURE_TRUE(mBottomHandle, NS_ERROR_FAILURE);
323 0 : mBottomRightHandle = CreateResizer(nsIHTMLObjectResizer::eBottomRight, parentNode);
324 0 : NS_ENSURE_TRUE(mBottomRightHandle, NS_ERROR_FAILURE);
325 :
326 0 : rv = GetPositionAndDimensions(aResizedElement,
327 : mResizedObjectX,
328 : mResizedObjectY,
329 : mResizedObjectWidth,
330 : mResizedObjectHeight,
331 : mResizedObjectBorderLeft,
332 : mResizedObjectBorderTop,
333 : mResizedObjectMarginLeft,
334 0 : mResizedObjectMarginTop);
335 0 : NS_ENSURE_SUCCESS(rv, rv);
336 :
337 : // and let's set their absolute positions in the document
338 0 : rv = SetAllResizersPosition();
339 0 : NS_ENSURE_SUCCESS(rv, rv);
340 :
341 : // now, let's create the resizing shadow
342 0 : mResizingShadow = CreateShadow(parentNode, aResizedElement);
343 0 : NS_ENSURE_TRUE(mResizingShadow, NS_ERROR_FAILURE);
344 : // and set its position
345 0 : rv = SetShadowPosition(mResizingShadow, mResizedObject,
346 0 : mResizedObjectX, mResizedObjectY);
347 0 : NS_ENSURE_SUCCESS(rv, rv);
348 :
349 : // and then the resizing info tooltip
350 0 : mResizingInfo = CreateResizingInfo(parentNode);
351 0 : NS_ENSURE_TRUE(mResizingInfo, NS_ERROR_FAILURE);
352 :
353 : // and listen to the "resize" event on the window first, get the
354 : // window from the document...
355 0 : nsCOMPtr<nsIDocument> doc = GetDocument();
356 0 : NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
357 :
358 0 : nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(doc->GetWindow());
359 0 : if (!target) {
360 0 : return NS_ERROR_NULL_POINTER;
361 : }
362 :
363 0 : mResizeEventListenerP = new DocumentResizeEventListener(*this);
364 0 : if (!mResizeEventListenerP) {
365 0 : return NS_ERROR_OUT_OF_MEMORY;
366 : }
367 0 : rv = target->AddEventListener(NS_LITERAL_STRING("resize"),
368 0 : mResizeEventListenerP, false);
369 : // XXX Even when it failed to add event listener, should we need to set
370 : // _moz_resizing attribute?
371 0 : aResizedElement->SetAttribute(NS_LITERAL_STRING("_moz_resizing"), NS_LITERAL_STRING("true"));
372 0 : return rv;
373 : }
374 :
375 : NS_IMETHODIMP
376 0 : HTMLEditor::HideResizers()
377 : {
378 0 : NS_ENSURE_TRUE(mResizedObject, NS_OK);
379 :
380 : // get the presshell's document observer interface.
381 0 : nsCOMPtr<nsIPresShell> ps = GetPresShell();
382 : // We allow the pres shell to be null; when it is, we presume there
383 : // are no document observers to notify, but we still want to
384 : // UnbindFromTree.
385 :
386 0 : NS_NAMED_LITERAL_STRING(mousedown, "mousedown");
387 :
388 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
389 0 : mTopLeftHandle, ps);
390 0 : mTopLeftHandle = nullptr;
391 :
392 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
393 0 : mTopHandle, ps);
394 0 : mTopHandle = nullptr;
395 :
396 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
397 0 : mTopRightHandle, ps);
398 0 : mTopRightHandle = nullptr;
399 :
400 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
401 0 : mLeftHandle, ps);
402 0 : mLeftHandle = nullptr;
403 :
404 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
405 0 : mRightHandle, ps);
406 0 : mRightHandle = nullptr;
407 :
408 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
409 0 : mBottomLeftHandle, ps);
410 0 : mBottomLeftHandle = nullptr;
411 :
412 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
413 0 : mBottomHandle, ps);
414 0 : mBottomHandle = nullptr;
415 :
416 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
417 0 : mBottomRightHandle, ps);
418 0 : mBottomRightHandle = nullptr;
419 :
420 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
421 0 : mResizingShadow, ps);
422 0 : mResizingShadow = nullptr;
423 :
424 0 : RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
425 0 : mResizingInfo, ps);
426 0 : mResizingInfo = nullptr;
427 :
428 0 : if (mActivatedHandle) {
429 0 : mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
430 0 : true);
431 0 : mActivatedHandle = nullptr;
432 : }
433 :
434 : // don't forget to remove the listeners !
435 :
436 0 : nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
437 :
438 0 : if (target && mMouseMotionListenerP) {
439 : DebugOnly<nsresult> rv =
440 0 : target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
441 0 : mMouseMotionListenerP, true);
442 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove mouse motion listener");
443 : }
444 0 : mMouseMotionListenerP = nullptr;
445 :
446 0 : nsCOMPtr<nsIDocument> doc = GetDocument();
447 0 : if (!doc) {
448 0 : return NS_ERROR_NULL_POINTER;
449 : }
450 0 : target = do_QueryInterface(doc->GetWindow());
451 0 : if (!target) {
452 0 : return NS_ERROR_NULL_POINTER;
453 : }
454 :
455 0 : if (mResizeEventListenerP) {
456 : DebugOnly<nsresult> rv =
457 0 : target->RemoveEventListener(NS_LITERAL_STRING("resize"),
458 0 : mResizeEventListenerP, false);
459 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove resize event listener");
460 : }
461 0 : mResizeEventListenerP = nullptr;
462 :
463 0 : mResizedObject->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_resizing, true);
464 0 : mResizedObject = nullptr;
465 :
466 0 : return NS_OK;
467 : }
468 :
469 : void
470 0 : HTMLEditor::HideShadowAndInfo()
471 : {
472 0 : if (mResizingShadow) {
473 0 : mResizingShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
474 0 : NS_LITERAL_STRING("hidden"), true);
475 : }
476 0 : if (mResizingInfo) {
477 0 : mResizingInfo->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
478 0 : NS_LITERAL_STRING("hidden"), true);
479 : }
480 0 : }
481 :
482 : nsresult
483 0 : HTMLEditor::StartResizing(nsIDOMElement* aHandle)
484 : {
485 0 : mIsResizing = true;
486 0 : mActivatedHandle = do_QueryInterface(aHandle);
487 0 : NS_ENSURE_STATE(mActivatedHandle || !aHandle);
488 0 : mActivatedHandle->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
489 0 : NS_LITERAL_STRING("true"), true);
490 :
491 : // do we want to preserve ratio or not?
492 0 : bool preserveRatio = HTMLEditUtils::IsImage(mResizedObject) &&
493 0 : Preferences::GetBool("editor.resizing.preserve_ratio", true);
494 :
495 : // the way we change the position/size of the shadow depends on
496 : // the handle
497 0 : nsAutoString locationStr;
498 0 : aHandle->GetAttribute(NS_LITERAL_STRING("anonlocation"), locationStr);
499 0 : if (locationStr.Equals(kTopLeft)) {
500 0 : SetResizeIncrements(1, 1, -1, -1, preserveRatio);
501 0 : } else if (locationStr.Equals(kTop)) {
502 0 : SetResizeIncrements(0, 1, 0, -1, false);
503 0 : } else if (locationStr.Equals(kTopRight)) {
504 0 : SetResizeIncrements(0, 1, 1, -1, preserveRatio);
505 0 : } else if (locationStr.Equals(kLeft)) {
506 0 : SetResizeIncrements(1, 0, -1, 0, false);
507 0 : } else if (locationStr.Equals(kRight)) {
508 0 : SetResizeIncrements(0, 0, 1, 0, false);
509 0 : } else if (locationStr.Equals(kBottomLeft)) {
510 0 : SetResizeIncrements(1, 0, -1, 1, preserveRatio);
511 0 : } else if (locationStr.Equals(kBottom)) {
512 0 : SetResizeIncrements(0, 0, 0, 1, false);
513 0 : } else if (locationStr.Equals(kBottomRight)) {
514 0 : SetResizeIncrements(0, 0, 1, 1, preserveRatio);
515 : }
516 :
517 : // make the shadow appear
518 0 : mResizingShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
519 :
520 : // position it
521 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
522 0 : mResizedObjectWidth);
523 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
524 0 : mResizedObjectHeight);
525 :
526 : // add a mouse move listener to the editor
527 0 : nsresult result = NS_OK;
528 0 : if (!mMouseMotionListenerP) {
529 0 : mMouseMotionListenerP = new ResizerMouseMotionListener(*this);
530 0 : if (!mMouseMotionListenerP) {
531 0 : return NS_ERROR_OUT_OF_MEMORY;
532 : }
533 :
534 0 : nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
535 0 : NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
536 :
537 0 : result = target->AddEventListener(NS_LITERAL_STRING("mousemove"),
538 0 : mMouseMotionListenerP, true);
539 0 : NS_ASSERTION(NS_SUCCEEDED(result),
540 : "failed to register mouse motion listener");
541 : }
542 0 : return result;
543 : }
544 :
545 : NS_IMETHODIMP
546 0 : HTMLEditor::MouseDown(int32_t aClientX,
547 : int32_t aClientY,
548 : nsIDOMElement* aTarget,
549 : nsIDOMEvent* aEvent)
550 : {
551 0 : bool anonElement = false;
552 0 : if (aTarget && NS_SUCCEEDED(aTarget->HasAttribute(NS_LITERAL_STRING("_moz_anonclass"), &anonElement)))
553 : // we caught a click on an anonymous element
554 0 : if (anonElement) {
555 0 : nsAutoString anonclass;
556 : nsresult rv =
557 0 : aTarget->GetAttribute(NS_LITERAL_STRING("_moz_anonclass"), anonclass);
558 0 : NS_ENSURE_SUCCESS(rv, rv);
559 0 : if (anonclass.EqualsLiteral("mozResizer")) {
560 : // and that element is a resizer, let's start resizing!
561 0 : aEvent->PreventDefault();
562 :
563 0 : mOriginalX = aClientX;
564 0 : mOriginalY = aClientY;
565 0 : return StartResizing(aTarget);
566 : }
567 0 : if (anonclass.EqualsLiteral("mozGrabber")) {
568 : // and that element is a grabber, let's start moving the element!
569 0 : mOriginalX = aClientX;
570 0 : mOriginalY = aClientY;
571 0 : return GrabberClicked();
572 : }
573 : }
574 0 : return NS_OK;
575 : }
576 :
577 : NS_IMETHODIMP
578 0 : HTMLEditor::MouseUp(int32_t aClientX,
579 : int32_t aClientY,
580 : nsIDOMElement* aTarget)
581 : {
582 0 : if (mIsResizing) {
583 : // we are resizing and release the mouse button, so let's
584 : // end the resizing process
585 0 : mIsResizing = false;
586 0 : HideShadowAndInfo();
587 0 : SetFinalSize(aClientX, aClientY);
588 0 : } else if (mIsMoving || mGrabberClicked) {
589 0 : if (mIsMoving) {
590 0 : mPositioningShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
591 0 : NS_LITERAL_STRING("hidden"), true);
592 0 : SetFinalPosition(aClientX, aClientY);
593 : }
594 0 : if (mGrabberClicked) {
595 0 : EndMoving();
596 : }
597 : }
598 0 : return NS_OK;
599 : }
600 :
601 : void
602 0 : HTMLEditor::SetResizeIncrements(int32_t aX,
603 : int32_t aY,
604 : int32_t aW,
605 : int32_t aH,
606 : bool aPreserveRatio)
607 : {
608 0 : mXIncrementFactor = aX;
609 0 : mYIncrementFactor = aY;
610 0 : mWidthIncrementFactor = aW;
611 0 : mHeightIncrementFactor = aH;
612 0 : mPreserveRatio = aPreserveRatio;
613 0 : }
614 :
615 : nsresult
616 0 : HTMLEditor::SetResizingInfoPosition(int32_t aX,
617 : int32_t aY,
618 : int32_t aW,
619 : int32_t aH)
620 : {
621 : // Determine the position of the resizing info box based upon the new
622 : // position and size of the element (aX, aY, aW, aH), and which
623 : // resizer is the "activated handle". For example, place the resizing
624 : // info box at the bottom-right corner of the new element, if the element
625 : // is being resized by the bottom-right resizer.
626 : int32_t infoXPosition;
627 : int32_t infoYPosition;
628 :
629 0 : if (mActivatedHandle == mTopLeftHandle ||
630 0 : mActivatedHandle == mLeftHandle ||
631 0 : mActivatedHandle == mBottomLeftHandle) {
632 0 : infoXPosition = aX;
633 0 : } else if (mActivatedHandle == mTopHandle ||
634 0 : mActivatedHandle == mBottomHandle) {
635 0 : infoXPosition = aX + (aW / 2);
636 : } else {
637 : // should only occur when mActivatedHandle is one of the 3 right-side
638 : // handles, but this is a reasonable default if it isn't any of them (?)
639 0 : infoXPosition = aX + aW;
640 : }
641 :
642 0 : if (mActivatedHandle == mTopLeftHandle ||
643 0 : mActivatedHandle == mTopHandle ||
644 0 : mActivatedHandle == mTopRightHandle) {
645 0 : infoYPosition = aY;
646 0 : } else if (mActivatedHandle == mLeftHandle ||
647 0 : mActivatedHandle == mRightHandle) {
648 0 : infoYPosition = aY + (aH / 2);
649 : } else {
650 : // should only occur when mActivatedHandle is one of the 3 bottom-side
651 : // handles, but this is a reasonable default if it isn't any of them (?)
652 0 : infoYPosition = aY + aH;
653 : }
654 :
655 : // Offset info box by 20 so it's not directly under the mouse cursor.
656 0 : const int mouseCursorOffset = 20;
657 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::left,
658 0 : infoXPosition + mouseCursorOffset);
659 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::top,
660 0 : infoYPosition + mouseCursorOffset);
661 :
662 0 : nsCOMPtr<nsIContent> textInfo = mResizingInfo->GetFirstChild();
663 0 : ErrorResult erv;
664 0 : if (textInfo) {
665 0 : mResizingInfo->RemoveChild(*textInfo, erv);
666 0 : NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
667 0 : textInfo = nullptr;
668 : }
669 :
670 0 : nsAutoString widthStr, heightStr, diffWidthStr, diffHeightStr;
671 0 : widthStr.AppendInt(aW);
672 0 : heightStr.AppendInt(aH);
673 0 : int32_t diffWidth = aW - mResizedObjectWidth;
674 0 : int32_t diffHeight = aH - mResizedObjectHeight;
675 0 : if (diffWidth > 0) {
676 0 : diffWidthStr.Assign('+');
677 : }
678 0 : if (diffHeight > 0) {
679 0 : diffHeightStr.Assign('+');
680 : }
681 0 : diffWidthStr.AppendInt(diffWidth);
682 0 : diffHeightStr.AppendInt(diffHeight);
683 :
684 0 : nsAutoString info(widthStr + NS_LITERAL_STRING(" x ") + heightStr +
685 0 : NS_LITERAL_STRING(" (") + diffWidthStr +
686 0 : NS_LITERAL_STRING(", ") + diffHeightStr +
687 0 : NS_LITERAL_STRING(")"));
688 :
689 0 : nsCOMPtr<nsIDocument> doc = GetDocument();
690 0 : textInfo = doc->CreateTextNode(info);
691 0 : if (NS_WARN_IF(!textInfo)) {
692 0 : return NS_ERROR_FAILURE;
693 : }
694 0 : mResizingInfo->AppendChild(*textInfo, erv);
695 0 : if (NS_WARN_IF(erv.Failed())) {
696 0 : return erv.StealNSResult();
697 : }
698 :
699 0 : return mResizingInfo->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
700 : }
701 :
702 : nsresult
703 0 : HTMLEditor::SetShadowPosition(Element* aShadow,
704 : Element* aOriginalObject,
705 : int32_t aOriginalObjectX,
706 : int32_t aOriginalObjectY)
707 : {
708 0 : SetAnonymousElementPosition(aOriginalObjectX, aOriginalObjectY, aShadow);
709 :
710 0 : if (HTMLEditUtils::IsImage(aOriginalObject)) {
711 0 : nsAutoString imageSource;
712 0 : aOriginalObject->GetAttr(kNameSpaceID_None, nsGkAtoms::src, imageSource);
713 0 : nsresult rv = aShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::src,
714 0 : imageSource, true);
715 0 : NS_ENSURE_SUCCESS(rv, rv);
716 : }
717 0 : return NS_OK;
718 : }
719 :
720 : int32_t
721 0 : HTMLEditor::GetNewResizingIncrement(int32_t aX,
722 : int32_t aY,
723 : int32_t aID)
724 : {
725 0 : int32_t result = 0;
726 0 : if (!mPreserveRatio) {
727 0 : switch (aID) {
728 : case kX:
729 : case kWidth:
730 0 : result = aX - mOriginalX;
731 0 : break;
732 : case kY:
733 : case kHeight:
734 0 : result = aY - mOriginalY;
735 0 : break;
736 : }
737 0 : return result;
738 : }
739 :
740 0 : int32_t xi = (aX - mOriginalX) * mWidthIncrementFactor;
741 0 : int32_t yi = (aY - mOriginalY) * mHeightIncrementFactor;
742 : float objectSizeRatio =
743 0 : ((float)mResizedObjectWidth) / ((float)mResizedObjectHeight);
744 0 : result = (xi > yi) ? xi : yi;
745 0 : switch (aID) {
746 : case kX:
747 : case kWidth:
748 0 : if (result == yi)
749 0 : result = (int32_t) (((float) result) * objectSizeRatio);
750 0 : result = (int32_t) (((float) result) * mWidthIncrementFactor);
751 0 : break;
752 : case kY:
753 : case kHeight:
754 0 : if (result == xi)
755 0 : result = (int32_t) (((float) result) / objectSizeRatio);
756 0 : result = (int32_t) (((float) result) * mHeightIncrementFactor);
757 0 : break;
758 : }
759 0 : return result;
760 : }
761 :
762 : int32_t
763 0 : HTMLEditor::GetNewResizingX(int32_t aX,
764 : int32_t aY)
765 : {
766 0 : int32_t resized = mResizedObjectX +
767 0 : GetNewResizingIncrement(aX, aY, kX) * mXIncrementFactor;
768 0 : int32_t max = mResizedObjectX + mResizedObjectWidth;
769 0 : return std::min(resized, max);
770 : }
771 :
772 : int32_t
773 0 : HTMLEditor::GetNewResizingY(int32_t aX,
774 : int32_t aY)
775 : {
776 0 : int32_t resized = mResizedObjectY +
777 0 : GetNewResizingIncrement(aX, aY, kY) * mYIncrementFactor;
778 0 : int32_t max = mResizedObjectY + mResizedObjectHeight;
779 0 : return std::min(resized, max);
780 : }
781 :
782 : int32_t
783 0 : HTMLEditor::GetNewResizingWidth(int32_t aX,
784 : int32_t aY)
785 : {
786 0 : int32_t resized = mResizedObjectWidth +
787 0 : GetNewResizingIncrement(aX, aY, kWidth) *
788 0 : mWidthIncrementFactor;
789 0 : return std::max(resized, 1);
790 : }
791 :
792 : int32_t
793 0 : HTMLEditor::GetNewResizingHeight(int32_t aX,
794 : int32_t aY)
795 : {
796 0 : int32_t resized = mResizedObjectHeight +
797 0 : GetNewResizingIncrement(aX, aY, kHeight) *
798 0 : mHeightIncrementFactor;
799 0 : return std::max(resized, 1);
800 : }
801 :
802 : NS_IMETHODIMP
803 0 : HTMLEditor::MouseMove(nsIDOMEvent* aMouseEvent)
804 : {
805 0 : nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent);
806 0 : if (NS_WARN_IF(!mouseEvent)) {
807 0 : return NS_OK;
808 : }
809 0 : return MouseMove(mouseEvent);
810 : }
811 :
812 : nsresult
813 0 : HTMLEditor::MouseMove(nsIDOMMouseEvent* aMouseEvent)
814 : {
815 0 : MOZ_ASSERT(aMouseEvent);
816 :
817 0 : NS_NAMED_LITERAL_STRING(leftStr, "left");
818 0 : NS_NAMED_LITERAL_STRING(topStr, "top");
819 :
820 0 : if (mIsResizing) {
821 : // we are resizing and the mouse pointer's position has changed
822 : // we have to resdisplay the shadow
823 : int32_t clientX, clientY;
824 0 : aMouseEvent->GetClientX(&clientX);
825 0 : aMouseEvent->GetClientY(&clientY);
826 :
827 0 : int32_t newX = GetNewResizingX(clientX, clientY);
828 0 : int32_t newY = GetNewResizingY(clientX, clientY);
829 0 : int32_t newWidth = GetNewResizingWidth(clientX, clientY);
830 0 : int32_t newHeight = GetNewResizingHeight(clientX, clientY);
831 :
832 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::left,
833 0 : newX);
834 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::top,
835 0 : newY);
836 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
837 0 : newWidth);
838 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
839 0 : newHeight);
840 :
841 0 : return SetResizingInfoPosition(newX, newY, newWidth, newHeight);
842 : }
843 :
844 0 : if (mGrabberClicked) {
845 : int32_t clientX, clientY;
846 0 : aMouseEvent->GetClientX(&clientX);
847 0 : aMouseEvent->GetClientY(&clientY);
848 :
849 : int32_t xThreshold =
850 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdX, 1);
851 : int32_t yThreshold =
852 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 1);
853 :
854 0 : if (DeprecatedAbs(clientX - mOriginalX) * 2 >= xThreshold ||
855 0 : DeprecatedAbs(clientY - mOriginalY) * 2 >= yThreshold) {
856 0 : mGrabberClicked = false;
857 0 : StartMoving(nullptr);
858 : }
859 : }
860 0 : if (mIsMoving) {
861 : int32_t clientX, clientY;
862 0 : aMouseEvent->GetClientX(&clientX);
863 0 : aMouseEvent->GetClientY(&clientY);
864 :
865 0 : int32_t newX = mPositionedObjectX + clientX - mOriginalX;
866 0 : int32_t newY = mPositionedObjectY + clientY - mOriginalY;
867 :
868 0 : SnapToGrid(newX, newY);
869 :
870 0 : mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::left,
871 0 : newX);
872 0 : mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::top,
873 0 : newY);
874 : }
875 0 : return NS_OK;
876 : }
877 :
878 : void
879 0 : HTMLEditor::SetFinalSize(int32_t aX,
880 : int32_t aY)
881 : {
882 0 : if (!mResizedObject) {
883 : // paranoia
884 0 : return;
885 : }
886 :
887 0 : if (mActivatedHandle) {
888 0 : mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated, true);
889 0 : mActivatedHandle = nullptr;
890 : }
891 :
892 : // we have now to set the new width and height of the resized object
893 : // we don't set the x and y position because we don't control that in
894 : // a normal HTML layout
895 0 : int32_t left = GetNewResizingX(aX, aY);
896 0 : int32_t top = GetNewResizingY(aX, aY);
897 0 : int32_t width = GetNewResizingWidth(aX, aY);
898 0 : int32_t height = GetNewResizingHeight(aX, aY);
899 0 : bool setWidth = !mResizedObjectIsAbsolutelyPositioned || (width != mResizedObjectWidth);
900 0 : bool setHeight = !mResizedObjectIsAbsolutelyPositioned || (height != mResizedObjectHeight);
901 :
902 : int32_t x, y;
903 0 : x = left - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderLeft+mResizedObjectMarginLeft : 0);
904 0 : y = top - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderTop+mResizedObjectMarginTop : 0);
905 :
906 : // we want one transaction only from a user's point of view
907 0 : AutoEditBatch batchIt(this);
908 :
909 0 : if (mResizedObjectIsAbsolutelyPositioned) {
910 0 : if (setHeight) {
911 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::top, y);
912 : }
913 0 : if (setWidth) {
914 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::left, x);
915 : }
916 : }
917 0 : if (IsCSSEnabled() || mResizedObjectIsAbsolutelyPositioned) {
918 0 : if (setWidth && mResizedObject->HasAttr(kNameSpaceID_None, nsGkAtoms::width)) {
919 0 : RemoveAttribute(mResizedObject, nsGkAtoms::width);
920 : }
921 :
922 0 : if (setHeight && mResizedObject->HasAttr(kNameSpaceID_None,
923 : nsGkAtoms::height)) {
924 0 : RemoveAttribute(mResizedObject, nsGkAtoms::height);
925 : }
926 :
927 0 : if (setWidth) {
928 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::width,
929 0 : width);
930 : }
931 0 : if (setHeight) {
932 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::height,
933 0 : height);
934 : }
935 : } else {
936 : // we use HTML size and remove all equivalent CSS properties
937 :
938 : // we set the CSS width and height to remove it later,
939 : // triggering an immediate reflow; otherwise, we have problems
940 : // with asynchronous reflow
941 0 : if (setWidth) {
942 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::width,
943 0 : width);
944 : }
945 0 : if (setHeight) {
946 0 : mCSSEditUtils->SetCSSPropertyPixels(*mResizedObject, *nsGkAtoms::height,
947 0 : height);
948 : }
949 0 : if (setWidth) {
950 0 : nsAutoString w;
951 0 : w.AppendInt(width);
952 0 : SetAttribute(mResizedObject, nsGkAtoms::width, w);
953 : }
954 0 : if (setHeight) {
955 0 : nsAutoString h;
956 0 : h.AppendInt(height);
957 0 : SetAttribute(mResizedObject, nsGkAtoms::height, h);
958 : }
959 :
960 0 : if (setWidth) {
961 0 : mCSSEditUtils->RemoveCSSProperty(*mResizedObject, *nsGkAtoms::width,
962 0 : EmptyString());
963 : }
964 0 : if (setHeight) {
965 0 : mCSSEditUtils->RemoveCSSProperty(*mResizedObject, *nsGkAtoms::height,
966 0 : EmptyString());
967 : }
968 : }
969 :
970 : // keep track of that size
971 0 : mResizedObjectWidth = width;
972 0 : mResizedObjectHeight = height;
973 :
974 0 : RefreshResizers();
975 : }
976 :
977 : NS_IMETHODIMP
978 0 : HTMLEditor::GetResizedObject(nsIDOMElement** aResizedObject)
979 : {
980 0 : nsCOMPtr<nsIDOMElement> ret = static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject));
981 0 : ret.forget(aResizedObject);
982 0 : return NS_OK;
983 : }
984 :
985 : NS_IMETHODIMP
986 0 : HTMLEditor::GetObjectResizingEnabled(bool* aIsObjectResizingEnabled)
987 : {
988 0 : *aIsObjectResizingEnabled = mIsObjectResizingEnabled;
989 0 : return NS_OK;
990 : }
991 :
992 : NS_IMETHODIMP
993 0 : HTMLEditor::SetObjectResizingEnabled(bool aObjectResizingEnabled)
994 : {
995 0 : mIsObjectResizingEnabled = aObjectResizingEnabled;
996 0 : return NS_OK;
997 : }
998 :
999 : } // namespace mozilla
|