Index: doc/wm.n ================================================================== --- doc/wm.n +++ doc/wm.n @@ -92,11 +92,36 @@ value accepted by \fBTk_GetColor\fR. If the empty string is specified (default), no transparent color is used. This is supported on Windows 2000/XP+. Where not supported, the \fB\-transparentcolor\fR value remains at \fB{}\fR. .PP -On Mac OS X, the following attributes may be set. +On MacOS, the following attributes may be set. +.TP +\fB\-appearance\fR +. +Specifies whether the window is rendered in "dark mode". Allowed +values are \fBauto\fR, \fBaqua\fR and \fBdarkaqua\fR. If the setting +is auto then the appearance of the window is controlled by the +System Settings. +.TP +\fB\-class\fR +. +Specifies whether the underlying Aqua window for a toplevel is an +object of the NSWindow class or the NSPanel class. The two allowed +values for this option are \fBnswindow\fR and \fBnspanel\fR. It is +not possible to change the class of the underlying Aqua window once +that window has been instantiated, and attempting to do that is an +error. However it is allowed to set this option for a pathname that +does not correspond to an existing window. Doing that causes the +class name to be cached for later use. When a toplevel with that +pathname is eventually created, the cached class name will determine which +class is used for the underlying Aqua window. +.TP +\fB\-isdark\fR +. +Returns a boolean value which is true if the window is currently in +dark mode. .TP \fB\-modified\fR . Specifies the modification state of the window (determines whether the window close widget contains the modification indicator and whether the @@ -104,14 +129,53 @@ .TP \fB\-notify\fR . Specifies process notification state (bouncing of the application dock icon). .TP +\fB\-stylemask\fR +. +Specifies an integer to be assigned as the styleMask of the underlying +Aqua window. (See the Apple documentation for styleMask property of +the NSWindow class.) The value of this option should be a list of +bitnames. Each bit named in the list will be set to 1, and all other +bits will be set to 0. The allowed bitnames are: \fBtitled\fR, +\fBclosable\fR, \fBminiaturizable\fR, \fBresizable\fR, +\fBfullsizecontentview\fR, \fBdocmodal\fR, \fButility\fR, +\fBnonactivatingpanel\fR, and \fBHUDwindow\fR. Note that a side +effect of setting the fullsizecontentview bit is that the window title +bar becomes transparent. +.TP +\fB\-tabbingid\fR +. +Controls how tabbed toplevel windows are grouped together. Two tabs +in the same group must correspond to toplevels with the same +tabbingid, which can be an arbitrary UTF8 string. In the Tk +implementation, changing the tabbingid of a toplevel in a tab group +will cause it to be moved into a different group, in which all tabs +have the new tabbingid or, if the new tabbingid is unique, to become +a normal non-tabbed toplevel. It is allowed to set the tabbingid before the +toplevel is created. If the pathname provided in the command does not +correspond to a toplevel, the value will be cached and used later when +the toplevel is actually created. +.TP +\fB\-tabbingmode\fR +. +Controls whether a toplevel can be opened as a tab within a tab group. +The allowed values are \fBauto\fR, \fBpreferred\fR or +\fBdisallowed\fR. A toplevel can be opened as a tab in exactly two +situations: if its tabbingmode is \fBpreferred\fR; or if its +tabbingmode is \fBauto\fR and the user has selected "prefer tabs when +opening documents" in the Desktop and Dock panel of the System +Settings application. It is allowed to set the tabbingmode before the +toplevel is created. If the pathname provided in the command does not +correspond to a toplevel, the value will be cached and used later when +the toplevel is actually created. +.TP \fB\-titlepath\fR . -Specifies the path of the file referenced as the window proxy icon (which -can be dragged and dropped in lieu of the file's finder icon). +Specifies the path of the file referenced as the window proxy icon +(which can be dragged and dropped in lieu of the file's finder icon). .TP \fB\-transparent\fR . Makes the window content area transparent and turns off the window shadow. For the transparency to be effective, the toplevel background needs to be set to a Index: generic/tkConfig.c ================================================================== --- generic/tkConfig.c +++ generic/tkConfig.c @@ -2027,22 +2027,24 @@ if (!(optionPtr->specPtr->flags & (TK_OPTION_NULL_OK|TCL_NULL_OK)) || *((int *) internalPtr) != INT_MIN) { objPtr = Tcl_NewWideIntObj(*((int *)internalPtr)); } break; case TK_OPTION_INDEX: - if (*((int *) internalPtr) == INT_MIN) { - objPtr = TkNewIndexObj(TCL_INDEX_NONE); - } else if (*((int *) internalPtr) == INT_MAX) { - objPtr = Tcl_NewStringObj("end+1", TCL_INDEX_NONE); - } else if (*((int *) internalPtr) == -1) { - objPtr = Tcl_NewStringObj("end", TCL_INDEX_NONE); - } else if (*((int *) internalPtr) < 0) { - char buf[32]; - snprintf(buf, 32, "end%d", 1 + *((int *) internalPtr)); - objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); - } else { - objPtr = Tcl_NewWideIntObj(*((int *) internalPtr)); + if (!(optionPtr->specPtr->flags & (TK_OPTION_NULL_OK|TCL_NULL_OK)) || *((int *) internalPtr) != INT_MIN) { + if (*((int *) internalPtr) == INT_MIN) { + objPtr = TkNewIndexObj(TCL_INDEX_NONE); + } else if (*((int *) internalPtr) == INT_MAX) { + objPtr = Tcl_NewStringObj("end+1", TCL_INDEX_NONE); + } else if (*((int *) internalPtr) == -1) { + objPtr = Tcl_NewStringObj("end", TCL_INDEX_NONE); + } else if (*((int *) internalPtr) < 0) { + char buf[32]; + snprintf(buf, 32, "end%d", 1 + *((int *) internalPtr)); + objPtr = Tcl_NewStringObj(buf, TCL_INDEX_NONE); + } else { + objPtr = Tcl_NewWideIntObj(*((int *) internalPtr)); + } } break; case TK_OPTION_DOUBLE: if (!(optionPtr->specPtr->flags & (TK_OPTION_NULL_OK|TCL_NULL_OK)) || !isnan(*((double *) internalPtr))) { objPtr = Tcl_NewDoubleObj(*((double *) internalPtr)); Index: library/demos/mac_styles.tcl ================================================================== --- library/demos/mac_styles.tcl +++ library/demos/mac_styles.tcl @@ -32,12 +32,12 @@ image create nsimage starry1 -source $starryImg -as file -width 96 -radius 10 -pressed 1 image create nsimage starry2 -source $starryImg -as file -width 96 -radius 10 -ring 3 image create nsimage field -source $fieldImg -as file -width 96 -radius 10 image create nsimage field1 -source $fieldImg -as file -width 96 -radius 10 -pressed 1 image create nsimage field2 -source $fieldImg -as file -width 96 -radius 10 -ring 3 -image create nsimage add -source NSAddTemplate -width 11 -height 11 -image create nsimage remove -source NSRemoveTemplate -width 11 -height 11 +image create nsimage add -source NSAddTemplate -width 20 -height 20 +image create nsimage remove -source NSRemoveTemplate -width 18 -height 4 # Off state and variables for checkbuttons and radio buttons set off {!selected !alternate} variable $w.checkVar variable $w.radioVar @@ -128,13 +128,13 @@ -image {bonjour pressed bonjour1}] set feather [ttk::button $buttonFrame.feather -style ImageButton -text Tk \ -image {tkfeather pressed tkfeather1}] set gradient [ttk::frame $buttonFrame.gradient] pack [ttk::button $buttonFrame.gradient.add -style GradientButton \ - -image add -padding 7] -side left + -image add -padding {2 0}] -side left pack [ttk::button $buttonFrame.gradient.remove -style GradientButton \ - -image remove -padding 7] -side left + -image remove -padding {2 8}] -side left set disclosure [ttk::checkbutton $buttonFrame.disclosure -style DisclosureButton] set help [ttk::button $buttonFrame.help -style HelpButton]; $check state $off $radio.r1 state $off @@ -238,24 +238,24 @@ grid $light -row 1 -column 0 -sticky e set dark [ttk::button $appearanceFrame.dark -style ImageButton -text Dark \ -image {starry pressed starry1 selected starry2} \ -command "beDark $appearanceFrame $w"] grid $dark -row 1 -column 2 -sticky w -if { [::tk::unsupported::MacWindowStyle isdark $w] } { +if { [wm attributes $w -isdark] } { $dark state selected } else { $light state selected } proc beLight {f w} { - ::tk::unsupported::MacWindowStyle appearance $w aqua + wm attributes $w -appearance aqua $f.dark state !selected $f.light state selected after 10 $f.light state !hover } proc beDark {f w} { - ::tk::unsupported::MacWindowStyle appearance $w darkaqua + wm attributes $w -appearance darkaqua $f.light state !selected $f.dark state selected after 10 $f.dark state !hover } $w.notebook add $appearanceFrame -text "Appearance" ADDED library/demos/mac_tabs.tcl Index: library/demos/mac_tabs.tcl ================================================================== --- /dev/null +++ library/demos/mac_tabs.tcl @@ -0,0 +1,77 @@ +# mac_tabs.tcl -- +# +# This demonstration script creates three tabbable windows and allows the +# wm attributes tabbingid and tabbingmode to be manipulated for the third +# window, to demonstrate the effects of those attributes. + +if {![info exists widgetDemo]} { + error "This script should be run from the \"widget\" demo." +} + +package require Tk +catch {font create giant -family {Times New Roman} -size 64} +set w .mac_tabs +catch {destroy $w} +toplevel $w +package require Tk +wm title $w "Tabbed Windows in Aqua" +wm iconname $w "mac_tabs" +positionWindow $w +set suffix 0 +set winlist {} +## + +## See Code / Dismiss +pack [addSeeDismiss $w.buttons $w] -side bottom -fill x +## +set info "\ +This demo shows 3 toplevels, A, B, and C. \ +Each of these has tabbingmode set to preferred. \ +The tabbingid of Window A is groupA, the \ +tabbingid of Window B is groupB and the tabbingid \ +of Window C is groupC. Use the menubuttons below \ +to see the effect of changing the tabbingid and \ +tabbingmode attributes for Window C. \ +" +pack [message $w.info -text $info -width 300] +wm geometry $w +450+350 + +ttk::frame $w.f +menu $w.idmenu -tearoff 0 +foreach id {groupA groupB groupC} { + $w.idmenu add command -label $id \ + -command [list wm attributes $w.c -tabbingid $id] +} +menu $w.modemenu -tearoff 0 +foreach mode {auto preferred disallowed} { + $w.modemenu add command -label $mode \ + -command [list wm attributes $w.c -tabbingmode $mode] +} +ttk::menubutton $w.f.idbutton -menu $w.idmenu -text "tabbingid"\ + -direction below +grid $w.f.idbutton -row 0 -column 0 +ttk::menubutton $w.f.modebutton -menu $w.modemenu -text "tabbingmode"\ + -direction below +grid $w.f.modebutton -row 1 -column 0 +pack $w.f + +wm attributes $w.a -tabbingid groupA +wm attributes $w.a -tabbingmode preferred +toplevel $w.a +wm geometry $w.a +50+100 +wm title $w.a "Window A" +pack [ttk::label $w.a.l -text A -font giant] -padx 100 -pady 30 + +wm attributes $w.b -tabbingid groupB +wm attributes $w.b -tabbingmode preferred +toplevel $w.b +wm geometry $w.b +400+100 +wm title $w.b "Window B" +pack [ttk::label $w.b.l -text B -font giant] -padx 100 -pady 30 + +wm attributes $w.c -tabbingid groupC +wm attributes $w.c -tabbingmode preferred +toplevel $w.c +wm geometry $w.c +750+100 +wm title $w.c "Window C" +pack [ttk::label $w.c.l -text C -font giant] -padx 100 -pady 30 ADDED library/demos/mac_wm.tcl Index: library/demos/mac_wm.tcl ================================================================== --- /dev/null +++ library/demos/mac_wm.tcl @@ -0,0 +1,228 @@ +# mac_window_styles.tcl -- +# +# This demonstration script creates a toplevel window containing a notebook +# whose pages provide examples of the various mac-specific widgets that are +# provided via special values for the -style option. + +if {![info exists widgetDemo]} { + error "This script should be run from the \"widget\" demo." +} + +package require Tk + +set w .mac_wm +catch {destroy $w} +toplevel $w +package require Tk +wm title $w "Tk Aqua Window Styles" +wm iconname $w "mac_wm" +positionWindow $w +set suffix 0 +set winlist {} +## + +## See Code / Dismiss +pack [addSeeDismiss $w.buttons $w] -side bottom -fill x + +proc launch {name windowInfo class} { + if {[winfo exists $name]} { + wm deiconify $name + focus -force $name + return + } + wm attributes $name -class $class; toplevel $name + wm title $name $class + set f $name.f + ttk::frame $f + set t $f.t + text $t -background systemWindowBackgroundColor \ + -highlightcolor systemWindowBackgroundColor \ + -font systemDefaultFont\ + -wrap word -width 50 -height 6 + $t insert insert $windowInfo + $t configure -state disabled + grid columnconfigure $f 0 -weight 1 + grid $t -row 0 -column 0 -columnspan 2 -sticky NSEW + ttk::labelframe $f.stylemask -text "styleMask bits" + # titled + if {$class == "nswindow"} { + ttk::checkbutton $f.stylemask.titled -text titled -variable $name.titled \ + -command [list setbit $name $f.stylemask.titled titled] + $f.stylemask.titled state selected + grid $f.stylemask.titled -row 0 -column 0 -sticky w + } + # closable + ttk::checkbutton $f.stylemask.closable -text closable -variable $name.closable \ + -command [list setbit $name $f.stylemask.closable closable] + $f.stylemask.closable state selected + grid $f.stylemask.closable -row 1 -column 0 -sticky w + # miniaturizableable + ttk::checkbutton $f.stylemask.miniaturizable -text miniaturizable \ + -variable $name.miniaturizable \ + -command [list setbit $name $f.stylemask.miniaturizable miniaturizable] + if {$class == "nswindow"} { + $f.stylemask.miniaturizable state selected + } else { + $f.stylemask.miniaturizable state !alternate + } + grid $f.stylemask.miniaturizable -row 2 -column 0 -sticky w + # resizable + ttk::checkbutton $f.stylemask.resizable -text resizable -variable $name.resizable \ + -command [list setbit $name $f.stylemask.resizable resizable] + $f.stylemask.resizable state selected + grid $f.stylemask.resizable -row 3 -column 0 -sticky w + # docmodal + ttk::checkbutton $f.stylemask.docmodal -text docmodal -variable $name.docmodal \ + -command [list setbit $name $f.stylemask.docmodal docmodal] + $f.stylemask.docmodal state !alternate + grid $f.stylemask.docmodal -row 4 -column 0 -sticky w + + grid $f.stylemask -row 1 -column 0 + pack $name.f -side bottom -fill both -expand 1 -padx 16 -pady 16 +} + +set info "The command wm attributes window -stylemask ?bitnames? can \ +be used to modify bits in the stylemask property of the NSWindow or \ +NSPanel underlying a Tk Window. Changing these bits causes the \ +style of the window to change. This demo allows you to see the \ +effects of changing the bits. (Note that buttons in the title bar \ +can also be enabled or disabled with the ::tk::unsupported::MacWindowStyle \ +command.)" + +set panelInfo "A toplevel based on an NSPanel has a narrower title bar\ +than one based on an NSWindow. In addition the panel remains above all\ +windows on the screen, regardless of which app is active. These are\ +intended to be used as modal windows." + +set windowInfo "This is a standard Apple document window, based on an\ +NSWindow. It has a larger title bar and behaves normally with respect\ +to other windows from the same or another app." + +## background frame +set f $w.f +ttk::frame $f +set t $f.t +text $t -background systemWindowBackgroundColor \ + -highlightcolor systemWindowBackgroundColor \ + -font systemDefaultFont\ + -wrap word -width 50 -height 8 +$t insert insert $info +$t configure -state disabled +grid columnconfigure $f 0 -weight 1 +grid $t -row 0 -column 0 -columnspan 2 -sticky NSEW +ttk::labelframe $f.stylemask -text "styleMask" +grid $f.stylemask -row 1 -column 0 +grid [ttk::button $f.wbw -text "Open an NSWindow" -width 20 \ + -command [list launch .nswindow $windowInfo nswindow]] -row 2 -column 0 +grid [ttk::button $f.wbp -text "Open an NSPanel" -width 20 \ + -command [list launch .nspanel $panelInfo nspanel]] -row 3 -column 0 +grid [ttk::button $f.wbm -text "Open a modern window" -width 20 \ + -command launchModernWindow] -row 4 -column 0 +pack $w.f -side bottom -fill both -expand 1 -padx 16 -pady 16 + +proc setbit {win cb bitname} { + set state [$cb instate selected] + set bits [wm attributes $win -stylemask] + set index [lsearch $bits $bitname] + if {$index >= 0 && !$state} { + set bits [lreplace $bits $index $index] + } + if {$index < 0 && $state} { + lappend bits $bitname + } + wm attributes $win -stylemask $bits +} + +set aboutText \ +"Most of the apps which ship with a contemporary version of macOS \ +feature a window similar to this one, with a left sidebar that \ +allows selecting the content to be shown on the right hand side of \ +the window. These windows do not have a (visible) titlebar.\ +\n\nApps that use such windows include the Finder and the App Store as \ +well as Notes, Messages, Books, Maps and many others.\ +\n\nTo create a window like this one in Tk simply set the fullsizecontent bit \ +in the stylemask. For example:\n\n" + +set aboutCode \ +"wm attributes .t -stylemask {titled \\\ +\nfullsizecontent closable miniaturizable \\\ +\nresizable}\n\n" + +set detailsText \ +"(1) In the Apple API, setting the fullsizecontent bit in the stylemask \ +only allows content to be drawn in the part of the window covered by \ +the titlebar. In order for that content to be visible the title bar \ +must be transparent. Since it would be pointless to draw content under \ +an opaque title bar, Tk makes the title bar transparent whenever the \ +fullsizecontent bit is set.\ + +\n\n\(2) Each radio button in the sidebar is a standard ttk::radiobutton \ +but created with a special value for its -style option. The value of the \ +-style option used to create these buttons is SidebarButton.\n" + +set whichPage 1 +trace add variable whichPage write "flipPage whichPage" +proc flipPage {varname args} { + global whichPage + set newpage [set $varname] + grid remove [grid content .mod.right -row 0 -column 0] + switch $newpage\ + 1 {grid .mod.right.about -padx 30 -pady 30 -row 0 -column 0 -sticky nsew}\ + 2 {grid .mod.right.details -padx 30 -pady 30 -row 0 -column 0 -sticky nsew} + update idletasks +} + +proc launchModernWindow {} { + global whichPage + global aboutText + global aboutCode + global detailsText + if {[winfo exists .mod]} { + wm deiconify .mod + focus -force .mod + return + } + toplevel .mod + wm title .mod {} + wm attributes .mod -stylemask {titled fullsizecontent closable \ + miniaturizable resizable} + .mod configure -background white + grid columnconfigure .mod 0 -weight 0 + grid columnconfigure .mod 1 -weight 1 + grid rowconfigure .mod 0 -weight 1 + frame .mod.left -width 220 -height 400 -background systemWindowBackgroundColor + catch { + font create leftFont -family .AppleSystemUIFont -size 11 + font create rightFont -family .AppleSystemUIFont -size 16 + font create codeFont -family Courier -size 16 + } + grid [ttk::label .mod.left.spacer -padding {220 30 0 0}] -row 0 -column 0 + grid [ttk::radiobutton .mod.left.about -text About -style SidebarButton \ + -variable whichPage -value 1] \ + -row 1 -column 0 -sticky nsew -padx 14 + grid [ttk::radiobutton .mod.left.details -text Details -style SidebarButton \ + -variable whichPage -value 2] \ + -row 2 -column 0 -sticky nsew -padx 14 + grid .mod.left -row 0 -column 0 -sticky nsew + frame .mod.right -width 500 -background systemTextBackgroundColor + grid rowconfigure .mod.right 0 -weight 0 + text .mod.right.about -highlightcolor systemTextBackgroundColor \ + -background systemTextBackgroundColor -font rightFont \ + -highlightthickness 0 -wrap word -width 40 + .mod.right.about tag configure code -font codeFont + .mod.right.about insert end $aboutText + .mod.right.about insert end $aboutCode code + .mod.right.about configure -state disabled + + text .mod.right.details -highlightcolor systemTextBackgroundColor \ + -background systemTextBackgroundColor -font rightFont\ + -highlightthickness 0 -wrap word -width 40 + .mod.right.details insert end $detailsText + .mod.right.details configure -state disabled + + grid .mod.right.about -padx 30 -pady 30 -row 0 -column 0 -sticky nsew + grid .mod.right -row 0 -column 1 -sticky nsew + wm geometry .mod 800x500 + update idletasks +} + Index: library/demos/widget ================================================================== --- library/demos/widget +++ library/demos/widget @@ -326,16 +326,23 @@ @@demo image1 Two labels displaying images @@demo image2 A simple user interface for viewing images @@demo labelframe Labelled frames @@demo ttkbut The simple Themed Tk widgets } + if {[tk windowingsystem] eq "aqua"} { addFormattedText { + @@subtitle Mac-Specific Widgets and Window Styles @@new @@demo mac_styles Special widgets for macOS + @@new + @@demo mac_wm Window styles for macOS + @@new + @@demo mac_tabs Tabbed Windows on macOS } } + addFormattedText { @@subtitle Listboxes and Trees @@demo states The 50 states @@demo colors Colors: change the color scheme for the application @@demo sayings A collection of famous and infamous sayings Index: library/ttk/aquaTheme.tcl ================================================================== --- library/ttk/aquaTheme.tcl +++ library/ttk/aquaTheme.tcl @@ -72,10 +72,24 @@ -font { selected RecessedFont active RecessedFont pressed RecessedFont } + + # Sidebar (radio) button + font create SidebarFont -family .AppleSystemUIFont -size 11 -weight normal + ttk::style configure SidebarButton \ + -foreground systemControlTextColor \ + -font SidebarFont + ttk::style map SidebarButton \ + -foreground { + {disabled selected} systemWindowBackgroundColor3 + {disabled !selected} systemDisabledControlTextColor + selected systemTextColor + active systemTextColor + pressed systemTextColor + } # For Entry, Combobox and Spinbox widgets the selected text background # is the "Highlight color" selected in preferences when the widget # has focus. It is a gray color when the widget does not have focus or # the window does not have focus. (The background state implies !focus Index: macosx/tkMacOSXMouseEvent.c ================================================================== --- macosx/tkMacOSXMouseEvent.c +++ macosx/tkMacOSXMouseEvent.c @@ -86,11 +86,11 @@ { NSWindow *eventWindow = [theEvent window]; NSEventType eventType = [theEvent type]; TKContentView *contentView = [eventWindow contentView]; NSPoint location = [theEvent locationInWindow]; - NSPoint viewLocation = [contentView convertPoint:location fromView:nil]; + //NSPoint viewLocation = [contentView convertPoint:location fromView:nil]; TkWindow *winPtr = NULL, *grabWinPtr, *scrollTarget = NULL; Tk_Window tkwin = NULL, capture, target; NSPoint local, global; NSInteger button; TkWindow *newFocus = NULL; @@ -115,11 +115,12 @@ * are recognized by having their timestamp set to 0. The other is for * motion events sent by the local event monitor, which will have their * window attribute set to nil. */ - if (![eventWindow isMemberOfClass:[TKWindow class]]) { + if (![eventWindow isMemberOfClass:[TKWindow class]] && + ![eventWindow isMemberOfClass:[TKPanel class]]) { if ([theEvent timestamp] == 0) { isTestingEvent = YES; eventWindow = [NSApp keyWindow]; } if (eventType == NSLeftMouseDragged || @@ -128,11 +129,11 @@ isMotionEvent = YES; } if (!isTestingEvent && !isMotionEvent) { return theEvent; } - } else if (!NSPointInRect(viewLocation, [contentView bounds])) { + } else if (!NSPointInRect(location, [contentView bounds])) { isOutside = YES; } button = [theEvent buttonNumber] + Button1; if ((button & -2) == Button2) { button ^= 1; /* Swap buttons 2/3 */ @@ -244,14 +245,14 @@ } if (!isTestingEvent) { NSRect bounds = [contentView bounds]; NSRect grip = NSMakeRect(bounds.size.width - 10, 0, 10, 10); bounds = NSInsetRect(bounds, 2.0, 2.0); - if (!NSPointInRect(viewLocation, bounds)) { + if (!NSPointInRect(location, bounds)) { return theEvent; } - if (NSPointInRect(viewLocation, grip)) { + if (NSPointInRect(location, grip)) { return theEvent; } if ([NSApp tkLiveResizeEnded]) { [NSApp setTkLiveResizeEnded:NO]; return theEvent; Index: macosx/tkMacOSXPrivate.h ================================================================== --- macosx/tkMacOSXPrivate.h +++ macosx/tkMacOSXPrivate.h @@ -463,10 +463,13 @@ { } @property Window tkWindow; @end +@interface TKPanel(TKWm) +- (void) tkLayoutChanged; +@end #pragma mark NSMenu & NSMenuItem Utilities @interface NSMenu(TKUtils) + (id)menuWithTitle:(NSString *)title; + (id)menuWithTitle:(NSString *)title menuItems:(NSArray *)items; Index: macosx/tkMacOSXSubwindows.c ================================================================== --- macosx/tkMacOSXSubwindows.c +++ macosx/tkMacOSXSubwindows.c @@ -401,11 +401,11 @@ if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { TKWindow *w = (TKWindow *)macWin->winPtr->wmInfoPtr->window; if (w) { if ([w styleMask] & NSFullScreenWindowMask) { - [w tkLayoutChanged]; + [(TKWindow *)w tkLayoutChanged]; } else { NSRect r = [w contentRectForFrameRect:[w frame]]; r.origin.y += r.size.height - height; r.size.width = width; Index: macosx/tkMacOSXWm.c ================================================================== --- macosx/tkMacOSXWm.c +++ macosx/tkMacOSXWm.c @@ -35,11 +35,11 @@ #define TK_MAC_DEBUG_WINDOWS #endif */ /* - * Window attributes and classes + * Carbon window attributes and classes. */ #define WM_NSMASK_SHIFT 36 #define tkWindowDoesNotHideAttribute \ ((UInt64) 1 << kHIWindowBitDoesNotHide) @@ -109,11 +109,12 @@ .styleMask = NSUtilityWindowMask, }, [kHelpWindowClass] = { .defaultAttrs = kWindowHideOnSuspendAttribute, .forceOnAttrs = kWindowNoTitleBarAttribute | kWindowDoesNotCycleAttribute, - .flags = WM_TOPMOST, }, + .flags = WM_TOPMOST, + .styleMask = 0}, [kSheetWindowClass] = { .validAttrs = kWindowResizableAttribute, .forceOnAttrs = kWindowNoTitleBarAttribute | kWindowDoesNotCycleAttribute, .styleMask = NSDocModalWindowMask, }, @@ -148,23 +149,165 @@ #define ForceAttributes(attributes, class) \ ((attributes) & (~macClassAttrs[(class)].forceOffAttrs | \ (macClassAttrs[(class)].forceOnAttrs & ~kWindowResizableAttribute))) /* - * Data for [wm attributes] command: + * Structures and data for the wm attributes command (macOS 10.13 and later): */ + +/* Hash tables for attributes which can be set before a window exists */ +static Tcl_HashTable pathnameToSubclass; +static Tcl_HashTable pathnameToTabbingId; +static Tcl_HashTable pathnameToTabbingMode; + +enum NSWindowSubclass { + subclassNSWindow = 0, + subclassNSPanel = 1 +}; +/* This array must be indexed by the enum above.*/ +static const char *subclassNames[] = {"nswindow", "nspanel", NULL}; + +typedef struct buttonField_t { + unsigned zoom: 1; + unsigned miniaturize: 1; + unsigned close: 1; +} buttonField; + +/* The order of these names must match the order of the bits above! */ +static const char *buttonNames[] = {"zoom", "miniaturize", "close", NULL}; + +typedef union windowButtonState_t { + int intvalue; + buttonField bits; +} windowButtonState; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 +enum NSWindowClass { + NSWindowClass_any = 0, + NSWindowClass_window = 1, + NSWindowClass_panel = 2 +}; +typedef struct styleMaskBit_t { + const char *bitname; + unsigned long bitvalue; + enum NSWindowClass allowed; +} styleMaskBit; + +static const styleMaskBit styleMaskBits[] = { + /* Make the titlebar visible and use round corners. */ + {"titled", NSWindowStyleMaskTitled, NSWindowClass_window}, + /* Enable the close button. */ + {"closable", NSWindowStyleMaskClosable, NSWindowClass_window}, + /* Enable the miniaturize button. */ + {"miniaturizable", NSWindowStyleMaskMiniaturizable, NSWindowClass_window}, + /* Allow the user to resize the window. */ + {"resizable", NSWindowStyleMaskResizable, NSWindowClass_window}, + /* + * Make the content view extend under the titlebar. We force + * titlebarAppearsTransparent when this bit is set. Otherwise it is + * pretty useless. + */ + {"fullsizecontentview", NSWindowStyleMaskFullSizeContentView, NSWindowClass_window}, + /* Rounded corners, cannot have a titlebar (overrides titled bit). */ + {"docmodal", NSWindowStyleMaskDocModalWindow, NSWindowClass_any}, + /* ============================================ + * The following bits are only valid for panels. + */ + /* Make the title bar thinner. */ + {"utility", NSWindowStyleMaskUtilityWindow, NSWindowClass_panel}, + /* Do not activate the app when the window is activated. */ + {"nonactivatingpanel", NSWindowStyleMaskNonactivatingPanel, NSWindowClass_panel}, + /* + * Requires utility. Cannot be resizable. Close button is an X; no other buttons. + * Cannot be a docmodal. + */ + {"HUDwindow", NSWindowStyleMaskHUDWindow, NSWindowClass_panel}, + {NULL, 0, 0} +}; + +typedef struct tabbingMode_t { + const char *modeName; + long modeValue; +} tabbingMode; + +static const tabbingMode tabbingModes[] = { + {"auto", NSWindowTabbingModeAutomatic}, + {"disallowed", NSWindowTabbingModeDisallowed}, + {"preferred", NSWindowTabbingModePreferred}, + {NULL, -1} +}; + +static const char *const appearanceStrings[] = { + "aqua", "auto", "darkaqua", NULL +}; +enum appearances { + APPEARANCE_AQUA, APPEARANCE_AUTO, APPEARANCE_DARKAQUA +}; + +static Bool wantsToBeTab(NSWindow *macWindow) { + Bool result; + switch ([macWindow tabbingMode]) { + case NSWindowTabbingModeDisallowed: + result = False; + break; + case NSWindowTabbingModePreferred: + result = True; + break; + case NSWindowTabbingModeAutomatic: + result = ([NSWindow userTabbingPreference] == + NSWindowUserTabbingPreferenceAlways); + break; + default: + result = False; + break; + } + return result; +} + +/* + * Helper for the tkLayoutChanged methods. Synchronizes Tk's understanding of + * the bounds of a contentView with the window's. It is needed because there + * are situations when the window manager can change the layout of an NSWindow + * without having been requested to do so by Tk. Examples are when a window + * goes FullScreen or shows a tab bar. NSWindow methods which involve such + * layout changes should be overridden or protected by methods which call this. + */ + +static void syncLayout(NSWindow *macWindow) +{ + TkWindow *winPtr = TkMacOSXGetTkWindow(macWindow); + + if (winPtr) { + // Using screen coordinates with origin at bottom left. + NSRect frameRect = [macWindow frame]; + // This accounts for the tab bar, if there is one. + NSRect contentRect = [macWindow contentRectForFrameRect: frameRect]; + WmInfo *wmPtr = winPtr->wmInfoPtr; + + // The parent includes the title bar, tab bar and window frame. + wmPtr->xInParent = frameRect.origin.x - contentRect.origin.x; + wmPtr->yInParent = (frameRect.origin.y + frameRect.size.height - + contentRect.origin.y - contentRect.size.height); + wmPtr->parentWidth = winPtr->changes.width + frameRect.size.width - + contentRect.size.width; + wmPtr->parentHeight = winPtr->changes.height + frameRect.size.height - + contentRect.size.height; + } +} +#endif typedef enum { - WMATT_ALPHA, WMATT_FULLSCREEN, WMATT_MODIFIED, WMATT_NOTIFY, - WMATT_TITLEPATH, WMATT_TOPMOST, WMATT_TRANSPARENT, - WMATT_TYPE, _WMATT_LAST_ATTRIBUTE + WMATT_ALPHA, WMATT_APPEARANCE, WMATT_BUTTONS, WMATT_FULLSCREEN, + WMATT_ISDARK, WMATT_MODIFIED, WMATT_NOTIFY, WMATT_TITLEPATH, WMATT_TOPMOST, + WMATT_TRANSPARENT, WMATT_STYLEMASK, WMATT_CLASS, WMATT_TABBINGID, + WMATT_TABBINGMODE, WMATT_TYPE, _WMATT_LAST_ATTRIBUTE } WmAttribute; static const char *const WmAttributeNames[] = { - "-alpha", "-fullscreen", "-modified", "-notify", - "-titlepath", "-topmost", "-transparent", - "-type", NULL + "-alpha", "-appearance", "-buttons", "-fullscreen", "-isdark", "-modified", + "-notify", "-titlepath", "-topmost", "-transparent", "-stylemask", "-class", + "-tabbingid", "-tabbingmode", "-type", NULL }; /* * The variable below is used to enable or disable tracing in this module. If * tracing is enabled, then information is printed on standard output about @@ -191,11 +334,11 @@ */ static int tkMacOSXWmAttrNotifyVal = 0; /* - * Forward declarations for procedures defined in this file: + * Declarations of static functions defined in this file: */ static NSRect InitialWindowBounds(TkWindow *winPtr, NSWindow *macWindow); static int ParseGeometry(Tcl_Interp *interp, char *string, @@ -307,12 +450,10 @@ Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); static void WmUpdateGeom(WmInfo *wmPtr, TkWindow *winPtr); static int WmWinStyle(Tcl_Interp *interp, TkWindow *winPtr, int objc, Tcl_Obj *const objv[]); -static int WmWinTabbingId(Tcl_Interp *interp, TkWindow *winPtr, - int objc, Tcl_Obj *const objv[]); static int WmWinAppearance(Tcl_Interp *interp, TkWindow *winPtr, int objc, Tcl_Obj *const objv[]); static void ApplyWindowAttributeFlagChanges(TkWindow *winPtr, NSWindow *macWindow, UInt64 oldAttributes, int oldFlags, int create, int initial); @@ -323,10 +464,94 @@ static void GetMaxSize(TkWindow *winPtr, int *maxWidthPtr, int *maxHeightPtr); static void RemapWindows(TkWindow *winPtr, MacDrawable *parentWin); static void RemoveTransient(TkWindow *winPtr); + +#if MAC_OS_X_VERSION_MAX_ALLOWED > 101300 + +/* + * Add a window as a tab in the group specified by its tabbingid, or + * make it a standalone window if it is the only window with that + * tabbingid. Adjust the window size if a tab bar appeared or + * disappeared. + */ + +static void placeAsTab(TKWindow *macWindow) { + TkWindow *winPtr = NULL, *winPtr2 = NULL; + TKWindow *target = NULL, *sibling = NULL; + NSString *identifier = [macWindow tabbingIdentifier]; + if (!wantsToBeTab(macWindow)) { + [macWindow moveTabToNewWindow:NSApp]; + [(TKWindow *)target tkLayoutChanged]; + return; + } + for (NSWindow *window in [NSApp windows]) { + if (window == macWindow) { + continue; + } + if ([identifier isEqualTo: [window tabbingIdentifier]] && + wantsToBeTab(window)) { + target = (TKWindow*) window; + syncLayout(target); + break; + } + } + syncLayout(macWindow); + NSArray *tabs = [macWindow tabbedWindows]; + if ([tabs count] == 2) { + sibling = tabs[0] == macWindow ? (TKWindow *)tabs[1] : (TKWindow *)tabs[0]; + syncLayout(sibling); + winPtr2 = TkMacOSXGetTkWindow(sibling); + } + if (target) { + CGFloat winHeight = [macWindow contentRectForFrameRect: + [macWindow frame]].size.height; + CGFloat winDelta = 0, targetHeight, targetDelta = 0; + targetHeight = [target contentRectForFrameRect: + [target frame]].size.height; + [target addTabbedWindow:macWindow ordered:NSWindowAbove]; + targetDelta = targetHeight - [target contentRectForFrameRect: + [target frame]].size.height; + winDelta = winHeight - [target contentRectForFrameRect: + [target frame]].size.height; + if (winDelta) { + winPtr = TkMacOSXGetTkWindow(macWindow); + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + winPtr->changes.width, winPtr->changes.height + winDelta ); + if (sibling) { + winPtr = TkMacOSXGetTkWindow(sibling); + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + winPtr->changes.width, winPtr->changes.height - winDelta ); + } + } + if (targetDelta) { + winPtr = TkMacOSXGetTkWindow(target); + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + winPtr->changes.width, winPtr->changes.height + targetDelta ); + } + } else { + CGFloat height = [macWindow contentRectForFrameRect: + [macWindow frame]].size.height; + [macWindow moveTabToNewWindow:NSApp]; + CGFloat delta = height - [macWindow contentRectForFrameRect: + [macWindow frame]].size.height; + winPtr = TkMacOSXGetTkWindow(macWindow); + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + winPtr->changes.width, winPtr->changes.height + delta); + if (winPtr2) { + XMoveResizeWindow(winPtr2->display, winPtr2->window, + winPtr2->changes.x, winPtr2->changes.y, + winPtr2->changes.width, winPtr2->changes.height + delta ); + } + } +} +#endif #pragma mark NSWindow(TKWm) @implementation NSWindow(TKWm) @@ -343,23 +568,29 @@ - (NSPoint) tkConvertPointToScreen: (NSPoint) point { NSRect pointrect = {point, {0,0}}; return [self convertRectToScreen:pointrect].origin; } + - (NSPoint) tkConvertPointFromScreen: (NSPoint)point { NSRect pointrect = {point, {0,0}}; return [self convertRectFromScreen:pointrect].origin; } #endif - @end #pragma mark - @implementation TKPanel: NSPanel @synthesize tkWindow = _tkWindow; + +- (void) tkLayoutChanged +{ + syncLayout(self); +} + @end @implementation TKDrawerWindow: NSWindow @synthesize tkWindow = _tkWindow; @end @@ -370,48 +601,16 @@ #pragma mark TKWindow(TKWm) @implementation TKWindow(TKWm) -/* - * This method synchronizes Tk's understanding of the bounds of a contentView - * with the window's. It is needed because there are situations when the - * window manager can change the layout of an NSWindow without having been - * requested to do so by Tk. Examples are when a window goes FullScreen or - * shows a tab bar. NSWindow methods which involve such layout changes should - * be overridden or protected by methods which call this. - */ - - (void) tkLayoutChanged { - TkWindow *winPtr = TkMacOSXGetTkWindow(self); - - if (winPtr) { - NSRect frameRect; - - /* - * This avoids including the title bar for full screen windows - * but does include it for normal windows. - */ - - if ([self styleMask] & NSFullScreenWindowMask) { - frameRect = [NSWindow frameRectForContentRect:NSZeroRect - styleMask:[self styleMask]]; - } else { - frameRect = [self frameRectForContentRect:NSZeroRect]; - } - - WmInfo *wmPtr = winPtr->wmInfoPtr; - - wmPtr->xInParent = -frameRect.origin.x; - wmPtr->yInParent = frameRect.origin.y + frameRect.size.height; - wmPtr->parentWidth = winPtr->changes.width + frameRect.size.width; - wmPtr->parentHeight = winPtr->changes.height + frameRect.size.height; - } -} - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 + syncLayout(self); +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 - (void)toggleTabBar:(id)sender { TkWindow *winPtr = TkMacOSXGetTkWindow(self); if (!winPtr) { return; @@ -724,10 +923,12 @@ wmPtr->attributes = macClassAttrs[kDocumentWindowClass].defaultAttrs; wmPtr->scrollWinPtr = NULL; wmPtr->menuPtr = NULL; wmPtr->window = nil; winPtr->wmInfoPtr = wmPtr; + + // initialize wmPtr->NSWindowSubclass here UpdateVRootGeometry(wmPtr); /* * Tk must monitor structure events for top-level windows, in order to @@ -1207,20 +1408,43 @@ if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings, sizeof(char *), "option", 0, &index) != TCL_OK) { return TCL_ERROR; } - if (objc < 3) { goto wrongNumArgs; } - - if (TkGetWindowFromObj(interp, tkwin, objv[2], (Tk_Window *) &winPtr) - != TCL_OK) { + if (index == WMOPT_ATTRIBUTES && objc == 5 && + strcmp(Tcl_GetString(objv[3]), "-class") == 0) { + if (TkGetWindowFromObj(NULL, tkwin, objv[2], (Tk_Window *) &winPtr) + == TCL_OK) { + if (winPtr->wmInfoPtr->window != NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Cannot change the class after the mac window is created.",-1)); + Tcl_SetErrorCode(interp, "TK", "CLASS_CHANGE", NULL); + return TCL_ERROR; + } + } else { + winPtr = NULL; + } + } else if (index == WMOPT_ATTRIBUTES && objc == 5 && + strcmp(Tcl_GetString(objv[3]), "-tabbingid") == 0) { + if (TkGetWindowFromObj(NULL, tkwin, objv[2], (Tk_Window *) &winPtr) + != TCL_OK) { + winPtr = NULL; + } + } else if (index == WMOPT_ATTRIBUTES && objc == 5 && + strcmp(Tcl_GetString(objv[3]), "-tabbingmode") == 0) { + if (TkGetWindowFromObj(NULL, tkwin, objv[2], (Tk_Window *) &winPtr) + != TCL_OK) { + winPtr = NULL; + } + } else if (TkGetWindowFromObj(interp, tkwin, objv[2], (Tk_Window *) &winPtr) + != TCL_OK) { return TCL_ERROR; } - if (!Tk_IsTopLevel(winPtr) + if (winPtr && !Tk_IsTopLevel(winPtr) && (index != WMOPT_MANAGE) && (index != WMOPT_FORGET)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "window \"%s\" isn't a top-level window", winPtr->pathName)); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TOPLEVEL", winPtr->pathName, NULL); @@ -1396,10 +1620,11 @@ WmAttribute attribute, /* Code of attribute to set */ Tcl_Obj *value) /* New value */ { WmInfo *wmPtr = winPtr->wmInfoPtr; int boolean; + NSString *identifier; switch (attribute) { case WMATT_ALPHA: { double dval; @@ -1416,10 +1641,62 @@ } else if (dval > 1.0) { dval = 1.0; } [macWindow setAlphaValue:dval]; break; + } + case WMATT_APPEARANCE: { + int index; + if (Tcl_GetIndexFromObjStruct(interp, value, appearanceStrings, + sizeof(char *), "appearancename", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum appearances) index) { + case APPEARANCE_AQUA: + macWindow.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameAqua]; + break; + case APPEARANCE_DARKAQUA: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + if (@available(macOS 10.14, *)) { + macWindow.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameDarkAqua]; + } +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + break; + default: + macWindow.appearance = nil; + } + break; + } + case WMATT_BUTTONS: { + windowButtonState state = {0}; + Tcl_Obj **elements; + Tcl_Size nElements, i; + if (Tcl_ListObjGetElements(interp, value, &nElements, &elements) == TCL_OK) { + int index = 0; + for (i = 0; i < nElements; i++) { + if (Tcl_GetIndexFromObjStruct(interp, elements[i], buttonNames, + sizeof(char *), "window button name", 0, &index) != TCL_OK) { + return TCL_ERROR; + } else { + state.intvalue |= (1 << index); + } + } + } else if (Tcl_GetIntFromObj(interp, value, &state.intvalue) != TCL_OK) { + return TCL_ERROR; + } + NSButton *closer = [macWindow standardWindowButton: + NSWindowCloseButton]; + NSButton *miniaturizer = [macWindow standardWindowButton: + NSWindowMiniaturizeButton]; + NSButton *zoomer = [macWindow standardWindowButton: + NSWindowZoomButton]; + closer.enabled = (state.bits.close != 0); + miniaturizer.enabled = (state.bits.miniaturize != 0); + zoomer.enabled = (state.bits.zoom != 0); + break; } case WMATT_FULLSCREEN: if (Tcl_GetBooleanFromObj(interp, value, &boolean) != TCL_OK) { return TCL_ERROR; } @@ -1454,10 +1731,125 @@ request = [NSApp requestUserAttention:NSCriticalRequest]; } tkMacOSXWmAttrNotifyVal = boolean; } break; + case WMATT_STYLEMASK: { + unsigned long styleMaskValue = 0; + Tcl_Obj **elements; + Tcl_Size nElements, i; + if (Tcl_ListObjGetElements(interp, value, &nElements, &elements) == TCL_OK) { + int index; + for (i = 0; i < nElements; i++) { + if (Tcl_GetIndexFromObjStruct(interp, elements[i], styleMaskBits, + sizeof(styleMaskBit), "styleMask bit", 0, &index) != TCL_OK) { + return TCL_ERROR; + } else if (![macWindow isKindOfClass: [NSPanel class]] && + styleMaskBits[index].allowed == NSWindowClass_panel) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "styleMask bit \"%s\" can only be used with an NSPanel", + styleMaskBits[index].bitname)); + Tcl_SetErrorCode(interp, "TK", "INVALID_STYLEMASK_BIT", NULL); + return TCL_ERROR; + } else { + styleMaskValue |= styleMaskBits[index].bitvalue; + } + /* + * Be sure not to change the fullscreen bit. + */ + styleMaskValue |= (NSWindowStyleMaskFullScreen & macWindow.styleMask); + } + /* + * A resizable docmodal NSWindow or NSPanel does not work + * correctly. It cannot be resized from the top edge. Other bits, + * such as titled are ignored for docmodals. To be safe, we clear + * all other bits when the docmodal bit is set. + */ + if (styleMaskValue & NSDocModalWindowMask) { + styleMaskValue &= ~NSWindowStyleMaskResizable; + } + if ([macWindow isKindOfClass: [NSPanel class]]) { + /* + * We always make NSPanels titled, nonactivating utility windows, + * even if these bits are not requested in the command. + */ + if (!(styleMaskValue & NSWindowStyleMaskTitled) ) { + styleMaskValue |= NSWindowStyleMaskTitled; + styleMaskValue |= NSWindowStyleMaskUtilityWindow; + styleMaskValue |= NSWindowStyleMaskNonactivatingPanel; + } + } + if (styleMaskValue & NSWindowStyleMaskFullSizeContentView) { + macWindow.titlebarAppearsTransparent = YES; + } else { + macWindow.titlebarAppearsTransparent = NO; + } + } else { + return TCL_ERROR; + } + NSRect oldFrame = [macWindow frame]; +#ifdef DEBUG + fprintf(stderr, "Current styleMask: %lx\n", [macWindow styleMask]); + fprintf(stderr, "Setting styleMask to %lx\n", styleMaskValue); +#endif + macWindow.styleMask = (unsigned long) styleMaskValue; + NSRect newFrame = [macWindow frame]; + int heightDiff = newFrame.size.height - oldFrame.size.height; + int newHeight = heightDiff < 0 ? newFrame.size.height : + newFrame.size.height - heightDiff; + [(TKWindow *)macWindow tkLayoutChanged]; + if (heightDiff) { + //Calling XMoveResizeWindow twice is a hack to force a relayout + //of the window. + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + newFrame.size.width, newHeight - 1); + XMoveResizeWindow(winPtr->display, winPtr->window, + winPtr->changes.x, winPtr->changes.y, + newFrame.size.width, newHeight); + } + break; + } + case WMATT_TABBINGID: { + NSString *oldId = [macWindow tabbingIdentifier]; + char *valueString; + int length; + if ([NSApp macOSVersion] < 101300) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Tabbing identifiers require macOS 10.13", TCL_INDEX_NONE)); + Tcl_SetErrorCode(interp, "TK", "WM", "TABBINGID", NULL); + return TCL_ERROR; + } + valueString = Tcl_GetStringFromObj(value, &length); + identifier = [NSString stringWithUTF8String:valueString]; + [macWindow setTabbingIdentifier: identifier]; + + /* + * If the tabbingIdentifier of a tab is changed we move it into + * the tab group with that identifier. + */ + + if ([oldId compare:identifier] != NSOrderedSame) { + placeAsTab((TKWindow *)macWindow); + } + break; + } + case WMATT_TABBINGMODE: { + int index; + tabbingMode mode; + if (Tcl_GetIndexFromObjStruct(interp, value, tabbingModes, + sizeof(tabbingMode), "NSWindow Tabbing Mode", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + mode = tabbingModes[index]; + [macWindow setTabbingMode: mode.modeValue]; + placeAsTab((TKWindow *)macWindow); + break; + } + case WMATT_ISDARK: { + break; + } case WMATT_TITLEPATH: { const char *path = (const char *)Tcl_FSGetNativePath(value); NSString *filename = @""; if (path && *path) { @@ -1503,10 +1895,12 @@ [macWindow setOpaque:!boolean]; TkMacOSXInvalidateWindow((MacDrawable *)winPtr->window, TK_PARENT_WINDOW); } break; + case WMATT_CLASS: + break; case WMATT_TYPE: TKLog(@"The type attribute is ignored on macOS."); break; case _WMATT_LAST_ATTRIBUTE: default: @@ -1536,20 +1930,93 @@ WmInfo *wmPtr = winPtr->wmInfoPtr; switch (attribute) { case WMATT_ALPHA: result = Tcl_NewDoubleObj([macWindow alphaValue]); + break; + case WMATT_APPEARANCE: { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + NSAppearanceName appearance; +#else + NSString *appearance; +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 + const char *resultString = "unrecognized"; + appearance = macWindow.appearance.name; + if (appearance == nil) { + resultString = appearanceStrings[APPEARANCE_AUTO]; + } else if (appearance == NSAppearanceNameAqua) { + resultString = appearanceStrings[APPEARANCE_AQUA]; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + } else if (@available(macOS 10.14, *)) { + if (appearance == NSAppearanceNameDarkAqua) { + resultString = appearanceStrings[APPEARANCE_DARKAQUA]; + } +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + } + result = Tcl_NewStringObj(resultString, TCL_INDEX_NONE); + break; + } + case WMATT_BUTTONS: { + result = Tcl_NewListObj(3, NULL); + if ([macWindow standardWindowButton:NSWindowCloseButton].enabled) { + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj("close", TCL_INDEX_NONE)); + } + if ([macWindow standardWindowButton:NSWindowMiniaturizeButton].enabled) { + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj("miniaturize", TCL_INDEX_NONE)); + } + if ([macWindow standardWindowButton:NSWindowZoomButton].enabled) { + Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj("zoom", TCL_INDEX_NONE)); + } + break; + } + case WMATT_CLASS: + if ([macWindow isKindOfClass:[NSPanel class]]) { + result = Tcl_NewStringObj(subclassNames[subclassNSPanel], TCL_INDEX_NONE); + } else { + result = Tcl_NewStringObj(subclassNames[subclassNSWindow], TCL_INDEX_NONE); + } break; case WMATT_FULLSCREEN: result = Tcl_NewBooleanObj([macWindow styleMask] & NSFullScreenWindowMask); break; + case WMATT_ISDARK: + result = Tcl_NewBooleanObj(TkMacOSXInDarkMode((Tk_Window)winPtr)); + break; case WMATT_MODIFIED: result = Tcl_NewBooleanObj([macWindow isDocumentEdited]); break; case WMATT_NOTIFY: result = Tcl_NewBooleanObj(tkMacOSXWmAttrNotifyVal); break; + case WMATT_STYLEMASK: { + unsigned long styleMaskValue = [macWindow styleMask]; + const styleMaskBit *bit; + result = Tcl_NewListObj(9, NULL); + for (bit = styleMaskBits; bit->bitname != NULL; bit++) { + if (styleMaskValue & bit->bitvalue) { + Tcl_ListObjAppendElement(NULL, result, + Tcl_NewStringObj(bit->bitname, TCL_INDEX_NONE)); + } + } + break; + } + case WMATT_TABBINGID: + result = Tcl_NewStringObj([[macWindow tabbingIdentifier] UTF8String], + -1); + break; + case WMATT_TABBINGMODE: { + long mode = [macWindow tabbingMode]; + const char *name = "unrecognized"; + for (const tabbingMode *m = tabbingModes; m->modeName != NULL; m++) { + if (m->modeValue == mode) { + name = m->modeName; + break; + } + } + result = Tcl_NewStringObj(name, TCL_INDEX_NONE); + break; + } case WMATT_TITLEPATH: result = Tcl_NewStringObj([[macWindow representedFilename] UTF8String], TCL_INDEX_NONE); break; case WMATT_TOPMOST: @@ -1585,20 +2052,70 @@ *---------------------------------------------------------------------- */ static int WmAttributesCmd( - TCL_UNUSED(Tk_Window), /* Main window of the application. */ + TCL_UNUSED(Tk_Window), /* Main window of the application. */ TkWindow *winPtr, /* Toplevel to work with */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int attribute = 0; NSWindow *macWindow; + if (winPtr == NULL && objc == 5) { + int index, isNew = 0, length; - if (winPtr->window == None) { + /* + * If we are setting an atttribute of a future window, save the value + * in a hash table so we can look it up when the window is actually + * created. + */ + + if (strcmp(Tcl_GetString(objv[3]), "-class") == 0) { + if (Tcl_GetIndexFromObjStruct(interp, objv[4], subclassNames, + sizeof(char *), "NSWindow subclass", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&pathnameToSubclass, + Tcl_GetString(objv[2]), &isNew); + if (hPtr) { + Tcl_SetHashValue(hPtr, INT2PTR(index)); + return TCL_OK; + } + } else if (strcmp(Tcl_GetString(objv[3]), "-tabbingid") == 0) { + char *identifier = Tcl_GetStringFromObj(objv[4], &length); + char *value = ckalloc(length + 1); + strncpy(value, identifier, length + 1); + Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&pathnameToTabbingId, + Tcl_GetString(objv[2]), &isNew); + if (hPtr) { + Tcl_SetHashValue(hPtr, value); + return TCL_OK; + } + } else if (strcmp(Tcl_GetString(objv[3]), "-tabbingmode") == 0) { + long value = NSWindowTabbingModeAutomatic; + int modeIndex; + if (Tcl_GetIndexFromObjStruct(interp, objv[4], tabbingModes, + sizeof(tabbingMode), "NSWindow Tabbing Mode", 0, &modeIndex) == TCL_OK) { + value = tabbingModes[modeIndex].modeValue; + } + Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&pathnameToTabbingMode, + Tcl_GetString(objv[2]), &isNew); + if (hPtr) { + Tcl_SetHashValue(hPtr, value); + return TCL_OK; + } + } + } + if (!winPtr) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "Only -class, -tabbingid, or -tabbingmode can be set before the window exists.")); + Tcl_SetErrorCode(interp, "TK", "NO_WINDOW", NULL); + return TCL_ERROR; + } + if (winPtr && winPtr->window == None) { Tk_MakeWindowExist((Tk_Window)winPtr); } if (!TkMacOSXHostToplevelExists(winPtr)) { TkMacOSXMakeRealWindowExist(winPtr); } @@ -3095,11 +3612,11 @@ *---------------------------------------------------------------------- */ static int WmOverrideredirectCmd( - TCL_UNUSED(Tk_Window), /* Main window of the application. */ + TCL_UNUSED(Tk_Window), /* Main window of the application. */ TkWindow *winPtr, /* Toplevel to work with */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { @@ -3121,11 +3638,19 @@ if (Tcl_GetBooleanFromObj(interp, objv[3], &flag) != TCL_OK) { return TCL_ERROR; } atts.override_redirect = flag ? True : False; Tk_ChangeWindowAttributes((Tk_Window)winPtr, CWOverrideRedirect, &atts); - ApplyContainerOverrideChanges(winPtr, win); + if ([NSApp macOSVersion] >= 101300) { + if (flag) { + win.styleMask |= NSWindowStyleMaskDocModalWindow; + } else { + win.styleMask &= ~NSWindowStyleMaskDocModalWindow; + } + } else { + ApplyContainerOverrideChanges(winPtr, win); + } return TCL_OK; } /* *---------------------------------------------------------------------- @@ -5670,14 +6195,14 @@ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { - "appearance", "isdark", "style", "tabbingid", NULL + "appearance", "isdark", "style", NULL }; enum SubCmds { - TKMWS_APPEARANCE, TKMWS_ISDARK, TKMWS_STYLE, TKMWS_TABID + TKMWS_APPEARANCE, TKMWS_ISDARK, TKMWS_STYLE }; Tk_Window tkwin = (Tk_Window)clientData; TkWindow *winPtr; int index; @@ -5707,22 +6232,10 @@ if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "window ?class attributes?"); return TCL_ERROR; } return WmWinStyle(interp, winPtr, objc, objv); - case TKMWS_TABID: - if ([NSApp macOSVersion] < 101200) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "Tabbing identifiers did not exist until OSX 10.12.", TCL_INDEX_NONE)); - Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "TABBINGID", NULL); - return TCL_ERROR; - } - if ((objc < 3) || (objc > 4)) { - Tcl_WrongNumArgs(interp, 2, objv, "window ?newid?"); - return TCL_ERROR; - } - return WmWinTabbingId(interp, winPtr, objc, objv); case TKMWS_APPEARANCE: if ([NSApp macOSVersion] < 100900) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "Window appearances did not exist until OSX 10.9.", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "APPEARANCE", NULL); @@ -5938,91 +6451,10 @@ } /* *---------------------------------------------------------------------- * - * WmWinTabbingId -- - * - * This procedure is invoked to process the - * "::tk::unsupported::MacWindowStyle tabbingid" subcommand. The command - * allows you to get or set the tabbingIdentifier for the NSWindow - * associated with a Tk Window. The syntax is: - * - * tk::unsupported::MacWindowStyle tabbingid window ?newId? - * - * Results: - * Returns the tabbingIdentifier of the window prior to calling this - * function. If the optional newId argument is omitted, the window's - * tabbingIdentifier is not changed. - * - * Side effects: - * Windows may only be grouped together as tabs if they all have the same - * tabbingIdentifier. In particular, by giving a window a unique - * tabbingIdentifier one can prevent it from becoming a tab in any other - * window. Changing the tabbingIdentifier of a window which is already - * a tab causes it to become a separate window. - * - *---------------------------------------------------------------------- - */ - -static int -WmWinTabbingId( - Tcl_Interp *interp, /* Current interpreter. */ - TkWindow *winPtr, /* Window to be manipulated. */ - int objc, /* Number of arguments. */ - Tcl_Obj * const objv[]) /* Argument objects. */ -{ -#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 - (void) interp; - (void) winPtr; - (void) objc; - (void) objv; - return TCL_OK; -#else - Tcl_Obj *result = NULL; - NSString *idString; - NSWindow *win = TkMacOSXGetNSWindowForDrawable(winPtr->window); - if (win) { - idString = win.tabbingIdentifier; - result = Tcl_NewStringObj(idString.UTF8String, [idString length]); - } - if (result == NULL) { - NSLog(@"Failed to read tabbing identifier; try calling update idletasks" - " before getting/setting the tabbing identifier of the window."); - return TCL_OK; - } - Tcl_SetObjResult(interp, result); - if (objc == 3) { - return TCL_OK; - } else if (objc == 4) { - int len; - char *newId = Tcl_GetStringFromObj(objv[3], &len); - NSString *newIdString = [NSString stringWithUTF8String:newId]; - [win setTabbingIdentifier: newIdString]; - - /* - * If the tabbingIdentifier of a tab is changed we also turn it into a - * separate window so we don't violate the rule that all tabs in the - * same frame must have the same tabbingIdentifier. - */ - - if ([idString compare:newIdString] != NSOrderedSame -#if MAC_OS_X_VERSION_MIN_REQUIRED > 101200 - && [win tab] -#endif - ) { - [win moveTabToNewWindow:nil]; - } - return TCL_OK; - } - return TCL_ERROR; -#endif -} - -/* - *---------------------------------------------------------------------- - * * WmWinAppearance -- * * This procedure is invoked to process the * "::tk::unsupported::MacWindowStyle appearance" subcommand. The command * allows you to get or set the appearance for the NSWindow associated @@ -6061,16 +6493,10 @@ (void) winPtr; (void) objc; (void) objv; return TCL_OK; #else - static const char *const appearanceStrings[] = { - "aqua", "auto", "darkaqua", NULL - }; - enum appearances { - APPEARANCE_AQUA, APPEARANCE_AUTO, APPEARANCE_DARKAQUA - }; Tcl_Obj *result = NULL; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 NSAppearanceName appearance; #else NSString *appearance; @@ -6185,11 +6611,18 @@ TkWindow *winPtr) /* Tk window. */ { WmInfo *wmPtr = winPtr->wmInfoPtr; MacDrawable *macWin; WindowClass macClass; + Class winClass = nil; Bool overrideRedirect = Tk_Attributes((Tk_Window)winPtr)->override_redirect; + Tcl_HashEntry *hPtr = NULL; + NSUInteger styleMask; + NSString *identifier; + char *tabbingId = NULL; + long tabbingMode = NSWindowTabbingModeAutomatic; + static int initialized = 0; if (TkMacOSXHostToplevelExists(winPtr)) { return; } @@ -6216,55 +6649,121 @@ /* * TODO: Here we should handle out of process embedding. */ } - /* - * If this is an override-redirect window, the NSWindow is created first as - * a document window then converted to a simple window. - */ - - if (overrideRedirect) { - wmPtr->macClass = kDocumentWindowClass; - } - macClass = wmPtr->macClass; - wmPtr->attributes &= (tkAlwaysValidAttributes | - macClassAttrs[macClass].validAttrs); - wmPtr->flags |= macClassAttrs[macClass].flags | + if ([NSApp macOSVersion] >= 101300) { + /* + * Prior to macOS 10.12 the styleMask was readonly. From macOS 10.12 + * onward, the styleMask can replace the Carbon window classes and + * attributes. + */ + int index; + if (!initialized) { + Tcl_InitHashTable(&pathnameToSubclass, TCL_STRING_KEYS); + Tcl_InitHashTable(&pathnameToTabbingId, TCL_STRING_KEYS); + Tcl_InitHashTable(&pathnameToTabbingMode, TCL_STRING_KEYS); + initialized = 1; + } + hPtr = Tcl_FindHashEntry(&pathnameToSubclass, Tk_PathName(winPtr)); + index = hPtr ? PTR2INT(Tcl_GetHashValue(hPtr)) : subclassNSWindow; + switch(index) { + case subclassNSPanel: + winClass = [TKPanel class]; + styleMask = (NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskResizable | + NSWindowStyleMaskUtilityWindow | + NSWindowStyleMaskNonactivatingPanel ); + break; + default: + winClass = [TKWindow class]; + styleMask = (NSWindowStyleMaskTitled | + NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskResizable ); + break; + } + if (overrideRedirect) { + styleMask |= NSWindowStyleMaskDocModalWindow; + } + /* Help windows (used for tooltips) should have stylemask 0. */ + if (wmPtr->macClass == kHelpWindowClass) { + styleMask = 0; + } + if (hPtr) { + Tcl_DeleteHashEntry(hPtr); + } + hPtr = Tcl_FindHashEntry(&pathnameToTabbingId, Tk_PathName(winPtr)); + if (hPtr) { + tabbingId = Tcl_GetHashValue(hPtr); + Tcl_DeleteHashEntry(hPtr); + ckfree(tabbingId); + } + hPtr = Tcl_FindHashEntry(&pathnameToTabbingMode, Tk_PathName(winPtr)); + if (hPtr) { + tabbingMode = PTR2INT(Tcl_GetHashValue(hPtr)); + Tcl_DeleteHashEntry(hPtr); + } + } else { + + /* + * If this is an override-redirect window, the NSWindow is created first as + * a document window then converted to a simple window. + */ + + if (overrideRedirect) { + wmPtr->macClass = kDocumentWindowClass; + } + macClass = wmPtr->macClass; + wmPtr->attributes &= (tkAlwaysValidAttributes | + macClassAttrs[macClass].validAttrs); + wmPtr->flags |= macClassAttrs[macClass].flags | ((wmPtr->attributes & kWindowResizableAttribute) ? 0 : - WM_WIDTH_NOT_RESIZABLE|WM_HEIGHT_NOT_RESIZABLE); - UInt64 attributes = (wmPtr->attributes & - ~macClassAttrs[macClass].forceOffAttrs) | + WM_WIDTH_NOT_RESIZABLE|WM_HEIGHT_NOT_RESIZABLE); + UInt64 attributes = (wmPtr->attributes & + ~macClassAttrs[macClass].forceOffAttrs) | macClassAttrs[macClass].forceOnAttrs; - NSUInteger styleMask = macClassAttrs[macClass].styleMask | - ((attributes & kWindowNoTitleBarAttribute) ? 0 : NSTitledWindowMask) | - ((attributes & kWindowCloseBoxAttribute) ? NSClosableWindowMask : 0) | - ((attributes & kWindowCollapseBoxAttribute) ? - NSMiniaturizableWindowMask : 0) | - ((attributes & kWindowResizableAttribute) ? NSResizableWindowMask : 0) | - ((attributes & kWindowMetalAttribute) ? - NSTexturedBackgroundWindowMask : 0) | - ((attributes & kWindowUnifiedTitleAndToolbarAttribute) ? - NSUnifiedTitleAndToolbarWindowMask : 0) | - ((attributes & kWindowSideTitlebarAttribute) ? 1 << 9 : 0) | - (attributes >> WM_NSMASK_SHIFT); - Class winClass = (macClass == kDrawerWindowClass ? [TKDrawerWindow class] : - (styleMask & (NSUtilityWindowMask|NSDocModalWindowMask| - NSNonactivatingPanelMask|NSHUDWindowMask)) ? [TKPanel class] : - [TKWindow class]); + styleMask = macClassAttrs[macClass].styleMask | + ((attributes & kWindowNoTitleBarAttribute) ? 0 : NSTitledWindowMask) | + ((attributes & kWindowCloseBoxAttribute) ? NSClosableWindowMask : 0) | + ((attributes & kWindowCollapseBoxAttribute) ? + NSMiniaturizableWindowMask : 0) | + ((attributes & kWindowResizableAttribute) ? NSResizableWindowMask : 0) | + ((attributes & kWindowMetalAttribute) ? + NSTexturedBackgroundWindowMask : 0) | + ((attributes & kWindowUnifiedTitleAndToolbarAttribute) ? + NSUnifiedTitleAndToolbarWindowMask : 0) | + ((attributes & kWindowSideTitlebarAttribute) ? 1 << 9 : 0) | + (attributes >> WM_NSMASK_SHIFT); + winClass = (macClass == kDrawerWindowClass ? [TKDrawerWindow class] : + (styleMask & (NSUtilityWindowMask|NSDocModalWindowMask| + NSNonactivatingPanelMask|NSHUDWindowMask)) ? + [TKPanel class] : [TKWindow class]); + } NSRect structureRect = [winClass frameRectForContentRect:NSZeroRect styleMask:styleMask]; NSRect contentRect = NSMakeRect(5 - structureRect.origin.x, TkMacOSXZeroScreenHeight() - (TkMacOSXZeroScreenTop() + 5 + structureRect.origin.y + structureRect.size.height + 200), 200, 200); if (wmPtr->hints.initial_state == WithdrawnState) { + //// ??????? } TKWindow *window = [[winClass alloc] initWithContentRect:contentRect styleMask:styleMask backing:NSBackingStoreBuffered defer:YES]; if (!window) { Tcl_Panic("couldn't allocate new Mac window"); } +#if MAC_OS_X_VERSION_MAX_ALLOWED > 101200 + if (tabbingId) { + identifier = [NSString stringWithUTF8String:tabbingId]; + } else { + identifier = [NSString stringWithUTF8String:Tk_PathName(winPtr)]; + } + [window setTabbingIdentifier: identifier]; + [window setTabbingMode: tabbingMode]; +#endif TKContentView *contentView = [[TKContentView alloc] initWithFrame:NSZeroRect]; [window setContentView:contentView]; [contentView release]; [window setDelegate:NSApp]; @@ -6297,11 +6796,15 @@ if (overrideRedirect) { XSetWindowAttributes atts; atts.override_redirect = True; Tk_ChangeWindowAttributes((Tk_Window)winPtr, CWOverrideRedirect, &atts); - ApplyContainerOverrideChanges(winPtr, NULL); + if ([NSApp macOSVersion] >= 101300) { + window.styleMask |= NSWindowStyleMaskDocModalWindow; + } else { + ApplyContainerOverrideChanges(winPtr, NULL); + } } [window display]; } /* Index: macosx/ttkMacOSXTheme.c ================================================================== --- macosx/ttkMacOSXTheme.c +++ macosx/ttkMacOSXTheme.c @@ -260,10 +260,13 @@ bounds.size.height -= 4; bounds.origin.y += 1; break; case TkRecessedButton: bounds.size.height -= 2; + break; + case TkSidebarButton: + bounds.size.height += 8; break; case kThemeRoundButtonHelp: if (isDark) { bounds.size.height = bounds.size.width = 22; } else { @@ -1202,10 +1205,13 @@ DrawGrayButton(context, bounds, &inlineDesign, state, tkwin); break; case TkRecessedButton: DrawGrayButton(context, bounds, &recessedDesign, state, tkwin); break; + case TkSidebarButton: + DrawGrayButton(context, bounds, &sidebarDesign, state, tkwin); + break; case kThemeRoundedBevelButton: DrawGrayButton(context, bounds, &bevelDesign, state, tkwin); break; case kThemePushButton: @@ -1687,10 +1693,13 @@ CGRect contentBounds, backgroundBounds; int verticalPad; ButtonElementMinSize(clientData, minWidth, minHeight); switch (info.kind) { + case TkSidebarButton: + *paddingPtr = Ttk_MakePadding(30, 10, 30, 10); + return; case TkGradientButton: *paddingPtr = Ttk_MakePadding(1, 1, 1, 1); /* Fall through. */ case kThemeArrowButton: case kThemeRoundButtonHelp: @@ -1767,10 +1776,11 @@ */ case kThemeArrowButton: case kThemeCheckBox: case kThemeRadioButton: + case TkSidebarButton: break; /* * Other buttons have a maximum height. We have to deal with that. */ @@ -3442,10 +3452,17 @@ TTK_LAYOUT("RecessedButton", TTK_GROUP("RecessedButton.button", TTK_FILL_BOTH, TTK_GROUP("Button.padding", TTK_FILL_BOTH, TTK_NODE("Button.label", TTK_FILL_BOTH)))) + +/* Sidebar Button - text only radio button for sidebars */ + +TTK_LAYOUT("SidebarButton", + TTK_GROUP("SidebarButton.button", TTK_FILL_BOTH, + TTK_GROUP("Button.padding", TTK_FILL_BOTH, + TTK_NODE("Button.label", TTK_FILL_BOTH)))) /* DisclosureButton (not a triangle) -- No label, no border*/ TTK_LAYOUT("DisclosureButton", TTK_NODE("DisclosureButton.button", TTK_FILL_BOTH)) @@ -3579,10 +3596,12 @@ &ButtonElementSpec, &CheckBoxParams); Ttk_RegisterElementSpec(themePtr, "Radiobutton.button", &ButtonElementSpec, &RadioButtonParams); Ttk_RegisterElementSpec(themePtr, "RecessedButton.button", &ButtonElementSpec, &RecessedButtonParams); + Ttk_RegisterElementSpec(themePtr, "SidebarButton.button", + &ButtonElementSpec, &SidebarButtonParams); Ttk_RegisterElementSpec(themePtr, "Toolbutton.border", &ButtonElementSpec, &BevelButtonParams); Ttk_RegisterElementSpec(themePtr, "Menubutton.button", &ButtonElementSpec, &PopupButtonParams); Ttk_RegisterElementSpec(themePtr, "DisclosureButton.button", Index: macosx/ttkMacOSXTheme.h ================================================================== --- macosx/ttkMacOSXTheme.h +++ macosx/ttkMacOSXTheme.h @@ -299,10 +299,31 @@ .dark = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0}, .onBits = 0, .offBits = 0 } } }; + +static const ButtonDesign sidebarDesign = { + .radius = 8.0, + .palettes = { + { + .light = {.face = 210.0, .top = 210.0, .side = 210.0, .bottom = 210.0}, + .dark = {.face = 129.0, .top = 129.0, .side = 129.0, .bottom = 129.0}, + .onBits = TTK_STATE_SELECTED, .offBits = 0 + }, + { + .light = {.face = 210.0, .top = 210.0, .side = 210.0, .bottom = 210.0}, + .dark = {.face = 129.0, .top = 129.0, .side = 129.0, .bottom = 129.0}, + .onBits = TTK_STATE_PRESSED, .offBits = 0 + }, + { + .light = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0}, + .dark = {.face = 256.0, .top = 256.0, .side = 256.0, .bottom = 256.0}, + .onBits = 0, .offBits = 0 + } + } +}; static const ButtonDesign incdecDesign = { .radius = 5.0, .palettes = { { @@ -472,10 +493,11 @@ #define TkGradientButton 0x8001 #define TkRoundedRectButton 0x8002 #define TkRecessedButton 0x8003 #define TkInlineButton 0x8004 +#define TkSidebarButton 0x8005 /* * The struct passed as clientData when drawing Ttk buttons. */ typedef struct { @@ -507,12 +529,12 @@ GradientButtonParams = {TkGradientButton, NoThemeMetric, NoThemeMetric}, RoundedRectButtonParams = {TkRoundedRectButton, kThemeMetricPushButtonHeight, NoThemeMetric}, RecessedButtonParams = {TkRecessedButton, kThemeMetricPushButtonHeight, NoThemeMetric}, - InlineButtonParams = {TkInlineButton, kThemeMetricPushButtonHeight, - NoThemeMetric}; + SidebarButtonParams = {TkSidebarButton, NoThemeMetric, NoThemeMetric}, + InlineButtonParams = {TkInlineButton, kThemeMetricPushButtonHeight, NoThemeMetric}; /* * Others: kThemeDisclosureRight, kThemeDisclosureDown, * kThemeDisclosureLeft */ Index: tests/event.test ================================================================== --- tests/event.test +++ tests/event.test @@ -854,18 +854,21 @@ wm geometry . 200x200+300+300 wm deiconify . _pause 200 toplevel .top2 -width 200 -height 200 wm geometry .top2 +[expr {[winfo rootx .]+50}]+[expr {[winfo rooty .]+50}] + update idletasks wm deiconify .top2 + update idletasks raise .top2 _pause 400 event generate .top2 -warp 1 -x 50 -y 50 _pause 100 bind . {lappend res %W} set res [list ] destroy .top2 + update idletasks _pause 200 set res } -cleanup { deleteWindows bind . $EnterBind @@ -882,11 +885,13 @@ wm geometry .top1 200x200+300+300 wm deiconify .top1 _pause 200 toplevel .top2 -width 200 -height 200 wm geometry .top2 +[expr {[winfo rootx .top1]+50}]+[expr {[winfo rooty .top1]+50}] + update idletasks wm deiconify .top2 + update idletasks raise .top2 _pause 400 event generate .top2 -warp 1 -x 50 -y 50 _pause 100 bind .top1 {lappend res %W} Index: tests/unixWm.test ================================================================== --- tests/unixWm.test +++ tests/unixWm.test @@ -2065,11 +2065,13 @@ list $result [winfo containing [winfo rootx .raise2] \ [winfo rooty .raise2]] } {.raise1 .raise3} deleteWindows test unixWm-51.6 {TkWmRestackToplevel procedure, window to be stacked isn't mapped} unix { + wm geometry . +300+300 destroy .t + update idletasks toplevel .t -width 200 -height 200 -bg green tkwait visibility .t wm geometry .t +0+0 update restackDelay @@ -2528,13 +2530,11 @@ # # NOTE: since [wm attributes] is not guaranteed to have any effect, # the only thing we can really test here is the syntax. # if {[tk windowingsystem] eq "aqua"} { - set result_60_1 {-alpha 1.0 -fullscreen 0 -modified 0 -notify 0\ - -titlepath {} -topmost 0 -transparent 0\ - -type unsupported} + set result_60_1 {-alpha 1.0 -appearance auto -buttons {close miniaturize zoom} -fullscreen 0 -isdark 0 -modified 0 -notify 0 -titlepath {} -topmost 0 -transparent 0 -stylemask {titled closable miniaturizable resizable} -class nswindow -tabbingid .t -tabbingmode auto -type unsupported} } else { set result_60_1 {-alpha 1.0 -fullscreen 0 -topmost 0 -type {} -zoomed 0} } test unixWm-60.1 {wm attributes - test} -constraints unix -body { destroy .t Index: tests/wm.test ================================================================== --- tests/wm.test +++ tests/wm.test @@ -202,14 +202,16 @@ wm attributes . -to } -result {bad attribute "-to": must be -alpha, -disabled, -fullscreen, -toolwindow, -topmost, or -transparentcolor} test wm-attributes-1.2.4 {usage} -constraints {unix notAqua} -returnCodes error -body { wm attributes . _ } -result {bad attribute "_": must be -alpha, -fullscreen, -topmost, -type, or -zoomed} +if {[tk windowingsystem] eq "aqua"} { + set result_1_2_5 {bad attribute "_": must be -alpha, -appearance, -buttons, -fullscreen, -isdark, -modified, -notify, -titlepath, -topmost, -transparent, -stylemask, -class, -tabbingid, -tabbingmode, or -type} +} else {set result_1_2_5 {bad attribute "_": must be -alpha, -fullscreen, -modified, -notify, -titlepath, -topmost, -transparent, or -type}} test wm-attributes-1.2.5 {usage} -constraints aqua -returnCodes error -body { wm attributes . _ -} -result {bad attribute "_": must be -alpha, -fullscreen, -modified, -notify, -titlepath, -topmost, -transparent, or -type} - +} -result $result_1_2_5 ### wm client ### test wm-client-1.1 {usage} -returnCodes error -body { wm client } -result {wrong # args: should be "wm option window ?arg ...?"}