diff options
Diffstat (limited to 'macosx/ttkMacOSXTheme.c')
-rw-r--r-- | macosx/ttkMacOSXTheme.c | 1203 |
1 files changed, 1203 insertions, 0 deletions
diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c new file mode 100644 index 0000000..5752fb1 --- /dev/null +++ b/macosx/ttkMacOSXTheme.c @@ -0,0 +1,1203 @@ +/* + * ttkMacOSXTheme.c -- + * + * Tk theme engine for Mac OSX, using the Appearance Manager API. + * + * Copyright (c) 2004 Joe English + * Copyright (c) 2005 Neil Madden + * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright 2008-2009, Apple Inc. + * Copyright 2009 Kevin Walzer/WordTech Communications LLC. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * See also: + * + * <URL: http://developer.apple.com/documentation/Carbon/Reference/ + * Appearance_Manager/appearance_manager/APIIndex.html > + * + * Notes: + * "Active" means different things in Mac and Tk terminology -- + * On Aqua, widgets are "Active" if they belong to the foreground window, + * "Inactive" if they are in a background window. + * Tk uses the term "active" to mean that the mouse cursor + * is over a widget; aka "hover", "prelight", or "hot-tracked". + * Aqua doesn't use this kind of feedback. + * + * The QuickDraw/Carbon coordinate system is relative to the + * top-level window, not to the Tk_Window. BoxToRect() + * accounts for this. + */ + +#include "tkMacOSXPrivate.h" +#include "ttk/ttkTheme.h" + +/* + * Use this version in the core: + */ +#define BEGIN_DRAWING(d) { \ + TkMacOSXDrawingContext dc; \ + if (!TkMacOSXSetupDrawingContext((d), NULL, 1, &dc)) {return;} +#define END_DRAWING \ + TkMacOSXRestoreDrawingContext(&dc); } + +#define HIOrientation kHIThemeOrientationNormal + +#ifdef __LP64__ +#define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) +#else +#define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) +#endif /* __LP64__ */ + +/*---------------------------------------------------------------------- + * +++ Utilities. + */ + +/* + * BoxToRect -- + * Convert a Ttk_Box in Tk coordinates relative to the given Drawable + * to a native Rect relative to the containing port. + */ +static inline CGRect BoxToRect(Drawable d, Ttk_Box b) +{ + MacDrawable *md = (MacDrawable*)d; + CGRect rect; + + rect.origin.y = b.y + md->yOff; + rect.origin.x = b.x + md->xOff; + rect.size.height = b.height; + rect.size.width = b.width; + + return rect; +} + +/* + * Table mapping Tk states to Appearance manager ThemeStates + */ + +static Ttk_StateTable ThemeStateTable[] = { + {kThemeStateUnavailable, TTK_STATE_DISABLED, 0}, + {kThemeStatePressed, TTK_STATE_PRESSED, 0}, + {kThemeStateInactive, TTK_STATE_BACKGROUND, 0}, + {kThemeStateActive, 0, 0} +/* Others: Not sure what these are supposed to mean. + Up/Down have something to do with "little arrow" increment controls... + Dunno what a "Rollover" is. + NEM: Rollover is TTK_STATE_ACTIVE... but we don't handle that yet, by the + looks of things + {kThemeStateRollover, 0, 0}, + {kThemeStateUnavailableInactive, 0, 0} + {kThemeStatePressedUp, 0, 0}, + {kThemeStatePressedDown, 0, 0} +*/ +}; + +/*---------------------------------------------------------------------- + * +++ Button element: Used for elements drawn with DrawThemeButton. + */ + +/* + * Extra margins to account for drop shadow. + */ +static Ttk_Padding ButtonMargins = {2,2,2,2}; + +#define NoThemeMetric 0xFFFFFFFF + +typedef struct { + ThemeButtonKind kind; + ThemeMetric heightMetric; +} ThemeButtonParams; + +static ThemeButtonParams + PushButtonParams = { kThemePushButton, kThemeMetricPushButtonHeight }, + CheckBoxParams = { kThemeCheckBox, kThemeMetricCheckBoxHeight }, + RadioButtonParams = { kThemeRadioButton, kThemeMetricRadioButtonHeight }, + BevelButtonParams = { kThemeBevelButton, NoThemeMetric }, + PopupButtonParams = { kThemePopupButton, kThemeMetricPopupButtonHeight }, + DisclosureParams = { kThemeDisclosureButton, kThemeMetricDisclosureTriangleHeight }, + ListHeaderParams = { kThemeListHeaderButton, kThemeMetricListHeaderHeight }; + +static Ttk_StateTable ButtonValueTable[] = { + { kThemeButtonMixed, TTK_STATE_ALTERNATE, 0 }, + { kThemeButtonOn, TTK_STATE_SELECTED, 0 }, + { kThemeButtonOff, 0, 0 } +/* Others: kThemeDisclosureRight, kThemeDisclosureDown, kThemeDisclosureLeft */ +}; + +static Ttk_StateTable ButtonAdornmentTable[] = { + { kThemeAdornmentDefault| kThemeAdornmentFocus, + TTK_STATE_ALTERNATE| TTK_STATE_FOCUS, 0 }, + { kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0 }, + { kThemeAdornmentFocus, TTK_STATE_FOCUS, 0 }, + { kThemeAdornmentNone, 0, 0 } +}; + +/* + * computeButtonDrawInfo -- + * Fill in an appearance manager HIThemeButtonDrawInfo record. + */ +static inline HIThemeButtonDrawInfo computeButtonDrawInfo( + ThemeButtonParams *params, Ttk_State state) +{ + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = params ? params->kind : 0, + .value = Ttk_StateTableLookup(ButtonValueTable, state), + .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state), + }; + return info; +} + +static void ButtonElementSizeNoPadding( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ThemeButtonParams *params = clientData; + + if (params->heightMetric != NoThemeMetric) { + SInt32 height; + + ChkErr(GetThemeMetric, params->heightMetric, &height); + *heightPtr = height; + } +} + +static void ButtonElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + ThemeButtonParams *params = clientData; + const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, 0); + static const CGRect scratchBounds = {{0, 0}, {100, 100}}; + CGRect contentBounds; + + ButtonElementSizeNoPadding( + clientData, elementRecord, tkwin, + widthPtr, heightPtr, paddingPtr); + + /* + * To compute internal padding, query the appearance manager + * for the content bounds of a dummy rectangle, then use + * the difference as the padding. + */ + ChkErr(HIThemeGetButtonContentBounds, + &scratchBounds, &info, &contentBounds); + + paddingPtr->left = CGRectGetMinX(contentBounds); + paddingPtr->top = CGRectGetMinY(contentBounds); + paddingPtr->right = CGRectGetMaxX(scratchBounds) - CGRectGetMaxX(contentBounds) + 1; + paddingPtr->bottom = CGRectGetMaxY(scratchBounds) - CGRectGetMaxY(contentBounds); + + /* + * Now add a little extra padding to account for drop shadows. + * @@@ SHOULD: call GetThemeButtonBackgroundBounds() instead. + */ + + *paddingPtr = Ttk_AddPadding(*paddingPtr, ButtonMargins); + *widthPtr += Ttk_PaddingWidth(ButtonMargins); + *heightPtr += Ttk_PaddingHeight(ButtonMargins); +} + +static void ButtonElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ThemeButtonParams *params = clientData; + CGRect bounds = BoxToRect(d, Ttk_PadBox(b, ButtonMargins)); + const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state); + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING +} + +static Ttk_ElementSpec ButtonElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + ButtonElementSize, + ButtonElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Notebook elements. + */ + + +/* Tab position logic, c.f. ttkNotebook.c TabState() */ + +#define TTK_STATE_NOTEBOOK_FIRST TTK_STATE_USER1 +#define TTK_STATE_NOTEBOOK_LAST TTK_STATE_USER2 +static Ttk_StateTable TabStyleTable[] = { + { kThemeTabFrontInactive, TTK_STATE_SELECTED|TTK_STATE_BACKGROUND}, + { kThemeTabNonFrontInactive, TTK_STATE_BACKGROUND}, + { kThemeTabFrontUnavailable, TTK_STATE_DISABLED|TTK_STATE_SELECTED}, + { kThemeTabNonFrontUnavailable, TTK_STATE_DISABLED}, + { kThemeTabFront, TTK_STATE_SELECTED}, + { kThemeTabNonFrontPressed, TTK_STATE_PRESSED}, + { kThemeTabNonFront, 0} +}; + +static Ttk_StateTable TabAdornmentTable[] = { + { kHIThemeTabAdornmentNone, + TTK_STATE_NOTEBOOK_FIRST|TTK_STATE_NOTEBOOK_LAST}, + {kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_NOTEBOOK_FIRST}, + {kHIThemeTabAdornmentNone, TTK_STATE_NOTEBOOK_LAST}, + {kHIThemeTabAdornmentTrailingSeparator, 0 }, +}; + +static Ttk_StateTable TabPositionTable[] = { + { kHIThemeTabPositionOnly, + TTK_STATE_NOTEBOOK_FIRST|TTK_STATE_NOTEBOOK_LAST}, + { kHIThemeTabPositionFirst, TTK_STATE_NOTEBOOK_FIRST}, + { kHIThemeTabPositionLast, TTK_STATE_NOTEBOOK_LAST}, + { kHIThemeTabPositionMiddle, 0 }, +}; + +/* + * Apple XHIG Tab View Specifications: + * + * Control sizes: Tab views are available in regular, small, and mini sizes. + * The tab height is fixed for each size, but you control the size of the pane + * area. The tab heights for each size are listed below: + * - Regular size: 20 pixels. + * - Small: 17 pixels. + * - Mini: 15 pixels. + * + * Label spacing and fonts: The tab labels should be in a font that’s + * proportional to the size of the tab view control. In addition, the label + * should be placed so that there are equal margins of space before and after + * it. The guidelines below provide the specifications you should use for tab + * labels: + * - Regular size: System font. Center in tab, leaving 12 pixels on each side. + * - Small: Small system font. Center in tab, leaving 10 pixels on each side. + * - Mini: Mini system font. Center in tab, leaving 8 pixels on each side. + * + * Control spacing: Whether you decide to inset a tab view in a window or + * extend its edges to the window sides and bottom, you should place the top + * edge of the tab view 12 or 14 pixels below the bottom edge of the title bar + * (or toolbar, if there is one). If you choose to inset a tab view in a + * window, you should leave a margin of 20 pixels between the sides and bottom + * of the tab view and the sides and bottom of the window (although 16 pixels + * is also an acceptable margin-width). If you need to provide controls below + * the tab view, leave enough space below the tab view so the controls are 20 + * pixels above the bottom edge of the window and 12 pixels between the tab + * view and the controls. + * If you choose to extend the tab view sides and bottom so that they meet the + * window sides and bottom, you should leave a margin of at least 20 pixels + * between the content in the tab view and the tab-view edges. + * + * <URL: http://developer.apple.com/documentation/userexperience/Conceptual/ + * AppleHIGuidelines/XHIGControls/XHIGControls.html#//apple_ref/doc/uid/ + * TP30000359-TPXREF116> + */ + +static const int TAB_HEIGHT = 10; +static const int TAB_OVERLAP = 10; + +static void TabElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *heightPtr = TAB_HEIGHT + TAB_OVERLAP - 1; +} + +static void TabElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, b); + HIThemeTabDrawInfo info = { + .version = 1, + .style = Ttk_StateTableLookup(TabStyleTable, state), + .direction = kThemeTabNorth, + .size = kHIThemeTabSizeNormal, + .adornment = Ttk_StateTableLookup(TabAdornmentTable, state), + .kind = kHIThemeTabKindNormal, + .position = Ttk_StateTableLookup(TabPositionTable, state), + }; + + bounds.size.height += TAB_OVERLAP; + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING +} + +static Ttk_ElementSpec TabElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TabElementSize, + TabElementDraw +}; + +/* + * Notebook panes: + */ +static void PaneElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *paddingPtr = Ttk_MakePadding(9, 5, 9, 9); +} + +static void PaneElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, b); + HIThemeTabPaneDrawInfo info = { + .version = 1, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .direction = kThemeTabNorth, + .size = kHIThemeTabSizeNormal, + .kind = kHIThemeTabKindNormal, + .adornment = kHIThemeTabPaneAdornmentNormal, + }; + + bounds.origin.y -= TAB_OVERLAP; + bounds.size.height += TAB_OVERLAP; + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawTabPane, &bounds, &info, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec PaneElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + PaneElementSize, + PaneElementDraw +}; + +/* + * Labelframe borders: + * Use "primary group box ..." + * Quoth DrawThemePrimaryGroup reference: + * "The primary group box frame is drawn inside the specified + * rectangle and is a maximum of 2 pixels thick." + * + * "Maximum of 2 pixels thick" is apparently a lie; + * looks more like 4 to me with shading. + */ +static void GroupElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *paddingPtr = Ttk_UniformPadding(4); +} + +static void GroupElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, b); + const HIThemeGroupBoxDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = kHIThemeGroupBoxKindPrimaryOpaque, + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawGroupBox, &bounds, &info, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec GroupElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + GroupElementSize, + GroupElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Entry element -- + * 3 pixels padding for focus rectangle + * 2 pixels padding for EditTextFrame + */ + +typedef struct { + Tcl_Obj *backgroundObj; +} EntryElement; + +static Ttk_ElementOptionSpec EntryElementOptions[] = { + { "-background", TK_OPTION_BORDER, + Tk_Offset(EntryElement,backgroundObj), "white" }, + {0} +}; + +static void EntryElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *paddingPtr = Ttk_UniformPadding(5); +} + +static void EntryElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + EntryElement *e = elementRecord; + Tk_3DBorder backgroundPtr = Tk_Get3DBorderFromObj(tkwin,e->backgroundObj); + Ttk_Box inner = Ttk_PadBox(b, Ttk_UniformPadding(3)); + CGRect bounds = BoxToRect(d, inner); + const HIThemeFrameDrawInfo info = { + .version = 0, + .kind = kHIThemeFrameTextFieldSquare, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .isFocused = state & TTK_STATE_FOCUS, + }; + + /* + * Erase w/background color: + */ + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + inner.x,inner.y, inner.width, inner.height); + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); + /*if (state & TTK_STATE_FOCUS) { + ChkErr(DrawThemeFocusRect, &bounds, 1); + }*/ + END_DRAWING +} + +static Ttk_ElementSpec EntryElementSpec = { + TK_STYLE_VERSION_2, + sizeof(EntryElement), + EntryElementOptions, + EntryElementSize, + EntryElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Combobox: + * + * NOTES: + * kThemeMetricComboBoxLargeDisclosureWidth -> 17 + * Padding and margins guesstimated by trial-and-error. + */ + +static Ttk_Padding ComboboxPadding = { 2, 3, 17, 1 }; +static Ttk_Padding ComboboxMargins = { 3, 3, 4, 4 }; + +static void ComboboxElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *widthPtr = 0; + *heightPtr = 0; + *paddingPtr = Ttk_AddPadding(ComboboxMargins, ComboboxPadding); +} + +static void ComboboxElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, Ttk_PadBox(b, ComboboxMargins)); + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = kThemeComboBox, + .value = Ttk_StateTableLookup(ButtonValueTable, state), + .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state), + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING +} + +static Ttk_ElementSpec ComboboxElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + ComboboxElementSize, + ComboboxElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Spinbuttons. + * + * From Apple HIG, part III, section "Controls", "The Stepper Control": + * there should be 2 pixels of space between the stepper control + * (AKA IncDecButton, AKA "little arrows") and the text field it modifies. + */ + +static Ttk_Padding SpinbuttonMargins = {2,0,2,0}; +static void SpinButtonElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SInt32 s; + + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s); + *widthPtr = s + Ttk_PaddingWidth(SpinbuttonMargins); + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s); + *heightPtr = s + Ttk_PaddingHeight(SpinbuttonMargins); +} + +static void SpinButtonElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins)); + /* @@@ can't currently distinguish PressedUp (== Pressed) from PressedDown; + * ignore this bit for now [see #2219588] + */ + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state & ~TTK_STATE_PRESSED), + .kind = kThemeIncDecButton, + .value = Ttk_StateTableLookup(ButtonValueTable, state), + .adornment = kThemeAdornmentNone, + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING +} + +static Ttk_ElementSpec SpinButtonElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinButtonElementSize, + SpinButtonElementDraw +}; + + +/*---------------------------------------------------------------------- + * +++ DrawThemeTrack-based elements -- + * Progress bars and scales. (See also: <<NOTE-TRACKS>>) + */ + +static Ttk_StateTable ThemeTrackEnableTable[] = { + { kThemeTrackDisabled, TTK_STATE_DISABLED, 0 }, + { kThemeTrackInactive, TTK_STATE_BACKGROUND, 0 }, + { kThemeTrackActive, 0, 0 } + /* { kThemeTrackNothingToScroll, ?, ? }, */ +}; + +typedef struct { /* TrackElement client data */ + ThemeTrackKind kind; + SInt32 thicknessMetric; +} TrackElementData; + +static TrackElementData ScaleData = { + kThemeSlider, kThemeMetricHSliderHeight +}; + +typedef struct { + Tcl_Obj *fromObj; /* minimum value */ + Tcl_Obj *toObj; /* maximum value */ + Tcl_Obj *valueObj; /* current value */ + Tcl_Obj *orientObj; /* horizontal / vertical */ +} TrackElement; + +static Ttk_ElementOptionSpec TrackElementOptions[] = { + { "-from", TK_OPTION_DOUBLE, Tk_Offset(TrackElement,fromObj) }, + { "-to", TK_OPTION_DOUBLE, Tk_Offset(TrackElement,toObj) }, + { "-value", TK_OPTION_DOUBLE, Tk_Offset(TrackElement,valueObj) }, + { "-orient", TK_OPTION_STRING, Tk_Offset(TrackElement,orientObj) }, + {0,0,0} +}; + +static void TrackElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + TrackElementData *data = clientData; + SInt32 size = 24; /* reasonable default ... */ + + ChkErr(GetThemeMetric, data->thicknessMetric, &size); + *widthPtr = *heightPtr = size; +} + +static void TrackElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + TrackElementData *data = clientData; + TrackElement *elem = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + double from = 0, to = 100, value = 0, factor; + + Ttk_GetOrientFromObj(NULL, elem->orientObj, &orientation); + Tcl_GetDoubleFromObj(NULL, elem->fromObj, &from); + Tcl_GetDoubleFromObj(NULL, elem->toObj, &to); + Tcl_GetDoubleFromObj(NULL, elem->valueObj, &value); + factor = RangeToFactor(to - from); + + HIThemeTrackDrawInfo info = { + .version = 0, + .kind = data->kind, + .bounds = BoxToRect(d, b), + .min = from * factor, + .max = to * factor, + .value = value * factor, + .attributes = kThemeTrackShowThumb | + (orientation == TTK_ORIENT_HORIZONTAL ? + kThemeTrackHorizontal : 0), + .enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state), + .trackInfo.progress.phase = 0, + }; + + if (info.kind == kThemeSlider) { + info.trackInfo.slider.pressState = state & TTK_STATE_PRESSED ? + kThemeThumbPressed : 0; + info.trackInfo.slider.thumbDir = kThemeThumbPlain; + } + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec TrackElementSpec = { + TK_STYLE_VERSION_2, + sizeof(TrackElement), + TrackElementOptions, + TrackElementSize, + TrackElementDraw +}; + +/* + * Slider element -- <<NOTE-TRACKS>> + * Has geometry only. The Scale widget adjusts the position of this element, + * and uses it for hit detection. In the Aqua theme, the slider is actually + * drawn as part of the trough element. + * + * Also buggy: The geometry here is a Wild-Assed-Guess; I can't + * figure out how to get the Appearance Manager to tell me the + * slider size. + */ +static void SliderElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *widthPtr = *heightPtr = 24; +} + +static Ttk_ElementSpec SliderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SliderElementSize, + TtkNullElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Progress bar element (new): + * + * @@@ NOTE: According to an older revision of the Aqua reference docs, + * @@@ the 'phase' field is between 0 and 4. Newer revisions say + * @@@ that it can be any UInt8 value. + */ + +typedef struct { + Tcl_Obj *orientObj; /* horizontal / vertical */ + Tcl_Obj *valueObj; /* current value */ + Tcl_Obj *maximumObj; /* maximum value */ + Tcl_Obj *phaseObj; /* animation phase */ + Tcl_Obj *modeObj; /* progress bar mode */ +} PbarElement; + +static Ttk_ElementOptionSpec PbarElementOptions[] = { + { "-orient", TK_OPTION_STRING, + Tk_Offset(PbarElement,orientObj), "horizontal" }, + { "-value", TK_OPTION_DOUBLE, + Tk_Offset(PbarElement,valueObj), "0" }, + { "-maximum", TK_OPTION_DOUBLE, + Tk_Offset(PbarElement,maximumObj), "100" }, + { "-phase", TK_OPTION_INT, + Tk_Offset(PbarElement,phaseObj), "0" }, + { "-mode", TK_OPTION_STRING, + Tk_Offset(PbarElement,modeObj), "determinate" }, + {0,0,0,0} +}; + +static void PbarElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SInt32 size = 24; /* @@@ Check HIG for correct default */ + + ChkErr(GetThemeMetric, kThemeMetricLargeProgressBarThickness, &size); + *widthPtr = *heightPtr = size; +} + +static void PbarElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + PbarElement *pbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL, phase = 0; + double value = 0, maximum = 100, factor; + + Ttk_GetOrientFromObj(NULL, pbar->orientObj, &orientation); + Tcl_GetDoubleFromObj(NULL, pbar->valueObj, &value); + Tcl_GetDoubleFromObj(NULL, pbar->maximumObj, &maximum); + Tcl_GetIntFromObj(NULL, pbar->phaseObj, &phase); + factor = RangeToFactor(maximum); + + HIThemeTrackDrawInfo info = { + .version = 0, + .kind = (!strcmp("indeterminate", Tcl_GetString(pbar->modeObj)) && value) ? + kThemeIndeterminateBar : kThemeProgressBar, + .bounds = BoxToRect(d, b), + .min = 0, + .max = maximum * factor, + .value = value * factor, + .attributes = kThemeTrackShowThumb | + (orientation == TTK_ORIENT_HORIZONTAL ? + kThemeTrackHorizontal : 0), + .enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state), + .trackInfo.progress.phase = phase, + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec PbarElementSpec = { + TK_STYLE_VERSION_2, + sizeof(PbarElement), + PbarElementOptions, + PbarElementSize, + PbarElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Separator element. + * + * DrawThemeSeparator() guesses the orientation of the line from + * the width and height of the rectangle, so the same element can + * can be used for horizontal, vertical, and general separators. + */ + +static void SeparatorElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + *widthPtr = *heightPtr = 1; +} + +static void SeparatorElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + CGRect bounds = BoxToRect(d, b); + const HIThemeSeparatorDrawInfo info = { + .version = 0, + /* Separator only supports kThemeStateActive, kThemeStateInactive */ + .state = Ttk_StateTableLookup(ThemeStateTable, state & TTK_STATE_BACKGROUND), + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec SeparatorElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SeparatorElementSize, + SeparatorElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Size grip element. + */ +static const ThemeGrowDirection sizegripGrowDirection + = kThemeGrowRight|kThemeGrowDown; + +static void SizegripElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + HIThemeGrowBoxDrawInfo info = { + .version = 0, + .state = kThemeStateActive, + .kind = kHIThemeGrowBoxKindNormal, + .direction = sizegripGrowDirection, + .size = kHIThemeGrowBoxSizeNormal, + }; + CGRect bounds = CGRectZero; + + ChkErr(HIThemeGetGrowBoxBounds, &bounds.origin, &info, &bounds); + *widthPtr = bounds.size.width; + *heightPtr = bounds.size.height; +} + +static void SizegripElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + CGRect bounds = BoxToRect(d, b); + HIThemeGrowBoxDrawInfo info = { + .version = 0, + /* Grow box only supports kThemeStateActive, kThemeStateInactive */ + .state = Ttk_StateTableLookup(ThemeStateTable, state & TTK_STATE_BACKGROUND), + .kind = kHIThemeGrowBoxKindNormal, + .direction = sizegripGrowDirection, + .size = kHIThemeGrowBoxSizeNormal, + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawGrowBox, &bounds.origin, &info, dc.context, HIOrientation); + END_DRAWING +} + +static Ttk_ElementSpec SizegripElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SizegripElementSize, + SizegripElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Background and fill elements. + * + * This isn't quite right: In Aqua, the correct background for + * a control depends on what kind of container it belongs to, + * and the type of the top-level window. + * + * Also: patterned backgrounds should be aligned with the coordinate + * system of the top-level window. If we're drawing into an + * off-screen graphics port this leads to alignment glitches. + */ + +static void FillElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + CGRect bounds = BoxToRect(d, b); + ThemeBrush brush = (state & TTK_STATE_BACKGROUND) + ? kThemeBrushModelessDialogBackgroundInactive + : kThemeBrushModelessDialogBackgroundActive; + + BEGIN_DRAWING(d) + ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation); + //QDSetPatternOrigin(PatternOrigin(tkwin, d)); + CGContextFillRect(dc.context, bounds); + END_DRAWING +} + +static void BackgroundElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, unsigned int state) +{ + FillElementDraw( + clientData, elementRecord, tkwin, + d, Ttk_WinBox(tkwin), state); +} + +static Ttk_ElementSpec FillElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TtkNullElementSize, + FillElementDraw +}; + +static Ttk_ElementSpec BackgroundElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TtkNullElementSize, + BackgroundElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ ToolbarBackground element -- toolbar style for frames. + * + * This is very similar to the normal background element, but uses a + * different ThemeBrush in order to get the lighter pinstripe effect + * used in toolbars. We use SetThemeBackground() rather than + * ApplyThemeBackground() in order to get the right style. + * + * <URL: http://developer.apple.com/documentation/Carbon/Reference/ + * Appearance_Manager/appearance_manager/constant_7.html#/ + * /apple_ref/doc/uid/TP30000243/C005321> + * + */ +static void ToolbarBackgroundElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ThemeBrush brush = kThemeBrushToolbarBackground; + CGRect bounds = BoxToRect(d, Ttk_WinBox(tkwin)); + + BEGIN_DRAWING(d) + ChkErr(HIThemeSetFill, brush, NULL, dc.context, HIOrientation); + //QDSetPatternOrigin(PatternOrigin(tkwin, d)); + CGContextFillRect(dc.context, bounds); + END_DRAWING +} + +static Ttk_ElementSpec ToolbarBackgroundElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TtkNullElementSize, + ToolbarBackgroundElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Treeview header + * Redefine the header to use a kThemeListHeaderButton. + */ + +#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1 +static Ttk_StateTable TreeHeaderValueTable[] = { + { kThemeButtonOn, TTK_STATE_ALTERNATE}, + { kThemeButtonOn, TTK_STATE_SELECTED}, + { kThemeButtonOff, 0} +}; +static Ttk_StateTable TreeHeaderAdornmentTable[] = { + { kThemeAdornmentHeaderButtonSortUp, + TTK_STATE_ALTERNATE|TTK_TREEVIEW_STATE_SORTARROW}, + { kThemeAdornmentDefault, + TTK_STATE_SELECTED|TTK_TREEVIEW_STATE_SORTARROW}, + { kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_ALTERNATE}, + { kThemeAdornmentHeaderButtonNoSortArrow, TTK_STATE_SELECTED}, + { kThemeAdornmentFocus, TTK_STATE_FOCUS}, + { kThemeAdornmentNone, 0} +}; + +static void TreeHeaderElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + ThemeButtonParams *params = clientData; + CGRect bounds = BoxToRect(d, b); + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = params->kind, + .value = Ttk_StateTableLookup(TreeHeaderValueTable, state), + .adornment = Ttk_StateTableLookup(TreeHeaderAdornmentTable, state), + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING +} + +static Ttk_ElementSpec TreeHeaderElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + ButtonElementSizeNoPadding, + TreeHeaderElementDraw +}; + +/* + * Disclosure triangle: + */ +#define TTK_TREEVIEW_STATE_OPEN TTK_STATE_USER1 +#define TTK_TREEVIEW_STATE_LEAF TTK_STATE_USER2 +static Ttk_StateTable DisclosureValueTable[] = { + { kThemeDisclosureDown, TTK_TREEVIEW_STATE_OPEN, 0 }, + { kThemeDisclosureRight, 0, 0 }, +}; + +static void DisclosureElementSize( + void *clientData, void *elementRecord, Tk_Window tkwin, + int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +{ + SInt32 s; + + ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleWidth, &s); + *widthPtr = s; + ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleHeight, &s); + *heightPtr = s; +} + +static void DisclosureElementDraw( + void *clientData, void *elementRecord, Tk_Window tkwin, + Drawable d, Ttk_Box b, Ttk_State state) +{ + if (!(state & TTK_TREEVIEW_STATE_LEAF)) { + CGRect bounds = BoxToRect(d, b); + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = kThemeDisclosureTriangle, + .value = Ttk_StateTableLookup(DisclosureValueTable, state), + .adornment = kThemeAdornmentDrawIndicatorOnly, + }; + + BEGIN_DRAWING(d) + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + END_DRAWING + } +} + +static Ttk_ElementSpec DisclosureElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + DisclosureElementSize, + DisclosureElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Widget layouts. + */ + +TTK_BEGIN_LAYOUT_TABLE(LayoutTable) + +TTK_LAYOUT("Toolbar", + TTK_NODE("Toolbar.background", TTK_FILL_BOTH)) + +TTK_LAYOUT("TButton", + TTK_GROUP("Button.button", TTK_FILL_BOTH, + TTK_GROUP("Button.padding", TTK_FILL_BOTH, + TTK_NODE("Button.label", TTK_FILL_BOTH)))) + +TTK_LAYOUT("TRadiobutton", + TTK_GROUP("Radiobutton.button", TTK_FILL_BOTH, + TTK_GROUP("Radiobutton.padding", TTK_FILL_BOTH, + TTK_NODE("Radiobutton.label", TTK_PACK_LEFT)))) + +TTK_LAYOUT("TCheckbutton", + TTK_GROUP("Checkbutton.button", TTK_FILL_BOTH, + TTK_GROUP("Checkbutton.padding", TTK_FILL_BOTH, + TTK_NODE("Checkbutton.label", TTK_PACK_LEFT)))) + +TTK_LAYOUT("TMenubutton", + TTK_GROUP("Menubutton.button", TTK_FILL_BOTH, + TTK_GROUP("Menubutton.padding", TTK_FILL_BOTH, + TTK_NODE("Menubutton.label", TTK_PACK_LEFT)))) + +TTK_LAYOUT("TCombobox", + TTK_GROUP("Combobox.button", TTK_PACK_TOP|TTK_FILL_X, + TTK_GROUP("Combobox.padding", TTK_FILL_BOTH, + TTK_NODE("Combobox.textarea", TTK_FILL_X)))) + +/* Notebook tabs -- no focus ring */ +TTK_LAYOUT("Tab", + TTK_GROUP("Notebook.tab", TTK_FILL_BOTH, + TTK_GROUP("Notebook.padding", TTK_EXPAND|TTK_FILL_BOTH, + TTK_NODE("Notebook.label", TTK_EXPAND|TTK_FILL_BOTH)))) + +/* Progress bars -- track only */ +TTK_LAYOUT("TSpinbox", + TTK_NODE("Spinbox.spinbutton", TTK_PACK_RIGHT|TTK_STICK_E) + TTK_GROUP("Spinbox.field", TTK_EXPAND|TTK_FILL_X, + TTK_NODE("Spinbox.textarea", TTK_EXPAND|TTK_FILL_X))) + +TTK_LAYOUT("TProgressbar", + TTK_NODE("Progressbar.track", TTK_EXPAND|TTK_FILL_BOTH)) + +/* Tree heading -- no border, fixed height */ +TTK_LAYOUT("Heading", + TTK_NODE("Treeheading.cell", TTK_FILL_X) + TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) + TTK_NODE("Treeheading.text", 0)) + +/* Tree items -- omit focus ring */ +TTK_LAYOUT("Item", + TTK_GROUP("Treeitem.padding", TTK_FILL_BOTH, + TTK_NODE("Treeitem.indicator", TTK_PACK_LEFT) + TTK_NODE("Treeitem.image", TTK_PACK_LEFT) + TTK_NODE("Treeitem.text", TTK_PACK_LEFT))) + +TTK_END_LAYOUT_TABLE + +/*---------------------------------------------------------------------- + * +++ Initialization. + */ + +static int AquaTheme_Init(Tcl_Interp *interp) +{ + Ttk_Theme themePtr = Ttk_CreateTheme(interp, "aqua", NULL); + + if (!themePtr) { + return TCL_ERROR; + } + + /* + * Elements: + */ + Ttk_RegisterElementSpec(themePtr, "background", &BackgroundElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "fill", &FillElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Toolbar.background", + &ToolbarBackgroundElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "Button.button", + &ButtonElementSpec, &PushButtonParams); + Ttk_RegisterElementSpec(themePtr, "Checkbutton.button", + &ButtonElementSpec, &CheckBoxParams); + Ttk_RegisterElementSpec(themePtr, "Radiobutton.button", + &ButtonElementSpec, &RadioButtonParams); + Ttk_RegisterElementSpec(themePtr, "Toolbutton.border", + &ButtonElementSpec, &BevelButtonParams); + Ttk_RegisterElementSpec(themePtr, "Menubutton.button", + &ButtonElementSpec, &PopupButtonParams); + Ttk_RegisterElementSpec(themePtr, "Spinbox.spinbutton", + &SpinButtonElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Combobox.button", + &ComboboxElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Treeitem.indicator", + &DisclosureElementSpec, &DisclosureParams); + Ttk_RegisterElementSpec(themePtr, "Treeheading.cell", + &TreeHeaderElementSpec, &ListHeaderParams); + + Ttk_RegisterElementSpec(themePtr, "Notebook.tab", &TabElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Notebook.client", &PaneElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "Labelframe.border",&GroupElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "Entry.field",&EntryElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.field",&EntryElementSpec,0); + + Ttk_RegisterElementSpec(themePtr, "separator",&SeparatorElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "hseparator",&SeparatorElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "vseparator",&SeparatorElementSpec,0); + + Ttk_RegisterElementSpec(themePtr, "sizegrip",&SizegripElementSpec,0); + + /* + * <<NOTE-TRACKS>> + * The Progressbar widget adjusts the size of the pbar element. + * In the Aqua theme, the appearance manager computes the bar geometry; + * we do all the drawing in the ".track" element and leave the .pbar out. + */ + Ttk_RegisterElementSpec(themePtr,"Scale.trough", + &TrackElementSpec, &ScaleData); + Ttk_RegisterElementSpec(themePtr,"Scale.slider",&SliderElementSpec,0); + Ttk_RegisterElementSpec(themePtr,"Progressbar.track", &PbarElementSpec, 0); + + /* + * Layouts: + */ + Ttk_RegisterLayouts(themePtr, LayoutTable); + + Tcl_PkgProvide(interp, "ttk::theme::aqua", TTK_VERSION); + return TCL_OK; +} + +MODULE_SCOPE +int Ttk_MacOSXPlatformInit(Tcl_Interp *interp) +{ + return AquaTheme_Init(interp); +} + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ + |