From 7e1063f2b6bd8940c379a3bc549831a3bc8f319f Mon Sep 17 00:00:00 2001 From: "cbarrett@mozilla.com" Date: Mon, 29 Oct 2007 21:03:42 -0700 Subject: [PATCH] Bug 303110 - Add Unified Toolbar machinery to Cocoa widgets. r=joshmoz, r=bz, sr=roc. --- content/base/src/nsGkAtomList.h | 1 + content/xul/content/src/nsXULElement.cpp | 43 +++- content/xul/content/src/nsXULElement.h | 2 + widget/public/nsIWidget.h | 22 +- widget/src/cocoa/nsCocoaWindow.h | 41 ++- widget/src/cocoa/nsCocoaWindow.mm | 311 ++++++++++++++++++++++- widget/src/xpwidgets/nsBaseWidget.cpp | 7 + widget/src/xpwidgets/nsBaseWidget.h | 1 + 8 files changed, 414 insertions(+), 14 deletions(-) diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index 447167643d65..ea6fc17e72e2 100755 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -838,6 +838,7 @@ GK_ATOM(thead, "thead") GK_ATOM(thumb, "thumb") GK_ATOM(title, "title") GK_ATOM(titlebar, "titlebar") +GK_ATOM(titlebarcolor, "titlebarcolor") GK_ATOM(titletip, "titletip") GK_ATOM(toggled, "toggled") GK_ATOM(token, "token") diff --git a/content/xul/content/src/nsXULElement.cpp b/content/xul/content/src/nsXULElement.cpp index 2cc482e868cf..93b935a89e43 100644 --- a/content/xul/content/src/nsXULElement.cpp +++ b/content/xul/content/src/nsXULElement.cpp @@ -63,6 +63,7 @@ #include "nsIPrivateDOMEvent.h" #include "nsHashtable.h" #include "nsIAtom.h" +#include "nsIBaseWindow.h" #include "nsIDOMAttr.h" #include "nsIDOMDocument.h" #include "nsIDOMElement.h" @@ -1055,8 +1056,20 @@ nsXULElement::AfterSetAttr(PRInt32 aNamespaceID, nsIAtom* aName, HideWindowChrome(aValue && NS_LITERAL_STRING("true").Equals(*aValue)); } - // handle :read-only/:read-write + // titlebarcolor is settable on any root node (windows, dialogs, etc) nsIDocument *document = GetCurrentDoc(); + if (aName == nsGkAtoms::titlebarcolor && + document && document->GetRootContent() == this) { + + nscolor color = NS_RGBA(0, 0, 0, 0); + nsAttrValue attrValue; + attrValue.ParseColor(*aValue, document); + attrValue.GetColorValue(color); + + SetTitlebarColor(color); + } + + // handle :read-only/:read-write if (aName == nsGkAtoms::readonly && document) { mozAutoDocUpdate upd(document, UPDATE_CONTENT_STATE, PR_TRUE); document->ContentStatesChanged(this, nsnull, @@ -1298,6 +1311,12 @@ nsXULElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, PRBool aNotify) HideWindowChrome(PR_FALSE); } + if (aName == nsGkAtoms::titlebarcolor && + doc && doc->GetRootContent() == this) { + // Use 0, 0, 0, 0 as the "none" color. + SetTitlebarColor(NS_RGBA(0, 0, 0, 0)); + } + // If the accesskey attribute is removed, unregister it here // Also see nsAreaFrame, nsBoxFrame and nsTextBoxFrame's AttributeChanged if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control) { @@ -2291,6 +2310,28 @@ nsXULElement::HideWindowChrome(PRBool aShouldHide) return NS_OK; } +void +nsXULElement::SetTitlebarColor(nscolor aColor) +{ + nsIDocument* doc = GetCurrentDoc(); + if (!doc || doc->GetRootContent() != this) { + return; + } + + // only top level chrome documents can set the titlebar color + if (!doc->GetParentDocument()) { + nsCOMPtr container = doc->GetContainer(); + nsCOMPtr baseWindow = do_QueryInterface(container); + if (baseWindow) { + nsCOMPtr mainWidget; + baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetWindowTitlebarColor(aColor); + } + } + } +} + PRBool nsXULElement::BoolAttrIsTrue(nsIAtom* aName) { diff --git a/content/xul/content/src/nsXULElement.h b/content/xul/content/src/nsXULElement.h index aa3af24903ab..cbf3f8d07610 100644 --- a/content/xul/content/src/nsXULElement.h +++ b/content/xul/content/src/nsXULElement.h @@ -713,6 +713,8 @@ protected: nsresult HideWindowChrome(PRBool aShouldHide); + void SetTitlebarColor(nscolor aColor); + const nsAttrName* InternalGetExistingAttrNameFromQName(const nsAString& aStr) const; protected: diff --git a/widget/public/nsIWidget.h b/widget/public/nsIWidget.h index b300dcb120d1..5a32ebd12680 100644 --- a/widget/public/nsIWidget.h +++ b/widget/public/nsIWidget.h @@ -95,11 +95,11 @@ typedef nsEventStatus (*PR_CALLBACK EVENT_CALLBACK)(nsGUIEvent *event); #define NS_NATIVE_PLUGIN_PORT_CG 101 #endif -// 15800FBD-650A-4F67-81FB-186E73F45BE1 +// d9d02313-6a10-4b6d-9f15-18177e94047a #define NS_IWIDGET_IID \ -{ 0x15800FBD, 0x650A, 0x4F67, \ - { 0x81, 0xFB, 0x18, 0x6E, 0x73, 0xF4, 0x5B, 0xE1 } } +{ 0xd9d02313, 0x6a10, 0x4b6d, \ + { 0x9f, 0x15, 0x18, 0x17, 0x7e, 0x94, 0x04, 0x7a } } // Hide the native window systems real window type so as to avoid // including native window system types and api's. This is necessary @@ -1030,11 +1030,27 @@ class nsIWidget : public nsISupports { */ NS_IMETHOD EndSecureKeyboardInput() = 0; + /** + * Set the background color of the window titlebar for this widget. On Mac, + * for example, this will remove the grey gradient and bottom border and + * instead show a single, solid color. + * + * Ignored on any platform that does not support it. Ignored by widgets that + * do not represent windows. + * + * @param aColor The color to set the title bar background to. Alpha values + * other than fully transparent (0) are respected if possible + * on the platform. An alpha of 0 will cause the window to + * draw with the default style for the platform. + */ + NS_IMETHOD SetWindowTitlebarColor(nscolor aColor) = 0; + /** * Get the Thebes surface associated with this widget. */ virtual gfxASurface *GetThebesSurface() = 0; + protected: // keep the list of children. We also keep track of our siblings. // The ownership model is as follows: parent holds a strong ref to diff --git a/widget/src/cocoa/nsCocoaWindow.h b/widget/src/cocoa/nsCocoaWindow.h index df45fd0f264c..a5af426e6f8c 100644 --- a/widget/src/cocoa/nsCocoaWindow.h +++ b/widget/src/cocoa/nsCocoaWindow.h @@ -21,6 +21,7 @@ * * Contributor(s): * Josh Aas + * Colin Barrett * * 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 @@ -70,6 +71,11 @@ class nsChildView; // original value. - (void)_setWindowNumber:(int)aNumber; +// If we set the window's stylemask to be textured, the corners on the bottom of +// the window are rounded by default. We use this private method to make +// the corners square again, a la Safari. +- (void)setBottomCornerRounded:(BOOL)rounded; + @end @@ -107,10 +113,42 @@ class nsChildView; - (nsCocoaWindow*)geckoWidget; @end -// Class that allows us to show the toolbar pill button + +// NSColor subclass that allows us to draw separate colors both in the titlebar +// and for background of the window. +@interface TitlebarAndBackgroundColor : NSColor +{ + NSColor *mTitlebarColor; + NSColor *mBackgroundColor; + NSWindow *mWindow; // [WEAK] (we are owned by the window) + float mTitlebarHeight; +} + +- (id)initWithTitlebarColor:(NSColor*)aTitlebarColor + andBackgroundColor:(NSColor*)aBackgroundColor + forWindow:(NSWindow*)aWindow; + +// Pass nil here to get the default appearance. +- (void)setTitlebarColor:(NSColor*)aColor; +- (NSColor*)titlebarColor; + +- (void)setBackgroundColor:(NSColor*)aColor; +- (NSColor*)backgroundColor; + +- (NSWindow*)window; +- (float)titlebarHeight; +@end + +// NSWindow subclass for handling windows with toolbars. @interface ToolbarWindow : NSWindow { + TitlebarAndBackgroundColor *mColor; } +- (void)setTitlebarColor:(NSColor*)aColor; +- (NSColor*)titlebarColor; +// This method is also available on NSWindows (via a category), and is the +// preferred way to check the background color of a window. +- (NSColor*)windowBackgroundColor; @end class nsCocoaWindow : public nsBaseWidget, public nsPIWidgetCocoa @@ -199,6 +237,7 @@ public: NS_IMETHOD DispatchEvent(nsGUIEvent* event, nsEventStatus & aStatus) ; NS_IMETHOD CaptureRollupEvents(nsIRollupListener * aListener, PRBool aDoCapture, PRBool aConsumeRollupEvent); NS_IMETHOD GetAttention(PRInt32 aCycleCount); + NS_IMETHOD SetWindowTitlebarColor(nscolor aColor); virtual gfxASurface* GetThebesSurface(); diff --git a/widget/src/cocoa/nsCocoaWindow.mm b/widget/src/cocoa/nsCocoaWindow.mm index bdb7ff2fdf5c..a83a4ac89b39 100644 --- a/widget/src/cocoa/nsCocoaWindow.mm +++ b/widget/src/cocoa/nsCocoaWindow.mm @@ -21,6 +21,7 @@ * * Contributor(s): * Josh Aas + * Colin Barrett * * 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 @@ -317,11 +318,12 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent, // NSLog(@"Top-level window being created at Cocoa rect: %f, %f, %f, %f\n", // rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); - // Create the window Class windowClass = [NSWindow class]; - // If we're a top level window, we want to be able to have the toolbar - // pill button, so use the special ToolbarWindow class. - if (mWindowType == eWindowType_toplevel) + // If we have a titlebar, we want to be able to control the titlebar color + // (for unified), so use the special ToolbarWindow class. Note that we need + // to check the window type because we mark sheets sheets as having titlebars. + if ((mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog) && + (features & NSTitledWindowMask)) windowClass = [ToolbarWindow class]; // If we're a popup window we need to use the PopupWindow class. else if (mWindowType == eWindowType_popup) @@ -330,6 +332,8 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent, // BorderlessWindow class. else if (features == NSBorderlessWindowMask) windowClass = [BorderlessWindow class]; + + // Create the window mWindow = [[windowClass alloc] initWithContentRect:rect styleMask:features backing:NSBackingStoreBuffered defer:NO]; @@ -358,7 +362,7 @@ nsresult nsCocoaWindow::StandardCreate(nsIWidget *aParent, [mWindow setBackgroundColor:[NSColor whiteColor]]; [mWindow setContentMinSize:NSMakeSize(60, 60)]; [mWindow setReleasedWhenClosed:NO]; - + // setup our notification delegate. Note that setDelegate: does NOT retain. mDelegate = [[WindowDelegate alloc] initWithGeckoWindow:this]; [mWindow setDelegate:mDelegate]; @@ -1079,6 +1083,27 @@ NS_IMETHODIMP nsCocoaWindow::GetAttention(PRInt32 aCycleCount) } +NS_IMETHODIMP nsCocoaWindow::SetWindowTitlebarColor(nscolor aColor) +{ + if (![mWindow isKindOfClass:[ToolbarWindow class]]) { + NS_WARNING("Calling SetWindowTitlebarColor on window that isn't of the ToolbarWindow class."); + return NS_ERROR_FAILURE; + } + + // If they pass a color with a complete transparent alpha component, use the + // native titlebar appearance. + if (NS_GET_A(aColor) == 0) { + [(ToolbarWindow*)mWindow setTitlebarColor:nil]; + } else { + [(ToolbarWindow*)mWindow setTitlebarColor:[NSColor colorWithDeviceRed:NS_GET_R(aColor)/255.0 + green:NS_GET_G(aColor)/255.0 + blue:NS_GET_B(aColor)/255.0 + alpha:NS_GET_A(aColor)/255.0]]; + } + return NS_OK; +} + + gfxASurface* nsCocoaWindow::GetThebesSurface() { if (mPopupContentView) @@ -1294,18 +1319,96 @@ NS_IMETHODIMP nsCocoaWindow::EndSecureKeyboardInput() @end + +// Category on NSWindow so callers can use the same method on both ToolbarWindows +// and NSWindows for accessing the background color. +@implementation NSWindow(ToolbarWindowCompat) + +- (NSColor*)windowBackgroundColor +{ + return [self backgroundColor]; +} + +@end + + +// This class allows us to have a "unified toolbar" style window. It works like this: +// 1) We set the window's style to textured. +// 2) Because of this, the background color applies to the entire window, including +// the titlebar area. For normal textured windows, the default pattern is a +// "brushed metal" image. +// 3) We set the background color to a custom NSColor subclass that knows how tall the window is. +// When -set is called on it, it sets a pattern (with a draw callback) as the fill. In that callback, +// it paints the the titlebar and background colrs in the correct areas of the context its given, +// which will fill the entire window (CG will tile it horizontally for us). +// +// This class also provides us with a pill button to show/hide the toolbar. @implementation ToolbarWindow -// The carbon widget code was saying we want a toolbar for all top level -// windows, and since we're only using this class for top level windows, we -// always want to return yes from here. +- (id)initWithContentRect:(NSRect)aContentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)aFlag +{ + aStyle = aStyle | NSTexturedBackgroundWindowMask; + if ((self = [super initWithContentRect:aContentRect styleMask:aStyle backing:aBufferingType defer:aFlag])) { + mColor = [[TitlebarAndBackgroundColor alloc] initWithTitlebarColor:nil + andBackgroundColor:[NSColor whiteColor] + forWindow:self]; + // Call the superclass's implementation, to avoid our guard method below. + [super setBackgroundColor:mColor]; + + // setBottomCornerRounded: is a private API call, so we check to make sure + // we respond to it just in case. + if ([self respondsToSelector:@selector(setBottomCornerRounded:)]) + [self setBottomCornerRounded:NO]; + } + return self; +} + + +- (void)dealloc +{ + [mColor release]; + [super dealloc]; +} + + +// We don't provide our own implementation of -backgroundColor because NSWindow +// looks at it, apparently. This is here to keep someone from messing with our +// custom NSColor subclass. +- (void)setBackgroundColor:(NSColor*)aColor +{ + [mColor setBackgroundColor:aColor]; +} + + +// If you need to get at the background color of the window (in the traditional +// sense) use this method instead. +- (NSColor*)windowBackgroundColor +{ + return [mColor backgroundColor]; +} + + +// Pass nil here to get the default appearance. +- (void)setTitlebarColor:(NSColor*)aColor +{ + [mColor setTitlebarColor:aColor]; +} + + +- (NSColor*)titlebarColor +{ + return [mColor titlebarColor]; +} + + +// Always show the toolbar pill button. - (BOOL)_hasToolbar { return YES; } -// Dispatch a toolbar pill button clicked message to Gecko +// Dispatch a toolbar pill button clicked message to Gecko. - (void)_toolbarPillButtonClicked:(id)sender { nsCocoaWindow *geckoWindow = [[self delegate] geckoWidget]; @@ -1329,6 +1432,196 @@ NS_IMETHODIMP nsCocoaWindow::EndSecureKeyboardInput() @end + +// Custom NSColor subclass where most of the work takes place for drawing in +// the titlebar area. +@implementation TitlebarAndBackgroundColor + +- (id)initWithTitlebarColor:(NSColor*)aTitlebarColor + andBackgroundColor:(NSColor*)aBackgroundColor + forWindow:(NSWindow*)aWindow +{ + if ((self = [super init])) { + mTitlebarColor = [aTitlebarColor retain]; + mBackgroundColor = [aBackgroundColor retain]; + mWindow = aWindow; // weak ref + NSRect frameRect = [aWindow frame]; + mTitlebarHeight = frameRect.size.height - [aWindow contentRectForFrameRect:frameRect].size.height; + } + return self; +} + + +- (void)dealloc +{ + [mTitlebarColor release]; + [mBackgroundColor release]; + [super dealloc]; +} + +// Our pattern width is 1 pixel. CoreGraphics can cache and tile for us. +static const float sPatternWidth = 1.0f; + +// These are the start and end greys for the default titlebar gradient. +static const float sHeaderStartGrey = 196/255.0f; +static const float sHeaderEndGrey = 149/255.0f; + +// This is the grey for the border at the bottom of the titlebar. +static const float sTitlebarBorderGrey = 64/255.0f; + +// Callback used by the default titlebar shading. +static void headerShading(void* aInfo, const float* aIn, float* aOut) +{ + float result = (*aIn) * sHeaderStartGrey + (1.0f - *aIn) * sHeaderEndGrey; + aOut[0] = result; + aOut[1] = result; + aOut[2] = result; + aOut[3] = 1.0f; +} + + +// Callback where all of the drawing for this color takes place. +void patternDraw(void* aInfo, CGContextRef aContext) +{ + TitlebarAndBackgroundColor *color = (TitlebarAndBackgroundColor*)aInfo; + NSColor *titlebarColor = [color titlebarColor]; + NSColor *backgroundColor = [color backgroundColor]; + NSWindow *window = [color window]; + + // Remember: this context is NOT flipped, so the origin is in the bottom left. + float titlebarHeight = [color titlebarHeight]; + float titlebarOrigin = [window frame].size.height - titlebarHeight; + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:aContext flipped:NO]]; + + // If the titlebar color is nil, draw the default titlebar shading. + if (!titlebarColor) { + //Create and draw a CGShading that uses headerShading() as its callback. + CGFunctionCallbacks callbacks = {0, headerShading, NULL}; + CGFunctionRef function = CGFunctionCreate(NULL, 1, NULL, 4, NULL, &callbacks); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGShadingRef shading = CGShadingCreateAxial(colorSpace, CGPointMake(0.0f, titlebarOrigin), + CGPointMake(0.0f, titlebarOrigin + titlebarHeight), + function, NO, NO); + CGColorSpaceRelease(colorSpace); + CGFunctionRelease(function); + CGContextDrawShading(aContext, shading); + + // Draw the one pixel border at the bottom of the titlebar. + [[NSColor colorWithDeviceWhite:sTitlebarBorderGrey alpha:1.0f] set]; + NSRectFill(NSMakeRect(0.0f, titlebarOrigin, sPatternWidth, 1.0f)); + } else { + // if the titlebar color is not nil, just set and draw it normally. + [titlebarColor set]; + NSRectFill(NSMakeRect(0.0f, titlebarOrigin, sPatternWidth, titlebarHeight)); + } + + // Draw the background color of the window everywhere but where the titlebar is. + [backgroundColor set]; + NSRectFill(NSMakeRect(0.0f, 0.0f, 1.0f, titlebarOrigin)); + + [NSGraphicsContext restoreGraphicsState]; +} + + +- (void)setFill +{ + CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + + // Set up the pattern to be as tall as our window, and one pixel wide. + // CoreGraphics can cache and tile us quickly. + CGPatternCallbacks callbacks = {0, &patternDraw, NULL}; + CGPatternRef pattern = CGPatternCreate(self, CGRectMake(0.0f, 0.0f, sPatternWidth, [mWindow frame].size.height), + CGAffineTransformIdentity, 1, [mWindow frame].size.height, + kCGPatternTilingConstantSpacing, true, &callbacks); + + // Set the pattern as the fill, which is what we were asked to do. All our + // drawing will take place in the patternDraw callback. + CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL); + CGContextSetFillColorSpace(context, patternSpace); + CGColorSpaceRelease(patternSpace); + float component = 1.0f; + CGContextSetFillPattern(context, pattern, &component); + CGPatternRelease(pattern); +} + + +// Pass nil here to get the default appearance. +- (void)setTitlebarColor:(NSColor*)aColor +{ + [mTitlebarColor autorelease]; + mTitlebarColor = [aColor retain]; +} + + +- (NSColor*)titlebarColor +{ + return mTitlebarColor; +} + + +- (void)setBackgroundColor:(NSColor*)aColor +{ + [mBackgroundColor autorelease]; + mBackgroundColor = [aColor retain]; +} + + +- (NSColor*)backgroundColor +{ + return mBackgroundColor; +} + + +- (NSWindow*)window +{ + return mWindow; +} + + +- (NSString*)colorSpaceName +{ + return NSDeviceRGBColorSpace; +} + + +- (void)set +{ + [self setFill]; +} + +- (float)titlebarHeight +{ + return mTitlebarHeight; +} + +@end + + +// This is an internal Apple class, which we need to work around a bug in. It is +// the class responsible for drawing the titlebar for metal windows. It actually +// is a few levels deep in the inhertiance graph, but we don't need to know about +// all its superclasses. +@interface NSGrayFrame : NSObject ++ (void)drawBevel:(NSRect)bevel inFrame:(NSRect)frame topCornerRounded:(BOOL)top; ++ (void)drawBevel:(NSRect)bevel inFrame:(NSRect)frame topCornerRounded:(BOOL)top bottomCornerRounded:(BOOL)bottom; +@end + +@implementation NSGrayFrame(DrawingBugWorkaround) + +// Work around a bug in this method -- it draws a strange 1px border on the left and +// right edges of a window. We don't want that, so call the similar method defined +// in the superclass. ++ (void)drawBevel:(NSRect)bevel inFrame:(NSRect)frame topCornerRounded:(BOOL)top bottomCornerRounded:(BOOL)bottom +{ + if ([self respondsToSelector:@selector(drawBevel:inFrame:topCornerRounded:)]) + [self drawBevel:bevel inFrame:frame topCornerRounded:top]; +} + +@end + + @implementation PopupWindow // The OS treats our custom popup windows very strangely -- many mouse events diff --git a/widget/src/xpwidgets/nsBaseWidget.cpp b/widget/src/xpwidgets/nsBaseWidget.cpp index 9375df06885b..be48a62d89d7 100644 --- a/widget/src/xpwidgets/nsBaseWidget.cpp +++ b/widget/src/xpwidgets/nsBaseWidget.cpp @@ -858,6 +858,13 @@ nsBaseWidget::EndSecureKeyboardInput() return NS_OK; } +NS_IMETHODIMP +nsBaseWidget::SetWindowTitlebarColor(nscolor aColor) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + /** * Modifies aFile to point at an icon file with the given name and suffix. The * suffix may correspond to a file extension with leading '.' if appropriate. diff --git a/widget/src/xpwidgets/nsBaseWidget.h b/widget/src/xpwidgets/nsBaseWidget.h index dfe2fa0f422d..e73bae54da8c 100644 --- a/widget/src/xpwidgets/nsBaseWidget.h +++ b/widget/src/xpwidgets/nsBaseWidget.h @@ -133,6 +133,7 @@ public: NS_IMETHOD SetIcon(const nsAString &anIconSpec); NS_IMETHOD BeginSecureKeyboardInput(); NS_IMETHOD EndSecureKeyboardInput(); + NS_IMETHOD SetWindowTitlebarColor(nscolor aColor); virtual void ConvertToDeviceCoordinates(nscoord &aX,nscoord &aY) {} virtual void FreeNativeData(void * data, PRUint32 aDataType) {}