/* * 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 * 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: * * * * 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. * * */ static void TabElementSize( void *clientData, void *elementRecord, Tk_Window tkwin, int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) { GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *)heightPtr); *paddingPtr = Ttk_MakePadding(0, 0, 0, 2); } 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), }; 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 -= kThemeMetricTabFrameOverlap; bounds.size.height += kThemeMetricTabFrameOverlap; 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: <>) */ 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 -- <> * 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. * * * */ 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); /* * <> * 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: */