1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* ***** BEGIN LICENSE BLOCK *****
3
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
5
* The contents of this file are subject to the Mozilla Public License Version
6
* 1.1 (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
8
* http://www.mozilla.org/MPL/
10
* Software distributed under the License is distributed on an "AS IS" basis,
11
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12
* for the specific language governing rights and limitations under the
15
* The Original Code is globalmenu-extension.
17
* The Initial Developer of the Original Code is
19
* Portions created by the Initial Developer are Copyright (C) 2010
20
* the Initial Developer. All Rights Reserved.
23
* Chris Coulson <chris.coulson@canonical.com>
25
* Alternatively, the contents of this file may be used under the terms of
26
* either the GNU General Public License Version 2 or later (the "GPL"), or
27
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
* in which case the provisions of the GPL or the LGPL are applicable instead
29
* of those above. If you wish to allow use of your version of this file only
30
* under the terms of either the GPL or the LGPL, and not to allow others to
31
* use your version of this file under the terms of the MPL, indicate your
32
* decision by deleting the provisions above and replace them with the notice
33
* and other provisions required by the GPL or the LGPL. If you do not delete
34
* the provisions above, a recipient may use your version of this file under
35
* the terms of any one of the MPL, the GPL or the LGPL.
37
* ***** END LICENSE BLOCK ***** */
40
#include <nsStringAPI.h>
42
#include <nsILoadGroup.h>
43
#include <nsServiceManagerUtils.h>
44
#include <imgIContainer.h>
45
#include <nsNetError.h>
46
#include <nsNetUtil.h>
47
#include <nsIImageToPixbuf.h>
48
#if MOZILLA_BRANCH_MAJOR_VERSION < 11
49
# include <nsIDOMNSElement.h>
51
#if MOZILLA_BRANCH_MAJOR_VERSION == 13
52
# include <nsIDOMNSElement.h>
54
#include <nsIDOMDOMTokenList.h>
55
#include <nsIDOMDocument.h>
56
#include <nsIDOMWindow.h>
57
#include <nsIDOMElement.h>
58
#include <nsIDOMCSSStyleDeclaration.h>
59
#include <nsIDOMCSSValue.h>
60
#include <nsIDOMCSSPrimitiveValue.h>
61
#include <nsIDOMRect.h>
62
#include <nsICaseConversion.h>
63
#include <nsUnicharUtilCIID.h>
65
#include <libdbusmenu-gtk/menuitem.h>
68
#include "uGlobalMenuObject.h"
69
#include "uGlobalMenuBar.h"
70
#include "uWidgetAtoms.h"
75
#define MAX_LABEL_NCHARS 40
77
typedef nsresult (nsIDOMRect::*GetRectSideMethod)(nsIDOMCSSPrimitiveValue**);
79
NS_IMPL_ISUPPORTS3(uGlobalMenuIconLoader, imgIDecoderObserver, imgIContainerObserver, nsIRunnable)
81
// Yes, we're abusing PRPackedBool a bit here. We initialize it to a value
82
// that is neither true or false, so that we don't need another static member
83
// to indicate the intialization status of it.
84
PRPackedBool uGlobalMenuIconLoader::sImagesInMenus = -1;
85
nsCOMPtr<imgILoader> uGlobalMenuIconLoader::sLoader = 0;
88
uGlobalMenuIconLoader::ShouldShowIcon()
90
// Ideally, we want to get the visibility of the XUL image in our menu item,
91
// but that is anonymous content which is only created when the frame is drawn
92
// (which obviously never happens here).
93
// As an alternative, we get the user setting for menus-have-icons from
94
// nsILookAndFeel. If menu icons are to be hidden, we hide everything except
95
// for menuitems with the menuitem-with-favicon class. This is basically
96
// how the visibility gets set anyway (see chrome://toolkit/content/xul.css),
97
// which should work in most cases. But, I guess a theme could override this,
98
// and then we ignore the users theme settings. Oh well......
100
if (sImagesInMenus == static_cast<PRPackedBool>(-1)) {
101
// We could get the correct GtkSettings by getting the GdkScreen that our
102
// top-level window is on. However, I don't think this matters, as
103
// nsILookAndFeel never had per-screen settings
104
GtkSettings *settings = gtk_settings_get_default();
105
gboolean menus_have_icons;
106
g_object_get(settings, "gtk-menu-images", &menus_have_icons, NULL);
108
sImagesInMenus = !!menus_have_icons;
111
if (sImagesInMenus) {
115
return mMenuItem->WithFavicon();
119
uGlobalMenuIconLoader::LoadIcon()
121
NS_DispatchToCurrentThread(this);
125
GetDOMRectSide(nsIDOMRect* aRect, GetRectSideMethod aMethod)
127
nsCOMPtr<nsIDOMCSSPrimitiveValue> dimensionValue;
128
(aRect->*aMethod)(getter_AddRefs(dimensionValue));
132
PRUint16 primitiveType;
133
nsresult rv = dimensionValue->GetPrimitiveType(&primitiveType);
134
if (NS_FAILED(rv) || primitiveType != nsIDOMCSSPrimitiveValue::CSS_PX)
138
rv = dimensionValue->GetFloatValue(nsIDOMCSSPrimitiveValue::CSS_PX,
143
return NSToIntRound(dimension);
147
uGlobalMenuIconLoader::Run()
149
TRACE_WITH_MENUOBJECT(mMenuItem);
150
// Some of this is borrowed from widget/src/cocoa/nsMenuItemIconX.mm
152
mIconRequest->Cancel(NS_BINDING_ABORTED);
153
mIconRequest = nsnull;
157
// Our menu item got destroyed already
161
mMenuItem->GetContent(getter_AddRefs(mContent));
163
nsIDocument *doc = mContent->GetCurrentDoc();
165
// We might have been removed from the menu, in which case we will
166
// no longer be in a document
170
if (!ShouldShowIcon()) {
175
mIconLoaded = PR_FALSE;
177
nsAutoString uriString;
178
PRBool hasImage = mContent->GetAttr(kNameSpaceID_None, uWidgetAtoms::image,
182
nsCOMPtr<nsIDOMRect> domRect;
185
DEBUG_WITH_MENUOBJECT(mMenuItem, "Menuitem does not have an image");
186
nsCOMPtr<nsIDOMCSSStyleDeclaration> cssStyleDecl;
187
nsCOMPtr<nsIDOMWindow> domWin;
188
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
190
domDoc->GetDefaultView(getter_AddRefs(domWin));
192
nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(mContent);
194
domWin->GetComputedStyle(domElement, EmptyString(),
195
getter_AddRefs(cssStyleDecl));
201
return NS_ERROR_FAILURE;
204
nsCOMPtr<nsIDOMCSSValue> cssValue;
205
nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue;
206
PRUint16 primitiveType;
207
cssStyleDecl->GetPropertyCSSValue(NS_LITERAL_STRING("list-style-image"),
208
getter_AddRefs(cssValue));
210
primitiveValue = do_QueryInterface(cssValue);
211
if (primitiveValue) {
212
primitiveValue->GetPrimitiveType(&primitiveType);
213
if (primitiveType == nsIDOMCSSPrimitiveValue::CSS_URI) {
214
rv = primitiveValue->GetStringValue(uriString);
215
if (NS_SUCCEEDED(rv)) {
219
NS_WARNING("list-style-image has wrong primitive type");
228
cssStyleDecl->GetPropertyCSSValue(NS_LITERAL_STRING("-moz-image-region"),
229
getter_AddRefs(cssValue));
231
primitiveValue = do_QueryInterface(cssValue);
232
if (primitiveValue) {
233
primitiveValue->GetPrimitiveType(&primitiveType);
234
if (primitiveType == nsIDOMCSSPrimitiveValue::CSS_RECT) {
235
primitiveValue->GetRectValue(getter_AddRefs(domRect));
241
DEBUG_WITH_MENUOBJECT(mMenuItem, "Icon URI: %s", DEBUG_CSTR_FROM_UTF16(uriString));
242
nsCOMPtr<nsIURI> uri;
243
rv = NS_NewURI(getter_AddRefs(uri), uriString);
245
NS_WARNING("Failed to create new URI");
251
sLoader = do_GetService("@mozilla.org/image/loader;1");
254
NS_ASSERTION(sLoader, "No icon loader");
259
nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
261
#if MOZILLA_BRANCH_MAJOR_VERSION >= 8
262
rv = sLoader->LoadImage(uri, nsnull, nsnull, nsnull, loadGroup, this,
264
rv = sLoader->LoadImage(uri, nsnull, nsnull, loadGroup, this,
266
nsnull, nsIRequest::LOAD_NORMAL, nsnull,
267
nsnull, nsnull, getter_AddRefs(mIconRequest));
268
NS_ENSURE_SUCCESS(rv, rv);
270
mIconRequest->RequestDecode();
272
mImageRect.SetEmpty();
275
PRInt32 bottom = GetDOMRectSide(domRect, &nsIDOMRect::GetBottom);
276
PRInt32 right = GetDOMRectSide(domRect, &nsIDOMRect::GetRight);
277
PRInt32 top = GetDOMRectSide(domRect, &nsIDOMRect::GetTop);
278
PRInt32 left = GetDOMRectSide(domRect, &nsIDOMRect::GetLeft);
280
if (top < 0 || left < 0 || bottom <= top || right <= left) {
281
return NS_ERROR_FAILURE;
284
mImageRect.SetRect(left, top, right - left, bottom - top);
291
uGlobalMenuIconLoader::ClearIcon()
293
dbusmenu_menuitem_property_remove(mMenuItem->GetDbusMenuItem(),
294
DBUSMENU_MENUITEM_PROP_ICON_DATA);
298
uGlobalMenuIconLoader::OnStartRequest(imgIRequest *aRequest)
300
return NS_ERROR_NOT_IMPLEMENTED;
304
uGlobalMenuIconLoader::OnStartDecode(imgIRequest *aRequest)
306
return NS_ERROR_NOT_IMPLEMENTED;
310
uGlobalMenuIconLoader::OnStartContainer(imgIRequest *aRequest, imgIContainer *aContainer)
312
TRACE_WITH_MENUOBJECT(mMenuItem);
313
return NS_ERROR_NOT_IMPLEMENTED;
317
uGlobalMenuIconLoader::OnStartFrame(imgIRequest *aRequest, PRUint32 aFrame)
319
return NS_ERROR_NOT_IMPLEMENTED;
323
uGlobalMenuIconLoader::OnDataAvailable(imgIRequest *aRequest,
324
MOZ_API_BOOL aCurrentFrame,
325
const nsIntRect *aRect)
327
return NS_ERROR_NOT_IMPLEMENTED;
331
uGlobalMenuIconLoader::OnStopFrame(imgIRequest *aRequest, PRUint32 aFrame)
333
TRACE_WITH_MENUOBJECT(mMenuItem);
334
if (aRequest != mIconRequest) {
335
return NS_ERROR_FAILURE;
339
DEBUG_WITH_MENUOBJECT(mMenuItem, "Icon is already loaded");
343
mIconLoaded = PR_TRUE;
345
nsCOMPtr<imgIContainer> img;
346
aRequest->GetImage(getter_AddRefs(img));
348
NS_WARNING("Failed to get image");
349
return NS_ERROR_FAILURE;
354
img->GetWidth(&origWidth);
355
img->GetHeight(&origHeight);
357
PRBool needsClip = PR_FALSE;
359
if (!mImageRect.IsEmpty()) {
360
if (mImageRect.XMost() > origWidth || mImageRect.YMost() > origHeight) {
361
NS_WARNING("-moz-image-region is larger than image");
362
return NS_ERROR_FAILURE;
365
if (!(mImageRect.x == 0 && mImageRect.y == 0 &&
366
mImageRect.width == origWidth && mImageRect.height == origHeight)) {
371
if ((!needsClip && (origWidth > 100 || origHeight > 100)) ||
372
(needsClip && (mImageRect.width > 100 || mImageRect.height > 100))) {
373
/* The icon data needs to go across DBus. Make sure the icon
374
* data isn't too large, else our connection gets terminated and
375
* GDbus helpfully aborts the application. Thank you :)
377
NS_WARNING("Icon data too large");
382
nsCOMPtr<imgIContainer> clippedImg;
384
nsresult rv = img->ExtractFrame(0, mImageRect, 0,
385
getter_AddRefs(clippedImg));
387
NS_WARNING("Failed to clip icon");
388
return NS_ERROR_FAILURE;
392
nsCOMPtr<nsIImageToPixbuf> converter =
393
do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
394
NS_ASSERTION(converter, "No image converter");
396
return NS_ERROR_FAILURE;
400
converter->ConvertImageToPixbuf(clippedImg ? clippedImg : img);
402
dbusmenu_menuitem_property_set_image(mMenuItem->GetDbusMenuItem(),
403
DBUSMENU_MENUITEM_PROP_ICON_DATA,
405
g_object_unref(pixbuf);
414
uGlobalMenuIconLoader::OnStopContainer(imgIRequest *aRequest,
415
imgIContainer *aContainer)
417
return NS_ERROR_NOT_IMPLEMENTED;
421
uGlobalMenuIconLoader::OnStopDecode(imgIRequest *aRequest, nsresult status,
422
const PRUnichar *statusArg)
424
TRACE_WITH_MENUOBJECT(mMenuItem);
425
return NS_ERROR_NOT_IMPLEMENTED;
429
uGlobalMenuIconLoader::OnStopRequest(imgIRequest *aRequest, MOZ_API_BOOL aIsLastPart)
431
TRACE_WITH_MENUOBJECT(mMenuItem);
434
mIconRequest->Cancel(NS_BINDING_ABORTED);
435
mIconRequest = nsnull;
442
uGlobalMenuIconLoader::OnDiscard(imgIRequest *aRequest)
444
return NS_ERROR_NOT_IMPLEMENTED;
448
#if MOZILLA_BRANCH_MAJOR_VERSION >= 12
449
uGlobalMenuIconLoader::FrameChanged(imgIRequest *aRequest,
450
imgIContainer *aContainer,
452
uGlobalMenuIconLoader::FrameChanged(imgIContainer *aContainer,
454
const nsIntRect *aDirtyRect)
456
return NS_ERROR_NOT_IMPLEMENTED;
459
#if MOZILLA_BRANCH_MAJOR_VERSION >= 11
461
uGlobalMenuIconLoader::OnImageIsAnimated(imgIRequest* aRequest)
468
uGlobalMenuIconLoader::Destroy()
470
TRACE_WITH_MENUOBJECT(mMenuItem);
472
mIconRequest->Cancel(NS_BINDING_ABORTED);
473
mIconRequest = nsnull;
480
uGlobalMenuObject::GetContent(nsIContent **_retval)
486
NS_IF_ADDREF(*_retval);
490
uGlobalMenuObject::SyncLabelFromContent(nsIContent *aContent)
492
TRACE_WITH_THIS_MENUOBJECT();
493
// Gecko stores the label and access key in separate attributes
494
// so we need to convert label="Foo"/accesskey="F" in to
495
// label="_Foo" for dbusmenu
498
if (aContent && aContent->GetAttr(kNameSpaceID_None,
499
uWidgetAtoms::label, label)) {
500
UGM_BLOCK_EVENTS_FOR_CURRENT_SCOPE();
501
DEBUG_WITH_CONTENT(aContent, "Content has label \"%s\"", DEBUG_CSTR_FROM_UTF16(label));
502
mContent->SetAttr(kNameSpaceID_None, uWidgetAtoms::label, label, MOZ_API_TRUE);
504
mContent->GetAttr(kNameSpaceID_None, uWidgetAtoms::label, label);
507
nsAutoString accesskey;
508
mContent->GetAttr(kNameSpaceID_None, uWidgetAtoms::accesskey, accesskey);
510
const PRUnichar *tmp = accesskey.BeginReading();
513
// XXX: I think we need to link against libxul.so to get ToLowerCase
514
// and ToUpperCase from nsUnicharUtils.h
515
nsCOMPtr<nsICaseConversion> converter =
516
do_GetService(NS_UNICHARUTIL_CONTRACTID);
518
converter->ToUpper(*tmp, &keyUpper);
519
converter->ToLower(*tmp, &keyLower);
522
keyUpper = toupper(char(*tmp));
523
keyLower = tolower(char(*tmp));
525
NS_WARNING("accesskey matching is case-sensitive when it shouldn't be");
531
PRUnichar *cur = label.BeginWriting();
532
PRUnichar *end = label.EndWriting();
533
int length = label.Length();
535
PRBool foundAccessKey = PR_FALSE;
538
if (*cur != PRUnichar('_')) {
539
if ((*cur != keyLower && *cur != keyUpper) || foundAccessKey) {
544
foundAccessKey = PR_TRUE;
548
label.SetLength(length);
549
int newLength = label.Length();
550
if (length != newLength)
553
cur = label.BeginWriting() + pos;
554
end = label.EndWriting();
555
memmove(cur + 1, cur, (length - 1 - pos) * sizeof(PRUnichar));
557
*cur = PRUnichar('_'); // Yeah!
564
// Ellipsize long labels. I've picked an arbitrary length here
565
if (length > MAX_LABEL_NCHARS) {
566
cur = label.BeginWriting();
567
for (PRUint32 i = 1; i < 4; i++) {
568
*(cur + (MAX_LABEL_NCHARS - i)) = PRUnichar('.');
570
*(cur + MAX_LABEL_NCHARS) = nsnull;
571
label.SetLength(MAX_LABEL_NCHARS);
574
nsCAutoString clabel;
575
CopyUTF16toUTF8(label, clabel);
576
DEBUG_WITH_THIS_MENUOBJECT("Setting label to \"%s\"", clabel.get());
577
dbusmenu_menuitem_property_set(mDbusMenuItem,
578
DBUSMENU_MENUITEM_PROP_LABEL,
583
uGlobalMenuObject::SyncLabelFromContent()
585
SyncLabelFromContent(nsnull);
588
// Synchronize the 'hidden' attribute on the DOM node with the
589
// 'visible' property on the dbusmenu node
591
uGlobalMenuObject::SyncVisibilityFromContent()
593
TRACE_WITH_THIS_MENUOBJECT();
594
mContentVisible = !IsHidden();
595
PRBool realVis = (!mMenuBar || !ShouldShowOnlyForKb() ||
596
mMenuBar->OpenedByKeyboard()) ?
597
mContentVisible : PR_FALSE;
598
DEBUG_WITH_THIS_MENUOBJECT("Setting %s", realVis ? "visible" : "hidden");
600
dbusmenu_menuitem_property_set_bool(mDbusMenuItem,
601
DBUSMENU_MENUITEM_PROP_VISIBLE,
606
uGlobalMenuObject::SyncSensitivityFromContent(nsIContent *aContent)
608
TRACE_WITH_THIS_MENUOBJECT();
616
PRBool disabled = content->AttrValueIs(kNameSpaceID_None,
617
uWidgetAtoms::disabled,
620
DEBUG_WITH_THIS_MENUOBJECT("Setting %s", disabled ? "disabled" : "enabled");
623
UGM_BLOCK_EVENTS_FOR_CURRENT_SCOPE();
625
mContent->SetAttr(kNameSpaceID_None, uWidgetAtoms::disabled,
626
NS_LITERAL_STRING("true"), MOZ_API_TRUE);
628
mContent->UnsetAttr(kNameSpaceID_None, uWidgetAtoms::disabled, MOZ_API_TRUE);
632
dbusmenu_menuitem_property_set_bool(mDbusMenuItem,
633
DBUSMENU_MENUITEM_PROP_ENABLED,
638
uGlobalMenuObject::SyncSensitivityFromContent()
640
SyncSensitivityFromContent(nsnull);
644
uGlobalMenuObject::UpdateInfoFromContentClass()
646
TRACE_WITH_THIS_MENUOBJECT();
647
nsCOMPtr<nsIDOMNSElement> element(do_QueryInterface(mContent));
652
nsCOMPtr<nsIDOMDOMTokenList> classes;
653
element->GetClassList(getter_AddRefs(classes));
659
classes->Contains(NS_LITERAL_STRING("show-only-for-keyboard"),
661
mShowOnlyForKb = !!tmp;
663
classes->Contains(NS_LITERAL_STRING("menuitem-with-favicon"),
665
mWithFavicon = !!tmp;
667
DEBUG_WITH_THIS_MENUOBJECT("show-only-for-keyboard class? %s", mShowOnlyForKb ? "Yes" : "No");
668
DEBUG_WITH_THIS_MENUOBJECT("menuitem-with-favicon class? %s", mWithFavicon ? "Yes" : "No");
672
uGlobalMenuObject::IsHidden()
674
return mContent->AttrValueIs(kNameSpaceID_None, uWidgetAtoms::hidden,
675
uWidgetAtoms::_true, eCaseMatters) ||
676
mContent->AttrValueIs(kNameSpaceID_None, uWidgetAtoms::collapsed,
677
uWidgetAtoms::_true, eCaseMatters);
681
uGlobalMenuObject::UpdateVisibility()
683
TRACE_WITH_THIS_MENUOBJECT();
688
PRBool newVis = (!ShouldShowOnlyForKb() || mMenuBar->OpenedByKeyboard()) ?
689
mContentVisible : PR_FALSE;
691
DEBUG_WITH_THIS_MENUOBJECT("Setting %s", newVis ? "visible" : "hidden");
692
dbusmenu_menuitem_property_set_bool(mDbusMenuItem,
693
DBUSMENU_MENUITEM_PROP_VISIBLE,
698
uGlobalMenuObject::SyncIconFromContent()
700
TRACE_WITH_THIS_MENUOBJECT();
702
mIconLoader = new uGlobalMenuIconLoader(this);
705
mIconLoader->LoadIcon();
709
uGlobalMenuObject::DestroyIconLoader()
712
mIconLoader->Destroy();