diff options
137 files changed, 13010 insertions, 3256 deletions
diff --git a/doc/CrtItemType.3 b/doc/CrtItemType.3 index 9422668..cd270f4 100644 --- a/doc/CrtItemType.3 +++ b/doc/CrtItemType.3 @@ -83,6 +83,9 @@ typedef struct Tk_ItemType { Tk_ItemInsertProc *\fIinsertProc\fR; Tk_ItemDCharsProc *\fIdCharsProc\fR; Tk_ItemType *\fInextPtr\fR; +.VS "8.7, TIP164" + Tk_ItemRotateProc *\fIrotateProc\fR; +.VE "8.7, TIP164" } \fBTk_ItemType\fR; .CE .PP @@ -549,6 +552,46 @@ and \fIdeltaX\fR and \fIdeltaY\fR give the amounts that should be added to each x and y coordinate within the item. The type manager should adjust the item's coordinates and update the bounding box in the item's header. +.SS ROTATEPROC +.VS "8.7, TIP164" +.PP +\fItypePtr\->rotateProc\fR is invoked by Tk to rotate a canvas item +during the \fBrotate\fR widget command. +The procedure must match the following prototype: +.PP +.CS +typedef void \fBTk_ItemRotateProc\fR( + Tk_Canvas \fIcanvas\fR, + Tk_Item *\fIitemPtr\fR, + double \fIoriginX\fR, + double \fIoriginY\fR, + double \fIangleRad\fR); +.CE +.PP +The \fIcanvas\fR and \fIitemPtr\fR arguments have the usual meaning. +\fIoriginX\fR and \fIoriginY\fR specify an origin relative to which +the item is to be rotated, and \fIangleRad\fR gives the anticlockwise +rotation to be applied in radians. +The item should adjust the coordinates of its control points so that where +they used to have coordinates \fIx\fR and \fIy\fR, they will have new +coordinates \fIx\(fm\fR and \fIy\(fm\fR, where +.PP +.CS +\fIrelX\fR = \fIx\fR - \fIoriginX\fR +\fIrelY\fR = \fIy\fR - \fIoriginY\fR +\fIx\(fm\fR = \fIoriginX\fR + \fIrelX\fR \(mu cos(\fIangleRad\fR) + \fIrelY\fR \(mu sin(\fIangleRad\fR) +\fIy\(fm\fR = \fIoriginY\fR \(mi \fIrelX\fR \(mu sin(\fIangleRad\fR) + \fIrelY\fR \(mu cos(\fIangleRad\fR) +.CE +.PP +The control points for an item are not necessarily the coordinates provided to +the item when it is created (or via the \fItypePtr\->coordProc\fR), but could +instead be derived from them. +\fIrotateProc\fR must also update the bounding box in the item's header. +.PP +Item types do not need to provide a \fItypePtr\->rotateProc\fR. If the +\fItypePtr\->rotateProc\fR is NULL, the \fItypePtr\->coordProc\fR will be +used instead to retrieve and update the list of coordinates. +.VE "8.7, TIP164" .SS INDEXPROC .PP \fItypePtr\->indexProc\fR is invoked by Tk to translate a string diff --git a/doc/GetScroll.3 b/doc/GetScroll.3 index 70145aa..c0b302d 100644 --- a/doc/GetScroll.3 +++ b/doc/GetScroll.3 @@ -15,10 +15,10 @@ Tk_GetScrollInfoObj, Tk_GetScrollInfo \- parse arguments for scrolling commands \fB#include <tk.h>\fR .sp int -\fBTk_GetScrollInfoObj(\fIinterp, objc, objv, dblPtr, intPtr\fB)\fR +\fBTk_GetScrollInfoObj(\fIinterp, objc, objv, fractionPtr, stepsPtr\fB)\fR .sp int -\fBTk_GetScrollInfo(\fIinterp, argc, argv, dblPtr, intPtr\fB)\fR +\fBTk_GetScrollInfo(\fIinterp, argc, argv, fractionPtr, stepsPtr\fB)\fR .SH ARGUMENTS .AS "Tcl_Interp" *fractionPtr .AP Tcl_Interp *interp in diff --git a/doc/canvas.n b/doc/canvas.n index fa7843d..424c3ca 100644 --- a/doc/canvas.n +++ b/doc/canvas.n @@ -220,8 +220,12 @@ for scrolling. Canvases do not support scaling or rotation of the canvas coordinate system relative to the window coordinate system. .PP -Individual items may be moved or scaled using widget commands -described below, but they may not be rotated. +Individual items may be moved, scaled +.VS "8.7, TIP164" +or rotated +.VE "8.7, TIP164" +using widget commands +described below. .PP Note that the default origin of the canvas's visible area is coincident with the origin for the whole window as that makes bindings @@ -674,13 +678,12 @@ mapped. This command returns an empty string. .TP \fIpathName \fBimove \fItagOrId index x y\fR -.VS 8.6 +. This command causes the \fIindex\fR'th coordinate of each of the items indicated by \fItagOrId\fR to be relocated to the location (\fIx\fR,\fIy\fR). Each item interprets \fIindex\fR independently according to the rules described in \fBINDICES\fR above. Out of the standard set of items, only line and polygon items may have their coordinates relocated this way. -.VE 8.6 .TP \fIpathName \fBindex \fItagOrId index\fR . @@ -766,7 +769,7 @@ each point associated with the item. This command returns an empty string. .TP \fIpathName \fBmoveto \fItagOrId xPos yPos\fR -.VS 8.6 +. Move the items given by \fItagOrId\fR in the canvas coordinate space so that the first coordinate pair of the bottommost item with tag \fItagOrId\fR is located at @@ -775,7 +778,6 @@ the empty string, in which case the corresponding coordinate will be unchanged. All items matching \fItagOrId\fR remain in the same positions relative to each other. This command returns an empty string. -.VE 8.6 .TP \fIpathName \fBpostscript \fR?\fIoption value option value ...\fR? . @@ -961,7 +963,7 @@ determined by the \fBraise\fR command and \fBlower\fR command, not the .RE .TP \fIpathName \fBrchars \fItagOrId first last string\fR -.VS 8.6 +. This command causes the text or coordinates between \fIfirst\fR and \fIlast\fR for each of the items indicated by \fItagOrId\fR to be replaced by \fIstring\fR. Each item interprets \fIfirst\fR and \fIlast\fR independently @@ -970,7 +972,27 @@ set of items, text items support this operation by altering their text as directed, and line and polygon items support this operation by altering their coordinate list (in which case \fIstring\fR should be a list of coordinates to use as a replacement). The other items ignore this operation. -.VE 8.6 +.TP +\fIpathName \fBrotate \fItagOrId xOrigin yOrigin angle\fR +.VS "8.7, TIP164" +Rotate the coordinates of all of the items given by \fItagOrId\fR in canvas +coordinate space. +\fIXOrigin\fR and \fIyOrigin\fR identify the origin for the rotation +operation and \fIangle\fR identifies the amount to rotate the coordinates +anticlockwise, in degrees. (Negative values rotate clockwise.) +This command returns an empty string. +.RS +.PP +Implementation note: not all item types work the same with rotations. In +particular,\fB bitmap\fR,\fB image\fR,\fB text\fR and\fB window\fR items only +rotate their anchor points and do not rotate the items themselves about those +points, and the \fBarc\fR, \fBoval\fR and \fBrectangle\fR types rotate about a +computed center point instead of moving the bounding box coordinates directly. +.PP +Some items (currently \fBarc\R and\fB text\fR) have angles in their options; +this command \fIdoes not\fR affect those options. +.RE +.VE "8.7, TIP164" .TP \fIpathName \fBscale \fItagOrId xOrigin yOrigin xScale yScale\fR . @@ -1834,13 +1856,12 @@ The following standard options are supported by text items: The following extra options are supported for text items: .TP \fB\-angle \fIrotationDegrees\fR -.VS 8.6 +. \fIRotationDegrees\fR tells how many degrees to rotate the text anticlockwise about the positioning point for the text; it may have any floating-point value from 0.0 to 360.0. For example, if \fIrotationDegrees\fR is \fB90\fR, then the text will be drawn vertically from bottom to top. This option defaults to \fB0.0\fR. -.VE 8.6 .TP \fB\-font \fIfontName\fR Specifies the font to use for the text item. diff --git a/doc/colors.n b/doc/colors.n index dc7007b..0d3c162 100644 --- a/doc/colors.n +++ b/doc/colors.n @@ -784,9 +784,10 @@ YellowGreen 154 205 50 .TP \fBMac OS X\fR . -On Mac OS X, the following additional system colors are available -(note that the actual color values depend on the currently active OS theme, -and typically many of these will in fact be patterns rather than pure colors): +On macOS, the following additional system colors are available. +This first group contains all colors available in the HIToolbox library. +(Note that in some cases the actual color values may depend on the +current Appearance.) .RS .DS systemActiveAreaFill @@ -900,6 +901,7 @@ systemScrollBarDelimiterActive systemScrollBarDelimiterInactive systemSecondaryGroupBoxBackground systemSecondaryHighlightColor +systemSelectedTabTextColor systemSheetBackground systemSheetBackgroundOpaque systemSheetBackgroundTransparent @@ -925,7 +927,40 @@ systemWindowHeaderBackground systemWindowHeaderInactiveText .DE .RE +. +The second group of MacOS colors below are based on Apple's "semantic" +NScolors. On OSX 10.14 (Mojave) and later these colors change value +when Dark Mode is enabled. However, the change is only observable +when the Apple window manager is drawing to the screen. So the +\fBwinfo rgb\fR command will return the color coordinates used in the +standard Aqua mode, even if Dark Mode has been selected in the system +preferences. The numbered systemWindowBackgroundColors are used in +the \fBttk::notebook\fR and \fBttk::labelframe\fR widgets to provide a +contrasting background. Each numbered color constrasts with its +predecessor. +.RS +.DS +systemControlAccentColor +systemControlTextColor +systemDisabledControlTextColor +systemLabelColor +systemSelectedTextBackgroundColor +systemSelectedTextColor +systemTextBackgroundColor +systemTextColor +systemWindowBackgroundColor +systemWindowBackgroundColor1 +systemWindowBackgroundColor2 +systemWindowBackgroundColor3 +systemWindowBackgroundColor4 +systemWindowBackgroundColor5 +systemWindowBackgroundColor6 +systemWindowBackgroundColor7 +.DE +.RE .TP + + \fBWindows\fR . On Windows, the following additional system colors are available diff --git a/doc/frame.n b/doc/frame.n index 6aa412e..9c34d03 100644 --- a/doc/frame.n +++ b/doc/frame.n @@ -99,21 +99,25 @@ The \fBframe\fR command creates a new Tcl command whose name is the same as the path name of the frame's window. This command may be used to invoke various operations on the widget. It has the following general form: +.PP .CS \fIpathName option \fR?\fIarg arg ...\fR? .CE +.PP \fIPathName\fR is the name of the command, which is the same as the frame widget's path name. \fIOption\fR and the \fIarg\fRs determine the exact behavior of the command. The following commands are possible for frame widgets: .TP \fIpathName \fBcget\fR \fIoption\fR +. Returns the current value of the configuration option given by \fIoption\fR. \fIOption\fR may have any of the values accepted by the \fBframe\fR command. .TP \fIpathName \fBconfigure\fR ?\fIoption\fR? \fI?value option value ...\fR? +. Query or modify the configuration options of the widget. If no \fIoption\fR is specified, returns a list describing all of the available options for \fIpathName\fR (see \fBTk_ConfigureInfo\fR for diff --git a/doc/photo.n b/doc/photo.n index d875098..2f6076e 100644 --- a/doc/photo.n +++ b/doc/photo.n @@ -551,6 +551,59 @@ an additional alpha filtering for the overall image, which allows the background on which the image is displayed to show through. This usually also has the effect of desaturating the image. The \fIalphaValue\fR must be between 0.0 and 1.0. +.TP +\fBsvg \-dpi\fI dpiValue\fB \-scale\fI scaleValue\fB \-unit\fI unitValue\fR +. +\fIdpiValue\fR is used in conversion between given coordinates and +screen resolution. The value must be greater than 0 and the default +value is 96. +\fIscaleValue\fR is used to scale the resulting image. The value must +be greater than 0 and the default value is 1. +\fIunitValue\fR is the unit of all coordinates in the SVG data. +Available units are px (default, coordinates in pixel), pt (1/72 inch), +pc (12 pt), mm , cm and in. +The svg format supports a wide range of SVG features, but the +full SVG standard is not available, for instance the 'text' feature +is missing and silently ignores when reading the SVG data. +The supported SVG features are: +. +.RS +\fB elements:\fR g, path, rect, circle, ellipse, line, polyline, polygon, +linearGradient, radialGradient, stop, defs, svg, style +.PP +\fB attributes:\fR width, height, viewBox, +preserveAspectRatio with none, xMin, xMid, xMax, yMin, yMid, yMax, slice +.PP +\fB gradient attributes:\fR gradientUnits with objectBoundingBox, +gradientTransform, cx, cy, r fx, fy x1, y1, x2, y2 +spreadMethod with pad, reflect or repeat, +xlink:href +.PP +\fB poly attributes: \fR points +.PP +\fB line attributes: \fR x1, y1, x2, y2 +.PP +\fB ellipse attributes: \fR cx, cy, rx, ry +.PP +\fB circle attributes: \fR cx, cy, r +.PP +\fB rectangle attributes: \fR x, y, width, height, rx, ry +.PP +\fB path attributes: \fR d with m, M, l, L, h, H, v, V, c, C, s, S, q, Q, t, T, a, A, z, Z +.PP +\fB style attributes: \fR display with none, visibility, hidden, visible, +fill with nonzero and evenodd, opacity, fill-opacity, +stroke, stroke-width, stroke-dasharray, stroke-dashoffset, stroke-opacity, +stroke-linecap with butt, round and square, +stroke-linejoin with miter, round and bevel, stroke-miterlimit +fill-rule, font-size, +transform with matrix, translate, scale, rotate, skewX and skewY, +stop-color, stop-opacity, offset, id, class +.RE +. +Currently only SVG images reading and conversion into (pixel-based +format) photos is supported: Tk does not (yet) support bundling photo +images in SVG vector graphics. .VE 8.6 .VS 8.7 .SH "COLOR FORMATS" diff --git a/doc/toplevel.n b/doc/toplevel.n index 31f241c..04fadc4 100644 --- a/doc/toplevel.n +++ b/doc/toplevel.n @@ -104,9 +104,9 @@ to configure aspects of the toplevel such as its background color and relief. The \fBtoplevel\fR command returns the path name of the new window. .PP -A toplevel is similar to a frame except that it is created as a +A toplevel is similar to a \fBframe\fR except that it is created as a top-level window: its X parent is the root window of a screen -rather than the logical parent from its path name. The primary +rather than the logical parent from its Tk path name. The primary purpose of a toplevel is to serve as a container for dialog boxes and other collections of widgets. The only visible features of a toplevel are its background color and an optional 3-D border @@ -117,21 +117,25 @@ The \fBtoplevel\fR command creates a new Tcl command whose name is the same as the path name of the toplevel's window. This command may be used to invoke various operations on the widget. It has the following general form: +.PP .CS \fIpathName option \fR?\fIarg arg ...\fR? .CE +.PP \fIPathName\fR is the name of the command, which is the same as the toplevel widget's path name. \fIOption\fR and the \fIarg\fRs determine the exact behavior of the command. The following commands are possible for toplevel widgets: .TP \fIpathName \fBcget \fIoption\fR +. Returns the current value of the configuration option given by \fIoption\fR. \fIOption\fR may have any of the values accepted by the \fBtoplevel\fR command. .TP \fIpathName \fBconfigure\fR ?\fIoption\fR? ?\fIvalue option value ...\fR? +. Query or modify the configuration options of the widget. If no \fIoption\fR is specified, returns a list describing all of the available options for \fIpathName\fR (see \fBTk_ConfigureInfo\fR for @@ -148,8 +152,10 @@ command. .PP When a new toplevel is created, it has no default event bindings: toplevels are not intended to be interactive. +.PP +Be aware that bindings on toplevels may receive events from subwidgets. .SH "SEE ALSO" -frame(n) +bind(n), bindtags(n), frame(n), wm(n) .SH KEYWORDS toplevel, widget '\" Local Variables: diff --git a/doc/ttk_combobox.n b/doc/ttk_combobox.n index 37acf19..24bf8d9 100644 --- a/doc/ttk_combobox.n +++ b/doc/ttk_combobox.n @@ -124,6 +124,8 @@ are: .PP \fB\-arrowcolor\fP \fIcolor\fP .br +\fB\-arrowsize\fP \fIamount\fP +.br \fB\-background\fP \fIcolor\fP .br \fB\-bordercolor\fP \fIcolor\fP diff --git a/doc/ttk_entry.n b/doc/ttk_entry.n index 10557b0..cb58005 100644 --- a/doc/ttk_entry.n +++ b/doc/ttk_entry.n @@ -222,52 +222,13 @@ by the \fB\-validate\fR option. Returns 0 if validation fails, 1 if it succeeds. Sets or clears the \fBinvalid\fR state accordingly. See \fBVALIDATION\fR below for more details. -.TP -\fIpathName \fBxview \fIargs\fR -This command is used to query and change the horizontal position of the -text in the widget's window. It can take any of the following -forms: -.RS -.TP -\fIpathName \fBxview\fR -Returns a list containing two elements. -Each element is a real fraction between 0 and 1; together they describe -the horizontal span that is visible in the window. -For example, if the first element is .2 and the second element is .6, -20% of the entry's text is off-screen to the left, the middle 40% is visible -in the window, and 40% of the text is off-screen to the right. -These are the same values passed to scrollbars via the \fB\-xscrollcommand\fR -option. -.TP -\fIpathName \fBxview\fR \fIindex\fR -Adjusts the view in the window so that the character given by \fIindex\fR -is displayed at the left edge of the window. -.TP -\fIpathName \fBxview moveto\fI fraction\fR -Adjusts the view in the window so that the character \fIfraction\fR of the -way through the text appears at the left edge of the window. -\fIFraction\fR must be a fraction between 0 and 1. -.TP -\fIpathName \fBxview scroll \fInumber what\fR -This command shifts the view in the window left or right according to -\fInumber\fR and \fIwhat\fR. -\fINumber\fR must be an integer. -\fIWhat\fR must be either \fBunits\fR or \fBpages\fR. -'\" or an abbreviation of one of these, but we don't document that. -If \fIwhat\fR is \fBunits\fR, the view adjusts left or right by -\fInumber\fR average-width characters on the display; if it is -\fBpages\fR then the view adjusts by \fInumber\fR screenfuls. -If \fInumber\fR is negative then characters farther to the left -become visible; if it is positive then characters farther to the right -become visible. -.RE .PP The entry widget also supports the following generic \fBttk::widget\fR widget subcommands (see \fIttk::widget(n)\fR for details): .DS .ta 5.5c 11c \fBcget\fR \fBconfigure\fR \fBidentify\fR -\fBinstate\fR \fBstate\fR +\fBinstate\fR \fBstate\fR \fBxview\fR .DE .SH VALIDATION .PP @@ -472,7 +433,9 @@ are: .PP \fB\-background\fP \fIcolor\fP .RS -When using the aqua theme (Mac OS X), changes the \fB\-fieldbackground\fP. +For backwards compatibility, when using the aqua theme (for macOS), this +option behaves as an alias for the \fB\-fieldbackground\fP provided that no +value is specified for \fB\-fieldbackground\fP. Otherwise it is ignored. .RE \fB\-bordercolor\fP \fIcolor\fP .br @@ -480,8 +443,6 @@ When using the aqua theme (Mac OS X), changes the \fB\-fieldbackground\fP. .br \fB\-fieldbackground\fP \fIcolor\fP .RS -Does not work with the aqua theme (Mac OS X). -.br Some themes use a graphical background and their field background colors cannot be changed. .RE \fB\-foreground\fP \fIcolor\fP diff --git a/doc/ttk_frame.n b/doc/ttk_frame.n index 8702592..572f10c 100644 --- a/doc/ttk_frame.n +++ b/doc/ttk_frame.n @@ -58,6 +58,10 @@ Some options are only available for specific themes. .PP See the \fBttk::style\fP manual page for information on how to configure ttk styles. +.SH BINDINGS +.PP +When a new \fBttk::frame\fR is created, it has no default event bindings; +\fBttk::frame\fRs are not intended to be interactive. .SH "SEE ALSO" ttk::widget(n), ttk::labelframe(n), frame(n) .SH "KEYWORDS" diff --git a/doc/ttk_scale.n b/doc/ttk_scale.n index 4e16999..f8f5072 100644 --- a/doc/ttk_scale.n +++ b/doc/ttk_scale.n @@ -93,7 +93,7 @@ corresponding to the current value of the \fB\-value\fR option if \fIvalue\fR is omitted. .SH "STYLING OPTIONS" .PP -The class name for a \fBttk::scale\fP is \fBTProgressbar\fP. +The class name for a \fBttk::scale\fP is \fBTScale\fP. .PP Dynamic states: \fBactive\fP. .PP diff --git a/doc/ttk_scrollbar.n b/doc/ttk_scrollbar.n index 49681a1..bd80760 100644 --- a/doc/ttk_scrollbar.n +++ b/doc/ttk_scrollbar.n @@ -76,7 +76,7 @@ Modify or query the widget state; see \fIttk::widget(n)\fR. .SH "INTERNAL COMMANDS" .PP The following widget commands are used internally -by the TScrollbar widget class bindings. +by the \fBTScrollbar\fP widget class bindings. .TP \fIpathName \fBdelta \fIdeltaX deltaY\fR Returns a real number indicating the fractional change in @@ -153,6 +153,7 @@ grid $f.vsb \-row 0 \-column 1 \-sticky nsew grid $f.hsb \-row 1 \-column 0 \-sticky nsew grid columnconfigure $f 0 \-weight 1 grid rowconfigure $f 0 \-weight 1 +pack $f .CE .SH "STYLING OPTIONS" .PP @@ -160,20 +161,25 @@ The class name for a \fBttk::scrollbar\fP is \fBTScrollbar\fP. .PP Dynamic states: \fBactive\fP, \fBdisabled\fP. .PP -\fBTScrollbar\fP styling options configurable with \fBttk::style\fP -are: +\fBTScrollbar\fP (or more specifically \fBVertical.TScrollbar\fP and +\fBHorizontal.TScrollbar\fP) styling options that are configurable with +\fBttk::style\fP are: .PP \fB\-arrowcolor\fP \fIcolor\fP .br +\fB\-arrowsize\fP \fIamount\fP +.br \fB\-background\fP \fIcolor\fP .br \fB\-bordercolor\fP \fIcolor\fP .br -\fB\-darkcolor\fP \fIcolor\fP +\fB\-darkcolor\fP \fIcolor\fP (color of the dark part of the 3D relief) .br \fB\-foreground\fP \fIcolor\fP .br -\fB\-lightcolor\fP \fIcolor\fP +\fB\-gripcount\fP \fIcount\fP (number of lines on the thumb) +.br +\fB\-lightcolor\fP \fIcolor\fP (color of the light part of the 3D relief) .br \fB\-troughcolor\fP \fIcolor\fP .PP diff --git a/doc/ttk_spinbox.n b/doc/ttk_spinbox.n index 980fd75..c18f987 100644 --- a/doc/ttk_spinbox.n +++ b/doc/ttk_spinbox.n @@ -95,16 +95,16 @@ are: .br \fB\-background\fP \fIcolor\fP .RS -When using the aqua theme (Mac OS X), changes the \fB\-fieldbackground\fP. +For backwards compatibility, when using the aqua theme (for macOS), this +option behaves as an alias for the \fB\-fieldbackground\fP provided that no +value is specified for \fB\-fieldbackground\fP. Otherwise it is ignored. .RE \fB\-bordercolor\fP \fIcolor\fP .br \fB\-darkcolor\fP \fIcolor\fP .br \fB\-fieldbackground\fP \fIcolor\fP -.RS -Does not work with the aqua theme (Mac OS X). -.RE +.br \fB\-foreground\fP \fIcolor\fP .br \fB\-lightcolor\fP \fIcolor\fP diff --git a/doc/ttk_treeview.n b/doc/ttk_treeview.n index 96565a3..5fd5e6d 100644 --- a/doc/ttk_treeview.n +++ b/doc/ttk_treeview.n @@ -388,12 +388,13 @@ If \fIitems\fR is omitted, removes \fItag\fR from each item in the tree. If \fItag\fR is not present for a particular item, then the \fB\-tags\fR for that item are unchanged. .RE -.TP -\fIpathName \fBxview \fIargs\fR -Standard command for horizontal scrolling; see \fIwidget(n)\fR. -.TP -\fIpathName \fByview \fIargs\fR -Standard command for vertical scrolling; see \fIttk::widget(n)\fR. +.PP +The treeview widget also supports the following generic \fBttk::widget\fR +widget subcommands (see \fIttk::widget(n)\fR for details): +.DS +.ta 5.5c 11c +\fBxview\fR \fByview\fR +.DE .SH "ITEM OPTIONS" .PP The following item options may be specified for items diff --git a/doc/ttk_widget.n b/doc/ttk_widget.n index 9e9f3db..b1c280d 100644 --- a/doc/ttk_widget.n +++ b/doc/ttk_widget.n @@ -224,6 +224,84 @@ will restore \fIpathName\fR to the original state. If \fIstateSpec\fR is not specified, returns a list of the currently-enabled state flags. .RE +.TP +\fIpathName \fBxview \fIargs\fR +This command is used to query and change the horizontal position of the +content in the widget's window. It can take any of the following +forms: +.RS +.TP +\fIpathName \fBxview\fR +Returns a list containing two elements. +Each element is a real fraction between 0 and 1; together they describe +the horizontal span that is visible in the window. +For example, if the first element is .2 and the second element is .6, +20% of the widget's content is off-screen to the left, the middle 40% is visible +in the window, and 40% of the content is off-screen to the right. +These are the same values passed to scrollbars via the \fB\-xscrollcommand\fR +option. +.TP +\fIpathName \fBxview\fR \fIindex\fR +Adjusts the view in the window so that the content given by \fIindex\fR +is displayed at the left edge of the window. +.TP +\fIpathName \fBxview moveto\fI fraction\fR +Adjusts the view in the window so that the character \fIfraction\fR of the +way through the content appears at the left edge of the window. +\fIFraction\fR must be a fraction between 0 and 1. +.TP +\fIpathName \fBxview scroll \fInumber what\fR +This command shifts the view in the window left or right according to +\fInumber\fR and \fIwhat\fR. +\fINumber\fR must be an integer. +\fIWhat\fR must be either \fBunits\fR or \fBpages\fR. +'\" or an abbreviation of one of these, but we don't document that. +If \fIwhat\fR is \fBunits\fR, the view adjusts left or right by +\fInumber\fR average-width characters on the display; if it is +\fBpages\fR then the view adjusts by \fInumber\fR screenfuls. +If \fInumber\fR is negative then characters farther to the left +become visible; if it is positive then characters farther to the right +become visible. +.RE +.TP +\fIpathName \fByview \fIargs\fR +This command is used to query and change the vertical position of the +content in the widget's window. It can take any of the following +forms: +.RS +.TP +\fIpathName \fByview\fR +Returns a list containing two elements. +Each element is a real fraction between 0 and 1; together they describe +the vertical span that is visible in the window. +For example, if the first element is .2 and the second element is .6, +20% of the widget's content is off-screen to the top, the middle 40% is visible +in the window, and 40% of the content is off-screen to the bottom. +These are the same values passed to scrollbars via the \fB\-yscrollcommand\fR +option. +.TP +\fIpathName \fByview\fR \fIindex\fR +Adjusts the view in the window so that the content given by \fIindex\fR +is displayed at the top edge of the window. +.TP +\fIpathName \fByview moveto\fI fraction\fR +Adjusts the view in the window so that the item \fIfraction\fR of the +way through the content appears at the top edge of the window. +\fIFraction\fR must be a fraction between 0 and 1. +.TP +\fIpathName \fByview scroll \fInumber what\fR +This command shifts the view in the window up or down according to +\fInumber\fR and \fIwhat\fR. +\fINumber\fR must be an integer. +\fIWhat\fR must be either \fBunits\fR or \fBpages\fR. +'\" or an abbreviation of one of these, but we don't document that. +If \fIwhat\fR is \fBunits\fR, the view adjusts up or down by +\fInumber\fR average-width characters on the display; if it is +\fBpages\fR then the view adjusts by \fInumber\fR screenfuls. +If \fInumber\fR is negative then items farther to the top +become visible; if it is positive then items farther to the bottom +become visible. +.RE .SH "WIDGET STATES" The widget state is a bitmap of independent state flags. Widget state flags include: diff --git a/generic/nanosvg.h b/generic/nanosvg.h new file mode 100755 index 0000000..ade6ec7 --- /dev/null +++ b/generic/nanosvg.h @@ -0,0 +1,3084 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example + * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) + * + * Arc calculation code based on canvg (https://code.google.com/p/canvg/) + * + * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html + * + */ + +#ifndef NANOSVG_H +#define NANOSVG_H + +#ifdef __cplusplus +extern "C" { +#endif + +// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. +// +// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. +// +// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! +// +// The shapes in the SVG images are transformed by the viewBox and converted to specified units. +// That is, you should get the same looking data as your designed in your favorite app. +// +// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose +// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. +// +// The units passed to NanoVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. +// DPI (dots-per-inch) controls how the unit conversion is done. +// +// If you don't know or care about the units stuff, "px" and 96 should get you going. + + +/* Example Usage: + // Load + NSVGImage* image; + image = nsvgParseFromFile("test.svg", "px", 96); + printf("size: %f x %f\n", image->width, image->height); + // Use... + for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { + for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { + for (int i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); + } + } + } + // Delete + nsvgDelete(image); +*/ + +#ifndef NANOSVG_SCOPE +#define NANOSVG_SCOPE +#endif + +#ifndef NANOSVG_malloc +#define NANOSVG_malloc malloc +#endif + +#ifndef NANOSVG_realloc +#define NANOSVG_realloc realloc +#endif + +#ifndef NANOSVG_free +#define NANOSVG_free free +#endif + +// float emulation for MS VC6++ compiler +#if (_MSC_VER == 1200) +#define tanf(a) (float)tan(a) +#define cosf(a) (float)cos(a) +#define sinf(a) (float)sin(a) +#define sqrtf(a) (float)sqrt(a) +#define fabsf(a) (float)abs(a) +#define acosf(a) (float)acos(a) +#define atan2f(a,b) (float)atan2(a,b) +#define ceilf(a) (float)ceil(a) +#define fmodf(a,b) (float)fmod(a,b) +#define floorf(a) (float)floor(a) +#endif + +enum NSVGpaintType { + NSVG_PAINT_NONE = 0, + NSVG_PAINT_COLOR = 1, + NSVG_PAINT_LINEAR_GRADIENT = 2, + NSVG_PAINT_RADIAL_GRADIENT = 3 +}; + +enum NSVGspreadType { + NSVG_SPREAD_PAD = 0, + NSVG_SPREAD_REFLECT = 1, + NSVG_SPREAD_REPEAT = 2 +}; + +enum NSVGlineJoin { + NSVG_JOIN_MITER = 0, + NSVG_JOIN_ROUND = 1, + NSVG_JOIN_BEVEL = 2 +}; + +enum NSVGlineCap { + NSVG_CAP_BUTT = 0, + NSVG_CAP_ROUND = 1, + NSVG_CAP_SQUARE = 2 +}; + +enum NSVGfillRule { + NSVG_FILLRULE_NONZERO = 0, + NSVG_FILLRULE_EVENODD = 1 +}; + +enum NSVGflags { + NSVG_FLAGS_VISIBLE = 0x01 +}; + +typedef struct NSVGgradientStop { + unsigned int color; + float offset; +} NSVGgradientStop; + +typedef struct NSVGgradient { + float xform[6]; + char spread; + float fx, fy; + int nstops; + NSVGgradientStop stops[1]; +} NSVGgradient; + +typedef struct NSVGpaint { + char type; + union { + unsigned int color; + NSVGgradient* gradient; + }; +} NSVGpaint; + +typedef struct NSVGpath +{ + float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + struct NSVGpath* next; // Pointer to next path, or NULL if last element. +} NSVGpath; + +typedef struct NSVGshape +{ + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaint fill; // Fill paint + NSVGpaint stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + NSVGpath* paths; // Linked list of paths in the image. + struct NSVGshape* next; // Pointer to next shape, or NULL if last element. +} NSVGshape; + +typedef struct NSVGimage +{ + float width; // Width of the image. + float height; // Height of the image. + NSVGshape* shapes; // Linked list of shapes in the image. +} NSVGimage; + +// Parses SVG file from a file, returns SVG image as paths. +NANOSVG_SCOPE NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); + +// Parses SVG file from a null terminated string, returns SVG image as paths. +// Important note: changes the string. +NANOSVG_SCOPE NSVGimage* nsvgParse(char* input, const char* units, float dpi); + +// Deletes list of paths. +NANOSVG_SCOPE void nsvgDelete(NSVGimage* image); + +#ifdef __cplusplus +} +#endif + +#endif // NANOSVG_H + +#ifdef NANOSVG_IMPLEMENTATION + +#include <string.h> +#include <stdlib.h> +#include <math.h> + +#define NSVG_PI (3.14159265358979323846264338327f) +#define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. + +#define NSVG_ALIGN_MIN 0 +#define NSVG_ALIGN_MID 1 +#define NSVG_ALIGN_MAX 2 +#define NSVG_ALIGN_NONE 0 +#define NSVG_ALIGN_MEET 1 +#define NSVG_ALIGN_SLICE 2 + +#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) +#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) + +#ifdef _MSC_VER + #pragma warning (disable: 4996) // Switch off security warnings + #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings + #ifdef __cplusplus + #define NSVG_INLINE inline + #else + #define NSVG_INLINE + #endif + #if !defined(strtoll) // old MSVC versions do not have strtoll() + #define strtoll _strtoi64 + #endif +#else + #define NSVG_INLINE inline +#endif + + +static int nsvg__isspace(char c) +{ + return strchr(" \t\n\v\f\r", c) != 0; +} + +static int nsvg__isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int nsvg__isnum(char c) +{ + return strchr("0123456789+-.eE", c) != 0; +} + +static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } +static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } + + +// Simple XML parser + +#define NSVG_XML_TAG 1 +#define NSVG_XML_CONTENT 2 +#define NSVG_XML_MAX_ATTRIBS 256 + +static void nsvg__parseContent(char* s, + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + // Trim start white spaces + while (*s && nsvg__isspace(*s)) s++; + if (!*s) return; + + if (contentCb) + (*contentCb)(ud, s); +} + +static void nsvg__parseElement(char* s, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void* ud) +{ + const char* attr[NSVG_XML_MAX_ATTRIBS]; + int nattr = 0; + char* name; + int start = 0; + int end = 0; + char quote; + + // Skip white space after the '<' + while (*s && nsvg__isspace(*s)) s++; + + // Check if the tag is end tag + if (*s == '/') { + s++; + end = 1; + } else { + start = 1; + } + + // Skip comments, data and preprocessor stuff. + if (!*s || *s == '?' || *s == '!') + return; + + // Get tag name + name = s; + while (*s && !nsvg__isspace(*s)) s++; + if (*s) { *s++ = '\0'; } + + // Get attribs + while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { + char* name = NULL; + char* value = NULL; + + // Skip white space before the attrib name + while (*s && nsvg__isspace(*s)) s++; + if (!*s) break; + if (*s == '/') { + end = 1; + break; + } + name = s; + // Find end of the attrib name. + while (*s && !nsvg__isspace(*s) && *s != '=') s++; + if (*s) { *s++ = '\0'; } + // Skip until the beginning of the value. + while (*s && *s != '\"' && *s != '\'') s++; + if (!*s) break; + quote = *s; + s++; + // Store value and find the end of it. + value = s; + while (*s && *s != quote) s++; + if (*s) { *s++ = '\0'; } + + // Store only well formed attributes + if (name && value) { + attr[nattr++] = name; + attr[nattr++] = value; + } + } + + // List terminator + attr[nattr++] = 0; + attr[nattr++] = 0; + + // Call callbacks. + if (start && startelCb) + (*startelCb)(ud, name, attr); + if (end && endelCb) + (*endelCb)(ud, name); +} + +NANOSVG_SCOPE +int nsvg__parseXML(char* input, + void (*startelCb)(void* ud, const char* el, const char** attr), + void (*endelCb)(void* ud, const char* el), + void (*contentCb)(void* ud, const char* s), + void* ud) +{ + char* s = input; + char* mark = s; + int state = NSVG_XML_CONTENT; + while (*s) { + if (*s == '<' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } else { + s++; + } + } + + return 1; +} + + +/* Simple SVG parser. */ + +#define NSVG_MAX_ATTR 128 + +enum NSVGgradientUnits { + NSVG_USER_SPACE = 0, + NSVG_OBJECT_SPACE = 1 +}; + +#define NSVG_MAX_DASHES 8 + +enum NSVGunits { + NSVG_UNITS_USER, + NSVG_UNITS_PX, + NSVG_UNITS_PT, + NSVG_UNITS_PC, + NSVG_UNITS_MM, + NSVG_UNITS_CM, + NSVG_UNITS_IN, + NSVG_UNITS_PERCENT, + NSVG_UNITS_EM, + NSVG_UNITS_EX +}; + +enum NSVGvisible { + NSVG_VIS_DISPLAY = 1, + NSVG_VIS_VISIBLE = 2 +}; + +typedef struct NSVGcoordinate { + float value; + int units; +} NSVGcoordinate; + +typedef struct NSVGlinearData { + NSVGcoordinate x1, y1, x2, y2; +} NSVGlinearData; + +typedef struct NSVGradialData { + NSVGcoordinate cx, cy, r, fx, fy; +} NSVGradialData; + +typedef struct NSVGgradientData +{ + char id[64]; + char ref[64]; + char type; + union { + NSVGlinearData linear; + NSVGradialData radial; + }; + char spread; + char units; + float xform[6]; + int nstops; + NSVGgradientStop* stops; + struct NSVGgradientData* next; +} NSVGgradientData; + +typedef struct NSVGattrib +{ + char id[64]; + float xform[6]; + unsigned int fillColor; + unsigned int strokeColor; + float opacity; + float fillOpacity; + float strokeOpacity; + char fillGradient[64]; + char strokeGradient[64]; + float strokeWidth; + float strokeDashOffset; + float strokeDashArray[NSVG_MAX_DASHES]; + int strokeDashCount; + char strokeLineJoin; + char strokeLineCap; + float miterLimit; + char fillRule; + float fontSize; + unsigned int stopColor; + float stopOpacity; + float stopOffset; + char hasFill; + char hasStroke; + char visible; +} NSVGattrib; + +typedef struct NSVGstyles +{ + char* name; + char* description; + struct NSVGstyles* next; +} NSVGstyles; + +typedef struct NSVGparser +{ + NSVGattrib attr[NSVG_MAX_ATTR]; + int attrHead; + float* pts; + int npts; + int cpts; + NSVGpath* plist; + NSVGimage* image; + NSVGstyles* styles; + NSVGgradientData* gradients; + NSVGshape* shapesTail; + float viewMinx, viewMiny, viewWidth, viewHeight; + int alignX, alignY, alignType; + float dpi; + char pathFlag; + char defsFlag; + char styleFlag; +} NSVGparser; + +static void nsvg__xformIdentity(float* t) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetTranslation(float* t, float tx, float ty) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = 0.0f; t[3] = 1.0f; + t[4] = tx; t[5] = ty; +} + +static void nsvg__xformSetScale(float* t, float sx, float sy) +{ + t[0] = sx; t[1] = 0.0f; + t[2] = 0.0f; t[3] = sy; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewX(float* t, float a) +{ + t[0] = 1.0f; t[1] = 0.0f; + t[2] = tanf(a); t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetSkewY(float* t, float a) +{ + t[0] = 1.0f; t[1] = tanf(a); + t[2] = 0.0f; t[3] = 1.0f; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformSetRotation(float* t, float a) +{ + float cs = cosf(a), sn = sinf(a); + t[0] = cs; t[1] = sn; + t[2] = -sn; t[3] = cs; + t[4] = 0.0f; t[5] = 0.0f; +} + +static void nsvg__xformMultiply(float* t, float* s) +{ + float t0 = t[0] * s[0] + t[1] * s[2]; + float t2 = t[2] * s[0] + t[3] * s[2]; + float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; + t[1] = t[0] * s[1] + t[1] * s[3]; + t[3] = t[2] * s[1] + t[3] * s[3]; + t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; + t[0] = t0; + t[2] = t2; + t[4] = t4; +} + +static void nsvg__xformInverse(float* inv, float* t) +{ + double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; + if (det > -1e-6 && det < 1e-6) { + nsvg__xformIdentity(t); + return; + } + invdet = 1.0 / det; + inv[0] = (float)(t[3] * invdet); + inv[2] = (float)(-t[2] * invdet); + inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); + inv[1] = (float)(-t[1] * invdet); + inv[3] = (float)(t[0] * invdet); + inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); +} + +static void nsvg__xformPremultiply(float* t, float* s) +{ + float s2[6]; + memcpy(s2, s, sizeof(float)*6); + nsvg__xformMultiply(s2, t); + memcpy(t, s2, sizeof(float)*6); +} + +static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2] + t[4]; + *dy = x*t[1] + y*t[3] + t[5]; +} + +static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) +{ + *dx = x*t[0] + y*t[2]; + *dy = x*t[1] + y*t[3]; +} + +#define NSVG_EPSILON (1e-12) + +static int nsvg__ptInBounds(float* pt, float* bounds) +{ + return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; +} + + +static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) +{ + double it = 1.0-t; + return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; +} + +static void nsvg__curveBounds(float* bounds, float* curve) +{ + int i, j, count; + double roots[2], a, b, c, b2ac, t, v; + float* v0 = &curve[0]; + float* v1 = &curve[2]; + float* v2 = &curve[4]; + float* v3 = &curve[6]; + + // Start the bounding box by end points + bounds[0] = nsvg__minf(v0[0], v3[0]); + bounds[1] = nsvg__minf(v0[1], v3[1]); + bounds[2] = nsvg__maxf(v0[0], v3[0]); + bounds[3] = nsvg__maxf(v0[1], v3[1]); + + // Bezier curve fits inside the convex hull of it's control points. + // If control points are inside the bounds, we're done. + if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) + return; + + // Add bezier curve inflection points in X and Y. + for (i = 0; i < 2; i++) { + a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; + b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; + c = 3.0 * v1[i] - 3.0 * v0[i]; + count = 0; + if (fabs(a) < NSVG_EPSILON) { + if (fabs(b) > NSVG_EPSILON) { + t = -c / b; + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } else { + b2ac = b*b - 4.0*c*a; + if (b2ac > NSVG_EPSILON) { + t = (-b + sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + t = (-b - sqrt(b2ac)) / (2.0 * a); + if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) + roots[count++] = t; + } + } + for (j = 0; j < count; j++) { + v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); + bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); + bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); + } + } +} + +static NSVGparser* nsvg__createParser() +{ + NSVGparser* p; + p = (NSVGparser*)NANOSVG_malloc(sizeof(NSVGparser)); + if (p == NULL) goto error; + memset(p, 0, sizeof(NSVGparser)); + + p->image = (NSVGimage*)NANOSVG_malloc(sizeof(NSVGimage)); + if (p->image == NULL) goto error; + memset(p->image, 0, sizeof(NSVGimage)); + + // Init style + nsvg__xformIdentity(p->attr[0].xform); + memset(p->attr[0].id, 0, sizeof p->attr[0].id); + p->attr[0].fillColor = NSVG_RGB(0,0,0); + p->attr[0].strokeColor = NSVG_RGB(0,0,0); + p->attr[0].opacity = 1; + p->attr[0].fillOpacity = 1; + p->attr[0].strokeOpacity = 1; + p->attr[0].stopOpacity = 1; + p->attr[0].strokeWidth = 1; + p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; + p->attr[0].strokeLineCap = NSVG_CAP_BUTT; + p->attr[0].miterLimit = 4; + p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; + p->attr[0].hasFill = 1; + p->attr[0].visible = NSVG_VIS_DISPLAY | NSVG_VIS_VISIBLE; + + return p; + +error: + if (p) { + if (p->image) NANOSVG_free(p->image); + NANOSVG_free(p); + } + return NULL; +} + +static void nsvg__deleteStyles(NSVGstyles* style) { + while (style) { + NSVGstyles *next = style->next; + if (style->name!= NULL) + free(style->name); + if (style->description != NULL) + free(style->description); + free(style); + style = next; + } +} + +static void nsvg__deletePaths(NSVGpath* path) +{ + while (path) { + NSVGpath *next = path->next; + if (path->pts != NULL) + NANOSVG_free(path->pts); + NANOSVG_free(path); + path = next; + } +} + +static void nsvg__deletePaint(NSVGpaint* paint) +{ + if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) + NANOSVG_free(paint->gradient); +} + +static void nsvg__deleteGradientData(NSVGgradientData* grad) +{ + NSVGgradientData* next; + while (grad != NULL) { + next = grad->next; + NANOSVG_free(grad->stops); + NANOSVG_free(grad); + grad = next; + } +} + +static void nsvg__deleteParser(NSVGparser* p) +{ + if (p != NULL) { + nsvg__deleteStyles(p->styles); + nsvg__deletePaths(p->plist); + nsvg__deleteGradientData(p->gradients); + nsvgDelete(p->image); + NANOSVG_free(p->pts); + NANOSVG_free(p); + } +} + +static void nsvg__resetPath(NSVGparser* p) +{ + p->npts = 0; +} + +static void nsvg__addPoint(NSVGparser* p, float x, float y) +{ + if (p->npts+1 > p->cpts) { + p->cpts = p->cpts ? p->cpts*2 : 8; + p->pts = (float*)NANOSVG_realloc(p->pts, p->cpts*2*sizeof(float)); + if (!p->pts) return; + } + p->pts[p->npts*2+0] = x; + p->pts[p->npts*2+1] = y; + p->npts++; +} + +static void nsvg__moveTo(NSVGparser* p, float x, float y) +{ + if (p->npts > 0) { + p->pts[(p->npts-1)*2+0] = x; + p->pts[(p->npts-1)*2+1] = y; + } else { + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__lineTo(NSVGparser* p, float x, float y) +{ + float px,py, dx,dy; + if (p->npts > 0) { + px = p->pts[(p->npts-1)*2+0]; + py = p->pts[(p->npts-1)*2+1]; + dx = x - px; + dy = y - py; + nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); + nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); + nsvg__addPoint(p, x, y); + } +} + +static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) +{ + nsvg__addPoint(p, cpx1, cpy1); + nsvg__addPoint(p, cpx2, cpy2); + nsvg__addPoint(p, x, y); +} + +static NSVGattrib* nsvg__getAttr(NSVGparser* p) +{ + return &p->attr[p->attrHead]; +} + +static void nsvg__pushAttr(NSVGparser* p) +{ + if (p->attrHead < NSVG_MAX_ATTR-1) { + p->attrHead++; + memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); + } +} + +static void nsvg__popAttr(NSVGparser* p) +{ + if (p->attrHead > 0) + p->attrHead--; +} + +static float nsvg__actualOrigX(NSVGparser* p) +{ + return p->viewMinx; +} + +static float nsvg__actualOrigY(NSVGparser* p) +{ + return p->viewMiny; +} + +static float nsvg__actualWidth(NSVGparser* p) +{ + return p->viewWidth; +} + +static float nsvg__actualHeight(NSVGparser* p) +{ + return p->viewHeight; +} + +static float nsvg__actualLength(NSVGparser* p) +{ + float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); + return sqrtf(w*w + h*h) / sqrtf(2.0f); +} + +static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) +{ + NSVGattrib* attr = nsvg__getAttr(p); + switch (c.units) { + case NSVG_UNITS_USER: return c.value; + case NSVG_UNITS_PX: return c.value; + case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; + case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; + case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; + case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; + case NSVG_UNITS_IN: return c.value * p->dpi; + case NSVG_UNITS_EM: return c.value * attr->fontSize; + case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. + case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; + default: return c.value; + } + return c.value; +} + +static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) +{ + NSVGgradientData* grad = p->gradients; + while (grad) { + if (strcmp(grad->id, id) == 0) + return grad; + grad = grad->next; + } + return NULL; +} + +static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, char* paintType) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGgradientData* data = NULL; + NSVGgradientData* ref = NULL; + NSVGgradientStop* stops = NULL; + NSVGgradient* grad; + float ox, oy, sw, sh, sl; + int nstops = 0; + + data = nsvg__findGradientData(p, id); + if (data == NULL) return NULL; + + // TODO: use ref to fill in all unset values too. + ref = data; + while (ref != NULL) { + if (stops == NULL && ref->stops != NULL) { + stops = ref->stops; + nstops = ref->nstops; + break; + } + ref = nsvg__findGradientData(p, ref->ref); + } + if (stops == NULL) return NULL; + + grad = (NSVGgradient*)NANOSVG_malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); + if (grad == NULL) return NULL; + + // The shape width and height. + if (data->units == NSVG_OBJECT_SPACE) { + ox = localBounds[0]; + oy = localBounds[1]; + sw = localBounds[2] - localBounds[0]; + sh = localBounds[3] - localBounds[1]; + } else { + ox = nsvg__actualOrigX(p); + oy = nsvg__actualOrigY(p); + sw = nsvg__actualWidth(p); + sh = nsvg__actualHeight(p); + } + sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); + + if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { + float x1, y1, x2, y2, dx, dy; + x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); + y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); + x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); + y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); + // Calculate transform aligned to the line + dx = x2 - x1; + dy = y2 - y1; + grad->xform[0] = dy; grad->xform[1] = -dx; + grad->xform[2] = dx; grad->xform[3] = dy; + grad->xform[4] = x1; grad->xform[5] = y1; + } else { + float cx, cy, fx, fy, r; + cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); + cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); + fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); + fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); + r = nsvg__convertToPixels(p, data->radial.r, 0, sl); + // Calculate transform aligned to the circle + grad->xform[0] = r; grad->xform[1] = 0; + grad->xform[2] = 0; grad->xform[3] = r; + grad->xform[4] = cx; grad->xform[5] = cy; + grad->fx = fx / r; + grad->fy = fy / r; + } + + nsvg__xformMultiply(grad->xform, data->xform); + nsvg__xformMultiply(grad->xform, attr->xform); + + grad->spread = data->spread; + memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); + grad->nstops = nstops; + + *paintType = data->type; + + return grad; +} + +static float nsvg__getAverageScale(float* t) +{ + float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); + float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); + return (sx + sy) * 0.5f; +} + +static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) +{ + NSVGpath* path; + float curve[4*2], curveBounds[4]; + int i, first = 1; + for (path = shape->paths; path != NULL; path = path->next) { + nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); + for (i = 0; i < path->npts-1; i += 3) { + nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); + nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); + nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); + nsvg__curveBounds(curveBounds, curve); + if (first) { + bounds[0] = curveBounds[0]; + bounds[1] = curveBounds[1]; + bounds[2] = curveBounds[2]; + bounds[3] = curveBounds[3]; + first = 0; + } else { + bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); + bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); + bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); + bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); + } + curve[0] = curve[6]; + curve[1] = curve[7]; + } + } +} + +static void nsvg__addShape(NSVGparser* p) +{ + NSVGattrib* attr = nsvg__getAttr(p); + float scale = 1.0f; + NSVGshape* shape; + NSVGpath* path; + int i; + + if (p->plist == NULL) + return; + + shape = (NSVGshape*)NANOSVG_malloc(sizeof(NSVGshape)); + if (shape == NULL) goto error; + memset(shape, 0, sizeof(NSVGshape)); + + memcpy(shape->id, attr->id, sizeof shape->id); + scale = nsvg__getAverageScale(attr->xform); + shape->strokeWidth = attr->strokeWidth * scale; + shape->strokeDashOffset = attr->strokeDashOffset * scale; + shape->strokeDashCount = (char)attr->strokeDashCount; + for (i = 0; i < attr->strokeDashCount; i++) + shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; + shape->strokeLineJoin = attr->strokeLineJoin; + shape->strokeLineCap = attr->strokeLineCap; + shape->miterLimit = attr->miterLimit; + shape->fillRule = attr->fillRule; + shape->opacity = attr->opacity; + + shape->paths = p->plist; + p->plist = NULL; + + // Calculate shape bounds + shape->bounds[0] = shape->paths->bounds[0]; + shape->bounds[1] = shape->paths->bounds[1]; + shape->bounds[2] = shape->paths->bounds[2]; + shape->bounds[3] = shape->paths->bounds[3]; + for (path = shape->paths->next; path != NULL; path = path->next) { + shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); + shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); + shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); + shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); + } + + // Set fill + if (attr->hasFill == 0) { + shape->fill.type = NSVG_PAINT_NONE; + } else if (attr->hasFill == 1) { + shape->fill.type = NSVG_PAINT_COLOR; + shape->fill.color = attr->fillColor; + shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; + } else if (attr->hasFill == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->fill.gradient = nsvg__createGradient(p, attr->fillGradient, localBounds, &shape->fill.type); + if (shape->fill.gradient == NULL) { + shape->fill.type = NSVG_PAINT_NONE; + } + } + + // Set stroke + if (attr->hasStroke == 0) { + shape->stroke.type = NSVG_PAINT_NONE; + } else if (attr->hasStroke == 1) { + shape->stroke.type = NSVG_PAINT_COLOR; + shape->stroke.color = attr->strokeColor; + shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; + } else if (attr->hasStroke == 2) { + float inv[6], localBounds[4]; + nsvg__xformInverse(inv, attr->xform); + nsvg__getLocalBounds(localBounds, shape, inv); + shape->stroke.gradient = nsvg__createGradient(p, attr->strokeGradient, localBounds, &shape->stroke.type); + if (shape->stroke.gradient == NULL) + shape->stroke.type = NSVG_PAINT_NONE; + } + + // Set flags + shape->flags = ((attr->visible & NSVG_VIS_DISPLAY) && (attr->visible & NSVG_VIS_VISIBLE) ? NSVG_FLAGS_VISIBLE : 0x00); + + // Add to tail + if (p->image->shapes == NULL) + p->image->shapes = shape; + else + p->shapesTail->next = shape; + p->shapesTail = shape; + + return; + +error: + if (shape) NANOSVG_free(shape); +} + +static void nsvg__addPath(NSVGparser* p, char closed) +{ + NSVGattrib* attr = nsvg__getAttr(p); + NSVGpath* path = NULL; + float bounds[4]; + float* curve; + int i; + + if (p->npts < 4) + return; + + if (closed) + nsvg__lineTo(p, p->pts[0], p->pts[1]); + + path = (NSVGpath*)NANOSVG_malloc(sizeof(NSVGpath)); + if (path == NULL) goto error; + memset(path, 0, sizeof(NSVGpath)); + + path->pts = (float*)NANOSVG_malloc(p->npts*2*sizeof(float)); + if (path->pts == NULL) goto error; + path->closed = closed; + path->npts = p->npts; + + // Transform path. + for (i = 0; i < p->npts; ++i) + nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); + + // Find bounds + for (i = 0; i < path->npts-1; i += 3) { + curve = &path->pts[i*2]; + nsvg__curveBounds(bounds, curve); + if (i == 0) { + path->bounds[0] = bounds[0]; + path->bounds[1] = bounds[1]; + path->bounds[2] = bounds[2]; + path->bounds[3] = bounds[3]; + } else { + path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); + path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); + path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); + path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); + } + } + + path->next = p->plist; + p->plist = path; + + return; + +error: + if (path != NULL) { + if (path->pts != NULL) NANOSVG_free(path->pts); + NANOSVG_free(path); + } +} + +// We roll our own string to float because the std library one uses locale and messes things up. +static double nsvg__atof(const char* s) +{ + char* cur = (char*)s; + char* end = NULL; + double res = 0.0, sign = 1.0; +#if (_MSC_VER == 1200) + __int64 intPart = 0, fracPart = 0; +#else + long long intPart = 0, fracPart = 0; +#endif + char hasIntPart = 0, hasFracPart = 0; + + // Parse optional sign + if (*cur == '+') { + cur++; + } else if (*cur == '-') { + sign = -1; + cur++; + } + + // Parse integer part + if (nsvg__isdigit(*cur)) { + // Parse digit sequence +#if (_MSC_VER == 1200) + intPart = strtol(cur, &end, 10); +#else + intPart = strtoll(cur, &end, 10); +#endif + if (cur != end) { + res = (double)intPart; + hasIntPart = 1; + cur = end; + } + } + + // Parse fractional part. + if (*cur == '.') { + cur++; // Skip '.' + if (nsvg__isdigit(*cur)) { + // Parse digit sequence +#if (_MSC_VER == 1200) + fracPart = strtol(cur, &end, 10); +#else + fracPart = strtoll(cur, &end, 10); +#endif + if (cur != end) { + res += (double)fracPart / pow(10.0, (double)(end - cur)); + hasFracPart = 1; + cur = end; + } + } + } + + // A valid number should have integer or fractional part. + if (!hasIntPart && !hasFracPart) + return 0.0; + + // Parse optional exponent + if (*cur == 'e' || *cur == 'E') { + int expPart = 0; + cur++; // skip 'E' + expPart = strtol(cur, &end, 10); // Parse digit sequence with sign + if (cur != end) { + res *= pow(10.0, (double)expPart); + } + } + + return res * sign; +} + + +static const char* nsvg__parseNumber(const char* s, char* it, const int size) +{ + const int last = size-1; + int i = 0; + + // sign + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + // integer part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + if (*s == '.') { + // decimal point + if (i < last) it[i++] = *s; + s++; + // fraction part + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + // exponent + if (*s == 'e' || *s == 'E') { + if (i < last) it[i++] = *s; + s++; + if (*s == '-' || *s == '+') { + if (i < last) it[i++] = *s; + s++; + } + while (*s && nsvg__isdigit(*s)) { + if (i < last) it[i++] = *s; + s++; + } + } + it[i] = '\0'; + + return s; +} + +static const char* nsvg__getNextPathItem(const char* s, char* it) +{ + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + if (!*s) return s; + if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { + s = nsvg__parseNumber(s, it, 64); + } else { + // Parse command + it[0] = *s++; + it[1] = '\0'; + return s; + } + + return s; +} + +static unsigned int nsvg__parseColorHex(const char* str) +{ + unsigned int c = 0, r = 0, g = 0, b = 0; + int n = 0; + str++; // skip # + // Calculate number of characters. + while(str[n] && !nsvg__isspace(str[n])) + n++; + if (n == 6) { + sscanf(str, "%x", &c); + } else if (n == 3) { + sscanf(str, "%x", &c); + c = (c&0xf) | ((c&0xf0) << 4) | ((c&0xf00) << 8); + c |= c<<4; + } + r = (c >> 16) & 0xff; + g = (c >> 8) & 0xff; + b = c & 0xff; + return NSVG_RGB(r,g,b); +} + +static unsigned int nsvg__parseColorRGB(const char* str) +{ + int r = -1, g = -1, b = -1; + char s1[32]="", s2[32]=""; + sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b); + if (strchr(s1, '%')) { + return NSVG_RGB((r*255)/100,(g*255)/100,(b*255)/100); + } else { + return NSVG_RGB(r,g,b); + } +} + +typedef struct NSVGNamedColor { + const char* name; + unsigned int color; +} NSVGNamedColor; + +NSVGNamedColor nsvg__colors[] = { + + { "red", NSVG_RGB(255, 0, 0) }, + { "green", NSVG_RGB( 0, 128, 0) }, + { "blue", NSVG_RGB( 0, 0, 255) }, + { "yellow", NSVG_RGB(255, 255, 0) }, + { "cyan", NSVG_RGB( 0, 255, 255) }, + { "magenta", NSVG_RGB(255, 0, 255) }, + { "black", NSVG_RGB( 0, 0, 0) }, + { "grey", NSVG_RGB(128, 128, 128) }, + { "gray", NSVG_RGB(128, 128, 128) }, + { "white", NSVG_RGB(255, 255, 255) }, + +#ifdef NANOSVG_ALL_COLOR_KEYWORDS + { "aliceblue", NSVG_RGB(240, 248, 255) }, + { "antiquewhite", NSVG_RGB(250, 235, 215) }, + { "aqua", NSVG_RGB( 0, 255, 255) }, + { "aquamarine", NSVG_RGB(127, 255, 212) }, + { "azure", NSVG_RGB(240, 255, 255) }, + { "beige", NSVG_RGB(245, 245, 220) }, + { "bisque", NSVG_RGB(255, 228, 196) }, + { "blanchedalmond", NSVG_RGB(255, 235, 205) }, + { "blueviolet", NSVG_RGB(138, 43, 226) }, + { "brown", NSVG_RGB(165, 42, 42) }, + { "burlywood", NSVG_RGB(222, 184, 135) }, + { "cadetblue", NSVG_RGB( 95, 158, 160) }, + { "chartreuse", NSVG_RGB(127, 255, 0) }, + { "chocolate", NSVG_RGB(210, 105, 30) }, + { "coral", NSVG_RGB(255, 127, 80) }, + { "cornflowerblue", NSVG_RGB(100, 149, 237) }, + { "cornsilk", NSVG_RGB(255, 248, 220) }, + { "crimson", NSVG_RGB(220, 20, 60) }, + { "darkblue", NSVG_RGB( 0, 0, 139) }, + { "darkcyan", NSVG_RGB( 0, 139, 139) }, + { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, + { "darkgray", NSVG_RGB(169, 169, 169) }, + { "darkgreen", NSVG_RGB( 0, 100, 0) }, + { "darkgrey", NSVG_RGB(169, 169, 169) }, + { "darkkhaki", NSVG_RGB(189, 183, 107) }, + { "darkmagenta", NSVG_RGB(139, 0, 139) }, + { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, + { "darkorange", NSVG_RGB(255, 140, 0) }, + { "darkorchid", NSVG_RGB(153, 50, 204) }, + { "darkred", NSVG_RGB(139, 0, 0) }, + { "darksalmon", NSVG_RGB(233, 150, 122) }, + { "darkseagreen", NSVG_RGB(143, 188, 143) }, + { "darkslateblue", NSVG_RGB( 72, 61, 139) }, + { "darkslategray", NSVG_RGB( 47, 79, 79) }, + { "darkslategrey", NSVG_RGB( 47, 79, 79) }, + { "darkturquoise", NSVG_RGB( 0, 206, 209) }, + { "darkviolet", NSVG_RGB(148, 0, 211) }, + { "deeppink", NSVG_RGB(255, 20, 147) }, + { "deepskyblue", NSVG_RGB( 0, 191, 255) }, + { "dimgray", NSVG_RGB(105, 105, 105) }, + { "dimgrey", NSVG_RGB(105, 105, 105) }, + { "dodgerblue", NSVG_RGB( 30, 144, 255) }, + { "firebrick", NSVG_RGB(178, 34, 34) }, + { "floralwhite", NSVG_RGB(255, 250, 240) }, + { "forestgreen", NSVG_RGB( 34, 139, 34) }, + { "fuchsia", NSVG_RGB(255, 0, 255) }, + { "gainsboro", NSVG_RGB(220, 220, 220) }, + { "ghostwhite", NSVG_RGB(248, 248, 255) }, + { "gold", NSVG_RGB(255, 215, 0) }, + { "goldenrod", NSVG_RGB(218, 165, 32) }, + { "greenyellow", NSVG_RGB(173, 255, 47) }, + { "honeydew", NSVG_RGB(240, 255, 240) }, + { "hotpink", NSVG_RGB(255, 105, 180) }, + { "indianred", NSVG_RGB(205, 92, 92) }, + { "indigo", NSVG_RGB( 75, 0, 130) }, + { "ivory", NSVG_RGB(255, 255, 240) }, + { "khaki", NSVG_RGB(240, 230, 140) }, + { "lavender", NSVG_RGB(230, 230, 250) }, + { "lavenderblush", NSVG_RGB(255, 240, 245) }, + { "lawngreen", NSVG_RGB(124, 252, 0) }, + { "lemonchiffon", NSVG_RGB(255, 250, 205) }, + { "lightblue", NSVG_RGB(173, 216, 230) }, + { "lightcoral", NSVG_RGB(240, 128, 128) }, + { "lightcyan", NSVG_RGB(224, 255, 255) }, + { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, + { "lightgray", NSVG_RGB(211, 211, 211) }, + { "lightgreen", NSVG_RGB(144, 238, 144) }, + { "lightgrey", NSVG_RGB(211, 211, 211) }, + { "lightpink", NSVG_RGB(255, 182, 193) }, + { "lightsalmon", NSVG_RGB(255, 160, 122) }, + { "lightseagreen", NSVG_RGB( 32, 178, 170) }, + { "lightskyblue", NSVG_RGB(135, 206, 250) }, + { "lightslategray", NSVG_RGB(119, 136, 153) }, + { "lightslategrey", NSVG_RGB(119, 136, 153) }, + { "lightsteelblue", NSVG_RGB(176, 196, 222) }, + { "lightyellow", NSVG_RGB(255, 255, 224) }, + { "lime", NSVG_RGB( 0, 255, 0) }, + { "limegreen", NSVG_RGB( 50, 205, 50) }, + { "linen", NSVG_RGB(250, 240, 230) }, + { "maroon", NSVG_RGB(128, 0, 0) }, + { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, + { "mediumblue", NSVG_RGB( 0, 0, 205) }, + { "mediumorchid", NSVG_RGB(186, 85, 211) }, + { "mediumpurple", NSVG_RGB(147, 112, 219) }, + { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, + { "mediumslateblue", NSVG_RGB(123, 104, 238) }, + { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, + { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, + { "mediumvioletred", NSVG_RGB(199, 21, 133) }, + { "midnightblue", NSVG_RGB( 25, 25, 112) }, + { "mintcream", NSVG_RGB(245, 255, 250) }, + { "mistyrose", NSVG_RGB(255, 228, 225) }, + { "moccasin", NSVG_RGB(255, 228, 181) }, + { "navajowhite", NSVG_RGB(255, 222, 173) }, + { "navy", NSVG_RGB( 0, 0, 128) }, + { "oldlace", NSVG_RGB(253, 245, 230) }, + { "olive", NSVG_RGB(128, 128, 0) }, + { "olivedrab", NSVG_RGB(107, 142, 35) }, + { "orange", NSVG_RGB(255, 165, 0) }, + { "orangered", NSVG_RGB(255, 69, 0) }, + { "orchid", NSVG_RGB(218, 112, 214) }, + { "palegoldenrod", NSVG_RGB(238, 232, 170) }, + { "palegreen", NSVG_RGB(152, 251, 152) }, + { "paleturquoise", NSVG_RGB(175, 238, 238) }, + { "palevioletred", NSVG_RGB(219, 112, 147) }, + { "papayawhip", NSVG_RGB(255, 239, 213) }, + { "peachpuff", NSVG_RGB(255, 218, 185) }, + { "peru", NSVG_RGB(205, 133, 63) }, + { "pink", NSVG_RGB(255, 192, 203) }, + { "plum", NSVG_RGB(221, 160, 221) }, + { "powderblue", NSVG_RGB(176, 224, 230) }, + { "purple", NSVG_RGB(128, 0, 128) }, + { "rosybrown", NSVG_RGB(188, 143, 143) }, + { "royalblue", NSVG_RGB( 65, 105, 225) }, + { "saddlebrown", NSVG_RGB(139, 69, 19) }, + { "salmon", NSVG_RGB(250, 128, 114) }, + { "sandybrown", NSVG_RGB(244, 164, 96) }, + { "seagreen", NSVG_RGB( 46, 139, 87) }, + { "seashell", NSVG_RGB(255, 245, 238) }, + { "sienna", NSVG_RGB(160, 82, 45) }, + { "silver", NSVG_RGB(192, 192, 192) }, + { "skyblue", NSVG_RGB(135, 206, 235) }, + { "slateblue", NSVG_RGB(106, 90, 205) }, + { "slategray", NSVG_RGB(112, 128, 144) }, + { "slategrey", NSVG_RGB(112, 128, 144) }, + { "snow", NSVG_RGB(255, 250, 250) }, + { "springgreen", NSVG_RGB( 0, 255, 127) }, + { "steelblue", NSVG_RGB( 70, 130, 180) }, + { "tan", NSVG_RGB(210, 180, 140) }, + { "teal", NSVG_RGB( 0, 128, 128) }, + { "thistle", NSVG_RGB(216, 191, 216) }, + { "tomato", NSVG_RGB(255, 99, 71) }, + { "turquoise", NSVG_RGB( 64, 224, 208) }, + { "violet", NSVG_RGB(238, 130, 238) }, + { "wheat", NSVG_RGB(245, 222, 179) }, + { "whitesmoke", NSVG_RGB(245, 245, 245) }, + { "yellowgreen", NSVG_RGB(154, 205, 50) }, +#endif +}; + +static unsigned int nsvg__parseColorName(const char* str) +{ + int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); + + for (i = 0; i < ncolors; i++) { + if (strcmp(nsvg__colors[i].name, str) == 0) { + return nsvg__colors[i].color; + } + } + + return NSVG_RGB(128, 128, 128); +} + +static unsigned int nsvg__parseColor(const char* str) +{ + size_t len = 0; + while(*str == ' ') ++str; + len = strlen(str); + if (len >= 1 && *str == '#') + return nsvg__parseColorHex(str); + else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') + return nsvg__parseColorRGB(str); + return nsvg__parseColorName(str); +} + +static float nsvg__parseOpacity(const char* str) +{ + float val = 0; + sscanf(str, "%f", &val); + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + return val; +} + +static float nsvg__parseMiterLimit(const char* str) +{ + float val = 0; + sscanf(str, "%f", &val); + if (val < 0.0f) val = 0.0f; + return val; +} + +static int nsvg__parseUnits(const char* units) +{ + if (units[0] == 'p' && units[1] == 'x') + return NSVG_UNITS_PX; + else if (units[0] == 'p' && units[1] == 't') + return NSVG_UNITS_PT; + else if (units[0] == 'p' && units[1] == 'c') + return NSVG_UNITS_PC; + else if (units[0] == 'm' && units[1] == 'm') + return NSVG_UNITS_MM; + else if (units[0] == 'c' && units[1] == 'm') + return NSVG_UNITS_CM; + else if (units[0] == 'i' && units[1] == 'n') + return NSVG_UNITS_IN; + else if (units[0] == '%') + return NSVG_UNITS_PERCENT; + else if (units[0] == 'e' && units[1] == 'm') + return NSVG_UNITS_EM; + else if (units[0] == 'e' && units[1] == 'x') + return NSVG_UNITS_EX; + return NSVG_UNITS_USER; +} + +static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) +{ + NSVGcoordinate coord = {0, NSVG_UNITS_USER}; + char units[32]=""; + sscanf(str, "%f%s", &coord.value, units); + coord.units = nsvg__parseUnits(units); + return coord; +} + +static NSVGcoordinate nsvg__coord(float v, int units) +{ + NSVGcoordinate coord = {v, units}; + return coord; +} + +static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) +{ + NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); + return nsvg__convertToPixels(p, coord, orig, length); +} + +static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) +{ + const char* end; + const char* ptr; + char it[64]; + + *na = 0; + ptr = str; + while (*ptr && *ptr != '(') ++ptr; + if (*ptr == 0) + return 1; + end = ptr; + while (*end && *end != ')') ++end; + if (*end == 0) + return 1; + + while (ptr < end) { + if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { + if (*na >= maxNa) return 0; + ptr = nsvg__parseNumber(ptr, it, 64); + args[(*na)++] = (float)nsvg__atof(it); + } else { + ++ptr; + } + } + return (int)(end - str); +} + + +static int nsvg__parseMatrix(float* xform, const char* str) +{ + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, t, 6, &na); + if (na != 6) return len; + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseTranslate(float* xform, const char* str) +{ + float args[2]; + float t[6]; + int na = 0; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = 0.0; + + nsvg__xformSetTranslation(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseScale(float* xform, const char* str) +{ + float args[2]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 2, &na); + if (na == 1) args[1] = args[0]; + nsvg__xformSetScale(t, args[0], args[1]); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewX(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseSkewY(float* xform, const char* str) +{ + float args[1]; + int na = 0; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 1, &na); + nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); + memcpy(xform, t, sizeof(float)*6); + return len; +} + +static int nsvg__parseRotate(float* xform, const char* str) +{ + float args[3]; + int na = 0; + float m[6]; + float t[6]; + int len = nsvg__parseTransformArgs(str, args, 3, &na); + if (na == 1) + args[1] = args[2] = 0.0f; + nsvg__xformIdentity(m); + + if (na > 1) { + nsvg__xformSetTranslation(t, -args[1], -args[2]); + nsvg__xformMultiply(m, t); + } + + nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); + nsvg__xformMultiply(m, t); + + if (na > 1) { + nsvg__xformSetTranslation(t, args[1], args[2]); + nsvg__xformMultiply(m, t); + } + + memcpy(xform, m, sizeof(float)*6); + + return len; +} + +static void nsvg__parseTransform(float* xform, const char* str) +{ + float t[6]; + nsvg__xformIdentity(xform); + while (*str) + { + if (strncmp(str, "matrix", 6) == 0) + str += nsvg__parseMatrix(t, str); + else if (strncmp(str, "translate", 9) == 0) + str += nsvg__parseTranslate(t, str); + else if (strncmp(str, "scale", 5) == 0) + str += nsvg__parseScale(t, str); + else if (strncmp(str, "rotate", 6) == 0) + str += nsvg__parseRotate(t, str); + else if (strncmp(str, "skewX", 5) == 0) + str += nsvg__parseSkewX(t, str); + else if (strncmp(str, "skewY", 5) == 0) + str += nsvg__parseSkewY(t, str); + else{ + ++str; + continue; + } + + nsvg__xformPremultiply(xform, t); + } +} + +static void nsvg__parseUrl(char* id, const char* str) +{ + int i = 0; + str += 4; // "url("; + if (*str == '#') + str++; + while (i < 63 && *str != ')') { + id[i] = *str++; + i++; + } + id[i] = '\0'; +} + +static char nsvg__parseLineCap(const char* str) +{ + if (strcmp(str, "butt") == 0) + return NSVG_CAP_BUTT; + else if (strcmp(str, "round") == 0) + return NSVG_CAP_ROUND; + else if (strcmp(str, "square") == 0) + return NSVG_CAP_SQUARE; + // TODO: handle inherit. + return NSVG_CAP_BUTT; +} + +static char nsvg__parseLineJoin(const char* str) +{ + if (strcmp(str, "miter") == 0) + return NSVG_JOIN_MITER; + else if (strcmp(str, "round") == 0) + return NSVG_JOIN_ROUND; + else if (strcmp(str, "bevel") == 0) + return NSVG_JOIN_BEVEL; + // TODO: handle inherit. + return NSVG_JOIN_MITER; +} + +static char nsvg__parseFillRule(const char* str) +{ + if (strcmp(str, "nonzero") == 0) + return NSVG_FILLRULE_NONZERO; + else if (strcmp(str, "evenodd") == 0) + return NSVG_FILLRULE_EVENODD; + // TODO: handle inherit. + return NSVG_FILLRULE_NONZERO; +} + +static const char* nsvg__getNextDashItem(const char* s, char* it) +{ + int n = 0; + it[0] = '\0'; + // Skip white spaces and commas + while (*s && (nsvg__isspace(*s) || *s == ',')) s++; + // Advance until whitespace, comma or end. + while (*s && (!nsvg__isspace(*s) && *s != ',')) { + if (n < 63) + it[n++] = *s; + s++; + } + it[n++] = '\0'; + return s; +} + +static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) +{ + char item[64]; + int count = 0, i; + float sum = 0.0f; + + // Handle "none" + if (str[0] == 'n') + return 0; + + // Parse dashes + while (*str) { + str = nsvg__getNextDashItem(str, item); + if (!*item) break; + if (count < NSVG_MAX_DASHES) + strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); + } + + for (i = 0; i < count; i++) + sum += strokeDashArray[i]; + if (sum <= 1e-6f) + count = 0; + + return count; +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str); + +static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) +{ + float xform[6]; + NSVGattrib* attr = nsvg__getAttr(p); + if (!attr) return 0; + + if (strcmp(name, "style") == 0) { + nsvg__parseStyle(p, value); + } else if (strcmp(name, "display") == 0) { + if (strcmp(value, "none") == 0) + attr->visible &= ~NSVG_VIS_DISPLAY; + // Don't reset ->visible on display:inline, one display:none hides the whole subtree + + } else if (strcmp(name, "visibility") == 0) { + if (strcmp(value, "hidden") == 0) { + attr->visible &= ~NSVG_VIS_VISIBLE; + } else if (strcmp(value, "visible") == 0) { + attr->visible |= NSVG_VIS_VISIBLE; + } + } else if (strcmp(name, "fill") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasFill = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasFill = 2; + nsvg__parseUrl(attr->fillGradient, value); + } else { + attr->hasFill = 1; + attr->fillColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "opacity") == 0) { + attr->opacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "fill-opacity") == 0) { + attr->fillOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke") == 0) { + if (strcmp(value, "none") == 0) { + attr->hasStroke = 0; + } else if (strncmp(value, "url(", 4) == 0) { + attr->hasStroke = 2; + nsvg__parseUrl(attr->strokeGradient, value); + } else { + attr->hasStroke = 1; + attr->strokeColor = nsvg__parseColor(value); + } + } else if (strcmp(name, "stroke-width") == 0) { + attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-dasharray") == 0) { + attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); + } else if (strcmp(name, "stroke-dashoffset") == 0) { + attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "stroke-opacity") == 0) { + attr->strokeOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "stroke-linecap") == 0) { + attr->strokeLineCap = nsvg__parseLineCap(value); + } else if (strcmp(name, "stroke-linejoin") == 0) { + attr->strokeLineJoin = nsvg__parseLineJoin(value); + } else if (strcmp(name, "stroke-miterlimit") == 0) { + attr->miterLimit = nsvg__parseMiterLimit(value); + } else if (strcmp(name, "fill-rule") == 0) { + attr->fillRule = nsvg__parseFillRule(value); + } else if (strcmp(name, "font-size") == 0) { + attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); + } else if (strcmp(name, "transform") == 0) { + nsvg__parseTransform(xform, value); + nsvg__xformPremultiply(attr->xform, xform); + } else if (strcmp(name, "stop-color") == 0) { + attr->stopColor = nsvg__parseColor(value); + } else if (strcmp(name, "stop-opacity") == 0) { + attr->stopOpacity = nsvg__parseOpacity(value); + } else if (strcmp(name, "offset") == 0) { + attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); + } else if (strcmp(name, "id") == 0) { + strncpy(attr->id, value, 63); + attr->id[63] = '\0'; + } else if (strcmp(name, "class") == 0) { + NSVGstyles* style = p->styles; + while (style) { + if (strcmp(style->name + 1, value) == 0) { + break; + } + style = style->next; + } + if (style) { + nsvg__parseStyle(p, style->description); + } + } else { + return 0; + } + return 1; +} + +static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) +{ + const char* str; + const char* val; + char name[512]; + char value[512]; + int n; + + str = start; + while (str < end && *str != ':') ++str; + + val = str; + + // Right Trim + while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; + ++str; + + n = (int)(str - start); + if (n > 511) n = 511; + if (n) memcpy(name, start, n); + name[n] = 0; + + while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; + + n = (int)(end - val); + if (n > 511) n = 511; + if (n) memcpy(value, val, n); + value[n] = 0; + + return nsvg__parseAttr(p, name, value); +} + +static void nsvg__parseStyle(NSVGparser* p, const char* str) +{ + const char* start; + const char* end; + + while (*str) { + // Left Trim + while(*str && nsvg__isspace(*str)) ++str; + start = str; + while(*str && *str != ';') ++str; + end = str; + + // Right Trim + while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; + ++end; + + nsvg__parseNameValue(p, start, end); + if (*str) ++str; + } +} + +static void nsvg__parseAttribs(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) + { + if (strcmp(attr[i], "style") == 0) + nsvg__parseStyle(p, attr[i + 1]); + else + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } +} + +static int nsvg__getArgsPerElement(char cmd) +{ + switch (cmd) { + case 'v': + case 'V': + case 'h': + case 'H': + return 1; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + return 2; + case 'q': + case 'Q': + case 's': + case 'S': + return 4; + case 'c': + case 'C': + return 6; + case 'a': + case 'A': + return 7; + } + return 0; +} + +static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__moveTo(p, *cpx, *cpy); +} + +static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) { + *cpx += args[0]; + *cpy += args[1]; + } else { + *cpx = args[0]; + *cpy = args[1]; + } + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpx += args[0]; + else + *cpx = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + if (rel) + *cpy += args[0]; + else + *cpy = args[0]; + nsvg__lineTo(p, *cpx, *cpy); +} + +static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x2, y2, cx1, cy1, cx2, cy2; + + if (rel) { + cx1 = *cpx + args[0]; + cy1 = *cpy + args[1]; + cx2 = *cpx + args[2]; + cy2 = *cpy + args[3]; + x2 = *cpx + args[4]; + y2 = *cpy + args[5]; + } else { + cx1 = args[0]; + cy1 = args[1]; + cx2 = args[2]; + cy2 = args[3]; + x2 = args[4]; + y2 = args[5]; + } + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx2 = *cpx + args[0]; + cy2 = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx2 = args[0]; + cy2 = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + cx1 = 2*x1 - *cpx2; + cy1 = 2*y1 - *cpy2; + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx2; + *cpy2 = cy2; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + cx = *cpx + args[0]; + cy = *cpy + args[1]; + x2 = *cpx + args[2]; + y2 = *cpy + args[3]; + } else { + cx = args[0]; + cy = args[1]; + x2 = args[2]; + y2 = args[3]; + } + + // Convert to cubic bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, + float* cpx2, float* cpy2, float* args, int rel) +{ + float x1, y1, x2, y2, cx, cy; + float cx1, cy1, cx2, cy2; + + x1 = *cpx; + y1 = *cpy; + if (rel) { + x2 = *cpx + args[0]; + y2 = *cpy + args[1]; + } else { + x2 = args[0]; + y2 = args[1]; + } + + cx = 2*x1 - *cpx2; + cy = 2*y1 - *cpy2; + + // Convert to cubix bezier + cx1 = x1 + 2.0f/3.0f*(cx - x1); + cy1 = y1 + 2.0f/3.0f*(cy - y1); + cx2 = x2 + 2.0f/3.0f*(cx - x2); + cy2 = y2 + 2.0f/3.0f*(cy - y2); + + nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); + + *cpx2 = cx; + *cpy2 = cy; + *cpx = x2; + *cpy = y2; +} + +static float nsvg__sqr(float x) { return x*x; } +static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } + +static float nsvg__vecrat(float ux, float uy, float vx, float vy) +{ + return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); +} + +static float nsvg__vecang(float ux, float uy, float vx, float vy) +{ + float r = nsvg__vecrat(ux,uy, vx,vy); + if (r < -1.0f) r = -1.0f; + if (r > 1.0f) r = 1.0f; + return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); +} + +static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) +{ + // Ported from canvg (https://code.google.com/p/canvg/) + float rx, ry, rotx; + float x1, y1, x2, y2, cx, cy, dx, dy, d; + float x1p, y1p, cxp, cyp, s, sa, sb; + float ux, uy, vx, vy, a1, da; + float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; + float sinrx, cosrx; + int fa, fs; + int i, ndivs; + float hda, kappa; + + rx = fabsf(args[0]); // y radius + ry = fabsf(args[1]); // x radius + rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle + fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc + fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction + x1 = *cpx; // start point + y1 = *cpy; + if (rel) { // end point + x2 = *cpx + args[5]; + y2 = *cpy + args[6]; + } else { + x2 = args[5]; + y2 = args[6]; + } + + dx = x1 - x2; + dy = y1 - y2; + d = sqrtf(dx*dx + dy*dy); + if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { + // The arc degenerates to a line + nsvg__lineTo(p, x2, y2); + *cpx = x2; + *cpy = y2; + return; + } + + sinrx = sinf(rotx); + cosrx = cosf(rotx); + + // Convert to center point parameterization. + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + // 1) Compute x1', y1' + x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; + y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; + d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); + if (d > 1) { + d = sqrtf(d); + rx *= d; + ry *= d; + } + // 2) Compute cx', cy' + s = 0.0f; + sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); + sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); + if (sa < 0.0f) sa = 0.0f; + if (sb > 0.0f) + s = sqrtf(sa / sb); + if (fa == fs) + s = -s; + cxp = s * rx * y1p / ry; + cyp = s * -ry * x1p / rx; + + // 3) Compute cx,cy from cx',cy' + cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; + cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; + + // 4) Calculate theta1, and delta theta. + ux = (x1p - cxp) / rx; + uy = (y1p - cyp) / ry; + vx = (-x1p - cxp) / rx; + vy = (-y1p - cyp) / ry; + a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle + da = nsvg__vecang(ux,uy, vx,vy); // Delta angle + +// if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; +// if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; + + if (fs == 0 && da > 0) + da -= 2 * NSVG_PI; + else if (fs == 1 && da < 0) + da += 2 * NSVG_PI; + + // Approximate the arc using cubic spline segments. + t[0] = cosrx; t[1] = sinrx; + t[2] = -sinrx; t[3] = cosrx; + t[4] = cx; t[5] = cy; + + // Split arc into max 90 degree segments. + // The loop assumes an iteration per end point (including start and end), this +1. + ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); + hda = (da / (float)ndivs) / 2.0f; + kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda)); + if (da < 0.0f) + kappa = -kappa; + + for (i = 0; i <= ndivs; i++) { + a = a1 + da * ((float)i/(float)ndivs); + dx = cosf(a); + dy = sinf(a); + nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position + nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent + if (i > 0) + nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); + px = x; + py = y; + ptanx = tanx; + ptany = tany; + } + + *cpx = x2; + *cpy = y2; +} + +static void nsvg__parsePath(NSVGparser* p, const char** attr) +{ + const char* s = NULL; + char cmd = '\0'; + float args[10]; + int nargs; + int rargs = 0; + float cpx, cpy, cpx2, cpy2; + const char* tmp[4]; + char closedFlag; + int i; + char item[64]; + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "d") == 0) { + s = attr[i + 1]; + } else { + tmp[0] = attr[i]; + tmp[1] = attr[i + 1]; + tmp[2] = 0; + tmp[3] = 0; + nsvg__parseAttribs(p, tmp); + } + } + + if (s) { + nsvg__resetPath(p); + cpx = 0; cpy = 0; + cpx2 = 0; cpy2 = 0; + closedFlag = 0; + nargs = 0; + + while (*s) { + s = nsvg__getNextPathItem(s, item); + if (!*item) break; + if (nsvg__isnum(item[0])) { + if (nargs < 10) + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= rargs) { + switch (cmd) { + case 'm': + case 'M': + nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); + // Moveto can be followed by multiple coordinate pairs, + // which should be treated as linetos. + cmd = (cmd == 'm') ? 'l' : 'L'; + rargs = nsvg__getArgsPerElement(cmd); + cpx2 = cpx; cpy2 = cpy; + break; + case 'l': + case 'L': + nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'H': + case 'h': + nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'V': + case 'v': + nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + case 'C': + case 'c': + nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); + break; + case 'S': + case 's': + nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); + break; + case 'Q': + case 'q': + nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); + break; + case 'T': + case 't': + nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); + break; + case 'A': + case 'a': + nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); + cpx2 = cpx; cpy2 = cpy; + break; + default: + if (nargs >= 2) { + cpx = args[nargs-2]; + cpy = args[nargs-1]; + cpx2 = cpx; cpy2 = cpy; + } + break; + } + nargs = 0; + } + } else { + cmd = item[0]; + rargs = nsvg__getArgsPerElement(cmd); + if (cmd == 'M' || cmd == 'm') { + // Commit path. + if (p->npts > 0) + nsvg__addPath(p, closedFlag); + // Start new subpath. + nsvg__resetPath(p); + closedFlag = 0; + nargs = 0; + } else if (cmd == 'Z' || cmd == 'z') { + closedFlag = 1; + // Commit path. + if (p->npts > 0) { + // Move current point to first point + cpx = p->pts[0]; + cpy = p->pts[1]; + cpx2 = cpx; cpy2 = cpy; + nsvg__addPath(p, closedFlag); + } + // Start new subpath. + nsvg__resetPath(p); + nsvg__moveTo(p, cpx, cpy); + closedFlag = 0; + nargs = 0; + } + } + } + // Commit path. + if (p->npts) + nsvg__addPath(p, closedFlag); + } + + nsvg__addShape(p); +} + +static void nsvg__parseRect(NSVGparser* p, const char** attr) +{ + float x = 0.0f; + float y = 0.0f; + float w = 0.0f; + float h = 0.0f; + float rx = -1.0f; // marks not set + float ry = -1.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); + if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx < 0.0f && ry > 0.0f) rx = ry; + if (ry < 0.0f && rx > 0.0f) ry = rx; + if (rx < 0.0f) rx = 0.0f; + if (ry < 0.0f) ry = 0.0f; + if (rx > w/2.0f) rx = w/2.0f; + if (ry > h/2.0f) ry = h/2.0f; + + if (w != 0.0f && h != 0.0f) { + nsvg__resetPath(p); + + if (rx < 0.00001f || ry < 0.0001f) { + nsvg__moveTo(p, x, y); + nsvg__lineTo(p, x+w, y); + nsvg__lineTo(p, x+w, y+h); + nsvg__lineTo(p, x, y+h); + } else { + // Rounded rectangle + nsvg__moveTo(p, x+rx, y); + nsvg__lineTo(p, x+w-rx, y); + nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); + nsvg__lineTo(p, x+w, y+h-ry); + nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); + nsvg__lineTo(p, x+rx, y+h); + nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); + nsvg__lineTo(p, x, y+ry); + nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); + } + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseCircle(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float r = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); + } + } + + if (r > 0.0f) { + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+r, cy); + nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); + nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); + nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); + nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseEllipse(NSVGparser* p, const char** attr) +{ + float cx = 0.0f; + float cy = 0.0f; + float rx = 0.0f; + float ry = 0.0f; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); + if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); + } + } + + if (rx > 0.0f && ry > 0.0f) { + + nsvg__resetPath(p); + + nsvg__moveTo(p, cx+rx, cy); + nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); + nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); + nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); + nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); + + nsvg__addPath(p, 1); + + nsvg__addShape(p); + } +} + +static void nsvg__parseLine(NSVGparser* p, const char** attr) +{ + float x1 = 0.0; + float y1 = 0.0; + float x2 = 0.0; + float y2 = 0.0; + int i; + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); + if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); + } + } + + nsvg__resetPath(p); + + nsvg__moveTo(p, x1, y1); + nsvg__lineTo(p, x2, y2); + + nsvg__addPath(p, 0); + + nsvg__addShape(p); +} + +static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) +{ + int i; + const char* s; + float args[2]; + int nargs, npts = 0; + char item[64]; + + nsvg__resetPath(p); + + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "points") == 0) { + s = attr[i + 1]; + nargs = 0; + while (*s) { + s = nsvg__getNextPathItem(s, item); + args[nargs++] = (float)nsvg__atof(item); + if (nargs >= 2) { + if (npts == 0) + nsvg__moveTo(p, args[0], args[1]); + else + nsvg__lineTo(p, args[0], args[1]); + nargs = 0; + npts++; + } + } + } + } + } + + nsvg__addPath(p, (char)closeFlag); + + nsvg__addShape(p); +} + +static void nsvg__parseSVG(NSVGparser* p, const char** attr) +{ + int i; + for (i = 0; attr[i]; i += 2) { + if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "width") == 0) { + p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "height") == 0) { + p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); + } else if (strcmp(attr[i], "viewBox") == 0) { + sscanf(attr[i + 1], "%f%*[%%, \t]%f%*[%%, \t]%f%*[%%, \t]%f", &p->viewMinx, &p->viewMiny, &p->viewWidth, &p->viewHeight); + } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { + if (strstr(attr[i + 1], "none") != 0) { + // No uniform scaling + p->alignType = NSVG_ALIGN_NONE; + } else { + // Parse X align + if (strstr(attr[i + 1], "xMin") != 0) + p->alignX = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "xMid") != 0) + p->alignX = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "xMax") != 0) + p->alignX = NSVG_ALIGN_MAX; + // Parse X align + if (strstr(attr[i + 1], "yMin") != 0) + p->alignY = NSVG_ALIGN_MIN; + else if (strstr(attr[i + 1], "yMid") != 0) + p->alignY = NSVG_ALIGN_MID; + else if (strstr(attr[i + 1], "yMax") != 0) + p->alignY = NSVG_ALIGN_MAX; + // Parse meet/slice + p->alignType = NSVG_ALIGN_MEET; + if (strstr(attr[i + 1], "slice") != 0) + p->alignType = NSVG_ALIGN_SLICE; + } + } + } + } +} + +static void nsvg__parseGradient(NSVGparser* p, const char** attr, char type) +{ + int i; + NSVGgradientData* grad = (NSVGgradientData*)NANOSVG_malloc(sizeof(NSVGgradientData)); + if (grad == NULL) return; + memset(grad, 0, sizeof(NSVGgradientData)); + grad->units = NSVG_OBJECT_SPACE; + grad->type = type; + if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { + grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); + grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); + } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { + grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); + } + + nsvg__xformIdentity(grad->xform); + + for (i = 0; attr[i]; i += 2) { + if (strcmp(attr[i], "id") == 0) { + strncpy(grad->id, attr[i+1], 63); + grad->id[63] = '\0'; + } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { + if (strcmp(attr[i], "gradientUnits") == 0) { + if (strcmp(attr[i+1], "objectBoundingBox") == 0) + grad->units = NSVG_OBJECT_SPACE; + else + grad->units = NSVG_USER_SPACE; + } else if (strcmp(attr[i], "gradientTransform") == 0) { + nsvg__parseTransform(grad->xform, attr[i + 1]); + } else if (strcmp(attr[i], "cx") == 0) { + grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "cy") == 0) { + grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "r") == 0) { + grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fx") == 0) { + grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "fy") == 0) { + grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x1") == 0) { + grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y1") == 0) { + grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "x2") == 0) { + grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "y2") == 0) { + grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); + } else if (strcmp(attr[i], "spreadMethod") == 0) { + if (strcmp(attr[i+1], "pad") == 0) + grad->spread = NSVG_SPREAD_PAD; + else if (strcmp(attr[i+1], "reflect") == 0) + grad->spread = NSVG_SPREAD_REFLECT; + else if (strcmp(attr[i+1], "repeat") == 0) + grad->spread = NSVG_SPREAD_REPEAT; + } else if (strcmp(attr[i], "xlink:href") == 0) { + const char *href = attr[i+1]; + strncpy(grad->ref, href+1, 62); + grad->ref[62] = '\0'; + } + } + } + + grad->next = p->gradients; + p->gradients = grad; +} + +static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) +{ + NSVGattrib* curAttr = nsvg__getAttr(p); + NSVGgradientData* grad; + NSVGgradientStop* stop; + int i, idx; + + curAttr->stopOffset = 0; + curAttr->stopColor = 0; + curAttr->stopOpacity = 1.0f; + + for (i = 0; attr[i]; i += 2) { + nsvg__parseAttr(p, attr[i], attr[i + 1]); + } + + // Add stop to the last gradient. + grad = p->gradients; + if (grad == NULL) return; + + grad->nstops++; + grad->stops = (NSVGgradientStop*)NANOSVG_realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); + if (grad->stops == NULL) return; + + // Insert + idx = grad->nstops-1; + for (i = 0; i < grad->nstops-1; i++) { + if (curAttr->stopOffset < grad->stops[i].offset) { + idx = i; + break; + } + } + if (idx != grad->nstops-1) { + for (i = grad->nstops-1; i > idx; i--) + grad->stops[i] = grad->stops[i-1]; + } + + stop = &grad->stops[idx]; + stop->color = curAttr->stopColor; + stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; + stop->offset = curAttr->stopOffset; +} + +static void nsvg__startElement(void* ud, const char* el, const char** attr) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (p->defsFlag) { + // Skip everything but gradients in defs + if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } + return; + } + + if (strcmp(el, "g") == 0) { + nsvg__pushAttr(p); + nsvg__parseAttribs(p, attr); + } else if (strcmp(el, "path") == 0) { + if (p->pathFlag) // Do not allow nested paths. + return; + nsvg__pushAttr(p); + nsvg__parsePath(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "rect") == 0) { + nsvg__pushAttr(p); + nsvg__parseRect(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "circle") == 0) { + nsvg__pushAttr(p); + nsvg__parseCircle(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "ellipse") == 0) { + nsvg__pushAttr(p); + nsvg__parseEllipse(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "line") == 0) { + nsvg__pushAttr(p); + nsvg__parseLine(p, attr); + nsvg__popAttr(p); + } else if (strcmp(el, "polyline") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 0); + nsvg__popAttr(p); + } else if (strcmp(el, "polygon") == 0) { + nsvg__pushAttr(p); + nsvg__parsePoly(p, attr, 1); + nsvg__popAttr(p); + } else if (strcmp(el, "linearGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); + } else if (strcmp(el, "radialGradient") == 0) { + nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); + } else if (strcmp(el, "stop") == 0) { + nsvg__parseGradientStop(p, attr); + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 1; + } else if (strcmp(el, "svg") == 0) { + nsvg__parseSVG(p, attr); + } else if (strcmp(el, "style") == 0) { + p->styleFlag = 1; + } +} + +static void nsvg__endElement(void* ud, const char* el) +{ + NSVGparser* p = (NSVGparser*)ud; + + if (strcmp(el, "g") == 0) { + nsvg__popAttr(p); + } else if (strcmp(el, "path") == 0) { + p->pathFlag = 0; + } else if (strcmp(el, "defs") == 0) { + p->defsFlag = 0; + } else if (strcmp(el, "style") == 0) { + p->styleFlag = 0; + } +} + +static char *nsvg__strndup(const char *s, size_t n) +{ + char *result; + size_t len = strlen(s); + + if (n < len) + len = n; + + result = (char*)NANOSVG_malloc(len+1); + if (!result) + return 0; + + result[len] = '\0'; + return (char *)memcpy(result, s, len); +} + +static void nsvg__content(void* ud, const char* s) +{ + NSVGparser* p = (NSVGparser*)ud; + if (p->styleFlag) { + + int state = 0; + const char* start = NULL; + while (*s) { + char c = *s; + if (nsvg__isspace(c) || c == '{') { + if (state == 1) { + NSVGstyles* next = p->styles; + + p->styles = (NSVGstyles*)malloc(sizeof(NSVGstyles)); + p->styles->next = next; + p->styles->name = nsvg__strndup(start, (size_t)(s - start)); + start = s + 1; + state = 2; + } + } else if (state == 2 && c == '}') { + p->styles->description = nsvg__strndup(start, (size_t)(s - start)); + state = 0; + } + else if (state == 0) { + start = s; + state = 1; + } + s++; + /* + if (*s == '{' && state == NSVG_XML_CONTENT) { + // Start of a tag + *s++ = '\0'; + nsvg__parseContent(mark, contentCb, ud); + mark = s; + state = NSVG_XML_TAG; + } + else if (*s == '>' && state == NSVG_XML_TAG) { + // Start of a content or new tag. + *s++ = '\0'; + nsvg__parseElement(mark, startelCb, endelCb, ud); + mark = s; + state = NSVG_XML_CONTENT; + } + else { + s++; + } + */ + } + + } +} + +static void nsvg__imageBounds(NSVGparser* p, float* bounds) +{ + NSVGshape* shape; + shape = p->image->shapes; + if (shape == NULL) { + bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; + return; + } + bounds[0] = shape->bounds[0]; + bounds[1] = shape->bounds[1]; + bounds[2] = shape->bounds[2]; + bounds[3] = shape->bounds[3]; + for (shape = shape->next; shape != NULL; shape = shape->next) { + bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); + bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); + bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); + bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); + } +} + +static float nsvg__viewAlign(float content, float container, int type) +{ + if (type == NSVG_ALIGN_MIN) + return 0; + else if (type == NSVG_ALIGN_MAX) + return container - content; + // mid + return (container - content) * 0.5f; +} + +static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) +{ + float t[6]; + nsvg__xformSetTranslation(t, tx, ty); + nsvg__xformMultiply (grad->xform, t); + + nsvg__xformSetScale(t, sx, sy); + nsvg__xformMultiply (grad->xform, t); +} + +static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) +{ + NSVGshape* shape; + NSVGpath* path; + float tx, ty, sx, sy, us, bounds[4], t[6], avgs; + int i; + float* pt; + + // Guess image size if not set completely. + nsvg__imageBounds(p, bounds); + + if (p->viewWidth == 0) { + if (p->image->width > 0) { + p->viewWidth = p->image->width; + } else { + p->viewMinx = bounds[0]; + p->viewWidth = bounds[2] - bounds[0]; + } + } + if (p->viewHeight == 0) { + if (p->image->height > 0) { + p->viewHeight = p->image->height; + } else { + p->viewMiny = bounds[1]; + p->viewHeight = bounds[3] - bounds[1]; + } + } + if (p->image->width == 0) + p->image->width = p->viewWidth; + if (p->image->height == 0) + p->image->height = p->viewHeight; + + tx = -p->viewMinx; + ty = -p->viewMiny; + sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; + sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; + // Unit scaling + us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); + + // Fix aspect ratio + if (p->alignType == NSVG_ALIGN_MEET) { + // fit whole image into viewbox + sx = sy = nsvg__minf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } else if (p->alignType == NSVG_ALIGN_SLICE) { + // fill whole viewbox with image + sx = sy = nsvg__maxf(sx, sy); + tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; + ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; + } + + // Transform + sx *= us; + sy *= us; + avgs = (sx+sy) / 2.0f; + for (shape = p->image->shapes; shape != NULL; shape = shape->next) { + shape->bounds[0] = (shape->bounds[0] + tx) * sx; + shape->bounds[1] = (shape->bounds[1] + ty) * sy; + shape->bounds[2] = (shape->bounds[2] + tx) * sx; + shape->bounds[3] = (shape->bounds[3] + ty) * sy; + for (path = shape->paths; path != NULL; path = path->next) { + path->bounds[0] = (path->bounds[0] + tx) * sx; + path->bounds[1] = (path->bounds[1] + ty) * sy; + path->bounds[2] = (path->bounds[2] + tx) * sx; + path->bounds[3] = (path->bounds[3] + ty) * sy; + for (i =0; i < path->npts; i++) { + pt = &path->pts[i*2]; + pt[0] = (pt[0] + tx) * sx; + pt[1] = (pt[1] + ty) * sy; + } + } + + if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); + memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->fill.gradient->xform, t); + } + if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { + nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); + memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); + nsvg__xformInverse(shape->stroke.gradient->xform, t); + } + + shape->strokeWidth *= avgs; + shape->strokeDashOffset *= avgs; + for (i = 0; i < shape->strokeDashCount; i++) + shape->strokeDashArray[i] *= avgs; + } +} + +NANOSVG_SCOPE +NSVGimage* nsvgParse(char* input, const char* units, float dpi) +{ + NSVGparser* p; + NSVGimage* ret = 0; + + p = nsvg__createParser(); + if (p == NULL) { + return NULL; + } + p->dpi = dpi; + + nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); + + // Scale to viewBox + nsvg__scaleToViewbox(p, units); + + ret = p->image; + p->image = NULL; + + nsvg__deleteParser(p); + + return ret; +} + +NANOSVG_SCOPE +NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) +{ + FILE* fp = NULL; + size_t size; + char* data = NULL; + NSVGimage* image = NULL; + + fp = fopen(filename, "rb"); + if (!fp) goto error; + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + data = (char*)NANOSVG_malloc(size+1); + if (data == NULL) goto error; + if (fread(data, 1, size, fp) != size) goto error; + data[size] = '\0'; // Must be null terminated. + fclose(fp); + image = nsvgParse(data, units, dpi); + NANOSVG_free(data); + + return image; + +error: + if (fp) fclose(fp); + if (data) NANOSVG_free(data); + if (image) nsvgDelete(image); + return NULL; +} + +NANOSVG_SCOPE +void nsvgDelete(NSVGimage* image) +{ + NSVGshape *snext, *shape; + if (image == NULL) return; + shape = image->shapes; + while (shape != NULL) { + snext = shape->next; + nsvg__deletePaths(shape->paths); + nsvg__deletePaint(&shape->fill); + nsvg__deletePaint(&shape->stroke); + NANOSVG_free(shape); + shape = snext; + } + NANOSVG_free(image); +} + +#endif diff --git a/generic/nanosvgrast.h b/generic/nanosvgrast.h new file mode 100644 index 0000000..37636fe --- /dev/null +++ b/generic/nanosvgrast.h @@ -0,0 +1,1467 @@ +/* + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + * + * The polygon rasterization is heavily based on stb_truetype rasterizer + * by Sean Barrett - http://nothings.org/ + * + */ + +#ifndef NANOSVGRAST_H +#define NANOSVGRAST_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef NANOSVG_SCOPE +#define NANOSVG_SCOPE +#endif + +#ifndef NANOSVG_malloc +#define NANOSVG_malloc malloc +#endif + +#ifndef NANOSVG_realloc +#define NANOSVG_realloc realloc +#endif + +#ifndef NANOSVG_free +#define NANOSVG_free free +#endif + +typedef struct NSVGrasterizer NSVGrasterizer; + +/* Example Usage: + // Load SVG + struct SNVGImage* image = nsvgParseFromFile("test.svg."); + + // Create rasterizer (can be used to render multiple images). + struct NSVGrasterizer* rast = nsvgCreateRasterizer(); + // Allocate memory for image + unsigned char* img = malloc(w*h*4); + // Rasterize + nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); +*/ + +// Allocated rasterizer context. +NANOSVG_SCOPE NSVGrasterizer* nsvgCreateRasterizer(); + +// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) +// r - pointer to rasterizer context +// image - pointer to image to rasterize +// tx,ty - image offset (applied after scaling) +// scale - image scale +// dst - pointer to destination image data, 4 bytes per pixel (RGBA) +// w - width of the image to render +// h - height of the image to render +// stride - number of bytes per scaleline in the destination buffer +NANOSVG_SCOPE void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride); + +// Deletes rasterizer context. +NANOSVG_SCOPE void nsvgDeleteRasterizer(NSVGrasterizer*); + + +#ifdef __cplusplus +} +#endif + +#endif // NANOSVGRAST_H + +#ifdef NANOSVGRAST_IMPLEMENTATION + +#include <math.h> + +#define NSVG__SUBSAMPLES 5 +#define NSVG__FIXSHIFT 10 +#define NSVG__FIX (1 << NSVG__FIXSHIFT) +#define NSVG__FIXMASK (NSVG__FIX-1) +#define NSVG__MEMPAGE_SIZE 1024 + +typedef struct NSVGedge { + float x0,y0, x1,y1; + int dir; + struct NSVGedge* next; +} NSVGedge; + +typedef struct NSVGpoint { + float x, y; + float dx, dy; + float len; + float dmx, dmy; + unsigned char flags; +} NSVGpoint; + +typedef struct NSVGactiveEdge { + int x,dx; + float ey; + int dir; + struct NSVGactiveEdge *next; +} NSVGactiveEdge; + +typedef struct NSVGmemPage { + unsigned char mem[NSVG__MEMPAGE_SIZE]; + int size; + struct NSVGmemPage* next; +} NSVGmemPage; + +typedef struct NSVGcachedPaint { + char type; + char spread; + float xform[6]; + unsigned int colors[256]; +} NSVGcachedPaint; + +struct NSVGrasterizer +{ + float px, py; + + float tessTol; + float distTol; + + NSVGedge* edges; + int nedges; + int cedges; + + NSVGpoint* points; + int npoints; + int cpoints; + + NSVGpoint* points2; + int npoints2; + int cpoints2; + + NSVGactiveEdge* freelist; + NSVGmemPage* pages; + NSVGmemPage* curpage; + + unsigned char* scanline; + int cscanline; + + unsigned char* bitmap; + int width, height, stride; +}; + +NANOSVG_SCOPE +NSVGrasterizer* nsvgCreateRasterizer() +{ + NSVGrasterizer* r = (NSVGrasterizer*)NANOSVG_malloc(sizeof(NSVGrasterizer)); + if (r == NULL) goto error; + memset(r, 0, sizeof(NSVGrasterizer)); + + r->tessTol = 0.25f; + r->distTol = 0.01f; + + return r; + +error: + nsvgDeleteRasterizer(r); + return NULL; +} + +NANOSVG_SCOPE +void nsvgDeleteRasterizer(NSVGrasterizer* r) +{ + NSVGmemPage* p; + + if (r == NULL) return; + + p = r->pages; + while (p != NULL) { + NSVGmemPage* next = p->next; + NANOSVG_free(p); + p = next; + } + + if (r->edges) NANOSVG_free(r->edges); + if (r->points) NANOSVG_free(r->points); + if (r->points2) NANOSVG_free(r->points2); + if (r->scanline) NANOSVG_free(r->scanline); + + NANOSVG_free(r); +} + +static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) +{ + NSVGmemPage *newp; + + // If using existing chain, return the next page in chain + if (cur != NULL && cur->next != NULL) { + return cur->next; + } + + // Alloc new page + newp = (NSVGmemPage*)NANOSVG_malloc(sizeof(NSVGmemPage)); + if (newp == NULL) return NULL; + memset(newp, 0, sizeof(NSVGmemPage)); + + // Add to linked list + if (cur != NULL) + cur->next = newp; + else + r->pages = newp; + + return newp; +} + +static void nsvg__resetPool(NSVGrasterizer* r) +{ + NSVGmemPage* p = r->pages; + while (p != NULL) { + p->size = 0; + p = p->next; + } + r->curpage = r->pages; +} + +static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) +{ + unsigned char* buf; + if (size > NSVG__MEMPAGE_SIZE) return NULL; + if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { + r->curpage = nsvg__nextPage(r, r->curpage); + } + buf = &r->curpage->mem[r->curpage->size]; + r->curpage->size += size; + return buf; +} + +static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) +{ + float dx = x2 - x1; + float dy = y2 - y1; + return dx*dx + dy*dy < tol*tol; +} + +static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) +{ + NSVGpoint* pt; + + if (r->npoints > 0) { + pt = &r->points[r->npoints-1]; + if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { + pt->flags = (unsigned char)(pt->flags | flags); + return; + } + } + + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)NANOSVG_realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + + pt = &r->points[r->npoints]; + pt->x = x; + pt->y = y; + pt->flags = (unsigned char)flags; + r->npoints++; +} + +static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) +{ + if (r->npoints+1 > r->cpoints) { + r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; + r->points = (NSVGpoint*)NANOSVG_realloc(r->points, sizeof(NSVGpoint) * r->cpoints); + if (r->points == NULL) return; + } + r->points[r->npoints] = pt; + r->npoints++; +} + +static void nsvg__duplicatePoints(NSVGrasterizer* r) +{ + if (r->npoints > r->cpoints2) { + r->cpoints2 = r->npoints; + r->points2 = (NSVGpoint*)NANOSVG_realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); + if (r->points2 == NULL) return; + } + + memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); + r->npoints2 = r->npoints; +} + +static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) +{ + NSVGedge* e; + + // Skip horizontal edges + if (y0 == y1) + return; + + if (r->nedges+1 > r->cedges) { + r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; + r->edges = (NSVGedge*)NANOSVG_realloc(r->edges, sizeof(NSVGedge) * r->cedges); + if (r->edges == NULL) return; + } + + e = &r->edges[r->nedges]; + r->nedges++; + + if (y0 < y1) { + e->x0 = x0; + e->y0 = y0; + e->x1 = x1; + e->y1 = y1; + e->dir = 1; + } else { + e->x0 = x1; + e->y0 = y1; + e->x1 = x0; + e->y1 = y0; + e->dir = -1; + } +} + +static float nsvg__normalize(float *x, float* y) +{ + float d = sqrtf((*x)*(*x) + (*y)*(*y)); + if (d > 1e-6f) { + float id = 1.0f / d; + *x *= id; + *y *= id; + } + return d; +} + +static float nsvg__absf(float x) { return x < 0 ? -x : x; } + +static void nsvg__flattenCubicBez(NSVGrasterizer* r, + float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4, + int level, int type) +{ + float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; + float dx,dy,d2,d3; + + if (level > 10) return; + + x12 = (x1+x2)*0.5f; + y12 = (y1+y2)*0.5f; + x23 = (x2+x3)*0.5f; + y23 = (y2+y3)*0.5f; + x34 = (x3+x4)*0.5f; + y34 = (y3+y4)*0.5f; + x123 = (x12+x23)*0.5f; + y123 = (y12+y23)*0.5f; + + dx = x4 - x1; + dy = y4 - y1; + d2 = nsvg__absf(((x2 - x4) * dy - (y2 - y4) * dx)); + d3 = nsvg__absf(((x3 - x4) * dy - (y3 - y4) * dx)); + + if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { + nsvg__addPathPoint(r, x4, y4, type); + return; + } + + x234 = (x23+x34)*0.5f; + y234 = (y23+y34)*0.5f; + x1234 = (x123+x234)*0.5f; + y1234 = (y123+y234)*0.5f; + + nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); + nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); +} + +static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j; + NSVGpath* path; + + for (path = shape->paths; path != NULL; path = path->next) { + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + } +} + +enum NSVGpointFlags +{ + NSVG_PT_CORNER = 0x01, + NSVG_PT_BEVEL = 0x02, + NSVG_PT_LEFT = 0x04 +}; + +static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + float len = nsvg__normalize(&dx, &dy); + float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) +{ + float w = lineWidth * 0.5f; + float px = p->x - dx*w, py = p->y - dy*w; + float dlx = dy, dly = -dx; + float lx = px - dlx*w, ly = py - dly*w; + float rx = px + dlx*w, ry = py + dly*w; + + nsvg__addEdge(r, lx, ly, rx, ry); + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +#ifndef NSVG_PI +#define NSVG_PI (3.14159265358979323846264338327f) +#endif + +static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) +{ + int i; + float w = lineWidth * 0.5f; + float px = p->x, py = p->y; + float dlx = dy, dly = -dx; + float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; + + for (i = 0; i < ncap; i++) { + float a = (float)i/(float)(ncap-1)*NSVG_PI; + float ax = cosf(a) * w, ay = sinf(a) * w; + float x = px - dlx*ax - dx*ay; + float y = py - dly*ax - dy*ay; + + if (i > 0) + nsvg__addEdge(r, prevx, prevy, x, y); + + prevx = x; + prevy = y; + + if (i == 0) { + lx = x; ly = y; + } else if (i == ncap-1) { + rx = x; ry = y; + } + } + + if (connect) { + nsvg__addEdge(r, left->x, left->y, lx, ly); + nsvg__addEdge(r, rx, ry, right->x, right->y); + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); + float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); + float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); + float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); + + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float lx0, rx0, lx1, rx1; + float ly0, ry0, ly1, ry1; + + if (p1->flags & NSVG_PT_LEFT) { + lx0 = lx1 = p1->x - p1->dmx * w; + ly0 = ly1 = p1->y - p1->dmy * w; + nsvg__addEdge(r, lx1, ly1, left->x, left->y); + + rx0 = p1->x + (dlx0 * w); + ry0 = p1->y + (dly0 * w); + rx1 = p1->x + (dlx1 * w); + ry1 = p1->y + (dly1 * w); + nsvg__addEdge(r, right->x, right->y, rx0, ry0); + nsvg__addEdge(r, rx0, ry0, rx1, ry1); + } else { + lx0 = p1->x - (dlx0 * w); + ly0 = p1->y - (dly0 * w); + lx1 = p1->x - (dlx1 * w); + ly1 = p1->y - (dly1 * w); + nsvg__addEdge(r, lx0, ly0, left->x, left->y); + nsvg__addEdge(r, lx1, ly1, lx0, ly0); + + rx0 = rx1 = p1->x + p1->dmx * w; + ry0 = ry1 = p1->y + p1->dmy * w; + nsvg__addEdge(r, right->x, right->y, rx1, ry1); + } + + left->x = lx1; left->y = ly1; + right->x = rx1; right->y = ry1; +} + +static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) +{ + int i, n; + float w = lineWidth * 0.5f; + float dlx0 = p0->dy, dly0 = -p0->dx; + float dlx1 = p1->dy, dly1 = -p1->dx; + float a0 = atan2f(dly0, dlx0); + float a1 = atan2f(dly1, dlx1); + float da = a1 - a0; + float lx, ly, rx, ry; + + if (da < NSVG_PI) da += NSVG_PI*2; + if (da > NSVG_PI) da -= NSVG_PI*2; + + n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); + if (n < 2) n = 2; + if (n > ncap) n = ncap; + + lx = left->x; + ly = left->y; + rx = right->x; + ry = right->y; + + for (i = 0; i < n; i++) { + float u = (float)i/(float)(n-1); + float a = a0 + u*da; + float ax = cosf(a) * w, ay = sinf(a) * w; + float lx1 = p1->x - ax, ly1 = p1->y - ay; + float rx1 = p1->x + ax, ry1 = p1->y + ay; + + nsvg__addEdge(r, lx1, ly1, lx, ly); + nsvg__addEdge(r, rx, ry, rx1, ry1); + + lx = lx1; ly = ly1; + rx = rx1; ry = ry1; + } + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) +{ + float w = lineWidth * 0.5f; + float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); + float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); + + nsvg__addEdge(r, lx, ly, left->x, left->y); + nsvg__addEdge(r, right->x, right->y, rx, ry); + + left->x = lx; left->y = ly; + right->x = rx; right->y = ry; +} + +static int nsvg__curveDivs(float r, float arc, float tol) +{ + float da = acosf(r / (r + tol)) * 2.0f; + int divs = (int)ceilf(arc / da); + if (divs < 2) divs = 2; + return divs; +} + +static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) +{ + int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. + NSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0}; + NSVGpoint* p0, *p1; + int j, s, e; + + // Build stroke edges + if (closed) { + // Looping + p0 = &points[npoints-1]; + p1 = &points[0]; + s = 0; + e = npoints; + } else { + // Add cap + p0 = &points[0]; + p1 = &points[1]; + s = 1; + e = npoints-1; + } + + if (closed) { + nsvg__initClosed(&left, &right, p0, p1, lineWidth); + firstLeft = left; + firstRight = right; + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); + } + + for (j = s; j < e; ++j) { + if (p1->flags & NSVG_PT_CORNER) { + if (lineJoin == NSVG_JOIN_ROUND) + nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); + else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) + nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); + else + nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); + } else { + nsvg__straightJoin(r, &left, &right, p1, lineWidth); + } + p0 = p1++; + } + + if (closed) { + // Loop it + nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); + nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); + } else { + // Add cap + float dx = p1->x - p0->x; + float dy = p1->y - p0->y; + nsvg__normalize(&dx, &dy); + if (lineCap == NSVG_CAP_BUTT) + nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_SQUARE) + nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); + else if (lineCap == NSVG_CAP_ROUND) + nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); + } +} + +static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) +{ + int i, j; + NSVGpoint* p0, *p1; + + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (i = 0; i < r->npoints; i++) { + // Calculate segment direction and length + p0->dx = p1->x - p0->x; + p0->dy = p1->y - p0->y; + p0->len = nsvg__normalize(&p0->dx, &p0->dy); + // Advance + p0 = p1++; + } + + // calculate joins + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + for (j = 0; j < r->npoints; j++) { + float dlx0, dly0, dlx1, dly1, dmr2, cross; + dlx0 = p0->dy; + dly0 = -p0->dx; + dlx1 = p1->dy; + dly1 = -p1->dx; + // Calculate extrusions + p1->dmx = (dlx0 + dlx1) * 0.5f; + p1->dmy = (dly0 + dly1) * 0.5f; + dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; + if (dmr2 > 0.000001f) { + float s2 = 1.0f / dmr2; + if (s2 > 600.0f) { + s2 = 600.0f; + } + p1->dmx *= s2; + p1->dmy *= s2; + } + + // Clear flags, but keep the corner. + p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; + + // Keep track of left turns. + cross = p1->dx * p0->dy - p0->dx * p1->dy; + if (cross > 0.0f) + p1->flags |= NSVG_PT_LEFT; + + // Check to see if the corner needs to be beveled. + if (p1->flags & NSVG_PT_CORNER) { + if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { + p1->flags |= NSVG_PT_BEVEL; + } + } + + p0 = p1++; + } +} + +static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) +{ + int i, j, closed; + NSVGpath* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (path = shape->paths; path != NULL; path = path->next) { + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &path->pts[i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; + } + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); + } + } +} + +static int nsvg__cmpEdge(const void *p, const void *q) +{ + const NSVGedge* a = (const NSVGedge*)p; + const NSVGedge* b = (const NSVGedge*)q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + + +static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) +{ + NSVGactiveEdge* z; + float dxdy; + + if (r->freelist != NULL) { + // Restore from freelist. + z = r->freelist; + r->freelist = z->next; + } else { + // Alloc new edge. + z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); + if (z == NULL) return NULL; + } + + dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); +// STBTT_assert(e->y0 <= start_point); + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = (int)(-floorf(NSVG__FIX * -dxdy)); + else + z->dx = (int)floorf(NSVG__FIX * dxdy); + z->x = (int)floorf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); +// z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->dir = e->dir; + + return z; +} + +static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) +{ + z->next = r->freelist; + r->freelist = z; +} + +static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) +{ + int i = x0 >> NSVG__FIXSHIFT; + int j = x1 >> NSVG__FIXSHIFT; + if (i < *xmin) *xmin = i; + if (j > *xmax) *xmax = j; + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = (unsigned char)(scanline[i] + maxWeight); + } + } +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) +{ + // non-zero winding fill + int x0 = 0, w = 0; + + if (fillRule == NSVG_FILLRULE_NONZERO) { + // Non-zero + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->dir; + } else { + int x1 = e->x; w += e->dir; + // if we went to zero, we need to draw + if (w == 0) + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } else if (fillRule == NSVG_FILLRULE_EVENODD) { + // Even-odd + while (e != NULL) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w = 1; + } else { + int x1 = e->x; w = 0; + nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); + } + e = e->next; + } + } +} + +static float nsvg__clampf(float a, float mn, float mx) { return a < mn ? mn : (a > mx ? mx : a); } + +static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + return (r) | (g << 8) | (b << 16) | (a << 24); +} + +static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; + int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; + int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; + int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static unsigned int nsvg__applyOpacity(unsigned int c, float u) +{ + int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); + int r = (c) & 0xff; + int g = (c>>8) & 0xff; + int b = (c>>16) & 0xff; + int a = (((c>>24) & 0xff)*iu) >> 8; + return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); +} + +static inline int nsvg__div255(int x) +{ + return ((x+1) * 257) >> 16; +} + +static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, + float tx, float ty, float scale, NSVGcachedPaint* cache) +{ + + if (cache->type == NSVG_PAINT_COLOR) { + int i, cr, cg, cb, ca; + cr = cache->colors[0] & 0xff; + cg = (cache->colors[0] >> 8) & 0xff; + cb = (cache->colors[0] >> 16) & 0xff; + ca = (cache->colors[0] >> 24) & 0xff; + + for (i = 0; i < count; i++) { + int r,g,b; + int a = nsvg__div255((int)cover[0] * ca); + int ia = 255 - a; + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + } + } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + float fx, fy, dx, gy; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gy = fx*t[1] + fy*t[3] + t[5]; + c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { + // TODO: spread modes. + // TODO: plenty of opportunities to optimize. + // TODO: focus (fx,fy) + float fx, fy, dx, gx, gy, gd; + float* t = cache->xform; + int i, cr, cg, cb, ca; + unsigned int c; + + fx = ((float)x - tx) / scale; + fy = ((float)y - ty) / scale; + dx = 1.0f / scale; + + for (i = 0; i < count; i++) { + int r,g,b,a,ia; + gx = fx*t[0] + fy*t[2] + t[4]; + gy = fx*t[1] + fy*t[3] + t[5]; + gd = sqrtf(gx*gx + gy*gy); + c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; + cr = (c) & 0xff; + cg = (c >> 8) & 0xff; + cb = (c >> 16) & 0xff; + ca = (c >> 24) & 0xff; + + a = nsvg__div255((int)cover[0] * ca); + ia = 255 - a; + + // Premultiply + r = nsvg__div255(cr * a); + g = nsvg__div255(cg * a); + b = nsvg__div255(cb * a); + + // Blend over + r += nsvg__div255(ia * (int)dst[0]); + g += nsvg__div255(ia * (int)dst[1]); + b += nsvg__div255(ia * (int)dst[2]); + a += nsvg__div255(ia * (int)dst[3]); + + dst[0] = (unsigned char)r; + dst[1] = (unsigned char)g; + dst[2] = (unsigned char)b; + dst[3] = (unsigned char)a; + + cover++; + dst += 4; + fx += dx; + } + } +} + +static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) +{ + NSVGactiveEdge *active = NULL; + int y, s; + int e = 0; + int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline + int xmin, xmax; + + for (y = 0; y < r->height; y++) { + memset(r->scanline, 0, r->width); + xmin = r->width; + xmax = 0; + for (s = 0; s < NSVG__SUBSAMPLES; ++s) { + // find center of pixel for this scanline + float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; + NSVGactiveEdge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + NSVGactiveEdge *z = *step; + if (z->ey <= scany) { + *step = z->next; // delete from list +// NSVG__assert(z->valid); + nsvg__freeActive(r, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for (;;) { + int changed = 0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + NSVGactiveEdge* t = *step; + NSVGactiveEdge* q = t->next; + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e < r->nedges && r->edges[e].y0 <= scany) { + if (r->edges[e].y1 > scany) { + NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); + if (z == NULL) break; + // find insertion point + if (active == NULL) { + active = z; + } else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + NSVGactiveEdge* p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + e++; + } + + // now process all active edges in non-zero fashion + if (active != NULL) + nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); + } + // Blit + if (xmin < 0) xmin = 0; + if (xmax > r->width-1) xmax = r->width-1; + if (xmin <= xmax) { + nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); + } + } + +} + +static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) +{ + int x,y; + + // Unpremultiply + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = row[0], g = row[1], b = row[2], a = row[3]; + if (a != 0) { + row[0] = (unsigned char)(r*255/a); + row[1] = (unsigned char)(g*255/a); + row[2] = (unsigned char)(b*255/a); + } + row += 4; + } + } + + // Defringe + for (y = 0; y < h; y++) { + unsigned char *row = &image[y*stride]; + for (x = 0; x < w; x++) { + int r = 0, g = 0, b = 0, a = row[3], n = 0; + if (a == 0) { + if (x-1 > 0 && row[-1] != 0) { + r += row[-4]; + g += row[-3]; + b += row[-2]; + n++; + } + if (x+1 < w && row[7] != 0) { + r += row[4]; + g += row[5]; + b += row[6]; + n++; + } + if (y-1 > 0 && row[-stride+3] != 0) { + r += row[-stride]; + g += row[-stride+1]; + b += row[-stride+2]; + n++; + } + if (y+1 < h && row[stride+3] != 0) { + r += row[stride]; + g += row[stride+1]; + b += row[stride+2]; + n++; + } + if (n > 0) { + row[0] = (unsigned char)(r/n); + row[1] = (unsigned char)(g/n); + row[2] = (unsigned char)(b/n); + } + } + row += 4; + } + } +} + + +static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) +{ + int i, j; + NSVGgradient* grad; + + cache->type = paint->type; + + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } + + grad = paint->gradient; + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); + + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity(grad->stops[i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(grad->stops[0].color, opacity); + ua = nsvg__clampf(grad->stops[0].offset, 0, 1); + ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; + } + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(grad->stops[i].color, opacity); + cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); + ua = nsvg__clampf(grad->stops[i].offset, 0, 1); + ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; + } + +} + +/* +static void dumpEdges(NSVGrasterizer* r, const char* name) +{ + float xmin = 0, xmax = 0, ymin = 0, ymax = 0; + NSVGedge *e = NULL; + int i; + if (r->nedges == 0) return; + FILE* fp = fopen(name, "w"); + if (fp == NULL) return; + + xmin = xmax = r->edges[0].x0; + ymin = ymax = r->edges[0].y0; + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + xmin = nsvg__minf(xmin, e->x0); + xmin = nsvg__minf(xmin, e->x1); + xmax = nsvg__maxf(xmax, e->x0); + xmax = nsvg__maxf(xmax, e->x1); + ymin = nsvg__minf(ymin, e->y0); + ymin = nsvg__minf(ymin, e->y1); + ymax = nsvg__maxf(ymax, e->y0); + ymax = nsvg__maxf(ymax, e->y1); + } + + fprintf(fp, "<svg viewBox=\"%f %f %f %f\" xmlns=\"http://www.w3.org/2000/svg\">", xmin, ymin, (xmax - xmin), (ymax - ymin)); + + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#000;\" />", e->x0,e->y0, e->x1,e->y1); + } + + for (i = 0; i < r->npoints; i++) { + if (i+1 < r->npoints) + fprintf(fp ,"<line x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" style=\"stroke:#f00;\" />", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); + fprintf(fp ,"<circle cx=\"%f\" cy=\"%f\" r=\"1\" style=\"fill:%s;\" />", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); + } + + fprintf(fp, "</svg>"); + fclose(fp); +} +*/ + +NANOSVG_SCOPE +void nsvgRasterize(NSVGrasterizer* r, + NSVGimage* image, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) +{ + NSVGshape *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)NANOSVG_realloc(r->scanline, w); + if (r->scanline == NULL) return; + } + + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); + + for (shape = image->shapes; shape != NULL; shape = shape->next) { + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShape(r, shape, scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->fill, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStroke(r, shape, scale); + +// dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaint(&cache, &shape->stroke, shape->opacity); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } + } + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; +} + +#endif diff --git a/generic/tk.h b/generic/tk.h index e00ea5c..98cdd5f 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -832,10 +832,11 @@ typedef struct Tk_FakeWin { int internalBorderBottom; int minReqWidth; int minReqHeight; - char *dummy20; /* geometryMaster */ #ifdef TK_USE_INPUT_METHODS - int dummy21; + int dummy20; #endif /* TK_USE_INPUT_METHODS */ + char *dummy21; /* geomMgrName */ + Tk_Window dummy22; /* maintainerPtr */ } Tk_FakeWin; /* @@ -1036,6 +1037,8 @@ typedef int (Tk_ItemAreaProc)(Tk_Canvas canvas, Tk_Item *itemPtr, double *rectPtr); typedef int (Tk_ItemPostscriptProc)(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +typedef void (Tk_ItemRotateProc)(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRadians); typedef void (Tk_ItemScaleProc)(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -1119,7 +1122,9 @@ typedef struct Tk_ItemType { /* Procedure to delete characters from an * item. */ struct Tk_ItemType *nextPtr;/* Used to link types together into a list. */ - char *reserved1; /* Reserved for future extension. */ + Tk_ItemRotateProc *rotateProc; + /* Procedure to rotate an item's coordinates + * about a point. */ int reserved2; /* Carefully compatible with */ char *reserved3; /* Jan Nijtmans dash patch */ char *reserved4; diff --git a/generic/tkButton.c b/generic/tkButton.c index 9755861..d006d88 100644 --- a/generic/tkButton.c +++ b/generic/tkButton.c @@ -1175,7 +1175,7 @@ ConfigureButton( */ if ((butPtr->type == TYPE_RADIO_BUTTON) && - (*Tcl_GetString(butPtr->onValuePtr) == 0)) { + (*Tcl_GetString(butPtr->onValuePtr) == '\0')) { butPtr->flags |= SELECTED; } } diff --git a/generic/tkCanvArc.c b/generic/tkCanvArc.c index d9c2eca..cd7ce9d 100644 --- a/generic/tkCanvArc.c +++ b/generic/tkCanvArc.c @@ -13,6 +13,8 @@ #include "tkInt.h" #include "tkCanvas.h" +#include "float.h" + /* * The structure below defines the record for each arc item. */ @@ -183,7 +185,7 @@ static void ComputeArcBbox(Tk_Canvas canvas, ArcItem *arcPtr); static int ConfigureArc(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int objc, Tcl_Obj *const objv[], int flags); -static void ComputeArcFromHeight(ArcItem *arcPtr); +static void ComputeArcParametersFromHeight(ArcItem *arcPtr); static int CreateArc(Tcl_Interp *interp, Tk_Canvas canvas, struct Tk_Item *itemPtr, int objc, Tcl_Obj *const objv[]); @@ -214,6 +216,8 @@ static int HorizLineToArc(double x1, double x2, static int VertLineToArc(double x, double y1, double y2, double rx, double ry, double start, double extent); +static void RotateArc(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); /* * The structures below defines the arc item types by means of functions that @@ -241,7 +245,8 @@ Tk_ItemType tkArcType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateArc, /* rotateProc */ + 0, NULL, NULL }; /* @@ -469,13 +474,12 @@ ConfigureArc( } /* - * If either the height is provided then the start and extent will be - * overridden. + * Override the start and extent if the height is given. */ - if (arcPtr->height != 0) { - ComputeArcFromHeight(arcPtr); - ComputeArcBbox(canvas, arcPtr); - } + + ComputeArcParametersFromHeight(arcPtr); + + ComputeArcBbox(canvas, arcPtr); i = (int) (arcPtr->start/360.0); arcPtr->start -= i*360.0; @@ -589,7 +593,7 @@ ConfigureArc( /* *-------------------------------------------------------------- * - * ComputeArcFromHeight -- + * ComputeArcParametersFromHeight -- * * This function calculates the arc parameters given start-point, * end-point and height (!= 0). @@ -604,17 +608,30 @@ ConfigureArc( */ static void -ComputeArcFromHeight( +ComputeArcParametersFromHeight( ArcItem* arcPtr) { double chordLen, chordDir[2], chordCen[2], arcCen[2], d, radToDeg, radius; /* - * The chord. + * Do nothing if no height has been specified. + */ + + if (arcPtr->height == 0) + return; + + /* + * Calculate the chord length, return early if it is too small. */ chordLen = hypot(arcPtr->endPoint[1] - arcPtr->startPoint[1], arcPtr->startPoint[0] - arcPtr->endPoint[0]); + + if (chordLen < DBL_EPSILON) { + arcPtr->start = arcPtr->extent = arcPtr->height = 0; + return; + } + chordDir[0] = (arcPtr->endPoint[0] - arcPtr->startPoint[0]) / chordLen; chordDir[1] = (arcPtr->endPoint[1] - arcPtr->startPoint[1]) / chordLen; chordCen[0] = (arcPtr->startPoint[0] + arcPtr->endPoint[0]) / 2; @@ -791,7 +808,7 @@ ComputeArcBbox( ComputeArcOutline(canvas,arcPtr); /* - * To compute the bounding box, start with the the bbox formed by the two + * To compute the bounding box, start with the bbox formed by the two * endpoints of the arc. Then add in the center of the arc's oval (if * relevant) and the 3-o'clock, 6-o'clock, 9-o'clock, and 12-o'clock * positions, if they are relevant. @@ -1493,6 +1510,60 @@ ScaleArc( /* *-------------------------------------------------------------- * + * RotateArc -- + * + * This function is called to rotate an arc by a given amount. + * + * Results: + * None. + * + * Side effects: + * The position of the arc is rotated by angleRad radians about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateArc( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + ArcItem *arcPtr = (ArcItem *) itemPtr; + double newX, newY, oldX, oldY; + + /* + * Compute the centre of the box, then rotate that about the origin. + */ + + newX = oldX = (arcPtr->bbox[0] + arcPtr->bbox[2]) / 2.0; + newY = oldY = (arcPtr->bbox[1] + arcPtr->bbox[3]) / 2.0; + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &newX, &newY); + + /* + * Apply the translation to the box. + */ + + arcPtr->bbox[0] += newX - oldX; + arcPtr->bbox[1] += newY - oldY; + arcPtr->bbox[2] += newX - oldX; + arcPtr->bbox[3] += newY - oldY; + + /* + * TODO: update the arc endpoints? + */ + + ComputeArcBbox(canvas, arcPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslateArc -- * * This function is called to move an arc by a given amount. diff --git a/generic/tkCanvBmap.c b/generic/tkCanvBmap.c index 1fbfddf..1fdfdf5 100644 --- a/generic/tkCanvBmap.c +++ b/generic/tkCanvBmap.c @@ -105,6 +105,8 @@ static void DeleteBitmap(Tk_Canvas canvas, static void DisplayBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleBitmap(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -137,7 +139,8 @@ Tk_ItemType tkBitmapType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateBitmap, /* rotateProc */ + 0, NULL, NULL }; /* @@ -790,6 +793,39 @@ ScaleBitmap( /* *-------------------------------------------------------------- * + * RotateBitmap -- + * + * This function is called to rotate a bitmap's origin by a given amount. + * + * Results: + * None. + * + * Side effects: + * The position of the bitmap is rotated by angleRad radians about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateBitmap( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + BitmapItem *bmapPtr = (BitmapItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &bmapPtr->x, &bmapPtr->y); + ComputeBitmapBbox(canvas, bmapPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslateBitmap -- * * This function is called to move an item by a given amount. diff --git a/generic/tkCanvImg.c b/generic/tkCanvImg.c index 76b3689..19e1c70 100644 --- a/generic/tkCanvImg.c +++ b/generic/tkCanvImg.c @@ -94,6 +94,8 @@ static void DeleteImage(Tk_Canvas canvas, static void DisplayImage(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateImage(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleImage(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -126,7 +128,8 @@ Tk_ItemType tkImageType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateImage, /* rotateProc */ + 0, NULL, NULL }; /* @@ -761,6 +764,40 @@ ImageToPostscript( /* *-------------------------------------------------------------- * + * RotateImage -- + * + * This function is called to rotate an image's origin by a given amount. + * This does *not* rotate the contents of the image. + * + * Results: + * None. + * + * Side effects: + * The position of the image anchor is rotated by angleRad radians about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateImage( + Tk_Canvas canvas, + Tk_Item *itemPtr, + double originX, + double originY, + double angleRad) +{ + ImageItem *imgPtr = (ImageItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &imgPtr->x, &imgPtr->y); + ComputeImageBbox(canvas, imgPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleImage -- * * This function is invoked to rescale an item. diff --git a/generic/tkCanvLine.c b/generic/tkCanvLine.c index dd0a6cb..62251fc 100644 --- a/generic/tkCanvLine.c +++ b/generic/tkCanvLine.c @@ -117,6 +117,8 @@ static int ParseArrowShape(ClientData clientData, static const char * PrintArrowShape(ClientData clientData, Tk_Window tkwin, char *recordPtr, int offset, Tcl_FreeProc **freeProcPtr); +static void RotateLine(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleLine(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -239,7 +241,8 @@ Tk_ItemType tkLineType = { LineInsert, /* insertProc */ LineDeleteCoords, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateLine, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1856,6 +1859,56 @@ TranslateLine( /* *-------------------------------------------------------------- * + * RotateLine -- + * + * This function is called to rotate a line by a given amount about a + * point. + * + * Results: + * None. + * + * Side effects: + * The position of the line is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateLine( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being moved. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + LineItem *linePtr = (LineItem *) itemPtr; + double *coordPtr; + int i; + double s = sin(angleRad), c = cos(angleRad); + + for (i = 0, coordPtr = linePtr->coordPtr; i < linePtr->numPoints; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + if (linePtr->firstArrowPtr != NULL) { + for (i = 0, coordPtr = linePtr->firstArrowPtr; i < PTS_IN_ARROW; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + } + if (linePtr->lastArrowPtr != NULL) { + for (i = 0, coordPtr = linePtr->lastArrowPtr; i < PTS_IN_ARROW; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + } + ComputeLineBbox(canvas, linePtr); +} + +/* + *-------------------------------------------------------------- + * * ParseArrowShape -- * * This function is called back during option parsing to parse arrow diff --git a/generic/tkCanvPoly.c b/generic/tkCanvPoly.c index f10aae7..0b2d134 100644 --- a/generic/tkCanvPoly.c +++ b/generic/tkCanvPoly.c @@ -176,6 +176,8 @@ static double PolygonToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); static int PolygonToPostscript(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +static void RotatePolygon(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScalePolygon(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -202,13 +204,14 @@ Tk_ItemType tkPolygonType = { PolygonToPostscript, /* postscriptProc */ ScalePolygon, /* scaleProc */ TranslatePolygon, /* translateProc */ - GetPolygonIndex, /* indexProc */ + GetPolygonIndex, /* indexProc */ NULL, /* icursorProc */ NULL, /* selectionProc */ - PolygonInsert, /* insertProc */ + PolygonInsert, /* insertProc */ PolygonDeleteCoords, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotatePolygon, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1738,6 +1741,44 @@ GetPolygonIndex( /* *-------------------------------------------------------------- * + * RotatePolygon -- + * + * This function is called to rotate a polygon by a given amount about a + * point. + * + * Results: + * None. + * + * Side effects: + * The position of the polygon is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotatePolygon( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being moved. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + PolygonItem *polyPtr = (PolygonItem *) itemPtr; + double *coordPtr; + int i; + double s = sin(angleRad), c = cos(angleRad); + + for (i = 0, coordPtr = polyPtr->coordPtr; i < polyPtr->numPoints; + i++, coordPtr += 2) { + TkRotatePoint(originX, originY, s, c, &coordPtr[0], &coordPtr[1]); + } + ComputePolygonBbox(canvas, polyPtr); +} + +/* + *-------------------------------------------------------------- + * * TranslatePolygon -- * * This function is called to move a polygon by a given amount. diff --git a/generic/tkCanvText.c b/generic/tkCanvText.c index cfffc62..b9607a5 100644 --- a/generic/tkCanvText.c +++ b/generic/tkCanvText.c @@ -170,6 +170,8 @@ static double TextToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); static int TextToPostscript(Tcl_Interp *interp, Tk_Canvas canvas, Tk_Item *itemPtr, int prepass); +static void RotateText(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void TranslateText(Tk_Canvas canvas, Tk_Item *itemPtr, double deltaX, double deltaY); @@ -199,7 +201,8 @@ Tk_ItemType tkTextType = { TextInsert, /* insertProc */ TextDeleteChars, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateText, /* rotateProc */ + 0, NULL, NULL }; #define ROUND(d) ((int) floor((d) + 0.5)) @@ -1007,7 +1010,7 @@ TextInsert( { TextItem *textPtr = (TextItem *) itemPtr; int byteIndex, charsAdded; - size_t byteCount; + TkSizeT byteCount; char *newStr, *text; const char *string; Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr; @@ -1029,7 +1032,7 @@ TextInsert( } newStr = ckalloc(textPtr->numBytes + byteCount + 1); - memcpy(newStr, text, (size_t) byteIndex); + memcpy(newStr, text, byteIndex); strcpy(newStr + byteIndex, string); strcpy(newStr + byteIndex + byteCount, text + byteIndex); @@ -1110,7 +1113,7 @@ TextDeleteChars( - (text + byteIndex); newStr = ckalloc(textPtr->numBytes + 1 - byteCount); - memcpy(newStr, text, (size_t) byteIndex); + memcpy(newStr, text, byteIndex); strcpy(newStr + byteIndex, text + byteIndex + byteCount); ckfree(text); @@ -1250,6 +1253,39 @@ TextToArea( /* *-------------------------------------------------------------- * + * RotateText -- + * + * This function is called to rotate a text item by a given amount about a + * point. Note that this does *not* rotate the text of the item. + * + * Results: + * None. + * + * Side effects: + * The position of the text anchor is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateText( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being rotated. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + TextItem *textPtr = (TextItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &textPtr->x, &textPtr->y); + ComputeTextBbox(canvas, textPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleText -- * * This function is invoked to rescale a text item. @@ -1344,7 +1380,7 @@ GetTextIndex( * index. */ { TextItem *textPtr = (TextItem *) itemPtr; - size_t length; + TkSizeT length; int c; TkCanvas *canvasPtr = (TkCanvas *) canvas; Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr; @@ -1505,7 +1541,7 @@ GetSelText( if (byteCount <= 0) { return 0; } - memcpy(buffer, selStart + offset, (size_t) byteCount); + memcpy(buffer, selStart + offset, byteCount); buffer[byteCount] = '\0'; return byteCount; } diff --git a/generic/tkCanvUtil.c b/generic/tkCanvUtil.c index 6ce671d..52d7bf6 100644 --- a/generic/tkCanvUtil.c +++ b/generic/tkCanvUtil.c @@ -1265,7 +1265,6 @@ Tk_ChangeOutlineGC( } return 0; } - /* *-------------------------------------------------------------- @@ -1865,6 +1864,43 @@ TkCanvTranslatePath( } /* + *-------------------------------------------------------------- + * + * TkRotatePoint -- + * + * Rotate a point about another point. The angle should be converted into + * its sine and cosine before calling this function. + * + * Results: + * None + * + * Side effects: + * The point in (*xPtr,*yPtr) is updated to be rotated about + * (originX,originY) by the amount given by the sine and cosine of the + * angle to rotate. + * + *-------------------------------------------------------------- + */ + +void +TkRotatePoint( + double originX, double originY, /* The point about which to rotate. */ + double sine, double cosine, /* How much to rotate? */ + double *xPtr, double *yPtr) /* The point to be rotated. (INOUT) */ +{ + double x = *xPtr - originX; + double y = *yPtr - originY; + + /* + * Beware! The canvas coordinate space is flipped vertically, so rotations + * go the "wrong" way with respect to mathematics. + */ + + *xPtr = originX + x * cosine + y * sine; + *yPtr = originY - x * sine + y * cosine; +} + +/* * Local Variables: * mode: c * c-basic-offset: 4 diff --git a/generic/tkCanvWind.c b/generic/tkCanvWind.c index a57d428..59e81af 100644 --- a/generic/tkCanvWind.c +++ b/generic/tkCanvWind.c @@ -77,6 +77,8 @@ static void DeleteWinItem(Tk_Canvas canvas, static void DisplayWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, Display *display, Drawable dst, int x, int y, int width, int height); +static void RotateWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleWinItem(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -130,7 +132,8 @@ Tk_ItemType tkWindowType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateWinItem, /* rotateProc */ + 0, NULL, NULL }; /* @@ -915,6 +918,40 @@ CanvasPsWindow( /* *-------------------------------------------------------------- * + * RotateWinItem -- + * + * This function is called to rotate a window item by a given amount + * about a point. Note that this does *not* rotate the window of the + * item. + * + * Results: + * None. + * + * Side effects: + * The position of the window anchor is rotated by angleRad about (originX, + * originY), and the bounding box is updated in the generic part of the + * item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateWinItem( + Tk_Canvas canvas, /* Canvas containing item. */ + Tk_Item *itemPtr, /* Item that is being rotated. */ + double originX, double originY, + double angleRad) /* Amount by which item is to be rotated. */ +{ + WindowItem *winItemPtr = (WindowItem *) itemPtr; + + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &winItemPtr->x, &winItemPtr->y); + ComputeWindowBbox(canvas, winItemPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleWinItem -- * * This function is invoked to rescale a window item. diff --git a/generic/tkCanvas.c b/generic/tkCanvas.c index 15a2ffb..f8573fc 100644 --- a/generic/tkCanvas.c +++ b/generic/tkCanvas.c @@ -241,6 +241,9 @@ static void CanvasWorldChanged(ClientData instanceData); static int ConfigureCanvas(Tcl_Interp *interp, TkCanvas *canvasPtr, int argc, Tcl_Obj *const *argv, int flags); +static void DefaultRotateImplementation(TkCanvas *canvasPtr, + Tk_Item *itemPtr, double x, double y, + double angleRadians); static void DestroyCanvas(void *memPtr); static int DrawCanvas(Tcl_Interp *interp, ClientData clientData, Tk_PhotoHandle photohandle, int subsample, int zoom); static void DisplayCanvas(ClientData clientData); @@ -560,6 +563,102 @@ ItemTranslate( itemPtr->typePtr->translateProc((Tk_Canvas) canvasPtr, itemPtr, xDelta, yDelta); } + +static inline void +ItemRotate( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double x, + double y, + double angleRadians) +{ + if (itemPtr->typePtr->rotateProc != NULL) { + itemPtr->typePtr->rotateProc((Tk_Canvas) canvasPtr, + itemPtr, x, y, angleRadians); + } else { + DefaultRotateImplementation(canvasPtr, itemPtr, x, y, angleRadians); + } +} + +/* + *-------------------------------------------------------------- + * + * DefaultRotateImplementation -- + * + * The default implementation of the rotation operation, used when items + * do not provide their own version. + * + *-------------------------------------------------------------- + */ + +static void +DefaultRotateImplementation( + TkCanvas *canvasPtr, + Tk_Item *itemPtr, + double x, + double y, + double angleRadians) +{ + int objc, i, ok = 1; + Tcl_Obj **objv, **newObjv; + double *coordv; + double s = sin(angleRadians); + double c = cos(angleRadians); + Tcl_Interp *interp = canvasPtr->interp; + + /* + * Get the coordinates out of the item. + */ + + if (ItemCoords(canvasPtr, itemPtr, 0, NULL) == TCL_OK && + Tcl_ListObjGetElements(NULL, Tcl_GetObjResult(interp), + &objc, &objv) == TCL_OK) { + coordv = (double *) Tcl_Alloc(sizeof(double) * objc); + for (i=0 ; i<objc ; i++) { + if (Tcl_GetDoubleFromObj(NULL, objv[i], &coordv[i]) != TCL_OK) { + ok = 0; + break; + } + } + if (ok) { + /* + * Apply the rotation. + */ + + for (i=0 ; i<objc ; i+=2) { + double px = coordv[i+0] - x; + double py = coordv[i+1] - y; + double nx = px * c - py * s; + double ny = px * s + py * c; + + coordv[i+0] = nx + x; + coordv[i+1] = ny + y; + } + + /* + * Write the coordinates back into the item. + */ + + newObjv = (Tcl_Obj **) Tcl_Alloc(sizeof(Tcl_Obj *) * objc); + for (i=0 ; i<objc ; i++) { + newObjv[i] = Tcl_NewDoubleObj(coordv[i]); + Tcl_IncrRefCount(newObjv[i]); + } + ItemCoords(canvasPtr, itemPtr, objc, newObjv); + for (i=0 ; i<objc ; i++) { + Tcl_DecrRefCount(newObjv[i]); + } + Tcl_Free((char *) newObjv); + } + Tcl_Free((char *) coordv); + } + + /* + * The interpreter result was (probably) modified above; reset it. + */ + + Tcl_ResetResult(interp); +} /* *-------------------------------------------------------------- @@ -746,25 +845,24 @@ CanvasWidgetCmd( "canvasy", "cget", "configure", "coords", "create", "dchars", "delete", "dtag", "find", "focus", "gettags", "icursor", - "image", - "imove", "index", "insert", "itemcget", - "itemconfigure", + "image", "imove", "index", "insert", + "itemcget", "itemconfigure", "lower", "move", "moveto", "postscript", - "raise", "rchars", "scale", "scan", - "select", "type", "xview", "yview", - NULL + "raise", "rchars", "rotate", "scale", + "scan", "select", "type", "xview", + "yview", NULL }; enum options { CANV_ADDTAG, CANV_BBOX, CANV_BIND, CANV_CANVASX, CANV_CANVASY, CANV_CGET, CANV_CONFIGURE, CANV_COORDS, CANV_CREATE, CANV_DCHARS, CANV_DELETE, CANV_DTAG, CANV_FIND, CANV_FOCUS, CANV_GETTAGS, CANV_ICURSOR, - CANV_IMAGE, - CANV_IMOVE, CANV_INDEX, CANV_INSERT, CANV_ITEMCGET, - CANV_ITEMCONFIGURE, + CANV_IMAGE, CANV_IMOVE, CANV_INDEX, CANV_INSERT, + CANV_ITEMCGET, CANV_ITEMCONFIGURE, CANV_LOWER, CANV_MOVE, CANV_MOVETO, CANV_POSTSCRIPT, - CANV_RAISE, CANV_RCHARS, CANV_SCALE, CANV_SCAN, - CANV_SELECT, CANV_TYPE, CANV_XVIEW, CANV_YVIEW + CANV_RAISE, CANV_RCHARS, CANV_ROTATE, CANV_SCALE, + CANV_SCAN, CANV_SELECT, CANV_TYPE, CANV_XVIEW, + CANV_YVIEW }; if (objc < 2) { @@ -1153,7 +1251,7 @@ CanvasWidgetCmd( int isNew = 0; Tcl_HashEntry *entryPtr; const char *arg; - size_t length; + TkSizeT length; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "type coords ?arg ...?"); @@ -1760,6 +1858,30 @@ CanvasWidgetCmd( } break; } + case CANV_ROTATE: { + double x, y, angle; + Tk_Canvas canvas = (Tk_Canvas) canvasPtr; + + if (objc != 6) { + Tcl_WrongNumArgs(interp, 2, objv, "tagOrId x y angle"); + result = TCL_ERROR; + goto done; + } + if (Tk_CanvasGetCoordFromObj(interp, canvas, objv[3], &x) != TCL_OK || + Tk_CanvasGetCoordFromObj(interp, canvas, objv[4], &y) != TCL_OK || + Tcl_GetDoubleFromObj(interp, objv[5], &angle) != TCL_OK) { + result = TCL_ERROR; + goto done; + } + angle = angle * 3.1415927 / 180.0; + FOR_EVERY_CANVAS_ITEM_MATCHING(objv[2], &searchPtr, goto done) { + EventuallyRedrawItem(canvasPtr, itemPtr); + ItemRotate(canvasPtr, itemPtr, x, y, angle); + EventuallyRedrawItem(canvasPtr, itemPtr); + canvasPtr->flags |= REPICK_NEEDED; + } + break; + } case CANV_SCALE: { double xOrigin, yOrigin, xScale, yScale; diff --git a/generic/tkClipboard.c b/generic/tkClipboard.c index 9f6822f..3c3d89b 100644 --- a/generic/tkClipboard.c +++ b/generic/tkClipboard.c @@ -450,7 +450,7 @@ Tk_ClipboardObjCmd( }; enum appendOptions { APPEND_DISPLAYOF, APPEND_FORMAT, APPEND_TYPE }; int subIndex; - size_t length; + TkSizeT length; for (i = 2; i < objc - 1; i++) { string = TkGetStringFromObj(objv[i], &length); diff --git a/generic/tkCmds.c b/generic/tkCmds.c index 391d906..f0abd70 100644 --- a/generic/tkCmds.c +++ b/generic/tkCmds.c @@ -2071,7 +2071,7 @@ TkGetDisplayOf( * present. */ { const char *string; - size_t length; + TkSizeT length; if (objc < 1) { return 0; diff --git a/generic/tkConfig.c b/generic/tkConfig.c index 892692a..7b84207 100644 --- a/generic/tkConfig.c +++ b/generic/tkConfig.c @@ -656,7 +656,7 @@ DoObjConfig( case TK_OPTION_STRING: { char *newStr; const char *value; - size_t length; + TkSizeT length; if (nullOK && ObjectIsEmpty(valuePtr)) { valuePtr = NULL; diff --git a/generic/tkEntry.c b/generic/tkEntry.c index fdd2ff4..9fd8371 100644 --- a/generic/tkEntry.c +++ b/generic/tkEntry.c @@ -4385,7 +4385,7 @@ SpinboxInvoke( */ int i, listc; - size_t elemLen, length = entryPtr->numChars; + TkSizeT elemLen, length = entryPtr->numChars; const char *bytes; Tcl_Obj **listv; diff --git a/generic/tkFileFilter.c b/generic/tkFileFilter.c index 6cb188b..561676b 100644 --- a/generic/tkFileFilter.c +++ b/generic/tkFileFilter.c @@ -262,7 +262,7 @@ AddClause( */ for (i=0; i<ostypeCount; i++) { - size_t len; + TkSizeT len; const char *strType = TkGetStringFromObj(ostypeList[i], &len); /* @@ -322,7 +322,7 @@ AddClause( if (globCount > 0 && globList != NULL) { for (i=0; i<globCount; i++) { GlobPattern *globPtr = ckalloc(sizeof(GlobPattern)); - size_t len; + TkSizeT len; const char *str = TkGetStringFromObj(globList[i], &len); len = (len + 1) * sizeof(char); @@ -375,7 +375,7 @@ AddClause( } for (i=0; i<ostypeCount; i++) { Tcl_DString osTypeDS; - size_t len; + TkSizeT len; MacFileType *mfPtr = ckalloc(sizeof(MacFileType)); const char *strType = TkGetStringFromObj(ostypeList[i], &len); char *string; diff --git a/generic/tkFont.c b/generic/tkFont.c index 053524c..cc7861f 100644 --- a/generic/tkFont.c +++ b/generic/tkFont.c @@ -712,7 +712,7 @@ Tk_FontObjCmd( case FONT_MEASURE: { const char *string; Tk_Font tkfont; - size_t length = 0; + TkSizeT length = 0; int skip = 0; if (objc > 4) { @@ -3245,7 +3245,7 @@ Tk_TextLayoutToPostscript( int baseline = chunkPtr->y; Tcl_Obj *psObj = Tcl_NewObj(); int i, j; - size_t len; + TkSizeT len; const char *p, *glyphname; char uindex[5], c, *ps; int ch; @@ -4250,7 +4250,7 @@ TkFontGetFirstTextLayout( } chunkPtr = layoutPtr->chunks; numBytesInChunk = chunkPtr->numBytes; - strncpy(dst, chunkPtr->start, (size_t) numBytesInChunk); + strncpy(dst, chunkPtr->start, numBytesInChunk); *font = layoutPtr->tkfont; return numBytesInChunk; } diff --git a/generic/tkFrame.c b/generic/tkFrame.c index 708faef..d15f9a3 100644 --- a/generic/tkFrame.c +++ b/generic/tkFrame.c @@ -489,7 +489,7 @@ CreateFrame( const char *className, *screenName, *visualName, *colormapName; const char *arg, *useOption; int i, depth; - size_t length; + TkSizeT length; unsigned int mask; Colormap colormap; Visual *visual; @@ -745,7 +745,7 @@ FrameWidgetObjCmd( register Frame *framePtr = clientData; int result = TCL_OK, index; int c, i; - size_t length; + TkSizeT length; Tcl_Obj *objPtr; if (objc < 2) { diff --git a/generic/tkGeometry.c b/generic/tkGeometry.c index eab3850..fbdd530 100644 --- a/generic/tkGeometry.c +++ b/generic/tkGeometry.c @@ -327,23 +327,23 @@ TkSetGeometryMaster( { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) == 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) == 0) { return TCL_OK; } - if (winPtr->geometryMaster != NULL) { + if (winPtr->geomMgrName != NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "cannot use geometry manager %s inside %s which already" " has slaves managed by %s", - master, Tk_PathName(tkwin), winPtr->geometryMaster)); + master, Tk_PathName(tkwin), winPtr->geomMgrName)); Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "FIGHT", NULL); } return TCL_ERROR; } - winPtr->geometryMaster = ckalloc(strlen(master) + 1); - strcpy(winPtr->geometryMaster, master); + winPtr->geomMgrName = ckalloc(strlen(master) + 1); + strcpy(winPtr->geomMgrName, master); return TCL_OK; } @@ -372,14 +372,14 @@ TkFreeGeometryMaster( { register TkWindow *winPtr = (TkWindow *) tkwin; - if (winPtr->geometryMaster != NULL && - strcmp(winPtr->geometryMaster, master) != 0) { + if (winPtr->geomMgrName != NULL && + strcmp(winPtr->geomMgrName, master) != 0) { Tcl_Panic("Trying to free %s from geometry manager %s", - winPtr->geometryMaster, master); + winPtr->geomMgrName, master); } - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } } @@ -425,6 +425,9 @@ Tk_MaintainGeometry( Tk_Window ancestor, parent; TkDisplay *dispPtr = ((TkWindow *) master)->dispPtr; + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)master; + + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)master; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, don't bother @@ -570,6 +573,9 @@ Tk_UnmaintainGeometry( Tk_Window ancestor; TkDisplay *dispPtr = ((TkWindow *) slave)->dispPtr; + ((TkWindow *)slave)->maintainerPtr = NULL; + + ((TkWindow *)slave)->maintainerPtr = NULL; if (master == Tk_Parent(slave)) { /* * If the slave is a direct descendant of the master, diff --git a/generic/tkGrab.c b/generic/tkGrab.c index ff5d083..0efddee 100644 --- a/generic/tkGrab.c +++ b/generic/tkGrab.c @@ -190,7 +190,7 @@ Tk_GrabObjCmd( TkDisplay *dispPtr; const char *arg; int index; - size_t len; + TkSizeT len; static const char *const optionStrings[] = { "current", "release", "set", "status", NULL }; diff --git a/generic/tkGrid.c b/generic/tkGrid.c index 436151a..cedf5f9 100644 --- a/generic/tkGrid.c +++ b/generic/tkGrid.c @@ -2941,6 +2941,7 @@ ConfigureSlaves( Gridder *masterPtr = NULL; Gridder *slavePtr; Tk_Window other, slave, parent, ancestor; + TkWindow *master; int i, j, tmp; int numWindows; int width; @@ -2968,7 +2969,7 @@ ConfigureSlaves( firstChar = 0; for (numWindows=0, i=0; i < objc; i++) { - size_t length; + TkSizeT length; char prevChar = firstChar; string = TkGetStringFromObj(objv[i], &length); @@ -3355,17 +3356,23 @@ ConfigureSlaves( } /* - * Try to make sure our master isn't managed by us. + * Check for management loops. */ - if (masterPtr->masterPtr == slavePtr) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( + for (master = (TkWindow *)masterPtr->tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slave) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't put %s inside %s, would cause management loop", - Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); - Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); - Unlink(slavePtr); - return TCL_ERROR; - } + Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + Unlink(slavePtr); + return TCL_ERROR; + } + } + if (masterPtr->tkwin != Tk_Parent(slave)) { + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)masterPtr->tkwin; + } Tk_ManageGeometry(slave, &gridMgrType, slavePtr); diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index da62d1f..cebf8e3 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -757,7 +757,7 @@ StringMatchGIF( Tcl_Interp *interp) /* not used */ { unsigned char *data, header[10]; - size_t got, length; + TkSizeT got, length; MFile handle; data = TkGetByteArrayFromObj(dataObj, &length); @@ -826,7 +826,7 @@ StringReadGIF( int srcX, int srcY) { MFile handle, *hdlPtr = &handle; - size_t length; + TkSizeT length; const char *xferFormat; unsigned char *data = TkGetByteArrayFromObj(dataObj, &length); diff --git a/generic/tkImgListFormat.c b/generic/tkImgListFormat.c index 4636c41..0a4435c 100644 --- a/generic/tkImgListFormat.c +++ b/generic/tkImgListFormat.c @@ -772,7 +772,7 @@ ParseColor( unsigned char *alphaPtr) { const char *specString; - size_t charCount; + TkSizeT charCount; /* * Find out which color format we have diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index a620515..871684d 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -127,7 +127,7 @@ typedef struct { Tcl_Channel channel; /* Channel for from-file reads. */ Tcl_Obj *objDataPtr; unsigned char *strDataBuf; /* Raw source data for from-string reads. */ - size_t strDataLen; /* Length of source data. */ + TkSizeT strDataLen; /* Length of source data. */ unsigned char *base64Data; /* base64 encoded string data. */ unsigned char base64Bits; /* Remaining bits from last base64 read. */ unsigned char base64State; /* Current state of base64 decoder. */ @@ -2098,7 +2098,7 @@ ReadIDAT( */ while (chunkSz && !Tcl_ZlibStreamEof(pngPtr->stream)) { - size_t len1, len2; + TkSizeT len1, len2; /* * Read another block of input into the zlib stream if data remains. @@ -2154,7 +2154,7 @@ ReadIDAT( } TkGetByteArrayFromObj(pngPtr->thisLineObj, &len2); - if (len2 == (size_t)pngPtr->phaseSize) { + if (len2 == (TkSizeT)pngPtr->phaseSize) { if (pngPtr->phase > 7) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "extra data after final scan line of final phase", @@ -2863,7 +2863,7 @@ WriteData( */ if (pngPtr->objDataPtr) { - size_t objSz; + TkSizeT objSz; unsigned char *destPtr; TkGetByteArrayFromObj(pngPtr->objDataPtr, &objSz); @@ -3135,7 +3135,7 @@ WriteIDAT( int rowNum, flush = TCL_ZLIB_NO_FLUSH, result; Tcl_Obj *outputObj; unsigned char *outputBytes; - size_t outputSize; + TkSizeT outputSize; /* * Filter and compress each row one at a time. diff --git a/generic/tkImgPPM.c b/generic/tkImgPPM.c index d6ec63c..ea49fc2 100644 --- a/generic/tkImgPPM.c +++ b/generic/tkImgPPM.c @@ -765,7 +765,7 @@ ReadPPMStringHeader( #define BUFFER_SIZE 1000 char buffer[BUFFER_SIZE], c; int i, numFields, type = 0; - size_t dataSize; + TkSizeT dataSize; unsigned char *dataBuffer; dataBuffer = TkGetByteArrayFromObj(dataPtr, &dataSize); diff --git a/generic/tkImgPhInstance.c b/generic/tkImgPhInstance.c index f67ab36..1360d1e 100644 --- a/generic/tkImgPhInstance.c +++ b/generic/tkImgPhInstance.c @@ -867,8 +867,8 @@ TkImgPhotoInstanceSetSize( if (masterPtr->width == instancePtr->width) { offset = validBox.y * masterPtr->width * 3; memcpy(newError + offset, instancePtr->error + offset, - (size_t) (validBox.height - * masterPtr->width * 3 * sizeof(schar))); + (size_t) validBox.height + * masterPtr->width * 3 * sizeof(schar)); } else if (validBox.width > 0 && validBox.height > 0) { errDestPtr = newError + @@ -1982,8 +1982,8 @@ TkImgResetDither( { if (instancePtr->error) { memset(instancePtr->error, 0, - /*(size_t)*/ (instancePtr->masterPtr->width - * instancePtr->masterPtr->height * 3 * sizeof(schar))); + (size_t) instancePtr->masterPtr->width + * instancePtr->masterPtr->height * 3 * sizeof(schar)); } } diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index c8825a1..d200d0b 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -410,7 +410,7 @@ ImgPhotoCmd( unsigned char *pixelPtr; Tk_PhotoImageBlock block; Tk_PhotoImageFormat *imageFormat; - size_t length; + TkSizeT length; int imageWidth, imageHeight, matched, oldformat = 0; Tcl_Channel chan; Tk_PhotoHandle srcHandle; @@ -1476,7 +1476,7 @@ ParseSubcommandOptions( * TK_PHOTO_COMPOSITE_* constants. */ NULL }; - size_t length; + TkSizeT length; int index, c, bit, currentBit; int values[4], numValues, maxValues, argIndex; const char *option, *expandedOption, *needed; @@ -1758,7 +1758,7 @@ ImgPhotoConfigureMaster( const char *oldFileString, *oldPaletteString; Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL; Tcl_Obj *tempdata, *tempformat; - size_t length; + TkSizeT length; int i, j, result, imageWidth, imageHeight, oldformat; double oldGamma; Tcl_Channel chan; @@ -1846,7 +1846,7 @@ ImgPhotoConfigureMaster( * Force into ByteArray format, which most (all) image handlers will * use anyway. Empty length means ignore the -data option. */ - size_t bytesize; + TkSizeT bytesize; (void) TkGetByteArrayFromObj(data, &bytesize); if (bytesize) { diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c new file mode 100644 index 0000000..f86c45e --- /dev/null +++ b/generic/tkImgSVGnano.c @@ -0,0 +1,698 @@ +/* + * tkImgSVGnano.c + * + * A photo file handler for SVG files. + * + * Copyright (c) 2013-14 Mikko Mononen memon@inside.org + * Copyright (c) 2018 Christian Gollwitzer auriocus@gmx.de + * Copyright (c) 2018 Rene Zaumseil r.zaumseil@freenet.de + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * This handler is build using the original nanosvg library files from + * https://github.com/memononen/nanosvg and the tcl extension files from + * https://github.com/auriocus/tksvg + * + */ + +#include "tkInt.h" +#define NANOSVG_malloc ckalloc +#define NANOSVG_realloc ckrealloc +#define NANOSVG_free ckfree +#define NANOSVG_SCOPE MODULE_SCOPE +#define NANOSVG_ALL_COLOR_KEYWORDS +#define NANOSVG_IMPLEMENTATION +#include "nanosvg.h" +#define NANOSVGRAST_IMPLEMENTATION +#include "nanosvgrast.h" + +/* Additional parameters to nsvgRasterize() */ + +typedef struct { + double x; + double y; + double scale; +} RastOpts; + +/* + * Per interp cache of last NSVGimage which was matched to + * be immediately rasterized after the match. This helps to + * eliminate double parsing of the SVG file/string. + */ + +typedef struct { + ClientData dataOrChan; + Tcl_DString formatString; + NSVGimage *nsvgImage; + RastOpts ropts; +} NSVGcache; + +static int FileMatchSVG(Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); +static int FileReadSVG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int StringMatchSVG(Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp); +static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY); +static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, + const char *input, int length, Tcl_Obj *format, + RastOpts *ropts); +static int RasterizeSVG(Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, NSVGimage *nsvgImage, + int destX, int destY, int width, int height, + int srcX, int srcY, RastOpts *ropts); +static NSVGcache * GetCachePtr(Tcl_Interp *interp); +static int CacheSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, NSVGimage *nsvgImage, + RastOpts *ropts); +static NSVGimage * GetCachedSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, RastOpts *ropts); +static void CleanCache(Tcl_Interp *interp); +static void FreeCache(ClientData clientData, Tcl_Interp *interp); + +/* + * The format record for the SVG nano file format: + */ + +Tk_PhotoImageFormat tkImgFmtSVGnano = { + "svg", /* name */ + FileMatchSVG, /* fileMatchProc */ + StringMatchSVG, /* stringMatchProc */ + FileReadSVG, /* fileReadProc */ + StringReadSVG, /* stringReadProc */ + NULL, /* fileWriteProc */ + NULL, /* stringWriteProc */ + NULL +}; + +/* + *---------------------------------------------------------------------- + * + * FileMatchSVG -- + * + * This function is invoked by the photo image type to see if a file + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +FileMatchSVG( + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + Tcl_Obj *dataObj = Tcl_NewObj(); + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + return 0; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage != NULL) { + *widthPtr = (int) ceil(nsvgImage->width * ropts.scale); + *heightPtr = (int) ceil(nsvgImage->height * ropts.scale); + if ((*widthPtr <= 0) || (*heightPtr <= 0)) { + nsvgDelete(nsvgImage); + return 0; + } + if (!CacheSVG(interp, chan, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * FileReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a file and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * The access position in file f is changed, and new data is added to the + * image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +FileReadSVG( + Tcl_Interp *interp, + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, chan, formatObj, &ropts); + + if (nsvgImage == NULL) { + Tcl_Obj *dataObj = Tcl_NewObj(); + + if (Tcl_ReadChars(chan, dataObj, -1, 0) == -1) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + Tcl_SetObjResult(interp, Tcl_NewStringObj("read error", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "READ_ERROR", NULL); + return TCL_ERROR; + } + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage == NULL) { + return TCL_ERROR; + } + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * StringMatchSVG -- + * + * This function is invoked by the photo image type to see if a string + * contains image data in SVG format. + * + * Results: + * The return value is >0 if the file can be successfully parsed, + * and 0 otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static int +StringMatchSVG( + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage; + + CleanCache(interp); + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + if (nsvgImage != NULL) { + *widthPtr = (int) ceil(nsvgImage->width * ropts.scale); + *heightPtr = (int) ceil(nsvgImage->height * ropts.scale); + if ((*widthPtr <= 0) || (*heightPtr <= 0)) { + nsvgDelete(nsvgImage); + return 0; + } + if (!CacheSVG(interp, dataObj, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * StringReadSVG -- + * + * This function is called by the photo image type to read SVG format + * data from a string and write it into a given photo image. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * Side effects: + * New data is added to the image given by imageHandle. + * + *---------------------------------------------------------------------- + */ + +static int +StringReadSVG( + Tcl_Interp *interp, + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) +{ + int length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, dataObj, formatObj, &ropts); + + if (nsvgImage == NULL) { + data = Tcl_GetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + } + if (nsvgImage == NULL) { + return TCL_ERROR; + } + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); +} + +/* + *---------------------------------------------------------------------- + * + * ParseSVGWithOptions -- + * + * This function is called to parse the given input string as SVG. + * + * Results: + * Return a newly create NSVGimage on success, and NULL otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +ParseSVGWithOptions( + Tcl_Interp *interp, + const char *input, + int length, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + Tcl_Obj **objv = NULL; + int objc = 0; + double dpi = 96.0; + char unit[3], *p; + char *inputCopy = NULL; + NSVGimage *nsvgImage; + static const char *const fmtOptions[] = { + "-dpi", "-scale", "-unit", NULL + }; + enum fmtOptions { + OPT_DPI, OPT_SCALE, OPT_UNIT + }; + + /* + * The parser destroys the original input string, + * therefore first duplicate. + */ + + inputCopy = attemptckalloc(length+1); + if (inputCopy == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto error; + } + memcpy(inputCopy, input, length); + inputCopy[length] = '\0'; + + /* + * Process elements of format specification as a list. + */ + + strcpy(unit, "px"); + ropts->x = ropts->y = 0.0; + ropts->scale = 1.0; + if ((formatObj != NULL) && + Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { + goto error; + } + for (; objc > 0 ; objc--, objv++) { + int optIndex; + + /* + * Ignore the "svg" part of the format specification. + */ + + if (!strcasecmp(Tcl_GetString(objv[0]), "svg")) { + continue; + } + + if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, + sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { + goto error; + } + + if (objc < 2) { + ckfree(inputCopy); + inputCopy = NULL; + Tcl_WrongNumArgs(interp, 1, objv, "value"); + goto error; + } + + objc--; + objv++; + + switch ((enum fmtOptions) optIndex) { + case OPT_DPI: + if (Tcl_GetDoubleFromObj(interp, objv[0], &dpi) == TCL_ERROR) { + goto error; + } + if (dpi < 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-dpi value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_DPI", + NULL); + goto error; + } + break; + case OPT_SCALE: + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->scale) == + TCL_ERROR) { + goto error; + } + if (ropts->scale <= 0.0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "-scale value must be positive", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", + NULL); + goto error; + } + break; + case OPT_UNIT: + p = Tcl_GetString(objv[0]); + if ((p != NULL) && (p[0])) { + strncpy(unit, p, 3); + unit[2] = '\0'; + } + break; + } + } + + nsvgImage = nsvgParse(inputCopy, unit, (float) dpi); + if (nsvgImage == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot parse SVG image", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "PARSE_ERROR", NULL); + goto error; + } + ckfree(inputCopy); + return nsvgImage; + +error: + if (inputCopy != NULL) { + ckfree(inputCopy); + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * RasterizeSVG -- + * + * This function is called to rasterize the given nsvgImage and + * fill the imageHandle with data. + * + * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * + * + * Side effects: + * On error the given nsvgImage will be deleted. + * + *---------------------------------------------------------------------- + */ + +static int +RasterizeSVG( + Tcl_Interp *interp, + Tk_PhotoHandle imageHandle, + NSVGimage *nsvgImage, + int destX, int destY, + int width, int height, + int srcX, int srcY, + RastOpts *ropts) +{ + int w, h, c; + NSVGrasterizer *rast; + unsigned char *imgData; + Tk_PhotoImageBlock svgblock; + + w = (int) ceil(nsvgImage->width * ropts->scale); + h = (int) ceil(nsvgImage->height * ropts->scale); + rast = nsvgCreateRasterizer(); + if (rast == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot initialize rasterizer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "RASTERIZER_ERROR", + NULL); + goto cleanAST; + } + imgData = attemptckalloc(w * h *4); + if (imgData == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc image buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto cleanRAST; + } + nsvgRasterize(rast, nsvgImage, (float) ropts->x, (float) ropts->y, + (float) ropts->scale, imgData, w, h, w * 4); + /* transfer the data to a photo block */ + svgblock.pixelPtr = imgData; + svgblock.width = w; + svgblock.height = h; + svgblock.pitch = w * 4; + svgblock.pixelSize = 4; + for (c = 0; c <= 3; c++) { + svgblock.offset[c] = c; + } + if (Tk_PhotoExpand(interp, imageHandle, + destX + width, destY + height) != TCL_OK) { + goto cleanRAST; + } + if (Tk_PhotoPutBlock(interp, imageHandle, &svgblock, destX, destY, + width, height, TK_PHOTO_COMPOSITE_SET) != TCL_OK) { + goto cleanimg; + } + ckfree(imgData); + nsvgDeleteRasterizer(rast); + nsvgDelete(nsvgImage); + return TCL_OK; + +cleanimg: + ckfree(imgData); + +cleanRAST: + nsvgDeleteRasterizer(rast); + +cleanAST: + nsvgDelete(nsvgImage); + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachePtr -- + * + * This function is called to get the per interpreter used + * svg image cache. + * + * Results: + * Return a pointer to the used cache. + * + * Side effects: + * Initialize the cache on the first call. + * + *---------------------------------------------------------------------- + */ + +static NSVGcache * +GetCachePtr( + Tcl_Interp *interp +) { + NSVGcache *cachePtr = Tcl_GetAssocData(interp, "tksvgnano", NULL); + if (cachePtr == NULL) { + cachePtr = ckalloc(sizeof(NSVGcache)); + cachePtr->dataOrChan = NULL; + Tcl_DStringInit(&cachePtr->formatString); + cachePtr->nsvgImage = NULL; + Tcl_SetAssocData(interp, "tksvgnano", FreeCache, cachePtr); + } + return cachePtr; +} + +/* + *---------------------------------------------------------------------- + * + * CacheSVG -- + * + * Add the given svg image informations to the cache for further usage. + * + * Results: + * Return 1 on success, and 0 otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static int +CacheSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + NSVGimage *nsvgImage, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = dataOrChan; + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + Tcl_DStringAppend(&cachePtr->formatString, data, length); + } + cachePtr->nsvgImage = nsvgImage; + cachePtr->ropts = *ropts; + return 1; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * GetCachedSVG -- + * + * Try to get the NSVGimage from the internal cache. + * + * Results: + * Return the found NSVGimage on success, and NULL otherwise. + * + * Side effects: + * Calls the CleanCache() function. + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +GetCachedSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + int length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + NSVGimage *nsvgImage = NULL; + + if ((cachePtr != NULL) && (cachePtr->nsvgImage != NULL) && + (cachePtr->dataOrChan == dataOrChan)) { + if (formatObj != NULL) { + data = Tcl_GetStringFromObj(formatObj, &length); + if (strcmp(data, Tcl_DStringValue(&cachePtr->formatString)) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } else if (Tcl_DStringLength(&cachePtr->formatString) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; + } + } + CleanCache(interp); + return nsvgImage; +} + +/* + *---------------------------------------------------------------------- + * + * CleanCache -- + * + * Reset the cache and delete the saved image in it. + * + * Results: + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static void +CleanCache(Tcl_Interp *interp) +{ + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = NULL; + Tcl_DStringSetLength(&cachePtr->formatString, 0); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + cachePtr->nsvgImage = NULL; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * FreeCache -- + * + * This function is called to clean up the internal cache data. + * + * Results: + * + * Side effects: + * Existing image data in the cache and the cache will be deleted. + * + *---------------------------------------------------------------------- + */ + +static void +FreeCache(ClientData clientData, Tcl_Interp *interp) +{ + NSVGcache *cachePtr = clientData; + + Tcl_DStringFree(&cachePtr->formatString); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + } + ckfree(cachePtr); +} + diff --git a/generic/tkInt.h b/generic/tkInt.h index 767bbbb..e94dcad 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -344,6 +344,9 @@ typedef struct TkDisplay { * by that master. */ int geomInit; +#define TkGetGeomMaster(tkwin) (((TkWindow *)tkwin)->maintainerPtr != NULL ? \ + ((TkWindow *)tkwin)->maintainerPtr : ((TkWindow *)tkwin)->parentPtr) + /* * Information used by tkGet.c only: */ @@ -837,10 +840,14 @@ typedef struct TkWindow { int minReqWidth; /* Minimum requested width. */ int minReqHeight; /* Minimum requested height. */ - char *geometryMaster; #ifdef TK_USE_INPUT_METHODS int ximGeneration; /* Used to invalidate XIC */ #endif /* TK_USE_INPUT_METHODS */ + char *geomMgrName; /* Records the name of the geometry manager. */ + struct TkWindow *maintainerPtr; + /* The geometry master for this window. The + * value is NULL if the window has no master or + * if its master is its parent. */ } TkWindow; /* @@ -979,6 +986,7 @@ MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; +MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtSVGnano; MODULE_SCOPE TkMainInfo *tkMainWindowList; MODULE_SCOPE Tk_ImageType tkPhotoImageType; MODULE_SCOPE Tcl_HashTable tkPredefBitmapTable; @@ -1254,11 +1262,16 @@ MODULE_SCOPE int TkInitTkCmd(Tcl_Interp *interp, ClientData clientData); MODULE_SCOPE int TkInitFontchooser(Tcl_Interp *interp, ClientData clientData); +MODULE_SCOPE void TkInitEmbeddedConfigurationInformation( + Tcl_Interp *interp); MODULE_SCOPE void TkpWarpPointer(TkDisplay *dispPtr); MODULE_SCOPE void TkpCancelWarp(TkDisplay *dispPtr); MODULE_SCOPE int TkListCreateFrame(ClientData clientData, Tcl_Interp *interp, Tcl_Obj *listObj, int toplevel, Tcl_Obj *nameObj); +MODULE_SCOPE void TkRotatePoint(double originX, double originY, + double sine, double cosine, double *xPtr, + double *yPtr); #ifdef _WIN32 #define TkParseColor XParseColor @@ -1279,12 +1292,17 @@ MODULE_SCOPE void TkUnixSetXftClipRegion(TkRegion clipRegion); MODULE_SCOPE size_t TkUniCharToUtf(int, char *); #endif +#if TCL_MAJOR_VERSION > 8 #define TkGetStringFromObj(objPtr, lenPtr) \ (((objPtr)->bytes ? 0 : Tcl_GetString(objPtr)), \ *(lenPtr) = (objPtr)->length, (objPtr)->bytes) - MODULE_SCOPE unsigned char *TkGetByteArrayFromObj(Tcl_Obj *objPtr, size_t *lengthPtr); +#else +#define TkGetStringFromObj Tcl_GetStringFromObj +#define TkGetByteArrayFromObj Tcl_GetByteArrayFromObj +#endif + /* * Unsupported commands. diff --git a/generic/tkListbox.c b/generic/tkListbox.c index f722674..64026b1 100644 --- a/generic/tkListbox.c +++ b/generic/tkListbox.c @@ -1103,7 +1103,7 @@ ListboxBboxSubCmd( Tcl_Obj *el, *results[4]; const char *stringRep; int pixelWidth, x, y, result; - size_t stringLen; + TkSizeT stringLen; Tk_FontMetrics fm; /* @@ -1842,7 +1842,7 @@ DisplayListbox( register Tk_Window tkwin = listPtr->tkwin; GC gc; int i, limit, x, y, prevSelected, freeGC; - size_t stringLen; + TkSizeT stringLen; Tk_FontMetrics fm; Tcl_Obj *curElement; Tcl_HashEntry *entry; @@ -2239,7 +2239,7 @@ ListboxComputeGeometry( * window. */ { int width, height, pixelWidth, pixelHeight, i, result; - size_t textLength; + TkSizeT textLength; Tk_FontMetrics fm; Tcl_Obj *element; const char *text; @@ -2327,7 +2327,7 @@ ListboxInsertSubCmd( Tcl_Obj *const objv[]) /* New elements (one per entry). */ { int i, oldMaxWidth, pixelWidth, result; - size_t length; + TkSizeT length; Tcl_Obj *newListObj; const char *stringRep; @@ -2442,7 +2442,7 @@ ListboxDeleteSubCmd( int last) /* Index of last element to delete. */ { int count, i, widthChanged, result, pixelWidth; - size_t length; + TkSizeT length; Tcl_Obj *newListObj, *element; const char *stringRep; Tcl_HashEntry *entry; @@ -3128,7 +3128,7 @@ ListboxFetchSelection( register Listbox *listPtr = clientData; Tcl_DString selection; int count, needNewline, i; - size_t length, stringLen; + TkSizeT length, stringLen; Tcl_Obj *curElement; const char *stringRep; Tcl_HashEntry *entry; @@ -3166,7 +3166,7 @@ ListboxFetchSelection( * Copy the requested portion of the selection to the buffer. */ - if (length <= (size_t)offset) { + if (length <= (TkSizeT)offset) { count = 0; } else { count = length - offset; diff --git a/generic/tkPack.c b/generic/tkPack.c index c1b6345..faa3533 100644 --- a/generic/tkPack.c +++ b/generic/tkPack.c @@ -1182,7 +1182,7 @@ PackAfter( packPtr->flags |= OLD_STYLE; for (index = 0 ; index < optionCount; index++) { Tcl_Obj *curOptPtr = options[index]; - size_t length; + TkSizeT length; const char *curOpt = TkGetStringFromObj(curOptPtr, &length); c = curOpt[0]; @@ -1240,7 +1240,7 @@ PackAfter( packPtr->iPadY = 0; index++; } else if ((c == 'f') && (length > 1) - && (strncmp(curOpt, "frame", (size_t) length) == 0)) { + && (strncmp(curOpt, "frame", length) == 0)) { if (optionCount < (index+2)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "wrong # args: \"frame\"" @@ -1540,6 +1540,7 @@ ConfigureSlaves( { Packer *masterPtr, *slavePtr, *prevPtr, *otherPtr; Tk_Window other, slave, parent, ancestor; + TkWindow *master; int i, j, numWindows, tmp, positionGiven; const char *string; static const char *const optionStrings[] = { @@ -1817,6 +1818,24 @@ ConfigureSlaves( } /* + * Check for management loops. + */ + + for (master = (TkWindow *)masterPtr->tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slave) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s, would cause management loop", + Tcl_GetString(objv[j]), Tk_PathName(masterPtr->tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + return TCL_ERROR; + } + } + if (masterPtr->tkwin != Tk_Parent(slave)) { + ((TkWindow *)slave)->maintainerPtr = (TkWindow *)masterPtr->tkwin; + } + + /* * Unpack the slave if it's currently packed, then position it after * prevPtr. */ diff --git a/generic/tkPanedWindow.c b/generic/tkPanedWindow.c index b86df0a..c605bf3 100644 --- a/generic/tkPanedWindow.c +++ b/generic/tkPanedWindow.c @@ -1011,7 +1011,7 @@ ConfigureSlaves( i = sizeof(Slave *) * (pwPtr->numSlaves + numNewSlaves); newSlaves = ckalloc(i); - memset(newSlaves, 0, (size_t) i); + memset(newSlaves, 0, i); if (index == -1) { /* * If none of the existing slaves have to be moved, just copy the old diff --git a/generic/tkPkgConfig.c b/generic/tkPkgConfig.c new file mode 100644 index 0000000..ac0583f --- /dev/null +++ b/generic/tkPkgConfig.c @@ -0,0 +1,166 @@ +/* + * tkPkgConfig.c -- + * + * This file contains the configuration information to embed into the tcl + * binary library. + * + * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net> + * Copyright (c) 2017 Stuart Cassoff <stwo@users.sourceforge.net> + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +/* Note, the definitions in this module are influenced by the following C + * preprocessor macros: + * + * OSCMa = shortcut for "old style configuration macro activates" + * NSCMdt = shortcut for "new style configuration macro declares that" + * + * - TCL_THREADS OSCMa compilation as threaded. + * - TCL_MEM_DEBUG OSCMa memory debugging. + * + * - TCL_CFG_DO64BIT NSCMdt tk is compiled for a 64bit system. + * - NDEBUG NSCMdt tk is compiled with symbol info off. + * - TCL_CFG_OPTIMIZED NSCMdt tk is compiled with cc optimizations on + * - TCL_CFG_PROFILED NSCMdt tk is compiled with profiling info. + * + * - _WIN32 || __CYGWIN__ The value for the fontsytem key will be + * MAC_OSX_TK chosen based on these macros/defines. + * HAVE_XFT NSCMdt xft font support was requested. + * + * - CFG_RUNTIME_* Paths to various stuff at runtime. + * - CFG_INSTALL_* Paths to various stuff at installation time. + * + * - TCL_CFGVAL_ENCODING string containing the encoding used for the + * configuration values. + */ + +#include "tkInt.h" + + +#ifndef TCL_CFGVAL_ENCODING +#define TCL_CFGVAL_ENCODING "ascii" +#endif + +/* + * Use C preprocessor statements to define the various values for the embedded + * configuration information. + */ + +#ifdef TCL_THREADS +# define CFG_THREADED "1" +#else +# define CFG_THREADED "0" +#endif + +#ifdef TCL_MEM_DEBUG +# define CFG_MEMDEBUG "1" +#else +# define CFG_MEMDEBUG "0" +#endif + +#ifdef TCL_CFG_DO64BIT +# define CFG_64 "1" +#else +# define CFG_64 "0" +#endif + +#ifndef NDEBUG +# define CFG_DEBUG "1" +#else +# define CFG_DEBUG "0" +#endif + +#ifdef TCL_CFG_OPTIMIZED +# define CFG_OPTIMIZED "1" +#else +# define CFG_OPTIMIZED "0" +#endif + +#ifdef TCL_CFG_PROFILED +# define CFG_PROFILED "1" +#else +# define CFG_PROFILED "0" +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +# define CFG_FONTSYSTEM "gdi" +#elif defined(MAC_OSX_TK) +# define CFG_FONTSYSTEM "cocoa" +#elif defined(HAVE_XFT) +# define CFG_FONTSYSTEM "xft" +#else +# define CFG_FONTSYSTEM "x11" +#endif + +static Tcl_Config const cfg[] = { + {"debug", CFG_DEBUG}, + {"threaded", CFG_THREADED}, + {"profiled", CFG_PROFILED}, + {"64bit", CFG_64}, + {"optimized", CFG_OPTIMIZED}, + {"mem_debug", CFG_MEMDEBUG}, + {"fontsystem", CFG_FONTSYSTEM}, + + /* Runtime paths to various stuff */ + +#ifdef CFG_RUNTIME_LIBDIR + {"libdir,runtime", CFG_RUNTIME_LIBDIR}, +#endif +#ifdef CFG_RUNTIME_BINDIR + {"bindir,runtime", CFG_RUNTIME_BINDIR}, +#endif +#ifdef CFG_RUNTIME_SCRDIR + {"scriptdir,runtime", CFG_RUNTIME_SCRDIR}, +#endif +#ifdef CFG_RUNTIME_INCDIR + {"includedir,runtime", CFG_RUNTIME_INCDIR}, +#endif +#ifdef CFG_RUNTIME_DOCDIR + {"docdir,runtime", CFG_RUNTIME_DOCDIR}, +#endif +#ifdef CFG_RUNTIME_DEMODIR + {"demodir,runtime", CFG_RUNTIME_DEMODIR}, +#endif + + /* Installation paths to various stuff */ + +#ifdef CFG_INSTALL_LIBDIR + {"libdir,install", CFG_INSTALL_LIBDIR}, +#endif +#ifdef CFG_INSTALL_BINDIR + {"bindir,install", CFG_INSTALL_BINDIR}, +#endif +#ifdef CFG_INSTALL_SCRDIR + {"scriptdir,install", CFG_INSTALL_SCRDIR}, +#endif +#ifdef CFG_INSTALL_INCDIR + {"includedir,install", CFG_INSTALL_INCDIR}, +#endif +#ifdef CFG_INSTALL_DOCDIR + {"docdir,install", CFG_INSTALL_DOCDIR}, +#endif +#ifdef CFG_INSTALL_DEMODIR + {"demodir,install", CFG_INSTALL_DEMODIR}, +#endif + + /* Last entry, closes the array */ + {NULL, NULL} +}; + +void +TkInitEmbeddedConfigurationInformation( + Tcl_Interp *interp) /* Interpreter the configuration command is + * registered in. */ +{ + Tcl_RegisterConfig(interp, "tk", cfg, TCL_CFGVAL_ENCODING); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ diff --git a/generic/tkPlace.c b/generic/tkPlace.c index 44eac5d..f6be0e5 100644 --- a/generic/tkPlace.c +++ b/generic/tkPlace.c @@ -617,6 +617,7 @@ ConfigureSlave( int mask; Slave *slavePtr; Tk_Window masterWin = (Tk_Window) NULL; + TkWindow *master; if (Tk_TopWinHierarchy(tkwin)) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -694,6 +695,25 @@ ConfigureSlave( Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); goto error; } + + /* + * Check for management loops. + */ + + for (master = (TkWindow *)tkwin; master != NULL; + master = (TkWindow *)TkGetGeomMaster(master)) { + if (master == (TkWindow *)slavePtr->tkwin) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't put %s inside %s, would cause management loop", + Tk_PathName(slavePtr->tkwin), Tk_PathName(tkwin))); + Tcl_SetErrorCode(interp, "TK", "GEOMETRY", "LOOP", NULL); + goto error; + } + } + if (tkwin != Tk_Parent(slavePtr->tkwin)) { + ((TkWindow *)slavePtr->tkwin)->maintainerPtr = (TkWindow *)tkwin; + } + if ((slavePtr->masterPtr != NULL) && (slavePtr->masterPtr->tkwin == tkwin)) { /* diff --git a/generic/tkRectOval.c b/generic/tkRectOval.c index 58bb99a..507f072 100644 --- a/generic/tkRectOval.c +++ b/generic/tkRectOval.c @@ -148,6 +148,8 @@ static int RectToArea(Tk_Canvas canvas, Tk_Item *itemPtr, double *areaPtr); static double RectToPoint(Tk_Canvas canvas, Tk_Item *itemPtr, double *pointPtr); +static void RotateRectOval(Tk_Canvas canvas, Tk_Item *itemPtr, + double originX, double originY, double angleRad); static void ScaleRectOval(Tk_Canvas canvas, Tk_Item *itemPtr, double originX, double originY, double scaleX, double scaleY); @@ -180,7 +182,8 @@ Tk_ItemType tkRectangleType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateRectOval, /* rotateProc */ + 0, NULL, NULL }; Tk_ItemType tkOvalType = { @@ -204,7 +207,8 @@ Tk_ItemType tkOvalType = { NULL, /* insertProc */ NULL, /* dTextProc */ NULL, /* nextPtr */ - NULL, 0, NULL, NULL + RotateRectOval, /* rotateProc */ + 0, NULL, NULL }; /* @@ -1285,6 +1289,57 @@ OvalToArea( /* *-------------------------------------------------------------- * + * RotateRectOval -- + * + * This function is invoked to rotate a rectangle or oval item's + * coordinates. It works by rotating a computed point in the centre of + * the bounding box, NOT by rotating the corners of the bounding box. + * + * Results: + * None. + * + * Side effects: + * The position of the rectangle or oval is rotated by angleRad about + * (originX, originY), and the bounding box is updated in the generic + * part of the item structure. + * + *-------------------------------------------------------------- + */ + +static void +RotateRectOval( + Tk_Canvas canvas, /* Canvas containing rectangle. */ + Tk_Item *itemPtr, /* Rectangle to be scaled. */ + double originX, double originY, + /* Origin about which to rotate rect. */ + double angleRad) /* Amount to scale in X direction. */ +{ + RectOvalItem *rectOvalPtr = (RectOvalItem *) itemPtr; + double newX, newY, oldX, oldY; + + /* + * Compute the centre of the box, then rotate that about the origin. + */ + + newX = oldX = (rectOvalPtr->bbox[0] + rectOvalPtr->bbox[2]) / 2.0; + newY = oldY = (rectOvalPtr->bbox[1] + rectOvalPtr->bbox[3]) / 2.0; + TkRotatePoint(originX, originY, sin(angleRad), cos(angleRad), + &newX, &newY); + + /* + * Apply the translation to the box. + */ + + rectOvalPtr->bbox[0] += newX - oldX; + rectOvalPtr->bbox[1] += newY - oldY; + rectOvalPtr->bbox[2] += newX - oldX; + rectOvalPtr->bbox[3] += newY - oldY; + ComputeRectOvalBbox(canvas, rectOvalPtr); +} + +/* + *-------------------------------------------------------------- + * * ScaleRectOval -- * * This function is invoked to rescale a rectangle or oval item. diff --git a/generic/tkScrollbar.c b/generic/tkScrollbar.c index 02daafb..5b0f6af 100644 --- a/generic/tkScrollbar.c +++ b/generic/tkScrollbar.c @@ -227,7 +227,7 @@ ScrollbarWidgetObjCmd( { register TkScrollbar *scrollPtr = clientData; int result = TCL_OK, cmdIndex; - size_t length; + TkSizeT length; static const char *const commandNames[] = { "activate", "cget", "configure", "delta", "fraction", "get", "identify", "set", NULL diff --git a/generic/tkSelect.c b/generic/tkSelect.c index 513cf75..85865de 100644 --- a/generic/tkSelect.c +++ b/generic/tkSelect.c @@ -831,7 +831,7 @@ Tk_SelectionObjCmd( const char *targetName = NULL; const char *formatName = NULL; register CommandInfo *cmdInfoPtr; - size_t cmdLength; + TkSizeT cmdLength; static const char *const handleOptionStrings[] = { "-format", "-selection", "-type", NULL }; @@ -1387,7 +1387,7 @@ HandleTclCommand( string = Tcl_GetStringFromObj(Tcl_GetObjResult(interp), &length); count = (length > maxBytes) ? maxBytes : length; - memcpy(buffer, string, (size_t) count); + memcpy(buffer, string, count); buffer[count] = '\0'; /* @@ -1410,7 +1410,7 @@ HandleTclCommand( cmdInfoPtr->charOffset += numChars; length = p - string; if (length > 0) { - strncpy(cmdInfoPtr->buffer, string, (size_t) length); + strncpy(cmdInfoPtr->buffer, string, length); } cmdInfoPtr->buffer[length] = '\0'; } diff --git a/generic/tkText.c b/generic/tkText.c index c748015..acd0ed3 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -864,7 +864,7 @@ TextWidgetObjCmd( for (i = 2; i < objc-2; i++) { int value; - size_t length; + TkSizeT length; const char *option = TkGetStringFromObj(objv[i], &length); char c; @@ -1259,7 +1259,7 @@ TextWidgetObjCmd( Tcl_Obj *objPtr = NULL; int i, found = 0, visible = 0; const char *name; - size_t length; + TkSizeT length; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, @@ -2631,7 +2631,7 @@ InsertChars( int viewUpdate) /* Update the view if set. */ { int lineIndex; - size_t length; + TkSizeT length; TkText *tPtr; int *lineAndByteIndex; int resetViewCount; @@ -3509,7 +3509,7 @@ TextFetchSelection( if ((segPtr->typePtr == &tkTextCharType) && !TkTextIsElided(textPtr, &textPtr->selIndex, NULL)) { memcpy(buffer, segPtr->body.chars + offsetInSeg, - (size_t) chunkSize); + chunkSize); buffer += chunkSize; maxBytes -= chunkSize; count += chunkSize; @@ -4273,7 +4273,7 @@ TextSearchFoundMatch( int matchLength) /* Length also in bytes/chars as per search * type. */ { - size_t numChars; + TkSizeT numChars; int leftToScan; TkTextIndex curIndex, foundIndex; TkTextSegment *segPtr; @@ -4313,7 +4313,7 @@ TextSearchFoundMatch( if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) { if (searchSpecPtr->backwards ^ - ((matchOffset + numChars + 1) > (size_t) searchSpecPtr->stopOffset + 1)) { + ((matchOffset + numChars + 1) > (TkSizeT) searchSpecPtr->stopOffset + 1)) { return 0; } } @@ -4753,7 +4753,7 @@ TextDumpCmd( if (objc == arg) { TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES); } else { - size_t length; + TkSizeT length; const char *str; if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) { @@ -5760,7 +5760,7 @@ SearchCore( */ int firstOffset, lastOffset; - size_t matchOffset, matchLength; + TkSizeT matchOffset, matchLength; int passes; int lineNum = searchSpecPtr->startLine; int code = TCL_OK; @@ -5781,9 +5781,9 @@ SearchCore( #define LOTS_OF_MATCHES 20 int matchNum = LOTS_OF_MATCHES; - size_t smArray[2 * LOTS_OF_MATCHES]; - size_t *storeMatch = smArray; - size_t *storeLength = smArray + LOTS_OF_MATCHES; + TkSizeT smArray[2 * LOTS_OF_MATCHES]; + TkSizeT *storeMatch = smArray; + TkSizeT *storeLength = smArray + LOTS_OF_MATCHES; int lastBackwardsLineMatch = -1; int lastBackwardsMatchOffset = -1; @@ -5968,11 +5968,11 @@ SearchCore( do { int ch; const char *p; - size_t lastFullLine = lastOffset; + TkSizeT lastFullLine = lastOffset; if (firstNewLine == -1) { if (searchSpecPtr->strictLimits - && (firstOffset + matchLength + 1 > (size_t)lastOffset + 1)) { + && (firstOffset + matchLength + 1 > (TkSizeT)lastOffset + 1)) { /* * Not enough characters to match. */ @@ -6090,7 +6090,7 @@ SearchCore( * exact searches. */ - if ((size_t)lastTotal - skipFirst + 1 >= matchLength + 1) { + if ((TkSizeT)lastTotal - skipFirst + 1 >= matchLength + 1) { /* * We now have enough text to match, so we * make a final test and break whatever the @@ -6172,7 +6172,7 @@ SearchCore( } } else { firstOffset = matchLength ? p - startOfLine + matchLength - : p - startOfLine + (size_t)1; + : p - startOfLine + (TkSizeT)1; if (firstOffset >= lastOffset) { /* * Now, we have to be careful not to find @@ -6212,7 +6212,7 @@ SearchCore( do { Tcl_RegExpInfo info; int match; - size_t lastFullLine = lastOffset; + TkSizeT lastFullLine = lastOffset; match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset, 1, (firstOffset>0 ? TCL_REG_NOTBOL : 0)); @@ -6230,9 +6230,9 @@ SearchCore( if (!match || ((info.extendStart == info.matches[0].start) - && ((size_t) info.matches[0].end == (size_t) lastOffset - firstOffset))) { + && ((TkSizeT) info.matches[0].end == (TkSizeT) lastOffset - firstOffset))) { int extraLines = 0; - size_t prevFullLine; + TkSizeT prevFullLine; /* * If we find a match that overlaps more than one line, we @@ -6248,7 +6248,7 @@ SearchCore( lastNonOverlap = lastTotal; } - if ((size_t) info.extendStart == (size_t) -1) { + if ((TkSizeT) info.extendStart == TCL_AUTO_LENGTH) { /* * No multi-line match is possible. */ @@ -6345,9 +6345,9 @@ SearchCore( */ if ((match && - firstOffset+(size_t) info.matches[0].end != (size_t) lastTotal && - firstOffset+(size_t) info.matches[0].end + 1 < prevFullLine + 1) - || (size_t) info.extendStart == (size_t) -1) { + firstOffset + (TkSizeT) info.matches[0].end != (TkSizeT) lastTotal && + firstOffset + (TkSizeT) info.matches[0].end + 1 < prevFullLine + 1) + || (TkSizeT) info.extendStart == TCL_AUTO_LENGTH) { break; } @@ -6358,10 +6358,10 @@ SearchCore( * that line. */ - if (match && ((size_t) info.matches[0].start + 1 >= (size_t) lastOffset + 1)) { + if (match && ((TkSizeT) info.matches[0].start + 1 >= (TkSizeT) lastOffset + 1)) { break; } - if (match && ((firstOffset + (size_t) info.matches[0].end) + if (match && ((firstOffset + (TkSizeT) info.matches[0].end) >= prevFullLine)) { if (extraLines > 0) { extraLinesSearched = extraLines - 1; @@ -6415,7 +6415,7 @@ SearchCore( * Possible overlap or enclosure. */ - if ((size_t)thisOffset - lastNonOverlap >= + if ((TkSizeT)thisOffset - lastNonOverlap >= lastBackwardsMatchOffset + matchLength + 1){ /* * Totally encloses previous match, so @@ -6497,11 +6497,11 @@ SearchCore( * previous match. */ - if (matchOffset == (size_t)-1 || + if (matchOffset == TCL_AUTO_LENGTH || ((searchSpecPtr->all || searchSpecPtr->backwards) - && (((size_t)firstOffset + 1< matchOffset + 1) - || ((firstOffset + (size_t) info.matches[0].end - - (size_t) info.matches[0].start) + && (((TkSizeT)firstOffset + 1< matchOffset + 1) + || ((firstOffset + (TkSizeT) info.matches[0].end + - (TkSizeT) info.matches[0].start) > matchOffset + matchLength)))) { matchOffset = firstOffset; @@ -6520,11 +6520,11 @@ SearchCore( * matches on the heap. */ - size_t *newArray = - ckalloc(4 * matchNum * sizeof(size_t)); - memcpy(newArray, storeMatch, matchNum*sizeof(size_t)); + TkSizeT *newArray = + ckalloc(4 * matchNum * sizeof(TkSizeT)); + memcpy(newArray, storeMatch, matchNum*sizeof(TkSizeT)); memcpy(newArray + 2*matchNum, storeLength, - matchNum * sizeof(int)); + matchNum * sizeof(TkSizeT)); if (storeMatch != smArray) { ckfree(storeMatch); } @@ -6665,7 +6665,7 @@ SearchCore( * we are done. */ - if ((lastBackwardsLineMatch == -1) && (matchOffset != (size_t) -1) + if ((lastBackwardsLineMatch == -1) && (matchOffset != TCL_AUTO_LENGTH) && !searchSpecPtr->all) { searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr, lineInfo, theLine, matchOffset, matchLength); @@ -6940,7 +6940,7 @@ TkpTesttextCmd( Tcl_Obj *const objv[]) /* Argument strings. */ { TkText *textPtr; - size_t len; + TkSizeT len; int lineIndex, byteIndex, byteOffset; TkTextIndex index; char buf[64]; diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 1f44d37..d4f6b83 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -6153,7 +6153,7 @@ TkTextYviewCmd( TextDInfo *dInfoPtr = textPtr->dInfoPtr; int pickPlace, type; int pixels, count; - size_t switchLength; + TkSizeT switchLength; double fraction; TkTextIndex index; @@ -6172,7 +6172,7 @@ TkTextYviewCmd( pickPlace = 0; if (Tcl_GetString(objv[2])[0] == '-') { - register const char *switchStr = + const char *switchStr = TkGetStringFromObj(objv[2], &switchLength); if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace", diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index 4b773cb..aca08fb 100644 --- a/generic/tkTextMark.c +++ b/generic/tkTextMark.c @@ -126,7 +126,7 @@ TkTextMarkCmd( switch ((enum markOptions) optionIndex) { case MARK_GRAVITY: { char c; - size_t length; + TkSizeT length; const char *str; if (objc < 4 || objc > 5) { diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index 25e6af1..cb0993b 100644 --- a/generic/tkTextTag.c +++ b/generic/tkTextTag.c @@ -1108,7 +1108,7 @@ FindTag( Tcl_Obj *tagName) /* Name of desired tag. */ { Tcl_HashEntry *hPtr; - size_t len; + TkSizeT len; const char *str; str = TkGetStringFromObj(tagName, &len); diff --git a/generic/tkUtil.c b/generic/tkUtil.c index 6850f47..2950fe0 100644 --- a/generic/tkUtil.c +++ b/generic/tkUtil.c @@ -729,7 +729,7 @@ Tk_GetScrollInfoObj( int *intPtr) /* Filled in with number of pages or lines to * scroll, if any. */ { - size_t length; + TkSizeT length; const char *arg = TkGetStringFromObj(objv[2], &length); #define ArgPfxEq(str) \ @@ -1272,24 +1272,17 @@ size_t TkUniCharToUtf(int ch, char *buf) #endif +#if TCL_MAJOR_VERSION > 8 unsigned char * TkGetByteArrayFromObj( Tcl_Obj *objPtr, size_t *lengthPtr ) { - int length; - - unsigned char *result = Tcl_GetByteArrayFromObj(objPtr, &length); -#if TCL_MAJOR_VERSION > 8 - if (sizeof(TCL_HASH_TYPE) > sizeof(int)) { - /* 64-bit and TIP #494 situation: */ - *lengthPtr = *(TCL_HASH_TYPE *) objPtr->internalRep.twoPtrValue.ptr1; - } else -#endif - /* 32-bit or without TIP #494 */ - *lengthPtr = (size_t) (unsigned) length; + unsigned char *result = Tcl_GetByteArrayFromObj(objPtr, NULL); + *lengthPtr = *(size_t *) objPtr->internalRep.twoPtrValue.ptr1; return result; } +#endif /* TCL_MAJOR_VERSION > 8 */ /* * Local Variables: diff --git a/generic/tkWindow.c b/generic/tkWindow.c index d8c2068..6ad9477 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -340,6 +340,7 @@ CreateTopLevelWindow( Tk_CreatePhotoImageFormat(&tkImgFmtGIF); Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); + Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); } if ((parent != NULL) && (screenName != NULL) && (screenName[0] == '\0')) { @@ -665,6 +666,8 @@ TkAllocWindow( winPtr->selHandlerList = NULL; winPtr->geomMgrPtr = NULL; winPtr->geomData = NULL; + winPtr->geomMgrName = NULL; + winPtr->maintainerPtr = NULL; winPtr->reqWidth = winPtr->reqHeight = 1; winPtr->internalBorderLeft = 0; winPtr->wmInfoPtr = NULL; @@ -676,7 +679,6 @@ TkAllocWindow( winPtr->internalBorderBottom = 0; winPtr->minReqWidth = 0; winPtr->minReqHeight = 0; - winPtr->geometryMaster = NULL; return winPtr; } @@ -1461,9 +1463,9 @@ Tk_DestroyWindow( TkOptionDeadWindow(winPtr); TkSelDeadWindow(winPtr); TkGrabDeadWindow(winPtr); - if (winPtr->geometryMaster != NULL) { - ckfree(winPtr->geometryMaster); - winPtr->geometryMaster = NULL; + if (winPtr->geomMgrName != NULL) { + ckfree(winPtr->geomMgrName); + winPtr->geomMgrName = NULL; } if (winPtr->mainPtr != NULL) { if (winPtr->pathName != NULL) { @@ -3065,6 +3067,12 @@ Initialize( } /* + * TIP #59: Make embedded configuration information available. + */ + + TkInitEmbeddedConfigurationInformation(interp); + + /* * Ensure that our obj-types are registered with the Tcl runtime. */ @@ -3202,7 +3210,7 @@ Initialize( */ { - size_t numBytes; + TkSizeT numBytes; const char *bytes = TkGetStringFromObj(nameObj, &numBytes); classObj = Tcl_NewStringObj(bytes, numBytes); diff --git a/generic/ttk/ttkEntry.c b/generic/ttk/ttkEntry.c index 444bc83..b592883 100644 --- a/generic/ttk/ttkEntry.c +++ b/generic/ttk/ttkEntry.c @@ -315,7 +315,7 @@ static char *EntryDisplayString(const char *showChar, int numChars) */ static void EntryUpdateTextLayout(Entry *entryPtr) { - size_t length; + TkSizeT length; char *text; Tk_FreeTextLayout(entryPtr->entry.textLayout); if ((entryPtr->entry.numChars != 0) || (entryPtr->entry.placeholderObj == NULL)) { @@ -1001,7 +1001,7 @@ static int EntryConfigure(Tcl_Interp *interp, void *recordPtr, int mask) Ttk_TraceHandle *vt = 0; if (mask & TEXTVAR_CHANGED) { - if (textVarName && *Tcl_GetString(textVarName)) { + if (textVarName && *Tcl_GetString(textVarName) != '\0') { vt = Ttk_TraceVariable(interp, textVarName,EntryTextVariableTrace,entryPtr); if (!vt) return TCL_ERROR; @@ -1362,7 +1362,7 @@ EntryIndex( int *indexPtr) /* Return value */ { # define EntryWidth(e) (Tk_Width(entryPtr->core.tkwin)) /* Not Right */ - size_t length; + TkSizeT length; const char *string = TkGetStringFromObj(indexObj, &length); if (strncmp(string, "end", length) == 0) { @@ -1403,6 +1403,7 @@ EntryIndex( *indexPtr = Tk_PointToChar(entryPtr->entry.textLayout, x - entryPtr->entry.layoutX, 0); + TtkUpdateScrollInfo(entryPtr->entry.xscrollHandle); if (*indexPtr < entryPtr->entry.xscroll.first) { *indexPtr = entryPtr->entry.xscroll.first; } @@ -1697,7 +1698,7 @@ static int EntryXViewCommand( if (EntryIndex(interp, entryPtr, objv[2], &newFirst) != TCL_OK) { return TCL_ERROR; } - TtkScrollTo(entryPtr->entry.xscrollHandle, newFirst); + TtkScrollTo(entryPtr->entry.xscrollHandle, newFirst, 1); return TCL_OK; } return TtkScrollviewCommand(interp, objc, objv, entryPtr->entry.xscrollHandle); diff --git a/generic/ttk/ttkScroll.c b/generic/ttk/ttkScroll.c index 184f5f2..47db6ac 100644 --- a/generic/ttk/ttkScroll.c +++ b/generic/ttk/ttkScroll.c @@ -181,6 +181,19 @@ void TtkScrollbarUpdateRequired(ScrollHandle h) h->flags |= SCROLL_UPDATE_REQUIRED; } +/* TtkUpdateScrollInfo -- + * Call the layoutProc to update the scroll info first, last, and total. + * Do it only if needed, that is when a redisplay is pending (which + * indicates scroll info are possibly out of date). + */ + +void TtkUpdateScrollInfo(ScrollHandle h) +{ + if (h->corePtr->flags & REDISPLAY_PENDING) { + h->corePtr->widgetSpec->layoutProc(h->corePtr); + } +} + /* TtkScrollviewCommand -- * Widget [xy]view command implementation. * @@ -193,7 +206,10 @@ int TtkScrollviewCommand( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle h) { Scrollable *s = h->scrollPtr; - int newFirst = s->first; + int newFirst; + + TtkUpdateScrollInfo(h); + newFirst = s->first; if (objc == 2) { Tcl_Obj *result[2]; @@ -226,15 +242,19 @@ int TtkScrollviewCommand( } } - TtkScrollTo(h, newFirst); + TtkScrollTo(h, newFirst, 0); return TCL_OK; } -void TtkScrollTo(ScrollHandle h, int newFirst) +void TtkScrollTo(ScrollHandle h, int newFirst, int updateScrollInfo) { Scrollable *s = h->scrollPtr; + if (updateScrollInfo) { + TtkUpdateScrollInfo(h); + } + if (newFirst >= s->total) newFirst = s->total - 1; if (newFirst > s->first && s->last >= s->total) /* don't scroll past end */ diff --git a/generic/ttk/ttkTheme.h b/generic/ttk/ttkTheme.h index f087ce3..e067337 100644 --- a/generic/ttk/ttkTheme.h +++ b/generic/ttk/ttkTheme.h @@ -29,9 +29,13 @@ extern "C" { * +++ Defaults for element option specifications. */ #define DEFAULT_FONT "TkDefaultFont" +#ifdef MAC_OSX_TK +#define DEFAULT_BACKGROUND "systemTextBackgroundColor" +#define DEFAULT_FOREGROUND "systemTextColor" +#else #define DEFAULT_BACKGROUND "#d9d9d9" #define DEFAULT_FOREGROUND "black" - +#endif /*------------------------------------------------------------------------ * +++ Widget states. * Keep in sync with stateNames[] in tkstate.c. diff --git a/generic/ttk/ttkTreeview.c b/generic/ttk/ttkTreeview.c index 0f720d1..f912956 100644 --- a/generic/ttk/ttkTreeview.c +++ b/generic/ttk/ttkTreeview.c @@ -2843,10 +2843,10 @@ static int TreeviewSeeCommand( */ rowNumber = RowNumber(tv, item); if (rowNumber < tv->tree.yscroll.first) { - TtkScrollTo(tv->tree.yscrollHandle, rowNumber); + TtkScrollTo(tv->tree.yscrollHandle, rowNumber, 1); } else if (rowNumber >= tv->tree.yscroll.last) { TtkScrollTo(tv->tree.yscrollHandle, - tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last)); + tv->tree.yscroll.first + (1+rowNumber - tv->tree.yscroll.last), 1); } return TCL_OK; diff --git a/generic/ttk/ttkWidget.h b/generic/ttk/ttkWidget.h index 7158a78..e764c3a 100644 --- a/generic/ttk/ttkWidget.h +++ b/generic/ttk/ttkWidget.h @@ -195,7 +195,8 @@ MODULE_SCOPE void TtkFreeScrollHandle(ScrollHandle); MODULE_SCOPE int TtkScrollviewCommand( Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle); -MODULE_SCOPE void TtkScrollTo(ScrollHandle, int newFirst); +MODULE_SCOPE void TtkUpdateScrollInfo(ScrollHandle h); +MODULE_SCOPE void TtkScrollTo(ScrollHandle, int newFirst, int updateScrollInfo); MODULE_SCOPE void TtkScrolled(ScrollHandle, int first, int last, int total); MODULE_SCOPE void TtkScrollbarUpdateRequired(ScrollHandle); diff --git a/library/button.tcl b/library/button.tcl index 80d8bf9..9b13607 100644 --- a/library/button.tcl +++ b/library/button.tcl @@ -748,11 +748,15 @@ proc ::tk::CheckLeave {w} { $w configure -state normal } - # Restore the original button "selected" color; assume that the user - # wasn't monkeying around with things too much. + # Restore the original button "selected" color; but only if the user + # has not changed it in the meantime. if {![$w cget -indicatoron] && [info exist Priv($w,selectcolor)]} { - $w configure -selectcolor $Priv($w,selectcolor) + if {[$w cget -selectcolor] eq $Priv($w,selectcolor) + || ([info exist Priv($w,aselectcolor)] && + [$w cget -selectcolor] eq $Priv($w,aselectcolor))} { + $w configure -selectcolor $Priv($w,selectcolor) + } } unset -nocomplain Priv($w,selectcolor) Priv($w,aselectcolor) diff --git a/library/demos/toolbar.tcl b/library/demos/toolbar.tcl index 0ae4669..cb2a495 100644 --- a/library/demos/toolbar.tcl +++ b/library/demos/toolbar.tcl @@ -31,7 +31,7 @@ ttk::separator $w.sep ttk::frame $t.tearoff -cursor fleur ttk::separator $t.tearoff.to -orient vertical ttk::separator $t.tearoff.to2 -orient vertical -pack $t.tearoff.to -fill y -expand 1 -padx 2 -side left +pack $t.tearoff.to -fill y -expand 1 -padx 4 -side left pack $t.tearoff.to2 -fill y -expand 1 -side left ttk::frame $t.contents grid $t.tearoff $t.contents -sticky nsew @@ -79,7 +79,7 @@ text $w.txt -width 40 -height 10 interp alias {} doInsert {} $w.txt insert end ;# Make bindings easy to write ## Arrange contents -grid $t.button $t.check $t.menu $t.combo -in $t.contents -padx 2 -sticky ns +grid $t.button $t.check $t.menu $t.combo -in $t.contents -padx 2 -pady 4 -sticky ns grid $t -sticky ew grid $w.sep -sticky ew grid $w.msg -sticky ew diff --git a/library/demos/tree.tcl b/library/demos/tree.tcl index 50dba9b..1cc70f8 100644 --- a/library/demos/tree.tcl +++ b/library/demos/tree.tcl @@ -76,7 +76,7 @@ ttk::scrollbar $w.vsb -orient vertical -command "$w.tree yview" ttk::scrollbar $w.hsb -orient horizontal -command "$w.tree xview" $w.tree heading \#0 -text "Directory Structure" $w.tree heading size -text "File Size" -$w.tree column size -stretch 0 -width 70 +$w.tree column size -width 70 populateRoots $w.tree bind $w.tree <<TreeviewOpen>> {populateTree %W [%W focus]} diff --git a/library/demos/ttkpane.tcl b/library/demos/ttkpane.tcl index 7575d76..3f88987 100644 --- a/library/demos/ttkpane.tcl +++ b/library/demos/ttkpane.tcl @@ -104,7 +104,7 @@ if {[tk windowingsystem] ne "aqua"} { pack $w.outer -fill both -expand 1 } else { text $w.txt -wrap word -yscroll "$w.sb set" -width 30 -borderwidth 0 - scrollbar $w.sb -orient vertical -command "$w.txt yview" + ttk::scrollbar $w.sb -orient vertical -command "$w.txt yview" pack $w.sb -side right -fill y -in $w.outer.inRight.bot pack $w.txt -fill both -expand 1 -in $w.outer.inRight.bot pack $w.outer -fill both -expand 1 -padx 10 -pady {6 10} diff --git a/library/menu.tcl b/library/menu.tcl index 8d06868..9d6370a 100644 --- a/library/menu.tcl +++ b/library/menu.tcl @@ -1178,15 +1178,6 @@ if {[tk windowingsystem] eq "aqua"} { set entry 0 } } - if {$entry ne ""} { - if {$entry == [$menu index last]} { - set entryHeight [expr {[winfo reqheight $menu] \ - - [$menu yposition $entry]}] - } else { - set entryHeight [expr {[$menu yposition [expr {$entry+1}]] \ - - [$menu yposition $entry]}] - } - } set x [winfo rootx $button] set y [winfo rooty $button] switch [$button cget -direction] { diff --git a/library/ttk/aquaTheme.tcl b/library/ttk/aquaTheme.tcl index a548d65..d1b0bff 100644 --- a/library/ttk/aquaTheme.tcl +++ b/library/ttk/aquaTheme.tcl @@ -7,45 +7,102 @@ namespace eval ttk::theme::aqua { ttk::style configure . \ -font TkDefaultFont \ - -background systemWindowBody \ - -foreground systemModelessDialogActiveText \ + -background systemWindowBackgroundColor \ + -foreground systemLabelColor \ -selectbackground systemHighlight \ - -selectforeground systemModelessDialogActiveText \ + -selectforeground systemLabelColor \ -selectborderwidth 0 \ -insertwidth 1 ttk::style map . \ - -foreground {disabled systemModelessDialogInactiveText - background systemModelessDialogInactiveText} \ - -selectbackground {background systemHighlightSecondary - !focus systemHighlightSecondary} \ - -selectforeground {background systemModelessDialogInactiveText - !focus systemDialogActiveText} + -foreground { + disabled systemDisabledControlTextColor + background systemLabelColor} \ + -selectbackground { + background systemSelectedTextBackgroundColor + !focus systemSelectedTextBackgroundColor} \ + -selectforeground { + background systemSelectedTextColor + !focus systemSelectedTextColor} + + # Button + ttk::style configure TButton -anchor center -width -6\ + -foreground systemControlTextColor + ttk::style configure TMenubutton -anchor center -padding {2 0 0 2} + ttk::style configure Toolbutton -anchor center + + # Entry + ttk::style configure TEntry \ + -foreground systemTextColor \ + -background systemTextBackgroundColor \ # Workaround for #1100117: # Actually, on Aqua we probably shouldn't stipple images in # disabled buttons even if it did work... ttk::style configure . -stipple {} - ttk::style configure TButton -anchor center -width -6 - ttk::style configure Toolbutton -padding 4 - + # Notebook ttk::style configure TNotebook -tabmargins {10 0} -tabposition n ttk::style configure TNotebook -padding {18 8 18 17} ttk::style configure TNotebook.Tab -padding {12 3 12 2} + ttk::style configure TNotebook.Tab -foreground systemControlTextColor + ttk::style map TNotebook.Tab \ + -foreground { + background systemControlTextColor + disabled systemDisabledControlTextColor + selected systemSelectedTabTextColor} # Combobox: - ttk::style configure TCombobox -postoffset {5 -2 -10 0} + ttk::style configure TCombobox \ + -foreground systemTextColor \ + -background systemTransparent \ + -selectforeground systemSelectedTextColor \ + -selectbackground systemSelectedTextBackgroundColor + ttk::style map TCombobox \ + -foreground { + disabled systemDisabledControlTextColor + } \ + -selectforeground { + !active systemTextColor + } \ + -selectbackground { + !active systemTextBackgroundColor + !focus systemTextBackgroundColor + focus systemSelectedTextBackgroundColor + } + + # Spinbox + ttk::style configure TSpinbox \ + -foreground systemTextColor \ + -background systemTextBackgroundColor \ + -selectforeground systemSelectedTextColor \ + -selectbackground systemSelectedTextBackgroundColor + ttk::style map TSpinbox \ + -foreground { + disabled systemDisabledControlTextColor + } \ + -selectforeground { + !active systemTextColor + } \ + -selectbackground { + !active systemTextBackgroundColor + !focus systemTextBackgroundColor + focus systemSelectedTextBackgroundColor + } # Treeview: - ttk::style configure Heading -font TkHeadingFont - ttk::style configure Treeview -rowheight 18 -background White + ttk::style configure Heading \ + -font TkHeadingFont \ + -foreground systemTextColor \ + -background systemWindowBackgroundColor + ttk::style configure Treeview -rowheight 18 \ + -background systemTextBackgroundColor \ + -foreground systemTextColor \ + -fieldbackground systemTextBackgroundColor ttk::style map Treeview \ - -background [list disabled systemDialogBackgroundInactive \ - {selected background} systemHighlightSecondary \ - selected systemHighlight] \ - -foreground [list disabled systemModelessDialogInactiveText \ - selected systemModelessDialogActiveText] + -background { + selected systemSelectedTextBackgroundColor + } # Enable animation for ttk::progressbar widget: ttk::style configure TProgressbar -period 100 -maxphase 255 diff --git a/library/ttk/combobox.tcl b/library/ttk/combobox.tcl index c1b6da6..1355a04 100644 --- a/library/ttk/combobox.tcl +++ b/library/ttk/combobox.tcl @@ -251,30 +251,16 @@ proc ttk::combobox::UnmapPopdown {w} { ttk::releaseGrab $w } -### -# - -namespace eval ::ttk::combobox { - # @@@ Until we have a proper native scrollbar on Aqua, use - # @@@ the regular Tk one. Use ttk::scrollbar on other platforms. - variable scrollbar ttk::scrollbar - if {[tk windowingsystem] eq "aqua"} { - set scrollbar ::scrollbar - } -} - ## PopdownWindow -- # Returns the popdown widget associated with a combobox, # creating it if necessary. # proc ttk::combobox::PopdownWindow {cb} { - variable scrollbar - if {![winfo exists $cb.popdown]} { set poplevel [PopdownToplevel $cb.popdown] set popdown [ttk::frame $poplevel.f -style ComboboxPopdownFrame] - $scrollbar $popdown.sb \ + ttk::scrollbar $popdown.sb \ -orient vertical -command [list $popdown.l yview] listbox $popdown.l \ -listvariable ttk::combobox::Values($cb) \ diff --git a/library/ttk/entry.tcl b/library/ttk/entry.tcl index c123bc9..e9f249c 100644 --- a/library/ttk/entry.tcl +++ b/library/ttk/entry.tcl @@ -211,7 +211,6 @@ proc ttk::entry::ClosestGap {w x} { ## See $index -- Make sure that the character at $index is visible. # proc ttk::entry::See {w {index insert}} { - update idletasks ;# ensure scroll data up-to-date set c [$w index $index] # @@@ OR: check [$w index left] / [$w index right] if {$c < [$w index @0] || $c >= [$w index @[winfo width $w]]} { diff --git a/library/ttk/menubutton.tcl b/library/ttk/menubutton.tcl index 648cdee..bb947c2 100644 --- a/library/ttk/menubutton.tcl +++ b/library/ttk/menubutton.tcl @@ -61,43 +61,112 @@ if {[tk windowingsystem] eq "x11"} { } # PostPosition -- -# Returns the x and y coordinates where the menu -# should be posted, based on the menubutton and menu size -# and -direction option. +# Returns x and y coordinates and a menu item index. +# If the index is not an empty string the menu should +# be posted so that the upper left corner of the indexed +# menu item is located at the point (x, y). Otherwise +# the top left corner of the menu itself should be located +# at that point. # # TODO: adjust menu width to be at least as wide as the button # for -direction above, below. # -proc ttk::menubutton::PostPosition {mb menu} { - set x [winfo rootx $mb] - set y [winfo rooty $mb] - set dir [$mb cget -direction] - set bw [winfo width $mb] - set bh [winfo height $mb] - set mw [winfo reqwidth $menu] - set mh [winfo reqheight $menu] - set sw [expr {[winfo screenwidth $menu] - $bw - $mw}] - set sh [expr {[winfo screenheight $menu] - $bh - $mh}] - - switch -- $dir { - above { if {$y >= $mh} { incr y -$mh } { incr y $bh } } - below { if {$y <= $sh} { incr y $bh } { incr y -$mh } } - left { if {$x >= $mw} { incr x -$mw } { incr x $bw } } - right { if {$x <= $sw} { incr x $bw } { incr x -$mw } } - flush { - # post menu atop menubutton. - # If there's a menu entry whose label matches the - # menubutton -text, assume this is an optionmenu - # and place that entry over the menubutton. - set index [FindMenuEntry $menu [$mb cget -text]] - if {$index ne ""} { - incr y -[$menu yposition $index] +if {[tk windowingsystem] eq "aqua"} { + proc ::ttk::menubutton::PostPosition {mb menu} { + set menuPad 5 + set buttonPad 1 + set bevelPad 4 + set mh [winfo reqheight $menu] + set bh [expr {[winfo height $mb]} + $buttonPad] + set bbh [expr {[winfo height $mb]} + $bevelPad] + set mw [winfo reqwidth $menu] + set bw [winfo width $mb] + set dF [expr {[winfo width $mb] - [winfo reqwidth $menu] - $menuPad}] + set entry "" + set entry [::tk::MenuFindName $menu [$mb cget -text]] + if {$entry eq ""} { + set entry 0 + } + set x [winfo rootx $mb] + set y [winfo rooty $mb] + switch [$mb cget -direction] { + above { + set entry "" + incr y [expr {-$mh + 2 * $menuPad}] + } + below { + set entry "" + incr y $bh + } + left { + incr y $menuPad + incr x -$mw + } + right { + incr y $menuPad + incr x $bw + } + default { + incr y $bbh } } + return [list $x $y $entry] + } +} else { + proc ::ttk::menubutton::PostPosition {mb menu} { + set mh [expr {[winfo reqheight $menu]}] + set bh [expr {[winfo height $mb]}] + set mw [expr {[winfo reqwidth $menu]}] + set bw [expr {[winfo width $mb]}] + set dF [expr {[winfo width $mb] - [winfo reqwidth $menu]}] + if {[tk windowingsystem] eq "win32"} { + incr mh 6 + incr mw 16 + } + set entry {} + set entry [::tk::MenuFindName $menu [$mb cget -text]] + if {$entry eq {}} { + set entry 0 + } + set x [winfo rootx $mb] + set y [winfo rooty $mb] + switch [$mb cget -direction] { + above { + set entry {} + incr y -$mh + # if we go offscreen to the top, show as 'below' + if {$y < [winfo vrooty $mb]} { + set y [expr {[winfo vrooty $mb] + [winfo rooty $mb]\ + + [winfo reqheight $mb]}] + } + } + below { + set entry {} + incr y $bh + # if we go offscreen to the bottom, show as 'above' + if {($y + $mh) > ([winfo vrooty $mb] + [winfo vrootheight $mb])} { + set y [expr {[winfo vrooty $mb] + [winfo vrootheight $mb] \ + + [winfo rooty $mb] - $mh}] + } + } + left { + incr x -$mw + } + right { + incr x $bw + } + default { + if {[$mb cget -style] eq ""} { + incr x [expr {([winfo width $mb] - \ + [winfo reqwidth $menu])/ 2}] + } else { + incr y $bh + } + } + } + return [list $x $y $entry] } - - return [list $x $y] } # Popdown -- @@ -107,8 +176,8 @@ proc ttk::menubutton::Popdown {mb} { if {[$mb instate disabled] || [set menu [$mb cget -menu]] eq ""} { return } - foreach {x y} [PostPosition $mb $menu] { break } - tk_popup $menu $x $y + foreach {x y entry} [PostPosition $mb $menu] { break } + tk_popup $menu $x $y $entry } # Pulldown (X11 only) -- @@ -121,13 +190,17 @@ proc ttk::menubutton::Pulldown {mb} { if {[$mb instate disabled] || [set menu [$mb cget -menu]] eq ""} { return } - foreach {x y} [PostPosition $mb $menu] { break } set State(pulldown) 1 set State(oldcursor) [$mb cget -cursor] $mb state pressed $mb configure -cursor [$menu cget -cursor] - $menu post $x $y + foreach {x y entry} [PostPosition $mb $menu] { break } + if {$entry ne {}} { + $menu post $x $y $entry + } else { + $menu post $x $y + } tk_menuSetFocus $menu } @@ -143,6 +216,7 @@ proc ttk::menubutton::TransferGrab {mb} { set State(pulldown) 0 set menu [$mb cget -menu] + foreach {x y entry} [PostPosition $mb $menu] { break } tk_popup $menu [winfo rootx $menu] [winfo rooty $menu] } } diff --git a/library/ttk/scrollbar.tcl b/library/ttk/scrollbar.tcl index 17d729d..e1e16e0 100644 --- a/library/ttk/scrollbar.tcl +++ b/library/ttk/scrollbar.tcl @@ -2,24 +2,6 @@ # Bindings for TScrollbar widget # -# Still don't have a working ttk::scrollbar under OSX - -# Swap in a [tk::scrollbar] on that platform, -# unless user specifies -class or -style. -# -if {[tk windowingsystem] eq "aqua"} { - rename ::ttk::scrollbar ::ttk::_scrollbar - proc ttk::scrollbar {w args} { - set constructor ::tk::scrollbar - foreach {option _} $args { - if {$option eq "-class" || $option eq "-style"} { - set constructor ::ttk::_scrollbar - break - } - } - return [$constructor $w {*}$args] - } -} - namespace eval ttk::scrollbar { variable State # State(xPress) -- diff --git a/library/ttk/treeview.tcl b/library/ttk/treeview.tcl index 52fb559..1e8b055 100644 --- a/library/ttk/treeview.tcl +++ b/library/ttk/treeview.tcl @@ -336,6 +336,12 @@ proc ttk::treeview::CloseItem {w item} { ## Toggle -- toggle opened/closed state of item # proc ttk::treeview::Toggle {w item} { + # don't allow toggling on indicators that + # are not present in front of leaf items + if {[$w children $item] == {}} { + return + } + # not a leaf, toggle! if {[$w item $item -open]} { CloseItem $w $item } else { diff --git a/macosx/README b/macosx/README index 1f5d8a1..dbd7003 100644 --- a/macosx/README +++ b/macosx/README @@ -184,10 +184,27 @@ Note that not all attributes are valid for all window classes. Support for the support for some legacy Carbon-specific classes and attributes was removed (they are still accepted by the command but no longer have any effect). -- Another command available in the tk::unsupported::MacWindowStyle namespace is -tk::unsupported::MacWindowStyle tabbingid window ?newId? which can be used to -get or set the tabbingIdentifier for the NSWindow associated with a Tk Window. -See section 3 for details. +- Another command available in the tk::unsupported::MacWindowStyle namespace is: + tk::unsupported::MacWindowStyle tabbingid window ?newId? +which can be used to get or set the tabbingIdentifier for the NSWindow +associated with a Tk Window. See section 3 for details. + +- The command: + tk::unsupported::MacWindowStyle appearance window ?newAappearance? +is available when Tk is built and run on macOS 10.14 (Mojave) or later. In +that case the Ttk widgets all support the "Dark Mode" appearance which was +introduced in 10.14. The command accepts the following values for the optional +newAppearance option: "aqua", "darkaqua", or "auto". If the appearance is set +to aqua or darkaqua then the window will be displayed with the corresponding +appearance independent of any preferences settings. If it is set to "auto" +the appearance will be determined by the preferences. This command can be +used to opt out of Dark Mode on a per-window basis. + +- To determine the current appearance of a window in macOS 10.14 (Mojave) and +higher, one can use the command: + tk::unsupported::MacWindowStyle isdark +The boolean return value is true if the window is currently displayed with the +dark appearance. - If you want to use Remote Debugging with Xcode, you need to set the environment variable XCNOSTDIN to 1 in the Executable editor for Wish. That will @@ -275,8 +292,48 @@ ensure that we maintain consistency, changing the tabbingIdentifier of a window which is already displayed as a tab will also cause it to become a separate window. - -4. Building Tcl/Tk on macOS +4. Ttk, Dark Mode and semantic colors +--------------------------------------- + +With the release of OSX 10.14 (Mojave), Apple introduced the DarkAqua +appearance. Part of the implementation of the Dark Mode was to make +some of the named NSColors have dynamic values. Apple calls these +"semantic colors" because the name does not specify a specific color, +but rather refers to the context in which the color should be used. +Tk now provides the following semantic colors as system colors: +systemTextColor, systemTextBackgroundColor, systemSelectedTextColor, +systemSelectedTextBackgroundColor, systemControlTextColor, +systemDisabledControlTextColor, systemLabelColor, and +systemControlAccentColor. All of these except the last two were +present in OSX 10.0 (and those two are simulated in systems where they +do not exist). The change in 10.14 was that the RGB color value of +these colors became dynamic, meaning that the color value can change +when the application appearance changes. In particular, when a user +selects Dark Mode in the system preferences these colors change +appearance. For example systemTextColor is dark in Aqua and light in +DarkAqua. One additional color, systemSelectedTabTextColor, does not +exist in macOS but is used by Tk to match the different colors used +for Notebook tab text in different OS versions. + +The default background and foreground colors of most of the Tk widgets +have been set to semantic colors, which means that the widgets will change +appearance, and remain usable, when Dark Mode is selected in the system +preferences. However, to get a close match to the native Dark Mode style it +is recommended to use Ttk widgets when possible. + +Apple's tab view and GroupBox objects delimit their content by +displaying it within a rounded rectangle with a background color that +contrasts with the background of the containing object. This means +that the background color of a Ttk widget depends on how deeply it is +nested inside of other widgets that use contrasting backgrounds. To +support this, there are 8 contrasting system colors named +systemWindowBackgroundColor, and systemWindowBackgroundColor1 - 7. +The systemWindowBackgroundColor is the standard background for a +dialog window and the others match the contrasting background colors +used in ttk::notebooks and ttk::labelframes which are nested to the +corresponding depth. + +5. Building Tcl/Tk on macOS ------------------------------ - At least macOS 10.3 is required to build Tcl and TkX11, and macOS 10.6 diff --git a/macosx/tkMacOSXButton.c b/macosx/tkMacOSXButton.c index 484dcf2..00c7c9b 100644 --- a/macosx/tkMacOSXButton.c +++ b/macosx/tkMacOSXButton.c @@ -1,8 +1,8 @@ /* * tkMacOSXButton.c -- * - * This file implements the Macintosh specific portion of the - * button widgets. + * This file implements the Macintosh specific portion of the button + * widgets. * * Copyright (c) 1996-1997 by Sun Microsystems, Inc. * Copyright 2001, Apple Computer, Inc. @@ -70,20 +70,23 @@ typedef struct { * Forward declarations for procedures defined later in this file: */ - -static void ButtonBackgroundDrawCB (const HIRect *btnbounds, MacButton *ptr, - SInt16 depth, Boolean isColorDev); -static void ButtonContentDrawCB (const HIRect *bounds, ThemeButtonKind kind, - const HIThemeButtonDrawInfo *info, MacButton *ptr, SInt16 depth, - Boolean isColorDev); -static void ButtonEventProc(ClientData clientData, XEvent *eventPtr); -static void TkMacOSXComputeButtonParams (TkButton * butPtr, ThemeButtonKind* btnkind, - HIThemeButtonDrawInfo* drawinfo); -static int TkMacOSXComputeButtonDrawParams (TkButton * butPtr, DrawParams * dpPtr); -static void TkMacOSXDrawButton (MacButton *butPtr, GC gc, Pixmap pixmap); -static void DrawButtonImageAndText(TkButton* butPtr); -static void PulseDefaultButtonProc(ClientData clientData); - +static void ButtonBackgroundDrawCB(const HIRect *btnbounds, + MacButton *ptr, SInt16 depth, Boolean isColorDev); +static void ButtonContentDrawCB(const HIRect *bounds, + ThemeButtonKind kind, + const HIThemeButtonDrawInfo *info, MacButton *ptr, + SInt16 depth, Boolean isColorDev); +static void ButtonEventProc(ClientData clientData, + XEvent *eventPtr); +static void TkMacOSXComputeButtonParams(TkButton *butPtr, + ThemeButtonKind *btnkind, + HIThemeButtonDrawInfo *drawinfo); +static int TkMacOSXComputeButtonDrawParams(TkButton *butPtr, + DrawParams * dpPtr); +static void TkMacOSXDrawButton(MacButton *butPtr, GC gc, + Pixmap pixmap); +static void DrawButtonImageAndText(TkButton *butPtr); +static void PulseDefaultButtonProc(ClientData clientData); /* * The class procedure table for the button widgets. @@ -95,15 +98,15 @@ const Tk_ClassProcs tkpButtonProcs = { }; static int bCount; - + /* *---------------------------------------------------------------------- * * TkpButtonSetDefaults -- * - * This procedure is invoked before option tables are created for - * buttons. It modifies some of the default values to match the current - * values defined for this platform. + * This procedure is invoked before option tables are created for buttons. + * It modifies some of the default values to match the current values + * defined for this platform. * * Results: * Some of the default values in *specPtr are modified. @@ -117,9 +120,8 @@ static int bCount; void TkpButtonSetDefaults() { -/*No-op.*/ + /*No-op.*/ } - /* *---------------------------------------------------------------------- @@ -141,10 +143,10 @@ TkButton * TkpCreateButton( Tk_Window tkwin) { - MacButton *macButtonPtr = (MacButton *) ckalloc(sizeof(MacButton)); + MacButton *macButtonPtr = ckalloc(sizeof(MacButton)); Tk_CreateEventHandler(tkwin, ActivateMask, - ButtonEventProc, (ClientData) macButtonPtr); + ButtonEventProc, macButtonPtr); macButtonPtr->id = bCount++; macButtonPtr->flags = FIRST_DRAW; macButtonPtr->btnkind = kThemePushButton; @@ -152,7 +154,7 @@ TkpCreateButton( bzero(&macButtonPtr->drawinfo, sizeof(macButtonPtr->drawinfo)); bzero(&macButtonPtr->lastdrawinfo, sizeof(macButtonPtr->lastdrawinfo)); - return (TkButton *)macButtonPtr; + return (TkButton *) macButtonPtr; } /* @@ -160,15 +162,15 @@ TkpCreateButton( * * TkpDisplayButton -- * - * This procedure is invoked to display a button widget. It is - * normally invoked as an idle handler. + * This procedure is invoked to display a button widget. It is normally + * invoked as an idle handler. * * Results: * None. * * Side effects: - * Commands are output to X to display the button in its - * current mode. The REDRAW_PENDING flag is cleared. + * Commands are output to X to display the button in its current mode. The + * REDRAW_PENDING flag is cleared. * *---------------------------------------------------------------------- */ @@ -177,32 +179,34 @@ void TkpDisplayButton( ClientData clientData) /* Information about widget. */ { - MacButton *macButtonPtr = (MacButton *) clientData; - TkButton *butPtr = (TkButton *) clientData; + MacButton *macButtonPtr = clientData; + TkButton *butPtr = clientData; Tk_Window tkwin = butPtr->tkwin; Pixmap pixmap; DrawParams* dpPtr = &macButtonPtr->drawParams; int needhighlight = 0; + if (butPtr->flags & BUTTON_DELETED) { + return; + } butPtr->flags &= ~REDRAW_PENDING; if ((butPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } pixmap = (Pixmap) Tk_WindowId(tkwin); - TkMacOSXSetUpClippingRgn(Tk_WindowId(tkwin)); - - if (TkMacOSXComputeButtonDrawParams(butPtr, dpPtr) ) { - macButtonPtr->useTkText = 0; - } else { - macButtonPtr->useTkText = 1; - } - /* * Set up clipping region. Make sure the we are using the port * for this button, or we will set the wrong window's clip. */ + TkMacOSXSetUpClippingRgn(Tk_WindowId(tkwin)); + + if (TkMacOSXComputeButtonDrawParams(butPtr, dpPtr)) { + macButtonPtr->useTkText = 0; + } else { + macButtonPtr->useTkText = 1; + } if (macButtonPtr->useTkText) { if (butPtr->type == TYPE_BUTTON) { Tk_Fill3DRectangle(tkwin, pixmap, butPtr->highlightBorder, 0, 0, @@ -212,26 +216,42 @@ TkpDisplayButton( Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); } - /* Display image or bitmap or text for labels or custom controls. */ + /* + * Display image or bitmap or text for labels or custom controls. + */ + DrawButtonImageAndText(butPtr); - needhighlight = 1; + needhighlight = 1; } else { - /* Draw the native portion of the buttons. */ + /* + * Draw the native portion of the buttons. + */ + TkMacOSXDrawButton(macButtonPtr, dpPtr->gc, pixmap); - /* Draw highlight border, if needed. */ + /* + * Ask for the highlight border, if needed. + */ + if (butPtr->highlightWidth < 3) { needhighlight = 1; } } - /* Draw highlight border, if needed. */ + /* + * Draw highlight border, if needed. + */ + if (needhighlight) { - if ((butPtr->flags & GOT_FOCUS)) { - Tk_Draw3DRectangle(tkwin, pixmap, butPtr->normalBorder, 0, 0, - Tk_Width(tkwin), Tk_Height(tkwin), - butPtr->highlightWidth, TK_RELIEF_SOLID); - } + GC gc = NULL; + if ((butPtr->flags & GOT_FOCUS) && butPtr->highlightColorPtr) { + gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap); + } else if (butPtr->type == TYPE_LABEL) { + gc = Tk_GCForColor(Tk_3DBorderColor(butPtr->highlightBorder), pixmap); + } + if (gc) { + TkMacOSXDrawSolidBorder(tkwin, gc, 0, butPtr->highlightWidth); + } } } @@ -240,9 +260,9 @@ TkpDisplayButton( * * TkpComputeButtonGeometry -- * - * After changes in a button's text or bitmap, this procedure - * recomputes the button's geometry and passes this information - * along to the geometry manager for the window. + * After changes in a button's text or bitmap, this procedure recomputes + * the button's geometry and passes this information along to the geometry + * manager for the window. * * Results: * None. @@ -259,7 +279,7 @@ TkpComputeButtonGeometry( { int width = 0, height = 0, charWidth = 1, haveImage = 0, haveText = 0; int txtWidth = 0, txtHeight = 0; - MacButton *mbPtr = (MacButton*)butPtr; + MacButton *mbPtr = (MacButton *) butPtr; Tk_FontMetrics fm; char *text = Tcl_GetString(butPtr->textPtr); @@ -270,21 +290,27 @@ TkpComputeButtonGeometry( */ if (butPtr->indicatorOn) { - switch (butPtr->type) { - case TYPE_RADIO_BUTTON: - GetThemeMetric(kThemeMetricRadioButtonWidth, (SInt32 *)&butPtr->indicatorDiameter); - break; - case TYPE_CHECK_BUTTON: - GetThemeMetric(kThemeMetricCheckBoxWidth, (SInt32 *)&butPtr->indicatorDiameter); - break; - default: - break; - } - /* Allow 2px extra space next to the indicator. */ - butPtr->indicatorSpace = butPtr->indicatorDiameter + 2; + switch (butPtr->type) { + case TYPE_RADIO_BUTTON: + GetThemeMetric(kThemeMetricRadioButtonWidth, + (SInt32 *) &butPtr->indicatorDiameter); + break; + case TYPE_CHECK_BUTTON: + GetThemeMetric(kThemeMetricCheckBoxWidth, + (SInt32 *) &butPtr->indicatorDiameter); + break; + default: + break; + } + + /* + * Allow 2px extra space next to the indicator. + */ + + butPtr->indicatorSpace = butPtr->indicatorDiameter + 2; } else { - butPtr->indicatorSpace = 0; - butPtr->indicatorDiameter = 0; + butPtr->indicatorSpace = 0; + butPtr->indicatorDiameter = 0; } if (butPtr->image != NULL) { @@ -308,44 +334,43 @@ TkpComputeButtonGeometry( if (haveImage && haveText) { /* Image and Text */ switch ((enum compound) butPtr->compound) { - case COMPOUND_TOP: - case COMPOUND_BOTTOM: - - /* - * Image is above or below text. - */ - - height += txtHeight + butPtr->padY; - width = (width > txtWidth ? width : txtWidth); - break; - case COMPOUND_LEFT: - case COMPOUND_RIGHT: - - /* - * Image is left or right of text. - */ - - width += txtWidth + 2*butPtr->padX; - height = (height > txtHeight ? height : txtHeight); - break; - case COMPOUND_CENTER: - - /* - * Image and text are superimposed. - */ - - width = (width > txtWidth ? width : txtWidth); - height = (height > txtHeight ? height : txtHeight); - break; - default: - break; + case COMPOUND_TOP: + case COMPOUND_BOTTOM: + /* + * Image is above or below text. + */ + + height += txtHeight + butPtr->padY; + width = (width > txtWidth ? width : txtWidth); + break; + case COMPOUND_LEFT: + case COMPOUND_RIGHT: + /* + * Image is left or right of text. + */ + + width += txtWidth + 2*butPtr->padX; + height = (height > txtHeight ? height : txtHeight); + break; + case COMPOUND_CENTER: + /* + * Image and text are superimposed. + */ + + width = (width > txtWidth ? width : txtWidth); + height = (height > txtHeight ? height : txtHeight); + break; + default: + break; } width += butPtr->indicatorSpace; } else if (haveImage) { /* Image only */ width = butPtr->width > 0 ? butPtr->width : width + butPtr->indicatorSpace; height = butPtr->height > 0 ? butPtr->height : height; if (butPtr->type == TYPE_BUTTON) { - /* Allow room to shift the image. */ + /* + * Allow room to shift the image. + */ width += 2; height += 2; } @@ -375,20 +400,20 @@ TkpComputeButtonGeometry( width += butPtr->inset*2; height += butPtr->inset*2; if ([NSApp macMinorVersion] == 6) { - width += 12; + width += 12; } if (mbPtr->btnkind == kThemePushButton) { HIRect tmpRect; HIRect contBounds; /* - * A PushButton has a minimum size. We make sure that we - * are not underestimating the size by requesting the content - * size of a Pushbutton whose overall size is our content size - * expanded by the standard padding. + * A PushButton has a minimum size. We make sure that we are not + * underestimating the size by requesting the content size of a + * Pushbutton whose overall size is our content size expanded by the + * standard padding. */ - - tmpRect = CGRectMake(0, 0, width + 2*HI_PADX, height + 2*HI_PADY); + + tmpRect = CGRectMake(0, 0, width + 2*HI_PADX, height + 2*HI_PADY); HIThemeGetButtonContentBounds(&tmpRect, &mbPtr->drawinfo, &contBounds); if (height < contBounds.size.height) { height = contBounds.size.height; @@ -402,51 +427,43 @@ TkpComputeButtonGeometry( Tk_GeometryRequest(butPtr->tkwin, width, height); Tk_SetInternalBorder(butPtr->tkwin, butPtr->inset); } - + /* *---------------------------------------------------------------------- * * DrawButtonImageAndText -- * - * Draws the image and text associated with a button or label. + * Draws the image and text associated with a button or label. * * Results: - * None. + * None. * * Side effects: - * The image and text are drawn. + * The image and text are drawn. * *---------------------------------------------------------------------- */ static void DrawButtonImageAndText( - TkButton* butPtr) + TkButton *butPtr) { - MacButton *mbPtr = (MacButton*)butPtr; - Tk_Window tkwin = butPtr->tkwin; - Pixmap pixmap; - int haveImage = 0; - int haveText = 0; - int imageWidth = 0; - int imageHeight = 0; - int imageXOffset = 0; - int imageYOffset = 0; - int textXOffset = 0; - int textYOffset = 0; - int width = 0; - int height = 0; - int fullWidth = 0; - int fullHeight = 0; - int pressed = 0; - + MacButton *mbPtr = (MacButton *) butPtr; + Tk_Window tkwin = butPtr->tkwin; + Pixmap pixmap; + int haveImage = 0, haveText = 0, pressed = 0; + int imageWidth = 0, imageHeight = 0; + int imageXOffset = 0, imageYOffset = 0; + int textXOffset = 0, textYOffset = 0; + int width = 0, height = 0; + int fullWidth = 0, fullHeight = 0; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } - DrawParams* dpPtr = &mbPtr->drawParams; - pixmap = (Pixmap)Tk_WindowId(tkwin); + DrawParams *dpPtr = &mbPtr->drawParams; + pixmap = (Pixmap) Tk_WindowId(tkwin); if (butPtr->image != None) { Tk_SizeOfImage(butPtr->image, &width, &height); @@ -456,74 +473,68 @@ DrawButtonImageAndText( haveImage = 1; } - imageWidth = width; + imageWidth = width; imageHeight = height; if (mbPtr->drawinfo.state == kThemeStatePressed) { - /* Offset bitmaps by a bit when the button is pressed. */ + /* + * Offset bitmaps by a bit when the button is pressed. + */ + pressed = 1; } haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); if (haveImage && haveText) { /* Image and Text */ - int x; - int y; - textXOffset = 0; - textYOffset = 0; - fullWidth = 0; - fullHeight = 0; + int x, y; switch ((enum compound) butPtr->compound) { case COMPOUND_TOP: - case COMPOUND_BOTTOM: { - /* Image is above or below text */ - if (butPtr->compound == COMPOUND_TOP) { - textYOffset = height + butPtr->padY; - } else { - imageYOffset = butPtr->textHeight + butPtr->padY; - } - fullHeight = height + butPtr->textHeight + butPtr->padY; - fullWidth = (width > butPtr->textWidth ? width : - butPtr->textWidth); - textXOffset = (fullWidth - butPtr->textWidth)/2; - imageXOffset = (fullWidth - width)/2; - break; - } + case COMPOUND_BOTTOM: + /* Image is above or below text */ + if (butPtr->compound == COMPOUND_TOP) { + textYOffset = height + butPtr->padY; + } else { + imageYOffset = butPtr->textHeight + butPtr->padY; + } + fullHeight = height + butPtr->textHeight + butPtr->padY; + fullWidth = (width > butPtr->textWidth ? width : butPtr->textWidth); + textXOffset = (fullWidth - butPtr->textWidth)/2; + imageXOffset = (fullWidth - width)/2; + break; case COMPOUND_LEFT: - case COMPOUND_RIGHT: { - /* - * Image is left or right of text - */ - - if (butPtr->compound == COMPOUND_LEFT) { - textXOffset = width + butPtr->padX; - } else { - imageXOffset = butPtr->textWidth + butPtr->padX; - } - fullWidth = butPtr->textWidth + butPtr->padX + width; - fullHeight = (height > butPtr->textHeight ? height : - butPtr->textHeight); - textYOffset = (fullHeight - butPtr->textHeight)/2; - imageYOffset = (fullHeight - height)/2; - break; - } - case COMPOUND_CENTER: { - /* - * Image and text are superimposed - */ - - fullWidth = (width > butPtr->textWidth ? width : - butPtr->textWidth); - fullHeight = (height > butPtr->textHeight ? height : - butPtr->textHeight); - textXOffset = (fullWidth - butPtr->textWidth)/2; - imageXOffset = (fullWidth - width)/2; - textYOffset = (fullHeight - butPtr->textHeight)/2; - imageYOffset = (fullHeight - height)/2; - break; - } + case COMPOUND_RIGHT: + /* + * Image is left or right of text + */ + + if (butPtr->compound == COMPOUND_LEFT) { + textXOffset = width + butPtr->padX; + } else { + imageXOffset = butPtr->textWidth + butPtr->padX; + } + fullWidth = butPtr->textWidth + butPtr->padX + width; + fullHeight = (height > butPtr->textHeight ? height : + butPtr->textHeight); + textYOffset = (fullHeight - butPtr->textHeight)/2; + imageYOffset = (fullHeight - height)/2; + break; + case COMPOUND_CENTER: + /* + * Image and text are superimposed + */ + + fullWidth = (width > butPtr->textWidth ? width : + butPtr->textWidth); + fullHeight = (height > butPtr->textHeight ? height : + butPtr->textHeight); + textXOffset = (fullWidth - butPtr->textWidth)/2; + imageXOffset = (fullWidth - width)/2; + textYOffset = (fullHeight - butPtr->textHeight)/2; + imageYOffset = (fullHeight - height)/2; + break; default: - break; + break; } TkComputeAnchor(butPtr->anchor, tkwin, @@ -547,25 +558,25 @@ DrawButtonImageAndText( imageYOffset += y; if (butPtr->image != NULL) { - if ((butPtr->selectImage != NULL) && - (butPtr->flags & SELECTED)) { - Tk_RedrawImage(butPtr->selectImage, 0, 0, - width, height, pixmap, imageXOffset, imageYOffset); - } else if ((butPtr->tristateImage != NULL) && - (butPtr->flags & TRISTATED)) { - Tk_RedrawImage(butPtr->tristateImage, 0, 0, - width, height, pixmap, imageXOffset, imageYOffset); - } else { - Tk_RedrawImage(butPtr->image, 0, 0, width, - height, pixmap, imageXOffset, imageYOffset); - } + if ((butPtr->selectImage != NULL) && + (butPtr->flags & SELECTED)) { + Tk_RedrawImage(butPtr->selectImage, 0, 0, + width, height, pixmap, imageXOffset, imageYOffset); + } else if ((butPtr->tristateImage != NULL) && + (butPtr->flags & TRISTATED)) { + Tk_RedrawImage(butPtr->tristateImage, 0, 0, + width, height, pixmap, imageXOffset, imageYOffset); + } else { + Tk_RedrawImage(butPtr->image, 0, 0, width, + height, pixmap, imageXOffset, imageYOffset); + } } else { - XSetClipOrigin(butPtr->display, dpPtr->gc, - imageXOffset, imageYOffset); - XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, dpPtr->gc, - 0, 0, (unsigned int) width, (unsigned int) height, - imageXOffset, imageYOffset, 1); - XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); + XSetClipOrigin(butPtr->display, dpPtr->gc, + imageXOffset, imageYOffset); + XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, dpPtr->gc, + 0, 0, (unsigned int) width, (unsigned int) height, + imageXOffset, imageYOffset, 1); + XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); } y += 1; /* Tweak to match native buttons. */ @@ -577,60 +588,57 @@ DrawButtonImageAndText( x + textXOffset, y + textYOffset, butPtr->underline); } else if (haveImage) { /* Image only */ - int x = 0; - int y; + int x = 0, y; + TkComputeAnchor(butPtr->anchor, tkwin, - butPtr->padX + butPtr->borderWidth, - butPtr->padY + butPtr->borderWidth, - width + butPtr->indicatorSpace, - height, &x, &y); + butPtr->padX + butPtr->borderWidth, + butPtr->padY + butPtr->borderWidth, + width + butPtr->indicatorSpace, height, &x, &y); x += butPtr->indicatorSpace; if (pressed) { - x += dpPtr->offset; - y += dpPtr->offset; + x += dpPtr->offset; + y += dpPtr->offset; } imageXOffset += x; imageYOffset += y; if (butPtr->image != NULL) { - - if ((butPtr->selectImage != NULL) && - (butPtr->flags & SELECTED)) { - Tk_RedrawImage(butPtr->selectImage, 0, 0, width, - height, pixmap, imageXOffset, imageYOffset); - } else if ((butPtr->tristateImage != NULL) && - (butPtr->flags & TRISTATED)) { - Tk_RedrawImage(butPtr->tristateImage, 0, 0, width, - height, pixmap, imageXOffset, imageYOffset); - } else { - Tk_RedrawImage(butPtr->image, 0, 0, width, height, - pixmap, imageXOffset, imageYOffset); - } + if ((butPtr->selectImage != NULL) && + (butPtr->flags & SELECTED)) { + Tk_RedrawImage(butPtr->selectImage, 0, 0, width, + height, pixmap, imageXOffset, imageYOffset); + } else if ((butPtr->tristateImage != NULL) && + (butPtr->flags & TRISTATED)) { + Tk_RedrawImage(butPtr->tristateImage, 0, 0, width, + height, pixmap, imageXOffset, imageYOffset); + } else { + Tk_RedrawImage(butPtr->image, 0, 0, width, height, + pixmap, imageXOffset, imageYOffset); + } } else { - XSetClipOrigin(butPtr->display, dpPtr->gc, x, y); - XCopyPlane(butPtr->display, butPtr->bitmap, - pixmap, dpPtr->gc, - 0, 0, (unsigned int) width, - (unsigned int) height, - imageXOffset, imageYOffset, 1); - XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); + XSetClipOrigin(butPtr->display, dpPtr->gc, x, y); + XCopyPlane(butPtr->display, butPtr->bitmap, pixmap, dpPtr->gc, + 0, 0, (unsigned int) width, (unsigned int) height, + imageXOffset, imageYOffset, 1); + XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); } } else { /* Text only */ int x, y; + TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, - butPtr->textWidth + butPtr->indicatorSpace, - butPtr->textHeight, &x, &y); + butPtr->textWidth + butPtr->indicatorSpace, + butPtr->textHeight, &x, &y); x += butPtr->indicatorSpace; y += 1; /* Tweak to match native buttons */ - Tk_DrawTextLayout(butPtr->display, pixmap, dpPtr->gc, butPtr->textLayout, - x, y, 0, -1); + Tk_DrawTextLayout(butPtr->display, pixmap, dpPtr->gc, + butPtr->textLayout, x, y, 0, -1); } /* * If the button is disabled with a stipple rather than a special - * foreground color, generate the stippled effect. If the widget - * is selected and we use a different background color when selected, - * must temporarily modify the GC so the stippling is the right color. + * foreground color, generate the stippled effect. If the widget is + * selected and we use a different background color when selected, must + * temporarily modify the GC so the stippling is the right color. */ if (mbPtr->useTkText) { @@ -668,18 +676,15 @@ DrawButtonImageAndText( */ if (dpPtr->relief != TK_RELIEF_FLAT) { - int inset = butPtr->highlightWidth; - Tk_Draw3DRectangle(tkwin, pixmap, dpPtr->border, inset, inset, - Tk_Width(tkwin) - 2*inset, Tk_Height(tkwin) - 2*inset, - butPtr->borderWidth, dpPtr->relief); + int inset = butPtr->highlightWidth; + + Tk_Draw3DRectangle(tkwin, pixmap, dpPtr->border, inset, inset, + Tk_Width(tkwin) - 2*inset, Tk_Height(tkwin) - 2*inset, + butPtr->borderWidth, dpPtr->relief); } } - - } - - - - +} + /* *---------------------------------------------------------------------- * @@ -701,6 +706,7 @@ TkpDestroyButton( TkButton *butPtr) { MacButton *mbPtr = (MacButton *) butPtr; /* Mac button. */ + if (mbPtr->defaultPulseHandler) { Tcl_DeleteTimerHandler(mbPtr->defaultPulseHandler); } @@ -711,9 +717,8 @@ TkpDestroyButton( * * TkMacOSXDrawButton -- * - * This function draws the tk button using Mac controls - * In addition, this code may apply custom colors passed - * in the TkButton. + * This function draws the tk button using Mac controls. In addition, + * this code may apply custom colors passed in the TkButton. * * Results: * None. @@ -732,23 +737,19 @@ TkMacOSXDrawButton( Pixmap pixmap) /* The pixmap we are drawing into - needed * for the bevel button */ { - TkButton * butPtr = ( TkButton *)mbPtr; - TkWindow * winPtr; - HIRect cntrRect; + TkButton *butPtr = (TkButton *) mbPtr; + TkWindow *winPtr = (TkWindow *) butPtr->tkwin; + HIRect cntrRect; TkMacOSXDrawingContext dc; - DrawParams* dpPtr = &mbPtr->drawParams; + DrawParams *dpPtr = &mbPtr->drawParams; int useNewerHITools = 1; - winPtr = (TkWindow *)butPtr->tkwin; - TkMacOSXComputeButtonParams(butPtr, &mbPtr->btnkind, &mbPtr->drawinfo); - cntrRect = CGRectMake(winPtr->privatePtr->xOff, - winPtr->privatePtr->yOff, - Tk_Width(butPtr->tkwin), - Tk_Height(butPtr->tkwin)); + cntrRect = CGRectMake(winPtr->privatePtr->xOff, winPtr->privatePtr->yOff, + Tk_Width(butPtr->tkwin), Tk_Height(butPtr->tkwin)); - cntrRect = CGRectInset(cntrRect, butPtr->inset, butPtr->inset); + cntrRect = CGRectInset(cntrRect, butPtr->inset, butPtr->inset); if (useNewerHITools == 1) { HIRect contHIRec; @@ -762,7 +763,7 @@ TkMacOSXDrawButton( hiinfo.version = 0; hiinfo.state = mbPtr->drawinfo.state; - hiinfo.kind = mbPtr->btnkind; + hiinfo.kind = mbPtr->btnkind; hiinfo.value = mbPtr->drawinfo.value; hiinfo.adornment = mbPtr->drawinfo.adornment; hiinfo.animation.time.current = CFAbsoluteTimeGetCurrent(); @@ -770,13 +771,21 @@ TkMacOSXDrawButton( hiinfo.animation.time.start = hiinfo.animation.time.current; } - HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, kHIThemeOrientationNormal, - &contHIRec); + /* + * To avoid buttons with white text on a white background, we always + * set the state to inactive in Dark Mode. It isn't perfect but it is + * usable. Using a ttk::button would be a better choice, however. + */ + + if (TkMacOSXInDarkMode(butPtr->tkwin)) { + hiinfo.state = kThemeStateInactive; + } + HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, + kHIThemeOrientationNormal, &contHIRec); TkMacOSXRestoreDrawingContext(&dc); ButtonContentDrawCB(&contHIRec, mbPtr->btnkind, &mbPtr->drawinfo, - (MacButton *)mbPtr, 32, true); - + (MacButton *) mbPtr, 32, true); } else { if (!TkMacOSXSetupDrawingContext(pixmap, dpPtr->gc, 1, &dc)) { return; @@ -792,8 +801,8 @@ TkMacOSXDrawButton( * * ButtonBackgroundDrawCB -- * - * This function draws the background that - * lies under checkboxes and radiobuttons. + * This function draws the background that lies under checkboxes and + * radiobuttons. * * Results: * None. @@ -803,32 +812,33 @@ TkMacOSXDrawButton( * *-------------------------------------------------------------- */ + static void -ButtonBackgroundDrawCB ( - const HIRect * btnbounds, +ButtonBackgroundDrawCB( + const HIRect *btnbounds, MacButton *ptr, SInt16 depth, Boolean isColorDev) { - MacButton* mbPtr = (MacButton*)ptr; - TkButton* butPtr = (TkButton*)mbPtr; - Tk_Window tkwin = butPtr->tkwin; + MacButton *mbPtr = (MacButton *) ptr; + TkButton *butPtr = (TkButton *) mbPtr; + Tk_Window tkwin = butPtr->tkwin; Pixmap pixmap; int usehlborder = 0; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } - pixmap = (Pixmap)Tk_WindowId(tkwin); + pixmap = (Pixmap) Tk_WindowId(tkwin); if (butPtr->type != TYPE_LABEL) { switch (mbPtr->btnkind) { - case kThemeSmallBevelButton: - case kThemeBevelButton: - case kThemeRoundedBevelButton: - case kThemePushButton: - usehlborder = 1; - break; + case kThemeSmallBevelButton: + case kThemeBevelButton: + case kThemeRoundedBevelButton: + case kThemePushButton: + usehlborder = 1; + break; } } if (usehlborder) { @@ -864,17 +874,18 @@ ButtonContentDrawCB ( SInt16 depth, Boolean isColorDev) { - TkButton *butPtr = (TkButton *)ptr; - Tk_Window tkwin = butPtr->tkwin; + TkButton *butPtr = (TkButton *) ptr; + Tk_Window tkwin = butPtr->tkwin; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } /* - * Overlay Tk elements over button native region: drawing elements - * within button boundaries/native region causes unpredictable metrics. + * Overlay Tk elements over button native region: drawing elements within + * button boundaries/native region causes unpredictable metrics. */ + DrawButtonImageAndText( butPtr); } @@ -883,8 +894,8 @@ ButtonContentDrawCB ( * * ButtonEventProc -- * - * This procedure is invoked by the Tk dispatcher for various - * events on buttons. + * This procedure is invoked by the Tk dispatcher for various events on + * buttons. * * Results: * None. @@ -900,8 +911,8 @@ ButtonEventProc( ClientData clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { - TkButton *buttonPtr = (TkButton *) clientData; - MacButton *mbPtr = (MacButton *) clientData; + TkButton *buttonPtr = clientData; + MacButton *mbPtr = clientData; if (eventPtr->type == ActivateNotify || eventPtr->type == DeactivateNotify) { @@ -914,7 +925,7 @@ ButtonEventProc( mbPtr->flags &= ~ACTIVE; } if ((buttonPtr->flags & REDRAW_PENDING) == 0) { - Tcl_DoWhenIdle(TkpDisplayButton, (ClientData) buttonPtr); + Tcl_DoWhenIdle(TkpDisplayButton, buttonPtr); buttonPtr->flags |= REDRAW_PENDING; } } @@ -925,9 +936,9 @@ ButtonEventProc( * * TkMacOSXComputeButtonParams -- * - * This procedure computes the various parameters used - * when creating a Carbon Appearance control. - * These are determined by the various tk button parameters + * This procedure computes the various parameters used when creating a + * Carbon Appearance control. These are determined by the various tk + * button parameters * * Results: * None. @@ -940,11 +951,11 @@ ButtonEventProc( static void TkMacOSXComputeButtonParams( - TkButton * butPtr, - ThemeButtonKind* btnkind, - HIThemeButtonDrawInfo *drawinfo) + TkButton *butPtr, + ThemeButtonKind *btnkind, + HIThemeButtonDrawInfo *drawinfo) { - MacButton *mbPtr = (MacButton *)butPtr; + MacButton *mbPtr = (MacButton *) butPtr; if (butPtr->borderWidth <= 2) { *btnkind = kThemeSmallBevelButton; @@ -958,47 +969,46 @@ TkMacOSXComputeButtonParams( if ((butPtr->image == None) && (butPtr->bitmap == None)) { switch (butPtr->type) { - case TYPE_BUTTON: - *btnkind = kThemePushButton; - break; - case TYPE_RADIO_BUTTON: - if (butPtr->borderWidth <= 1) { - *btnkind = kThemeSmallRadioButton; - } else { - *btnkind = kThemeRadioButton; - } - break; - case TYPE_CHECK_BUTTON: - if (butPtr->borderWidth <= 1) { - *btnkind = kThemeSmallCheckBox; - } else { - *btnkind = kThemeCheckBox; - } - break; + case TYPE_BUTTON: + *btnkind = kThemePushButton; + break; + case TYPE_RADIO_BUTTON: + if (butPtr->borderWidth <= 1) { + *btnkind = kThemeSmallRadioButton; + } else { + *btnkind = kThemeRadioButton; + } + break; + case TYPE_CHECK_BUTTON: + if (butPtr->borderWidth <= 1) { + *btnkind = kThemeSmallCheckBox; + } else { + *btnkind = kThemeCheckBox; + } + break; } } if (butPtr->indicatorOn) { switch (butPtr->type) { - case TYPE_RADIO_BUTTON: - if (butPtr->borderWidth <= 1) { - *btnkind = kThemeSmallRadioButton; - } else { - *btnkind = kThemeRadioButton; - } - break; - case TYPE_CHECK_BUTTON: - if (butPtr->borderWidth <= 1) { - *btnkind = kThemeSmallCheckBox; - } else { - *btnkind = kThemeCheckBox; - } - break; + case TYPE_RADIO_BUTTON: + if (butPtr->borderWidth <= 1) { + *btnkind = kThemeSmallRadioButton; + } else { + *btnkind = kThemeRadioButton; + } + break; + case TYPE_CHECK_BUTTON: + if (butPtr->borderWidth <= 1) { + *btnkind = kThemeSmallCheckBox; + } else { + *btnkind = kThemeCheckBox; + } + break; } } else { if (butPtr->type == TYPE_RADIO_BUTTON || - butPtr->type == TYPE_CHECK_BUTTON - ) { + butPtr->type == TYPE_CHECK_BUTTON) { if (*btnkind == kThemePushButton) { *btnkind = kThemeBevelButton; } @@ -1040,8 +1050,7 @@ TkMacOSXComputeButtonParams( drawinfo->adornment |= kThemeAdornmentDefault; if (!mbPtr->defaultPulseHandler) { mbPtr->defaultPulseHandler = Tcl_CreateTimerHandler( - PULSE_TIMER_MSECS, PulseDefaultButtonProc, - (ClientData) butPtr); + PULSE_TIMER_MSECS, PulseDefaultButtonProc, butPtr); } } else if (mbPtr->defaultPulseHandler) { Tcl_DeleteTimerHandler(mbPtr->defaultPulseHandler); @@ -1058,9 +1067,8 @@ TkMacOSXComputeButtonParams( * * TkMacOSXComputeButtonDrawParams -- * - * This procedure computes the various parameters used - * when drawing a button - * These are determined by the various tk button parameters + * This procedure computes the various parameters used when drawing a + * button. These are determined by the various tk button parameters * * Results: * 1 if control will be used, 0 otherwise. @@ -1076,7 +1084,7 @@ TkMacOSXComputeButtonDrawParams( TkButton *butPtr, DrawParams *dpPtr) { - MacButton *mbPtr = (MacButton *)butPtr; + MacButton *mbPtr = (MacButton *) butPtr; dpPtr->hasImageOrBitmap = ((butPtr->image != NULL) || (butPtr->bitmap != None)); @@ -1085,12 +1093,12 @@ TkMacOSXComputeButtonDrawParams( dpPtr->offset = 0; if (dpPtr->hasImageOrBitmap) { switch (mbPtr->btnkind) { - case kThemeSmallBevelButton: - case kThemeBevelButton: - case kThemeRoundedBevelButton: - case kThemePushButton: - dpPtr->offset = 1; - break; + case kThemeSmallBevelButton: + case kThemeBevelButton: + case kThemeRoundedBevelButton: + case kThemePushButton: + dpPtr->offset = 1; + break; } } } @@ -1111,8 +1119,8 @@ TkMacOSXComputeButtonDrawParams( } /* - * Override the relief specified for the button if this is a - * checkbutton or radiobutton and there's no indicator. + * Override the relief specified for the button if this is a checkbutton or + * radiobutton and there's no indicator. */ dpPtr->relief = butPtr->relief; @@ -1124,22 +1132,18 @@ TkMacOSXComputeButtonDrawParams( } } - if (butPtr->type != TYPE_LABEL && - (butPtr->type == TYPE_BUTTON || - butPtr->indicatorOn || - dpPtr->hasImageOrBitmap)) { - + if (butPtr->type != TYPE_LABEL && (butPtr->type == TYPE_BUTTON || + butPtr->indicatorOn || dpPtr->hasImageOrBitmap)) { /* * Draw this widget as a native control. */ - + return 1; } else { - /* * Draw this widget from scratch. */ - + return 0; } } @@ -1160,15 +1164,17 @@ TkMacOSXComputeButtonDrawParams( * *-------------------------------------------------------------- */ + static void PulseDefaultButtonProc(ClientData clientData) { - MacButton *mbPtr = (MacButton *)clientData; + MacButton *mbPtr = clientData; + TkpDisplayButton(clientData); mbPtr->defaultPulseHandler = Tcl_CreateTimerHandler( PULSE_TIMER_MSECS, PulseDefaultButtonProc, clientData); } - + /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXClipboard.c b/macosx/tkMacOSXClipboard.c index efd3c69..6cbcdf6 100644 --- a/macosx/tkMacOSXClipboard.c +++ b/macosx/tkMacOSXClipboard.c @@ -123,8 +123,9 @@ TkSelGetSelection( { int result = TCL_ERROR; TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr; + int haveExternalClip = + ([[NSPasteboard generalPasteboard] changeCount] != changeCount); - int haveExternalClip = ([[NSPasteboard generalPasteboard] changeCount] != changeCount); if (dispPtr && (haveExternalClip || dispPtr->clipboardActive) && selection == dispPtr->clipboardAtom && (target == XA_STRING || target == dispPtr->utf8Atom)) { @@ -177,6 +178,7 @@ XSetSelectionOwner( clipboardOwner = owner ? Tk_IdToWindow(display, owner) : NULL; if (!dispPtr->clipboardActive) { NSPasteboard *pb = [NSPasteboard generalPasteboard]; + changeCount = [pb declareTypes:[NSArray array] owner:NSApp]; } } @@ -188,8 +190,8 @@ XSetSelectionOwner( * * TkMacOSXSelDeadWindow -- * - * This function is invoked just before a TkWindow is deleted. It - * performs selection-related cleanup. + * This function is invoked just before a TkWindow is deleted. It performs + * selection-related cleanup. * * Results: * None. diff --git a/macosx/tkMacOSXColor.c b/macosx/tkMacOSXColor.c index 459ca47..f291289 100644 --- a/macosx/tkMacOSXColor.c +++ b/macosx/tkMacOSXColor.c @@ -17,11 +17,32 @@ #include "tkMacOSXPrivate.h" #include "tkColor.h" +/* + * The colorType specifies how the color value should be interpreted. For the + * unique rgbColor entry, the RGB values are generated from the pixel value of + * an XColor. The ttkBackground and semantic types are dynamic, meaning + * that they change when dark mode is enabled on OSX 10.13 and later. + */ + +enum colorType { + clearColor, /* There should be only one of these. */ + rgbColor, /* There should be only one of these. */ + appearance, /* There should be only one of these. */ + HIBrush, /* The value is a HITheme brush color table index. */ + HIText, /* The value is a HITheme text color table index. */ + HIBackground, /* The value is a HITheme background color table index. */ + ttkBackground, /* The value can be used as a parameter.*/ + semantic, /* The value can be used as a parameter.*/ +}; + +/* + + */ + struct SystemColorMapEntry { const char *name; - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; + enum colorType type; + long value; }; /* unsigned char pixelCode; */ /* @@ -32,157 +53,178 @@ struct SystemColorMapEntry { #define MIN_PIXELCODE 30 static const struct SystemColorMapEntry systemColorMap[] = { - { "Transparent", 0, 0, 0 }, /* 30: TRANSPARENT_PIXEL */ - { "Highlight", kThemeBrushPrimaryHighlightColor, 0, 0 }, /* 31: HIGHLIGHT_PIXEL */ - { "HighlightSecondary", kThemeBrushSecondaryHighlightColor, 0, 0 }, /* 32: HIGHLIGHT_SECONDARY_PIXEL */ - { "HighlightText", kThemeBrushBlack, 0, 0 }, /* 33: HIGHLIGHT_TEXT_PIXEL */ - { "HighlightAlternate", kThemeBrushAlternatePrimaryHighlightColor, 0, 0 }, /* 34: HIGHLIGHT_ALTERNATE_PIXEL */ - { "ButtonText", 0, kThemeTextColorPushButtonActive, 0 }, /* 35: CONTROL_TEXT_PIXEL */ - { "PrimaryHighlightColor", kThemeBrushPrimaryHighlightColor, 0, 0 }, /* 36 */ - { "ButtonFace", kThemeBrushButtonFaceActive, 0, 0 }, /* 37: CONTROL_BODY_PIXEL */ - { "SecondaryHighlightColor", kThemeBrushSecondaryHighlightColor, 0, 0 }, /* 38 */ - { "ButtonFrame", kThemeBrushButtonFrameActive, 0, 0 }, /* 39: CONTROL_FRAME_PIXEL */ - { "AlternatePrimaryHighlightColor", kThemeBrushAlternatePrimaryHighlightColor, 0, 0 }, /* 40 */ - { "WindowBody", kThemeBrushDocumentWindowBackground, 0, 0 }, /* 41: WINDOW_BODY_PIXEL */ - { "SheetBackground", kThemeBrushSheetBackground, 0, 0 }, /* 42 */ - { "MenuActive", kThemeBrushMenuBackgroundSelected, 0, 0 }, /* 43: MENU_ACTIVE_PIXEL */ - { "Black", kThemeBrushBlack, 0, 0 }, /* 44 */ - { "MenuActiveText", 0, kThemeTextColorMenuItemSelected, 0 }, /* 45: MENU_ACTIVE_TEXT_PIXEL */ - { "White", kThemeBrushWhite, 0, 0 }, /* 46 */ - { "Menu", kThemeBrushMenuBackground, 0, 0 }, /* 47: MENU_BACKGROUND_PIXEL */ - { "DialogBackgroundActive", kThemeBrushDialogBackgroundActive, 0, 0 }, /* 48 */ - { "MenuDisabled", 0, kThemeTextColorMenuItemDisabled, 0 }, /* 49: MENU_DISABLED_PIXEL */ - { "DialogBackgroundInactive", kThemeBrushDialogBackgroundInactive, 0, 0 }, /* 50 */ - { "MenuText", 0, kThemeTextColorMenuItemActive, 0 }, /* 51: MENU_TEXT_PIXEL */ - { "AppearanceColor", 0, 0, 0 }, /* 52: APPEARANCE_PIXEL */ - { "AlertBackgroundActive", kThemeBrushAlertBackgroundActive, 0, 0 }, /* 53 */ - { "AlertBackgroundInactive", kThemeBrushAlertBackgroundInactive, 0, 0 }, /* 54 */ - { "ModelessDialogBackgroundActive", kThemeBrushModelessDialogBackgroundActive, 0, 0 }, /* 55 */ - { "ModelessDialogBackgroundInactive", kThemeBrushModelessDialogBackgroundInactive, 0, 0 }, /* 56 */ - { "UtilityWindowBackgroundActive", kThemeBrushUtilityWindowBackgroundActive, 0, 0 }, /* 57 */ - { "UtilityWindowBackgroundInactive", kThemeBrushUtilityWindowBackgroundInactive, 0, 0 }, /* 58 */ - { "ListViewSortColumnBackground", kThemeBrushListViewSortColumnBackground, 0, 0 }, /* 59 */ - { "ListViewBackground", kThemeBrushListViewBackground, 0, 0 }, /* 60 */ - { "IconLabelBackground", kThemeBrushIconLabelBackground, 0, 0 }, /* 61 */ - { "ListViewSeparator", kThemeBrushListViewSeparator, 0, 0 }, /* 62 */ - { "ChasingArrows", kThemeBrushChasingArrows, 0, 0 }, /* 63 */ - { "DragHilite", kThemeBrushDragHilite, 0, 0 }, /* 64 */ - { "DocumentWindowBackground", kThemeBrushDocumentWindowBackground, 0, 0 }, /* 65 */ - { "FinderWindowBackground", kThemeBrushFinderWindowBackground, 0, 0 }, /* 66 */ - { "ScrollBarDelimiterActive", kThemeBrushScrollBarDelimiterActive, 0, 0 }, /* 67 */ - { "ScrollBarDelimiterInactive", kThemeBrushScrollBarDelimiterInactive, 0, 0 }, /* 68 */ - { "FocusHighlight", kThemeBrushFocusHighlight, 0, 0 }, /* 69 */ - { "PopupArrowActive", kThemeBrushPopupArrowActive, 0, 0 }, /* 70 */ - { "PopupArrowPressed", kThemeBrushPopupArrowPressed, 0, 0 }, /* 71 */ - { "PopupArrowInactive", kThemeBrushPopupArrowInactive, 0, 0 }, /* 72 */ - { "AppleGuideCoachmark", kThemeBrushAppleGuideCoachmark, 0, 0 }, /* 73 */ - { "IconLabelBackgroundSelected", kThemeBrushIconLabelBackgroundSelected, 0, 0 }, /* 74 */ - { "StaticAreaFill", kThemeBrushStaticAreaFill, 0, 0 }, /* 75 */ - { "ActiveAreaFill", kThemeBrushActiveAreaFill, 0, 0 }, /* 76 */ - { "ButtonFrameActive", kThemeBrushButtonFrameActive, 0, 0 }, /* 77 */ - { "ButtonFrameInactive", kThemeBrushButtonFrameInactive, 0, 0 }, /* 78 */ - { "ButtonFaceActive", kThemeBrushButtonFaceActive, 0, 0 }, /* 79 */ - { "ButtonFaceInactive", kThemeBrushButtonFaceInactive, 0, 0 }, /* 80 */ - { "ButtonFacePressed", kThemeBrushButtonFacePressed, 0, 0 }, /* 81 */ - { "ButtonActiveDarkShadow", kThemeBrushButtonActiveDarkShadow, 0, 0 }, /* 82 */ - { "ButtonActiveDarkHighlight", kThemeBrushButtonActiveDarkHighlight, 0, 0 }, /* 83 */ - { "ButtonActiveLightShadow", kThemeBrushButtonActiveLightShadow, 0, 0 }, /* 84 */ - { "ButtonActiveLightHighlight", kThemeBrushButtonActiveLightHighlight, 0, 0 }, /* 85 */ - { "ButtonInactiveDarkShadow", kThemeBrushButtonInactiveDarkShadow, 0, 0 }, /* 86 */ - { "ButtonInactiveDarkHighlight", kThemeBrushButtonInactiveDarkHighlight, 0, 0 }, /* 87 */ - { "ButtonInactiveLightShadow", kThemeBrushButtonInactiveLightShadow, 0, 0 }, /* 88 */ - { "ButtonInactiveLightHighlight", kThemeBrushButtonInactiveLightHighlight, 0, 0 }, /* 89 */ - { "ButtonPressedDarkShadow", kThemeBrushButtonPressedDarkShadow, 0, 0 }, /* 90 */ - { "ButtonPressedDarkHighlight", kThemeBrushButtonPressedDarkHighlight, 0, 0 }, /* 91 */ - { "ButtonPressedLightShadow", kThemeBrushButtonPressedLightShadow, 0, 0 }, /* 92 */ - { "ButtonPressedLightHighlight", kThemeBrushButtonPressedLightHighlight, 0, 0 }, /* 93 */ - { "BevelActiveLight", kThemeBrushBevelActiveLight, 0, 0 }, /* 94 */ - { "BevelActiveDark", kThemeBrushBevelActiveDark, 0, 0 }, /* 95 */ - { "BevelInactiveLight", kThemeBrushBevelInactiveLight, 0, 0 }, /* 96 */ - { "BevelInactiveDark", kThemeBrushBevelInactiveDark, 0, 0 }, /* 97 */ - { "NotificationWindowBackground", kThemeBrushNotificationWindowBackground, 0, 0 }, /* 98 */ - { "MovableModalBackground", kThemeBrushMovableModalBackground, 0, 0 }, /* 99 */ - { "SheetBackgroundOpaque", kThemeBrushSheetBackgroundOpaque, 0, 0 }, /* 100 */ - { "DrawerBackground", kThemeBrushDrawerBackground, 0, 0 }, /* 101 */ - { "ToolbarBackground", kThemeBrushToolbarBackground, 0, 0 }, /* 102 */ - { "SheetBackgroundTransparent", kThemeBrushSheetBackgroundTransparent, 0, 0 }, /* 103 */ - { "MenuBackground", kThemeBrushMenuBackground, 0, 0 }, /* 104 */ - { "Pixel", 0, 0, 0 }, /* 105: PIXEL_MAGIC */ - { "MenuBackgroundSelected", kThemeBrushMenuBackgroundSelected, 0, 0 }, /* 106 */ - { "ListViewOddRowBackground", kThemeBrushListViewOddRowBackground, 0, 0 }, /* 107 */ - { "ListViewEvenRowBackground", kThemeBrushListViewEvenRowBackground, 0, 0 }, /* 108 */ - { "ListViewColumnDivider", kThemeBrushListViewColumnDivider, 0, 0 }, /* 109 */ - { "BlackText", 0, kThemeTextColorBlack, 0 }, /* 110 */ - { "DialogActiveText", 0, kThemeTextColorDialogActive, 0 }, /* 111 */ - { "DialogInactiveText", 0, kThemeTextColorDialogInactive, 0 }, /* 112 */ - { "AlertActiveText", 0, kThemeTextColorAlertActive, 0 }, /* 113 */ - { "AlertInactiveText", 0, kThemeTextColorAlertInactive, 0 }, /* 114 */ - { "ModelessDialogActiveText", 0, kThemeTextColorModelessDialogActive, 0 }, /* 115 */ - { "ModelessDialogInactiveText", 0, kThemeTextColorModelessDialogInactive, 0 }, /* 116 */ - { "WindowHeaderActiveText", 0, kThemeTextColorWindowHeaderActive, 0 }, /* 117 */ - { "WindowHeaderInactiveText", 0, kThemeTextColorWindowHeaderInactive, 0 }, /* 118 */ - { "PlacardActiveText", 0, kThemeTextColorPlacardActive, 0 }, /* 119 */ - { "PlacardInactiveText", 0, kThemeTextColorPlacardInactive, 0 }, /* 120 */ - { "PlacardPressedText", 0, kThemeTextColorPlacardPressed, 0 }, /* 121 */ - { "PushButtonActiveText", 0, kThemeTextColorPushButtonActive, 0 }, /* 122 */ - { "PushButtonInactiveText", 0, kThemeTextColorPushButtonInactive, 0 }, /* 123 */ - { "PushButtonPressedText", 0, kThemeTextColorPushButtonPressed, 0 }, /* 124 */ - { "BevelButtonActiveText", 0, kThemeTextColorBevelButtonActive, 0 }, /* 125 */ - { "BevelButtonInactiveText", 0, kThemeTextColorBevelButtonInactive, 0 }, /* 126 */ - { "BevelButtonPressedText", 0, kThemeTextColorBevelButtonPressed, 0 }, /* 127 */ - { "PopupButtonActiveText", 0, kThemeTextColorPopupButtonActive, 0 }, /* 128 */ - { "PopupButtonInactiveText", 0, kThemeTextColorPopupButtonInactive, 0 }, /* 129 */ - { "PopupButtonPressedText", 0, kThemeTextColorPopupButtonPressed, 0 }, /* 130 */ - { "IconLabelText", 0, kThemeTextColorIconLabel, 0 }, /* 131 */ - { "ListViewText", 0, kThemeTextColorListView, 0 }, /* 132 */ - { "DocumentWindowTitleActiveText", 0, kThemeTextColorDocumentWindowTitleActive, 0 }, /* 133 */ - { "DocumentWindowTitleInactiveText", 0, kThemeTextColorDocumentWindowTitleInactive, 0 }, /* 134 */ - { "MovableModalWindowTitleActiveText", 0, kThemeTextColorMovableModalWindowTitleActive, 0 }, /* 135 */ - { "MovableModalWindowTitleInactiveText",0, kThemeTextColorMovableModalWindowTitleInactive, 0 }, /* 136 */ - { "UtilityWindowTitleActiveText", 0, kThemeTextColorUtilityWindowTitleActive, 0 }, /* 137 */ - { "UtilityWindowTitleInactiveText", 0, kThemeTextColorUtilityWindowTitleInactive, 0 }, /* 138 */ - { "PopupWindowTitleActiveText", 0, kThemeTextColorPopupWindowTitleActive, 0 }, /* 139 */ - { "PopupWindowTitleInactiveText", 0, kThemeTextColorPopupWindowTitleInactive, 0 }, /* 140 */ - { "RootMenuActiveText", 0, kThemeTextColorRootMenuActive, 0 }, /* 141 */ - { "RootMenuSelectedText", 0, kThemeTextColorRootMenuSelected, 0 }, /* 142 */ - { "RootMenuDisabledText", 0, kThemeTextColorRootMenuDisabled, 0 }, /* 143 */ - { "MenuItemActiveText", 0, kThemeTextColorMenuItemActive, 0 }, /* 144 */ - { "MenuItemSelectedText", 0, kThemeTextColorMenuItemSelected, 0 }, /* 145 */ - { "MenuItemDisabledText", 0, kThemeTextColorMenuItemDisabled, 0 }, /* 146 */ - { "PopupLabelActiveText", 0, kThemeTextColorPopupLabelActive, 0 }, /* 147 */ - { "PopupLabelInactiveText", 0, kThemeTextColorPopupLabelInactive, 0 }, /* 148 */ - { "TabFrontActiveText", 0, kThemeTextColorTabFrontActive, 0 }, /* 149 */ - { "TabNonFrontActiveText", 0, kThemeTextColorTabNonFrontActive, 0 }, /* 150 */ - { "TabNonFrontPressedText", 0, kThemeTextColorTabNonFrontPressed, 0 }, /* 151 */ - { "TabFrontInactiveText", 0, kThemeTextColorTabFrontInactive, 0 }, /* 152 */ - { "TabNonFrontInactiveText", 0, kThemeTextColorTabNonFrontInactive, 0 }, /* 153 */ - { "IconLabelSelectedText", 0, kThemeTextColorIconLabelSelected, 0 }, /* 154 */ - { "BevelButtonStickyActiveText", 0, kThemeTextColorBevelButtonStickyActive, 0 }, /* 155 */ - { "BevelButtonStickyInactiveText", 0, kThemeTextColorBevelButtonStickyInactive, 0 }, /* 156 */ - { "NotificationText", 0, kThemeTextColorNotification, 0 }, /* 157 */ - { "SystemDetailText", 0, kThemeTextColorSystemDetail, 0 }, /* 158 */ - { "WhiteText", 0, kThemeTextColorWhite, 0 }, /* 159 */ - { "TabPaneBackground", 0, 0, kThemeBackgroundTabPane }, /* 160 */ - { "PlacardBackground", 0, 0, kThemeBackgroundPlacard }, /* 161 */ - { "WindowHeaderBackground", 0, 0, kThemeBackgroundWindowHeader }, /* 162 */ - { "ListViewWindowHeaderBackground", 0, 0, kThemeBackgroundListViewWindowHeader }, /* 163 */ - { "SecondaryGroupBoxBackground", 0, 0, kThemeBackgroundSecondaryGroupBox }, /* 164 */ - { "MetalBackground", 0, 0, kThemeBackgroundMetal }, /* 165 */ - { NULL, 0, 0, 0 } + { "Transparent", clearColor, 0 }, /* 30: TRANSPARENT_PIXEL */ + { "Highlight", HIBrush, kThemeBrushPrimaryHighlightColor }, /* 31 */ + { "HighlightSecondary", HIBrush, kThemeBrushSecondaryHighlightColor }, /* 32 */ + { "HighlightText", HIBrush, kThemeBrushBlack }, /* 33 */ + { "HighlightAlternate", HIBrush, kThemeBrushAlternatePrimaryHighlightColor }, /* 34 */ + { "ButtonText", HIText, kThemeTextColorPushButtonActive }, /* 35 */ + { "PrimaryHighlightColor", HIBrush, kThemeBrushPrimaryHighlightColor }, /* 36 */ + { "ButtonFace", HIBrush, kThemeBrushButtonFaceActive }, /* 37 */ + { "SecondaryHighlightColor", HIBrush, kThemeBrushSecondaryHighlightColor }, /* 38 */ + { "ButtonFrame", HIBrush, kThemeBrushButtonFrameActive }, /* 39 */ + { "AlternatePrimaryHighlightColor", HIBrush, kThemeBrushAlternatePrimaryHighlightColor }, /* 40 */ + { "WindowBody", HIBrush, kThemeBrushDocumentWindowBackground }, /* 41 */ + { "SheetBackground", HIBrush, kThemeBrushSheetBackground }, /* 42 */ + { "MenuActive", HIBrush, kThemeBrushMenuBackgroundSelected }, /* 43 */ + { "Black", HIBrush, kThemeBrushBlack }, /* 44 */ + { "MenuActiveText", HIText, kThemeTextColorMenuItemSelected }, /* 45 */ + { "White", HIBrush, kThemeBrushWhite }, /* 46 */ + { "Menu", HIBrush, kThemeBrushMenuBackground }, /* 47 */ + { "DialogBackgroundActive", HIBrush, kThemeBrushDialogBackgroundActive }, /* 48 */ + { "MenuDisabled", HIText, kThemeTextColorMenuItemDisabled }, /* 49 */ + { "DialogBackgroundInactive", HIBrush, kThemeBrushDialogBackgroundInactive }, /* 50 */ + { "MenuText", HIText, kThemeTextColorMenuItemActive }, /* 51 */ + { "AppearanceColor", appearance, 0 }, /* 52: APPEARANCE_PIXEL */ + { "AlertBackgroundActive", HIBrush, kThemeBrushAlertBackgroundActive }, /* 53 */ + { "AlertBackgroundInactive", HIBrush, kThemeBrushAlertBackgroundInactive }, /* 54 */ + { "ModelessDialogBackgroundActive", HIBrush, kThemeBrushModelessDialogBackgroundActive }, /* 55 */ + { "ModelessDialogBackgroundInactive", HIBrush, kThemeBrushModelessDialogBackgroundInactive }, /* 56 */ + { "UtilityWindowBackgroundActive", HIBrush, kThemeBrushUtilityWindowBackgroundActive }, /* 57 */ + { "UtilityWindowBackgroundInactive", HIBrush, kThemeBrushUtilityWindowBackgroundInactive }, /* 58 */ + { "ListViewSortColumnBackground", HIBrush, kThemeBrushListViewSortColumnBackground }, /* 59 */ + { "ListViewBackground", HIBrush, kThemeBrushListViewBackground }, /* 60 */ + { "IconLabelBackground", HIBrush, kThemeBrushIconLabelBackground }, /* 61 */ + { "ListViewSeparator", HIBrush, kThemeBrushListViewSeparator }, /* 62 */ + { "ChasingArrows", HIBrush, kThemeBrushChasingArrows }, /* 63 */ + { "DragHilite", HIBrush, kThemeBrushDragHilite }, /* 64 */ + { "DocumentWindowBackground", HIBrush, kThemeBrushDocumentWindowBackground }, /* 65 */ + { "FinderWindowBackground", HIBrush, kThemeBrushFinderWindowBackground }, /* 66 */ + { "ScrollBarDelimiterActive", HIBrush, kThemeBrushScrollBarDelimiterActive }, /* 67 */ + { "ScrollBarDelimiterInactive", HIBrush, kThemeBrushScrollBarDelimiterInactive }, /* 68 */ + { "FocusHighlight", HIBrush, kThemeBrushFocusHighlight }, /* 69 */ + { "PopupArrowActive", HIBrush, kThemeBrushPopupArrowActive }, /* 70 */ + { "PopupArrowPressed", HIBrush, kThemeBrushPopupArrowPressed }, /* 71 */ + { "PopupArrowInactive", HIBrush, kThemeBrushPopupArrowInactive }, /* 72 */ + { "AppleGuideCoachmark", HIBrush, kThemeBrushAppleGuideCoachmark }, /* 73 */ + { "IconLabelBackgroundSelected", HIBrush, kThemeBrushIconLabelBackgroundSelected }, /* 74 */ + { "StaticAreaFill", HIBrush, kThemeBrushStaticAreaFill }, /* 75 */ + { "ActiveAreaFill", HIBrush, kThemeBrushActiveAreaFill }, /* 76 */ + { "ButtonFrameActive", HIBrush, kThemeBrushButtonFrameActive }, /* 77 */ + { "ButtonFrameInactive", HIBrush, kThemeBrushButtonFrameInactive }, /* 78 */ + { "ButtonFaceActive", HIBrush, kThemeBrushButtonFaceActive }, /* 79 */ + { "ButtonFaceInactive", HIBrush, kThemeBrushButtonFaceInactive }, /* 80 */ + { "ButtonFacePressed", HIBrush, kThemeBrushButtonFacePressed }, /* 81 */ + { "ButtonActiveDarkShadow", HIBrush, kThemeBrushButtonActiveDarkShadow }, /* 82 */ + { "ButtonActiveDarkHighlight", HIBrush, kThemeBrushButtonActiveDarkHighlight }, /* 83 */ + { "ButtonActiveLightShadow", HIBrush, kThemeBrushButtonActiveLightShadow }, /* 84 */ + { "ButtonActiveLightHighlight", HIBrush, kThemeBrushButtonActiveLightHighlight }, /* 85 */ + { "ButtonInactiveDarkShadow", HIBrush, kThemeBrushButtonInactiveDarkShadow }, /* 86 */ + { "ButtonInactiveDarkHighlight", HIBrush, kThemeBrushButtonInactiveDarkHighlight }, /* 87 */ + { "ButtonInactiveLightShadow", HIBrush, kThemeBrushButtonInactiveLightShadow }, /* 88 */ + { "ButtonInactiveLightHighlight", HIBrush, kThemeBrushButtonInactiveLightHighlight }, /* 89 */ + { "ButtonPressedDarkShadow", HIBrush, kThemeBrushButtonPressedDarkShadow }, /* 90 */ + { "ButtonPressedDarkHighlight", HIBrush, kThemeBrushButtonPressedDarkHighlight }, /* 91 */ + { "ButtonPressedLightShadow", HIBrush, kThemeBrushButtonPressedLightShadow }, /* 92 */ + { "ButtonPressedLightHighlight", HIBrush, kThemeBrushButtonPressedLightHighlight }, /* 93 */ + { "BevelActiveLight", HIBrush, kThemeBrushBevelActiveLight }, /* 94 */ + { "BevelActiveDark", HIBrush, kThemeBrushBevelActiveDark }, /* 95 */ + { "BevelInactiveLight", HIBrush, kThemeBrushBevelInactiveLight }, /* 96 */ + { "BevelInactiveDark", HIBrush, kThemeBrushBevelInactiveDark }, /* 97 */ + { "NotificationWindowBackground", HIBrush, kThemeBrushNotificationWindowBackground }, /* 98 */ + { "MovableModalBackground", HIBrush, kThemeBrushMovableModalBackground }, /* 99 */ + { "SheetBackgroundOpaque", HIBrush, kThemeBrushSheetBackgroundOpaque }, /* 100 */ + { "DrawerBackground", HIBrush, kThemeBrushDrawerBackground }, /* 101 */ + { "ToolbarBackground", HIBrush, kThemeBrushToolbarBackground }, /* 102 */ + { "SheetBackgroundTransparent", HIBrush, kThemeBrushSheetBackgroundTransparent }, /* 103 */ + { "MenuBackground", HIBrush, kThemeBrushMenuBackground }, /* 104 */ + { "Pixel", rgbColor, 0 }, /* 105: PIXEL_MAGIC */ + { "MenuBackgroundSelected", HIBrush, kThemeBrushMenuBackgroundSelected }, /* 106 */ + { "ListViewOddRowBackground", HIBrush, kThemeBrushListViewOddRowBackground }, /* 107 */ + { "ListViewEvenRowBackground", HIBrush, kThemeBrushListViewEvenRowBackground }, /* 108 */ + { "ListViewColumnDivider", HIBrush, kThemeBrushListViewColumnDivider }, /* 109 */ + { "BlackText", HIText, kThemeTextColorBlack }, /* 110 */ + { "DialogActiveText", HIText, kThemeTextColorDialogActive }, /* 111 */ + { "DialogInactiveText", HIText, kThemeTextColorDialogInactive }, /* 112 */ + { "AlertActiveText", HIText, kThemeTextColorAlertActive }, /* 113 */ + { "AlertInactiveText", HIText, kThemeTextColorAlertInactive }, /* 114 */ + { "ModelessDialogActiveText", HIText, kThemeTextColorModelessDialogActive }, /* 115 */ + { "ModelessDialogInactiveText", HIText, kThemeTextColorModelessDialogInactive }, /* 116 */ + { "WindowHeaderActiveText", HIText, kThemeTextColorWindowHeaderActive }, /* 117 */ + { "WindowHeaderInactiveText", HIText, kThemeTextColorWindowHeaderInactive }, /* 118 */ + { "PlacardActiveText", HIText, kThemeTextColorPlacardActive }, /* 119 */ + { "PlacardInactiveText", HIText, kThemeTextColorPlacardInactive }, /* 120 */ + { "PlacardPressedText", HIText, kThemeTextColorPlacardPressed }, /* 121 */ + { "PushButtonActiveText", HIText, kThemeTextColorPushButtonActive }, /* 122 */ + { "PushButtonInactiveText", HIText, kThemeTextColorPushButtonInactive }, /* 123 */ + { "PushButtonPressedText", HIText, kThemeTextColorPushButtonPressed }, /* 124 */ + { "BevelButtonActiveText", HIText, kThemeTextColorBevelButtonActive }, /* 125 */ + { "BevelButtonInactiveText", HIText, kThemeTextColorBevelButtonInactive }, /* 126 */ + { "BevelButtonPressedText", HIText, kThemeTextColorBevelButtonPressed }, /* 127 */ + { "PopupButtonActiveText", HIText, kThemeTextColorPopupButtonActive }, /* 128 */ + { "PopupButtonInactiveText", HIText, kThemeTextColorPopupButtonInactive }, /* 129 */ + { "PopupButtonPressedText", HIText, kThemeTextColorPopupButtonPressed }, /* 130 */ + { "IconLabelText", HIText, kThemeTextColorIconLabel }, /* 131 */ + { "ListViewText", HIText, kThemeTextColorListView }, /* 132 */ + { "DocumentWindowTitleActiveText", HIText, kThemeTextColorDocumentWindowTitleActive }, /* 133 */ + { "DocumentWindowTitleInactiveText", HIText, kThemeTextColorDocumentWindowTitleInactive }, /* 134 */ + { "MovableModalWindowTitleActiveText", HIText, kThemeTextColorMovableModalWindowTitleActive }, /* 135 */ + { "MovableModalWindowTitleInactiveText",HIText, kThemeTextColorMovableModalWindowTitleInactive }, /* 136 */ + { "UtilityWindowTitleActiveText", HIText, kThemeTextColorUtilityWindowTitleActive }, /* 137 */ + { "UtilityWindowTitleInactiveText", HIText, kThemeTextColorUtilityWindowTitleInactive }, /* 138 */ + { "PopupWindowTitleActiveText", HIText, kThemeTextColorPopupWindowTitleActive }, /* 139 */ + { "PopupWindowTitleInactiveText", HIText, kThemeTextColorPopupWindowTitleInactive }, /* 140 */ + { "RootMenuActiveText", HIText, kThemeTextColorRootMenuActive }, /* 141 */ + { "RootMenuSelectedText", HIText, kThemeTextColorRootMenuSelected }, /* 142 */ + { "RootMenuDisabledText", HIText, kThemeTextColorRootMenuDisabled }, /* 143 */ + { "MenuItemActiveText", HIText, kThemeTextColorMenuItemActive }, /* 144 */ + { "MenuItemSelectedText", HIText, kThemeTextColorMenuItemSelected }, /* 145 */ + { "MenuItemDisabledText", HIText, kThemeTextColorMenuItemDisabled }, /* 146 */ + { "PopupLabelActiveText", HIText, kThemeTextColorPopupLabelActive }, /* 147 */ + { "PopupLabelInactiveText", HIText, kThemeTextColorPopupLabelInactive }, /* 148 */ + { "TabFrontActiveText", HIText, kThemeTextColorTabFrontActive }, /* 149 */ + { "TabNonFrontActiveText", HIText, kThemeTextColorTabNonFrontActive }, /* 150 */ + { "TabNonFrontPressedText", HIText, kThemeTextColorTabNonFrontPressed }, /* 151 */ + { "TabFrontInactiveText", HIText, kThemeTextColorTabFrontInactive }, /* 152 */ + { "TabNonFrontInactiveText", HIText, kThemeTextColorTabNonFrontInactive }, /* 153 */ + { "IconLabelSelectedText", HIText, kThemeTextColorIconLabelSelected }, /* 154 */ + { "BevelButtonStickyActiveText", HIText, kThemeTextColorBevelButtonStickyActive }, /* 155 */ + { "BevelButtonStickyInactiveText", HIText, kThemeTextColorBevelButtonStickyInactive }, /* 156 */ + { "NotificationText", HIText, kThemeTextColorNotification }, /* 157 */ + { "SystemDetailText", HIText, kThemeTextColorSystemDetail }, /* 158 */ + { "WhiteText", HIText, kThemeTextColorWhite }, /* 159 */ + { "TabPaneBackground", HIBackground, kThemeBackgroundTabPane }, /* 160 */ + { "PlacardBackground", HIBackground, kThemeBackgroundPlacard }, /* 161 */ + { "WindowHeaderBackground", HIBackground, kThemeBackgroundWindowHeader }, /* 162 */ + { "ListViewWindowHeaderBackground", HIBackground, kThemeBackgroundListViewWindowHeader }, /* 163 */ + { "SecondaryGroupBoxBackground", HIBackground, kThemeBackgroundSecondaryGroupBox }, /* 164 */ + { "MetalBackground", HIBackground, kThemeBackgroundMetal }, /* 165 */ + + /* + * Colors based on "semantic" NSColors. + */ + + { "WindowBackgroundColor", ttkBackground, 0 }, /* 166 */ + { "WindowBackgroundColor1", ttkBackground, 1 }, /* 167 */ + { "WindowBackgroundColor2", ttkBackground, 2 }, /* 168 */ + { "WindowBackgroundColor3", ttkBackground, 3 }, /* 169 */ + { "WindowBackgroundColor4", ttkBackground, 4 }, /* 170 */ + { "WindowBackgroundColor5", ttkBackground, 5 }, /* 171 */ + { "WindowBackgroundColor6", ttkBackground, 6 }, /* 172 */ + { "WindowBackgroundColor7", ttkBackground, 7 }, /* 173 */ + { "TextColor", semantic, 0 }, /* 174 */ + { "SelectedTextColor", semantic, 1 }, /* 175 */ + { "LabelColor", semantic, 2 }, /* 176 */ + { "ControlTextColor", semantic, 3 }, /* 177 */ + { "DisabledControlTextColor", semantic, 4 }, /* 178 */ + { "SelectedTabTextColor", semantic, 5 }, /* 179 */ + { "TextBackgroundColor", semantic, 6 }, /* 180 */ + { "SelectedTextBackgroundColor", semantic, 7 }, /* 181 */ + { "ControlAccentColor", semantic, 8 }, /* 182 */ + { NULL, 0, 0 } }; -#define MAX_PIXELCODE 165 +#define FIRST_SEMANTIC_COLOR 166 +#define MAX_PIXELCODE 182 /* *---------------------------------------------------------------------- * - * GetThemeFromPixelCode -- + * GetEntryFromPixelCode -- * - * When given a pixel code corresponding to a theme system color, - * set one of brush, textColor or background to the corresponding - * Appearance Mgr theme constant. + * Extract a SystemColorMapEntry from the table. * * Results: - * Returns false if not a real pixel, true otherwise. + * Returns false if the code is out of bounds. * * Side effects: * None. @@ -190,36 +232,28 @@ static const struct SystemColorMapEntry systemColorMap[] = { *---------------------------------------------------------------------- */ -static int -GetThemeFromPixelCode( +static bool +GetEntryFromPixelCode( unsigned char code, - ThemeBrush *brush, - ThemeTextColor *textColor, - ThemeBackgroundKind *background) + struct SystemColorMapEntry *entry) { if (code >= MIN_PIXELCODE && code <= MAX_PIXELCODE) { - *brush = systemColorMap[code - MIN_PIXELCODE].brush; - *textColor = systemColorMap[code - MIN_PIXELCODE].textColor; - *background = systemColorMap[code - MIN_PIXELCODE].background; + *entry = systemColorMap[code - MIN_PIXELCODE]; + return true; } else { - *brush = 0; - *textColor = 0; - *background = 0; - } - if (!*brush && !*textColor && !*background && code != PIXEL_MAGIC && - code != TRANSPARENT_PIXEL) { return false; - } else { - return true; } } /* *---------------------------------------------------------------------- * - * GetThemeColor -- + * SetCGColorComponents -- * - * Get RGB color for a given system color or pixel value. + * Set the components of a CGColorRef from an XColor pixel value and a + * system color map entry. The pixel value is only used in the case where + * the color is of type rgbColor. In that case the normalized XColor RGB + * values are copied into the CGColorRef. * * Results: * OSStatus @@ -230,60 +264,215 @@ GetThemeFromPixelCode( *---------------------------------------------------------------------- */ +static NSColorSpace* deviceRGB = NULL; +static CGFloat blueAccentRGBA[4] = {0, 122.0 / 255, 1.0, 1.0}; +static CGFloat windowBackground[4] = + {236.0 / 255, 236.0 / 255, 236.0 / 255, 1.0}; + static OSStatus -GetThemeColor( +SetCGColorComponents( + struct SystemColorMapEntry entry, unsigned long pixel, - ThemeBrush brush, - ThemeTextColor textColor, - ThemeBackgroundKind background, CGColorRef *c) { OSStatus err = noErr; + NSColor *bgColor, *color; + CGFloat rgba[4] = {0, 0, 0, 1}; +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101400 + NSInteger colorVariant; + static CGFloat graphiteAccentRGBA[4] = + {152.0 / 255, 152.0 / 255, 152.0 / 255, 1.0}; +#endif + + if (!deviceRGB) { + deviceRGB = [NSColorSpace deviceRGBColorSpace]; + } - if (brush) { - err = ChkErr(HIThemeBrushCreateCGColor, brush, c); - /*} else if (textColor) { - err = ChkErr(GetThemeTextColor, textColor, 32, true, c);*/ - } else { - CGFloat rgba[4] = {0, 0, 0, 1}; - - switch ((pixel >> 24) & 0xff) { - case PIXEL_MAGIC: { - unsigned short red, green, blue; - red = (pixel >> 16) & 0xff; - green = (pixel >> 8) & 0xff; - blue = (pixel ) & 0xff; - red |= red << 8; - green |= green << 8; - blue |= blue << 8; - rgba[0] = red / 65535.0; - rgba[1] = green / 65535.0; - rgba[2] = blue / 65535.0; + /* + * This function is called before our autorelease pool is set up, + * so it needs its own pool. + */ + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + + switch (entry.type) { + case HIBrush: + err = ChkErr(HIThemeBrushCreateCGColor, entry.value, c); + return err; + case rgbColor: + rgba[0] = ((pixel >> 16) & 0xff) / 255.0; + rgba[1] = ((pixel >> 8) & 0xff) / 255.0; + rgba[2] = ((pixel ) & 0xff) / 255.0; + break; + case ttkBackground: + + /* + * Prior to OSX 10.14, getComponents returns black when applied to + * windowBackGroundColor. + */ + + if ([NSApp macMinorVersion] < 14) { + for (int i=0; i<3; i++) { + rgba[i] = windowBackground[i]; + } + } else { + bgColor = [[NSColor windowBackgroundColor] colorUsingColorSpace: + deviceRGB]; + [bgColor getComponents: rgba]; + } + if (rgba[0] + rgba[1] + rgba[2] < 1.5) { + for (int i=0; i<3; i++) { + rgba[i] += entry.value*8.0 / 255.0; + } + } else { + for (int i=0; i<3; i++) { + rgba[i] -= entry.value*8.0 / 255.0; + } + } + break; + case semantic: + switch (entry.value) { + case 0: + color = [[NSColor textColor] colorUsingColorSpace: deviceRGB]; + break; + case 1: + color = [[NSColor selectedTextColor] colorUsingColorSpace: deviceRGB]; + break; + case 2: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + color = [[NSColor labelColor] colorUsingColorSpace: deviceRGB]; +#else + color = [[NSColor textColor] colorUsingColorSpace: deviceRGB]; +#endif + break; + case 3: + color = [[NSColor controlTextColor] colorUsingColorSpace: + deviceRGB]; break; + case 4: + color = [[NSColor disabledControlTextColor] colorUsingColorSpace: + deviceRGB]; + break; + case 5: + if ([NSApp macMinorVersion] > 6) { + color = [[NSColor whiteColor] colorUsingColorSpace: + deviceRGB]; + } else { + color = [[NSColor blackColor] colorUsingColorSpace: + deviceRGB]; + } + break; + case 6: + color = [[NSColor textBackgroundColor] colorUsingColorSpace: + deviceRGB]; + break; + case 7: + color = [[NSColor selectedTextBackgroundColor] colorUsingColorSpace: + deviceRGB]; + break; + case 8: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + if (@available(macOS 10.14, *)) { + color = [[NSColor controlAccentColor] colorUsingColorSpace: + deviceRGB]; + } else { + color = [NSColor colorWithColorSpace: deviceRGB + components: blueAccentRGBA + count: 4]; } - case TRANSPARENT_PIXEL: - rgba[3] = 0.0; +#else + colorVariant = [[NSUserDefaults standardUserDefaults] + integerForKey:@"AppleAquaColorVariant"]; + if (colorVariant == 6) { + color = [NSColor colorWithColorSpace: deviceRGB + components: graphiteAccentRGBA + count: 4]; + } else { + color = [NSColor colorWithColorSpace: deviceRGB + components: blueAccentRGBA + count: 4]; + } +#endif + break; + default: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + if ([NSApp macMinorVersion] >= 10) { + color = [[NSColor labelColor] colorUsingColorSpace: + deviceRGB]; + break; + } +#endif + color = [[NSColor textColor] colorUsingColorSpace: deviceRGB]; break; } + [color getComponents: rgba]; + break; + case clearColor: + rgba[3] = 0.0; + break; - static CGColorSpaceRef deviceRGBSpace = NULL; - if (!deviceRGBSpace) { - deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); - } - *c = CGColorCreate(deviceRGBSpace, rgba ); + /* + * There are no HITheme functions which convert Text or background colors + * to CGColors. (GetThemeTextColor has been removed, and it was never + * possible with backgrounds.) If we get one of these we return black. + */ + + case HIText: + case HIBackground: + default: + break; } + *c = CGColorCreate(deviceRGB.CGColorSpace, rgba); + [pool drain]; return err; } /* *---------------------------------------------------------------------- * + * TkMacOSXInDarkMode -- + * + * Tests whether the given window's NSView has a DarkAqua Appearance. + * + * Results: + * Returns true if the NSView is in DarkMode, false if not. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE Bool +TkMacOSXInDarkMode(Tk_Window tkwin) +{ + int result = false; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + if ([NSApp macMinorVersion] >= 14) { + static NSAppearanceName darkAqua = @"NSAppearanceNameDarkAqua"; + TkWindow *winPtr = (TkWindow*) tkwin; + NSView *view = TkMacOSXDrawableView(winPtr->privatePtr); + result = (view && + [view.effectiveAppearance.name isEqualToString:darkAqua]); + } +#endif + return result; +} + +/* + *---------------------------------------------------------------------- + * * TkSetMacColor -- * - * Creates a CGColorRef from a X style pixel value. + * Sets the components of a CGColorRef from an XColor pixel value. + * The high order byte of the pixel value is used as an index into + * the system color table, and then SetCGColorComponents is called + * with the table entry and the pixel value. * * Results: - * Returns false if not a real pixel, true otherwise. + * Returns false if the high order byte is not a valid index, true + * otherwise. * * Side effects: * The variable macColor is set to a new CGColorRef, the caller is @@ -299,14 +488,10 @@ TkSetMacColor( { CGColorRef *color = (CGColorRef*)macColor; OSStatus err = -1; - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; - - if (GetThemeFromPixelCode((pixel >> 24) & 0xff, &brush, &textColor, - &background)) { - err = ChkErr(GetThemeColor, pixel, brush, textColor, background, - color); + struct SystemColorMapEntry entry; + + if (GetEntryFromPixelCode((pixel >> 24) & 0xff, &entry)) { + err = ChkErr(SetCGColorComponents, entry, pixel, color); } return (err == noErr); } @@ -321,7 +506,7 @@ TkSetMacColor( * Results: * None resp. retained CGColorRef for CopyCachedColor() * - * Side effects: + * Side effects:M * None. * *---------------------------------------------------------------------- @@ -450,6 +635,7 @@ TkMacOSXGetNSColor( if (cgColor) { NSColorSpace *colorSpace = [[NSColorSpace alloc] initWithCGColorSpace:CGColorGetColorSpace(cgColor)]; + nsColor = [NSColor colorWithColorSpace:colorSpace components:CGColorGetComponents(cgColor) count:CGColorGetNumberOfComponents(cgColor)]; @@ -484,48 +670,56 @@ TkMacOSXSetColorInContext( unsigned long pixel, CGContextRef context) { - OSStatus err = -1; - CGColorRef cgColor = CopyCachedColor(gc, pixel); - ThemeBrush brush; - ThemeTextColor textColor; - ThemeBackgroundKind background; - - if (!cgColor && GetThemeFromPixelCode((pixel >> 24) & 0xff, &brush, - &textColor, &background)) { - if (brush) { - err = ChkErr(HIThemeSetFill, brush, NULL, context, + OSStatus err = noErr; + CGColorRef cgColor = nil; + struct SystemColorMapEntry entry; + CGRect rect; + int code = (pixel >> 24) & 0xff; + HIThemeBackgroundDrawInfo info = {0, kThemeStateActive, 0};; + static CGColorSpaceRef deviceRGBSpace = NULL; + + if (!deviceRGBSpace) { + deviceRGBSpace = CGColorSpaceCreateDeviceRGB(); + } + if (code < FIRST_SEMANTIC_COLOR) { + cgColor = CopyCachedColor(gc, pixel); + } + if (!cgColor && GetEntryFromPixelCode(code, &entry)) { + switch (entry.type) { + case HIBrush: + err = ChkErr(HIThemeSetFill, entry.value, NULL, context, kHIThemeOrientationNormal); if (err == noErr) { - err = ChkErr(HIThemeSetStroke, brush, NULL, context, + err = ChkErr(HIThemeSetStroke, entry.value, NULL, context, kHIThemeOrientationNormal); } - } else if (textColor) { - err = ChkErr(HIThemeSetTextFill, textColor, NULL, context, + break; + case HIText: + err = ChkErr(HIThemeSetTextFill, entry.value, NULL, context, kHIThemeOrientationNormal); - } else if (background) { - CGRect rect = CGContextGetClipBoundingBox(context); - HIThemeBackgroundDrawInfo info = { 0, kThemeStateActive, - background }; - + break; + case HIBackground: + info.kind = entry.value; + rect = CGContextGetClipBoundingBox(context); err = ChkErr(HIThemeApplyBackground, &rect, &info, context, kHIThemeOrientationNormal); + break; + default: + err = ChkErr(SetCGColorComponents, entry, pixel, &cgColor); + if (err == noErr) { + SetCachedColor(gc, pixel, cgColor); + } + break; } - if (err == noErr) { - return; - } - err = ChkErr(GetThemeColor, pixel, brush, textColor, background, - &cgColor); - if (err == noErr) { - SetCachedColor(gc, pixel, cgColor); - } - } else if (!cgColor) { - TkMacOSXDbgMsg("Ignored unknown pixel value 0x%lx", pixel); } if (cgColor) { CGContextSetFillColorWithColor(context, cgColor); CGContextSetStrokeColorWithColor(context, cgColor); CGColorRelease(cgColor); } + if (err != noErr) { + TkMacOSXDbgMsg("Ignored unknown pixel value 0x%lx", pixel); + } } /* @@ -549,7 +743,7 @@ TkMacOSXSetColorInContext( TkColor * TkpGetColor( Tk_Window tkwin, /* Window in which color will be used. */ - Tk_Uid name) /* Name of color to allocated (in form + Tk_Uid name) /* Name of color to be allocated (in form * suitable for passing to XParseColor). */ { Display *display = tkwin != None ? Tk_Display(tkwin) : NULL; @@ -561,22 +755,21 @@ TkpGetColor( * Check to see if this is a system color. Otherwise, XParseColor * will do all the work. */ + if (strncasecmp(name, "system", 6) == 0) { Tcl_Obj *strPtr = Tcl_NewStringObj(name+6, -1); int idx, result; result = Tcl_GetIndexFromObjStruct(NULL, strPtr, systemColorMap, - sizeof(struct SystemColorMapEntry), NULL, TCL_EXACT, &idx); + sizeof(struct SystemColorMapEntry), NULL, TCL_EXACT, &idx); Tcl_DecrRefCount(strPtr); if (result == TCL_OK) { OSStatus err; CGColorRef c; unsigned char pixelCode = idx + MIN_PIXELCODE; - ThemeBrush brush = systemColorMap[idx].brush; - ThemeTextColor textColor = systemColorMap[idx].textColor; - ThemeBackgroundKind background = systemColorMap[idx].background; + struct SystemColorMapEntry entry = systemColorMap[idx]; - err = ChkErr(GetThemeColor, 0, brush, textColor, background, &c); + err = ChkErr(SetCGColorComponents, entry, 0, &c); if (err == noErr) { const size_t n = CGColorGetNumberOfComponents(c); const CGFloat *rgba = CGColorGetComponents(c); @@ -591,12 +784,12 @@ TkpGetColor( color.red = color.green = color.blue = rgba[0] * 65535.0; break; default: - Tcl_Panic("CGColor with %d components", (int) n); + Tcl_Panic("CGColor with %d components", (int) n); } color.pixel = ((((((pixelCode << 8) - | ((color.red >> 8) & 0xff)) << 8) - | ((color.green >> 8) & 0xff)) << 8) - | ((color.blue >> 8) & 0xff)); + | ((color.red >> 8) & 0xff)) << 8) + | ((color.green >> 8) & 0xff)) << 8) + | ((color.blue >> 8) & 0xff)); CGColorRelease(c); goto validXColor; } diff --git a/macosx/tkMacOSXCursor.c b/macosx/tkMacOSXCursor.c index b6394b7..03d13dd 100644 --- a/macosx/tkMacOSXCursor.c +++ b/macosx/tkMacOSXCursor.c @@ -185,7 +185,7 @@ static const struct CursorName cursorNames[] = { * Declarations of static variables used in this file. */ -static TkMacOSXCursor * gCurrentCursor = NULL; +static TkMacOSXCursor *gCurrentCursor = NULL; /* A pointer to the current cursor. */ static int gResizeOverride = false; /* A boolean indicating whether we should use @@ -194,7 +194,7 @@ static int gTkOwnsCursor = true;/* A boolean indicating whether Tk owns the * cursor. If not (for instance, in the case * where a Tk window is embedded in another * app's window, and the cursor is out of the - * tk window, we will not attempt to adjust + * Tk window, we will not attempt to adjust * the cursor. */ /* @@ -278,6 +278,7 @@ FindCursorByName( kCGColorSpaceGenericGray); CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, bitmap, pix*pix/8, NULL); + if (provider) { img = CGImageCreate(pix, pix, 1, 1, pix/8, colorspace, kCGBitmapByteOrderDefault, provider, decodeWB, 0, @@ -299,14 +300,21 @@ FindCursorByName( initWithCGImage:maskedImg]; CFRelease(maskedImg); } - if (mask) { CFRelease(mask); } - if (img) { CFRelease(img); } - if (colorspace) { CFRelease(colorspace); } + if (mask) { + CFRelease(mask); + } + if (img) { + CFRelease(img); + } + if (colorspace) { + CFRelease(colorspace); + } if (bitmapImageRep) { image = [[NSImage alloc] initWithSize:NSMakeSize(pix, pix)]; [image addRepresentation:bitmapImageRep]; [bitmapImageRep release]; } + uint16_t *hotSpotData = (uint16_t*)(bitmap + 2*pix*pix/8); hotSpot.y = CFSwapInt16BigToHost(*hotSpotData++); hotSpot.x = CFSwapInt16BigToHost(*hotSpotData); diff --git a/macosx/tkMacOSXDefault.h b/macosx/tkMacOSXDefault.h index f5ffbbf..c6d998c 100644 --- a/macosx/tkMacOSXDefault.h +++ b/macosx/tkMacOSXDefault.h @@ -23,7 +23,9 @@ /* * The definitions below provide symbolic names for the default colors. * NORMAL_BG - Normal background color. + * NORMAL_FG - Normal foreground color. * ACTIVE_BG - Background color when widget is active. + * ACTIVE_FG - Foreground color when widget is active. * SELECT_BG - Background color for selected text. * SELECT_FG - Foreground color for selected text. * TROUGH - Background color for troughs in scales and scrollbars. @@ -33,12 +35,13 @@ #define BLACK "Black" #define WHITE "White" -#define NORMAL_BG "systemWindowBody" -#define ACTIVE_BG "systemButtonFacePressed" -#define ACTIVE_FG "systemPushButtonPressedText" -#define SELECT_BG "systemHighlight" -#define SELECT_FG NULL -#define INACTIVE_SELECT_BG "systemHighlightSecondary" +#define NORMAL_BG "systemTextBackgroundColor" +#define NORMAL_FG "systemTextColor" +#define ACTIVE_BG "systemTextBackgroundColor" +#define ACTIVE_FG "systemTextColor" +#define SELECT_BG "systemSelectedTextBackgroundColor" +#define SELECT_FG "systemSelectedTextColor" +#define INACTIVE_SELECT_BG "systemSelectedTextBackgroundColor" #define TROUGH "#c3c3c3" #define INDICATOR "#b03060" #define DISABLED "#a3a3a3" @@ -53,8 +56,7 @@ #define DEF_BUTTON_ACTIVE_FG_COLOR ACTIVE_FG #define DEF_CHKRAD_ACTIVE_FG_COLOR DEF_BUTTON_ACTIVE_FG_COLOR #define DEF_BUTTON_ACTIVE_FG_MONO WHITE -/* #define DEF_BUTTON_BG_COLOR "systemButtonFace"*/ -#define DEF_BUTTON_BG_COLOR WHITE +#define DEF_BUTTON_BG_COLOR NORMAL_BG #define DEF_BUTTON_BG_MONO WHITE #define DEF_BUTTON_BITMAP "" #define DEF_BUTTON_BORDER_WIDTH "2" @@ -64,7 +66,7 @@ #define DEF_BUTTON_DEFAULT "disabled" #define DEF_BUTTON_DISABLED_FG_COLOR DISABLED #define DEF_BUTTON_DISABLED_FG_MONO "" -#define DEF_BUTTON_FG "systemButtonText" +#define DEF_BUTTON_FG NORMAL_FG #define DEF_CHKRAD_FG DEF_BUTTON_FG #define DEF_BUTTON_FONT "TkDefaultFont" #define DEF_BUTTON_HEIGHT "0" @@ -178,17 +180,15 @@ #define DEF_ENTRY_DISABLED_FG DISABLED #define DEF_ENTRY_EXPORT_SELECTION "1" #define DEF_ENTRY_FONT "TkTextFont" -#define DEF_ENTRY_FG BLACK +#define DEF_ENTRY_FG NORMAL_FG #define DEF_ENTRY_HIGHLIGHT_BG NORMAL_BG #define DEF_ENTRY_HIGHLIGHT BLACK -/* #define DEF_ENTRY_HIGHLIGHT_WIDTH "3" */ #define DEF_ENTRY_HIGHLIGHT_WIDTH "3" -#define DEF_ENTRY_INSERT_BG BLACK +#define DEF_ENTRY_INSERT_BG NORMAL_FG #define DEF_ENTRY_INSERT_BD_COLOR "0" #define DEF_ENTRY_INSERT_BD_MONO "0" #define DEF_ENTRY_INSERT_OFF_TIME "300" #define DEF_ENTRY_INSERT_ON_TIME "600" -/* #define DEF_ENTRY_INSERT_WIDTH "2" */ #define DEF_ENTRY_INSERT_WIDTH "1" #define DEF_ENTRY_JUSTIFY "left" #define DEF_ENTRY_PLACEHOLDER "" @@ -196,7 +196,6 @@ #define DEF_ENTRY_READONLY_BG_COLOR NORMAL_BG #define DEF_ENTRY_READONLY_BG_MONO WHITE #define DEF_ENTRY_RELIEF "sunken" -/* #define DEF_ENTRY_RELIEF "solid" */ #define DEF_ENTRY_SCROLL_COMMAND "" #define DEF_ENTRY_SELECT_COLOR SELECT_BG #define DEF_ENTRY_SELECT_MONO BLACK @@ -239,7 +238,7 @@ #define DEF_LABELFRAME_BORDER_WIDTH "2" #define DEF_LABELFRAME_CLASS "Labelframe" #define DEF_LABELFRAME_RELIEF "groove" -#define DEF_LABELFRAME_FG "systemButtonText" +#define DEF_LABELFRAME_FG NORMAL_FG #define DEF_LABELFRAME_FONT "TkDefaultFont" #define DEF_LABELFRAME_TEXT "" #define DEF_LABELFRAME_LABELANCHOR "nw" @@ -249,14 +248,14 @@ */ #define DEF_LISTBOX_ACTIVE_STYLE "dotbox" -#define DEF_LISTBOX_BG_COLOR WHITE +#define DEF_LISTBOX_BG_COLOR NORMAL_BG #define DEF_LISTBOX_BG_MONO WHITE #define DEF_LISTBOX_BORDER_WIDTH "1" #define DEF_LISTBOX_CURSOR "" #define DEF_LISTBOX_DISABLED_FG DISABLED #define DEF_LISTBOX_EXPORT_SELECTION "1" #define DEF_LISTBOX_FONT "TkTextFont" -#define DEF_LISTBOX_FG BLACK +#define DEF_LISTBOX_FG NORMAL_FG #define DEF_LISTBOX_HEIGHT "10" #define DEF_LISTBOX_HIGHLIGHT_BG NORMAL_BG #define DEF_LISTBOX_HIGHLIGHT BLACK @@ -351,7 +350,7 @@ #define DEF_MENUBUTTON_DISABLED_FG_COLOR DISABLED #define DEF_MENUBUTTON_DISABLED_FG_MONO "" #define DEF_MENUBUTTON_FONT "TkDefaultFont" -#define DEF_MENUBUTTON_FG BLACK +#define DEF_MENUBUTTON_FG NORMAL_FG #define DEF_MENUBUTTON_HEIGHT "0" #define DEF_MENUBUTTON_HIGHLIGHT_BG_COLOR DEF_MENUBUTTON_BG_COLOR #define DEF_MENUBUTTON_HIGHLIGHT_BG_MONO DEF_MENUBUTTON_BG_MONO @@ -382,7 +381,7 @@ #define DEF_MESSAGE_BG_MONO WHITE #define DEF_MESSAGE_BORDER_WIDTH "1" #define DEF_MESSAGE_CURSOR "" -#define DEF_MESSAGE_FG BLACK +#define DEF_MESSAGE_FG NORMAL_FG #define DEF_MESSAGE_FONT "TkDefaultFont" #define DEF_MESSAGE_HIGHLIGHT_BG NORMAL_BG #define DEF_MESSAGE_HIGHLIGHT BLACK @@ -446,7 +445,7 @@ #define DEF_SCALE_CURSOR "" #define DEF_SCALE_DIGITS "0" #define DEF_SCALE_FONT "TkDefaultFont" -#define DEF_SCALE_FG_COLOR BLACK +#define DEF_SCALE_FG_COLOR NORMAL_FG #define DEF_SCALE_FG_MONO BLACK #define DEF_SCALE_FROM "0" #define DEF_SCALE_HIGHLIGHT_BG_COLOR DEF_SCALE_BG_COLOR @@ -508,14 +507,14 @@ #define DEF_TEXT_BLOCK_CURSOR "0" #define DEF_TEXT_BORDER_WIDTH "0" #define DEF_TEXT_CURSOR "xterm" -#define DEF_TEXT_FG BLACK +#define DEF_TEXT_FG NORMAL_FG #define DEF_TEXT_EXPORT_SELECTION "1" #define DEF_TEXT_FONT "TkFixedFont" #define DEF_TEXT_HEIGHT "24" #define DEF_TEXT_HIGHLIGHT_BG NORMAL_BG #define DEF_TEXT_HIGHLIGHT BLACK #define DEF_TEXT_HIGHLIGHT_WIDTH "3" -#define DEF_TEXT_INSERT_BG BLACK +#define DEF_TEXT_INSERT_BG NORMAL_FG #define DEF_TEXT_INSERT_BD_COLOR "0" #define DEF_TEXT_INSERT_BD_MONO "0" #define DEF_TEXT_INSERT_OFF_TIME "300" diff --git a/macosx/tkMacOSXDialog.c b/macosx/tkMacOSXDialog.c index a7e10b4..ad952e8 100644 --- a/macosx/tkMacOSXDialog.c +++ b/macosx/tkMacOSXDialog.c @@ -22,31 +22,39 @@ #else #define modalOK NSModalResponseOK #define modalCancel NSModalResponseCancel -#endif +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1090 #define modalOther -1 #define modalError -2 -/*Vars for filtering in "open file" and "save file" dialogs.*/ -typedef struct { - bool doFileTypes; // show the accessory view which displays the filter menu - bool preselectFilter; // a filter was selected by the typevariable - bool userHasSelectedFilter; // The user has changed the filter in the accessory view - - NSMutableArray *fileTypeNames; // array of names, e.g. "Text document" - NSMutableArray *fileTypeExtensions; // array of allowed extensions per name, e.g. "txt", "doc" - NSMutableArray *fileTypeLabels; // displayed string, e.g. "Text document (.txt, .doc)" - NSMutableArray *fileTypeAllowsAll; // boolean if the all pattern (*.*) is included - - NSMutableArray *allowedExtensions; // set of all allowed extensions - bool allowedExtensionsAllowAll; // set of all allowed extensions includes *.* +/* + * Vars for filtering in "open file" and "save file" dialogs. + */ - NSUInteger fileTypeIndex; // index of currently selected filter +typedef struct { + bool doFileTypes; /* Show the accessory view which + * displays the filter menu */ + bool preselectFilter; /* A filter was selected by the + * typevariable. */ + bool userHasSelectedFilter; /* The user has changed the filter in + * the accessory view. */ + NSMutableArray *fileTypeNames; /* Array of names, e.g. "Text + * document". */ + NSMutableArray *fileTypeExtensions; /* Array of allowed extensions per + * name, e.g. "txt", "doc". */ + NSMutableArray *fileTypeLabels; /* Displayed string, e.g. "Text + * document (.txt, .doc)". */ + NSMutableArray *fileTypeAllowsAll; /* Boolean if the all pattern (*.*) is + * included. */ + NSMutableArray *allowedExtensions; /* Set of all allowed extensions. */ + bool allowedExtensionsAllowAll; /* Set of all allowed extensions + * includes *.* */ + NSUInteger fileTypeIndex; /* Index of currently selected + * filter. */ } filepanelFilterInfo; -filepanelFilterInfo filterInfo; - -NSOpenPanel *openpanel; -NSSavePanel *savepanel; +static filepanelFilterInfo filterInfo; +static NSOpenPanel *openpanel; +static NSSavePanel *savepanel; static const char *const colorOptionStrings[] = { "-initialcolor", "-parent", "-title", NULL @@ -166,10 +174,15 @@ static const short alertNativeButtonIndexAndTypeToButtonIndex[][3] = { }; /* - * Construct a file URL from directory and filename. Either may - * be nil. If both are nil, returns nil. + * Construct a file URL from directory and filename. Either may be nil. If both + * are nil, returns nil. */ -static NSURL *getFileURL(NSString *directory, NSString *filename) { + +static NSURL * +getFileURL( + NSString *directory, + NSString *filename) +{ NSURL *url = nil; if (directory) { url = [NSURL fileURLWithPath:directory isDirectory:YES]; @@ -229,7 +242,6 @@ static NSURL *getFileURL(NSString *directory, NSString *filename) { } } - - (void) tkAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo { @@ -272,12 +284,18 @@ static NSURL *getFileURL(NSString *directory, NSString *filename) { if ([[filterInfo.fileTypeAllowsAll objectAtIndex:filterInfo.fileTypeIndex] boolValue]) { [openpanel setAllowsOtherFileTypes:YES]; - /* setAllowsOtherFileTypes might have no effect; it's inherited from the - * NSSavePanel, where it has the effect that it does not append an extension - * Setting the allowed file types to nil allows selecting any file */ + + /* + * setAllowsOtherFileTypes might have no effect; it's inherited from + * the NSSavePanel, where it has the effect that it does not append an + * extension. Setting the allowed file types to nil allows selecting + * any file. + */ + [openpanel setAllowedFileTypes:nil]; } else { - NSMutableArray *allowedtypes = [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; + NSMutableArray *allowedtypes = + [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; [openpanel setAllowedFileTypes:allowedtypes]; [openpanel setAllowsOtherFileTypes:NO]; } @@ -293,7 +311,8 @@ static NSURL *getFileURL(NSString *directory, NSString *filename) { [savepanel setAllowsOtherFileTypes:YES]; [savepanel setAllowedFileTypes:nil]; } else { - NSMutableArray *allowedtypes = [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; + NSMutableArray *allowedtypes = + [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; [savepanel setAllowedFileTypes:allowedtypes]; [savepanel setAllowsOtherFileTypes:NO]; } @@ -384,6 +403,7 @@ Tk_ChooseColorObjCmd( [colorPanel _setUseModalAppearance:YES]; if (title) { NSString *s = [[NSString alloc] initWithUTF8String:title]; + [colorPanel setTitle:s]; [s release]; } @@ -415,9 +435,17 @@ end: return result; } -/* dissect the -filetype nested lists and store the information - * in the filterInfo structure */ -int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVariablePtr) { +/* + * Dissect the -filetype nested lists and store the information in the + * filterInfo structure. + */ + +static int +parseFileFilters( + Tcl_Interp *interp, + Tcl_Obj *fileTypesPtr, + Tcl_Obj *typeVariablePtr) +{ if (!fileTypesPtr) { filterInfo.doFileTypes = false; @@ -425,6 +453,7 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar } FileFilterList fl; + TkInitFileFilters(&fl); if (TkGetFileFilters(interp, &fl, fileTypesPtr, 0) != TCL_OK) { TkFreeFileFilters(&fl); @@ -445,11 +474,12 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar if (filterInfo.doFileTypes) { for (FileFilter *filterPtr = fl.filters; filterPtr; filterPtr = filterPtr->next) { - NSString * name = [[NSString alloc] initWithUTF8String: filterPtr -> name]; + NSString *name = [[NSString alloc] initWithUTF8String: filterPtr->name]; + [filterInfo.fileTypeNames addObject:name]; [name release]; - NSMutableArray * clauseextensions = [NSMutableArray array]; - NSMutableArray * displayextensions = [NSMutableArray array]; + NSMutableArray *clauseextensions = [NSMutableArray array]; + NSMutableArray *displayextensions = [NSMutableArray array]; bool allowsAll = NO; for (FileFilterClause *clausePtr = filterPtr->clauses; clausePtr; @@ -460,7 +490,7 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar const char *str = globPtr->pattern; while (*str && (*str == '*' || *str == '.')) { str++; - } + } if (*str) { NSString *extension = [[NSString alloc] initWithUTF8String:str]; if (![filterInfo.allowedExtensions containsObject:extension]) { @@ -472,7 +502,10 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar [extension release]; } else { - // it is the all pattern (*, .* or *.*) + /* + * It is the all pattern (*, .* or *.*) + */ + allowsAll = YES; filterInfo.allowedExtensionsAllowAll = YES; [displayextensions addObject:@"*"]; @@ -482,27 +515,39 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar [filterInfo.fileTypeExtensions addObject:clauseextensions]; [filterInfo.fileTypeAllowsAll addObject:[NSNumber numberWithBool:allowsAll]]; - NSMutableString * label = [[NSMutableString alloc] initWithString:name]; + NSMutableString *label = [[NSMutableString alloc] initWithString:name]; [label appendString:@" ("]; [label appendString:[displayextensions componentsJoinedByString:@", "]]; [label appendString:@")"]; [filterInfo.fileTypeLabels addObject:label]; [label release]; - } - /* Check if the typevariable exists and matches one of the names */ + /* + * Check if the typevariable exists and matches one of the names. + */ + filterInfo.preselectFilter = false; filterInfo.userHasSelectedFilter = false; if (typeVariablePtr) { - /* extract the variable content as a NSString */ - Tcl_Obj *selectedFileTypeObj = Tcl_ObjGetVar2(interp, typeVariablePtr, NULL, TCL_GLOBAL_ONLY); + /* + * Extract the variable content as a NSString. + */ + + Tcl_Obj *selectedFileTypeObj = Tcl_ObjGetVar2(interp, + typeVariablePtr, NULL, TCL_GLOBAL_ONLY); + + /* + * Check that the typevariable exists. + */ - /* check that the typevariable exists */ if (selectedFileTypeObj != NULL) { - const char *selectedFileType = Tcl_GetString(selectedFileTypeObj); - NSString *selectedFileTypeStr = [[NSString alloc] initWithUTF8String:selectedFileType]; - NSUInteger index = [filterInfo.fileTypeNames indexOfObject:selectedFileTypeStr]; + const char *selectedFileType = + Tcl_GetString(selectedFileTypeObj); + NSString *selectedFileTypeStr = + [[NSString alloc] initWithUTF8String:selectedFileType]; + NSUInteger index = + [filterInfo.fileTypeNames indexOfObject:selectedFileTypeStr]; if (index != NSNotFound) { filterInfo.fileTypeIndex = index; @@ -517,17 +562,24 @@ int parseFileFilters(Tcl_Interp *interp, Tcl_Obj *fileTypesPtr, Tcl_Obj *typeVar return TCL_OK; } -bool filterCompatible(NSString *extension, int filterIndex) { - NSMutableArray *allowedExtensions = [filterInfo.fileTypeExtensions objectAtIndex: filterIndex]; +static bool +filterCompatible( + NSString *extension, + int filterIndex) +{ + NSMutableArray *allowedExtensions = + [filterInfo.fileTypeExtensions objectAtIndex: filterIndex]; + + /* + * If this contains the all pattern, accept any extension. + */ - /* If this contains the all pattern, accept any extension */ if ([[filterInfo.fileTypeAllowsAll objectAtIndex:filterIndex] boolValue]) { return true; } return [allowedExtensions containsObject: extension]; } - /* *---------------------------------------------------------------------- @@ -631,13 +683,16 @@ Tk_GetOpenFileObjCmd( if (title) { [openpanel setTitle:title]; - /* From OSX 10.11, the title string is silently ignored in the open panel. - * Prepend the title to the message in this case - * NOTE should be conditional on OSX version, but - * -mmacosx-version-min does not revert this behaviour*/ + /* + * From OSX 10.11, the title string is silently ignored in the open + * panel. Prepend the title to the message in this case. NOTE should + * be conditional on OSX version, but -mmacosx-version-min does not + * revert this behaviour + */ if (message) { - NSString *fullmessage = [[NSString alloc] initWithFormat:@"%@\n%@",title,message]; + NSString *fullmessage = + [[NSString alloc] initWithFormat:@"%@\n%@", title, message]; [message release]; [title release]; message = fullmessage; @@ -658,15 +713,20 @@ Tk_GetOpenFileObjCmd( } if (filterInfo.doFileTypes) { - NSView *accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; - NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)]; + NSView *accessoryView = [[NSView alloc] + initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; + NSTextField *label = [[NSTextField alloc] + initWithFrame:NSMakeRect(0, 0, 60, 22)]; + [label setEditable:NO]; [label setStringValue:@"Filter:"]; [label setBordered:NO]; [label setBezeled:NO]; [label setDrawsBackground:NO]; - NSPopUpButton *popupButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 240, 22.0) pullsDown:NO]; + NSPopUpButton *popupButton = [[NSPopUpButton alloc] + initWithFrame:NSMakeRect(50.0, 2, 240, 22.0) pullsDown:NO]; + [popupButton addItemsWithTitles:filterInfo.fileTypeLabels]; [popupButton setAction:@selector(selectFormat:)]; @@ -674,10 +734,16 @@ Tk_GetOpenFileObjCmd( [accessoryView addSubview:popupButton]; if (filterInfo.preselectFilter) { - /* A specific filter was selected from the typevariable. Select it and - * open the accessory view */ + /* + * A specific filter was selected from the typevariable. Select it + * and open the accessory view. + */ + [popupButton selectItemAtIndex:filterInfo.fileTypeIndex]; - /* on OSX > 10.11, the optons are not visible by default. Ergo allow all file types + + /* + * On OSX > 10.11, the options are not visible by default. Ergo + * allow all file types [openpanel setAllowedFileTypes:filterInfo.fileTypeExtensions[filterInfo.fileTypeIndex]]; */ [openpanel setAllowedFileTypes:filterInfo.allowedExtensions]; @@ -693,7 +759,10 @@ Tk_GetOpenFileObjCmd( [openpanel setAccessoryView:accessoryView]; } else { - /* No filters are given. Allow picking all files */ + /* + * No filters are given. Allow picking all files. + */ + [openpanel setAllowsOtherFileTypes:YES]; } @@ -711,20 +780,24 @@ Tk_GetOpenFileObjCmd( parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); if (haveParentOption && parent && ![parent attachedSheet]) { parentIsKey = [parent isKeyWindow]; - if (directory || filename ) { - NSURL * fileURL = getFileURL(directory, filename); + if (directory || filename) { + NSURL *fileURL = getFileURL(directory, filename); + [openpanel setDirectoryURL:fileURL]; } [openpanel beginSheetModalForWindow:parent - completionHandler:^(NSInteger returnCode) - { [NSApp tkFilePanelDidEnd:openpanel + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:openpanel returnCode:returnCode - contextInfo:callbackInfo ]; } ]; - modalReturnCode = cmdObj ? modalOther : [NSApp runModalForWindow:openpanel]; + contextInfo:callbackInfo ]; + }]; + modalReturnCode = cmdObj ? modalOther : + [NSApp runModalForWindow:openpanel]; } else { - if (directory || filename ) { - NSURL * fileURL = getFileURL(directory, filename); + if (directory || filename) { + NSURL *fileURL = getFileURL(directory, filename); + [openpanel setDirectoryURL:fileURL]; } @@ -737,38 +810,50 @@ Tk_GetOpenFileObjCmd( [parent makeKeyWindow]; } - if ((typeVariablePtr && (modalReturnCode == NSOKButton)) && - filterInfo.doFileTypes) { + if ((typeVariablePtr && (modalReturnCode == NSOKButton)) + && filterInfo.doFileTypes) { /* - * The -typevariable must be set to the selected file type, if the dialog was not cancelled + * The -typevariable must be set to the selected file type, if the + * dialog was not cancelled. */ + NSUInteger selectedFilterIndex = filterInfo.fileTypeIndex; NSString *selectedFilter = NULL; + if (filterInfo.userHasSelectedFilter) { selectedFilterIndex = filterInfo.fileTypeIndex; selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex]; } else { - /* Difficult case: the user has not touched the filter settings, but we must - * return something in the typevariable. First check if the preselected type is compatible - * with the selected file, otherwise choose the first compatible type from the list, - * finally fall back to the empty string */ + /* + * Difficult case: the user has not touched the filter settings, + * but we must return something in the typevariable. First check if + * the preselected type is compatible with the selected file, + * otherwise choose the first compatible type from the list, + * finally fall back to the empty string. + */ + NSURL *selectedFile; + if (multiple) { - // Use the first file in the case of multiple selection - // Anyway it is not overly useful here + /* + * Use the first file in the case of multiple selection. + * Anyway it is not overly useful here. + */ selectedFile = [[openpanel URLs] objectAtIndex:0]; } else { selectedFile = [openpanel URL]; } NSString *extension = [selectedFile pathExtension]; + if (filterInfo.preselectFilter && - filterCompatible(extension, filterInfo.fileTypeIndex)) { + filterCompatible(extension, filterInfo.fileTypeIndex)) { selectedFilterIndex = filterInfo.fileTypeIndex; // The preselection from the typevariable selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex]; } else { // scan the list NSUInteger i; + for (i = 0; i < [filterInfo.fileTypeNames count]; i++) { if (filterCompatible(extension, i)) { selectedFilterIndex = i; @@ -780,19 +865,17 @@ Tk_GetOpenFileObjCmd( } else { selectedFilter = @""; } - } } Tcl_ObjSetVar2(interp, typeVariablePtr, NULL, - Tcl_NewStringObj([selectedFilter UTF8String], -1), TCL_GLOBAL_ONLY); + Tcl_NewStringObj([selectedFilter UTF8String], -1), + TCL_GLOBAL_ONLY); } - end: return result; } - /* *---------------------------------------------------------------------- @@ -807,6 +890,7 @@ Tk_GetOpenFileObjCmd( * * Side effects: * See user documentation. + * *---------------------------------------------------------------------- */ @@ -828,7 +912,7 @@ Tk_GetSaveFileObjCmd( NSString *directory = nil, *filename = nil, *defaultType = nil; NSString *message = nil, *title = nil; NSWindow *parent; - savepanel = [NSSavePanel savePanel]; + savepanel = [NSSavePanel savePanel]; NSInteger modalReturnCode = modalError; BOOL parentIsKey = NO; @@ -906,13 +990,17 @@ Tk_GetSaveFileObjCmd( if (title) { [savepanel setTitle:title]; - /* From OSX 10.11, the title string is silently ignored, if the save panel is a sheet. - * Prepend the title to the message in this case - * NOTE should be conditional on OSX version, but - * -mmacosx-version-min does not revert this behaviour*/ + /* + * From OSX 10.11, the title string is silently ignored, if the save + * panel is a sheet. Prepend the title to the message in this case. + * NOTE: should be conditional on OSX version, but -mmacosx-version-min + * does not revert this behaviour. + */ + if (haveParentOption) { if (message) { - NSString *fullmessage = [[NSString alloc] initWithFormat:@"%@\n%@",title,message]; + NSString *fullmessage = + [[NSString alloc] initWithFormat:@"%@\n%@",title,message]; [message release]; [title release]; message = fullmessage; @@ -932,15 +1020,20 @@ Tk_GetSaveFileObjCmd( } if (filterInfo.doFileTypes) { - NSView *accessoryView = [[NSView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; - NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 60, 22)]; + NSView *accessoryView = [[NSView alloc] + initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; + NSTextField *label = [[NSTextField alloc] + initWithFrame:NSMakeRect(0, 0, 60, 22)]; + [label setEditable:NO]; [label setStringValue:NSLocalizedString(@"Format:", nil)]; [label setBordered:NO]; [label setBezeled:NO]; [label setDrawsBackground:NO]; - NSPopUpButton *popupButton = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(50.0, 2, 340, 22.0) pullsDown:NO]; + NSPopUpButton *popupButton = [[NSPopUpButton alloc] + initWithFrame:NSMakeRect(50.0, 2, 340, 22.0) pullsDown:NO]; + [popupButton addItemsWithTitles:filterInfo.fileTypeLabels]; [popupButton selectItemAtIndex:filterInfo.fileTypeIndex]; [popupButton setAction:@selector(saveFormat:)]; @@ -953,10 +1046,14 @@ Tk_GetSaveFileObjCmd( [savepanel setAllowedFileTypes:[filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]]; [savepanel setAllowsOtherFileTypes:filterInfo.allowedExtensionsAllowAll]; } else if (defaultType) { - /* If no filetypes are given, defaultextension is an alternative way - * to specify the attached extension. Just propose this extension, - * but don't display an accessory view */ + /* + * If no filetypes are given, defaultextension is an alternative way to + * specify the attached extension. Just propose this extension, but + * don't display an accessory view. + */ + NSMutableArray *AllowedFileTypes = [NSMutableArray array]; + [AllowedFileTypes addObject:defaultType]; [savepanel setAllowedFileTypes:AllowedFileTypes]; [savepanel setAllowsOtherFileTypes:YES]; @@ -978,27 +1075,39 @@ Tk_GetSaveFileObjCmd( parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); if (haveParentOption && parent && ![parent attachedSheet]) { - parentIsKey = [parent isKeyWindow]; - if (directory) { + parentIsKey = [parent isKeyWindow]; + if (directory) { [savepanel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; } - /*check for file name, otherwise set to empty string; crashes with uncaught exception if set to nil*/ + + /* + * Check for file name, otherwise set to empty string; crashes with + * uncaught exception if set to nil. + */ + if (filename) { [savepanel setNameFieldStringValue:filename]; } else { [savepanel setNameFieldStringValue:@""]; } [savepanel beginSheetModalForWindow:parent - completionHandler:^(NSInteger returnCode) - { [NSApp tkFilePanelDidEnd:savepanel + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:savepanel returnCode:returnCode - contextInfo:callbackInfo ]; } ]; - modalReturnCode = cmdObj ? modalOther : [NSApp runModalForWindow:savepanel]; + contextInfo:callbackInfo]; + }]; + modalReturnCode = cmdObj ? modalOther : + [NSApp runModalForWindow:savepanel]; } else { if (directory) { [savepanel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; } - /*check for file name, otherwise set to empty string; crashes with uncaught exception if set to nil*/ + + /* + * Check for file name, otherwise set to empty string; crashes with + * uncaught exception if set to nil. + */ + if (filename) { [savepanel setNameFieldStringValue:filename]; } else { @@ -1013,16 +1122,20 @@ Tk_GetSaveFileObjCmd( [parent makeKeyWindow]; } - if ((typeVariablePtr && (modalReturnCode == NSOKButton)) && filterInfo.doFileTypes) { + if (typeVariablePtr && (modalReturnCode == NSOKButton) + && filterInfo.doFileTypes) { /* - * The -typevariable must be set to the selected file type, if the dialog was not cancelled + * The -typevariable must be set to the selected file type, if the + * dialog was not cancelled. */ - NSString * selectedFilter = [filterInfo.fileTypeNames objectAtIndex:filterInfo.fileTypeIndex]; + + NSString *selectedFilter = + [filterInfo.fileTypeNames objectAtIndex:filterInfo.fileTypeIndex]; Tcl_ObjSetVar2(interp, typeVariablePtr, NULL, - Tcl_NewStringObj([selectedFilter UTF8String], -1), TCL_GLOBAL_ONLY); + Tcl_NewStringObj([selectedFilter UTF8String], -1), + TCL_GLOBAL_ONLY); } - end: return result; } @@ -1130,7 +1243,12 @@ Tk_ChooseDirectoryObjCmd( callbackInfo->cmdObj = cmdObj; callbackInfo->interp = interp; callbackInfo->multiple = 0; - /*check for directory value, set to root if not specified; otherwise crashes with exception because of nil string parameter*/ + + /* + * Check for directory value, set to root if not specified; otherwise + * crashes with exception because of nil string parameter. + */ + if (!directory) { directory = @"/"; } @@ -1139,10 +1257,11 @@ Tk_ChooseDirectoryObjCmd( parentIsKey = [parent isKeyWindow]; [panel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; [panel beginSheetModalForWindow:parent - completionHandler:^(NSInteger returnCode) - { [NSApp tkFilePanelDidEnd:panel - returnCode:returnCode - contextInfo:callbackInfo ]; } ]; + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:panel + returnCode:returnCode + contextInfo:callbackInfo]; + }]; modalReturnCode = cmdObj ? modalOther : [NSApp runModalForWindow:panel]; } else { [panel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; @@ -1201,11 +1320,12 @@ TkAboutDlg(void) */ NSString *version = @"Tcl " TCL_PATCH_LEVEL " & Tk " TCL_PATCH_LEVEL; - NSString *url = @"www.tcl-lang.org"; + NSString *url = @"www.tcl-lang.org"; NSTextView *credits = [[NSTextView alloc] initWithFrame:NSMakeRect(0,0,300,300)]; NSFont *font = [NSFont systemFontOfSize:[NSFont systemFontSize]]; NSDictionary *textAttributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; + [credits insertText: [[NSAttributedString alloc] initWithString:[NSString stringWithFormat: @"\n" "Tcl and Tk are distributed under a modified BSD license: " @@ -1219,11 +1339,13 @@ TkAboutDlg(void) "%1$C 1998-2000 Jim Ingham & Ray Johnson\n\n" "%1$C 1998-2000 Scriptics Inc.\n\n" "%1$C 1996-1997 Sun Microsystems Inc.", 0xA9, year] - attributes:textAttributes] - replacementRange:NSMakeRange(0,0)]; + attributes:textAttributes] + replacementRange:NSMakeRange(0,0)]; [credits setDrawsBackground:NO]; [credits setEditable:NO]; - NSAlert *about = [[NSAlert alloc] init]; + + NSAlert *about = [[NSAlert alloc] init]; + [[about window] setTitle:@"About Tcl & Tk"]; [about setMessageText: version]; [about setInformativeText:url]; @@ -1378,7 +1500,8 @@ Tk_MessageBoxObjCmd( */ if (Tcl_GetIndexFromObjStruct(interp, objv[indexDefaultOption + 1], - alertButtonStrings, sizeof(char *), "-default value", TCL_EXACT, &index) != TCL_OK) { + alertButtonStrings, sizeof(char *), "-default value", + TCL_EXACT, &index) != TCL_OK) { goto end; } @@ -1399,7 +1522,7 @@ Tk_MessageBoxObjCmd( [alert setAlertStyle:alertStyles[iconIndex]]; i = 0; while (i < 3 && alertButtonNames[typeIndex][i]) { - [alert addButtonWithTitle:(NSString*)alertButtonNames[typeIndex][i++]]; + [alert addButtonWithTitle:(NSString*) alertButtonNames[typeIndex][i++]]; } buttons = [alert buttons]; for (NSButton *b in buttons) { @@ -1428,10 +1551,11 @@ Tk_MessageBoxObjCmd( parentIsKey = [parent isKeyWindow]; #if MAC_OS_X_VERSION_MIN_REQUIRED > 1090 [alert beginSheetModalForWindow:parent - completionHandler:^(NSModalResponse returnCode) - { [NSApp tkAlertDidEnd:alert - returnCode:returnCode - contextInfo:callbackInfo ]; } ]; + completionHandler:^(NSModalResponse returnCode) { + [NSApp tkAlertDidEnd:alert + returnCode:returnCode + contextInfo:callbackInfo]; + }]; #else [alert beginSheetModalForWindow:parent modalDelegate:NSApp @@ -1471,7 +1595,10 @@ typedef struct FontchooserData { Tk_Window parent; } FontchooserData; -enum FontchooserEvent { FontchooserClosed, FontchooserSelection }; +enum FontchooserEvent { + FontchooserClosed, + FontchooserSelection +}; static void FontchooserEvent(int kind); static Tcl_Obj * FontchooserCget(FontchooserData *fcdPtr, @@ -1563,8 +1690,8 @@ enum FontchooserOption { * * FontchooserEvent -- * - * This processes events generated by user interaction with the - * font panel. + * This processes events generated by user interaction with the font + * panel. * * Results: * None. @@ -1623,9 +1750,8 @@ FontchooserEvent( * * FontchooserCget -- * - * Helper for the FontchooserConfigure command to return the - * current value of any of the options (which may be NULL in - * the structure) + * Helper for the FontchooserConfigure command to return the current value + * of any of the options (which may be NULL in the structure). * * Results: * Tcl object of option value. @@ -1688,8 +1814,8 @@ FontchooserCget( * * FontchooserConfigureCmd -- * - * Implementation of the 'tk fontchooser configure' ensemble command. - * See the user documentation for what it does. + * Implementation of the 'tk fontchooser configure' ensemble command. See + * the user documentation for what it does. * * Results: * See the user documentation. @@ -1822,7 +1948,8 @@ FontchooserConfigureCmd( [fm setSelectedAttributes:fontPanelFontAttributes isMultiple:NO]; if ([fp isVisible]) { - TkSendVirtualEvent(fcdPtr->parent, "TkFontchooserFontChanged", NULL); + TkSendVirtualEvent(fcdPtr->parent, + "TkFontchooserFontChanged", NULL); } break; case FontchooserCmd: @@ -1850,9 +1977,9 @@ FontchooserConfigureCmd( * * FontchooserShowCmd -- * - * Implements the 'tk fontchooser show' ensemble command. The - * per-interp configuration data for the dialog is held in an interp - * associated structure. + * Implements the 'tk fontchooser show' ensemble command. The per-interp + * configuration data for the dialog is held in an interp associated + * structure. * * Results: * See the user documentation. @@ -1878,8 +2005,10 @@ FontchooserShowCmd( Tk_CreateEventHandler(fcdPtr->parent, StructureNotifyMask, FontchooserParentEventHandler, fcdPtr); } + NSFontManager *fm = [NSFontManager sharedFontManager]; NSFontPanel *fp = [fm fontPanel:YES]; + if ([fp delegate] != NSApp) { [fp setDelegate:NSApp]; } @@ -1897,8 +2026,8 @@ FontchooserShowCmd( * * FontchooserHideCmd -- * - * Implementation of the 'tk fontchooser hide' ensemble. See the - * user documentation for details. + * Implementation of the 'tk fontchooser hide' ensemble. See the user + * documentation for details. * * Results: * See the user documentation. @@ -1917,6 +2046,7 @@ FontchooserHideCmd( Tcl_Obj *const objv[]) { NSFontPanel *fp = [[NSFontManager sharedFontManager] fontPanel:NO]; + if ([fp isVisible]) { [fp orderOut:NSApp]; } @@ -1997,8 +2127,8 @@ DeleteFontchooserData( * * TkInitFontchooser -- * - * Associate the font chooser configuration data with the Tcl - * interpreter. There is one font chooser per interp. + * Associate the font chooser configuration data with the Tcl interpreter. + * There is one font chooser per interp. * * Results: * None. diff --git a/macosx/tkMacOSXDraw.c b/macosx/tkMacOSXDraw.c index faad137..11dca9a 100644 --- a/macosx/tkMacOSXDraw.c +++ b/macosx/tkMacOSXDraw.c @@ -1,9 +1,8 @@ /* * tkMacOSXDraw.c -- * - * This file contains functions that perform drawing to - * Xlib windows. Most of the functions simple emulate - * Xlib functions. + * This file contains functions that perform drawing to Xlib windows. Most + * of the functions simple emulate Xlib functions. * * Copyright (c) 1995-1997 Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. @@ -31,7 +30,7 @@ #endif */ -#define radians(d) ((d) * (M_PI/180.0)) +#define radians(d) ((d) * (M_PI/180.0)) /* * Non-antialiased CG drawing looks better and more like X11 drawing when using @@ -40,7 +39,7 @@ #define NON_AA_CG_OFFSET .999 static int cgAntiAliasLimit = 0; -#define notAA(w) ((w) < cgAntiAliasLimit) +#define notAA(w) ((w) < cgAntiAliasLimit) static int useThemedToplevel = 0; static int useThemedFrame = 0; @@ -119,24 +118,24 @@ TkMacOSXInitCGDrawing( * replacement is [NSView cacheDisplayInRect: toBitmapImageRep:] and that * is what is being used here. However, that method only works when the * view has a valid CGContext, and a view is only guaranteed to have a - * valid context during a call to [NSView drawRect]. To further - * complicate matters, cacheDisplayInRect calls [NSView drawRect]. - * Essentially it is asking the view to draw a subrectangle of itself into - * a special graphics context which is linked to the BitmapImageRep. But - * our implementation of [NSView drawRect] does not allow recursive calls. - * If called recursively it returns immediately without doing any drawing. + * valid context during a call to [NSView drawRect]. To further complicate + * matters, cacheDisplayInRect calls [NSView drawRect]. Essentially it is + * asking the view to draw a subrectangle of itself into a special + * graphics context which is linked to the BitmapImageRep. But our + * implementation of [NSView drawRect] does not allow recursive calls. If + * called recursively it returns immediately without doing any drawing. * So the bottom line is that this function either returns a NULL pointer - * or a black image. To make it useful would require a significant amount - * of rewriting of the drawRect method. Perhaps the next release of OSX + * or a black image. To make it useful would require a significant amount + * of rewriting of the drawRect method. Perhaps the next release of OSX * will include some more helpful ways of doing this. * * Results: - * Returns an NSBitmapRep representing the image of the given - * rectangle of the given drawable. This object is retained. - * The caller is responsible for releasing it. + * Returns an NSBitmapRep representing the image of the given rectangle of + * the given drawable. This object is retained. The caller is responsible + * for releasing it. * - * NOTE: The x,y coordinates should be relative to a coordinate system with - * origin at the top left, as used by XImage and CGImage, not bottom + * NOTE: The x,y coordinates should be relative to a coordinate system + * with origin at the top left, as used by XImage and CGImage, not bottom * left as used by NSView. * * Side effects: @@ -144,46 +143,46 @@ TkMacOSXInitCGDrawing( * *---------------------------------------------------------------------- */ -NSBitmapImageRep* + +NSBitmapImageRep * TkMacOSXBitmapRepFromDrawableRect( - Drawable drawable, - int x, - int y, - unsigned int width, - unsigned int height) + Drawable drawable, + int x, + int y, + unsigned int width, + unsigned int height) { MacDrawable *mac_drawable = (MacDrawable *) drawable; CGContextRef cg_context = NULL; - CGImageRef cg_image=NULL, sub_cg_image = NULL; + CGImageRef cg_image = NULL, sub_cg_image = NULL; NSBitmapImageRep *bitmap_rep = NULL; - NSView *view=NULL; - if ( mac_drawable->flags & TK_IS_PIXMAP ) { - + NSView *view = NULL; + if (mac_drawable->flags & TK_IS_PIXMAP) { /* * This MacDrawable is a bitmap, so its view is NULL. */ - cg_context = TkMacOSXGetCGContextForDrawable(drawable); CGRect image_rect = CGRectMake(x, y, width, height); - cg_image = CGBitmapContextCreateImage( (CGContextRef) cg_context); + + cg_context = TkMacOSXGetCGContextForDrawable(drawable); + cg_image = CGBitmapContextCreateImage((CGContextRef) cg_context); sub_cg_image = CGImageCreateWithImageInRect(cg_image, image_rect); - if ( sub_cg_image ) { + if (sub_cg_image) { bitmap_rep = [NSBitmapImageRep alloc]; [bitmap_rep initWithCGImage:sub_cg_image]; } - if ( cg_image ) { + if (cg_image) { CGImageRelease(cg_image); } - } else if ( (view = TkMacOSXDrawableView(mac_drawable)) ) { - + } else if ((view = TkMacOSXDrawableView(mac_drawable)) != NULL) { /* * Convert Tk top-left to NSView bottom-left coordinates. */ int view_height = [view bounds].size.height; NSRect view_rect = NSMakeRect(x + mac_drawable->xOff, - view_height - height - y - mac_drawable->yOff, - width, height); + view_height - height - y - mac_drawable->yOff, + width, height); /* * Attempt to copy from the view to a bitmapImageRep. If the view does @@ -218,8 +217,7 @@ TkMacOSXBitmapRepFromDrawableRect( * None. * * Side effects: - * Data is moved from a window or bitmap to a second window or - * bitmap. + * Data is moved from a window or bitmap to a second window or bitmap. * *---------------------------------------------------------------------- */ @@ -229,10 +227,10 @@ XCopyArea( Display *display, /* Display. */ Drawable src, /* Source drawable. */ Drawable dst, /* Destination drawable. */ - GC gc, /* GC to use. */ + GC gc, /* GC to use. */ int src_x, /* X & Y, width & height */ int src_y, /* define the source rectangle */ - unsigned int width, /* that will be copied. */ + unsigned int width, /* that will be copied. */ unsigned int height, int dest_x, /* Dest X & Y on dest rect. */ int dest_y) @@ -249,37 +247,36 @@ XCopyArea( } if (!TkMacOSXSetupDrawingContext(dst, gc, 1, &dc)) { - return; TkMacOSXDbgMsg("Failed to setup drawing context."); + return; } - if ( dc.context ) { - if (srcDraw->flags & TK_IS_PIXMAP) { - img = TkMacOSXCreateCGImageWithDrawable(src); - }else if (TkMacOSXDrawableWindow(src)) { - bitmap_rep = TkMacOSXBitmapRepFromDrawableRect(src, - src_x, src_y, width, height); - if ( bitmap_rep ) { - img = [bitmap_rep CGImage]; - } - } else { - TkMacOSXDbgMsg("Invalid source drawable - neither window nor pixmap."); - } + if (!dc.context) { + TkMacOSXDbgMsg("Invalid destination drawable - no context."); + return; + } - if (img) { - bounds = CGRectMake(0, 0, srcDraw->size.width, srcDraw->size.height); - srcRect = CGRectMake(src_x, src_y, width, height); - dstRect = CGRectMake(dest_x, dest_y, width, height); - TkMacOSXDrawCGImage(dst, gc, dc.context, img, - gc->foreground, gc->background, bounds, srcRect, dstRect); - CFRelease(img); - } else { - TkMacOSXDbgMsg("Failed to construct CGImage."); + if (srcDraw->flags & TK_IS_PIXMAP) { + img = TkMacOSXCreateCGImageWithDrawable(src); + } else if (TkMacOSXDrawableWindow(src)) { + bitmap_rep = TkMacOSXBitmapRepFromDrawableRect(src, + src_x, src_y, width, height); + if (bitmap_rep) { + img = [bitmap_rep CGImage]; } + } else { + TkMacOSXDbgMsg("Invalid source drawable - neither window nor pixmap."); + } + if (img) { + bounds = CGRectMake(0, 0, srcDraw->size.width, srcDraw->size.height); + srcRect = CGRectMake(src_x, src_y, width, height); + dstRect = CGRectMake(dest_x, dest_y, width, height); + TkMacOSXDrawCGImage(dst, gc, dc.context, img, + gc->foreground, gc->background, bounds, srcRect, dstRect); + CFRelease(img); } else { - TkMacOSXDbgMsg("Invalid destination drawable - no context."); - return; + TkMacOSXDbgMsg("Failed to construct CGImage."); } TkMacOSXRestoreDrawingContext(&dc); @@ -290,10 +287,9 @@ XCopyArea( * * XCopyPlane -- * - * Copies a bitmap from a source drawable to a destination - * drawable. The plane argument specifies which bit plane of - * the source contains the bitmap. Note that this implementation - * ignores the gc->function. + * Copies a bitmap from a source drawable to a destination drawable. The + * plane argument specifies which bit plane of the source contains the + * bitmap. Note that this implementation ignores the gc->function. * * Results: * None. @@ -334,29 +330,50 @@ XCopyPlane( if (!TkMacOSXSetupDrawingContext(dst, gc, 1, &dc)) { return; } + CGContextRef context = dc.context; + if (context) { CGImageRef img = TkMacOSXCreateCGImageWithDrawable(src); + if (img) { TkpClipMask *clipPtr = (TkpClipMask *) gc->clip_mask; unsigned long imageBackground = gc->background; - if (clipPtr && clipPtr->type == TKP_CLIP_PIXMAP){ + + if (clipPtr && clipPtr->type == TKP_CLIP_PIXMAP) { srcRect = CGRectMake(src_x, src_y, width, height); - CGImageRef mask = TkMacOSXCreateCGImageWithDrawable(clipPtr->value.pixmap); - CGImageRef submask = CGImageCreateWithImageInRect(img, srcRect); + CGImageRef mask = TkMacOSXCreateCGImageWithDrawable( + clipPtr->value.pixmap); + CGImageRef submask = CGImageCreateWithImageInRect( + img, srcRect); CGRect rect = CGRectMake(dest_x, dest_y, width, height); + rect = CGRectOffset(rect, dstDraw->xOff, dstDraw->yOff); CGContextSaveGState(context); - /* Move the origin of the destination to top left. */ - CGContextTranslateCTM(context, 0, rect.origin.y + CGRectGetMaxY(rect)); + + /* + * Move the origin of the destination to top left. + */ + + CGContextTranslateCTM(context, + 0, rect.origin.y + CGRectGetMaxY(rect)); CGContextScaleCTM(context, 1, -1); - /* Fill with the background color, clipping to the mask. */ + + /* + * Fill with the background color, clipping to the mask. + */ + CGContextClipToMask(context, rect, submask); TkMacOSXSetColorInContext(gc, gc->background, dc.context); CGContextFillRect(context, rect); - /* Fill with the foreground color, clipping to the - intersection of img and mask. */ - CGImageRef subimage = CGImageCreateWithImageInRect(img, srcRect); + + /* + * Fill with the foreground color, clipping to the + * intersection of img and mask. + */ + + CGImageRef subimage = CGImageCreateWithImageInRect( + img, srcRect); CGContextClipToMask(context, rect, subimage); TkMacOSXSetColorInContext(gc, gc->foreground, context); CGContextFillRect(context, rect); @@ -366,22 +383,31 @@ XCopyPlane( CGImageRelease(submask); CGImageRelease(subimage); } else { - bounds = CGRectMake(0, 0, srcDraw->size.width, srcDraw->size.height); + bounds = CGRectMake(0, 0, + srcDraw->size.width, srcDraw->size.height); srcRect = CGRectMake(src_x, src_y, width, height); dstRect = CGRectMake(dest_x, dest_y, width, height); - TkMacOSXDrawCGImage(dst, gc, dc.context, img, gc->foreground, - imageBackground, bounds, srcRect, dstRect); + TkMacOSXDrawCGImage(dst, gc, dc.context, img, + gc->foreground, imageBackground, bounds, + srcRect, dstRect); CGImageRelease(img); } - } else { /* no image */ + } else { + /* no image */ TkMacOSXDbgMsg("Invalid source drawable"); } } else { - TkMacOSXDbgMsg("Invalid destination drawable - could not get a bitmap context."); + TkMacOSXDbgMsg("Invalid destination drawable - " + "could not get a bitmap context."); } TkMacOSXRestoreDrawingContext(&dc); - } else { /* source drawable is a window, not a Pixmap */ - XCopyArea(display, src, dst, gc, src_x, src_y, width, height, dest_x, dest_y); + } else { + /* + * Source drawable is a Window, not a Pixmap. + */ + + XCopyArea(display, src, dst, gc, src_x, src_y, width, height, + dest_x, dest_y); } } @@ -430,7 +456,7 @@ TkMacOSXCreateCGImageWithDrawable( *---------------------------------------------------------------------- */ -static NSImage* +static NSImage * CreateNSImageWithPixmap( Pixmap pixmap, int width, @@ -466,7 +492,7 @@ CreateNSImageWithPixmap( *---------------------------------------------------------------------- */ -NSImage* +NSImage * TkMacOSXGetNSImageWithTkImage( Display *display, Tk_Image image, @@ -499,7 +525,7 @@ TkMacOSXGetNSImageWithTkImage( *---------------------------------------------------------------------- */ -NSImage* +NSImage * TkMacOSXGetNSImageWithBitmap( Display *display, Pixmap bitmap, @@ -550,23 +576,24 @@ TkMacOSXGetCGContextForDrawable( CGColorSpaceRef colorspace = NULL; CGBitmapInfo bitmapInfo = #ifdef __LITTLE_ENDIAN__ - kCGBitmapByteOrder32Host; + kCGBitmapByteOrder32Host; #else - kCGBitmapByteOrderDefault; + kCGBitmapByteOrderDefault; #endif char *data; - CGRect bounds = CGRectMake(0, 0, macDraw->size.width, macDraw->size.height); + CGRect bounds = CGRectMake(0, 0, + macDraw->size.width, macDraw->size.height); if (macDraw->flags & TK_IS_BW_PIXMAP) { bitsPerPixel = 8; - bitmapInfo = (CGBitmapInfo)kCGImageAlphaOnly; + bitmapInfo = (CGBitmapInfo) kCGImageAlphaOnly; } else { colorspace = CGColorSpaceCreateDeviceRGB(); bitsPerPixel = 32; bitmapInfo |= kCGImageAlphaPremultipliedFirst; } - bytesPerRow = ((size_t) macDraw->size.width * bitsPerPixel + 127) >> 3 - & ~15; + bytesPerRow = ((size_t) + macDraw->size.width * bitsPerPixel + 127) >> 3 & ~15; len = macDraw->size.height * bytesPerRow; data = ckalloc(len); bzero(data, len); @@ -630,8 +657,8 @@ TkMacOSXDrawCGImage( dstBounds = CGRectOffset(dstBounds, macDraw->xOff, macDraw->yOff); if (CGImageIsMask(image)) { if (macDraw->flags & TK_IS_BW_PIXMAP) { - - /* Set fill color to black; background comes from the context, + /* + * Set fill color to black; background comes from the context, * or is transparent. */ @@ -655,11 +682,13 @@ TkMacOSXDrawCGImage( CGContextSetRGBFillColor(context, 0, 1, 0, 0.1); CGContextFillRect(context, dstBounds); CGContextStrokeRect(context, dstBounds); + CGPoint p[4] = {dstBounds.origin, CGPointMake(CGRectGetMaxX(dstBounds), CGRectGetMaxY(dstBounds)), CGPointMake(CGRectGetMinX(dstBounds), CGRectGetMaxY(dstBounds)), CGPointMake(CGRectGetMaxX(dstBounds), CGRectGetMinY(dstBounds)) }; + CGContextStrokeLineSegments(context, p, 4); CGContextRestoreGState(context); TkMacOSXDbgMsg("Drawing CGImage at (x=%f, y=%f), (w=%f, h=%f)", @@ -736,12 +765,13 @@ XDrawLines( CGContextAddLineToPoint(dc.context, prevx, prevy); } } + /* - * In the case of closed polylines, the first and last points - * are the same. We want miter or bevel join be rendered also - * at this point, this needs telling CoreGraphics that the - * path is closed. + * In the case of closed polylines, the first and last points are the + * same. We want miter or bevel join be rendered also at this point, + * this needs telling CoreGraphics that the path is closed. */ + if ((points[0].x == points[npoints-1].x) && (points[0].y == points[npoints-1].y)) { CGContextClosePath(dc.context); @@ -820,10 +850,10 @@ XDrawSegments( void XFillPolygon( - Display* display, /* Display. */ + Display *display, /* Display. */ Drawable d, /* Draw on this. */ GC gc, /* Use this GC. */ - XPoint* points, /* Array of points. */ + XPoint *points, /* Array of points. */ int npoints, /* Number of points. */ int shape, /* Shape to draw. */ int mode) /* Drawing mode. */ @@ -902,8 +932,7 @@ XDrawRectangle( double o = (lw % 2) ? .5 : 0; rect = CGRectMake( - macWin->xOff + x + o, - macWin->yOff + y + o, + macWin->xOff + x + o, macWin->yOff + y + o, width, height); CGContextStrokeRect(dc.context, rect); } @@ -916,17 +945,15 @@ XDrawRectangle( * * XDrawRectangles -- * - * Draws the outlines of the specified rectangles as if a - * five-point PolyLine protocol request were specified for each - * rectangle: + * Draws the outlines of the specified rectangles as if a five-point + * PolyLine protocol request were specified for each rectangle: * * [x,y] [x+width,y] [x+width,y+height] [x,y+height] [x,y] * - * For the specified rectangles, these functions do not draw a - * pixel more than once. XDrawRectangles draws the rectangles in - * the order listed in the array. If rectangles intersect, the - * intersecting pixels are drawn multiple times. Draws a - * rectangle. + * For the specified rectangles, these functions do not draw a pixel more + * than once. XDrawRectangles draws the rectangles in the order listed in + * the array. If rectangles intersect, the intersecting pixels are drawn + * multiple times. Draws a rectangle. * * Results: * None. @@ -991,7 +1018,7 @@ XDrawRectangles( int XFillRectangles( - Display* display, /* Display. */ + Display *display, /* Display. */ Drawable d, /* Draw on this. */ GC gc, /* Use this GC. */ XRectangle *rectangles, /* Rectangle array. */ @@ -1027,6 +1054,54 @@ XFillRectangles( /* *---------------------------------------------------------------------- * + * TkMacOSXDrawSolidBorder -- + * + * Draws a border rectangle of specified thickness inside the bounding + * rectangle of a Tk Window. The border rectangle can be inset within the + * bounding rectangle. For a highlight border the inset should be 0, but + * for a solid border around the actual window the inset should equal the + * thickness of the highlight border. The color of the border rectangle + * is the foreground color of the graphics context passed to the function. + * + * Results: + * None. + * + * Side effects: + * Draws a rectangular border inside the bounding rectangle of a window. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE void +TkMacOSXDrawSolidBorder( + Tk_Window tkwin, + GC gc, + int inset, + int thickness) +{ + Drawable d = Tk_WindowId(tkwin); + TkMacOSXDrawingContext dc; + CGRect outerRect, innerRect; + + if (!TkMacOSXSetupDrawingContext(d, gc, 1, &dc)) { + return; + } + if (dc.context) { + outerRect = CGRectMake(Tk_X(tkwin), Tk_Y(tkwin), + Tk_Width(tkwin), Tk_Height(tkwin)); + outerRect = CGRectInset(outerRect, inset, inset); + innerRect = CGRectInset(outerRect, thickness, thickness); + CGContextBeginPath(dc.context); + CGContextAddRect(dc.context, outerRect); + CGContextAddRect(dc.context, innerRect); + CGContextEOFillPath(dc.context); + } + TkMacOSXRestoreDrawingContext(&dc); +} + +/* + *---------------------------------------------------------------------- + * * XDrawArc -- * * Draw an arc. @@ -1042,7 +1117,7 @@ XFillRectangles( void XDrawArc( - Display* display, /* Display. */ + Display *display, /* Display. */ Drawable d, /* Draw on this. */ GC gc, /* Use this GC. */ int x, int y, /* Upper left of bounding rect. */ @@ -1099,14 +1174,13 @@ XDrawArc( * * XDrawArcs -- * - * Draws multiple circular or elliptical arcs. Each arc is - * specified by a rectangle and two angles. The center of the - * circle or ellipse is the center of the rect- angle, and the - * major and minor axes are specified by the width and height. - * Positive angles indicate counterclock- wise motion, and - * negative angles indicate clockwise motion. If the magnitude - * of angle2 is greater than 360 degrees, XDrawArcs truncates it - * to 360 degrees. + * Draws multiple circular or elliptical arcs. Each arc is specified by a + * rectangle and two angles. The center of the circle or ellipse is the + * center of the rect- angle, and the major and minor axes are specified + * by the width and height. Positive angles indicate counterclock- wise + * motion, and negative angles indicate clockwise motion. If the magnitude + * of angle2 is greater than 360 degrees, XDrawArcs truncates it to 360 + * degrees. * * Results: * None. @@ -1125,7 +1199,6 @@ XDrawArcs( XArc *arcArr, int nArcs) { - MacDrawable *macWin = (MacDrawable *) d; TkMacOSXDrawingContext dc; XArc *arcPtr; @@ -1195,7 +1268,7 @@ XDrawArcs( void XFillArc( - Display* display, /* Display. */ + Display *display, /* Display. */ Drawable d, /* Draw on this. */ GC gc, /* Use this GC. */ int x, int y, /* Upper left of bounding rect. */ @@ -1360,13 +1433,12 @@ XMaxRequestSize( * * TkScrollWindow -- * - * Scroll a rectangle of the specified window and accumulate - * a damage region. + * Scroll a rectangle of the specified window and accumulate a damage + * region. * * Results: - * Returns 0 if the scroll generated no additional damage. - * Otherwise, sets the region that needs to be repainted after - * scrolling and returns 1. + * Returns 0 if the scroll generated no additional damage. Otherwise, sets + * the region that needs to be repainted after scrolling and returns 1. * * Side effects: * Scrolls the bits in the window. @@ -1385,47 +1457,63 @@ TkScrollWindow( { Drawable drawable = Tk_WindowId(tkwin); MacDrawable *macDraw = (MacDrawable *) drawable; - TKContentView *view = (TKContentView *)TkMacOSXDrawableView(macDraw); + TKContentView *view = (TKContentView *) TkMacOSXDrawableView(macDraw); CGRect srcRect, dstRect; HIShapeRef dmgRgn = NULL, extraRgn = NULL; NSRect bounds, visRect, scrollSrc, scrollDst; int result = 0; - if ( view ) { - /* Get the scroll area in NSView coordinates (origin at bottom left). */ + if (view) { + /* + * Get the scroll area in NSView coordinates (origin at bottom left). + */ + bounds = [view bounds]; scrollSrc = NSMakeRect(macDraw->xOff + x, - bounds.size.height - height - (macDraw->yOff + y), - width, height); + bounds.size.height - height - (macDraw->yOff + y), + width, height); scrollDst = NSOffsetRect(scrollSrc, dx, -dy); - /* Limit scrolling to the window content area. */ + /* + * Limit scrolling to the window content area. + */ + visRect = [view visibleRect]; scrollSrc = NSIntersectionRect(scrollSrc, visRect); scrollDst = NSIntersectionRect(scrollDst, visRect); - if ( !NSIsEmptyRect(scrollSrc) && !NSIsEmptyRect(scrollDst) ) { + if (!NSIsEmptyRect(scrollSrc) && !NSIsEmptyRect(scrollDst)) { /* * Mark the difference between source and destination as damaged. - * This region is described in NSView coordinates (y=0 at the bottom) - * and converted to Tk coordinates later. + * This region is described in NSView coordinates (y=0 at the + * bottom) and converted to Tk coordinates later. */ srcRect = CGRectMake(x, y, width, height); dstRect = CGRectOffset(srcRect, dx, dy); - /* Compute the damage. */ + /* + * Compute the damage. + */ + dmgRgn = HIShapeCreateMutableWithRect(&srcRect); extraRgn = HIShapeCreateWithRect(&dstRect); - ChkErr(HIShapeDifference, dmgRgn, extraRgn, (HIMutableShapeRef) dmgRgn); + ChkErr(HIShapeDifference, dmgRgn, extraRgn, + (HIMutableShapeRef) dmgRgn); result = HIShapeIsEmpty(dmgRgn) ? 0 : 1; - /* Convert to Tk coordinates, offset by the window origin. */ + /* + * Convert to Tk coordinates, offset by the window origin. + */ + TkMacOSXSetWithNativeRegion(damageRgn, dmgRgn); if (extraRgn) { CFRelease(extraRgn); } - /* Scroll the rectangle. */ + /* + * Scroll the rectangle. + */ + [view scrollRect:scrollSrc by:NSMakeSize(dx, -dy)]; } } else { @@ -1472,8 +1560,8 @@ TkMacOSXSetUpGraphicsPort( * Set up a drawing context for the given drawable and GC. * * Results: - * Boolean indicating whether it is ok to draw; if false, drawing - * context was not setup, so do not attempt to draw and do not call + * Boolean indicating whether it is ok to draw; if false, drawing context + * was not setup, so do not attempt to draw and do not call * TkMacOSXRestoreDrawingContext(). * * Side effects: @@ -1486,18 +1574,18 @@ Bool TkMacOSXSetupDrawingContext( Drawable d, GC gc, - int useCG, /* advisory only ! */ + int useCG, /* advisory only ! */ TkMacOSXDrawingContext *dcPtr) { - MacDrawable *macDraw = ((MacDrawable*)d); + MacDrawable *macDraw = (MacDrawable *) d; Bool canDraw = true; NSWindow *win = NULL; TkMacOSXDrawingContext dc = {}; CGRect clipBounds; /* - * If the drawable is not a pixmap and it has an associated - * NSWindow then we know we are drawing to a window. + * If the drawable is not a pixmap and it has an associated NSWindow then + * we know we are drawing to a window. */ if (!(macDraw->flags & TK_IS_PIXMAP)) { @@ -1516,9 +1604,8 @@ TkMacOSXSetupDrawingContext( } /* - * If we already have a CGContext, use it. Otherwise, if we - * are drawing to a window then we can get one from the - * window. + * If we already have a CGContext, use it. Otherwise, if we are drawing to + * a window then we can get one from the window. */ dc.context = TkMacOSXGetCGContextForDrawable(d); @@ -1526,35 +1613,34 @@ TkMacOSXSetupDrawingContext( dc.portBounds = clipBounds = CGContextGetClipBoundingBox(dc.context); } else if (win) { NSView *view = TkMacOSXDrawableView(macDraw); - if (view) { - /* - * We can only draw into the view when the current CGContext is - * valid and belongs to the view. Validity can only be guaranteed - * inside of a view's drawRect or setFrame methods. The isDrawing - * attribute tells us whether we are being called from one of those - * methods. - * - * If the CGContext is not valid, or belongs to a different View, - * then we mark our view as needing display and return failure. - * It should get drawn in a later call to drawRect. - */ - - if (view != [NSView focusView]) { - [view setNeedsDisplay:YES]; - canDraw = false; - goto end; - } - dc.view = view; - dc.context = GET_CGCONTEXT; - dc.portBounds = NSRectToCGRect([view bounds]); - if (dc.clipRgn) { - clipBounds = CGContextGetClipBoundingBox(dc.context); - } - } else { + if (!view) { Tcl_Panic("TkMacOSXSetupDrawingContext(): " "no NSView to draw into !"); } + + /* + * We can only draw into the view when the current CGContext is valid + * and belongs to the view. Validity can only be guaranteed inside of + * a view's drawRect or setFrame methods. The isDrawing attribute + * tells us whether we are being called from one of those methods. + * + * If the CGContext is not valid, or belongs to a different View, then + * we mark our view as needing display and return failure. It should + * get drawn in a later call to drawRect. + */ + + if (view != [NSView focusView]) { + [view setNeedsDisplay:YES]; + canDraw = false; + goto end; + } + dc.view = view; + dc.context = GET_CGCONTEXT; + dc.portBounds = NSRectToCGRect([view bounds]); + if (dc.clipRgn) { + clipBounds = CGContextGetClipBoundingBox(dc.context); + } } else { Tcl_Panic("TkMacOSXSetupDrawingContext(): " "no context to draw into !"); @@ -1565,8 +1651,13 @@ TkMacOSXSetupDrawingContext( */ if (dc.context) { - CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, - .ty = dc.portBounds.size.height}; + CGAffineTransform t = { + .a = 1, .b = 0, + .c = 0, .d = -1, + .tx = 0, + .ty = dc.portBounds.size.height + }; + dc.portBounds.origin.x += macDraw->xOff; dc.portBounds.origin.y += macDraw->yOff; CGContextSaveGState(dc.context); @@ -1581,6 +1672,7 @@ TkMacOSXSetupDrawingContext( CGContextRestoreGState(dc.context); #endif /* TK_MAC_DEBUG_DRAWING */ CGRect r; + if (!HIShapeIsRectangular(dc.clipRgn) || !CGRectContainsRect( *HIShapeGetBounds(dc.clipRgn, &r), CGRectApplyAffineTransform(clipBounds, t))) { @@ -1608,21 +1700,28 @@ TkMacOSXSetupDrawingContext( CGContextSetPatternPhase(dc.context, CGSizeMake( dc.portBounds.size.width, dc.portBounds.size.height)); } - if(gc->function != GXcopy) { + if (gc->function != GXcopy) { TkMacOSXDbgMsg("Logical functions other than GXcopy are " "not supported for CG drawing!"); } - /* When should we antialias? */ + + /* + * When should we antialias? + */ + shouldAntialias = !notAA(gc->line_width); if (!shouldAntialias) { - /* Make non-antialiased CG drawing look more like X11 */ + /* + * Make non-antialiased CG drawing look more like X11. + */ + w -= (gc->line_width ? NON_AA_CG_OFFSET : 0); } CGContextSetShouldAntialias(dc.context, shouldAntialias); CGContextSetLineWidth(dc.context, w); if (gc->line_style != LineSolid) { int num = 0; - char *p = &(gc->dashes); + char *p = &gc->dashes; CGFloat dashOffset = gc->dash_offset; CGFloat lengths[10]; @@ -1632,13 +1731,13 @@ TkMacOSXSetupDrawingContext( } CGContextSetLineDash(dc.context, dashOffset, lengths, num); } - if ((unsigned)gc->cap_style < sizeof(cgCap)/sizeof(CGLineCap)) { + if ((unsigned) gc->cap_style < sizeof(cgCap)/sizeof(CGLineCap)) { CGContextSetLineCap(dc.context, - cgCap[(unsigned)gc->cap_style]); + cgCap[(unsigned) gc->cap_style]); } if ((unsigned)gc->join_style < sizeof(cgJoin)/sizeof(CGLineJoin)) { CGContextSetLineJoin(dc.context, - cgJoin[(unsigned)gc->join_style]); + cgJoin[(unsigned) gc->join_style]); } } } @@ -1647,8 +1746,11 @@ end: #ifdef TK_MAC_DEBUG_DRAWING if (!canDraw && win != NULL) { TkWindow *winPtr = TkMacOSXGetTkWindow(win); - if (winPtr) fprintf(stderr, "Cannot draw in %s - postponing.\n", - Tk_PathName(winPtr)); + + if (winPtr) { + fprintf(stderr, "Cannot draw in %s - postponing.\n", + Tk_PathName(winPtr)); + } } #endif if (!canDraw && dc.clipRgn) { @@ -1719,7 +1821,9 @@ TkMacOSXGetClipRgn( TkMacOSXUpdateClipRgn(macDraw->winPtr); #ifdef TK_MAC_DEBUG_DRAWING TkMacOSXDbgMsg("%s", macDraw->winPtr->pathName); + NSView *view = TkMacOSXDrawableView(macDraw); + CGContextSaveGState(context); CGContextConcatCTM(context, CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, [view bounds].size.height)); @@ -1743,8 +1847,8 @@ TkMacOSXGetClipRgn( * * TkMacOSXSetUpClippingRgn -- * - * Set up the clipping region so that drawing only occurs on the - * specified X subwindow. + * Set up the clipping region so that drawing only occurs on the specified + * X subwindow. * * Results: * None. @@ -1766,8 +1870,8 @@ TkMacOSXSetUpClippingRgn( * * TkpClipDrawableToRect -- * - * Clip all drawing into the drawable d to the given rectangle. - * If width or height are negative, reset to no clipping. + * Clip all drawing into the drawable d to the given rectangle. If width + * or height are negative, reset to no clipping. * * Results: * None. @@ -1832,8 +1936,8 @@ ClipToGC( HIShapeRef *clipRgnPtr) /* must point to initialized variable */ { if (gc && gc->clip_mask && - ((TkpClipMask*)gc->clip_mask)->type == TKP_CLIP_REGION) { - TkRegion gcClip = ((TkpClipMask*)gc->clip_mask)->value.region; + ((TkpClipMask *) gc->clip_mask)->type == TKP_CLIP_REGION) { + TkRegion gcClip = ((TkpClipMask *) gc->clip_mask)->value.region; int xOffset = ((MacDrawable *) d)->xOff + gc->clip_x_origin; int yOffset = ((MacDrawable *) d)->yOff + gc->clip_y_origin; HIShapeRef clipRgn = *clipRgnPtr, gcClipRgn; @@ -1856,10 +1960,9 @@ ClipToGC( * * TkMacOSXMakeStippleMap -- * - * Given a drawable and a stipple pattern this function draws the - * pattern repeatedly over the drawable. The drawable can then - * be used as a mask for bit-bliting a stipple pattern over an - * object. + * Given a drawable and a stipple pattern this function draws the pattern + * repeatedly over the drawable. The drawable can then be used as a mask + * for bit-bliting a stipple pattern over an object. * * Results: * A BitMap data structure. @@ -1883,12 +1986,12 @@ TkMacOSXMakeStippleMap( * * TkpDrawHighlightBorder -- * - * This procedure draws a rectangular ring around the outside of - * a widget to indicate that it has received the input focus. + * This procedure draws a rectangular ring around the outside of a widget + * to indicate that it has received the input focus. * - * On the Macintosh, this puts a 1 pixel border in the bgGC color - * between the widget and the focus ring, except in the case where - * highlightWidth is 1, in which case the border is left out. + * On the Macintosh, this puts a 1 pixel border in the bgGC color between + * the widget and the focus ring, except in the case where highlightWidth + * is 1, in which case the border is left out. * * For proper Mac L&F, use highlightWidth of 3. * @@ -1896,8 +1999,8 @@ TkMacOSXMakeStippleMap( * None. * * Side effects: - * A rectangle "width" pixels wide is drawn in "drawable", - * corresponding to the outer area of "tkwin". + * A rectangle "width" pixels wide is drawn in "drawable", corresponding + * to the outer area of "tkwin". * *---------------------------------------------------------------------- */ @@ -1926,8 +2029,8 @@ TkpDrawHighlightBorder ( * * TkpDrawFrame -- * - * This procedure draws the rectangular frame area. If the user - * has requested themeing, it draws with the background theme. + * This procedure draws the rectangular frame area. If the user has + * requested themeing, it draws with the background theme. * * Results: * None. diff --git a/macosx/tkMacOSXEmbed.c b/macosx/tkMacOSXEmbed.c index b23f33b..4f67f13 100644 --- a/macosx/tkMacOSXEmbed.c +++ b/macosx/tkMacOSXEmbed.c @@ -334,15 +334,15 @@ TkpUseWindow( macWin->flags |= TK_EMBEDDED; macWin->xOff = parent->winPtr->privatePtr->xOff + - parent->winPtr->changes.border_width + - winPtr->changes.x; + parent->winPtr->changes.border_width + + winPtr->changes.x; macWin->yOff = parent->winPtr->privatePtr->yOff + - parent->winPtr->changes.border_width + - winPtr->changes.y; + parent->winPtr->changes.border_width + + winPtr->changes.y; /* - * Finish filling up the container structure with the embedded - * window's information. + * Finish filling up the container structure with the embedded window's + * information. */ containerPtr->embedded = (Window) macWin; @@ -353,8 +353,8 @@ TkpUseWindow( * tkwin is eventually deleted. */ - Tk_CreateEventHandler(tkwin, StructureNotifyMask, EmbeddedEventProc, - winPtr); + Tk_CreateEventHandler(tkwin, StructureNotifyMask, + EmbeddedEventProc, winPtr); return TCL_OK; } @@ -588,16 +588,25 @@ TkpTestembedCmd( continue; } Tcl_DStringStartSublist(&dString); - /* Parent id */ + + /* + * Parent id + */ + if (containerPtr->parent == None) { Tcl_DStringAppendElement(&dString, ""); } else if (all) { - sprintf(buffer, "0x%" TCL_Z_MODIFIER "x", (size_t) containerPtr->parent); + sprintf(buffer, "0x%" TCL_Z_MODIFIER "x", + (size_t) containerPtr->parent); Tcl_DStringAppendElement(&dString, buffer); } else { Tcl_DStringAppendElement(&dString, "XXX"); } - /* Parent pathName */ + + /* + * Parent pathName + */ + if (containerPtr->parentPtr == NULL || parentInterp != interp) { Tcl_DStringAppendElement(&dString, ""); @@ -605,11 +614,17 @@ TkpTestembedCmd( Tcl_DStringAppendElement(&dString, containerPtr->parentPtr->pathName); } + /* * On X11 embedded is a wrapper, which does not exist on macOS. */ + Tcl_DStringAppendElement(&dString, ""); - /* Embedded window pathName */ + + /* + * Embedded window pathName + */ + if (containerPtr->embeddedPtr == NULL || embeddedInterp != interp) { Tcl_DStringAppendElement(&dString, ""); @@ -768,6 +783,7 @@ ContainerEventProc( /* * When the interpreter is being dismantled this can be nil. */ + return; } @@ -920,6 +936,7 @@ EmbedActivateProc( XEvent *eventPtr) /* ResizeRequest event. */ { Container *containerPtr = clientData; + if (containerPtr->embeddedPtr != NULL) { if (eventPtr->type == ActivateNotify) { TkGenerateActivateEvents(containerPtr->embeddedPtr,1); @@ -1024,11 +1041,10 @@ EmbedGeometryRequest( /* * Forward the requested size into our geometry management hierarchy via * the container window. We need to send a Configure event back to the - * embedded application if we decide not to honor its request; to make - * this happen, process all idle event handlers synchronously here (so - * that the geometry managers have had a chance to do whatever they want - * to do), and if the window's size didn't change then generate a - * configure event. + * embedded application if we decide not to honor its request; to make this + * happen, process all idle event handlers synchronously here (so that the + * geometry managers have had a chance to do whatever they want to do), and + * if the window's size didn't change then generate a configure event. */ Tk_GeometryRequest((Tk_Window) winPtr, width, height); @@ -1050,8 +1066,8 @@ EmbedGeometryRequest( * application of its current size and location. This procedure is called * when the embedded application made a geometry request that we did not * grant, so that the embedded application knows that its geometry didn't - * change after all. It is a response to ConfigureRequest events, which - * we do not currently synthesize on the Mac + * change after all. It is a response to ConfigureRequest events, which we + * do not currently synthesize on the Mac * * Results: * None. @@ -1111,8 +1127,8 @@ EmbedWindowDeleted( containerPtr->parentPtr->flags & TK_BOTH_HALVES) { XEvent event; - event.xany.serial = - LastKnownRequestProcessed(Tk_Display(containerPtr->parentPtr)); + event.xany.serial = LastKnownRequestProcessed( + Tk_Display(containerPtr->parentPtr)); event.xany.send_event = False; event.xany.display = Tk_Display(containerPtr->parentPtr); diff --git a/macosx/tkMacOSXFont.c b/macosx/tkMacOSXFont.c index deaab20..7f1cc72 100644 --- a/macosx/tkMacOSXFont.c +++ b/macosx/tkMacOSXFont.c @@ -1,8 +1,8 @@ /* * tkMacOSXFont.c -- * - * Contains the Macintosh implementation of the platform-independant - * font package interface. + * Contains the Macintosh implementation of the platform-independant font + * package interface. * * Copyright 2002-2004 Benjamin Riefenstahl, Benjamin.Riefenstahl@epost.de * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> @@ -32,9 +32,8 @@ */ typedef struct { - TkFont font; /* Stuff used by generic font package. Must - * be first in structure. */ - + TkFont font; /* Stuff used by generic font package. Must be + * first in structure. */ NSFont *nsFont; NSDictionary *nsAttributes; } MacFont; @@ -83,17 +82,21 @@ static int antialiasedTextEnabled = -1; static NSCharacterSet *whitespaceCharacterSet = nil; static NSCharacterSet *lineendingCharacterSet = nil; -static void GetTkFontAttributesForNSFont(NSFont *nsFont, - TkFontAttributes *faPtr); -static NSFont *FindNSFont(const char *familyName, NSFontTraitMask traits, - NSInteger weight, CGFloat size, int fallbackToDefault); -static void InitFont(NSFont *nsFont, const TkFontAttributes *reqFaPtr, - MacFont * fontPtr); -static int CreateNamedSystemFont(Tcl_Interp *interp, Tk_Window tkwin, - const char* name, TkFontAttributes *faPtr); -static void DrawCharsInContext(Display *display, Drawable drawable, GC gc, - Tk_Font tkfont, const char *source, int numBytes, int rangeStart, - int rangeLength, int x, int y, double angle); +static void GetTkFontAttributesForNSFont(NSFont *nsFont, + TkFontAttributes *faPtr); +static NSFont * FindNSFont(const char *familyName, + NSFontTraitMask traits, NSInteger weight, + CGFloat size, int fallbackToDefault); +static void InitFont(NSFont *nsFont, + const TkFontAttributes *reqFaPtr, + MacFont *fontPtr); +static int CreateNamedSystemFont(Tcl_Interp *interp, + Tk_Window tkwin, const char *name, + TkFontAttributes *faPtr); +static void DrawCharsInContext(Display *display, Drawable drawable, + GC gc, Tk_Font tkfont, const char *source, + int numBytes, int rangeStart, int rangeLength, + int x, int y, double angle); #pragma mark - #pragma mark Font Helpers: @@ -232,7 +235,7 @@ InitFont( int ascent, descent/*, dontAA*/; static const UniChar ch[] = {'.', 'W', ' ', 0xc4, 0xc1, 0xc2, 0xc3, 0xc7}; /* ., W, Space, Auml, Aacute, Acirc, Atilde, Ccedilla */ - #define nCh (sizeof(ch) / sizeof(UniChar)) +#define nCh (sizeof(ch) / sizeof(UniChar)) CGGlyph glyphs[nCh]; CGRect boundingRects[nCh]; @@ -244,7 +247,11 @@ InitFont( TkInitFontAttributes(faPtr); } fontPtr->nsFont = nsFont; - // some don't like antialiasing on fixed-width even if bigger than limit + + /* + * Some don't like antialiasing on fixed-width even if bigger than limit + */ + // dontAA = [nsFont isFixedPitch] && fontPtr->font.fa.size <= 10; if (antialiasedTextEnabled >= 0/* || dontAA*/) { renderingMode = (antialiasedTextEnabled == 0/* || dontAA*/) ? @@ -294,7 +301,7 @@ InitFont( NSLigatureAttributeName, [NSNumber numberWithDouble:kern], NSKernAttributeName, nil]; fontPtr->nsAttributes = [nsAttributes retain]; - #undef nCh +#undef nCh } /* @@ -358,10 +365,14 @@ TkpFontPkgInit( NSFont *nsFont; TkFontAttributes fa; NSMutableCharacterSet *cs; - /* Since we called before TkpInit, we need our own autorelease pool. */ + /* + * Since we called before TkpInit, we need our own autorelease pool. + */ NSAutoreleasePool *pool = [NSAutoreleasePool new]; - /* force this for now */ + /* + * Force this for now. + */ if (!mainPtr->winPtr->mainPtr) { mainPtr->winPtr->mainPtr = mainPtr; } @@ -413,17 +424,17 @@ TkpFontPkgInit( * Map a platform-specific native font name to a TkFont. * * Results: - * The return value is a pointer to a TkFont that represents the - * native font. If a native font by the given name could not be - * found, the return value is NULL. + * The return value is a pointer to a TkFont that represents the native + * font. If a native font by the given name could not be found, the return + * value is NULL. * - * Every call to this procedure returns a new TkFont structure, even - * if the name has already been seen before. The caller should call + * Every call to this procedure returns a new TkFont structure, even if + * the name has already been seen before. The caller should call * TkpDeleteFont() when the font is no longer needed. * - * The caller is responsible for initializing the memory associated - * with the generic TkFont when this function returns and releasing - * the contents of the generics TkFont before calling TkpDeleteFont(). + * The caller is responsible for initializing the memory associated with + * the generic TkFont when this function returns and releasing the + * contents of the generics TkFont before calling TkpDeleteFont(). * * Side effects: * None. @@ -449,8 +460,8 @@ TkpGetNativeFont( } else { return NULL; } - ctFont = CTFontCreateUIFontForLanguage(HIThemeGetUIFontType( - themeFontId), 0, NULL); + ctFont = CTFontCreateUIFontForLanguage( + HIThemeGetUIFontType(themeFontId), 0, NULL); if (ctFont) { fontPtr = ckalloc(sizeof(MacFont)); InitFont((NSFont*) ctFont, NULL, fontPtr); @@ -468,19 +479,18 @@ TkpGetNativeFont( * closest matching attributes. * * Results: - * The return value is a pointer to a TkFont that represents the font - * with the desired attributes. If a font with the desired attributes - * could not be constructed, some other font will be substituted - * automatically. + * The return value is a pointer to a TkFont that represents the font with + * the desired attributes. If a font with the desired attributes could not + * be constructed, some other font will be substituted automatically. * - * Every call to this procedure returns a new TkFont structure, even - * if the specified attributes have already been seen before. The - * caller should call TkpDeleteFont() to free the platform- specific - * data when the font is no longer needed. + * Every call to this procedure returns a new TkFont structure, even if + * the specified attributes have already been seen before. The caller + * should call TkpDeleteFont() to free the platform- specific data when + * the font is no longer needed. * - * The caller is responsible for initializing the memory associated - * with the generic TkFont when this function returns and releasing - * the contents of the generic TkFont before calling TkpDeleteFont(). + * The caller is responsible for initializing the memory associated with + * the generic TkFont when this function returns and releasing the + * contents of the generic TkFont before calling TkpDeleteFont(). * * Side effects: * None. @@ -492,16 +502,16 @@ TkFont * TkpGetFontFromAttributes( TkFont *tkFontPtr, /* If non-NULL, store the information in this * existing TkFont structure, rather than - * allocating a new structure to hold the - * font; the existing contents of the font - * will be released. If NULL, a new TkFont - * structure is allocated. */ + * allocating a new structure to hold the font; + * the existing contents of the font will be + * released. If NULL, a new TkFont structure is + * allocated. */ Tk_Window tkwin, /* For display where font will be used. */ const TkFontAttributes *faPtr) /* Set of attributes to match. */ { MacFont *fontPtr; - int points = (int)(TkFontGetPoints(tkwin, faPtr->size) + 0.5); + int points = (int) (TkFontGetPoints(tkwin, faPtr->size) + 0.5); NSFontTraitMask traits = GetNSFontTraitsFromTkFontAttributes(faPtr); NSInteger weight = (faPtr->weight == TK_FW_BOLD ? 9 : 5); NSFont *nsFont; @@ -538,9 +548,9 @@ TkpGetFontFromAttributes( * TkpDeleteFont -- * * Called to release a font allocated by TkpGetNativeFont() or - * TkpGetFontFromAttributes(). The caller should have already - * released the fields of the TkFont that are used exclusively by the - * generic TkFont code. + * TkpGetFontFromAttributes(). The caller should have already released the + * fields of the TkFont that are used exclusively by the generic TkFont + * code. * * Results: * TkFont is deallocated. @@ -567,8 +577,8 @@ TkpDeleteFont( * * TkpGetFontFamilies -- * - * Return information about the font families that are available on - * the display of the given window. + * Return information about the font families that are available on the + * display of the given window. * * Results: * Modifies interp's result object to hold a list of all the available @@ -600,12 +610,12 @@ TkpGetFontFamilies( * * TkpGetSubFonts -- * - * A function used by the testing package for querying the actual - * screen fonts that make up a font object. + * A function used by the testing package for querying the actual screen + * fonts that make up a font object. * * Results: - * Modifies interp's result object to hold a list containing the names - * of the screen fonts that make up the given font object. + * Modifies interp's result object to hold a list containing the names of + * the screen fonts that make up the given font object. * * Side effects: * None. @@ -642,8 +652,8 @@ TkpGetSubFonts( * * TkpGetFontAttrsForChar -- * - * Retrieve the font attributes of the actual font used to render a - * given character. + * Retrieve the font attributes of the actual font used to render a given + * character. * * Results: * None. @@ -745,10 +755,9 @@ Tk_MeasureChars( * all the characters on the line for context. * * Results: - * The return value is the number of bytes from source that - * fit into the span that extends from 0 to maxLength. *lengthPtr is - * filled with the x-coordinate of the right edge of the last - * character that did fit. + * The return value is the number of bytes from source that fit into the + * span that extends from 0 to maxLength. *lengthPtr is filled with the + * x-coordinate of the right edge of the last character that did fit. * * Side effects: * None. @@ -774,11 +783,11 @@ TkpMeasureCharsInContext( * TK_PARTIAL_OK means include the last char * which only partially fits on this line. * TK_WHOLE_WORDS means stop on a word - * boundary, if possible. TK_AT_LEAST_ONE - * means return at least one character even - * if no characters fit. If TK_WHOLE_WORDS - * and TK_AT_LEAST_ONE are set and the first - * word doesn't fit, we return at least one + * boundary, if possible. TK_AT_LEAST_ONE means + * return at least one character even if no + * characters fit. If TK_WHOLE_WORDS and + * TK_AT_LEAST_ONE are set and the first word + * doesn't fit, we return at least one * character or whatever characters fit into * maxLength. TK_ISOLATE_END means that the * last character should not be considered in @@ -864,14 +873,13 @@ TkpMeasureCharsInContext( } /* - * If there is no line breakpoint in the source string between - * its start and the index position that fits in maxWidth, then + * If there is no line breakpoint in the source string between its + * start and the index position that fits in maxWidth, then * CTTypesetterSuggestLineBreak() returns that very last index. - * However if the TK_WHOLE_WORDS flag is set, we want to break - * at a word boundary. In this situation, unless TK_AT_LEAST_ONE - * is set, we must report that zero chars actually fit (in other - * words the smallest word of the source string is still larger - * than maxWidth). + * However if the TK_WHOLE_WORDS flag is set, we want to break at a + * word boundary. In this situation, unless TK_AT_LEAST_ONE is set, we + * must report that zero chars actually fit (in other words the + * smallest word of the source string is still larger than maxWidth). */ if ((index >= start) && (index < len) && @@ -903,9 +911,12 @@ TkpMeasureCharsInContext( CFRelease(line); } - /* The call to CTTypesetterSuggestClusterBreak above will always - return at least one character regardless of whether it exceeded - it or not. Clean that up now. */ + /* + * The call to CTTypesetterSuggestClusterBreak above will always return + * at least one character regardless of whether it exceeded it or not. + * Clean that up now. + */ + while (width > maxWidth && !(flags & TK_PARTIAL_OK) && index > start+(flags & TK_AT_LEAST_ONE)) { range.length = --index; @@ -946,7 +957,7 @@ done: * actual implementation in TkpDrawCharsInContext(). * * Results: - * None. + * None. * * Side effects: * Information gets drawn on the screen. @@ -964,8 +975,8 @@ Tk_DrawChars( const char *source, /* UTF-8 string to be displayed. Need not be * '\0' terminated. All Tk meta-characters * (tabs, control characters, and newlines) - * should be stripped out of the string that - * is passed to this function. If they are not + * should be stripped out of the string that is + * passed to this function. If they are not * stripped out, they will be displayed as * regular printing characters. */ int numBytes, /* Number of bytes in string. */ @@ -986,8 +997,8 @@ TkDrawAngledChars( const char *source, /* UTF-8 string to be displayed. Need not be * '\0' terminated. All Tk meta-characters * (tabs, control characters, and newlines) - * should be stripped out of the string that - * is passed to this function. If they are not + * should be stripped out of the string that is + * passed to this function. If they are not * stripped out, they will be displayed as * regular printing characters. */ int numBytes, /* Number of bytes in string. */ @@ -1029,8 +1040,8 @@ TkpDrawCharsInContext( const char * source, /* UTF-8 string to be displayed. Need not be * '\0' terminated. All Tk meta-characters * (tabs, control characters, and newlines) - * should be stripped out of the string that - * is passed to this function. If they are not + * should be stripped out of the string that is + * passed to this function. If they are not * stripped out, they will be displayed as * regular printing characters. */ int numBytes, /* Number of bytes in string. */ @@ -1054,8 +1065,8 @@ DrawCharsInContext( const char * source, /* UTF-8 string to be displayed. Need not be * '\0' terminated. All Tk meta-characters * (tabs, control characters, and newlines) - * should be stripped out of the string that - * is passed to this function. If they are not + * should be stripped out of the string that is + * passed to this function. If they are not * stripped out, they will be displayed as * regular printing characters. */ int numBytes, /* Number of bytes in string. */ @@ -1091,6 +1102,7 @@ DrawCharsInContext( if (!string) { return; } + context = drawingContext.context; fg = TkMacOSXCreateCGColor(gc, gc->foreground); attributes = [fontPtr->nsAttributes mutableCopy]; @@ -1119,6 +1131,7 @@ DrawCharsInContext( len = Tcl_NumUtfChars(source, rangeStart + rangeLength); if (start > 0) { CGRect clipRect = CGRectInfinite, startBounds; + line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, start)); startBounds = CTLineGetImageBounds(line, context); CFRelease(line); @@ -1241,9 +1254,10 @@ TkMacOSXFontDescriptionForNSFontAndNSFontAttributes( NSUnderlineStyleAttributeName]; id strikethrough = [nsAttributes objectForKey: NSStrikethroughStyleAttributeName]; + objv[i++] = Tcl_NewStringObj(familyName, -1); objv[i++] = Tcl_NewWideIntObj([nsFont pointSize]); -#define S(s) Tcl_NewStringObj(STRINGIFY(s),(int)(sizeof(STRINGIFY(s))-1)) +#define S(s) Tcl_NewStringObj(STRINGIFY(s), (int)(sizeof(STRINGIFY(s))-1)) objv[i++] = (traits & NSBoldFontMask) ? S(bold) : S(normal); objv[i++] = (traits & NSItalicFontMask) ? S(italic) : S(roman); if ([underline respondsToSelector:@selector(intValue)] && @@ -1267,8 +1281,9 @@ TkMacOSXFontDescriptionForNSFontAndNSFontAttributes( * TkMacOSXUseAntialiasedText -- * * Enables or disables application-wide use of antialiased text (where - * available). Sets up a linked Tcl global variable to allow - * disabling of antialiased text from tcl. + * available). Sets up a linked Tcl global variable to allow disabling of + * antialiased text from Tcl. + * * The possible values for this variable are: * * -1 - Use system default as configurable in "System Prefs" -> "General". diff --git a/macosx/tkMacOSXHLEvents.c b/macosx/tkMacOSXHLEvents.c index 9b874a5..8f04e45 100644 --- a/macosx/tkMacOSXHLEvents.c +++ b/macosx/tkMacOSXHLEvents.c @@ -33,12 +33,13 @@ typedef struct KillEvent { * Static functions used only in this file. */ -static void tkMacOSXProcessFiles(NSAppleEventDescriptor* event, - NSAppleEventDescriptor* replyEvent, - Tcl_Interp *interp, - const char* procedure); -static int MissedAnyParameters(const AppleEvent *theEvent); -static int ReallyKillMe(Tcl_Event *eventPtr, int flags); +static void tkMacOSXProcessFiles(NSAppleEventDescriptor *event, + NSAppleEventDescriptor *replyEvent, + Tcl_Interp *interp, const char* procedure); +static int MissedAnyParameters(const AppleEvent *theEvent); +static int ReallyKillMe(Tcl_Event *eventPtr, int flags); +static void RunSimpleTclCommand(Tcl_Interp *interp, + const char *command); #pragma mark TKApplication(TKHLEvents) @@ -78,55 +79,34 @@ static int ReallyKillMe(Tcl_Event *eventPtr, int flags); - (void) handleOpenApplicationEvent: (NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { - Tcl_Interp *interp = _eventInterp; - - if (interp && - Tcl_FindCommand(_eventInterp, "::tk::mac::OpenApplication", NULL, 0)){ - int code = Tcl_EvalEx(_eventInterp, "::tk::mac::OpenApplication", - -1, TCL_EVAL_GLOBAL); - if (code != TCL_OK) { - Tcl_BackgroundException(_eventInterp, code); - } - } + RunSimpleTclCommand(_eventInterp, "::tk::mac::OpenApplication"); } - (void) handleReopenApplicationEvent: (NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { [NSApp activateIgnoringOtherApps: YES]; - if (_eventInterp && Tcl_FindCommand(_eventInterp, - "::tk::mac::ReopenApplication", NULL, 0)) { - int code = Tcl_EvalEx(_eventInterp, "::tk::mac::ReopenApplication", - -1, TCL_EVAL_GLOBAL); - if (code != TCL_OK){ - Tcl_BackgroundException(_eventInterp, code); - } - } + RunSimpleTclCommand(_eventInterp, "::tk::mac::ReopenApplication"); } - (void) handleShowPreferencesEvent: (NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { - if (_eventInterp && - Tcl_FindCommand(_eventInterp, "::tk::mac::ShowPreferences", NULL, 0)){ - int code = Tcl_EvalEx(_eventInterp, "::tk::mac::ShowPreferences", - -1, TCL_EVAL_GLOBAL); - if (code != TCL_OK) { - Tcl_BackgroundException(_eventInterp, code); - } - } + RunSimpleTclCommand(_eventInterp, "::tk::mac::ShowPreferences"); } - (void) handleOpenDocumentsEvent: (NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { - tkMacOSXProcessFiles(event, replyEvent, _eventInterp, "::tk::mac::OpenDocument"); + tkMacOSXProcessFiles(event, replyEvent, _eventInterp, + "::tk::mac::OpenDocument"); } - (void) handlePrintDocumentsEvent: (NSAppleEventDescriptor *)event withReplyEvent: (NSAppleEventDescriptor *)replyEvent { - tkMacOSXProcessFiles(event, replyEvent, _eventInterp, "::tk::mac::PrintDocument"); + tkMacOSXProcessFiles(event, replyEvent, _eventInterp, + "::tk::mac::PrintDocument"); } - (void) handleDoScriptEvent: (NSAppleEventDescriptor *)event @@ -152,64 +132,73 @@ static int ReallyKillMe(Tcl_Event *eventPtr, int flags); } err = AEGetParamPtr(theDesc, keyDirectObject, typeWildCard, &initialType, - NULL, 0, NULL); + NULL, 0, NULL); if (err != noErr) { sprintf(errString, "AEDoScriptHandler: GetParamDesc error %d", (int)err); - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, - errString, strlen(errString)); + AEPutParamPtr((AppleEvent *) [replyEvent aeDesc], keyErrorString, + typeChar, errString, strlen(errString)); return; } - if (MissedAnyParameters((AppleEvent*)theDesc)) { + if (MissedAnyParameters((AppleEvent *) theDesc)) { sprintf(errString, "AEDoScriptHandler: extra parameters"); - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, - errString, strlen(errString)); + AEPutParamPtr((AppleEvent *) [replyEvent aeDesc], keyErrorString, + typeChar, errString, strlen(errString)); return; } + Tcl_Preserve(_eventInterp); if (initialType == typeFileURL || initialType == typeAlias) { /* * The descriptor can be coerced to a file url. Source the file, or * pass the path as a string argument to ::tk::mac::DoScriptFile if * that procedure exists. */ + err = AEGetParamPtr(theDesc, keyDirectObject, typeFileURL, &type, - (Ptr) URLBuffer, URL_MAX_LENGTH, &actual); + (Ptr) URLBuffer, URL_MAX_LENGTH, &actual); if (err == noErr && actual > 0){ URLBuffer[actual] = '\0'; NSString *urlString = [NSString stringWithUTF8String:(char*)URLBuffer]; NSURL *fileURL = [NSURL URLWithString:urlString]; Tcl_DString command; + Tcl_DStringInit(&command); - if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptFile", NULL, 0)){ + if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptFile", NULL, 0)) { Tcl_DStringAppend(&command, "::tk::mac::DoScriptFile", -1); } else { Tcl_DStringAppend(&command, "source", -1); } Tcl_DStringAppendElement(&command, [[fileURL path] UTF8String]); tclErr = Tcl_EvalEx(_eventInterp, Tcl_DStringValue(&command), - Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); + Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); } - } else if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, &type, - NULL, 0, &actual)) { + } else if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, + &type, NULL, 0, &actual)) { if (actual > 0) { /* * The descriptor can be coerced to UTF8 text. Evaluate as Tcl, or * or pass the text as a string argument to ::tk::mac::DoScriptText * if that procedure exists. */ + char *data = ckalloc(actual + 1); - if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, &type, - data, actual, NULL)) { - if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptText", NULL, 0)){ + + if (noErr == AEGetParamPtr(theDesc, keyDirectObject, typeUTF8Text, + &type, data, actual, NULL)) { + if (Tcl_FindCommand(_eventInterp, "::tk::mac::DoScriptText", + NULL, 0)) { Tcl_DString command; + Tcl_DStringInit(&command); Tcl_DStringAppend(&command, "::tk::mac::DoScriptText", -1); Tcl_DStringAppendElement(&command, data); - tclErr = Tcl_EvalEx(_eventInterp, Tcl_DStringValue(&command), - Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); + tclErr = Tcl_EvalEx(_eventInterp, + Tcl_DStringValue(&command), + Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); } else { - tclErr = Tcl_EvalEx(_eventInterp, data, actual, TCL_EVAL_GLOBAL); + tclErr = Tcl_EvalEx(_eventInterp, data, actual, + TCL_EVAL_GLOBAL); } } ckfree(data); @@ -224,32 +213,53 @@ static int ReallyKillMe(Tcl_Event *eventPtr, int flags); typeString[4] = '\0'; sprintf(errString, "AEDoScriptHandler: invalid script type '%s', " "must be coercable to 'furl' or 'utf8'", typeString); - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, errString, - strlen(errString)); + AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, + typeChar, errString, strlen(errString)); } + /* * If we ran some Tcl code, put the result in the reply. */ + if (tclErr >= 0) { int reslen; const char *result = - Tcl_GetStringFromObj(Tcl_GetObjResult(_eventInterp), &reslen); + Tcl_GetStringFromObj(Tcl_GetObjResult(_eventInterp), &reslen); + if (tclErr == TCL_OK) { - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyDirectObject, typeChar, - result, reslen); + AEPutParamPtr((AppleEvent *) [replyEvent aeDesc], keyDirectObject, + typeChar, result, reslen); } else { - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorString, typeChar, - result, reslen); - AEPutParamPtr((AppleEvent*)[replyEvent aeDesc], keyErrorNumber, typeSInt32, - (Ptr) &tclErr,sizeof(int)); + AEPutParamPtr((AppleEvent *) [replyEvent aeDesc], keyErrorString, + typeChar, result, reslen); + AEPutParamPtr((AppleEvent *) [replyEvent aeDesc], keyErrorNumber, + typeSInt32, (Ptr) &tclErr, sizeof(int)); } } + Tcl_Release(_eventInterp); return; } @end #pragma mark - + +static void +RunSimpleTclCommand( + Tcl_Interp *interp, + const char *command) +{ + int code; + if (interp && Tcl_FindCommand(interp, command, NULL, 0)) { + Tcl_Preserve(interp); + code = Tcl_EvalEx(interp, command, -1, TCL_EVAL_GLOBAL); + if (code != TCL_OK) { + Tcl_BackgroundException(interp, code); + } + Tcl_Release(interp); + } +} + /* *---------------------------------------------------------------------- * @@ -287,7 +297,8 @@ tkMacOSXProcessFiles( int code; /* - * Do nothing if we don't have an interpreter or the procedure doesn't exist. + * Do nothing if we don't have an interpreter or the procedure doesn't + * exist. */ if (!interp || !Tcl_FindCommand(interp, procedure, NULL, 0)) { @@ -353,12 +364,14 @@ tkMacOSXProcessFiles( * Handle the event by evaluating the Tcl expression we constructed. */ + Tcl_Preserve(interp); code = Tcl_EvalEx(interp, Tcl_DStringValue(&command), Tcl_DStringLength(&command), TCL_EVAL_GLOBAL); if (code != TCL_OK) { Tcl_BackgroundException(interp, code); } Tcl_DStringFree(&command); + Tcl_Release(interp); } /* @@ -382,7 +395,8 @@ void TkMacOSXInitAppleEvents( Tcl_Interp *interp) /* not used */ { - NSAppleEventManager *aeManager = [NSAppleEventManager sharedAppleEventManager]; + NSAppleEventManager *aeManager = + [NSAppleEventManager sharedAppleEventManager]; static Boolean initialized = FALSE; if (!initialized) { @@ -488,8 +502,12 @@ ReallyKillMe( int flags) { Tcl_Interp *interp = ((KillEvent *) eventPtr)->interp; + + Tcl_Preserve(interp); + int quit = Tcl_FindCommand(interp, "::tk::mac::Quit", NULL, 0)!=NULL; - int code = Tcl_EvalEx(interp, quit ? "::tk::mac::Quit" : "exit", -1, TCL_EVAL_GLOBAL); + int code = Tcl_EvalEx(interp, quit ? "::tk::mac::Quit" : "exit", -1, + TCL_EVAL_GLOBAL); if (code != TCL_OK) { /* @@ -498,6 +516,7 @@ ReallyKillMe( Tcl_BackgroundException(interp, code); } + Tcl_Release(interp); return 1; } @@ -531,7 +550,6 @@ MissedAnyParameters( return (err != errAEDescNotFound); } - /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXImage.c b/macosx/tkMacOSXImage.c index 38f4a70..36f1fc6 100644 --- a/macosx/tkMacOSXImage.c +++ b/macosx/tkMacOSXImage.c @@ -23,7 +23,7 @@ _XInitImageFuncPtrs( { return 0; } - + /* *---------------------------------------------------------------------- * @@ -81,14 +81,15 @@ TkMacOSXCreateCGImageWithXImage( data = memcpy(ckalloc(len), image->data + image->xoffset, len); } if (data) { - provider = CGDataProviderCreateWithData(data, data, len, releaseData); + provider = CGDataProviderCreateWithData(data, data, len, + releaseData); } if (provider) { - img = CGImageMaskCreate(image->width, image->height, bitsPerComponent, - bitsPerPixel, image->bytes_per_line, provider, decode, 0); + img = CGImageMaskCreate(image->width, image->height, + bitsPerComponent, bitsPerPixel, image->bytes_per_line, + provider, decode, 0); } - } else if (image->format == ZPixmap && image->bits_per_pixel == 32) { - + } else if ((image->format == ZPixmap) && (image->bits_per_pixel == 32)) { /* * Color image */ @@ -96,7 +97,6 @@ TkMacOSXCreateCGImageWithXImage( CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); if (image->width == 0 && image->height == 0) { - /* * CGCreateImage complains on early macOS releases. */ @@ -106,11 +106,12 @@ TkMacOSXCreateCGImageWithXImage( bitsPerComponent = 8; bitsPerPixel = 32; bitmapInfo = (image->byte_order == MSBFirst ? - kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big); + kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big); bitmapInfo |= kCGImageAlphaLast; data = memcpy(ckalloc(len), image->data + image->xoffset, len); if (data) { - provider = CGDataProviderCreateWithData(data, data, len, releaseData); + provider = CGDataProviderCreateWithData(data, data, len, + releaseData); } if (provider) { img = CGImageCreate(image->width, image->height, bitsPerComponent, @@ -127,7 +128,6 @@ TkMacOSXCreateCGImageWithXImage( return img; } - /* *---------------------------------------------------------------------- * @@ -179,15 +179,14 @@ XGetImage( if (win && has_retina == unknown) { #ifdef __clang__ - has_retina = [win respondsToSelector:@selector(backingScaleFactor)]? - yes : no; + has_retina = [win respondsToSelector:@selector(backingScaleFactor)] ? + yes : no; #else has_retina = no; #endif } if (has_retina == yes) { - /* * We only allow scale factors 1 or 2, as Apple currently does. */ @@ -205,7 +204,7 @@ XGetImage( } bitmap_rep = TkMacOSXBitmapRepFromDrawableRect(drawable, - x, y, width, height); + x, y, width, height); if (!bitmap_rep) { TkMacOSXDbgMsg("XGetImage: Failed to construct NSBitmapRep"); return NULL; @@ -214,12 +213,12 @@ XGetImage( size = [bitmap_rep bytesPerPlane]; bytes_per_row = [bitmap_rep bytesPerRow]; bitmap = ckalloc(size); - if (!bitmap || - (bitmap_fmt != 0 && bitmap_fmt != 1) || - [bitmap_rep samplesPerPixel] != 4 || - [bitmap_rep isPlanar] != 0 || - bytes_per_row < 4 * scaled_width || - size != bytes_per_row*scaled_height ) { + if (!bitmap + || (bitmap_fmt != 0 && bitmap_fmt != 1) + || [bitmap_rep samplesPerPixel] != 4 + || [bitmap_rep isPlanar] != 0 + || bytes_per_row < 4 * scaled_width + || size != bytes_per_row * scaled_height) { TkMacOSXDbgMsg("XGetImage: Unrecognized bitmap format"); CFRelease(bitmap_rep); return NULL; @@ -228,8 +227,8 @@ XGetImage( CFRelease(bitmap_rep); /* - * When Apple extracts a bitmap from an NSView, it may be in - * either BGRA or ABGR format. For an XImage we need RGBA. + * When Apple extracts a bitmap from an NSView, it may be in either + * BGRA or ABGR format. For an XImage we need RGBA. */ struct pixel_fmt pixel = bitmap_fmt == 0 ? bgra : abgr; @@ -248,16 +247,16 @@ XGetImage( } } imagePtr = XCreateImage(display, NULL, depth, format, offset, - (char*)bitmap, scaled_width, scaled_height, - bitmap_pad, bytes_per_row); + (char*) bitmap, scaled_width, scaled_height, + bitmap_pad, bytes_per_row); if (scalefactor == 2) { imagePtr->pixelpower = 1; } } else { /* - * There are some calls to XGetImage in the generic Tk - * code which pass an XYPixmap rather than a ZPixmap. - * XYPixmaps should be handled here. + * There are some calls to XGetImage in the generic Tk code which pass + * an XYPixmap rather than a ZPixmap. XYPixmaps should be handled + * here. */ TkMacOSXDbgMsg("XGetImage does not handle XYPixmaps at the moment."); } @@ -323,40 +322,40 @@ ImageGetPixel( + (((image->xoffset + x) * image->bits_per_pixel) / NBBY); switch (image->bits_per_pixel) { - case 32: { - r = (*((unsigned int*) srcPtr) >> 16) & 0xff; - g = (*((unsigned int*) srcPtr) >> 8) & 0xff; - b = (*((unsigned int*) srcPtr) ) & 0xff; - /*if (image->byte_order == LSBFirst) { - r = srcPtr[2]; g = srcPtr[1]; b = srcPtr[0]; - } else { - r = srcPtr[1]; g = srcPtr[2]; b = srcPtr[3]; - }*/ - break; - } - case 16: - r = (*((unsigned short*) srcPtr) >> 7) & 0xf8; - g = (*((unsigned short*) srcPtr) >> 2) & 0xf8; - b = (*((unsigned short*) srcPtr) << 3) & 0xf8; - break; - case 8: - r = (*srcPtr << 2) & 0xc0; - g = (*srcPtr << 4) & 0xc0; - b = (*srcPtr << 6) & 0xc0; - r |= r >> 2 | r >> 4 | r >> 6; - g |= g >> 2 | g >> 4 | g >> 6; - b |= b >> 2 | b >> 4 | b >> 6; - break; - case 4: { - unsigned char c = (x % 2) ? *srcPtr : (*srcPtr >> 4); - r = (c & 0x04) ? 0xff : 0; - g = (c & 0x02) ? 0xff : 0; - b = (c & 0x01) ? 0xff : 0; - break; - } - case 1: - r = g = b = ((*srcPtr) & (0x80 >> (x % 8))) ? 0xff : 0; - break; + case 32: + r = (*((unsigned int*) srcPtr) >> 16) & 0xff; + g = (*((unsigned int*) srcPtr) >> 8) & 0xff; + b = (*((unsigned int*) srcPtr) ) & 0xff; + /*if (image->byte_order == LSBFirst) { + r = srcPtr[2]; g = srcPtr[1]; b = srcPtr[0]; + } else { + r = srcPtr[1]; g = srcPtr[2]; b = srcPtr[3]; + }*/ + break; + case 16: + r = (*((unsigned short*) srcPtr) >> 7) & 0xf8; + g = (*((unsigned short*) srcPtr) >> 2) & 0xf8; + b = (*((unsigned short*) srcPtr) << 3) & 0xf8; + break; + case 8: + r = (*srcPtr << 2) & 0xc0; + g = (*srcPtr << 4) & 0xc0; + b = (*srcPtr << 6) & 0xc0; + r |= r >> 2 | r >> 4 | r >> 6; + g |= g >> 2 | g >> 4 | g >> 6; + b |= b >> 2 | b >> 4 | b >> 6; + break; + case 4: { + unsigned char c = (x % 2) ? *srcPtr : (*srcPtr >> 4); + + r = (c & 0x04) ? 0xff : 0; + g = (c & 0x02) ? 0xff : 0; + b = (c & 0x01) ? 0xff : 0; + break; + } + case 1: + r = g = b = ((*srcPtr) & (0x80 >> (x % 8))) ? 0xff : 0; + break; } } return (PIXEL_MAGIC << 24) | (r << 16) | (g << 8) | b; @@ -389,6 +388,7 @@ ImagePutPixel( unsigned char *dstPtr = ((unsigned char*) image->data) + (y * image->bytes_per_line) + (((image->xoffset + x) * image->bits_per_pixel) / NBBY); + if (image->bits_per_pixel == 32) { *((unsigned int*) dstPtr) = pixel; } else { @@ -420,7 +420,7 @@ ImagePutPixel( } return 0; } - + /* *---------------------------------------------------------------------- * @@ -451,6 +451,7 @@ XCreateImage( int bytes_per_line) { XImage *ximage; + display->request++; ximage = ckalloc(sizeof(XImage)); @@ -461,9 +462,12 @@ XCreateImage( ximage->format = format; ximage->data = data; ximage->obdata = NULL; - /* The default pixelpower is 0. This must be explicitly set to 1 in the + + /* + * The default pixelpower is 0. This must be explicitly set to 1 in the * case of an XImage extracted from a Retina display. */ + ximage->pixelpower = 0; if (format == ZPixmap) { @@ -476,7 +480,10 @@ XCreateImage( if (bitmap_pad) { ximage->bitmap_pad = bitmap_pad; } else { - /* Use 16 byte alignment for best Quartz perfomance */ + /* + * Use 16 byte alignment for best Quartz perfomance. + */ + ximage->bitmap_pad = 128; } if (bytes_per_line) { @@ -511,9 +518,9 @@ XCreateImage( * * TkPutImage -- * - * Copies a rectangular subimage of an XImage into a drawable. - * Currently this is only called by TkImgPhotoDisplay, using - * a Window as the drawable. + * Copies a rectangular subimage of an XImage into a drawable. Currently + * this is only called by TkImgPhotoDisplay, using a Window as the + * drawable. * * Results: * None. @@ -559,13 +566,15 @@ TkPutImage( } if (img) { - /* If the XImage has big pixels, the source is rescaled to reflect + /* + * If the XImage has big pixels, the source is rescaled to reflect * the actual pixel dimensions. This is not currently used, but * could arise if the image were copied from a retina monitor and * redrawn on an ordinary monitor. */ int pp = image->pixelpower; + bounds = CGRectMake(0, 0, image->width, image->height); srcRect = CGRectMake(src_x<<pp, src_y<<pp, width<<pp, height<<pp); dstRect = CGRectMake(dest_x, dest_y, width, height); diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c index 3c02d92..02715f2 100644 --- a/macosx/tkMacOSXInit.c +++ b/macosx/tkMacOSXInit.c @@ -45,7 +45,7 @@ static char scriptPath[PATH_MAX + 1] = ""; @implementation TKApplication(TKInit) - (void) _resetAutoreleasePool { - if([self poolLock] == 0) { + if ([self poolLock] == 0) { [_mainPool drain]; _mainPool = [NSAutoreleasePool new]; } else { @@ -101,7 +101,6 @@ static char scriptPath[PATH_MAX + 1] = ""; */ _defaultMainMenu = nil; [self _setupMenus]; - /* * Initialize event processing. */ @@ -117,18 +116,17 @@ static char scriptPath[PATH_MAX + 1] = ""; -(void)applicationDidFinishLaunching:(NSNotification *)notification { /* - * It is not safe to force activation of the NSApp until this - * method is called. Activating too early can cause the menu - * bar to be unresponsive. + * It is not safe to force activation of the NSApp until this method is + * called. Activating too early can cause the menu bar to be unresponsive. */ [NSApp activateIgnoringOtherApps: YES]; /* - * Process events to ensure that the root window is fully - * initialized. See ticket 56a1823c73. + * Process events to ensure that the root window is fully initialized. See + * ticket 56a1823c73. */ - + [NSApp _lockAutoreleasePool]; while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS| TCL_DONT_WAIT)) {} [NSApp _unlockAutoreleasePool]; @@ -151,7 +149,7 @@ static char scriptPath[PATH_MAX + 1] = ""; * Record the OS version we are running on. */ int minorVersion; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 Gestalt(gestaltSystemVersionMinor, (SInt32*)&minorVersion); #else NSOperatingSystemVersion systemVersion; @@ -310,8 +308,8 @@ TkpInit( } /* - * Instantiate our NSApplication object. This needs to be - * done before we check whether to open a console window. + * Instantiate our NSApplication object. This needs to be done before + * we check whether to open a console window. */ NSAutoreleasePool *pool = [NSAutoreleasePool new]; @@ -342,8 +340,8 @@ TkpInit( Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDERR)); /* - * Only show the console if we don't have a startup script - * and tcl_interactive hasn't been set already. + * Only show the console if we don't have a startup script and + * tcl_interactive hasn't been set already. */ if (Tcl_GetStartupScript(NULL) == NULL) { diff --git a/macosx/tkMacOSXInt.h b/macosx/tkMacOSXInt.h index d942286..22d7d2c 100644 --- a/macosx/tkMacOSXInt.h +++ b/macosx/tkMacOSXInt.h @@ -71,6 +71,7 @@ struct TkWindowPrivate { * gone. */ struct TkWindowPrivate *toplevel; /* Pointer to the toplevel datastruct. */ + CGFloat fillRGBA[4]; /* Background used by the ttk FillElement */ int flags; /* Various state see defines below. */ }; typedef struct TkWindowPrivate MacDrawable; @@ -86,6 +87,7 @@ typedef struct TkWindowPrivate MacDrawable; #define TK_IS_PIXMAP 0x10 #define TK_IS_BW_PIXMAP 0x20 #define TK_DO_NOT_DRAW 0x40 +#define TTK_HAS_CONTRASTING_BG 0x80 /* * I am reserving TK_EMBEDDED = 0x100 in the MacDrawable flags @@ -201,6 +203,7 @@ MODULE_SCOPE void TkpShiftButton(NSButton *button, NSPoint delta); MODULE_SCOPE Bool TkpAppIsDrawing(void); MODULE_SCOPE void TkpDisplayWindow(Tk_Window tkwin); MODULE_SCOPE Bool TkTestAppIsDrawing(void); +MODULE_SCOPE Bool TkMacOSXInDarkMode(Tk_Window tkwin); /* * Include the stubbed internal platform-specific API. @@ -209,3 +212,12 @@ MODULE_SCOPE Bool TkTestAppIsDrawing(void); #include "tkIntPlatDecls.h" #endif /* _TKMACINT */ + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index 543e7ab..8691ce2 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -27,16 +27,16 @@ static Tk_Window keyboardGrabWinPtr = NULL; /* Current keyboard grab window. */ static NSWindow *keyboardGrabNSWindow = nil; - /* NSWindow for the current keyboard grab window. */ + /* NSWindow for the current keyboard grab + * window. */ static NSModalSession modalSession = nil; - static BOOL processingCompose = NO; static BOOL finishedCompose = NO; - static int caret_x = 0, caret_y = 0, caret_height = 0; -static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state); -static unsigned isFunctionKey(unsigned int code); +static void setupXEvent(XEvent *xEvent, NSWindow *w, + unsigned int state); +static unsigned isFunctionKey(unsigned int code); unsigned short releaseCode; @@ -50,22 +50,21 @@ unsigned short releaseCode; #ifdef TK_MAC_DEBUG_EVENTS TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, theEvent); #endif - NSWindow* w; - NSEventType type = [theEvent type]; + NSWindow *w; + NSEventType type = [theEvent type]; NSUInteger modifiers = ([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask); - NSUInteger len = 0; - BOOL repeat = NO; - unsigned short keyCode = [theEvent keyCode]; - NSString *characters = nil, *charactersIgnoringModifiers = nil; + NSUInteger len = 0; + BOOL repeat = NO; + unsigned short keyCode = [theEvent keyCode]; + NSString *characters = nil, *charactersIgnoringModifiers = nil; static NSUInteger savedModifiers = 0; static NSMutableArray *nsEvArray; - if (nsEvArray == nil) - { + if (nsEvArray == nil) { nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1]; processingCompose = NO; - } + } w = [theEvent window]; TkWindow *winPtr = TkMacOSXGetTkWindow(w); @@ -109,37 +108,39 @@ unsigned short releaseCode; return theEvent; /* Unrecognized key event. */ } - /* Create an Xevent to add to the Tk queue. */ + /* + * Create an Xevent to add to the Tk queue. + */ + if (!processingCompose) { unsigned int state = 0; if (modifiers & NSAlphaShiftKeyMask) { - state |= LockMask; + state |= LockMask; } if (modifiers & NSShiftKeyMask) { - state |= ShiftMask; + state |= ShiftMask; } if (modifiers & NSControlKeyMask) { - state |= ControlMask; + state |= ControlMask; } if (modifiers & NSCommandKeyMask) { - state |= Mod1Mask; /* command key */ + state |= Mod1Mask; /* command key */ } if (modifiers & NSAlternateKeyMask) { - state |= Mod2Mask; /* option key */ + state |= Mod2Mask; /* option key */ } if (modifiers & NSNumericPadKeyMask) { - state |= Mod3Mask; + state |= Mod3Mask; } if (modifiers & NSFunctionKeyMask) { - state |= Mod4Mask; + state |= Mod4Mask; } /* - * Events are only received for the front Window on the Macintosh. - * So to build an XEvent we look up the Tk window associated to the - * Front window. If a different window has a local grab we ignore - * the event. + * Events are only received for the front Window on the Macintosh. So + * to build an XEvent we look up the Tk window associated to the Front + * window. If a different window has a local grab we ignore the event. */ TkWindow *winPtr = TkMacOSXGetTkWindow(w); @@ -147,113 +148,123 @@ unsigned short releaseCode; if (tkwin) { TkWindow *grabWinPtr = winPtr->dispPtr->grabWinPtr; - if (grabWinPtr && - grabWinPtr != winPtr && - !winPtr->dispPtr->grabFlags && /* this means the grab is local. */ - grabWinPtr->mainPtr == winPtr->mainPtr) { + + if (grabWinPtr + && grabWinPtr != winPtr + && !winPtr->dispPtr->grabFlags /* this means the grab is local. */ + && grabWinPtr->mainPtr == winPtr->mainPtr) { return theEvent; } } else { tkwin = (Tk_Window) winPtr->dispPtr->focusPtr; } if (!tkwin) { - TkMacOSXDbgMsg("tkwin == NULL"); - return theEvent; /* Give up. No window for this event. */ + TkMacOSXDbgMsg("tkwin == NULL"); + return theEvent; /* Give up. No window for this event. */ } /* - * If it's a function key, or we have modifiers other than Shift or Alt, - * pass it straight to Tk. Otherwise we'll send for input processing. + * If it's a function key, or we have modifiers other than Shift or + * Alt, pass it straight to Tk. Otherwise we'll send for input + * processing. */ - int code = (len == 0) ? - 0 : [charactersIgnoringModifiers characterAtIndex: 0]; + int code = (len == 0) ? 0 : + [charactersIgnoringModifiers characterAtIndex: 0]; if (type != NSKeyDown || isFunctionKey(code) - || (len > 0 && state & (ControlMask | Mod1Mask | Mod3Mask | Mod4Mask))) { - + || (len > 0 && state & (ControlMask | Mod1Mask | Mod3Mask | Mod4Mask))) { XEvent xEvent; - setupXEvent(&xEvent, w, state); + setupXEvent(&xEvent, w, state); if (type == NSFlagsChanged) { - if (savedModifiers > modifiers) { - xEvent.xany.type = KeyRelease; - } else { - xEvent.xany.type = KeyPress; - } + if (savedModifiers > modifiers) { + xEvent.xany.type = KeyRelease; + } else { + xEvent.xany.type = KeyPress; + } - /* - * Use special '-1' to signify a special keycode to our platform - * specific code in tkMacOSXKeyboard.c. This is rather like what - * happens on Windows. - */ + /* + * Use special '-1' to signify a special keycode to our + * platform specific code in tkMacOSXKeyboard.c. This is rather + * like what happens on Windows. + */ - xEvent.xany.send_event = -1; + xEvent.xany.send_event = -1; - /* - * Set keycode (which was zero) to the changed modifier - */ + /* + * Set keycode (which was zero) to the changed modifier + */ - xEvent.xkey.keycode = (modifiers ^ savedModifiers); + xEvent.xkey.keycode = (modifiers ^ savedModifiers); } else { - if (type == NSKeyUp || repeat) { - xEvent.xany.type = KeyRelease; - } else { - xEvent.xany.type = KeyPress; - } - - /* For command key, take input manager's word so things - like dvorak / qwerty layout work. */ - if ((modifiers & NSCommandKeyMask) == NSCommandKeyMask - && (modifiers & NSAlternateKeyMask) != NSAlternateKeyMask - && len > 0 && !isFunctionKey(code)) { - // head off keycode-based translation in tkMacOSXKeyboard.c - xEvent.xkey.nbytes = [characters length]; //len - } - - if ([characters length] > 0) { - xEvent.xkey.keycode = - (keyCode << 16) | (UInt16) [characters characterAtIndex:0]; - if (![characters getCString:xEvent.xkey.trans_chars - maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) { - /* prevent SF bug 2907388 (crash on some composite chars) */ - //PENDING: we might not need this anymore - TkMacOSXDbgMsg("characters too long"); - return theEvent; - } - } - - if (repeat) { - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); - xEvent.xany.type = KeyPress; - xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); - } + if (type == NSKeyUp || repeat) { + xEvent.xany.type = KeyRelease; + } else { + xEvent.xany.type = KeyPress; + } + + /* + * For command key, take input manager's word so things like + * dvorak / qwerty layout work. + */ + + if ((modifiers & NSCommandKeyMask) == NSCommandKeyMask + && (modifiers & NSAlternateKeyMask) != NSAlternateKeyMask + && len > 0 && !isFunctionKey(code)) { + // head off keycode-based translation in tkMacOSXKeyboard.c + xEvent.xkey.nbytes = [characters length]; //len + } + + if ([characters length] > 0) { + xEvent.xkey.keycode = (keyCode << 16) | + (UInt16) [characters characterAtIndex:0]; + if (![characters getCString:xEvent.xkey.trans_chars + maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) { + /* prevent SF bug 2907388 (crash on some composite chars) */ + //PENDING: we might not need this anymore + TkMacOSXDbgMsg("characters too long"); + return theEvent; + } + } + + if (repeat) { + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + xEvent.xany.type = KeyPress; + xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); + } } Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); savedModifiers = modifiers; return theEvent; - } /* if send straight to TK */ - - } /* if not processing compose */ + } /* if send straight to TK */ + } /* if not processing compose */ if (type == NSKeyDown) { - if (NS_KEYLOG) - fprintf (stderr, "keyDown: %s compose sequence.\n", - processingCompose == YES ? "Continue" : "Begin"); + if (NS_KEYLOG) { + TKLog(@"keyDown: %s compose sequence.\n", + processingCompose == YES ? "Continue" : "Begin"); + } processingCompose = YES; [nsEvArray addObject: theEvent]; [[w contentView] interpretKeyEvents: nsEvArray]; [nsEvArray removeObject: theEvent]; - } + } savedModifiers = modifiers; - return theEvent; } @end - @implementation TKContentView + +-(id)init { + if (self = [super init]) { + _needsRedisplay = NO; + } + return self; +} + /* <NSTextInput> implementation (called through interpretKeyEvents:]). */ /* <NSTextInput>: called when done composing; @@ -261,165 +272,193 @@ unsigned short releaseCode; by doCommandBySelector: deleteBackward: */ - (void)insertText: (id)aString { - int i, len = [(NSString *)aString length]; - XEvent xEvent; + int i, len = [(NSString *) aString length]; + XEvent xEvent; - if (NS_KEYLOG) - TKLog (@"insertText '%@'\tlen = %d", aString, len); - processingCompose = NO; - finishedCompose = YES; + if (NS_KEYLOG) { + TKLog(@"insertText '%@'\tlen = %d", aString, len); + } + processingCompose = NO; + finishedCompose = YES; - /* first, clear any working text */ - if (privateWorkingText != nil) - [self deleteWorkingText]; + /* + * First, clear any working text. + */ - /* now insert the string as keystrokes */ - setupXEvent(&xEvent, [self window], 0); - xEvent.xany.type = KeyPress; + if (privateWorkingText != nil) { + [self deleteWorkingText]; + } + + /* + * Now insert the string as keystrokes. + */ - for (i =0; i<len; i++) - { - xEvent.xkey.keycode = (UInt16) [aString characterAtIndex: i]; - [[aString substringWithRange: NSMakeRange(i,1)] + setupXEvent(&xEvent, [self window], 0); + xEvent.xany.type = KeyPress; + + for (i =0; i<len; i++) { + xEvent.xkey.keycode = (UInt16) [aString characterAtIndex: i]; + [[aString substringWithRange: NSMakeRange(i,1)] getCString: xEvent.xkey.trans_chars maxLength: XMaxTransChars encoding: NSUTF8StringEncoding]; - xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars); - xEvent.xany.type = KeyPress; - releaseCode = (UInt16) [aString characterAtIndex: 0]; - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); - } - releaseCode = (UInt16) [aString characterAtIndex: 0]; + xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars); + xEvent.xany.type = KeyPress; + releaseCode = (UInt16) [aString characterAtIndex: 0]; + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + } + releaseCode = (UInt16) [aString characterAtIndex: 0]; } /* <NSTextInput>: inserts display of composing characters */ - (void)setMarkedText: (id)aString selectedRange: (NSRange)selRange { - NSString *str = [aString respondsToSelector: @selector (string)] ? - [aString string] : aString; - if (NS_KEYLOG) - TKLog (@"setMarkedText '%@' len =%lu range %lu from %lu", str, - (unsigned long) [str length], (unsigned long) selRange.length, - (unsigned long) selRange.location); - - if (privateWorkingText != nil) - [self deleteWorkingText]; - if ([str length] == 0) - return; + NSString *str = [aString respondsToSelector: @selector (string)] ? + [aString string] : aString; + if (NS_KEYLOG) { + TKLog(@"setMarkedText '%@' len =%lu range %lu from %lu", str, + (unsigned long) [str length], (unsigned long) selRange.length, + (unsigned long) selRange.location); + } - processingCompose = YES; - privateWorkingText = [str copy]; + if (privateWorkingText != nil) { + [self deleteWorkingText]; + } + if ([str length] == 0) { + return; + } + + processingCompose = YES; + privateWorkingText = [str copy]; - //PENDING: insert workingText underlined + //PENDING: insert workingText underlined } - (BOOL)hasMarkedText { - return privateWorkingText != nil; + return privateWorkingText != nil; } - (NSRange)markedRange { - NSRange rng = privateWorkingText != nil - ? NSMakeRange (0, [privateWorkingText length]) : NSMakeRange (NSNotFound, 0); - if (NS_KEYLOG) - TKLog (@"markedRange request"); - return rng; + NSRange rng = privateWorkingText != nil + ? NSMakeRange(0, [privateWorkingText length]) + : NSMakeRange(NSNotFound, 0); + + if (NS_KEYLOG) { + TKLog(@"markedRange request"); + } + return rng; } - (void)unmarkText { - if (NS_KEYLOG) - TKLog (@"unmark (accept) text"); - [self deleteWorkingText]; - processingCompose = NO; + if (NS_KEYLOG) { + TKLog(@"unmark (accept) text"); + } + [self deleteWorkingText]; + processingCompose = NO; } /* used to position char selection windows, etc. */ - (NSRect)firstRectForCharacterRange: (NSRange)theRange { - NSRect rect; - NSPoint pt; + NSRect rect; + NSPoint pt; - pt.x = caret_x; - pt.y = caret_y; + pt.x = caret_x; + pt.y = caret_y; - pt = [self convertPoint: pt toView: nil]; - pt = [[self window] tkConvertPointToScreen: pt]; - pt.y -= caret_height; + pt = [self convertPoint: pt toView: nil]; + pt = [[self window] tkConvertPointToScreen: pt]; + pt.y -= caret_height; - rect.origin = pt; - rect.size.width = caret_height; - rect.size.height = caret_height; - return rect; + rect.origin = pt; + rect.size.width = caret_height; + rect.size.height = caret_height; + return rect; } - (NSInteger)conversationIdentifier { - return (NSInteger)self; + return (NSInteger) self; } - (void)doCommandBySelector: (SEL)aSelector { - if (NS_KEYLOG) - TKLog (@"doCommandBySelector: %@", NSStringFromSelector (aSelector)); - processingCompose = NO; - if (aSelector == @selector (deleteBackward:)) - { - /* happens when user backspaces over an ongoing composition: - throw a 'delete' into the event queue */ - XEvent xEvent; - setupXEvent(&xEvent, [self window], 0); - xEvent.xany.type = KeyPress; - xEvent.xkey.nbytes = 1; - xEvent.xkey.keycode = (0x33 << 16) | 0x7F; - xEvent.xkey.trans_chars[0] = 0x7F; - xEvent.xkey.trans_chars[1] = 0x0; - Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); + if (NS_KEYLOG) { + TKLog(@"doCommandBySelector: %@", NSStringFromSelector(aSelector)); + } + processingCompose = NO; + if (aSelector == @selector (deleteBackward:)) { + /* + * Happens when user backspaces over an ongoing composition: + * throw a 'delete' into the event queue. + */ + + XEvent xEvent; + + setupXEvent(&xEvent, [self window], 0); + xEvent.xany.type = KeyPress; + xEvent.xkey.nbytes = 1; + xEvent.xkey.keycode = (0x33 << 16) | 0x7F; + xEvent.xkey.trans_chars[0] = 0x7F; + xEvent.xkey.trans_chars[1] = 0x0; + Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL); } } - (NSArray *)validAttributesForMarkedText { - static NSArray *arr = nil; - if (arr == nil) arr = [NSArray new]; - /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */ - return arr; + static NSArray *arr = nil; + + if (arr == nil) { + arr = [NSArray new]; + } + /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */ + return arr; } - (NSRange)selectedRange { - if (NS_KEYLOG) - TKLog (@"selectedRange request"); - return NSMakeRange (NSNotFound, 0); + if (NS_KEYLOG) { + TKLog(@"selectedRange request"); + } + return NSMakeRange(NSNotFound, 0); } - (NSUInteger)characterIndexForPoint: (NSPoint)thePoint { - if (NS_KEYLOG) - TKLog (@"characterIndexForPoint request"); - return 0; + if (NS_KEYLOG) { + TKLog(@"characterIndexForPoint request"); + } + return 0; } - (NSAttributedString *)attributedSubstringFromRange: (NSRange)theRange { - static NSAttributedString *str = nil; - if (str == nil) str = [NSAttributedString new]; - if (NS_KEYLOG) - TKLog (@"attributedSubstringFromRange request"); - return str; + static NSAttributedString *str = nil; + if (str == nil) { + str = [NSAttributedString new]; + } + if (NS_KEYLOG) { + TKLog(@"attributedSubstringFromRange request"); + } + return str; } /* End <NSTextInput> impl. */ + +@synthesize needsRedisplay = _needsRedisplay; @end @@ -427,29 +466,30 @@ unsigned short releaseCode; /* delete display of composing characters [not in <NSTextInput>] */ - (void)deleteWorkingText { - if (privateWorkingText == nil) - return; - if (NS_KEYLOG) - TKLog(@"deleteWorkingText len = %lu\n", - (unsigned long)[privateWorkingText length]); - [privateWorkingText release]; - privateWorkingText = nil; - processingCompose = NO; - - //PENDING: delete working text + if (privateWorkingText == nil) { + return; + } + if (NS_KEYLOG) { + TKLog(@"deleteWorkingText len = %lu\n", + (unsigned long)[privateWorkingText length]); + } + [privateWorkingText release]; + privateWorkingText = nil; + processingCompose = NO; + + //PENDING: delete working text } @end - - /* - * Set up basic fields in xevent for keyboard input. + * Set up basic fields in xevent for keyboard input. */ static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state) { TkWindow *winPtr = TkMacOSXGetTkWindow(w); Tk_Window tkwin = (Tk_Window) winPtr; + if (!winPtr) { return; } @@ -497,12 +537,13 @@ XGrabKeyboard( Time time) { keyboardGrabWinPtr = Tk_IdToWindow(display, grab_window); - TkWindow *captureWinPtr = (TkWindow *)TkMacOSXGetCapture(); + TkWindow *captureWinPtr = (TkWindow *) TkMacOSXGetCapture(); + if (keyboardGrabWinPtr && captureWinPtr) { NSWindow *w = TkMacOSXDrawableWindow(grab_window); MacDrawable *macWin = (MacDrawable *) grab_window; - if (w && macWin->toplevel->winPtr == (TkWindow*) captureWinPtr) { + if (w && macWin->toplevel->winPtr == (TkWindow *) captureWinPtr) { if (modalSession) { Tcl_Panic("XGrabKeyboard: already grabbed"); } @@ -621,7 +662,10 @@ Tk_SetCaretPos( } } - /* But adjust for fact that NS uses flipped view. */ + /* + * But adjust for fact that NS uses flipped view. + */ + y = Tk_Height(tkwin) - y; caret_x = x; @@ -632,75 +676,80 @@ Tk_SetCaretPos( static unsigned convert_ns_to_X_keysym[] = { - NSHomeFunctionKey, 0x50, - NSLeftArrowFunctionKey, 0x51, - NSUpArrowFunctionKey, 0x52, - NSRightArrowFunctionKey, 0x53, - NSDownArrowFunctionKey, 0x54, - NSPageUpFunctionKey, 0x55, - NSPageDownFunctionKey, 0x56, - NSEndFunctionKey, 0x57, - NSBeginFunctionKey, 0x58, - NSSelectFunctionKey, 0x60, - NSPrintFunctionKey, 0x61, - NSExecuteFunctionKey, 0x62, - NSInsertFunctionKey, 0x63, - NSUndoFunctionKey, 0x65, - NSRedoFunctionKey, 0x66, - NSMenuFunctionKey, 0x67, - NSFindFunctionKey, 0x68, - NSHelpFunctionKey, 0x6A, - NSBreakFunctionKey, 0x6B, - - NSF1FunctionKey, 0xBE, - NSF2FunctionKey, 0xBF, - NSF3FunctionKey, 0xC0, - NSF4FunctionKey, 0xC1, - NSF5FunctionKey, 0xC2, - NSF6FunctionKey, 0xC3, - NSF7FunctionKey, 0xC4, - NSF8FunctionKey, 0xC5, - NSF9FunctionKey, 0xC6, - NSF10FunctionKey, 0xC7, - NSF11FunctionKey, 0xC8, - NSF12FunctionKey, 0xC9, - NSF13FunctionKey, 0xCA, - NSF14FunctionKey, 0xCB, - NSF15FunctionKey, 0xCC, - NSF16FunctionKey, 0xCD, - NSF17FunctionKey, 0xCE, - NSF18FunctionKey, 0xCF, - NSF19FunctionKey, 0xD0, - NSF20FunctionKey, 0xD1, - NSF21FunctionKey, 0xD2, - NSF22FunctionKey, 0xD3, - NSF23FunctionKey, 0xD4, - NSF24FunctionKey, 0xD5, - - NSBackspaceCharacter, 0x08, /* 8: Not on some KBs. */ - NSDeleteCharacter, 0xFF, /* 127: Big 'delete' key upper right. */ - NSDeleteFunctionKey, 0x9F, /* 63272: Del forw key off main array. */ - - NSTabCharacter, 0x09, - 0x19, 0x09, /* left tab->regular since pass shift */ - NSCarriageReturnCharacter, 0x0D, - NSNewlineCharacter, 0x0D, - NSEnterCharacter, 0x8D, - - 0x1B, 0x1B /* escape */ + NSHomeFunctionKey, 0x50, + NSLeftArrowFunctionKey, 0x51, + NSUpArrowFunctionKey, 0x52, + NSRightArrowFunctionKey, 0x53, + NSDownArrowFunctionKey, 0x54, + NSPageUpFunctionKey, 0x55, + NSPageDownFunctionKey, 0x56, + NSEndFunctionKey, 0x57, + NSBeginFunctionKey, 0x58, + NSSelectFunctionKey, 0x60, + NSPrintFunctionKey, 0x61, + NSExecuteFunctionKey, 0x62, + NSInsertFunctionKey, 0x63, + NSUndoFunctionKey, 0x65, + NSRedoFunctionKey, 0x66, + NSMenuFunctionKey, 0x67, + NSFindFunctionKey, 0x68, + NSHelpFunctionKey, 0x6A, + NSBreakFunctionKey, 0x6B, + + NSF1FunctionKey, 0xBE, + NSF2FunctionKey, 0xBF, + NSF3FunctionKey, 0xC0, + NSF4FunctionKey, 0xC1, + NSF5FunctionKey, 0xC2, + NSF6FunctionKey, 0xC3, + NSF7FunctionKey, 0xC4, + NSF8FunctionKey, 0xC5, + NSF9FunctionKey, 0xC6, + NSF10FunctionKey, 0xC7, + NSF11FunctionKey, 0xC8, + NSF12FunctionKey, 0xC9, + NSF13FunctionKey, 0xCA, + NSF14FunctionKey, 0xCB, + NSF15FunctionKey, 0xCC, + NSF16FunctionKey, 0xCD, + NSF17FunctionKey, 0xCE, + NSF18FunctionKey, 0xCF, + NSF19FunctionKey, 0xD0, + NSF20FunctionKey, 0xD1, + NSF21FunctionKey, 0xD2, + NSF22FunctionKey, 0xD3, + NSF23FunctionKey, 0xD4, + NSF24FunctionKey, 0xD5, + + NSBackspaceCharacter, 0x08, /* 8: Not on some KBs. */ + NSDeleteCharacter, 0xFF, /* 127: Big 'delete' key upper right. */ + NSDeleteFunctionKey, 0x9F, /* 63272: Del forw key off main array. */ + + NSTabCharacter, 0x09, + 0x19, 0x09, /* left tab->regular since pass shift */ + NSCarriageReturnCharacter, 0x0D, + NSNewlineCharacter, 0x0D, + NSEnterCharacter, 0x8D, + + 0x1B, 0x1B /* escape */ }; -static unsigned isFunctionKey(unsigned code) +static unsigned +isFunctionKey( + unsigned code) { - const unsigned last_keysym = (sizeof (convert_ns_to_X_keysym) - / sizeof (convert_ns_to_X_keysym[0])); - unsigned keysym; - for (keysym = 0; keysym < last_keysym; keysym += 2) - if (code == convert_ns_to_X_keysym[keysym]) - return 0xFF00 | convert_ns_to_X_keysym[keysym+1]; - return 0; - } + const unsigned last_keysym = (sizeof(convert_ns_to_X_keysym) + / sizeof(convert_ns_to_X_keysym[0])); + unsigned keysym; + + for (keysym = 0; keysym < last_keysym; keysym += 2) { + if (code == convert_ns_to_X_keysym[keysym]) { + return 0xFF00 | convert_ns_to_X_keysym[keysym + 1]; + } + } + return 0; +} /* * Local Variables: diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 3d7cdaf..790b6ff 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -193,10 +193,10 @@ static int ModifierCharWidth(Tk_Font tkfont); - (void) insertItem: (NSMenuItem *) newItem atIndex: (NSInteger) index { if (_tkMenu && index >= 0) { - if ((NSUInteger)index <= _tkOffset) { + if ((NSUInteger) index <= _tkOffset) { _tkOffset++; } else { - NSAssert((NSUInteger)index >= _tkItemCount + _tkOffset, + NSAssert((NSUInteger) index >= _tkItemCount + _tkOffset, @"Cannot insert in the middle of Tk menu"); } } @@ -206,9 +206,9 @@ static int ModifierCharWidth(Tk_Font tkfont); - (void) removeItemAtIndex: (NSInteger) index { if (_tkMenu && index >= 0) { - if ((NSUInteger)index < _tkOffset) { + if ((NSUInteger) index < _tkOffset) { _tkOffset--; - } else if ((NSUInteger)index < _tkItemCount + _tkOffset) { + } else if ((NSUInteger) index < _tkItemCount + _tkOffset) { _tkItemCount--; } } @@ -221,7 +221,7 @@ static int ModifierCharWidth(Tk_Font tkfont); action:@selector(tkMenuItemInvoke:) keyEquivalent:@""]; [menuItem setTarget:self]; - [menuItem setTag:(NSInteger)mePtr]; + [menuItem setTag:(NSInteger) mePtr]; return menuItem; } @end @@ -252,15 +252,16 @@ static int ModifierCharWidth(Tk_Font tkfont); */ if ([sender isKindOfClass:[NSMenuItem class]]) { - NSMenuItem *menuItem = (NSMenuItem *)sender; - TkMenu *menuPtr = (TkMenu *)_tkMenu; - TkMenuEntry *mePtr = (TkMenuEntry *)[menuItem tag]; + NSMenuItem *menuItem = (NSMenuItem *) sender; + TkMenu *menuPtr = (TkMenu *) _tkMenu; + TkMenuEntry *mePtr = (TkMenuEntry *) [menuItem tag]; if (menuPtr && mePtr) { Tcl_Interp *interp = menuPtr->interp; - /*Add time for errors to fire if necessary. This is sub-optimal - *but avoids issues with Tcl/Cocoa event loop integration. + /* + * Add time for errors to fire if necessary. This is sub-optimal + * but avoids issues with Tcl/Cocoa event loop integration. */ //Tcl_Sleep(100); @@ -305,8 +306,8 @@ static int ModifierCharWidth(Tk_Font tkfont); return NO; } else if (modifiers == (NSControlKeyMask | NSShiftKeyMask) && [event keyCode] == 48) { - - /* Starting with OSX 10.12 Control-Tab and Control-Shift-Tab are used + /* + * Starting with OSX 10.12 Control-Tab and Control-Shift-Tab are used * to select window tabs. But for some even more mysterious reason the * Control-Shift-Tab event has character 0x19 = NSBackTabCharacter * rather than 0x09 = NSTabCharacter. At the same time, the @@ -317,7 +318,6 @@ static int ModifierCharWidth(Tk_Font tkfont); key = @"\t"; } else if (([event modifierFlags] & NSCommandKeyMask) == NSCommandKeyMask) { - /* * If the command modifier is set, use the full character string so * things like the dvorak / qwerty layout will work. @@ -650,7 +650,7 @@ TkpConfigureMenuEntry( gc->foreground!=defaultFg? gc->foreground:gc->background); attributes = [[attributes mutableCopy] autorelease]; - [(NSMutableDictionary *)attributes setObject:color + [(NSMutableDictionary *) attributes setObject:color forKey:NSForegroundColorAttributeName]; } if (attributes) { @@ -690,21 +690,31 @@ TkpConfigureMenuEntry( [submenu setTitle:title]; if ([menuItem isEnabled]) { - /* This menuItem might have been previously disabled (XXX: - track this), which would have disabled entries; we must - re-enable the entries here. */ - int i = 0; - NSArray *itemArray = [submenu itemArray]; - for (NSMenuItem *item in itemArray) { - TkMenuEntry *submePtr = menuRefPtr->menuPtr->entries[i]; - /* Work around an apparent bug where itemArray can have - more items than the menu's entries[] array. */ - if (i >= (int)menuRefPtr->menuPtr->numEntries) break; - [item setEnabled: !(submePtr->state == ENTRY_DISABLED)]; - i++; - } - } + /* + * This menuItem might have been previously disabled (XXX: + * track this), which would have disabled entries; we must + * re-enable the entries here. + */ + + int i = 0; + NSArray *itemArray = [submenu itemArray]; + + for (NSMenuItem *item in itemArray) { + TkMenuEntry *submePtr = menuRefPtr->menuPtr->entries[i]; + + /* + * Work around an apparent bug where itemArray can have + * more items than the menu's entries[] array. + */ + + if (i >= (int) menuRefPtr->menuPtr->numEntries) { + break; + } + [item setEnabled: !(submePtr->state == ENTRY_DISABLED)]; + i++; + } + } } } } @@ -755,10 +765,10 @@ TkpDestroyMenuEntry( * * TkpPostMenu -- * - * Posts a menu on the screen. If entry is < 0 then the menu is - * drawn so its top left corner is located at the point with - * screen coordinates (x, y). Otherwise the top left corner of - * the specified entry is located at that point. + * Posts a menu on the screen. If entry is < 0 then the menu is drawn so + * its top left corner is located at the point with screen coordinates + * (x,y). Otherwise the top left corner of the specified entry is located + * at that point. * * Results: * Returns a standard Tcl result. @@ -828,13 +838,13 @@ TkpPostMenu( * * TkpPostTearoffMenu -- * - * Tearoff menus are not supported on the Mac. This placeholder - * function, which is simply a copy of the unix function, posts a - * completely useless window with a black background on the screen. If - * entry is < 0 then the window is positioned so that its top left corner - * is located at the point with screen coordinates (x, y). Otherwise the - * window position is offset so that top left corner of the specified - * entry would be located at that point, if there actually were a menu. + * Tearoff menus are not supported on the Mac. This placeholder function, + * which is simply a copy of the unix function, posts a completely useless + * window with a black background on the screen. If entry is < 0 then the + * window is positioned so that its top left corner is located at the + * point with screen coordinates (x, y). Otherwise the window position is + * offset so that top left corner of the specified entry would be located + * at that point, if there actually were a menu. * * Mac menus steal all mouse or keyboard input from the application until * the menu is dismissed, with or without a selection, by a mouse or key @@ -862,7 +872,7 @@ TkpPostTearoffMenu( int vRootX, vRootY, vRootWidth, vRootHeight; int result; - if (index >= (int)menuPtr->numEntries) { + if (index >= (int) menuPtr->numEntries) { index = menuPtr->numEntries - 1; } if (index >= 0) { @@ -960,12 +970,12 @@ TkpSetWindowMenuBar( * * TkpSetMainMenubar -- * - * Puts the menu associated with a window into the menubar. Should only - * be called when the window is in front. + * Puts the menu associated with a window into the menubar. Should only be + * called when the window is in front. * * This is a no-op on all other platforms. On OS X it is a no-op when - * passed a NULL menuName or a nonexistent menuName, with an exception - * for the first call in a new interpreter. In that special case, passing a + * passed a NULL menuName or a nonexistent menuName, with an exception for + * the first call in a new interpreter. In that special case, passing a * NULL menuName installs the default menu. * * Results: @@ -993,19 +1003,21 @@ TkpSetMainMenubar( */ if (Tk_IsEmbedded(winPtr)) { - return; - } + return; + } if (menuName) { Tk_Window menubar = NULL; + if (winPtr->wmInfoPtr && - winPtr->wmInfoPtr->menuPtr && - winPtr->wmInfoPtr->menuPtr->masterMenuPtr) { + winPtr->wmInfoPtr->menuPtr && + winPtr->wmInfoPtr->menuPtr->masterMenuPtr) { menubar = winPtr->wmInfoPtr->menuPtr->masterMenuPtr->tkwin; } /* - * Attempt to find the NSMenu directly. If that fails, ask Tk to find it. + * Attempt to find the NSMenu directly. If that fails, ask Tk to find + * it. */ if (menubar != NULL && strcmp(menuName, Tk_PathName(menubar)) == 0) { @@ -1013,6 +1025,7 @@ TkpSetMainMenubar( } else { TkMenuReferences *menuRefPtr = TkFindMenuReferences(interp, menuName); + if (menuRefPtr && menuRefPtr->menuPtr && menuRefPtr->menuPtr->platformData) { menu = (TKMenu *) menuRefPtr->menuPtr->platformData; @@ -1021,9 +1034,8 @@ TkpSetMainMenubar( } /* - * If we couldn't find a menu, do nothing unless the window belongs - * to a different application. In that case, install the default - * menubar. + * If we couldn't find a menu, do nothing unless the window belongs to a + * different application. In that case, install the default menubar. */ if (menu || interp != currentInterp) { @@ -1038,8 +1050,8 @@ TkpSetMainMenubar( * CheckForSpecialMenu -- * * Given a menu, check to see whether or not it is a cascade in a menubar - * with one of the special names ".apple", ".help" or ".window". If it - * is, the entry that points to this menu will be marked. + * with one of the special names ".apple", ".help" or ".window". If it is, + * the entry that points to this menu will be marked. * * Results: * None. @@ -1230,11 +1242,12 @@ TkpComputeStandardMenuGeometry( /* * Do nothing if this menu is a clone. */ + if (menuPtr->tkwin == NULL || menuPtr->masterMenuPtr != menuPtr) { return; } - menuSize = [(NSMenu *)menuPtr->platformData size]; + menuSize = [(NSMenu *) menuPtr->platformData size]; Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->borderWidthPtr, &borderWidth); Tk_GetPixelsFromObj(NULL, menuPtr->tkwin, menuPtr->activeBorderWidthPtr, @@ -1248,16 +1261,16 @@ TkpComputeStandardMenuGeometry( * want to do it intelligently. We are going to precalculate them and pass * them down to all of the measuring and drawing routines. We will measure * the font metrics of the menu once. If an entry does not have its own - * font set, then we give the geometry/drawing routines the menu's font - * and metrics. If an entry has its own font, we will measure that font - * and give all of the geometry/drawing the entry's font and metrics. + * font set, then we give the geometry/drawing routines the menu's font and + * metrics. If an entry has its own font, we will measure that font and + * give all of the geometry/drawing the entry's font and metrics. */ menuFont = Tk_GetFontFromObj(menuPtr->tkwin, menuPtr->fontPtr); Tk_GetFontMetrics(menuFont, &menuMetrics); menuModifierCharWidth = ModifierCharWidth(menuFont); - for (i = 0; i < (int)menuPtr->numEntries; i++) { + for (i = 0; i < (int) menuPtr->numEntries; i++) { mePtr = menuPtr->entries[i]; if (mePtr->type == CASCADE_ENTRY || mePtr->accelLength > 0) { haveAccel = 1; @@ -1265,7 +1278,7 @@ TkpComputeStandardMenuGeometry( } } - for (i = 0; i < (int)menuPtr->numEntries; i++) { + for (i = 0; i < (int) menuPtr->numEntries; i++) { mePtr = menuPtr->entries[i]; if (mePtr->type == TEAROFF_ENTRY) { continue; @@ -1295,6 +1308,7 @@ TkpComputeStandardMenuGeometry( NSMenuItem *menuItem = (NSMenuItem *) mePtr->platformEntryData; int haveImage = 0, width = 0, height = 0; + if (mePtr->image) { Tk_SizeOfImage(mePtr->image, &width, &height); haveImage = 1; @@ -1302,6 +1316,7 @@ TkpComputeStandardMenuGeometry( } else if (mePtr->bitmapPtr) { Pixmap bitmap = Tk_GetBitmapFromObj(menuPtr->tkwin, mePtr->bitmapPtr); + Tk_SizeOfBitmap(menuPtr->display, bitmap, &width, &height); haveImage = 1; height += 2; /* tweak */ @@ -1413,7 +1428,7 @@ GenerateMenuSelectEvent( if (menuPtr) { int index = [menu tkIndexOfItem:menuItem]; - if (index < 0 || index >= (int)menuPtr->numEntries || + if (index < 0 || index >= (int) menuPtr->numEntries || (menuPtr->entries[index])->state == ENTRY_DISABLED) { TkActivateMenuEntry(menuPtr, -1); } else { @@ -1492,7 +1507,7 @@ RecursivelyClearActiveMenu( int i; TkActivateMenuEntry(menuPtr, -1); - for (i = 0; i < (int)menuPtr->numEntries; i++) { + for (i = 0; i < (int) menuPtr->numEntries; i++) { TkMenuEntry *mePtr = menuPtr->entries[i]; if (mePtr->type == CASCADE_ENTRY @@ -1781,7 +1796,7 @@ TkMacOSXPreprocessMenu(void) * TkMacOSXUseID -- * * Take the ID out of the available list for new menus. Used by the - * default menu bar's menus so that they do not get created at the tk + * default menu bar's menus so that they do not get created at the Tk * level. See TkMacOSXGetNewMenuID for more information. * * Results: @@ -1806,8 +1821,7 @@ TkMacOSXUseMenuID( * * TkMacOSXDispatchMenuEvent -- * - * Given a menu id and an item, dispatches the command associated with - * it. + * Given a menu id and an item, dispatches the command associated with it. * * Results: * None. @@ -1857,9 +1871,10 @@ TkMacOSXHandleTearoffMenu(void) * * TkMacOSXSetHelpMenuItemCount -- * - * Has to be called after the first call to InsertMenu. Sets up the - * global variable for the number of items in the unmodified help menu. - * NB. Nobody uses this any more, since you can get the number of system + * Has to be called after the first call to InsertMenu. Sets up the global + * variable for the number of items in the unmodified help menu. + * + * NB: Nobody uses this any more, since you can get the number of system * help items from HMGetHelpMenu trivially. But it is in the stubs * table... * diff --git a/macosx/tkMacOSXMenubutton.c b/macosx/tkMacOSXMenubutton.c index b2b4b76..dbad8db 100644 --- a/macosx/tkMacOSXMenubutton.c +++ b/macosx/tkMacOSXMenubutton.c @@ -1,8 +1,8 @@ /* * tkMacOSXMenubutton.c -- * - * This file implements the Macintosh specific portion of the - * menubutton widget. + * This file implements the Macintosh specific portion of the menubutton + * widget. * * Copyright (c) 1996 by Sun Microsystems, Inc. * Copyright 2001, Apple Computer, Inc. @@ -32,7 +32,6 @@ typedef struct { int hasImageOrBitmap; } DrawParams; - /* * Declaration of Mac specific button structure. */ @@ -50,21 +49,24 @@ typedef struct MacMenuButton { * Forward declarations for static functions defined later in this file: */ -static void MenuButtonEventProc(ClientData clientData, XEvent *eventPtr); -static void MenuButtonBackgroundDrawCB (MacMenuButton *ptr, SInt16 depth, - Boolean isColorDev); -static void MenuButtonContentDrawCB (ThemeButtonKind kind, - const HIThemeButtonDrawInfo * info, - MacMenuButton *ptr, SInt16 depth, - Boolean isColorDev); -static void MenuButtonEventProc ( ClientData clientData, XEvent *eventPtr); -static void TkMacOSXComputeMenuButtonParams (TkMenuButton * butPtr, - ThemeButtonKind* btnkind, - HIThemeButtonDrawInfo* drawinfo); -static void TkMacOSXComputeMenuButtonDrawParams (TkMenuButton * butPtr, - DrawParams * dpPtr); -static void TkMacOSXDrawMenuButton (MacMenuButton *butPtr, GC gc, Pixmap pixmap); -static void DrawMenuButtonImageAndText(TkMenuButton* butPtr); +static void MenuButtonEventProc(ClientData clientData, + XEvent *eventPtr); +static void MenuButtonBackgroundDrawCB(MacMenuButton *ptr, + SInt16 depth, Boolean isColorDev); +static void MenuButtonContentDrawCB(ThemeButtonKind kind, + const HIThemeButtonDrawInfo *info, + MacMenuButton *ptr, SInt16 depth, + Boolean isColorDev); +static void MenuButtonEventProc(ClientData clientData, + XEvent *eventPtr); +static void TkMacOSXComputeMenuButtonParams(TkMenuButton *butPtr, + ThemeButtonKind *btnkind, + HIThemeButtonDrawInfo *drawinfo); +static void TkMacOSXComputeMenuButtonDrawParams( + TkMenuButton *butPtr, DrawParams *dpPtr); +static void TkMacOSXDrawMenuButton(MacMenuButton *butPtr, GC gc, + Pixmap pixmap); +static void DrawMenuButtonImageAndText(TkMenuButton *butPtr); /* * The structure below defines menubutton class behavior by means of @@ -133,8 +135,7 @@ TkpCreateMenuButton( { MacMenuButton *mbPtr = (MacMenuButton *) ckalloc(sizeof(MacMenuButton)); - Tk_CreateEventHandler(tkwin, ActivateMask, MenuButtonEventProc, - (ClientData) mbPtr); + Tk_CreateEventHandler(tkwin, ActivateMask, MenuButtonEventProc, mbPtr); mbPtr->flags = FIRST_DRAW; mbPtr->btnkind = kThemePopupButton; bzero(&mbPtr->drawinfo, sizeof(mbPtr->drawinfo)); @@ -153,8 +154,7 @@ TkpCreateMenuButton( * None. * * Side effects: - * Commands are output to X to display the menubutton in its - * current mode. + * Commands are output to X to display the menubutton in its current mode. * *---------------------------------------------------------------------- */ @@ -163,11 +163,11 @@ void TkpDisplayMenuButton( ClientData clientData) /* Information about widget. */ { - MacMenuButton *mbPtr = (MacMenuButton *)clientData; - TkMenuButton *butPtr = (TkMenuButton *) clientData; - Tk_Window tkwin = butPtr->tkwin; + MacMenuButton *mbPtr = clientData; + TkMenuButton *butPtr = clientData; + Tk_Window tkwin = butPtr->tkwin; Pixmap pixmap; - DrawParams* dpPtr = &mbPtr->drawParams; + DrawParams *dpPtr = &mbPtr->drawParams; butPtr->flags &= ~REDRAW_PENDING; if ((butPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { @@ -179,22 +179,27 @@ TkpDisplayMenuButton( TkMacOSXComputeMenuButtonDrawParams(butPtr, dpPtr); /* - * set up clipping region. Make sure the we are using the port - * for this button, or we will set the wrong window's clip. + * Set up clipping region. Make sure the we are using the port for this + * button, or we will set the wrong window's clip. */ TkMacOSXSetUpClippingRgn(pixmap); - /* Draw the native portion of the buttons. */ + /* + * Draw the native portion of the buttons. + */ + TkMacOSXDrawMenuButton(mbPtr, dpPtr->gc, pixmap); - /* Draw highlight border, if needed. */ + /* + * Draw highlight border, if needed. + */ + if (butPtr->highlightWidth < 3) { - if ((butPtr->flags & GOT_FOCUS)) { - Tk_Draw3DRectangle(tkwin, pixmap, butPtr->normalBorder, 0, 0, - Tk_Width(tkwin), Tk_Height(tkwin), - butPtr->highlightWidth, TK_RELIEF_SOLID); - } + if (butPtr->flags & GOT_FOCUS) { + GC gc = Tk_GCForColor(butPtr->highlightColorPtr, pixmap); + TkMacOSXDrawSolidBorder(tkwin, gc, 0, butPtr->highlightWidth); + } } } @@ -203,8 +208,8 @@ TkpDisplayMenuButton( * * TkpDestroyMenuButton -- * - * Free data structures associated with the menubutton control. - * This is a no-op on the Mac. + * Free data structures associated with the menubutton control. This is a + * no-op on the Mac. * * Results: * None. @@ -279,44 +284,42 @@ TkpComputeMenuButtonGeometry(butPtr) } /* - * If the button is compound (ie, it shows both an image and text), - * the new geometry is a combination of the image and text geometry. - * We only honor the compound bit if the button has both text and an - * image, because otherwise it is not really a compound button. + * If the button is compound (ie, it shows both an image and text), the new + * geometry is a combination of the image and text geometry. We only honor + * the compound bit if the button has both text and an image, because + * otherwise it is not really a compound button. */ if (haveImage && haveText) { switch ((enum compound) butPtr->compound) { - case COMPOUND_TOP: - case COMPOUND_BOTTOM: { - /* - * Image is above or below text - */ - - height += txtHeight + butPtr->padY; - width = (width > txtWidth ? width : txtWidth); - break; - } - case COMPOUND_LEFT: - case COMPOUND_RIGHT: { - /* - * Image is left or right of text - */ - - width += txtWidth + butPtr->padX; - height = (height > txtHeight ? height : txtHeight); - break; - } - case COMPOUND_CENTER: { - /* - * Image and text are superimposed - */ - - width = (width > txtWidth ? width : txtWidth); - height = (height > txtHeight ? height : txtHeight); - break; - } - case COMPOUND_NONE: {break;} + case COMPOUND_TOP: + case COMPOUND_BOTTOM: + /* + * Image is above or below text + */ + + height += txtHeight + butPtr->padY; + width = (width > txtWidth ? width : txtWidth); + break; + case COMPOUND_LEFT: + case COMPOUND_RIGHT: + /* + * Image is left or right of text + */ + + width += txtWidth + butPtr->padX; + height = (height > txtHeight ? height : txtHeight); + break; + case COMPOUND_CENTER: + /* + * Image and text are superimposed + */ + + width = (width > txtWidth ? width : txtWidth); + height = (height > txtHeight ? height : txtHeight); + break; + case COMPOUND_NONE: + break; } if (butPtr->width > 0) { @@ -345,7 +348,7 @@ TkpComputeMenuButtonGeometry(butPtr) } } } - + butPtr->inset = highlightWidth + butPtr->borderWidth; width += LEFT_INSET + RIGHT_INSET + 2*butPtr->inset; height += 2*butPtr->inset; @@ -371,32 +374,24 @@ TkpComputeMenuButtonGeometry(butPtr) */ void DrawMenuButtonImageAndText( - TkMenuButton* butPtr) + TkMenuButton *butPtr) { - MacMenuButton *mbPtr = (MacMenuButton*)butPtr; - Tk_Window tkwin = butPtr->tkwin; - Pixmap pixmap; - int haveImage = 0; - int haveText = 0; - int imageWidth = 0; - int imageHeight = 0; - int imageXOffset = 0; - int imageYOffset = 0; - int textXOffset = 0; - int textYOffset = 0; - int width = 0; - int height = 0; - int fullWidth = 0; - int fullHeight = 0; - int pressed; + MacMenuButton *mbPtr = (MacMenuButton *) butPtr; + Tk_Window tkwin = butPtr->tkwin; + Pixmap pixmap; + int haveImage = 0, haveText = 0; + int imageWidth = 0, imageHeight = 0; + int imageXOffset = 0, imageYOffset = 0; + int textXOffset = 0, textYOffset = 0; + int width = 0, height = 0; + int fullWidth = 0, fullHeight = 0; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } - DrawParams* dpPtr = &mbPtr->drawParams; - pixmap = (Pixmap)Tk_WindowId(tkwin); - + DrawParams *dpPtr = &mbPtr->drawParams; + pixmap = (Pixmap) Tk_WindowId(tkwin); if (butPtr->image != None) { Tk_SizeOfImage(butPtr->image, &width, &height); @@ -406,86 +401,80 @@ DrawMenuButtonImageAndText( haveImage = 1; } - imageWidth = width; + imageWidth = width; imageHeight = height; - if (mbPtr->drawinfo.state == kThemeStatePressed) { - /* Offset bitmaps by a bit when the button is pressed. */ - pressed = 1; - } - haveText = (butPtr->textWidth != 0 && butPtr->textHeight != 0); if (butPtr->compound != COMPOUND_NONE && haveImage && haveText) { - int x = 0; - int y = 0; + int x = 0, y = 0; + textXOffset = 0; textYOffset = 0; fullWidth = 0; fullHeight = 0; switch ((enum compound) butPtr->compound) { - case COMPOUND_TOP: - case COMPOUND_BOTTOM: { - /* Image is above or below text */ - if (butPtr->compound == COMPOUND_TOP) { - textYOffset = height + butPtr->padY; - } else { - imageYOffset = butPtr->textHeight + butPtr->padY; - } - fullHeight = height + butPtr->textHeight + butPtr->padY; - fullWidth = (width > butPtr->textWidth ? - width : butPtr->textWidth); - textXOffset = (fullWidth - butPtr->textWidth)/2; - imageXOffset = (fullWidth - width)/2; - break; - } - case COMPOUND_LEFT: - case COMPOUND_RIGHT: { - /* - * Image is left or right of text - */ - - if (butPtr->compound == COMPOUND_LEFT) { - textXOffset = width + butPtr->padX - 2; - } else { - imageXOffset = butPtr->textWidth + butPtr->padX; - } - fullWidth = butPtr->textWidth + butPtr->padX + width; - fullHeight = (height > butPtr->textHeight ? height : - butPtr->textHeight); - textYOffset = (fullHeight - butPtr->textHeight)/2; - imageYOffset = (fullHeight - height)/2; - break; - } - case COMPOUND_CENTER: { - /* - * Image and text are superimposed - */ - - fullWidth = (width > butPtr->textWidth ? width : - butPtr->textWidth); - fullHeight = (height > butPtr->textHeight ? height : - butPtr->textHeight); - textXOffset = (fullWidth - butPtr->textWidth)/2; - imageXOffset = (fullWidth - width)/2; - textYOffset = (fullHeight - butPtr->textHeight)/2; - imageYOffset = (fullHeight - height)/2; - break; - } - case COMPOUND_NONE: {break;} + case COMPOUND_TOP: + case COMPOUND_BOTTOM: + /* + * Image is above or below text. + */ + + if (butPtr->compound == COMPOUND_TOP) { + textYOffset = height + butPtr->padY; + } else { + imageYOffset = butPtr->textHeight + butPtr->padY; + } + fullHeight = height + butPtr->textHeight + butPtr->padY; + fullWidth = (width > butPtr->textWidth ? + width : butPtr->textWidth); + textXOffset = (fullWidth - butPtr->textWidth)/2; + imageXOffset = (fullWidth - width)/2; + break; + case COMPOUND_LEFT: + case COMPOUND_RIGHT: + /* + * Image is left or right of text + */ + + if (butPtr->compound == COMPOUND_LEFT) { + textXOffset = width + butPtr->padX - 2; + } else { + imageXOffset = butPtr->textWidth + butPtr->padX; + } + fullWidth = butPtr->textWidth + butPtr->padX + width; + fullHeight = (height > butPtr->textHeight ? height : + butPtr->textHeight); + textYOffset = (fullHeight - butPtr->textHeight)/2; + imageYOffset = (fullHeight - height)/2; + break; + case COMPOUND_CENTER: + /* + * Image and text are superimposed + */ + + fullWidth = (width > butPtr->textWidth ? width : butPtr->textWidth); + fullHeight = (height > butPtr->textHeight ? height : + butPtr->textHeight); + textXOffset = (fullWidth - butPtr->textWidth) / 2; + imageXOffset = (fullWidth - width) / 2; + textYOffset = (fullHeight - butPtr->textHeight) / 2; + imageYOffset = (fullHeight - height) / 2; + break; + case COMPOUND_NONE: + break; } TkComputeAnchor(butPtr->anchor, tkwin, - butPtr->padX + butPtr->inset, - butPtr->padY + butPtr->inset, + butPtr->padX + butPtr->inset, butPtr->padY + butPtr->inset, fullWidth, fullHeight, &x, &y); imageXOffset = LEFT_INSET; imageYOffset += y; textYOffset -= 1; if (butPtr->image != NULL) { - Tk_RedrawImage(butPtr->image, 0, 0, width, - height, pixmap, imageXOffset, imageYOffset); + Tk_RedrawImage(butPtr->image, 0, 0, width, + height, pixmap, imageXOffset, imageYOffset); } else { XSetClipOrigin(butPtr->display, dpPtr->gc, imageXOffset, imageYOffset); @@ -499,12 +488,12 @@ DrawMenuButtonImageAndText( dpPtr->gc, butPtr->textLayout, x + textXOffset, y + textYOffset, 0, -1); Tk_UnderlineTextLayout(butPtr->display, pixmap, dpPtr->gc, - butPtr->textLayout, - x + textXOffset, y + textYOffset, + butPtr->textLayout, x + textXOffset, y + textYOffset, butPtr->underline); } else { + int x, y; + if (haveImage) { - int x, y; TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX + butPtr->borderWidth, butPtr->padY + butPtr->borderWidth, @@ -524,27 +513,24 @@ DrawMenuButtonImageAndText( XSetClipOrigin(butPtr->display, dpPtr->gc, 0, 0); } } else { - int x, y; textXOffset = LEFT_INSET; TkComputeAnchor(butPtr->anchor, tkwin, butPtr->padX, butPtr->padY, - butPtr->textWidth, butPtr->textHeight, &x, &y); + butPtr->textWidth, butPtr->textHeight, &x, &y); Tk_DrawTextLayout(butPtr->display, pixmap, dpPtr->gc, - butPtr->textLayout, textXOffset, y, 0, -1); + butPtr->textLayout, textXOffset, y, 0, -1); y += butPtr->textHeight/2; - } + } } } - - - + /* *-------------------------------------------------------------- * * TkMacOSXDrawMenuButton -- * - * This function draws the tk menubutton using Mac controls - * In addition, this code may apply custom colors passed - * in the TkMenubutton. + * This function draws the tk menubutton using Mac controls. In + * addition, this code may apply custom colors passed in the + * TkMenubutton. * * Results: * None. @@ -554,43 +540,39 @@ DrawMenuButtonImageAndText( * *-------------------------------------------------------------- */ + static void TkMacOSXDrawMenuButton( MacMenuButton *mbPtr, /* Mac menubutton. */ - GC gc, /* The GC we are drawing into - needed for - * the bevel button */ - Pixmap pixmap) /* The pixmap we are drawing into - needed - * for the bevel button */ + GC gc, /* The GC we are drawing into - needed for the bevel + * button */ + Pixmap pixmap) /* The pixmap we are drawing into - needed for the + * bevel button */ { - TkMenuButton * butPtr = ( TkMenuButton *)mbPtr; - TkWindow * winPtr; - HIRect cntrRect; + TkMenuButton *butPtr = (TkMenuButton *) mbPtr; + TkWindow *winPtr = (TkWindow *) butPtr->tkwin; + HIRect cntrRect; TkMacOSXDrawingContext dc; - DrawParams* dpPtr = &mbPtr->drawParams; + DrawParams *dpPtr = &mbPtr->drawParams; int useNewerHITools = 1; - winPtr = (TkWindow *)butPtr->tkwin; - TkMacOSXComputeMenuButtonParams(butPtr, &mbPtr->btnkind, &mbPtr->drawinfo); cntrRect = CGRectMake(winPtr->privatePtr->xOff, winPtr->privatePtr->yOff, - Tk_Width(butPtr->tkwin), - Tk_Height(butPtr->tkwin)); + Tk_Width(butPtr->tkwin), Tk_Height(butPtr->tkwin)); if (useNewerHITools == 1) { HIRect contHIRec; static HIThemeButtonDrawInfo hiinfo; - MenuButtonBackgroundDrawCB((MacMenuButton*) mbPtr, 32, true); - + MenuButtonBackgroundDrawCB(mbPtr, 32, true); if (!TkMacOSXSetupDrawingContext(pixmap, dpPtr->gc, 1, &dc)) { return; } - hiinfo.version = 0; hiinfo.state = mbPtr->drawinfo.state; - hiinfo.kind = mbPtr->btnkind; + hiinfo.kind = mbPtr->btnkind; hiinfo.value = mbPtr->drawinfo.value; hiinfo.adornment = mbPtr->drawinfo.adornment; hiinfo.animation.time.current = CFAbsoluteTimeGetCurrent(); @@ -599,10 +581,10 @@ TkMacOSXDrawMenuButton( } HIThemeDrawButton(&cntrRect, &hiinfo, dc.context, - kHIThemeOrientationNormal, &contHIRec); + kHIThemeOrientationNormal, &contHIRec); TkMacOSXRestoreDrawingContext(&dc); - MenuButtonContentDrawCB( mbPtr->btnkind, &mbPtr->drawinfo, - (MacMenuButton *)mbPtr, 32, true); + MenuButtonContentDrawCB(mbPtr->btnkind, &mbPtr->drawinfo, + mbPtr, 32, true); } else { if (!TkMacOSXSetupDrawingContext(pixmap, dpPtr->gc, 1, &dc)) { return; @@ -617,33 +599,34 @@ TkMacOSXDrawMenuButton( * * MenuButtonBackgroundDrawCB -- * - * This function draws the background that - * lies under checkboxes and radiobuttons. + * This function draws the background that lies under checkboxes and + * radiobuttons. * * Results: - * None. + * None. * * Side effects: - * The background gets updated to the current color. + * The background gets updated to the current color. * *-------------------------------------------------------------- */ + static void MenuButtonBackgroundDrawCB ( MacMenuButton *ptr, SInt16 depth, Boolean isColorDev) { - TkMenuButton* butPtr = (TkMenuButton*)ptr; - Tk_Window tkwin = butPtr->tkwin; + TkMenuButton* butPtr = (TkMenuButton *) ptr; + Tk_Window tkwin = butPtr->tkwin; Pixmap pixmap; + if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; } - pixmap = (Pixmap)Tk_WindowId(tkwin); - + pixmap = (Pixmap) Tk_WindowId(tkwin); Tk_Fill3DRectangle(tkwin, pixmap, butPtr->normalBorder, 0, 0, - Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); + Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT); } /* @@ -651,16 +634,17 @@ MenuButtonBackgroundDrawCB ( * * MenuButtonContentDrawCB -- * - * This function draws the label and image for the button. + * This function draws the label and image for the button. * * Results: - * None. + * None. * * Side effects: - * The content of the button gets updated. + * The content of the button gets updated. * *-------------------------------------------------------------- */ + static void MenuButtonContentDrawCB ( ThemeButtonKind kind, @@ -669,8 +653,8 @@ MenuButtonContentDrawCB ( SInt16 depth, Boolean isColorDev) { - TkMenuButton *butPtr = (TkMenuButton *)ptr; - Tk_Window tkwin = butPtr->tkwin; + TkMenuButton *butPtr = (TkMenuButton *) ptr; + Tk_Window tkwin = butPtr->tkwin; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { return; @@ -683,8 +667,8 @@ MenuButtonContentDrawCB ( * * MenuButtonEventProc -- * - * This procedure is invoked by the Tk dispatcher for various - * events on buttons. + * This procedure is invoked by the Tk dispatcher for various events on + * buttons. * * Results: * None. @@ -700,8 +684,8 @@ MenuButtonEventProc( ClientData clientData, /* Information about window. */ XEvent *eventPtr) /* Information about event. */ { - TkMenuButton *buttonPtr = (TkMenuButton *) clientData; - MacMenuButton *mbPtr = (MacMenuButton *) clientData; + TkMenuButton *buttonPtr = clientData; + MacMenuButton *mbPtr = clientData; if (eventPtr->type == ActivateNotify || eventPtr->type == DeactivateNotify) { @@ -725,9 +709,9 @@ MenuButtonEventProc( * * TkMacOSXComputeMenuButtonParams -- * - * This procedure computes the various parameters used - * when creating a Carbon Appearance control. - * These are determined by the various tk button parameters + * This procedure computes the various parameters used when creating a + * Carbon Appearance control. These are determined by the various Tk + * button parameters * * Results: * None. @@ -740,11 +724,11 @@ MenuButtonEventProc( static void TkMacOSXComputeMenuButtonParams( - TkMenuButton * butPtr, - ThemeButtonKind* btnkind, + TkMenuButton *butPtr, + ThemeButtonKind *btnkind, HIThemeButtonDrawInfo *drawinfo) { - MacMenuButton *mbPtr = (MacMenuButton *)butPtr; + MacMenuButton *mbPtr = (MacMenuButton *) butPtr; if (butPtr->image || butPtr->bitmap || butPtr->text) { /* TODO: allow for Small and Mini menubuttons. */ @@ -789,25 +773,25 @@ TkMacOSXComputeMenuButtonParams( * * TkMacOSXComputeMenuButtonDrawParams -- * - * This procedure selects an appropriate drawing context for - * drawing a menubutton. + * This procedure selects an appropriate drawing context for drawing a + * menubutton. * * Results: - * None. + * None. * * Side effects: - * Sets the button draw parameters. + * Sets the button draw parameters. * *---------------------------------------------------------------------- */ static void TkMacOSXComputeMenuButtonDrawParams( - TkMenuButton * butPtr, - DrawParams * dpPtr) + TkMenuButton *butPtr, + DrawParams *dpPtr) { - dpPtr->hasImageOrBitmap = ((butPtr->image != NULL) || - (butPtr->bitmap != None)); + dpPtr->hasImageOrBitmap = + ((butPtr->image != NULL) || (butPtr->bitmap != None)); dpPtr->border = butPtr->normalBorder; if ((butPtr->state == STATE_DISABLED) && (butPtr->disabledFg != NULL)) { dpPtr->gc = butPtr->disabledGC; @@ -818,7 +802,7 @@ TkMacOSXComputeMenuButtonDrawParams( dpPtr->gc = butPtr->normalTextGC; } } - + /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXMenus.c b/macosx/tkMacOSXMenus.c index 7d77d23..bc66a10 100644 --- a/macosx/tkMacOSXMenus.c +++ b/macosx/tkMacOSXMenus.c @@ -177,7 +177,6 @@ static Tcl_Obj * GetWidgetDemoPath(Tcl_Interp *interp); SEL action = [anItem action]; if (sel_isEqual(action, @selector(preferences:))) { - return (_eventInterp && Tcl_FindCommand(_eventInterp, "::tk::mac::ShowPreferences", NULL, 0)); } else if (sel_isEqual(action, @selector(tkDemo:))) { @@ -322,19 +321,18 @@ static Tcl_Obj * GetWidgetDemoPath( Tcl_Interp *interp) { - Tcl_Obj *libpath, *result = NULL; + Tcl_Obj *result = NULL; - libpath = Tcl_GetVar2Ex(interp, "tk_library", NULL, TCL_GLOBAL_ONLY); - if (libpath) { - Tcl_Obj *demo[2] = { Tcl_NewStringObj("demos", 5), - Tcl_NewStringObj("widget", 6) }; + if (Tcl_EvalEx(interp, "::tk::pkgconfig get demodir,runtime", + -1, TCL_EVAL_GLOBAL) == TCL_OK) { + Tcl_Obj *libpath, *demo[1] = { Tcl_NewStringObj("widget", 6) }; + libpath = Tcl_GetObjResult(interp); Tcl_IncrRefCount(libpath); - result = Tcl_FSJoinToPath(libpath, 2, demo); + result = Tcl_FSJoinToPath(libpath, 1, demo); Tcl_DecrRefCount(libpath); - } else { - Tcl_ResetResult(interp); } + Tcl_ResetResult(interp); return result; } diff --git a/macosx/tkMacOSXMouseEvent.c b/macosx/tkMacOSXMouseEvent.c index ec713d4..adc24b1 100644 --- a/macosx/tkMacOSXMouseEvent.c +++ b/macosx/tkMacOSXMouseEvent.c @@ -24,11 +24,12 @@ typedef struct { Point global; Point local; } MouseEventData; -static Tk_Window captureWinPtr = NULL; /* Current capture window; may be NULL. */ +static Tk_Window captureWinPtr = NULL; /* Current capture window; may be + * NULL. */ static int GenerateButtonEvent(MouseEventData *medPtr); static unsigned int ButtonModifiers2State(UInt32 buttonState, - UInt32 keyModifiers); + UInt32 keyModifiers); #pragma mark TKApplication(TKMouseEvent) @@ -48,14 +49,15 @@ enum { @implementation TKApplication(TKMouseEvent) - (NSEvent *) tkProcessMouseEvent: (NSEvent *) theEvent { - NSWindow* eventWindow = [theEvent window]; - NSEventType eventType = [theEvent type]; + NSWindow *eventWindow = [theEvent window]; + NSEventType eventType = [theEvent type]; TkWindow *winPtr, *grabWinPtr; Tk_Window tkwin; #if 0 - NSTrackingArea *trackingArea = nil; + NSTrackingArea *trackingArea = nil; NSInteger eventNumber, clickCount, buttonNumber; #endif + #ifdef TK_MAC_DEBUG_EVENTS TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, theEvent); #endif @@ -76,12 +78,15 @@ enum { case NSTabletPoint: case NSTabletProximity: case NSScrollWheel: - break; + break; default: /* Unrecognized mouse event. */ return theEvent; } - /* Remember the window in case we need it next time. */ + /* + * Remember the window in case we need it next time. + */ + if (eventWindow && eventWindow != _windowWithMouse) { if (_windowWithMouse) { [_windowWithMouse release]; @@ -91,8 +96,8 @@ enum { } /* - * Compute the mouse position in Tk screen coordinates (global) and in - * the Tk coordinates of its containing Tk Window. + * Compute the mouse position in Tk screen coordinates (global) and in the + * Tk coordinates of its containing Tk Window. */ NSPoint global, local = [theEvent locationInWindow]; @@ -106,7 +111,6 @@ enum { eventWindow = _windowWithMouse; } if (eventWindow) { - /* * Set the local mouse position to its NSWindow flipped coordinates, * with the origin at top left, and the global mouse position to the @@ -116,9 +120,7 @@ enum { global = [eventWindow tkConvertPointToScreen: local]; local.y = [eventWindow frame].size.height - local.y; global.y = tkMacOSXZeroScreenHeight - global.y; - } else { - /* * As a last resort, with no NSWindow to work with, set both local and * global to the screen coordinates. @@ -135,13 +137,15 @@ enum { winPtr = TkMacOSXGetTkWindow(eventWindow); if (winPtr == NULL) { tkwin = TkMacOSXGetCapture(); - winPtr = (TkWindow *)tkwin; + winPtr = (TkWindow *) tkwin; } else { tkwin = (Tk_Window) winPtr; } if (!tkwin) { +#ifdef TK_MAC_DEBUG_EVENTS TkMacOSXDbgMsg("tkwin == NULL"); - return theEvent; /* Give up. No window for this event. */ +#endif + return theEvent; /* Give up. No window for this event. */ } /* @@ -150,9 +154,9 @@ enum { grabWinPtr = winPtr->dispPtr->grabWinPtr; if (grabWinPtr && - grabWinPtr != winPtr && - !winPtr->dispPtr->grabFlags && /* this means the grab is local. */ - grabWinPtr->mainPtr == winPtr->mainPtr) { + grabWinPtr != winPtr && + !winPtr->dispPtr->grabFlags && /* this means the grab is local. */ + grabWinPtr->mainPtr == winPtr->mainPtr) { return theEvent; } @@ -184,7 +188,7 @@ enum { EventRef eventRef = (EventRef)[theEvent eventRef]; UInt32 buttons; OSStatus err = GetEventParameter(eventRef, kEventParamMouseChord, - typeUInt32, NULL, sizeof(UInt32), NULL, &buttons); + typeUInt32, NULL, sizeof(UInt32), NULL, &buttons); if (err == noErr) { state |= (buttons & ((1<<5) - 1)) << 8; @@ -227,21 +231,20 @@ enum { } if (eventType != NSScrollWheel) { - /* * For normal mouse events, Tk_UpdatePointer will send the XEvent. */ #ifdef TK_MAC_DEBUG_EVENTS - TKLog(@"UpdatePointer %p x %f.0 y %f.0 %d", tkwin, global.x, global.y, state); + TKLog(@"UpdatePointer %p x %f.0 y %f.0 %d", + tkwin, global.x, global.y, state); #endif Tk_UpdatePointer(tkwin, global.x, global.y, state); } else { - /* * For scroll wheel events we need to send the XEvent here. */ - + CGFloat delta; int coarseDelta; XEvent xEvent; @@ -258,7 +261,7 @@ enum { delta = [theEvent deltaY]; if (delta != 0.0) { coarseDelta = (delta > -1.0 && delta < 1.0) ? - (signbit(delta) ? -1 : 1) : lround(delta); + (signbit(delta) ? -1 : 1) : lround(delta); xEvent.xbutton.state = state; xEvent.xkey.keycode = coarseDelta; xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); @@ -267,7 +270,7 @@ enum { delta = [theEvent deltaX]; if (delta != 0.0) { coarseDelta = (delta > -1.0 && delta < 1.0) ? - (signbit(delta) ? -1 : 1) : lround(delta); + (signbit(delta) ? -1 : 1) : lround(delta); xEvent.xbutton.state = state | ShiftMask; xEvent.xkey.keycode = coarseDelta; xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin)); @@ -627,10 +630,8 @@ TkpWarpPointer( buttonState = [NSEvent pressedMouseButtons]; CGEventType type = kCGEventMouseMoved; - CGEventRef theEvent = CGEventCreateMouseEvent(NULL, - type, - pt, - buttonState); + CGEventRef theEvent = CGEventCreateMouseEvent(NULL, type, pt, + buttonState); CGWarpMouseCursorPosition(pt); CGEventPost(kCGHIDEventTap, theEvent); CFRelease(theEvent); @@ -684,8 +685,6 @@ TkMacOSXGetCapture(void) return captureWinPtr; } - - /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c index 3a32527..7267b00 100644 --- a/macosx/tkMacOSXNotify.c +++ b/macosx/tkMacOSXNotify.c @@ -33,7 +33,7 @@ static void TkMacOSXEventsSetupProc(ClientData clientData, int flags); static void TkMacOSXEventsCheckProc(ClientData clientData, int flags); #ifdef TK_MAC_DEBUG_EVENTS -static char* Tk_EventName[39] = { +static const char *Tk_EventName[39] = { "", "", "KeyPress", /*2*/ @@ -136,8 +136,8 @@ void DebugPrintQueue(void) /* * Since the contentView is the first responder for a Tk Window, it is - * responsible for sending events up the responder chain. We also check - * the pasteboard here. + * responsible for sending events up the responder chain. We also check the + * pasteboard here. */ - (void) sendEvent: (NSEvent *) theEvent { @@ -190,8 +190,8 @@ GetRunLoopMode(NSModalSession modalSession) * * Tk_MacOSXSetupTkNotifier -- * - * This procedure is called during Tk initialization to create - * the event source for TkAqua events. + * This procedure is called during Tk initialization to create the event + * source for TkAqua events. * * Results: * None. @@ -224,8 +224,7 @@ Tk_MacOSXSetupTkNotifier(void) "first [load] of TkAqua has to occur in the main thread!"); } Tcl_CreateEventSource(TkMacOSXEventsSetupProc, - TkMacOSXEventsCheckProc, - NULL); + TkMacOSXEventsCheckProc, NULL); TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL); Tcl_SetServiceMode(TCL_SERVICE_ALL); TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode); @@ -258,8 +257,7 @@ TkMacOSXNotifyExitHandler( TSD_INIT(); Tcl_DeleteEventSource(TkMacOSXEventsSetupProc, - TkMacOSXEventsCheckProc, - NULL); + TkMacOSXEventsCheckProc, NULL); tsdPtr->initialized = 0; } @@ -268,19 +266,19 @@ TkMacOSXNotifyExitHandler( * * TkMacOSXEventsSetupProc -- * - * This procedure implements the setup part of the MacOSX event - * source. It is invoked by Tcl_DoOneEvent before calling - * TkMacOSXEventsProc to process all queued NSEvents. In our - * case, all we need to do is to set the Tcl MaxBlockTime to - * 0 before starting the loop to process all queued NSEvents. + * This procedure implements the setup part of the MacOSX event source. It + * is invoked by Tcl_DoOneEvent before calling TkMacOSXEventsProc to + * process all queued NSEvents. In our case, all we need to do is to set + * the Tcl MaxBlockTime to 0 before starting the loop to process all + * queued NSEvents. * * Results: * None. * * Side effects: * - * If NSEvents are queued, then the maximum block time will be set - * to 0 to ensure that control returns immediately to Tcl. + * If NSEvents are queued, then the maximum block time will be set to 0 to + * ensure that control returns immediately to Tcl. * *---------------------------------------------------------------------- */ @@ -291,15 +289,24 @@ TkMacOSXEventsSetupProc( int flags) { NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; - /* runloopMode will be nil if we are in a Tcl event loop. */ + + /* + * runloopMode will be nil if we are in a Tcl event loop. + */ + if (flags & TCL_WINDOW_EVENTS && !runloopMode) { static const Tcl_Time zeroBlockTime = { 0, 0 }; [NSApp _resetAutoreleasePool]; - /* Call this with dequeue=NO -- just checking if the queue is empty. */ - NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:GetRunLoopMode(TkMacOSXGetModalSession()) - dequeue:NO]; + + /* + * Call this with dequeue=NO -- just checking if the queue is empty. + */ + + NSEvent *currentEvent = + [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:GetRunLoopMode(TkMacOSXGetModalSession()) + dequeue:NO]; if (currentEvent) { if (currentEvent.type > 0) { Tcl_SetMaxBlockTime(&zeroBlockTime); @@ -313,15 +320,15 @@ TkMacOSXEventsSetupProc( * * TkMacOSXEventsCheckProc -- * - * This procedure loops through all NSEvents waiting in the - * TKApplication event queue, generating X events from them. + * This procedure loops through all NSEvents waiting in the TKApplication + * event queue, generating X events from them. * * Results: * None. * * Side effects: - * NSevents are used to generate X events, which are added to the - * Tcl event queue. + * NSevents are used to generate X events, which are added to the Tcl + * event queue. * *---------------------------------------------------------------------- */ @@ -331,34 +338,48 @@ TkMacOSXEventsCheckProc( int flags) { NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; - /* runloopMode will be nil if we are in a Tcl event loop. */ + + /* + * runloopMode will be nil if we are in a Tcl event loop. + */ + if (flags & TCL_WINDOW_EVENTS && !runloopMode) { NSEvent *currentEvent = nil; NSEvent *testEvent = nil; NSModalSession modalSession; - /* It is possible for the SetupProc to be called before this function + + /* + * It is possible for the SetupProc to be called before this function * returns. This happens, for example, when we process an event which * opens a modal window. To prevent premature release of our * application-wide autorelease pool by a nested call to the SetupProc, * we must lock it here. */ + [NSApp _lockAutoreleasePool]; do { modalSession = TkMacOSXGetModalSession(); - testEvent = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:GetRunLoopMode(modalSession) - dequeue:NO]; - /* We must not steal any events during LiveResize. */ + testEvent = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantPast] + inMode:GetRunLoopMode(modalSession) + dequeue:NO]; + + /* + * We must not steal any events during LiveResize. + */ + if (testEvent && [[testEvent window] inLiveResize]) { break; } currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:GetRunLoopMode(modalSession) - dequeue:YES]; + untilDate:[NSDate distantPast] + inMode:GetRunLoopMode(modalSession) + dequeue:YES]; if (currentEvent) { - /* Generate Xevents. */ + /* + * Generate Xevents. + */ + int oldServiceMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); NSEvent *processedEvent = [NSApp tkProcessEvent:currentEvent]; Tcl_SetServiceMode(oldServiceMode); @@ -372,16 +393,18 @@ TkMacOSXEventsCheckProc( [NSApp sendEvent:currentEvent]; } } - } else { break; } } while (1); - /* Now we can unlock the pool. */ + + /* + * Now we can unlock the pool. + */ + [NSApp _unlockAutoreleasePool]; } } - /* * Local Variables: diff --git a/macosx/tkMacOSXPort.h b/macosx/tkMacOSXPort.h index bd48bd5..617abda 100644 --- a/macosx/tkMacOSXPort.h +++ b/macosx/tkMacOSXPort.h @@ -128,17 +128,6 @@ #define TkpSync(display) /* - * The following macro returns the pixel value that corresponds to the - * RGB values in the given XColor structure. - */ - -#define PIXEL_MAGIC ((unsigned char) 0x69) -#define TkpGetPixel(p) ((((((PIXEL_MAGIC << 8) \ - | (((p)->red >> 8) & 0xff)) << 8) \ - | (((p)->green >> 8) & 0xff)) << 8) \ - | (((p)->blue >> 8) & 0xff)) - -/* * This macro stores a representation of the window handle in a string. */ @@ -159,19 +148,20 @@ */ #define TRANSPARENT_PIXEL 30 -#define HIGHLIGHT_PIXEL 31 -#define HIGHLIGHT_SECONDARY_PIXEL 32 -#define HIGHLIGHT_TEXT_PIXEL 33 -#define HIGHLIGHT_ALTERNATE_PIXEL 34 -#define CONTROL_TEXT_PIXEL 35 -#define CONTROL_BODY_PIXEL 37 -#define CONTROL_FRAME_PIXEL 39 -#define WINDOW_BODY_PIXEL 41 -#define MENU_ACTIVE_PIXEL 43 -#define MENU_ACTIVE_TEXT_PIXEL 45 -#define MENU_BACKGROUND_PIXEL 47 -#define MENU_DISABLED_PIXEL 49 -#define MENU_TEXT_PIXEL 51 #define APPEARANCE_PIXEL 52 +#define PIXEL_MAGIC ((unsigned char) 0x69) + +/* + * The following macro returns the pixel value that corresponds to the + * 16-bit RGB values in the given XColor structure. + * The format is: (PIXEL_MAGIC <<< 24) | (R << 16) | (G << 8) | B + * where each of R, G and B is the high order byte of a 16-bit component. + */ + +#define TkpGetPixel(p) ((((((PIXEL_MAGIC << 8) \ + | (((p)->red >> 8) & 0xff)) << 8) \ + | (((p)->green >> 8) & 0xff)) << 8) \ + | (((p)->blue >> 8) & 0xff)) + #endif /* _TKMACPORT */ diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 105317d..b4da2b3 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -233,6 +233,8 @@ MODULE_SCOPE int TkMacOSXStandardAboutPanelObjCmd(ClientData clientData, MODULE_SCOPE int TkMacOSXIconBitmapObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); +MODULE_SCOPE void TkMacOSXDrawSolidBorder(Tk_Window tkwin, GC gc, + int inset, int thickness); #pragma mark Private Objective-C Classes @@ -332,8 +334,14 @@ VISIBILITY_HIDDEN VISIBILITY_HIDDEN @interface TKContentView : NSView <NSTextInput> { +@private NSString *privateWorkingText; +#ifdef __i386__ + /* The Objective C runtime used on i386 requires this. */ + Bool _needsRedisplay; +#endif } +@property Bool needsRedisplay; @end @interface TKContentView(TKKeyEvent) diff --git a/macosx/tkMacOSXRegion.c b/macosx/tkMacOSXRegion.c index 0f2a74a..323318c 100644 --- a/macosx/tkMacOSXRegion.c +++ b/macosx/tkMacOSXRegion.c @@ -187,8 +187,8 @@ TkMacOSXIsEmptyRegion( * Xwindow documentation for more details. * * Results: - * Returns RectanglePart or RectangleOut. Note that this is not a - * complete implementation since it doesn't test for RectangleIn. + * Returns RectanglePart or RectangleOut. Note that this is not a complete + * implementation since it doesn't test for RectangleIn. * * Side effects: * None. @@ -204,13 +204,13 @@ TkRectInRegion( unsigned int width, unsigned int height) { - if ( TkMacOSXIsEmptyRegion(region) ) { - return RectangleOut; - } - else { + if (TkMacOSXIsEmptyRegion(region)) { + return RectangleOut; + } else { const CGRect r = CGRectMake(x, y, width, height); + return HIShapeIntersectsRect((HIShapeRef) region, &r) ? - RectanglePart : RectangleOut; + RectanglePart : RectangleOut; } } @@ -234,7 +234,7 @@ TkRectInRegion( void TkClipBox( TkRegion r, - XRectangle* rect_return) + XRectangle *rect_return) { CGRect rect; diff --git a/macosx/tkMacOSXScale.c b/macosx/tkMacOSXScale.c index 8a6a96b..0195ffb 100644 --- a/macosx/tkMacOSXScale.c +++ b/macosx/tkMacOSXScale.c @@ -51,10 +51,10 @@ static ControlActionUPP scaleActionProc = NULL; /* Pointer to func. */ * Forward declarations for procedures defined later in this file: */ -static void MacScaleEventProc(ClientData clientData, XEvent *eventPtr); -static pascal void ScaleActionProc(ControlRef theControl, - ControlPartCode partCode); - +static void MacScaleEventProc(ClientData clientData, + XEvent *eventPtr); +static pascal void ScaleActionProc(ControlRef theControl, + ControlPartCode partCode); /* *---------------------------------------------------------------------- @@ -84,7 +84,7 @@ TkpCreateScale( } Tk_CreateEventHandler(tkwin, ButtonPressMask, - MacScaleEventProc, (ClientData) macScalePtr); + MacScaleEventProc, macScalePtr); return (TkScale *) macScalePtr; } @@ -125,8 +125,8 @@ TkpDestroyScale( * * TkpDisplayScale -- * - * This procedure is invoked as an idle handler to redisplay - * the contents of a scale widget. + * This procedure is invoked as an idle handler to redisplay the contents + * of a scale widget. * * Results: * None. @@ -141,12 +141,12 @@ void TkpDisplayScale( ClientData clientData) /* Widget record for scale. */ { - TkScale *scalePtr = (TkScale *) clientData; + TkScale *scalePtr = clientData; Tk_Window tkwin = scalePtr->tkwin; Tcl_Interp *interp = scalePtr->interp; int result; char string[TCL_DOUBLE_SPACE]; - MacScale *macScalePtr = (MacScale *) clientData; + MacScale *macScalePtr = clientData; Rect r; WindowRef windowRef; CGrafPtr destPort, savePort; @@ -168,9 +168,9 @@ TkpDisplayScale( * Invoke the scale's command if needed. */ - Tcl_Preserve((ClientData) scalePtr); + Tcl_Preserve(scalePtr); if ((scalePtr->flags & INVOKE_COMMAND) && (scalePtr->command != NULL)) { - Tcl_Preserve((ClientData) interp); + Tcl_Preserve(interp); if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->format, scalePtr->value) < 0) { string[TCL_DOUBLE_SPACE - 1] = '\0'; @@ -185,19 +185,18 @@ TkpDisplayScale( Tcl_AddErrorInfo(interp, "\n (command executed by scale)"); Tcl_BackgroundException(interp, result); } - Tcl_Release((ClientData) interp); + Tcl_Release(interp); } scalePtr->flags &= ~INVOKE_COMMAND; if (scalePtr->flags & SCALE_DELETED) { - Tcl_Release((ClientData) scalePtr); + Tcl_Release(scalePtr); return; } - Tcl_Release((ClientData) scalePtr); + Tcl_Release(scalePtr); /* - * Now handle the part of redisplay that is the same for - * horizontal and vertical scales: border and traversal - * highlight. + * Now handle the part of redisplay that is the same for horizontal and + * vertical scales: border and traversal highlight. */ if (scalePtr->highlightWidth != 0) { @@ -229,7 +228,7 @@ TkpDisplayScale( #define MAC_OSX_SCROLL_WIDTH 10 if (scalePtr->orient == ORIENT_HORIZONTAL) { - int offset = (Tk_Height(tkwin) - MAC_OSX_SCROLL_WIDTH)/2; + int offset = (Tk_Height(tkwin) - MAC_OSX_SCROLL_WIDTH) / 2; if (offset < 0) { offset = 0; @@ -240,7 +239,7 @@ TkpDisplayScale( r.right = macDraw->xOff+Tk_Width(tkwin) - scalePtr->inset; r.bottom = macDraw->yOff + offset + MAC_OSX_SCROLL_WIDTH/2; } else { - int offset = (Tk_Width(tkwin) - MAC_OSX_SCROLL_WIDTH)/2; + int offset = (Tk_Width(tkwin) - MAC_OSX_SCROLL_WIDTH) / 2; if (offset < 0) { offset = 0; @@ -249,7 +248,7 @@ TkpDisplayScale( r.left = macDraw->xOff + offset; r.top = macDraw->yOff + scalePtr->inset; r.right = macDraw->xOff + offset + MAC_OSX_SCROLL_WIDTH/2; - r.bottom = macDraw->yOff+Tk_Height(tkwin) - scalePtr->inset; + r.bottom = macDraw->yOff + Tk_Height(tkwin) - scalePtr->inset; } if (macScalePtr->scaleHandle == NULL) { @@ -273,7 +272,7 @@ TkpDisplayScale( CreateSliderControl(windowRef, &r, initialValue, minValue, maxValue, kControlSliderPointsDownOrRight, numTicks, 1, scaleActionProc, - &(macScalePtr->scaleHandle)); + &macScalePtr->scaleHandle); SetControlReference(macScalePtr->scaleHandle, (UInt32) scalePtr); if (IsWindowActive(windowRef)) { @@ -290,8 +289,8 @@ TkpDisplayScale( * Finally draw the control. */ - SetControlVisibility(macScalePtr->scaleHandle,true,true); - HiliteControl(macScalePtr->scaleHandle,0); + SetControlVisibility(macScalePtr->scaleHandle, true, true); + HiliteControl(macScalePtr->scaleHandle, 0); Draw1Control(macScalePtr->scaleHandle); if (portChanged) { @@ -306,13 +305,12 @@ done: * * TkpScaleElement -- * - * Determine which part of a scale widget lies under a given - * point. + * Determine which part of a scale widget lies under a given point. * * Results: - * The return value is either TROUGH1, SLIDER, TROUGH2, or - * OTHER, depending on which of the scale's active elements - * (if any) is under the point at (x,y). + * The return value is either TROUGH1, SLIDER, TROUGH2, or OTHER, + * depending on which of the scale's active elements (if any) is under the + * point at (x,y). * * Side effects: * None. @@ -357,22 +355,22 @@ TkpScaleElement( #endif switch (part) { - case inSlider: - return SLIDER; - case inInc: - if (scalePtr->orient == ORIENT_VERTICAL) { - return TROUGH1; - } else { - return TROUGH2; - } - case inDecr: - if (scalePtr->orient == ORIENT_VERTICAL) { - return TROUGH2; - } else { - return TROUGH1; - } - default: - return OTHER; + case inSlider: + return SLIDER; + case inInc: + if (scalePtr->orient == ORIENT_VERTICAL) { + return TROUGH1; + } else { + return TROUGH2; + } + case inDecr: + if (scalePtr->orient == ORIENT_VERTICAL) { + return TROUGH2; + } else { + return TROUGH1; + } + default: + return OTHER; } } @@ -381,15 +379,15 @@ TkpScaleElement( * * MacScaleEventProc -- * - * This procedure is invoked by the Tk dispatcher for - * ButtonPress events on scales. + * This procedure is invoked by the Tk dispatcher for ButtonPress events + * on scales. * * Results: * None. * * Side effects: - * When the window gets deleted, internal structures get - * cleaned up. When it gets exposed, it is redisplayed. + * When the window gets deleted, internal structures get cleaned up. When + * it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ @@ -411,9 +409,9 @@ MacScaleEventProc( #endif /* - * To call Macintosh control routines we must have the port - * set to the window containing the control. We will then test - * which part of the control was hit and act accordingly. + * To call Macintosh control routines we must have the port set to the + * window containing the control. We will then test which part of the + * control was hit and act accordingly. */ destPort = TkMacOSXGetDrawablePort(Tk_WindowId(macScalePtr->info.tkwin)); @@ -461,8 +459,8 @@ MacScaleEventProc( * ScaleActionProc -- * * Callback procedure used by the Macintosh toolbox call - * HandleControlClick. This call will update the display - * while the scrollbar is being manipulated by the user. + * HandleControlClick. This call will update the display while the + * scrollbar is being manipulated by the user. * * Results: * None. @@ -486,9 +484,9 @@ ScaleActionProc( #endif value = GetControlValue(theControl); TkScaleSetValue(scalePtr, value, 1, 1); - Tcl_Preserve((ClientData) scalePtr); + Tcl_Preserve(scalePtr); TkMacOSXRunTclEventLoop(); - Tcl_Release((ClientData) scalePtr); + Tcl_Release(scalePtr); } #endif diff --git a/macosx/tkMacOSXScrlbr.c b/macosx/tkMacOSXScrlbr.c index 4a108db..06e0a64 100644 --- a/macosx/tkMacOSXScrlbr.c +++ b/macosx/tkMacOSXScrlbr.c @@ -9,6 +9,7 @@ * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * Copyright (c) 2015 Kevin Walzer/WordTech Commununications LLC. * Copyright (c) 2018 Marc Culler + * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. */ @@ -17,7 +18,6 @@ #include "tkScrollbar.h" #include "tkMacOSXPrivate.h" - #define MIN_SCROLLBAR_VALUE 0 /* @@ -27,18 +27,21 @@ #define MIN_SLIDER_LENGTH 5 -/*Borrowed from ttkMacOSXTheme.c to provide appropriate scaling.*/ +/* + * Borrowed from ttkMacOSXTheme.c to provide appropriate scaling. + */ + #ifdef __LP64__ -#define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) +#define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) #else -#define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) +#define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) #endif /* __LP64__ */ /* * Apple reversed the scroll direction with the release of OSX 10.7 Lion. */ -#define SNOW_LEOPARD_STYLE (NSAppKitVersionNumber < 1138) +#define SNOW_LEOPARD_STYLE (NSAppKitVersionNumber < 1138) /* * Declaration of an extended scrollbar structure with Mac specific additions. @@ -50,7 +53,7 @@ typedef struct MacScrollbar { GC copyGC; /* Used for copying from pixmap onto screen. */ Bool buttonDown; /* Is the mouse button down? */ Bool mouseOver; /* Is the pointer over the scrollbar. */ - HIThemeTrackDrawInfo info; /* Controls how the scrollbar is drawn. */ + HIThemeTrackDrawInfo info; /* Controls how the scrollbar is drawn. */ } MacScrollbar; /* Used to initialize a MacScrollbar's info field. */ @@ -69,33 +72,35 @@ HIThemeTrackDrawInfo defaultInfo = { const Tk_ClassProcs tkpScrollbarProcs = { sizeof(Tk_ClassProcs), /* size */ - NULL, /* worldChangedProc */ - NULL, /* createProc */ - NULL /* modalProc */ + NULL, /* worldChangedProc */ + NULL, /* createProc */ + NULL /* modalProc */ }; +/* + * Information on scrollbar layout, metrics, and draw info. + */ -/* Information on scrollbar layout, metrics, and draw info.*/ typedef struct ScrollbarMetrics { SInt32 width, minThumbHeight; int minHeight, topArrowHeight, bottomArrowHeight; NSControlSize controlSize; } ScrollbarMetrics; - static ScrollbarMetrics metrics = { - 15, 54, 26, 14, 14, kControlSizeNormal /* kThemeScrollBarMedium */ + 15, 54, 26, 14, 14, kControlSizeNormal /* kThemeScrollBarMedium */ }; - /* * Declarations of static functions defined later in this file: */ -static void ScrollbarEventProc(ClientData clientData, XEvent *eventPtr); -static int ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr); -static void UpdateControlValues(TkScrollbar *scrollPtr); - +static void ScrollbarEventProc(ClientData clientData, + XEvent *eventPtr); +static int ScrollbarEvent(TkScrollbar *scrollPtr, + XEvent *eventPtr); +static void UpdateControlValues(TkScrollbar *scrollPtr); + /* *---------------------------------------------------------------------- * @@ -114,10 +119,9 @@ static void UpdateControlValues(TkScrollbar *scrollPtr); TkScrollbar * TkpCreateScrollbar( - Tk_Window tkwin) + Tk_Window tkwin) { - - MacScrollbar *scrollPtr = (MacScrollbar *)ckalloc(sizeof(MacScrollbar)); + MacScrollbar *scrollPtr = ckalloc(sizeof(MacScrollbar)); scrollPtr->troughGC = NULL; scrollPtr->copyGC = NULL; @@ -125,15 +129,15 @@ TkpCreateScrollbar( scrollPtr->buttonDown = false; Tk_CreateEventHandler(tkwin, - ExposureMask | - StructureNotifyMask | - FocusChangeMask | - ButtonPressMask | - ButtonReleaseMask | - EnterWindowMask | - LeaveWindowMask | - VisibilityChangeMask, - ScrollbarEventProc, scrollPtr); + ExposureMask | + StructureNotifyMask | + FocusChangeMask | + ButtonPressMask | + ButtonReleaseMask | + EnterWindowMask | + LeaveWindowMask | + VisibilityChangeMask, + ScrollbarEventProc, scrollPtr); return (TkScrollbar *) scrollPtr; } @@ -144,8 +148,8 @@ TkpCreateScrollbar( * TkpDisplayScrollbar -- * * This procedure redraws the contents of a scrollbar window. It is - * invoked as a do-when-idle handler, so it only runs when there's - * nothing else for the application to do. + * invoked as a do-when-idle handler, so it only runs when there's nothing + * else for the application to do. * * Results: * None. @@ -158,9 +162,9 @@ TkpCreateScrollbar( void TkpDisplayScrollbar( - ClientData clientData) /* Information about window. */ + ClientData clientData) /* Information about window. */ { - register TkScrollbar *scrollPtr = (TkScrollbar *) clientData; + register TkScrollbar *scrollPtr = clientData; MacScrollbar *msPtr = (MacScrollbar *) scrollPtr; register Tk_Window tkwin = scrollPtr->tkwin; TkWindow *winPtr = (TkWindow *) tkwin; @@ -169,23 +173,31 @@ TkpDisplayScrollbar( scrollPtr->flags &= ~REDRAW_PENDING; if (tkwin == NULL || !Tk_IsMapped(tkwin)) { - return; + return; } - MacDrawable *macWin = (MacDrawable *) winPtr->window; + MacDrawable *macWin = (MacDrawable *) winPtr->window; NSView *view = TkMacOSXDrawableView(macWin); - if (!view || - macWin->flags & TK_DO_NOT_DRAW || - !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { - return; + + if ((view == NULL) + || (macWin->flags & TK_DO_NOT_DRAW) + || !TkMacOSXSetupDrawingContext((Drawable) macWin, NULL, 1, &dc)) { + return; } CGFloat viewHeight = [view bounds].size.height; - CGAffineTransform t = { .a = 1, .b = 0, .c = 0, .d = -1, .tx = 0, - .ty = viewHeight}; + CGAffineTransform t = { + .a = 1, .b = 0, + .c = 0, .d = -1, + .tx = 0, .ty = viewHeight + }; + CGContextConcatCTM(dc.context, t); - /*Draw a 3D rectangle to provide a base for the native scrollbar.*/ + /* + * Draw a 3D rectangle to provide a base for the native scrollbar. + */ + if (scrollPtr->highlightWidth != 0) { GC fgGC, bgGC; @@ -196,32 +208,37 @@ TkpDisplayScrollbar( fgGC = bgGC; } TkpDrawHighlightBorder(tkwin, fgGC, bgGC, scrollPtr->highlightWidth, - (Pixmap) macWin); + (Pixmap) macWin); } Tk_Draw3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder, - scrollPtr->highlightWidth, scrollPtr->highlightWidth, - Tk_Width(tkwin) - 2*scrollPtr->highlightWidth, - Tk_Height(tkwin) - 2*scrollPtr->highlightWidth, - scrollPtr->borderWidth, scrollPtr->relief); + scrollPtr->highlightWidth, scrollPtr->highlightWidth, + Tk_Width(tkwin) - 2*scrollPtr->highlightWidth, + Tk_Height(tkwin) - 2*scrollPtr->highlightWidth, + scrollPtr->borderWidth, scrollPtr->relief); Tk_Fill3DRectangle(tkwin, (Pixmap) macWin, scrollPtr->bgBorder, - scrollPtr->inset, scrollPtr->inset, - Tk_Width(tkwin) - 2*scrollPtr->inset, - Tk_Height(tkwin) - 2*scrollPtr->inset, 0, TK_RELIEF_FLAT); + scrollPtr->inset, scrollPtr->inset, + Tk_Width(tkwin) - 2*scrollPtr->inset, + Tk_Height(tkwin) - 2*scrollPtr->inset, 0, TK_RELIEF_FLAT); + + /* + * Update values and then draw the native scrollbar over the rectangle. + */ - /* Update values and then draw the native scrollbar over the rectangle.*/ UpdateControlValues(scrollPtr); if (SNOW_LEOPARD_STYLE) { - HIThemeDrawTrack (&(msPtr->info), 0, dc.context, kHIThemeOrientationInverted); + HIThemeDrawTrack(&msPtr->info, 0, dc.context, + kHIThemeOrientationInverted); } else { - HIThemeDrawTrack (&(msPtr->info), 0, dc.context, kHIThemeOrientationNormal); + HIThemeDrawTrack(&msPtr->info, 0, dc.context, + kHIThemeOrientationNormal); } TkMacOSXRestoreDrawingContext(&dc); scrollPtr->flags &= ~REDRAW_PENDING; } - + /* *---------------------------------------------------------------------- * @@ -240,44 +257,41 @@ TkpDisplayScrollbar( *---------------------------------------------------------------------- */ - - extern void TkpComputeScrollbarGeometry( register TkScrollbar *scrollPtr) - /* Scrollbar whose geometry may have - * changed. */ + /* Scrollbar whose geometry may have + * changed. */ { - - /* - * The code below is borrowed from tkUnixScrlbr.c but has been adjusted to - * account for some differences between macOS and X11. The Unix scrollbar - * has an arrow button on each end. On macOS 10.6 (Snow Leopard) the - * scrollbars by default have both arrow buttons at the bottom or right. - * (There is a preferences setting to use the Unix layout, but we are not - * supporting that!) On more recent versions of macOS there are no arrow - * buttons at all. The case of no arrow buttons can be handled as a special - * case of having both buttons at the end, but where scrollPtr->arrowLength - * happens to be zero. To adjust for having both arrows at the same end we - * shift the scrollbar up by the arrowLength. - */ + /* + * The code below is borrowed from tkUnixScrlbr.c but has been adjusted to + * account for some differences between macOS and X11. The Unix scrollbar + * has an arrow button on each end. On macOS 10.6 (Snow Leopard) the + * scrollbars by default have both arrow buttons at the bottom or right. + * (There is a preferences setting to use the Unix layout, but we are not + * supporting that!) On more recent versions of macOS there are no arrow + * buttons at all. The case of no arrow buttons can be handled as a special + * case of having both buttons at the end, but where scrollPtr->arrowLength + * happens to be zero. To adjust for having both arrows at the same end we + * shift the scrollbar up by the arrowLength. + */ int fieldLength; if (scrollPtr->highlightWidth < 0) { - scrollPtr->highlightWidth = 0; + scrollPtr->highlightWidth = 0; } scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth; if ([NSApp macMinorVersion] == 6) { - scrollPtr->arrowLength = scrollPtr->width; + scrollPtr->arrowLength = scrollPtr->width; } else { - scrollPtr->arrowLength = 0; + scrollPtr->arrowLength = 0; } fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin) - : Tk_Width(scrollPtr->tkwin)) - - 2*(scrollPtr->arrowLength + scrollPtr->inset); + : Tk_Width(scrollPtr->tkwin)) + - 2*(scrollPtr->arrowLength + scrollPtr->inset); if (fieldLength < 0) { - fieldLength = 0; + fieldLength = 0; } scrollPtr->sliderFirst = fieldLength*scrollPtr->firstFraction; scrollPtr->sliderLast = fieldLength*scrollPtr->lastFraction; @@ -289,16 +303,16 @@ TkpComputeScrollbarGeometry( */ if (scrollPtr->sliderFirst > fieldLength - MIN_SLIDER_LENGTH) { - scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH; + scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH; } if (scrollPtr->sliderFirst < 0) { - scrollPtr->sliderFirst = 0; + scrollPtr->sliderFirst = 0; } if (scrollPtr->sliderLast < scrollPtr->sliderFirst + MIN_SLIDER_LENGTH) { - scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH; + scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH; } if (scrollPtr->sliderLast > fieldLength) { - scrollPtr->sliderLast = fieldLength; + scrollPtr->sliderLast = fieldLength; } scrollPtr->sliderFirst += -scrollPtr->arrowLength + scrollPtr->inset; scrollPtr->sliderLast += scrollPtr->inset; @@ -310,20 +324,20 @@ TkpComputeScrollbarGeometry( * be redisplayed. */ - if (scrollPtr->vertical) { - Tk_GeometryRequest(scrollPtr->tkwin, - scrollPtr->width + 2*scrollPtr->inset, - 2*(scrollPtr->arrowLength + scrollPtr->borderWidth - + scrollPtr->inset) + metrics.minThumbHeight); + if (scrollPtr->vertical) { + Tk_GeometryRequest(scrollPtr->tkwin, + scrollPtr->width + 2*scrollPtr->inset, + 2*(scrollPtr->arrowLength + scrollPtr->borderWidth + + scrollPtr->inset) + metrics.minThumbHeight); } else { - Tk_GeometryRequest(scrollPtr->tkwin, - 2*(scrollPtr->arrowLength + scrollPtr->borderWidth - + scrollPtr->inset) + metrics.minThumbHeight, - scrollPtr->width + 2*scrollPtr->inset); + Tk_GeometryRequest(scrollPtr->tkwin, + 2*(scrollPtr->arrowLength + scrollPtr->borderWidth + + scrollPtr->inset) + metrics.minThumbHeight, + scrollPtr->width + 2*scrollPtr->inset); } Tk_SetInternalBorder(scrollPtr->tkwin, scrollPtr->inset); } - + /* *---------------------------------------------------------------------- * @@ -342,9 +356,9 @@ TkpComputeScrollbarGeometry( void TkpDestroyScrollbar( - TkScrollbar *scrollPtr) + TkScrollbar *scrollPtr) { - MacScrollbar *macScrollPtr = (MacScrollbar *)scrollPtr; + MacScrollbar *macScrollPtr = (MacScrollbar *) scrollPtr; if (macScrollPtr->troughGC != None) { Tk_FreeGC(scrollPtr->display, macScrollPtr->troughGC); @@ -353,15 +367,15 @@ TkpDestroyScrollbar( Tk_FreeGC(scrollPtr->display, macScrollPtr->copyGC); } } - + /* *---------------------------------------------------------------------- * * TkpConfigureScrollbar -- * - * This procedure is called after the generic code has finished - * processing configuration options, in order to configure platform - * specific options. There are no such option on the Mac, however. + * This procedure is called after the generic code has finished processing + * configuration options, in order to configure platform specific options. + * There are no such option on the Mac, however. * * Results: * None. @@ -374,11 +388,11 @@ TkpDestroyScrollbar( void TkpConfigureScrollbar( - register TkScrollbar *scrollPtr) + register TkScrollbar *scrollPtr) { - + /* empty */ } - + /* *-------------------------------------------------------------- * @@ -403,29 +417,28 @@ TkpScrollbarPosition( /* Scrollbar widget record. */ int x, int y) /* Coordinates within scrollPtr's window. */ { - - /* - * The code below is borrowed from tkUnixScrlbr.c and needs no adjustment - * since it does not involve the arrow buttons. - */ + /* + * The code below is borrowed from tkUnixScrlbr.c and needs no adjustment + * since it does not involve the arrow buttons. + */ int length, width, tmp; register const int inset = scrollPtr->inset; if (scrollPtr->vertical) { - length = Tk_Height(scrollPtr->tkwin); - width = Tk_Width(scrollPtr->tkwin); + length = Tk_Height(scrollPtr->tkwin); + width = Tk_Width(scrollPtr->tkwin); } else { - tmp = x; - x = y; - y = tmp; - length = Tk_Width(scrollPtr->tkwin); - width = Tk_Height(scrollPtr->tkwin); + tmp = x; + x = y; + y = tmp; + length = Tk_Width(scrollPtr->tkwin); + width = Tk_Height(scrollPtr->tkwin); } if (x < inset || x >= width - inset || - y < inset || y >= length - inset) { - return OUTSIDE; + y < inset || y >= length - inset) { + return OUTSIDE; } /* @@ -436,31 +449,34 @@ TkpScrollbarPosition( */ if (y < scrollPtr->sliderFirst + scrollPtr->arrowLength) { - return TOP_GAP; - } - if (y < scrollPtr->sliderLast) { - return SLIDER; - } - if (y < length - (2*scrollPtr->arrowLength + inset)) { - return BOTTOM_GAP; - } - /* On systems newer than 10.6 we have already returned. */ - if (y < length - (scrollPtr->arrowLength + inset)) { - return TOP_ARROW; - } - return BOTTOM_ARROW; -} + return TOP_GAP; + } + if (y < scrollPtr->sliderLast) { + return SLIDER; + } + if (y < length - (2*scrollPtr->arrowLength + inset)) { + return BOTTOM_GAP; + } + /* + * On systems newer than 10.6 we have already returned. + */ + + if (y < length - (scrollPtr->arrowLength + inset)) { + return TOP_ARROW; + } + return BOTTOM_ARROW; +} + /* *-------------------------------------------------------------- * * UpdateControlValues -- * - * This procedure updates the Macintosh scrollbar control to - * display the values defined by the Tk scrollbar. This is the - * key interface to the Mac-native scrollbar; the Unix bindings - * drive scrolling in the Tk window and all the Mac scrollbar has - * to do is redraw itself. + * This procedure updates the Macintosh scrollbar control to display the + * values defined by the Tk scrollbar. This is the key interface to the + * Mac-native scrollbar; the Unix bindings drive scrolling in the Tk + * window and all the Mac scrollbar has to do is redraw itself. * * Results: * None. @@ -473,20 +489,21 @@ TkpScrollbarPosition( static void UpdateControlValues( - TkScrollbar *scrollPtr) /* Scrollbar data struct. */ + TkScrollbar *scrollPtr) /* Scrollbar data struct. */ { - MacScrollbar *msPtr = (MacScrollbar *)scrollPtr; + MacScrollbar *msPtr = (MacScrollbar *) scrollPtr; Tk_Window tkwin = scrollPtr->tkwin; MacDrawable *macWin = (MacDrawable *) Tk_WindowId(scrollPtr->tkwin); double dViewSize; - HIRect contrlRect; + HIRect contrlRect; short width, height; NSView *view = TkMacOSXDrawableView(macWin); CGFloat viewHeight = [view bounds].size.height; NSRect frame; + frame = NSMakeRect(macWin->xOff, macWin->yOff, Tk_Width(tkwin), - Tk_Height(tkwin)); + Tk_Height(tkwin)); frame = NSInsetRect(frame, scrollPtr->inset, scrollPtr->inset); frame.origin.y = viewHeight - (frame.origin.y + frame.size.height); @@ -503,9 +520,9 @@ UpdateControlValues( msPtr->info.bounds = contrlRect; if (scrollPtr->vertical) { - msPtr->info.attributes &= ~kThemeTrackHorizontal; + msPtr->info.attributes &= ~kThemeTrackHorizontal; } else { - msPtr->info.attributes |= kThemeTrackHorizontal; + msPtr->info.attributes |= kThemeTrackHorizontal; } /* @@ -518,69 +535,73 @@ UpdateControlValues( * the view area. */ - double maximum = 100, factor; - factor = RangeToFactor(maximum); - dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction) - * factor; - msPtr->info.max = MIN_SCROLLBAR_VALUE + - factor - dViewSize; + double maximum = 100, factor = RangeToFactor(maximum); + + dViewSize = (scrollPtr->lastFraction - scrollPtr->firstFraction) * factor; + msPtr->info.max = MIN_SCROLLBAR_VALUE + factor - dViewSize; msPtr->info.trackInfo.scrollbar.viewsize = dViewSize; if (scrollPtr->vertical) { - if (SNOW_LEOPARD_STYLE) { - msPtr->info.value = factor * scrollPtr->firstFraction; - } else { - msPtr->info.value = msPtr->info.max - factor * scrollPtr->firstFraction; - } + if (SNOW_LEOPARD_STYLE) { + msPtr->info.value = factor * scrollPtr->firstFraction; + } else { + msPtr->info.value = msPtr->info.max - + factor * scrollPtr->firstFraction; + } } else { - msPtr->info.value = MIN_SCROLLBAR_VALUE + factor * scrollPtr->firstFraction; + msPtr->info.value = MIN_SCROLLBAR_VALUE + + factor * scrollPtr->firstFraction; } - if((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0) - || height <= metrics.minHeight) { + if ((scrollPtr->firstFraction <= 0.0 && scrollPtr->lastFraction >= 1.0) + || height <= metrics.minHeight) { msPtr->info.enableState = kThemeTrackHideTrack; } else { msPtr->info.enableState = kThemeTrackActive; - msPtr->info.attributes = kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost; + msPtr->info.attributes = + kThemeTrackShowThumb | kThemeTrackThumbRgnIsNotGhost; } - } - + /* *-------------------------------------------------------------- * * ScrollbarEvent -- * - * This procedure is invoked in response to <ButtonPress>, <ButtonRelease>, - * <EnterNotify>, and <LeaveNotify> events. The Scrollbar appearance is - * modified for each event. + * This procedure is invoked in response to <ButtonPress>, + * <ButtonRelease>, <EnterNotify>, and <LeaveNotify> events. The + * Scrollbar appearance is modified for each event. * *-------------------------------------------------------------- */ static int -ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr) +ScrollbarEvent( + TkScrollbar *scrollPtr, + XEvent *eventPtr) { - MacScrollbar *msPtr = (MacScrollbar *)scrollPtr; - - /* The pressState does not indicate whether the moused button was - * pressed at some location in the Scrollbar. Rather, it indicates - * that the scrollbar should appear as if it were pressed in that - * location. The standard Mac behavior is that once the button is - * pressed inside the Scrollbar the appearance should not change until - * the button is released, even if the mouse moves outside of the - * scrollbar. However, if the mouse lies over the scrollbar but the - * button is not pressed then the appearance should be the same as if - * the button had been pressed on the slider, i.e. kThemeThumbPressed. - * See the file Appearance.r, or HIToolbox.bridgesupport on 10.14. + MacScrollbar *msPtr = (MacScrollbar *) scrollPtr; + + /* + * The pressState does not indicate whether the moused button was pressed + * at some location in the Scrollbar. Rather, it indicates that the + * scrollbar should appear as if it were pressed in that location. The + * standard Mac behavior is that once the button is pressed inside the + * Scrollbar the appearance should not change until the button is released, + * even if the mouse moves outside of the scrollbar. However, if the mouse + * lies over the scrollbar but the button is not pressed then the + * appearance should be the same as if the button had been pressed on the + * slider, i.e. kThemeThumbPressed. See the file Appearance.r, or + * HIToolbox.bridgesupport on 10.14. */ if (eventPtr->type == ButtonPress) { msPtr->buttonDown = true; UpdateControlValues(scrollPtr); + int where = TkpScrollbarPosition(scrollPtr, - eventPtr->xbutton.x, - eventPtr->xbutton.y); - switch(where) { + eventPtr->xbutton.x, eventPtr->xbutton.y); + + switch (where) { case OUTSIDE: msPtr->info.trackInfo.scrollbar.pressState = 0; break; @@ -591,14 +612,20 @@ ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr) msPtr->info.trackInfo.scrollbar.pressState = kThemeThumbPressed; break; case BOTTOM_GAP: - msPtr->info.trackInfo.scrollbar.pressState = kThemeBottomTrackPressed; + msPtr->info.trackInfo.scrollbar.pressState = + kThemeBottomTrackPressed; break; case TOP_ARROW: - /* This looks wrong and the docs say it is wrong but it works. */ - msPtr->info.trackInfo.scrollbar.pressState = kThemeTopInsideArrowPressed; + /* + * This looks wrong and the docs say it is wrong but it works. + */ + + msPtr->info.trackInfo.scrollbar.pressState = + kThemeTopInsideArrowPressed; break; case BOTTOM_ARROW: - msPtr->info.trackInfo.scrollbar.pressState = kThemeBottomOutsideArrowPressed; + msPtr->info.trackInfo.scrollbar.pressState = + kThemeBottomOutsideArrowPressed; break; } } @@ -622,9 +649,7 @@ ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr) } return TCL_OK; } - - - + /* *-------------------------------------------------------------- * @@ -645,8 +670,8 @@ ScrollbarEvent(TkScrollbar *scrollPtr, XEvent *eventPtr) static void ScrollbarEventProc( - ClientData clientData, /* Information about window. */ - XEvent *eventPtr) /* Information about event. */ + ClientData clientData, /* Information about window. */ + XEvent *eventPtr) /* Information about event. */ { TkScrollbar *scrollPtr = clientData; @@ -668,7 +693,7 @@ ScrollbarEventProc( TkScrollbarEventProc(clientData, eventPtr); } } - + /* * Local Variables: * mode: objc diff --git a/macosx/tkMacOSXSend.c b/macosx/tkMacOSXSend.c index 1fdf048..8b65532 100644 --- a/macosx/tkMacOSXSend.c +++ b/macosx/tkMacOSXSend.c @@ -51,7 +51,7 @@ typedef struct RegisteredInterp { * A registry of all interpreters for a display is kept in a property * "InterpRegistry" on the root window of the display. It is organized as a * series of zero or more concatenated strings (in no particular order), each - * of the form + * of the form: * window space name '\0' * where "window" is the hex id of the comm. window to use to talk to an * interpreter named "name". @@ -78,7 +78,7 @@ typedef struct NameRegistry { * XFree; zero means use ckfree. */ } NameRegistry; -static int initialized = 0; /* A flag to denote if we have initialized +static int initialized = 0; /* A flag to denote if we have initialized * yet. */ static RegisteredInterp *interpListPtr = NULL; diff --git a/macosx/tkMacOSXSubwindows.c b/macosx/tkMacOSXSubwindows.c index 805d58f..21f40ce 100644 --- a/macosx/tkMacOSXSubwindows.c +++ b/macosx/tkMacOSXSubwindows.c @@ -65,7 +65,7 @@ XDestroyWindow( TkMacOSXSelDeadWindow(macWin->winPtr); macWin->toplevel->referenceCount--; - if (!Tk_IsTopLevel(macWin->winPtr) ) { + if (!Tk_IsTopLevel(macWin->winPtr)) { TkMacOSXInvalidateWindow(macWin, TK_PARENT_WINDOW); if (macWin->winPtr->parentPtr != NULL) { TkMacOSXInvalClipRgns((Tk_Window) macWin->winPtr->parentPtr); @@ -130,6 +130,8 @@ XMapWindow( Window window) /* Window. */ { MacDrawable *macWin = (MacDrawable *) window; + TkWindow *winPtr = macWin->winPtr; + NSWindow *win = TkMacOSXDrawableWindow(window); XEvent event; /* @@ -145,30 +147,28 @@ XMapWindow( } display->request++; - macWin->winPtr->flags |= TK_MAPPED; - if (Tk_IsTopLevel(macWin->winPtr)) { - if (!Tk_IsEmbedded(macWin->winPtr)) { - NSWindow *win = TkMacOSXDrawableWindow(window); + winPtr->flags |= TK_MAPPED; + if (Tk_IsTopLevel(winPtr)) { + if (!Tk_IsEmbedded(winPtr)) { /* - * We want to activate Tk when a toplevel is mapped - * but we must not supply YES here. This is because - * during Tk initialization the root window is mapped - * before applicationDidFinishLaunching returns. Forcing - * the app to activate too early can make the menu bar - * unresponsive. + * We want to activate Tk when a toplevel is mapped but we must not + * supply YES here. This is because during Tk initialization the + * root window is mapped before applicationDidFinishLaunching + * returns. Forcing the app to activate too early can make the menu + * bar unresponsive. */ - TkMacOSXApplyWindowAttributes(macWin->winPtr, win); + TkMacOSXApplyWindowAttributes(winPtr, win); [win setExcludedFromWindowsMenu:NO]; [NSApp activateIgnoringOtherApps:NO]; [[win contentView] setNeedsDisplay:YES]; - if ( [win canBecomeKeyWindow] ) { + if ([win canBecomeKeyWindow]) { [win makeKeyAndOrderFront:NSApp]; } else { [win orderFrontRegardless]; } - + /* * In some cases the toplevel will not be drawn unless we process * all pending events now. See ticket 56a1823c73. @@ -178,17 +178,18 @@ XMapWindow( while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS| TCL_DONT_WAIT)) {} [NSApp _unlockAutoreleasePool]; } else { + TkWindow *contWinPtr = TkpGetOtherWindow(winPtr); /* * Rebuild the container's clipping region and display * the window. */ - TkWindow *contWinPtr = TkpGetOtherWindow(macWin->winPtr); - TkMacOSXInvalClipRgns((Tk_Window)contWinPtr); + TkMacOSXInvalClipRgns((Tk_Window) contWinPtr); TkMacOSXInvalidateWindow(macWin, TK_PARENT_WINDOW); } - TkMacOSXInvalClipRgns((Tk_Window) macWin->winPtr); + + TkMacOSXInvalClipRgns((Tk_Window) winPtr); /* * We only need to send the MapNotify event for toplevel windows. @@ -201,18 +202,21 @@ XMapWindow( event.xmap.window = window; event.xmap.type = MapNotify; event.xmap.event = window; - event.xmap.override_redirect = macWin->winPtr->atts.override_redirect; + event.xmap.override_redirect = winPtr->atts.override_redirect; Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); - } else { /* - * Rebuild the parent's clipping region and display the window. - * + * For non-toplevel windows, rebuild the parent's clipping region + * and redisplay the window. */ - TkMacOSXInvalClipRgns((Tk_Window) macWin->winPtr->parentPtr); - TkMacOSXInvalidateWindow(macWin, TK_PARENT_WINDOW); + TkMacOSXInvalClipRgns((Tk_Window) winPtr->parentPtr); + if ([NSApp isDrawing]) { + [[win contentView] setNeedsRedisplay:YES]; + } else { + [[win contentView] setNeedsDisplay:YES]; + } } /* @@ -223,13 +227,7 @@ XMapWindow( event.xany.display = display; event.xvisibility.type = VisibilityNotify; event.xvisibility.state = VisibilityUnobscured; - NotifyVisibility(macWin->winPtr, &event); - - /* - * This seems to be needed to ensure that all subwindows get displayed. - */ - - GenerateConfigureNotify(macWin->winPtr, 1); + NotifyVisibility(winPtr, &event); } /* @@ -298,6 +296,7 @@ XUnmapWindow( if (!Tk_IsEmbedded(winPtr) && winPtr->wmInfoPtr->hints.initial_state!=IconicState) { NSWindow *win = TkMacOSXDrawableWindow(window); + [win orderOut:nil]; } TkMacOSXInvalClipRgns((Tk_Window) winPtr); @@ -316,15 +315,15 @@ XUnmapWindow( event.xunmap.from_configure = false; Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL); } else { - /* * Rebuild the visRgn clip region for the parent so it will be allowed * to draw in the space from which this subwindow was removed. */ if (parentPtr && parentPtr->privatePtr->visRgn) { - TkMacOSXInvalidateViewRegion(TkMacOSXDrawableView(parentPtr->privatePtr), - parentPtr->privatePtr->visRgn); + TkMacOSXInvalidateViewRegion( + TkMacOSXDrawableView(parentPtr->privatePtr), + parentPtr->privatePtr->visRgn); } TkMacOSXInvalClipRgns((Tk_Window) parentPtr); TkMacOSXUpdateClipRgn(parentPtr); @@ -357,11 +356,14 @@ XResizeWindow( unsigned int height) { MacDrawable *macWin = (MacDrawable *) window; + display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; + if (w) { NSRect r = [w contentRectForFrameRect:[w frame]]; + r.origin.y += r.size.height - height; r.size.width = width; r.size.height = height; @@ -377,8 +379,8 @@ XResizeWindow( * * XMoveResizeWindow -- * - * Move or resize a given X window. See X windows documentation - * for further details. + * Move or resize a given X window. See X windows documentation for + * further details. * * Results: * None. @@ -402,22 +404,24 @@ XMoveResizeWindow( display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; - if (w) { - /* We explicitly convert everything to doubles so we don't get + if (w) { + /* + * We explicitly convert everything to doubles so we don't get * surprised (again) by what happens when you do arithmetic with * unsigned ints. */ - CGFloat X = (CGFloat)x; - CGFloat Y = (CGFloat)y; - CGFloat Width = (CGFloat)width; - CGFloat Height = (CGFloat)height; - CGFloat XOff = (CGFloat)macWin->winPtr->wmInfoPtr->xInParent; - CGFloat YOff = (CGFloat)macWin->winPtr->wmInfoPtr->yInParent; - NSRect r = NSMakeRect(X + XOff, - tkMacOSXZeroScreenHeight - Y - YOff - Height, - Width, Height); + CGFloat X = (CGFloat) x; + CGFloat Y = (CGFloat) y; + CGFloat Width = (CGFloat) width; + CGFloat Height = (CGFloat) height; + CGFloat XOff = (CGFloat) macWin->winPtr->wmInfoPtr->xInParent; + CGFloat YOff = (CGFloat) macWin->winPtr->wmInfoPtr->yInParent; + NSRect r = NSMakeRect( + X + XOff, tkMacOSXZeroScreenHeight - Y - YOff - Height, + Width, Height); + [w setFrame:[w frameRectForContentRect:r] display:YES]; } } else { @@ -430,8 +434,7 @@ XMoveResizeWindow( * * XMoveWindow -- * - * Move a given X window. See X windows documentation for further - * details. + * Move a given X window. See X windows documentation for further details. * * Results: * None. @@ -453,8 +456,10 @@ XMoveWindow( display->request++; if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { NSWindow *w = macWin->winPtr->wmInfoPtr->window; + if (w) { - [w setFrameTopLeftPoint:NSMakePoint(x, tkMacOSXZeroScreenHeight - y)]; + [w setFrameTopLeftPoint: NSMakePoint( + x, tkMacOSXZeroScreenHeight - y)]; } } else { MoveResizeWindow(macWin); @@ -495,7 +500,6 @@ MoveResizeWindow( if (contWinPtr) { macParent = contWinPtr->privatePtr; } else { - /* * Here we should handle out of process embedding. At this point, * we are assuming that the changes.x,y is not maintained, if you @@ -504,7 +508,6 @@ MoveResizeWindow( */ } } else { - /* * TODO: update all xOff & yOffs */ @@ -596,7 +599,6 @@ XRaiseWindow( if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { TkWmRestackToplevel(macWin->winPtr, Above, NULL); } else { - /* * TODO: this should generate damage */ @@ -631,7 +633,6 @@ XLowerWindow( if (Tk_IsTopLevel(macWin->winPtr) && !Tk_IsEmbedded(macWin->winPtr)) { TkWmRestackToplevel(macWin->winPtr, Below, NULL); } else { - /* * TODO: this should generate damage */ @@ -694,14 +695,16 @@ XConfigureWindow( TkMacOSXInvalClipRgns((Tk_Window) winPtr->parentPtr); TkMacOSXWinBounds(winPtr, &bounds); r = NSMakeRect(bounds.left, - [view bounds].size.height - bounds.bottom, - bounds.right - bounds.left, bounds.bottom - bounds.top); + [view bounds].size.height - bounds.bottom, + bounds.right - bounds.left, bounds.bottom - bounds.top); [view setNeedsDisplayInRect:r]; } } - /* TkGenWMMoveRequestEvent(macWin->winPtr, - macWin->winPtr->changes.x, macWin->winPtr->changes.y); */ +#if 0 + TkGenWMMoveRequestEvent(macWin->winPtr, + macWin->winPtr->changes.x, macWin->winPtr->changes.y); +#endif } /* @@ -723,14 +726,14 @@ XConfigureWindow( void TkMacOSXSetDrawingEnabled( - TkWindow *winPtr, - int flag) + TkWindow *winPtr, + int flag) { TkWindow *childPtr; MacDrawable *macWin = winPtr->privatePtr; if (macWin) { - if (flag ) { + if (flag) { macWin->flags &= ~TK_DO_NOT_DRAW; } else { macWin->flags |= TK_DO_NOT_DRAW; @@ -738,8 +741,8 @@ TkMacOSXSetDrawingEnabled( } /* - * Set the flag for all children & their descendants, excluding - * Toplevels. (??? Do we need to exclude Toplevels?) + * Set the flag for all children & their descendants, excluding Toplevels. + * (??? Do we need to exclude Toplevels?) */ childPtr = winPtr->childList; @@ -903,7 +906,6 @@ TkMacOSXUpdateClipRgn( } CFRelease(rgn); } else { - /* * An unmapped window has empty clip regions to prevent any * (erroneous) drawing into it or its children from becoming @@ -933,8 +935,8 @@ TkMacOSXUpdateClipRgn( * TkMacOSXVisableClipRgn -- * * This function returns the Macintosh clipping region for the given - * window. The caller is responsible for disposing of the returned - * region via TkDestroyRegion(). + * window. The caller is responsible for disposing of the returned region + * via TkDestroyRegion(). * * Results: * The region. @@ -952,7 +954,7 @@ TkMacOSXVisableClipRgn( if (winPtr->privatePtr->flags & TK_CLIP_INVALID) { TkMacOSXUpdateClipRgn(winPtr); } - return (TkRegion)HIShapeCreateMutableCopy(winPtr->privatePtr->visRgn); + return (TkRegion) HIShapeCreateMutableCopy(winPtr->privatePtr->visRgn); } /* @@ -972,7 +974,12 @@ TkMacOSXVisableClipRgn( */ static OSStatus -InvalViewRect(int msg, HIShapeRef rgn, const CGRect *rect, void *ref) { +InvalViewRect( + int msg, + HIShapeRef rgn, + const CGRect *rect, + void *ref) +{ static CGAffineTransform t; NSView *view = ref; @@ -1052,7 +1059,7 @@ TkMacOSXInvalidateWindow( *---------------------------------------------------------------------- */ -NSWindow* +NSWindow * TkMacOSXDrawableWindow( Drawable drawable) { @@ -1070,6 +1077,7 @@ TkMacOSXDrawableWindow( result = macWin->winPtr->wmInfoPtr->window; } else if (macWin->toplevel && (macWin->toplevel->flags & TK_EMBEDDED)) { TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr); + if (contWinPtr) { result = TkMacOSXDrawableWindow((Drawable) contWinPtr->privatePtr); } @@ -1123,7 +1131,7 @@ TkMacOSXGetDrawablePort( *---------------------------------------------------------------------- */ -NSView* +NSView * TkMacOSXDrawableView( MacDrawable *macWin) { @@ -1137,6 +1145,7 @@ TkMacOSXDrawableView( result = macWin->toplevel->view; } else { TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr); + if (contWinPtr) { result = TkMacOSXDrawableView(contWinPtr->privatePtr); } @@ -1164,7 +1173,6 @@ void * TkMacOSXGetRootControl( Drawable drawable) { - /* * will probably need to fix this up for embedding */ @@ -1281,7 +1289,8 @@ TkMacOSXWinBounds( TkWindow *winPtr, void *bounds) { - Rect *b = (Rect *)bounds; + Rect *b = (Rect *) bounds; + b->left = winPtr->privatePtr->xOff; b->top = winPtr->privatePtr->yOff; b->right = b->left + winPtr->changes.width; @@ -1345,7 +1354,6 @@ UpdateOffsets( TkWindow *childPtr; if (winPtr->privatePtr == NULL) { - /* * We haven't called Tk_MakeWindowExist for this window yet. The offset * information will be postponed and calulated at that time. (This will diff --git a/macosx/tkMacOSXTest.c b/macosx/tkMacOSXTest.c index b7923b0..9192fd6 100644 --- a/macosx/tkMacOSXTest.c +++ b/macosx/tkMacOSXTest.c @@ -18,8 +18,11 @@ * Forward declarations of procedures defined later in this file: */ +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1080 static int DebuggerObjCmd (ClientData dummy, Tcl_Interp *interp, - int objc, Tcl_Obj *const objv[]); + int objc, Tcl_Obj *const objv[]); +#endif + /* *---------------------------------------------------------------------- @@ -46,8 +49,10 @@ TkplatformtestInit( * Add commands for platform specific tests on MacOS here. */ +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1080 Tcl_CreateObjCommand(interp, "debugger", DebuggerObjCmd, (ClientData) 0, (Tcl_CmdDeleteProc *) NULL); +#endif return TCL_OK; } @@ -57,7 +62,8 @@ TkplatformtestInit( * * DebuggerObjCmd -- * - * This procedure simply calls the low level debugger. + * This procedure simply calls the low level debugger, which was + * deprecated in OSX 10.8. * * Results: * A standard Tcl result. @@ -68,6 +74,7 @@ TkplatformtestInit( *---------------------------------------------------------------------- */ +#if MAC_OS_X_VERSION_MAX_ALLOWED < 1080 static int DebuggerObjCmd( ClientData clientData, /* Not used. */ @@ -78,6 +85,7 @@ DebuggerObjCmd( Debugger(); return TCL_OK; } +#endif /* *---------------------------------------------------------------------- diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index 212381e..3eda4ec 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -68,7 +68,7 @@ extern NSString *NSWindowDidOrderOffScreenNotification; TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); #endif BOOL movedOnly = [[notification name] - isEqualToString:NSWindowDidMoveNotification]; + isEqualToString:NSWindowDidMoveNotification]; NSWindow *w = [notification object]; TkWindow *winPtr = TkMacOSXGetTkWindow(w); @@ -91,7 +91,6 @@ extern NSString *NSWindowDidOrderOffScreenNotification; flags |= TK_SIZE_CHANGED; } if (Tcl_GetServiceMode() != TCL_SERVICE_NONE) { - /* * Propagate geometry changes immediately. */ @@ -140,11 +139,10 @@ extern NSString *NSWindowDidOrderOffScreenNotification; - (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)newFrame { - /* - * This method needs to be implemented in order for [NSWindow isZoomed] - * to give the correct answer. But it suffices to always validate - * every request. + * This method needs to be implemented in order for [NSWindow isZoomed] to + * give the correct answer. But it suffices to always validate every + * request. */ return newFrame; @@ -153,13 +151,12 @@ extern NSString *NSWindowDidOrderOffScreenNotification; - (NSSize)window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize { - /* - * We don't need to change the proposed size, but we do need to - * implement this method. Otherwise the full screen window will - * be sized to the screen's visibleFrame, leaving black bands at - * the top and bottom. + * We don't need to change the proposed size, but we do need to implement + * this method. Otherwise the full screen window will be sized to the + * screen's visibleFrame, leaving black bands at the top and bottom. */ + return proposedSize; } @@ -208,14 +205,13 @@ extern NSString *NSWindowDidOrderOffScreenNotification; } /* - * If necessary, TkGenWMDestroyEvent() handles [close]ing the window, - * so can always return NO from -windowShouldClose: for a Tk window. + * If necessary, TkGenWMDestroyEvent() handles [close]ing the window, so + * can always return NO from -windowShouldClose: for a Tk window. */ return (winPtr ? NO : YES); } - #ifdef TK_MAC_DEBUG_NOTIFICATIONS - (void) windowDragStart: (NSNotification *) notification @@ -256,7 +252,6 @@ extern NSString *NSWindowDidOrderOffScreenNotification; } } - #endif /* TK_MAC_DEBUG_NOTIFICATIONS */ - (void) _setupWindowNotifications @@ -363,11 +358,11 @@ extern NSString *NSWindowDidOrderOffScreenNotification; * * TkpAppIsDrawing -- * - * A widget display procedure can call this to determine whether it - * is being run inside of the drawRect method. This is needed for - * some tests, especially of the Text widget, which record data in - * a global Tcl variable and assume that display procedures will be - * run in a predictable sequence as Tcl idle tasks. + * A widget display procedure can call this to determine whether it is + * being run inside of the drawRect method. This is needed for some tests, + * especially of the Text widget, which record data in a global Tcl + * variable and assume that display procedures will be run in a + * predictable sequence as Tcl idle tasks. * * Results: * True only while running the drawRect method of a TKContentView; @@ -377,11 +372,11 @@ extern NSString *NSWindowDidOrderOffScreenNotification; * *---------------------------------------------------------------------- */ + MODULE_SCOPE Bool TkpAppIsDrawing(void) { return [NSApp isDrawing]; } - /* *---------------------------------------------------------------------- @@ -670,12 +665,10 @@ TkGenWMConfigureEvent( if ((flags & TK_SIZE_CHANGED) && !(wmPtr->flags & WM_SYNC_PENDING) && ((width != Tk_Width(tkwin)) || (height != Tk_Height(tkwin)))) { if ((wmPtr->width == -1) && (width == winPtr->reqWidth)) { - /* * Don't set external width, since the user didn't change it * from what the widgets asked for. */ - } else if (wmPtr->gridWin != NULL) { wmPtr->width = wmPtr->reqGridWidth + (width - winPtr->reqWidth)/wmPtr->widthInc; @@ -687,12 +680,10 @@ TkGenWMConfigureEvent( } if ((wmPtr->height == -1) && (height == winPtr->reqHeight)) { - /* * Don't set external height, since the user didn't change it * from what the widgets asked for. */ - } else if (wmPtr->gridWin != NULL) { wmPtr->height = wmPtr->reqGridHeight + (height - winPtr->reqHeight)/wmPtr->heightInc; @@ -708,7 +699,6 @@ TkGenWMConfigureEvent( } } - /* * Now set up the changes structure. Under X we wait for the * ConfigureNotify to set these values. On the Mac we know imediatly that @@ -866,7 +856,10 @@ Tk_MacOSXIsAppInFront(void) * */ -/*Restrict event processing to Expose events.*/ +/* + * Restrict event processing to Expose events. + */ + static Tk_RestrictAction ExposeRestrictProc( ClientData arg, @@ -876,7 +869,10 @@ ExposeRestrictProc( ? TK_PROCESS_EVENT : TK_DEFER_EVENT); } -/*Restrict event processing to ConfigureNotify events.*/ +/* + * Restrict event processing to ConfigureNotify events. + */ + static Tk_RestrictAction ConfigureRestrictProc( ClientData arg, @@ -885,6 +881,30 @@ ConfigureRestrictProc( return (eventPtr->type==ConfigureNotify ? TK_PROCESS_EVENT : TK_DEFER_EVENT); } +/* + * If a window gets mapped inside the drawRect method, this will be run as an + * idle task, after drawRect returns, to clean up the mess. + */ + +static void +RedisplayView( + ClientData clientdata) +{ + NSView *view = (NSView *) clientdata; + + /* + * Make sure that we are not trying to displaying a view that no longer + * exists. + */ + + for (NSWindow *w in [NSApp orderedWindows]) { + if ([w contentView] == view) { + [view setNeedsDisplay:YES]; + break; + } + } +} + @implementation TKContentView(TKWindowEvent) - (void) drawRect: (NSRect) rect @@ -899,8 +919,8 @@ ConfigureRestrictProc( #endif /* - * We do not allow recursive calls to drawRect, but we only log - * them on OSX > 10.13, where they should never happen. + * We do not allow recursive calls to drawRect, but we only log them on OSX + * > 10.13, where they should never happen. */ if ([NSApp isDrawing]) { @@ -931,6 +951,11 @@ ConfigureRestrictProc( CFRelease(drawShape); [NSApp setIsDrawing: NO]; + if ([self needsRedisplay]) { + [self setNeedsRedisplay:NO]; + Tcl_DoWhenIdle(RedisplayView, self); + } + #ifdef TK_MAC_DEBUG_DRAWING fprintf(stderr, "drawRect: done.\n"); #endif @@ -955,9 +980,9 @@ ConfigureRestrictProc( Tk_RestrictProc *oldProc; /* - * This can be called from outside the Tk event loop. - * Since it calls Tcl_DoOneEvent, we need to make sure we - * don't clobber the AutoreleasePool set up by the caller. + * This can be called from outside the Tk event loop. Since it calls + * Tcl_DoOneEvent, we need to make sure we don't clobber the + * AutoreleasePool set up by the caller. */ [NSApp _lockAutoreleasePool]; @@ -973,7 +998,7 @@ ConfigureRestrictProc( */ TkGenWMConfigureEvent(tkwin, Tk_X(tkwin), Tk_Y(tkwin), width, height, - TK_SIZE_CHANGED | TK_MACOSX_HANDLE_EVENT_IMMEDIATELY); + TK_SIZE_CHANGED | TK_MACOSX_HANDLE_EVENT_IMMEDIATELY); oldProc = Tk_RestrictEvents(ConfigureRestrictProc, NULL, &oldArg); Tk_RestrictEvents(oldProc, oldArg, &oldArg); @@ -1061,16 +1086,15 @@ ConfigureRestrictProc( } /* - * This method is called when a user changes between light and dark mode. - * The implementation here generates a Tk virtual event which can be bound - * to a function that redraws the window in an appropriate style. + * This method is called when a user changes between light and dark mode. The + * implementation here generates a Tk virtual event which can be bound to a + * function that redraws the window in an appropriate style. */ - (void) viewDidChangeEffectiveAppearance { XVirtualEvent event; int x, y; - NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; NSWindow *w = [self window]; TkWindow *winPtr = TkMacOSXGetTkWindow(w); Tk_Window tkwin = (Tk_Window) winPtr; @@ -1091,21 +1115,17 @@ ConfigureRestrictProc( &event.x_root, &event.y_root, &x, &y, &event.state); Tk_TopCoordsToWindow(tkwin, x, y, &event.x, &event.y); event.same_screen = true; - if (osxMode == nil) { - event.name = Tk_GetUid("LightAqua"); - Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL); - return; - } - if ([osxMode isEqual:@"Dark"]) { + if (TkMacOSXInDarkMode(tkwin)) { event.name = Tk_GetUid("DarkAqua"); - Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL); - return; + } else { + event.name = Tk_GetUid("LightAqua"); } + Tk_QueueWindowEvent((XEvent *) &event, TCL_QUEUE_TAIL); } /* - * This is no-op on 10.7 and up because Apple has removed this widget, - * but we are leaving it here for backwards compatibility. + * This is no-op on 10.7 and up because Apple has removed this widget, but we + * are leaving it here for backwards compatibility. */ - (void) tkToolbarButton: (id) sender diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 2a91822..f93fd40 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -3,14 +3,13 @@ * * This module takes care of the interactions between a Tk-based * application and the window manager. Among other things, it implements - * the "wm" command and passes geometry information to the window - * manager. + * the "wm" command and passes geometry information to the window manager. * * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright 2001-2009, Apple Inc. * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * Copyright (c) 2010 Kevin Walzer/WordTech Communications LLC. - * Copyright (c) 2017-2018 Marc Culler. + * Copyright (c) 2017-2019 Marc Culler. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -55,7 +54,6 @@ | tkCanJoinAllSpacesAttribute | tkMoveToActiveSpaceAttribute \ | tkNonactivatingPanelAttribute | tkHUDWindowAttribute) - static const struct { const UInt64 validAttrs, defaultAttrs, forceOnAttrs, forceOffAttrs; int flags; NSUInteger styleMask; @@ -311,6 +309,8 @@ 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); @@ -391,11 +391,13 @@ static void RemoveTransient(TkWindow *winPtr); if ([self styleMask] & NSFullScreenWindowMask) { frameRect = [NSWindow frameRectForContentRect:NSZeroRect - styleMask:[self styleMask]]; + 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; @@ -451,7 +453,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1){ + if (DEBUG_ZOMBIES > 1) { fprintf(stderr, "Retained <%s>. Count is: %lu\n", title, [self retainCount]); } @@ -465,7 +467,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1){ + if (DEBUG_ZOMBIES > 1) { fprintf(stderr, "Autoreleased <%s>. Count is %lu\n", title, [self retainCount]); } @@ -477,7 +479,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1){ + if (DEBUG_ZOMBIES > 1) { fprintf(stderr, "Releasing <%s>. Count is %lu\n", title, [self retainCount]); } @@ -489,14 +491,13 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 0){ + if (DEBUG_ZOMBIES > 0) { fprintf(stderr, ">>>> Freeing <%s>. Count is %lu\n", title, [self retainCount]); } [super dealloc]; } - #endif @end @@ -622,7 +623,8 @@ FrontWindowAtPoint( WmInfo *wmPtr = winPtr->wmInfoPtr; NSRect windowFrame = [w frame]; NSRect contentFrame = [w frame]; - contentFrame.size.height = [[w contentView] frame].size.height; + + contentFrame.size.height = [[w contentView] frame].size.height; /* * For consistency with other platforms, points in the * title bar are not considered to be contained in the @@ -630,7 +632,7 @@ FrontWindowAtPoint( */ if ((wmPtr->hints.initial_state == NormalState || - wmPtr->hints.initial_state == ZoomState)) { + wmPtr->hints.initial_state == ZoomState)) { if (NSMouseInRect(p, contentFrame, NO)) { return winPtr; } else if (NSMouseInRect(p, windowFrame, NO)) { @@ -938,9 +940,10 @@ TkWmDeadWindow( */ for (Transient *transientPtr = wmPtr->transientPtr; - transientPtr != NULL; transientPtr = transientPtr->nextPtr) { + transientPtr != NULL; transientPtr = transientPtr->nextPtr) { TkWindow *winPtr2 = transientPtr->winPtr; - TkWindow *masterPtr = (TkWindow *)TkGetTransientMaster(winPtr2); + TkWindow *masterPtr = (TkWindow *) TkGetTransientMaster(winPtr2); + if (masterPtr == winPtr) { wmPtr2 = winPtr2->wmInfoPtr; wmPtr2->master = NULL; @@ -949,6 +952,7 @@ TkWmDeadWindow( while (wmPtr->transientPtr != NULL) { Transient *transientPtr = wmPtr->transientPtr; + wmPtr->transientPtr = transientPtr->nextPtr; ckfree(transientPtr); } @@ -962,8 +966,9 @@ TkWmDeadWindow( NSWindow *window = wmPtr->window; - if (window && !Tk_IsEmbedded(winPtr) ) { + if (window && !Tk_IsEmbedded(winPtr)) { NSWindow *parent = [window parentWindow]; + if (parent) { [parent removeChildWindow:window]; } @@ -989,32 +994,36 @@ TkWmDeadWindow( NSArray *windows = [NSApp orderedWindows]; for (id nswindow in windows) { TkWindow *winPtr2 = TkMacOSXGetTkWindow(nswindow); + if (winPtr2 && nswindow != window) { WmInfo *wmPtr = winPtr2->wmInfoPtr; - BOOL minimized = (wmPtr->hints.initial_state == IconicState || - wmPtr->hints.initial_state == WithdrawnState); + BOOL minimized = (wmPtr->hints.initial_state == IconicState + || wmPtr->hints.initial_state == WithdrawnState); + /* - * If no windows are left on the screen and the next - * window is iconified or withdrawn, we don't want to - * make it be the KeyWindow because that would cause - * it to be displayed on the screen. + * If no windows are left on the screen and the next window is + * iconified or withdrawn, we don't want to make it be the + * KeyWindow because that would cause it to be displayed on the + * screen. */ + if ([nswindow canBecomeKeyWindow] && !minimized) { [nswindow makeKeyAndOrderFront:NSApp]; break; } } } + /* * Process all window events immediately to force the closed window to * be deallocated. But don't do this for the root window as that is * unnecessary and can lead to segfaults. */ + if (winPtr->parentPtr) { while (Tk_DoOneEvent(TK_WINDOW_EVENTS|TK_DONT_WAIT)) {} } [NSApp _resetAutoreleasePool]; - #if DEBUG_ZOMBIES > 0 fprintf(stderr, "================= Pool dump ===================\n"); [NSAutoreleasePool showPools]; @@ -1527,7 +1536,7 @@ WmAttributesCmd( WmGetAttribute(winPtr, macWindow, attribute)); } Tcl_SetObjResult(interp, result); - } else if (objc == 4) { /* wm attributes $win -attribute */ + } else if (objc == 4) { /* wm attributes $win -attribute */ if (Tcl_GetIndexFromObjStruct(interp, objv[3], WmAttributeNames, sizeof(char *), "attribute", 0, &attribute) != TCL_OK) { return TCL_ERROR; @@ -1736,7 +1745,7 @@ WmCommandCmd( } return TCL_OK; } - if (Tcl_GetString(objv[3])[0] == 0) { + if (*Tcl_GetString(objv[3]) == '\0') { if (wmPtr->commandObj != NULL) { Tcl_DecrRefCount(wmPtr->commandObj); wmPtr->commandObj = NULL; @@ -1817,13 +1826,14 @@ WmDeiconifyCmd( */ for (Transient *transientPtr = wmPtr->transientPtr; - transientPtr != NULL; transientPtr = transientPtr->nextPtr) { + transientPtr != NULL; transientPtr = transientPtr->nextPtr) { TkWindow *winPtr2 = transientPtr->winPtr; WmInfo *wmPtr2 = winPtr2->wmInfoPtr; - TkWindow *masterPtr = (TkWindow *)TkGetTransientMaster(winPtr2); + TkWindow *masterPtr = (TkWindow *) TkGetTransientMaster(winPtr2); + if (masterPtr == winPtr) { - if ((wmPtr2->hints.initial_state == WithdrawnState && - (transientPtr->flags & WITHDRAWN_BY_MASTER) != 0)) { + if ((wmPtr2->hints.initial_state == WithdrawnState) && + ((transientPtr->flags & WITHDRAWN_BY_MASTER) != 0)) { TkpWmSetState(winPtr2, NormalState); transientPtr->flags &= ~WITHDRAWN_BY_MASTER; } @@ -2359,11 +2369,11 @@ WmIconifyCmd( */ for (Transient *transientPtr = wmPtr->transientPtr; - transientPtr != NULL; transientPtr = transientPtr->nextPtr) { + transientPtr != NULL; transientPtr = transientPtr->nextPtr) { TkWindow *winPtr2 = transientPtr->winPtr; - TkWindow *masterPtr = (TkWindow *)TkGetTransientMaster(winPtr2); + TkWindow *masterPtr = (TkWindow *) TkGetTransientMaster(winPtr2); if (masterPtr == winPtr && - winPtr2->wmInfoPtr->hints.initial_state != WithdrawnState) { + winPtr2->wmInfoPtr->hints.initial_state != WithdrawnState) { TkpWmSetState(winPtr2, WithdrawnState); transientPtr->flags |= WITHDRAWN_BY_MASTER; } @@ -2519,26 +2529,35 @@ WmIconphotoCmd( return TCL_ERROR; } - /*Parse args.*/ + /* + * Parse args. + */ + if (strcmp(Tcl_GetString(objv[3]), "-default") == 0) { isDefault = 1; if (objc == 4) { Tcl_WrongNumArgs(interp, 2, objv, - "window ?-default? image1 ?image2 ...?"); + "window ?-default? image1 ?image2 ...?"); return TCL_ERROR; } } - /*Get icon name. We only use the first icon name because macOS does not - support multiple images in Tk photos.*/ + /* + * Get icon name. We only use the first icon name because macOS does not + * support multiple images in Tk photos. + */ + char *icon; if (strcmp(Tcl_GetString(objv[3]), "-default") == 0) { icon = Tcl_GetString(objv[4]); - } else { + } else { icon = Tcl_GetString(objv[3]); } - /*Get image and convert to NSImage that can be displayed as icon.*/ + /* + * Get image and convert to NSImage that can be displayed as icon. + */ + tk_icon = Tk_GetImage(interp, tkwin, icon, NULL, NULL); if (tk_icon == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -2550,7 +2569,8 @@ WmIconphotoCmd( NSImage *newIcon; Tk_SizeOfImage(tk_icon, &width, &height); - newIcon = TkMacOSXGetNSImageWithTkImage(winPtr->display, tk_icon, width, height); + newIcon = TkMacOSXGetNSImageWithTkImage(winPtr->display, tk_icon, + width, height); Tk_FreeImage(tk_icon); if (newIcon == NULL) { return TCL_ERROR; @@ -2607,7 +2627,7 @@ WmIconpositionCmd( wmPtr->hints.flags &= ~IconPositionHint; } else { if ((Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) - || (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)){ + || (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK)) { return TCL_ERROR; } wmPtr->hints.icon_x = x; @@ -2706,14 +2726,13 @@ WmIconwindowCmd( wmPtr->icon = tkwin2; wmPtr2->iconFor = (Tk_Window) winPtr; if (!(wmPtr2->flags & WM_NEVER_MAPPED)) { - /* * If the window is in normal or zoomed state, the icon should be * unmapped. */ if (wmPtr->hints.initial_state == NormalState || - wmPtr->hints.initial_state == ZoomState) { + wmPtr->hints.initial_state == ZoomState) { Tk_UnmapWindow(tkwin2); } } @@ -3084,7 +3103,7 @@ WmProtocolCmd( */ for (protPtr = wmPtr->protPtr, prevPtr = NULL; protPtr != NULL; - prevPtr = protPtr, protPtr = protPtr->nextPtr) { + prevPtr = protPtr, protPtr = protPtr->nextPtr) { if (protPtr->protocol == protocol) { if (prevPtr == NULL) { wmPtr->protPtr = protPtr->nextPtr; @@ -3148,8 +3167,10 @@ WmResizableCmd( if (objc == 3) { Tcl_Obj *results[2]; - results[0] = Tcl_NewWideIntObj((wmPtr->flags & WM_WIDTH_NOT_RESIZABLE) == 0); - results[1] = Tcl_NewWideIntObj((wmPtr->flags & WM_HEIGHT_NOT_RESIZABLE) == 0); + results[0] = Tcl_NewWideIntObj( + (wmPtr->flags & WM_WIDTH_NOT_RESIZABLE) == 0); + results[1] = Tcl_NewWideIntObj( + (wmPtr->flags & WM_HEIGHT_NOT_RESIZABLE) == 0); Tcl_SetObjResult(interp, Tcl_NewListObj(2, results)); return TCL_OK; } @@ -3437,14 +3458,17 @@ WmStateCmd( return TCL_ERROR; } - if (index == OPT_NORMAL) { + switch (index) { + case OPT_NORMAL: TkpWmSetState(winPtr, NormalState); /* * This varies from 'wm deiconify' because it does not force the * window to be raised and receive focus */ - } else if (index == OPT_ICONIC) { + + break; + case OPT_ICONIC: if (Tk_Attributes((Tk_Window) winPtr)->override_redirect) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't iconify \"%s\": override-redirect flag is set", @@ -3462,10 +3486,13 @@ WmStateCmd( return TCL_ERROR; } TkpWmSetState(winPtr, IconicState); - } else if (index == OPT_WITHDRAWN) { + break; + case OPT_WITHDRAWN: TkpWmSetState(winPtr, WithdrawnState); - } else { /* OPT_ZOOMED */ + break; + default: /* OPT_ZOOMED */ TkpWmSetState(winPtr, ZoomState); + break; } } else if (wmPtr->iconFor != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("icon", -1)); @@ -3583,16 +3610,14 @@ WmTransientCmd( } return TCL_OK; } - if (Tcl_GetString(objv[3])[0] == '\0') { + if (*Tcl_GetString(objv[3]) == '\0') { RemoveTransient(winPtr); - } else { if (TkGetWindowFromObj(interp, tkwin, objv[3], &master) != TCL_OK) { return TCL_ERROR; } masterPtr = (TkWindow*) master; while (!Tk_TopWinHierarchy(masterPtr)) { - /* * Ensure that the master window is actually a Tk toplevel. */ @@ -3610,7 +3635,11 @@ WmTransientCmd( } wmPtr2 = masterPtr->wmInfoPtr; - /* Under some circumstances, wmPtr2 is NULL here */ + + /* + * Under some circumstances, wmPtr2 is NULL here. + */ + if (wmPtr2 != NULL && wmPtr2->iconFor != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "can't make \"%s\" a master: it is an icon for %s", @@ -3620,7 +3649,7 @@ WmTransientCmd( } for (w = masterPtr; w != NULL && w->wmInfoPtr != NULL; - w = (TkWindow *)w->wmInfoPtr->master) { + w = (TkWindow *)w->wmInfoPtr->master) { if (w == winPtr) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "setting \"%s\" as master creates a transient/master cycle", @@ -3650,33 +3679,33 @@ WmTransientCmd( */ if ((wmPtr2->hints.initial_state == WithdrawnState || - wmPtr2->hints.initial_state == IconicState) && - wmPtr->hints.initial_state != WithdrawnState){ + wmPtr2->hints.initial_state == IconicState) && + wmPtr->hints.initial_state != WithdrawnState) { TkpWmSetState(winPtr, WithdrawnState); transient->flags |= WITHDRAWN_BY_MASTER; } - wmPtr->master = (Tk_Window)masterPtr; + wmPtr->master = (Tk_Window) masterPtr; } ApplyMasterOverrideChanges(winPtr, NULL); return TCL_OK; } - + /* *---------------------------------------------------------------------- * * RemoveTransient -- * - * Clears the transient's master record and removes the transient - * from the master's list. + * Clears the transient's master record and removes the transient from the + * master's list. * * Results: * None * * Side effects: * References to a master are removed from the transient's wmInfo - * structure and references to the transient are removed from its - * master's wmInfo. + * structure and references to the transient are removed from its master's + * wmInfo. * *---------------------------------------------------------------------- */ @@ -3687,34 +3716,34 @@ RemoveTransient( { WmInfo *wmPtr = winPtr->wmInfoPtr, *wmPtr2; TkWindow *masterPtr; - Transient *T, *temp; + Transient *transPtr, *temp; if (wmPtr == NULL || wmPtr->master == NULL) { return; } - masterPtr = (TkWindow*)wmPtr->master; + masterPtr = (TkWindow *) wmPtr->master; wmPtr2 = masterPtr->wmInfoPtr; if (wmPtr2 == NULL) { return; } wmPtr->master = NULL; - T = wmPtr2->transientPtr; - while (T != NULL) { - if (T->winPtr != winPtr) { + transPtr = wmPtr2->transientPtr; + while (transPtr != NULL) { + if (transPtr->winPtr != winPtr) { break; } - temp = T->nextPtr; - ckfree(T); - T = temp; + temp = transPtr->nextPtr; + ckfree(transPtr); + transPtr = temp; } - wmPtr2->transientPtr = T; - while (T != NULL) { - if (T->nextPtr && T->nextPtr->winPtr == winPtr) { - temp = T->nextPtr; - T->nextPtr = temp->nextPtr; + wmPtr2->transientPtr = transPtr; + while (transPtr != NULL) { + if (transPtr->nextPtr && transPtr->nextPtr->winPtr == winPtr) { + temp = transPtr->nextPtr; + transPtr->nextPtr = temp->nextPtr; ckfree(temp); } else { - T = T->nextPtr; + transPtr = transPtr->nextPtr; } } } @@ -3768,12 +3797,14 @@ WmWithdrawCmd( /* * If this window has a transient, the transient must also be withdrawn. */ + for (Transient *transientPtr = wmPtr->transientPtr; - transientPtr != NULL; transientPtr = transientPtr->nextPtr) { + transientPtr != NULL; transientPtr = transientPtr->nextPtr) { TkWindow *winPtr2 = transientPtr->winPtr; - TkWindow *masterPtr = (TkWindow *)TkGetTransientMaster(winPtr2); + TkWindow *masterPtr = (TkWindow *) TkGetTransientMaster(winPtr2); + if (masterPtr == winPtr && - winPtr2->wmInfoPtr->hints.initial_state != WithdrawnState) { + winPtr2->wmInfoPtr->hints.initial_state != WithdrawnState) { TkpWmSetState(winPtr2, WithdrawnState); transientPtr->flags |= WITHDRAWN_BY_MASTER; } @@ -3781,10 +3812,10 @@ WmWithdrawCmd( return TCL_OK; } - + /* - * Invoked by those wm subcommands that affect geometry. - * Schedules a geometry update. + * Invoked by those wm subcommands that affect geometry. Schedules a geometry + * update. */ static void @@ -3871,14 +3902,13 @@ Tk_SetGrid( /* * If gridding was previously off, then forget about any window size - * requests made by the user or via "wm geometry": these are in pixel - * units and there's no easy way to translate them to grid units since the - * new requested size of the top-level window in pixels may not yet have - * been registered yet (it may filter up the hierarchy in DoWhenIdle - * handlers). However, if the window has never been mapped yet then just - * leave the window size alone: assume that it is intended to be in grid - * units but just happened to have been specified before this procedure - * was called. + * requests made by the user or via "wm geometry": these are in pixel units + * and there's no easy way to translate them to grid units since the new + * requested size of the top-level window in pixels may not yet have been + * registered yet (it may filter up the hierarchy in DoWhenIdle handlers). + * However, if the window has never been mapped yet then just leave the + * window size alone: assume that it is intended to be in grid units but + * just happened to have been specified before this procedure was called. */ if ((wmPtr->gridWin == NULL) && !(wmPtr->flags & WM_NEVER_MAPPED)) { @@ -4399,9 +4429,10 @@ ParseGeometry( if (flags & WM_NEGATIVE_X) { int borderwidth = wmPtr->parentWidth - winPtr->changes.width; int newWidth = width == -1 ? winPtr->changes.width : width; + x = (x == -1) ? - wmPtr->x + winPtr->changes.width - newWidth : - wmPtr->vRootWidth - x - newWidth - borderwidth; + wmPtr->x + winPtr->changes.width - newWidth : + wmPtr->vRootWidth - x - newWidth - borderwidth; } if (x == -1) { x = wmPtr->x; @@ -4409,9 +4440,10 @@ ParseGeometry( if (flags & WM_NEGATIVE_Y) { int borderheight = wmPtr->parentHeight - winPtr->changes.height; int newHeight = height == -1 ? winPtr->changes.height : height; + y = (y == -1) ? - wmPtr->y + winPtr->changes.height - newHeight : - wmPtr->vRootHeight - y - newHeight - borderheight; + wmPtr->y + winPtr->changes.height - newHeight : + wmPtr->vRootHeight - y - newHeight - borderheight; } if (y == -1) { y = wmPtr->y; @@ -4538,8 +4570,7 @@ Tk_GetRootCoords( * * Results: * The return result is either a token for the window corresponding to - * rootX and rootY, or else NULL to indicate that there is no such - * window. + * rootX and rootY, or else NULL to indicate that there is no such window. * * Side effects: * None. @@ -4943,9 +4974,10 @@ TkWmRestackToplevel( /* * If the Tk windows has no drawable, or is withdrawn do nothing. */ + if (winPtr->window == None || - wmPtr == NULL || - wmPtr->hints.initial_state == WithdrawnState) { + wmPtr == NULL || + wmPtr->hints.initial_state == WithdrawnState) { return; } macWindow = TkMacOSXDrawableWindow(winPtr->window); @@ -4954,30 +4986,33 @@ TkWmRestackToplevel( } if (otherPtr) { /* - * When otherPtr is non-NULL, if the other window has no - * drawable or is withdrawn, do nothing. + * When otherPtr is non-NULL, if the other window has no drawable or is + * withdrawn, do nothing. */ + WmInfo *otherWmPtr = otherPtr->wmInfoPtr; if (winPtr->window == None || - otherWmPtr == NULL || - otherWmPtr->hints.initial_state == WithdrawnState) { - return; + otherWmPtr == NULL || + otherWmPtr->hints.initial_state == WithdrawnState) { + return; } otherMacWindow = TkMacOSXDrawableWindow(otherPtr->window); if (otherMacWindow == nil) { return; - } else { - /* - * If the other window is OK, get its number. - */ - otherNumber = [otherMacWindow windowNumber]; } + + /* + * If the other window is OK, get its number. + */ + + otherNumber = [otherMacWindow windowNumber]; } /* - * Just let the Mac window manager deal with all the subtleties - * of keeping track of off-screen windows, etc. + * Just let the Mac window manager deal with all the subtleties of keeping + * track of off-screen windows, etc. */ + [macWindow orderWindow:macAboveBelow relativeTo:otherNumber]; } @@ -5176,8 +5211,8 @@ TkGetPointerCoords( * * This function calculates the initial bounds for a new Mac toplevel * window. Unless the geometry is specified by the user this code will - * auto place the windows in a cascade diagonially across the main - * monitor of the Mac. + * auto place the windows in a cascade diagonially across the main monitor + * of the Mac. * * Results: * Window bounds. @@ -5418,7 +5453,6 @@ TkMacOSXIsWindowZoomed( NSWindow *macWindow = TkMacOSXDrawableWindow(winPtr->window); return [macWindow isZoomed]; } - /* *---------------------------------------------------------------------- @@ -5498,10 +5532,10 @@ TkUnsupported1ObjCmd( Tcl_Obj *const objv[]) /* Argument objects. */ { static const char *const subcmds[] = { - "style", "tabbingid", NULL + "style", "tabbingid", "appearance", "isdark", NULL }; enum SubCmds { - TKMWS_STYLE, TKMWS_TABID + TKMWS_STYLE, TKMWS_TABID, TKMWS_APPEARANCE, TKMWS_ISDARK }; Tk_Window tkwin = clientData; TkWindow *winPtr; @@ -5512,7 +5546,6 @@ TkUnsupported1ObjCmd( return TCL_ERROR; } - winPtr = (TkWindow *) Tk_NameToWindow(interp, Tcl_GetString(objv[2]), tkwin); if (winPtr == NULL) { @@ -5529,16 +5562,18 @@ TkUnsupported1ObjCmd( sizeof(char *), "option", 0, &index) != TCL_OK) { return TCL_ERROR; } - if (((enum SubCmds) index) == TKMWS_STYLE) { + switch((enum SubCmds) index) { + case TKMWS_STYLE: if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "window ?class attributes?"); return TCL_ERROR; } return WmWinStyle(interp, winPtr, objc, objv); - } else if (((enum SubCmds) index) == TKMWS_TABID) { + case TKMWS_TABID: if ([NSApp macMinorVersion] < 12) { - Tcl_AddErrorInfo(interp, - "\n (TabbingIdentifiers only exist on OSX 10.12 or later)"); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Tabbing identifiers did not exist until OSX 10.12.", -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "TABBINGID", NULL); return TCL_ERROR; } if ((objc < 3) || (objc > 4)) { @@ -5546,9 +5581,36 @@ TkUnsupported1ObjCmd( return TCL_ERROR; } return WmWinTabbingId(interp, winPtr, objc, objv); + case TKMWS_APPEARANCE: + if ([NSApp macMinorVersion] < 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Window appearances did not exist until OSX 10.9.", -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "APPEARANCE", NULL); + return TCL_ERROR; + } + if ((objc < 3) || (objc > 4)) { + Tcl_WrongNumArgs(interp, 2, objv, + "appearance window ?appearancename?"); + return TCL_ERROR; + } + if (objc == 4 && [NSApp macMinorVersion] < 14) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "Window appearances cannot be changed before OSX 10.14.", + -1)); + Tcl_SetErrorCode(interp, "TK", "WINDOWSTYLE", "APPEARANCE", NULL); + return TCL_ERROR; + } + return WmWinAppearance(interp, winPtr, objc, objv); + case TKMWS_ISDARK: + if ((objc != 3)) { + Tcl_WrongNumArgs(interp, 2, objv, "isdark window"); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TkMacOSXInDarkMode(tkwin))); + return TCL_OK; + default: + return TCL_ERROR; } - /* won't be reached */ - return TCL_ERROR; } /* @@ -5742,8 +5804,9 @@ WmWinStyle( * 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? + * 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 @@ -5757,7 +5820,6 @@ WmWinStyle( * window. Note, however, that changing the tabbingIdentifier of a window * which is already a tab does not cause it to become a separate window. * - * *---------------------------------------------------------------------- */ @@ -5794,7 +5856,11 @@ WmWinTabbingId( * same frame must have the same tabbingIdentifier. */ - if ([idString compare:newIdString] != NSOrderedSame && [win tab]) { + if ([idString compare:newIdString] != NSOrderedSame +#if MAC_OS_X_VERSION_MIN_REQUIRED > 101200 + && [win tab] +#endif + ) { [win moveTabToNewWindow:nil]; } return TCL_OK; @@ -5806,6 +5872,107 @@ WmWinTabbingId( /* *---------------------------------------------------------------------- * + * 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 + * with a Tk Window. The syntax is: + * + * tk::unsupported::MacWindowStyle tabbingid window ?newAppearance? + * + * Allowed appearance names are "aqua", "darkaqua", and "auto". + * + * Results: + * Returns the appearance setting of the window prior to calling this + * function. + * + * Side effects: + * The underlying NSWindow's appearance property is set to the specified + * value if the optional newAppearance argument is supplied. Otherwise the + * window's appearance property is not changed. If the appearance is set + * to aqua or darkaqua then the window will use the associated + * NSAppearance even if the user has selected a different appearance with + * the system preferences. If it is set to auto then the appearance + * property is set to nil, meaning that the preferences will determine the + * appearance. + * + *---------------------------------------------------------------------- + */ + +static int +WmWinAppearance( + 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 > 1090 + static const char *const appearanceStrings[] = { + "aqua", "darkaqua", "auto", NULL + }; + enum appearances { + APPEARANCE_AQUA, APPEARANCE_DARKAQUA, APPEARANCE_AUTO + }; + Tcl_Obj *result = NULL; +#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"; + NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); + if (win) { + appearance = win.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, strlen(resultString)); + } + if (result == NULL) { + Tcl_Panic("Failed to read appearance name."); + } + if (objc == 4) { + int index; + if (Tcl_GetIndexFromObjStruct(interp, objv[3], appearanceStrings, + sizeof(char *), "appearancename", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum appearances) index) { + case APPEARANCE_AQUA: + win.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameAqua]; + break; + case APPEARANCE_DARKAQUA: +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + if (@available(macOS 10.14, *)) { + win.appearance = [NSAppearance appearanceNamed: + NSAppearanceNameDarkAqua]; + } +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 101400 + break; + default: + win.appearance = nil; + } + } + Tcl_SetObjResult(interp, result); + return TCL_OK; +#else // MAC_OS_X_VERSION_MAX_ALLOWED > 1090 + return TCL_ERROR; +#endif +} + +/* + *---------------------------------------------------------------------- + * * TkpMakeMenuWindow -- * * Configure the window to be either a undecorated pull-down (or pop-up) @@ -5874,8 +6041,8 @@ TkMacOSXMakeRealWindowExist( macWin = (MacDrawable *) winPtr->window; /* - * If this is embedded, make sure its container's toplevel exists, - * then return... + * If this is embedded, make sure its container's toplevel exists, then + * return... */ if (Tk_IsEmbedded(winPtr)) { @@ -5904,8 +6071,8 @@ TkMacOSXMakeRealWindowExist( } /* - * If this is an override-redirect window, the NSWindow is created - * first as a document window then converted to a simple window. + * If this is an override-redirect window, the NSWindow is created first as + * a document window then converted to a simple window. */ if (overrideRedirect) { @@ -5959,8 +6126,8 @@ TkMacOSXMakeRealWindowExist( if ((styleMask & (NSTexturedBackgroundWindowMask|NSHUDWindowMask)) && !(styleMask & NSDocModalWindowMask)) { /* - * Workaround for [Bug 2824538]: Textured windows are draggable - * from opaque content. + * Workaround for [Bug 2824538]: Textured windows are draggable from + * opaque content. */ [window setMovableByWindowBackground:NO]; } @@ -5969,7 +6136,7 @@ TkMacOSXMakeRealWindowExist( macWin->view = window.contentView; TkMacOSXApplyWindowAttributes(winPtr, window); NSRect geometry = InitialWindowBounds(winPtr, window); - geometry.size.width += structureRect.size.width; + geometry.size.width += structureRect.size.width; geometry.size.height += structureRect.size.height; geometry.origin.y = tkMacOSXZeroScreenHeight - (geometry.origin.y + geometry.size.height); @@ -5979,6 +6146,7 @@ TkMacOSXMakeRealWindowExist( macWin->flags |= TK_HOST_EXISTS; if (overrideRedirect) { XSetWindowAttributes atts; + atts.override_redirect = True; Tk_ChangeWindowAttributes((Tk_Window) winPtr, CWOverrideRedirect, &atts); ApplyMasterOverrideChanges(winPtr, NULL); @@ -5990,9 +6158,9 @@ TkMacOSXMakeRealWindowExist( * * TkpDisplayWindow -- * - * Mark the contentView of this window as needing display so the - * window will be drawn by the window manager. If this is called - * within the drawRect method, do nothing. + * Mark the contentView of this window as needing display so the window + * will be drawn by the window manager. If this is called within the + * drawRect method, do nothing. * * Results: * None. @@ -6006,8 +6174,9 @@ TkMacOSXMakeRealWindowExist( MODULE_SCOPE void TkpDisplayWindow(Tk_Window tkwin) { if (![NSApp isDrawing]) { - TkWindow *winPtr = (TkWindow*)tkwin; + TkWindow *winPtr = (TkWindow *) tkwin; NSWindow *w = TkMacOSXDrawableWindow(winPtr->window); + [[w contentView] setNeedsDisplay: YES]; } } @@ -6017,8 +6186,8 @@ TkpDisplayWindow(Tk_Window tkwin) { * * TkMacOSXRegisterOffScreenWindow -- * - * This function adds the passed in Off Screen Port to the hash table - * that maps Mac windows to root X windows. + * This function adds the passed in Off Screen Port to the hash table that + * maps Mac windows to root X windows. * * Results: * None. @@ -6402,13 +6571,14 @@ TkpChangeFocus( return 0; } - if (Tk_IsTopLevel(winPtr) && !Tk_IsEmbedded(winPtr) ){ + if (Tk_IsTopLevel(winPtr) && !Tk_IsEmbedded(winPtr)) { NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); + TkWmRestackToplevel(winPtr, Above, NULL); - if (force ) { + if (force) { [NSApp activateIgnoringOtherApps:YES]; } - if ( win && [win canBecomeKeyWindow] ) { + if (win && [win canBecomeKeyWindow]) { [win makeKeyAndOrderFront:NSApp]; } } @@ -6567,6 +6737,7 @@ TkMacOSXApplyWindowAttributes( NSWindow *macWindow) { WmInfo *wmPtr = winPtr->wmInfoPtr; + ApplyWindowAttributeFlagChanges(winPtr, macWindow, 0, 0, 0, 1); if (wmPtr->master != NULL || winPtr->atts.override_redirect) { ApplyMasterOverrideChanges(winPtr, macWindow); @@ -6606,18 +6777,16 @@ ApplyWindowAttributeFlagChanges( if (changedAttributes || wmPtr->flags != oldFlags || initial) { if (!macWindow) { if (winPtr->window == None) { - if (create) { - Tk_MakeWindowExist((Tk_Window) winPtr); - } else { + if (!create) { return; } + Tk_MakeWindowExist((Tk_Window) winPtr); } if (!TkMacOSXHostToplevelExists(winPtr)) { - if (create) { - TkMacOSXMakeRealWindowExist(winPtr); - } else { + if (!create) { return; } + TkMacOSXMakeRealWindowExist(winPtr); } macWindow = TkMacOSXDrawableWindow(winPtr->window); } @@ -6654,6 +6823,7 @@ ApplyWindowAttributeFlagChanges( if ((newAttributes & kWindowToolbarButtonAttribute) && ![macWindow toolbar]) { NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""]; + [toolbar setVisible:NO]; [macWindow setToolbar:toolbar]; [toolbar release]; @@ -6697,26 +6867,26 @@ ApplyWindowAttributeFlagChanges( #if !(MAC_OS_X_VERSION_MAX_ALLOWED < 101000) if (!(macWindow.styleMask & NSUtilityWindowMask)) { - /* * Exclude overrideredirect, transient, and "help"-styled * windows from moving into their own fullscreen space. - * */ if ((winPtr->atts.override_redirect) || - (wmPtr->master != NULL) || - (winPtr->wmInfoPtr->macClass == kHelpWindowClass)) { + (wmPtr->master != NULL) || + (winPtr->wmInfoPtr->macClass == kHelpWindowClass)) { b |= (NSWindowCollectionBehaviorCanJoinAllSpaces | - NSWindowCollectionBehaviorFullScreenAuxiliary); + NSWindowCollectionBehaviorFullScreenAuxiliary); } else { - NSSize screenSize = [[macWindow screen]frame].size; + NSSize screenSize = [[macWindow screen] frame].size; b |= NSWindowCollectionBehaviorFullScreenPrimary; - /* The default max size has height less than the screen height. - * This causes the window manager to refuse to allow the window - * to be resized when it is a split window. To work around - * this we make the max size equal to the screen size. + /* + * The default max size has height less than the screen + * height. This causes the window manager to refuse to + * allow the window to be resized when it is a split + * window. To work around this we make the max size equal + * to the screen size. */ [macWindow setMaxFullScreenContentSize:screenSize]; @@ -6749,6 +6919,7 @@ ApplyWindowAttributeFlagChanges( */ NSRect structureRect = [macWindow frameRectForContentRect:NSZeroRect]; + wmPtr->xInParent = -structureRect.origin.x; wmPtr->yInParent = structureRect.origin.y + structureRect.size.height; wmPtr->parentWidth = winPtr->changes.width + structureRect.size.width; @@ -6824,7 +6995,7 @@ ApplyMasterOverrideChanges( } if (macWindow) { structureRect = [NSWindow frameRectForContentRect:NSZeroRect - styleMask:styleMask]; + styleMask:styleMask]; /* * Synchronize the wmInfoPtr to match the new window configuration @@ -6848,6 +7019,7 @@ ApplyMasterOverrideChanges( } } else { const char *title = winPtr->wmInfoPtr->titleUid; + if (!title) { title = winPtr->nameUid; } @@ -6857,11 +7029,12 @@ ApplyMasterOverrideChanges( wmPtr->flags &= ~WM_TOPMOST; } if (wmPtr->master != None) { - TkWindow *masterWinPtr = (TkWindow *)wmPtr->master; - if (masterWinPtr && masterWinPtr->window != None && - TkMacOSXHostToplevelExists(masterWinPtr)) { + TkWindow *masterWinPtr = (TkWindow *) wmPtr->master; + + if (masterWinPtr && (masterWinPtr->window != None) + && TkMacOSXHostToplevelExists(masterWinPtr)) { NSWindow *masterMacWin = TkMacOSXDrawableWindow( - masterWinPtr->window); + masterWinPtr->window); /* * Try to add the transient window as a child window of the @@ -6872,10 +7045,8 @@ ApplyMasterOverrideChanges( * zombie. So we only do this if the parent is visible. */ - if (masterMacWin && - [masterMacWin isVisible] && - (winPtr->flags & TK_MAPPED)) { - + if (masterMacWin && [masterMacWin isVisible] + && (winPtr->flags & TK_MAPPED)) { /* * If the transient is already a child of some other window, * remove it. @@ -6887,8 +7058,8 @@ ApplyMasterOverrideChanges( } [masterMacWin addChildWindow:macWindow - ordered:NSWindowAbove]; - } + ordered:NSWindowAbove]; + } } } else { parentWindow = [macWindow parentWindow]; @@ -7079,6 +7250,7 @@ RemapWindows( MacDrawable *parentWin) { TkWindow *childPtr; + /* * Remove the OS specific window. It will get rebuilt when the window gets * Mapped. @@ -7096,7 +7268,10 @@ RemapWindows( #endif } - /* Repeat for all the children */ + /* + * Repeat for all the children. + */ + for (childPtr = winPtr->childList; childPtr != NULL; childPtr = childPtr->nextPtr) { RemapWindows(childPtr, (MacDrawable *) winPtr->window); diff --git a/macosx/tkMacOSXXStubs.c b/macosx/tkMacOSXXStubs.c index bd9efa7..3cd70a7 100644 --- a/macosx/tkMacOSXXStubs.c +++ b/macosx/tkMacOSXXStubs.c @@ -178,7 +178,7 @@ TkpOpenDisplay( { int major, minor, patch; -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 Gestalt(gestaltSystemVersionMajor, (SInt32*)&major); Gestalt(gestaltSystemVersionMinor, (SInt32*)&minor); Gestalt(gestaltSystemVersionBugFix, (SInt32*)&patch); diff --git a/macosx/ttkMacOSXTheme.c b/macosx/ttkMacOSXTheme.c index c0f45bb..1179651 100644 --- a/macosx/ttkMacOSXTheme.c +++ b/macosx/ttkMacOSXTheme.c @@ -1,13 +1,14 @@ /* * ttkMacOSXTheme.c -- * - * Tk theme engine for Mac OSX, using the Appearance Manager API. + * Tk theme engine for Mac OSX, using the Appearance Manager API. * * Copyright (c) 2004 Joe English * Copyright (c) 2005 Neil Madden * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> * Copyright 2008-2009, Apple Inc. * Copyright 2009 Kevin Walzer/WordTech Communications LLC. + * Copyright 2019 Marc Culler * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -15,34 +16,35 @@ * See also: * * <URL: http://developer.apple.com/documentation/Carbon/Reference/ - * Appearance_Manager/appearance_manager/APIIndex.html > + * Appearance_Manager/appearance_manager/APIIndex.html > * * Notes: - * "Active" means different things in Mac and Tk terminology -- - * On Aqua, widgets are "Active" if they belong to the foreground window, - * "Inactive" if they are in a background window. - * Tk uses the term "active" to mean that the mouse cursor - * is over a widget; aka "hover", "prelight", or "hot-tracked". - * Aqua doesn't use this kind of feedback. + * "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. + * 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" +#include <math.h> /* - * Use this version in the core: + * Macros for handling drawing contexts. */ -#define BEGIN_DRAWING(d) { \ + +#define BEGIN_DRAWING(d) { \ TkMacOSXDrawingContext dc; \ if (!TkMacOSXSetupDrawingContext((d), NULL, 1, &dc)) {return;} #define END_DRAWING \ - TkMacOSXRestoreDrawingContext(&dc); } + TkMacOSXRestoreDrawingContext(&dc);} #define HIOrientation kHIThemeOrientationNormal +#define NoThemeMetric 0xFFFFFFFF #ifdef __LP64__ #define RangeToFactor(maximum) (((double) (INT_MAX >> 1)) / (maximum)) @@ -50,24 +52,88 @@ #define RangeToFactor(maximum) (((double) (LONG_MAX >> 1)) / (maximum)) #endif /* __LP64__ */ +#define TTK_STATE_FIRST_TAB TTK_STATE_USER1 +#define TTK_STATE_LAST_TAB TTK_STATE_USER2 +#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1 + +/* + * Colors and gradients used in Dark Mode. + */ + +static CGFloat darkButtonFace[4] = { + 112.0 / 255, 113.0 / 255, 115.0 / 255, 1.0 +}; +static CGFloat darkPressedBevelFace[4] = { + 135.0 / 255, 136.0 / 255, 138.0 / 255, 1.0 +}; +static CGFloat darkSelectedBevelFace[4] = { + 162.0 / 255, 163.0 / 255, 165.0 / 255, 1.0 +}; +static CGFloat darkDisabledButtonFace[4] = { + 86.0 / 255, 87.0 / 255, 89.0 / 255, 1.0 +}; +static CGFloat darkInactiveSelectedTab[4] = { + 159.0 / 255, 160.0 / 255, 161.0 / 255, 1.0 +}; +static CGFloat darkTabSeparator[4] = {0.0, 0.0, 0.0, 0.25}; +static CGFloat darkTrack[4] = {1.0, 1.0, 1.0, 0.25}; +static CGFloat darkFrameTop[4] = {1.0, 1.0, 1.0, 0.0625}; +static CGFloat darkFrameBottom[4] = {1.0, 1.0, 1.0, 0.125}; +static CGFloat darkFrameAccent[4] = {0.0, 0.0, 0.0, 0.0625}; +static CGFloat darkTopGradient[8] = { + 1.0, 1.0, 1.0, 0.3, + 1.0, 1.0, 1.0, 0.0 +}; +static CGFloat darkBackgroundGradient[8] = { + 0.0, 0.0, 0.0, 0.1, + 0.0, 0.0, 0.0, 0.25 +}; +static CGFloat darkInactiveGradient[8] = { + 89.0 / 255, 90.0 / 255, 93.0 / 255, 1.0, + 119.0 / 255, 120.0 / 255, 122.0 / 255, 1.0 +}; +static CGFloat darkSelectedGradient[8] = { + 23.0 / 255, 111.0 / 255, 232.0 / 255, 1.0, + 20.0 / 255, 94.0 / 255, 206.0 / 255, 1.0 +}; + +/* + * When building on systems earlier than 10.8 there is no reasonable way to + * convert an NSColor to a CGColor. We do run-time checking of the OS version, + * and never need the CGColor property on older systems, so we can use this + * CGCOLOR macro, which evaluates to NULL without raising compiler warnings. + * Similarly, we never draw rounded rectangles on older systems which did not + * have CGPathCreateWithRoundedRect, so we just redefine it to return NULL. + */ + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 +#define CGCOLOR(nscolor) nscolor.CGColor +#else +#define CGCOLOR(nscolor) (0 ? (CGColorRef) nscolor : NULL) +#define CGPathCreateWithRoundedRect(w, x, y, z) NULL +#endif + /*---------------------------------------------------------------------- * +++ Utilities. */ /* * BoxToRect -- - * Convert a Ttk_Box in Tk coordinates relative to the given Drawable - * to a native Rect relative to the containing port. + * 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) + +static inline CGRect BoxToRect( + Drawable d, + Ttk_Box b) { - MacDrawable *md = (MacDrawable*)d; + 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; + 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; } @@ -81,68 +147,1049 @@ static Ttk_StateTable ThemeStateTable[] = { {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} -*/ + + /* 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. + * NormalizeButtonBounds -- + * + * Apple's Human Interface Guidelines only allow three specific heights + * for most buttons: Regular, small and mini. We always use the regular + * size. However, Ttk may provide an arbitrary bounding rectangle. We + * always draw the button centered vertically on the rectangle, and + * having the same width as the rectangle. This function returns the + * actual bounding rectangle that will be used in drawing the button. + * + * The BevelButton is allowed to have arbitrary size, and also has + * external padding. This is handled separately here. + */ + +static CGRect NormalizeButtonBounds( + SInt32 heightMetric, + CGRect bounds) +{ + SInt32 height; + + if (heightMetric != (SInt32) NoThemeMetric) { + ChkErr(GetThemeMetric, heightMetric, &height); + bounds.origin.y += (bounds.size.height - height) / 2; + bounds.size.height = height; + } + return bounds; +} + +/*---------------------------------------------------------------------- + * +++ Backgrounds + * + * Support for contrasting background colors when GroupBoxes or Tabbed + * panes are nested inside each other. Early versions of macOS used ridged + * borders, so do not need contrasting backgrounds. */ /* - * Extra margins to account for drop shadow. + * For systems older than 10.14, [NSColor windowBackGroundColor] generates + * garbage when called from this function. In 10.14 it works correctly, and + * must be used in order to have a background color which responds to Dark + * Mode. So we use this hard-wired RGBA color on the older systems which don't + * support Dark Mode anyway. */ -static Ttk_Padding ButtonMargins = {2,2,2,2}; -#define NoThemeMetric 0xFFFFFFFF +static CGFloat windowBackground[4] = { + 235.0 / 255, 235.0 / 255, 235.0 / 255, 1.0 +}; +static CGFloat whiteRGBA[4] = {1.0, 1.0, 1.0, 1.0}; +static CGFloat blackRGBA[4] = {0.0, 0.0, 0.0, 1.0}; + +/*---------------------------------------------------------------------- + * GetBackgroundColor -- + * + * Fills the array rgba with the color coordinates for a background color. + * Start with the background color of a window's geometry master, or the + * standard ttk window background if there is no master. If the contrast + * parameter is nonzero, modify this color to be darker, for the aqua + * appearance, or lighter for the DarkAqua appearance. This is primarily + * used by the Fill and Background elements. + */ + +static void GetBackgroundColor( + CGContextRef context, + Tk_Window tkwin, + int contrast, + CGFloat *rgba) +{ + TkWindow *winPtr = (TkWindow *) tkwin; + TkWindow *masterPtr = (TkWindow *) TkGetGeomMaster(tkwin); + + while (masterPtr != NULL) { + if (masterPtr->privatePtr->flags & TTK_HAS_CONTRASTING_BG) { + break; + } + masterPtr = (TkWindow *) TkGetGeomMaster(masterPtr); + } + if (masterPtr) { + for (int i = 0; i < 4; i++) { + rgba[i] = masterPtr->privatePtr->fillRGBA[i]; + } + } else { + if ([NSApp macMinorVersion] > 13) { + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *windowColor = [[NSColor windowBackgroundColor] + colorUsingColorSpace: deviceRGB]; + [windowColor getComponents: rgba]; + } else { + for (int i = 0; i < 4; i++) { + rgba[i] = windowBackground[i]; + } + } + } + if (contrast) { + int isDark = (rgba[0] + rgba[1] + rgba[2] < 1.5); + + if (isDark) { + for (int i = 0; i < 3; i++) { + rgba[i] += 8.0 / 255.0; + } + } else { + for (int i = 0; i < 3; i++) { + rgba[i] -= 8.0 / 255.0; + } + } + winPtr->privatePtr->flags |= TTK_HAS_CONTRASTING_BG; + for (int i = 0; i < 4; i++) { + winPtr->privatePtr->fillRGBA[i] = rgba[i]; + } + } +} + + +/*---------------------------------------------------------------------- + * +++ Single Arrow Buttons -- + * + * Used in ListHeaders and Comboboxes. + */ + +static void DrawDownArrow( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + CGContextSetLineWidth(context, 1.5); + x = bounds.origin.x + inset; + y = bounds.origin.y + trunc(bounds.size.height / 2); + CGContextBeginPath(context); + CGPoint arrow[3] = { + {x, y - size / 4}, {x + size / 2, y + size / 4}, + {x + size, y - size / 4} + }; + CGContextAddLines(context, arrow, 3); + CGContextStrokePath(context); +} + +static void DrawUpArrow( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + CGContextSetLineWidth(context, 1.5); + x = bounds.origin.x + inset; + y = bounds.origin.y + trunc(bounds.size.height / 2); + CGContextBeginPath(context); + CGPoint arrow[3] = { + {x, y + size / 4}, {x + size / 2, y - size / 4}, + {x + size, y + size / 4} + }; + CGContextAddLines(context, arrow, 3); + CGContextStrokePath(context); +} + +/*---------------------------------------------------------------------- + * +++ Double Arrow Buttons -- + * + * Used in MenuButtons and SpinButtons. + */ + +static void DrawUpDownArrows( + CGContextRef context, + CGRect bounds, + CGFloat inset, + CGFloat size, + CGFloat *rgba) +{ + CGFloat x, y; + + CGContextSetRGBStrokeColor(context, rgba[0], rgba[1], rgba[2], rgba[3]); + CGContextSetLineWidth(context, 1.5); + x = bounds.origin.x + inset; + y = bounds.origin.y + trunc(bounds.size.height / 2); + CGContextBeginPath(context); + CGPoint bottomArrow[3] = + {{x, y + 2}, {x + size / 2, y + 2 + size / 2}, {x + size, y + 2}}; + CGContextAddLines(context, bottomArrow, 3); + CGPoint topArrow[3] = + {{x, y - 2}, {x + size / 2, y - 2 - size / 2}, {x + size, y - 2}}; + CGContextAddLines(context, topArrow, 3); + CGContextStrokePath(context); +} + + +/*---------------------------------------------------------------------- + * +++ FillButtonBackground -- + * + * Fills a rounded rectangle with a transparent black gradient. + * This is a no-op if building on 10.8 or older. + */ + +static void FillButtonBackground( + CGContextRef context, + CGRect bounds, + CGFloat radius) +{ + CGPathRef path; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGGradientRef backgroundGradient = CGGradientCreateWithColorComponents( + deviceRGB.CGColorSpace, darkBackgroundGradient, NULL, 2); + CGPoint backgroundEnd = { + bounds.origin.x, + bounds.origin.y + bounds.size.height + }; + + CGContextBeginPath(context); + path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL); + CGContextAddPath(context, path); + CGContextClip(context); + CGContextDrawLinearGradient(context, backgroundGradient, + bounds.origin, backgroundEnd, 0); + CFRelease(path); + CFRelease(backgroundGradient); +} + +/*---------------------------------------------------------------------- + * +++ HighlightButtonBorder -- + * + * Accent the top border of a rounded rectangle with a transparent + * white gradient. + */ + +static void HighlightButtonBorder( + CGContextRef context, + CGRect bounds) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGPoint topEnd = {bounds.origin.x, bounds.origin.y + 3}; + CGGradientRef topGradient = CGGradientCreateWithColorComponents( + deviceRGB.CGColorSpace, darkTopGradient, NULL, 2); + + CGContextSaveGState(context); + CGContextBeginPath(context); + CGContextAddArc(context, bounds.origin.x + 4, bounds.origin.y + 4, + 4, PI, 3 * PI / 2, 0); + CGContextAddArc(context, bounds.origin.x + bounds.size.width - 4, + bounds.origin.y + 4, 4, 3 * PI / 2, 0, 0); + CGContextReplacePathWithStrokedPath(context); + CGContextClip(context); + CGContextDrawLinearGradient(context, topGradient, bounds.origin, topEnd, + 0.0); + CGContextRestoreGState(context); + CFRelease(topGradient); +} + +/*---------------------------------------------------------------------- + * DrawGroupBox -- + * + * This is a standalone drawing procedure which draws the contrasting + * rounded rectangular box for LabelFrames and Notebook panes used in + * more recent versions of macOS. + */ + +static void DrawGroupBox( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin) +{ + CGPathRef path; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *borderColor, *bgColor; + static CGFloat border[4] = {1.0, 1.0, 1.0, 0.25}; + CGFloat fill[4]; + + GetBackgroundColor(context, tkwin, 1, fill); + bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill + count: 4]; + CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace); + CGContextSetFillColorWithColor(context, CGCOLOR(bgColor)); + path = CGPathCreateWithRoundedRect(bounds, 4, 4, NULL); + CGContextClipToRect(context, bounds); + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextFillPath(context); + borderColor = [NSColor colorWithColorSpace: deviceRGB components: border + count: 4]; + CGContextSetFillColorWithColor(context, CGCOLOR(borderColor)); + [borderColor getComponents: fill]; + CGContextSetRGBFillColor(context, fill[0], fill[1], fill[2], fill[3]); + + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextReplacePathWithStrokedPath(context); + CGContextFillPath(context); + CFRelease(path); +} + +/*---------------------------------------------------------------------- + * SolidFillRoundedRectangle -- + * + * Fill a rounded rectangle with a specified solid color. + */ + +static void SolidFillRoundedRectangle( + CGContextRef context, + CGRect bounds, + CGFloat radius, + NSColor *color) +{ + CGPathRef path; + + CGContextSetFillColorWithColor(context, CGCOLOR(color)); + path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL); + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextFillPath(context); + CFRelease(path); +} + +/*---------------------------------------------------------------------- + * +++ DrawListHeader -- + * + * This is a standalone drawing procedure which draws column headers for + * a Treeview in the Aqua appearance. The HITheme headers have not + * matched the native ones since OSX 10.8. Note that the header image is + * ignored, but we draw arrows according to the state. + */ + +static void DrawListHeader( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin, + int state) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *strokeColor, *bgColor; + static CGFloat borderRGBA[4] = { + 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0 + }; + static CGFloat separatorRGBA[4] = { + 220.0 / 255, 220.0 / 255, 220.0 / 255, 1.0 + }; + static CGFloat activeBgRGBA[4] = { + 238.0 / 255, 238.0 / 255, 238.0 / 255, 1.0 + }; + static CGFloat inactiveBgRGBA[4] = { + 246.0 / 255, 246.0 / 255, 246.0 / 255, 1.0 + }; + + /* + * Apple changes the background of a list header when the window is not + * active. But Ttk does not indicate that in the state of a TreeHeader. + * So we have to query the Apple window manager. + */ + + NSWindow *win = TkMacOSXDrawableWindow(Tk_WindowId(tkwin)); + CGFloat *bgRGBA = [win isKeyWindow] ? activeBgRGBA : inactiveBgRGBA; + CGFloat x = bounds.origin.x, y = bounds.origin.y; + CGFloat w = bounds.size.width, h = bounds.size.height; + CGPoint top[2] = {{x, y + 1}, {x + w, y + 1}}; + CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}}; + CGPoint separator[2] = {{x + w - 1, y + 3}, {x + w - 1, y + h - 3}}; + + bgColor = [NSColor colorWithColorSpace: deviceRGB + components: bgRGBA + count: 4]; + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + CGContextSetFillColorSpace(context, deviceRGB.CGColorSpace); + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + CGContextBeginPath(context); + CGContextSetFillColorWithColor(context, CGCOLOR(bgColor)); + CGContextAddRect(context, bounds); + CGContextFillPath(context); + strokeColor = [NSColor colorWithColorSpace: deviceRGB + components: separatorRGBA + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor)); + CGContextAddLines(context, separator, 2); + CGContextStrokePath(context); + strokeColor = [NSColor colorWithColorSpace: deviceRGB + components: borderRGBA + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(strokeColor)); + CGContextAddLines(context, top, 2); + CGContextStrokePath(context); + CGContextAddLines(context, bottom, 2); + CGContextStrokePath(context); + CGContextRestoreGState(context); + + if (state & TTK_TREEVIEW_STATE_SORTARROW) { + CGRect arrowBounds = bounds; + arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16; + arrowBounds.size.width = 16; + if (state & TTK_STATE_ALTERNATE) { + DrawUpArrow(context, arrowBounds, 3, 8, blackRGBA); + } else if (state & TTK_STATE_SELECTED) { + DrawDownArrow(context, arrowBounds, 3, 8, blackRGBA); + } + } +} + +/*---------------------------------------------------------------------- + * +++ Drawing procedures for widgets in Apple's "Dark Mode" (10.14 and up). + * + * The HIToolbox does not support Dark Mode, and apparently never will, + * so to make widgets look "native" we have to provide analogues of the + * HITheme drawing functions to be used in DarkAqua. We continue to use + * HITheme in Aqua, since it understands earlier versions of the OS. + * + * Drawing the dark widgets requires NSColors that were introduced in OSX + * 10.14, so we make some of these functions be no-ops when building on + * systems older than 10.14. + */ + +/*---------------------------------------------------------------------- + * GradientFillRoundedRectangle -- + * + * Fill a rounded rectangle with a specified gradient. + */ + +static void GradientFillRoundedRectangle( + CGContextRef context, + CGRect bounds, + CGFloat radius, + CGFloat *colors, + int numColors) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGPathRef path; + CGPoint end = { + bounds.origin.x, + bounds.origin.y + bounds.size.height + }; + CGGradientRef gradient = CGGradientCreateWithColorComponents( + deviceRGB.CGColorSpace, colors, NULL, numColors); + + path = CGPathCreateWithRoundedRect(bounds, radius, radius, NULL); + CGContextBeginPath(context); + CGContextAddPath(context, path); + CGContextClip(context); + CGContextDrawLinearGradient(context, gradient, bounds.origin, end, 0); + CFRelease(path); + CFRelease(gradient); +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkButton -- + * + * This is a standalone drawing procedure which draws PushButtons and + * PopupButtons in the Dark Mode style. + */ + +static void DrawDarkButton( + CGRect bounds, + ThemeButtonKind kind, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + + /* + * To match the appearance of Apple's buttons we need to increase the + * height by 1 pixel. + */ + + bounds.size.height += 1; + + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 5); + + /* + * Fill the button face with the appropriate color. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (kind == kThemePushButton && (state & TTK_STATE_PRESSED)) { + GradientFillRoundedRectangle(context, bounds, 4, + darkSelectedGradient, 2); + } else { + if (state & TTK_STATE_DISABLED) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkDisabledButtonFace + count: 4]; + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkButtonFace + count: 4]; + } + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + } + + /* + * If this is a popup, draw the arrow button. + */ + + if ((kind == kThemePopupButton) | (kind == kThemeComboBox)) { + CGRect arrowBounds = bounds; + arrowBounds.size.width = 16; + arrowBounds.origin.x += bounds.size.width - 16; + + /* + * If the toplevel is front, paint the button blue. + */ + + if (!(state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED)) { + GradientFillRoundedRectangle(context, arrowBounds, 4, + darkSelectedGradient, 2); + } + if (kind == kThemePopupButton) { + DrawUpDownArrows(context, arrowBounds, 3, 7, whiteRGBA); + } else { + DrawDownArrow(context, arrowBounds, 4, 8, whiteRGBA); + } + } + + HighlightButtonBorder(context, bounds); +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkIncDecButton -- + * + * This is a standalone drawing procedure which draws an IncDecButton + * (as used in a Spinbox) in the Dark Mode style. + */ + +static void DrawDarkIncDecButton( + CGRect bounds, + ThemeDrawState drawState, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + + bounds = CGRectInset(bounds, 0, -1); + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 6); + + /* + * Fill the button face with the appropriate color. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (state & TTK_STATE_DISABLED) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkDisabledButtonFace + count: 4]; + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkButtonFace + count: 4]; + } + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + + /* + * If pressed, paint the appropriate half blue. + */ + + if (state & TTK_STATE_PRESSED) { + CGRect clip = bounds; + clip.size.height /= 2; + CGContextSaveGState(context); + if (drawState == kThemeStatePressedDown) { + clip.origin.y += clip.size.height; + } + CGContextClipToRect(context, clip); + GradientFillRoundedRectangle(context, bounds, 5, + darkSelectedGradient, 2); + CGContextRestoreGState(context); + } + DrawUpDownArrows(context, bounds, 3, 5, whiteRGBA); + HighlightButtonBorder(context, bounds); +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkBevelButton -- + * + * This is a standalone drawing procedure which draws RoundedBevelButtons + * in the Dark Mode style. + */ + +static void DrawDarkBevelButton( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor; + + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 5); + + /* + * Fill the button face with the appropriate color. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (state & TTK_STATE_PRESSED) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkPressedBevelFace + count: 4]; + } else if ((state & TTK_STATE_DISABLED) || + (state & TTK_STATE_ALTERNATE)) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkDisabledButtonFace + count: 4]; + } else if (state & TTK_STATE_SELECTED) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkSelectedBevelFace + count: 4]; + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkButtonFace + count: 4]; + } + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + HighlightButtonBorder(context, bounds); +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkCheckBox -- + * + * This is a standalone drawing procedure which draws Checkboxes in the + * Dark Mode style. + */ + +static void DrawDarkCheckBox( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + CGRect checkbounds = {{0, bounds.size.height / 2 - 8}, {16, 16}}; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *stroke; + CGFloat x, y; + + bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y); + x = bounds.origin.x; + y = bounds.origin.y; + + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 4); + bounds = CGRectInset(bounds, 1, 1); + if (!(state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED) && + ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) { + GradientFillRoundedRectangle(context, bounds, 3, + darkSelectedGradient, 2); + } else { + GradientFillRoundedRectangle(context, bounds, 3, + darkInactiveGradient, 2); + } + HighlightButtonBorder(context, bounds); + if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) { + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + if (state & TTK_STATE_DISABLED) { + stroke = [NSColor disabledControlTextColor]; + } else { + stroke = [NSColor controlTextColor]; + } + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + } + if (state & TTK_STATE_SELECTED) { + CGContextSetLineWidth(context, 1.5); + CGContextBeginPath(context); + CGPoint check[3] = {{x + 4, y + 8}, {x + 7, y + 11}, {x + 11, y + 4}}; + CGContextAddLines(context, check, 3); + CGContextStrokePath(context); + } else if (state & TTK_STATE_ALTERNATE) { + CGContextSetLineWidth(context, 2.0); + CGContextBeginPath(context); + CGPoint bar[2] = {{x + 4, y + 8}, {x + 12, y + 8}}; + CGContextAddLines(context, bar, 2); + CGContextStrokePath(context); + } +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkRadioButton -- + * + * This is a standalone drawing procedure which draws RadioButtons + * in the Dark Mode style. + */ + +static void DrawDarkRadioButton( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + CGRect checkbounds = {{0, bounds.size.height / 2 - 9}, {18, 18}}; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *fill; + CGFloat x, y; + + bounds = CGRectOffset(checkbounds, bounds.origin.x, bounds.origin.y); + x = bounds.origin.x; + y = bounds.origin.y; + + CGContextClipToRect(context, bounds); + FillButtonBackground(context, bounds, 9); + bounds = CGRectInset(bounds, 1, 1); + if (!(state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED) && + ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE))) { + GradientFillRoundedRectangle(context, bounds, 8, + darkSelectedGradient, 2); + } else { + GradientFillRoundedRectangle(context, bounds, 8, + darkInactiveGradient, 2); + } + HighlightButtonBorder(context, bounds); + if ((state & TTK_STATE_SELECTED) || (state & TTK_STATE_ALTERNATE)) { + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + if (state & TTK_STATE_DISABLED) { + fill = [NSColor disabledControlTextColor]; + } else { + fill = [NSColor controlTextColor]; + } + CGContextSetFillColorWithColor(context, CGCOLOR(fill)); + } + if (state & TTK_STATE_SELECTED) { + CGContextBeginPath(context); + CGRect dot = {{x + 6, y + 6}, {6, 6}}; + CGContextAddEllipseInRect(context, dot); + CGContextFillPath(context); + } else if (state & TTK_STATE_ALTERNATE) { + CGRect bar = {{x + 5, y + 8}, {8, 2}}; + CGContextFillRect(context, bar); + } +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkTab -- + * + * This is a standalone drawing procedure which draws Tabbed Pane + * Tabs in the Dark Mode style. + */ + +static void DrawDarkTab( + CGRect bounds, + Ttk_State state, + CGContextRef context) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *faceColor, *stroke; + CGRect originalBounds = bounds; + + CGContextSetLineWidth(context, 1.0); + CGContextClipToRect(context, bounds); + + /* + * Extend the bounds to one or both sides so the rounded part will be + * clipped off. + */ + + if (!(state & TTK_STATE_FIRST_TAB)) { + bounds.origin.x -= 10; + bounds.size.width += 10; + } + + if (!(state & TTK_STATE_LAST_TAB)) { + bounds.size.width += 10; + } + + /* + * Fill the tab face with the appropriate color or gradient. Use a solid + * color if the tab is not selected, otherwise use a blue or gray + * gradient. + */ + + bounds = CGRectInset(bounds, 1, 1); + if (!(state & TTK_STATE_SELECTED)) { + if (state & TTK_STATE_DISABLED) { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkDisabledButtonFace + count: 4]; + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkButtonFace + count: 4]; + } + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + + /* + * Draw a separator line on the left side of the tab if it + * not first. + */ + + if (!(state & TTK_STATE_FIRST_TAB)) { + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkTabSeparator + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + CGContextBeginPath(context); + CGContextMoveToPoint(context, originalBounds.origin.x, + originalBounds.origin.y + 1); + CGContextAddLineToPoint(context, originalBounds.origin.x, + originalBounds.origin.y + originalBounds.size.height - 1); + CGContextStrokePath(context); + CGContextRestoreGState(context); + } + } else { + + /* + * This is the selected tab; paint it blue. If it is first, cover up + * the separator line drawn by the second one. (The selected tab is + * always drawn last.) + */ + + if ((state & TTK_STATE_FIRST_TAB) && !(state & TTK_STATE_LAST_TAB)) { + bounds.size.width += 1; + } + if (!(state & TTK_STATE_BACKGROUND)) { + GradientFillRoundedRectangle(context, bounds, 4, + darkSelectedGradient, 2); + } else { + faceColor = [NSColor colorWithColorSpace: deviceRGB + components: darkInactiveSelectedTab + count: 4]; + SolidFillRoundedRectangle(context, bounds, 4, faceColor); + } + HighlightButtonBorder(context, bounds); + } +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkSeparator -- + * + * This is a standalone drawing procedure which draws a separator widget + * in Dark Mode. + */ + +static void DrawDarkSeparator( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin) +{ + static CGFloat fill[4] = {1.0, 1.0, 1.0, 0.3}; + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *fillColor = [NSColor colorWithColorSpace: deviceRGB + components: fill + count:4]; + + CGContextSetFillColorWithColor(context, CGCOLOR(fillColor)); + CGContextFillRect(context, bounds); +} + +/*---------------------------------------------------------------------- + * +++ DrawDarkFrame -- + * + * This is a standalone drawing procedure which draws various + * types of borders in Dark Mode. + */ + +static void DrawDarkFrame( + CGRect bounds, + CGContextRef context, + HIThemeFrameKind kind) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *stroke; + + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + CGFloat x = bounds.origin.x, y = bounds.origin.y; + CGFloat w = bounds.size.width, h = bounds.size.height; + CGPoint topPart[4] = { + {x, y + h - 1}, {x, y}, {x + w, y}, {x + w, y + h - 1} + }; + CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}}; + CGPoint accent[2] = {{x, y + 1}, {x + w, y + 1}}; + switch (kind) { + case kHIThemeFrameTextFieldSquare: + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + CGContextBeginPath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameTop + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + CGContextAddLines(context, topPart, 4); + CGContextStrokePath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameBottom + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + CGContextAddLines(context, bottom, 2); + CGContextStrokePath(context); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameAccent + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + CGContextAddLines(context, accent, 2); + CGContextStrokePath(context); + CGContextRestoreGState(context); + break; + default: + break; + } +} + +/*---------------------------------------------------------------------- + * +++ DrawListHeader -- + * + * This is a standalone drawing procedure which draws column + * headers for a Treeview in the Dark Mode. + */ + +static void DrawDarkListHeader( + CGRect bounds, + CGContextRef context, + Tk_Window tkwin, + int state) +{ + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *stroke; + + CGContextSetStrokeColorSpace(context, deviceRGB.CGColorSpace); + CGFloat x = bounds.origin.x, y = bounds.origin.y; + CGFloat w = bounds.size.width, h = bounds.size.height; + CGPoint top[2] = {{x, y}, {x + w, y}}; + CGPoint bottom[2] = {{x, y + h}, {x + w, y + h}}; + CGPoint separator[2] = {{x + w, y + 3}, {x + w, y + h - 3}}; + + CGContextSaveGState(context); + CGContextSetShouldAntialias(context, false); + stroke = [NSColor colorWithColorSpace: deviceRGB + components: darkFrameBottom + count: 4]; + CGContextSetStrokeColorWithColor(context, CGCOLOR(stroke)); + CGContextBeginPath(context); + CGContextAddLines(context, top, 2); + CGContextStrokePath(context); + CGContextAddLines(context, bottom, 2); + CGContextStrokePath(context); + CGContextAddLines(context, separator, 2); + CGContextStrokePath(context); + CGContextRestoreGState(context); + + if (state & TTK_TREEVIEW_STATE_SORTARROW) { + CGRect arrowBounds = bounds; + + arrowBounds.origin.x = bounds.origin.x + bounds.size.width - 16; + arrowBounds.size.width = 16; + if (state & TTK_STATE_ALTERNATE) { + DrawUpArrow(context, arrowBounds, 3, 8, whiteRGBA); + } else if (state & TTK_STATE_SELECTED) { + DrawDownArrow(context, arrowBounds, 3, 8, whiteRGBA); + } + } +} + +/*---------------------------------------------------------------------- + * +++ Button element: Used for elements drawn with DrawThemeButton. + */ + +/* + * When Ttk draws the various types of buttons, a pointer to one of these + * is passed as the clientData. + */ 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 }; - + PushButtonParams = {kThemePushButton, kThemeMetricPushButtonHeight}, + CheckBoxParams = {kThemeCheckBox, kThemeMetricCheckBoxHeight}, + RadioButtonParams = {kThemeRadioButton, kThemeMetricRadioButtonHeight}, + BevelButtonParams = {kThemeRoundedBevelButton, 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 */ -}; + {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 } + {kThemeAdornmentDefault | kThemeAdornmentFocus, + TTK_STATE_ALTERNATE | TTK_STATE_FOCUS, 0}, + {kThemeAdornmentDefault, TTK_STATE_ALTERNATE, 0}, + {kThemeAdornmentNone, TTK_STATE_ALTERNATE, 0}, + {kThemeAdornmentFocus, TTK_STATE_FOCUS, 0}, + {kThemeAdornmentNone, 0, 0} }; -/* - * computeButtonDrawInfo -- - * Fill in an appearance manager HIThemeButtonDrawInfo record. +/*---------------------------------------------------------------------- + * +++ computeButtonDrawInfo -- + * + * Fill in an appearance manager HIThemeButtonDrawInfo record. */ + static inline HIThemeButtonDrawInfo computeButtonDrawInfo( - ThemeButtonParams *params, Ttk_State state) + ThemeButtonParams *params, + Ttk_State state, + Tk_Window tkwin) { + + /* + * See ButtonElementDraw for the explanation of why we always draw + * PushButtons in the active state. + */ + + SInt32 HIThemeState; + + switch (params->kind) { + case kThemePushButton: + HIThemeState = kThemeStateActive; + break; + default: + HIThemeState = Ttk_StateTableLookup(ThemeStateTable, state); + break; + } + const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state), + .state = HIThemeState, .kind = params ? params->kind : 0, .value = Ttk_StateTableLookup(ButtonValueTable, state), .adornment = Ttk_StateTableLookup(ButtonAdornmentTable, state), @@ -150,66 +1197,156 @@ static inline HIThemeButtonDrawInfo computeButtonDrawInfo( return info; } -static void ButtonElementSizeNoPadding( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) +/*---------------------------------------------------------------------- + * +++ Button elements. + */ + +static void ButtonElementMinSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { ThemeButtonParams *params = clientData; if (params->heightMetric != NoThemeMetric) { - SInt32 height; + ChkErr(GetThemeMetric, params->heightMetric, minHeight); + + /* + * The theme height does not include the 1-pixel border around + * the button, although it does include the 1-pixel shadow at + * the bottom. + */ - ChkErr(GetThemeMetric, params->heightMetric, &height); - *heightPtr = height; + *minHeight += 2; + + /* + * The minwidth must be 0 to force the generic ttk code to compute the + * correct text layout. For example, a non-zero value will cause the + * text to be left justified, no matter what -anchor setting is used in + * the style. + */ + + *minWidth = 0; } } static void ButtonElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { ThemeButtonParams *params = clientData; - const HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, 0); + const HIThemeButtonDrawInfo info = + computeButtonDrawInfo(params, 0, tkwin); static const CGRect scratchBounds = {{0, 0}, {100, 100}}; - CGRect contentBounds; + CGRect contentBounds, backgroundBounds; + int verticalPad; - ButtonElementSizeNoPadding( - clientData, elementRecord, tkwin, - widthPtr, heightPtr, paddingPtr); + ButtonElementMinSize(clientData, elementRecord, tkwin, + minWidth, minHeight, paddingPtr); /* - * To compute internal padding, query the appearance manager - * for the content bounds of a dummy rectangle, then use - * the difference as the padding. + * Given a hypothetical bounding rectangle for a button, HIToolbox will + * compute a bounding rectangle for the button contents and a bounding + * rectangle for the button background. The background bounds are large + * enough to contain the image of the button in any state, which might + * include highlight borders, shadows, etc. The content rectangle is not + * centered vertically within the background rectangle, presumably because + * shadows only appear on the bottom. Nonetheless, when HITools is asked + * to draw a button with a certain bounding rectangle it draws the button + * centered within the rectangle. + * + * To compute the effective padding around a button we request the + * content and bounding rectangles for a 100x100 button and use the + * padding between those. However, we symmetrize the padding on the + * top and bottom, because that is how the button will be drawn. */ + 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); + ChkErr(HIThemeGetButtonBackgroundBounds, + &scratchBounds, &info, &backgroundBounds); + paddingPtr->left = contentBounds.origin.x - backgroundBounds.origin.x; + paddingPtr->right = + CGRectGetMaxX(backgroundBounds) - CGRectGetMaxX(contentBounds); + verticalPad = backgroundBounds.size.height - contentBounds.size.height; + paddingPtr->top = paddingPtr->bottom = verticalPad / 2; } static void ButtonElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + 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); + CGRect bounds = BoxToRect(d, b); + HIThemeButtonDrawInfo info = computeButtonDrawInfo(params, state, tkwin); + + bounds = NormalizeButtonBounds(params->heightMetric, bounds); BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if (TkMacOSXInDarkMode(tkwin)) { + switch (info.kind) { + case kThemePushButton: + case kThemePopupButton: + DrawDarkButton(bounds, info.kind, state, dc.context); + break; + case kThemeCheckBox: + DrawDarkCheckBox(bounds, state, dc.context); + break; + case kThemeRadioButton: + DrawDarkRadioButton(bounds, state, dc.context); + break; + case kThemeRoundedBevelButton: + DrawDarkBevelButton(bounds, state, dc.context); + break; + default: + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, + HIOrientation, NULL); + } + } else { + + /* + * Apple's PushButton and PopupButton do not change their fill color + * when the window is inactive. However, except in 10.7 (Lion), the + * color of the arrow button on a PopupButton does change. For some + * reason HITheme fills inactive buttons with a transparent color that + * allows the window background to show through, leading to + * inconsistent behavior. We work around this by filling behind an + * inactive PopupButton with a text background color before asking + * HIToolbox to draw it. For PushButtons, we simply draw them in the + * active state. + */ + + if (info.kind == kThemePopupButton && + (state & TTK_STATE_BACKGROUND)) { + CGRect innerBounds = CGRectInset(bounds, 1, 1); + NSColor *whiteRGBA = [NSColor whiteColor]; + SolidFillRoundedRectangle(dc.context, innerBounds, 4, whiteRGBA); + } + + /* + * A BevelButton with mixed value is drawn borderless, which does make + * much sense for us. + */ + + if (info.kind == kThemeRoundedBevelButton && + info.value == kThemeButtonMixed) { + info.value = kThemeButtonOff; + info.state = kThemeStateInactive; + } + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); + } END_DRAWING } @@ -225,35 +1362,27 @@ static Ttk_ElementSpec ButtonElementSpec = { * +++ 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} + {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 }, + {kHIThemeTabAdornmentNone, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB}, + {kHIThemeTabAdornmentTrailingSeparator, TTK_STATE_FIRST_TAB}, + {kHIThemeTabAdornmentNone, TTK_STATE_LAST_TAB}, + {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 }, + {kHIThemeTabPositionOnly, TTK_STATE_FIRST_TAB | TTK_STATE_LAST_TAB}, + {kHIThemeTabPositionFirst, TTK_STATE_FIRST_TAB}, + {kHIThemeTabPositionLast, TTK_STATE_LAST_TAB}, + {kHIThemeTabPositionMiddle, 0}, }; /* @@ -271,7 +1400,8 @@ static Ttk_StateTable TabPositionTable[] = { * 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. + * - 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. * @@ -285,6 +1415,7 @@ static Ttk_StateTable TabPositionTable[] = { * 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. @@ -295,17 +1426,25 @@ static Ttk_StateTable TabPositionTable[] = { */ static void TabElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { - GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *)heightPtr); + GetThemeMetric(kThemeMetricLargeTabHeight, (SInt32 *) minHeight); *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) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) { CGRect bounds = BoxToRect(d, b); HIThemeTabDrawInfo info = { @@ -319,7 +1458,12 @@ static void TabElementDraw( }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation, NULL); + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkTab(bounds, state, dc.context); + } else { + ChkErr(HIThemeDrawTab, &bounds, &info, dc.context, HIOrientation, + NULL); + } END_DRAWING } @@ -334,31 +1478,46 @@ static Ttk_ElementSpec TabElementSpec = { /* * Notebook panes: */ + static void PaneElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + 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) + 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); + if ([NSApp macMinorVersion] > 8) { + DrawGroupBox(bounds, dc.context, tkwin); + } else { + 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; + ChkErr(HIThemeDrawTabPane, &bounds, &info, dc.context, HIOrientation); + } END_DRAWING } @@ -370,36 +1529,49 @@ static Ttk_ElementSpec PaneElementSpec = { 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." +/*---------------------------------------------------------------------- + * +++ Labelframe elements -- * - * "Maximum of 2 pixels thick" is apparently a lie; - * looks more like 4 to me with shading. + * 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) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + 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) + 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); + if ([NSApp macMinorVersion] > 8) { + DrawGroupBox(bounds, dc.context, tkwin); + } else { + const HIThemeGroupBoxDrawInfo info = { + .version = 0, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .kind = kHIThemeGroupBoxKindPrimaryOpaque, + }; + ChkErr(HIThemeDrawGroupBox, &bounds, &info, dc.context, HIOrientation); + } END_DRAWING } @@ -412,56 +1584,106 @@ static Ttk_ElementSpec GroupElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Entry element -- - * 3 pixels padding for focus rectangle - * 2 pixels padding for EditTextFrame + * +++ Entry elements -- + * + * 3 pixels padding for focus rectangle + * 2 pixels padding for EditTextFrame */ typedef struct { - Tcl_Obj *backgroundObj; + Tcl_Obj *backgroundObj; + Tcl_Obj *fieldbackgroundObj; } EntryElement; +#define ENTRY_DEFAULT_BACKGROUND "systemTextBackgroundColor" + static Ttk_ElementOptionSpec EntryElementOptions[] = { - { "-background", TK_OPTION_BORDER, - offsetof(EntryElement,backgroundObj), "white" }, + {"-background", TK_OPTION_BORDER, + offsetof(EntryElement, backgroundObj), ENTRY_DEFAULT_BACKGROUND}, + {"-fieldbackground", TK_OPTION_BORDER, + offsetof(EntryElement, fieldbackgroundObj), ENTRY_DEFAULT_BACKGROUND}, {0} }; static void EntryElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + 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) + 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, - }; + NSColor *background; + Tk_3DBorder backgroundPtr = NULL; + static const char *defaultBG = ENTRY_DEFAULT_BACKGROUND; - /* - * 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); + if (TkMacOSXInDarkMode(tkwin)) { + BEGIN_DRAWING(d) + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + CGFloat fill[4]; + GetBackgroundColor(dc.context, tkwin, 1, fill); + background = [NSColor colorWithColorSpace: deviceRGB + components: fill + count: 4]; + CGContextSetFillColorWithColor(dc.context, CGCOLOR(background)); + CGContextFillRect(dc.context, bounds); + DrawDarkFrame(bounds, dc.context, kHIThemeFrameTextFieldSquare); + END_DRAWING + } else { + const HIThemeFrameDrawInfo info = { + .version = 0, + .kind = kHIThemeFrameTextFieldSquare, + .state = Ttk_StateTableLookup(ThemeStateTable, state), + .isFocused = state & TTK_STATE_FOCUS, + }; - BEGIN_DRAWING(d) - ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); - /*if (state & TTK_STATE_FOCUS) { - ChkErr(DrawThemeFocusRect, &bounds, 1); - }*/ - END_DRAWING + /* + * Earlier versions of the Aqua theme ignored the -fieldbackground + * option and used the -background as if it were -fieldbackground. + * Here we are enabling -fieldbackground. For backwards + * compatibility, if -fieldbackground is set to the default color and + * -background is set to a different color then we use -background as + * -fieldbackground. + */ + + if (0 != strcmp(Tcl_GetString(e->fieldbackgroundObj), defaultBG)) { + backgroundPtr = + Tk_Get3DBorderFromObj(tkwin, e->fieldbackgroundObj); + } else if (0 != strcmp(Tcl_GetString(e->backgroundObj), defaultBG)) { + backgroundPtr = Tk_Get3DBorderFromObj(tkwin, e->backgroundObj); + } + if (backgroundPtr != NULL) { + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + inner.x, inner.y, inner.width, inner.height); + } + BEGIN_DRAWING(d) + if (backgroundPtr == NULL) { + if ([NSApp macMinorVersion] > 8) { + background = [NSColor textBackgroundColor]; + CGContextSetFillColorWithColor(dc.context, CGCOLOR(background)); + } else { + CGContextSetRGBFillColor(dc.context, 1.0, 1.0, 1.0, 1.0); + } + CGContextFillRect(dc.context, bounds); + } + ChkErr(HIThemeDrawFrame, &bounds, &info, dc.context, HIOrientation); + END_DRAWING + } } static Ttk_ElementSpec EntryElementSpec = { @@ -473,30 +1695,49 @@ static Ttk_ElementSpec EntryElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Combobox: + * +++ Combobox elements -- * * NOTES: - * kThemeMetricComboBoxLargeDisclosureWidth -> 17 - * Padding and margins guesstimated by trial-and-error. + * The HIToolbox has incomplete and inconsistent support for ComboBoxes. + * There is no constant available to get the height of a ComboBox with + * GetThemeMetric. In fact, ComboBoxes are the same (fixed) height as + * PopupButtons and PushButtons, but they have no shadow at the bottom. + * As a result, they are drawn 1 pixel above the center of the bounds + * rectangle rather than being centered like the other buttons. One can + * request background bounds for a ComboBox, and it is reported with + * height 23, while the actual button face, including its 1-pixel border + * has height 21. Attempting to request the content bounds returns a 0x0 + * rectangle. Measurement indicates that the arrow button has width 18. + * + * With no help available from HIToolbox, we have to use hard-wired + * constants for the padding. We shift the bounding rectangle downward by + * 1 pixel to account for the fact that the button is not centered. */ -static Ttk_Padding ComboboxPadding = { 2, 3, 17, 1 }; -static Ttk_Padding ComboboxMargins = { 3, 3, 4, 4 }; +static Ttk_Padding ComboboxPadding = {4, 2, 20, 2}; static void ComboboxElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { - *widthPtr = 0; - *heightPtr = 0; - *paddingPtr = Ttk_AddPadding(ComboboxMargins, ComboboxPadding); + *minWidth = 24; + *minHeight = 23; + *paddingPtr = ComboboxPadding; } static void ComboboxElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) { - CGRect bounds = BoxToRect(d, Ttk_PadBox(b, ComboboxMargins)); + CGRect bounds = BoxToRect(d, b); const HIThemeButtonDrawInfo info = { .version = 0, .state = Ttk_StateTableLookup(ThemeStateTable, state), @@ -506,7 +1747,20 @@ static void ComboboxElementDraw( }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + bounds.origin.y += 1; + if (TkMacOSXInDarkMode(tkwin)) { + bounds.size.height += 1; + DrawDarkButton(bounds, info.kind, state, dc.context); + } else if ([NSApp macMinorVersion] > 8) { + if ((state & TTK_STATE_BACKGROUND) && + !(state & TTK_STATE_DISABLED)) { + NSColor *background = [NSColor textBackgroundColor]; + CGRect innerBounds = CGRectInset(bounds, 1, 2); + SolidFillRoundedRectangle(dc.context, innerBounds, 4, background); + } + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); + } END_DRAWING } @@ -518,112 +1772,209 @@ static Ttk_ElementSpec ComboboxElementSpec = { ComboboxElementDraw }; +/*---------------------------------------------------------------------- + * +++ Spinbutton elements -- + * + * 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. + * + * Ttk expects the up and down arrows to be distinct elements but + * HIToolbox draws them as one widget with two different pressed states. + * We work around this by defining them as separate elements in the + * layout, but making each one have a drawing method which also draws the + * other one. The down button does no drawing when not pressed, and when + * pressed draws the entire IncDecButton in its "pressed down" state. + * The up button draws the entire IncDecButton when not pressed and when + * pressed draws the IncDecButton in its "pressed up" state. NOTE: This + * means that when the down button is pressed the IncDecButton will be + * drawn twice, first in unpressed state by the up arrow and then in + * "pressed down" state by the down button. The drawing must be done in + * that order. So the up button must be listed first in the layout. + */ +static Ttk_Padding SpinbuttonMargins = {0, 0, 2, 0}; +static void SpinButtonUpElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + SInt32 s; + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s); + *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins); + ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s); + *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2; +} -/*---------------------------------------------------------------------- - * +++ 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 void SpinButtonUpElementDraw( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) +{ + CGRect bounds = BoxToRect(d, Ttk_PadBox(b, SpinbuttonMargins)); + int infoState; -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) + bounds.size.height *= 2; + if (state & TTK_STATE_PRESSED) { + infoState = kThemeStatePressedUp; + } else { + infoState = Ttk_StateTableLookup(ThemeStateTable, state); + } + const HIThemeButtonDrawInfo info = { + .version = 0, + .state = infoState, + .kind = kThemeIncDecButton, + .value = Ttk_StateTableLookup(ButtonValueTable, state), + .adornment = kThemeAdornmentNone, + }; + BEGIN_DRAWING(d) + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkIncDecButton(bounds, infoState, state, dc.context); + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); + } + END_DRAWING +} + +static Ttk_ElementSpec SpinButtonUpElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + SpinButtonUpElementSize, + SpinButtonUpElementDraw +}; +static void SpinButtonDownElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { SInt32 s; ChkErr(GetThemeMetric, kThemeMetricLittleArrowsWidth, &s); - *widthPtr = s + Ttk_PaddingWidth(SpinbuttonMargins); + *minWidth = s + Ttk_PaddingWidth(SpinbuttonMargins); ChkErr(GetThemeMetric, kThemeMetricLittleArrowsHeight, &s); - *heightPtr = s + Ttk_PaddingHeight(SpinbuttonMargins); + *minHeight = (s + Ttk_PaddingHeight(SpinbuttonMargins)) / 2; } -static void SpinButtonElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) +static void SpinButtonDownElementDraw( + 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] - */ + int infoState = 0; + + bounds.origin.y -= bounds.size.height; + bounds.size.height *= 2; + if (state & TTK_STATE_PRESSED) { + infoState = kThemeStatePressedDown; + } else { + return; + } const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state & ~TTK_STATE_PRESSED), + .state = infoState, .kind = kThemeIncDecButton, .value = Ttk_StateTableLookup(ButtonValueTable, state), .adornment = kThemeAdornmentNone, }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkIncDecButton(bounds, infoState, state, dc.context); + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); + } END_DRAWING } -static Ttk_ElementSpec SpinButtonElementSpec = { +static Ttk_ElementSpec SpinButtonDownElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), TtkNullElementOptions, - SpinButtonElementSize, - SpinButtonElementDraw + SpinButtonDownElementSize, + SpinButtonDownElementDraw }; - /*---------------------------------------------------------------------- * +++ DrawThemeTrack-based elements -- - * Progress bars and scales. (See also: <<NOTE-TRACKS>>) + * + * Progress bars and scales. (See also: <<NOTE-TRACKS>>) + */ + +/* + * Apple does not change the appearance of a slider when the window becomes + * inactive. So we shouldn't either. */ static Ttk_StateTable ThemeTrackEnableTable[] = { - { kThemeTrackDisabled, TTK_STATE_DISABLED, 0 }, - { kThemeTrackInactive, TTK_STATE_BACKGROUND, 0 }, - { kThemeTrackActive, 0, 0 } + {kThemeTrackDisabled, TTK_STATE_DISABLED, 0}, + {kThemeTrackActive, TTK_STATE_BACKGROUND, 0}, + {kThemeTrackActive, 0, 0} /* { kThemeTrackNothingToScroll, ?, ? }, */ }; -typedef struct { /* TrackElement client data */ - ThemeTrackKind kind; - SInt32 thicknessMetric; +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 */ + 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, offsetof(TrackElement,fromObj) }, - { "-to", TK_OPTION_DOUBLE, offsetof(TrackElement,toObj) }, - { "-value", TK_OPTION_DOUBLE, offsetof(TrackElement,valueObj) }, - { "-orient", TK_OPTION_STRING, offsetof(TrackElement,orientObj) }, - {0,0,0} + {"-from", TK_OPTION_DOUBLE, offsetof(TrackElement, fromObj)}, + {"-to", TK_OPTION_DOUBLE, offsetof(TrackElement, toObj)}, + {"-value", TK_OPTION_DOUBLE, offsetof(TrackElement, valueObj)}, + {"-orient", TK_OPTION_STRING, offsetof(TrackElement, orientObj)}, + {0, 0, 0} }; - static void TrackElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { TrackElementData *data = clientData; - SInt32 size = 24; /* reasonable default ... */ + SInt32 size = 24; /* reasonable default ... */ ChkErr(GetThemeMetric, data->thicknessMetric, &size); - *widthPtr = *heightPtr = size; + *minWidth = *minHeight = size; } static void TrackElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) { TrackElementData *data = clientData; TrackElement *elem = elementRecord; @@ -644,20 +1995,35 @@ static void TrackElementDraw( .max = to * factor, .value = value * factor, .attributes = kThemeTrackShowThumb | - (orientation == TTK_ORIENT_HORIZONTAL ? - kThemeTrackHorizontal : 0), + (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; + kThemeThumbPressed : 0; + if (state & TTK_STATE_ALTERNATE) { + info.trackInfo.slider.thumbDir = kThemeThumbDownward; + } else { + info.trackInfo.slider.thumbDir = kThemeThumbPlain; + } } - - BEGIN_DRAWING(d) + if (TkMacOSXInDarkMode(tkwin)) { + CGRect bounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB + components: darkTrack + count: 4]; + if (orientation == TTK_ORIENT_HORIZONTAL) { + bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 2); + } else { + bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 2); + } + SolidFillRoundedRectangle(dc.context, bounds, 2, trackColor); + } ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); END_DRAWING } @@ -670,21 +2036,24 @@ static Ttk_ElementSpec TrackElementSpec = { TrackElementDraw }; -/* - * Slider element -- <<NOTE-TRACKS>> +/*---------------------------------------------------------------------- + * Slider elements -- <<NOTE-TRACKS>> + * * Has geometry only. The Scale widget adjusts the position of this element, * and uses it for hit detection. In the Aqua theme, the slider is actually * drawn as part of the trough element. * - * Also buggy: The geometry here is a Wild-Assed-Guess; I can't - * figure out how to get the Appearance Manager to tell me the - * slider size. */ + static void SliderElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { - *widthPtr = *heightPtr = 24; + *minWidth = *minHeight = 24; } static Ttk_ElementSpec SliderElementSpec = { @@ -696,7 +2065,7 @@ static Ttk_ElementSpec SliderElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Progress bar element (new): + * +++ Progress bar elements -- * * @@@ NOTE: According to an older revision of the Aqua reference docs, * @@@ the 'phase' field is between 0 and 4. Newer revisions say @@ -704,40 +2073,47 @@ static Ttk_ElementSpec SliderElementSpec = { */ 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 */ + 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, - offsetof(PbarElement,orientObj), "horizontal" }, - { "-value", TK_OPTION_DOUBLE, - offsetof(PbarElement,valueObj), "0" }, - { "-maximum", TK_OPTION_DOUBLE, - offsetof(PbarElement,maximumObj), "100" }, - { "-phase", TK_OPTION_INT, - offsetof(PbarElement,phaseObj), "0" }, - { "-mode", TK_OPTION_STRING, - offsetof(PbarElement,modeObj), "determinate" }, - {0,0,0,0} + {"-orient", TK_OPTION_STRING, + offsetof(PbarElement, orientObj), "horizontal"}, + {"-value", TK_OPTION_DOUBLE, + offsetof(PbarElement, valueObj), "0"}, + {"-maximum", TK_OPTION_DOUBLE, + offsetof(PbarElement, maximumObj), "100"}, + {"-phase", TK_OPTION_INT, + offsetof(PbarElement, phaseObj), "0"}, + {"-mode", TK_OPTION_STRING, + offsetof(PbarElement, modeObj), "determinate"}, + {0, 0, 0, 0} }; - static void PbarElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { - SInt32 size = 24; /* @@@ Check HIG for correct default */ + SInt32 size = 24; /* @@@ Check HIG for correct default */ ChkErr(GetThemeMetric, kThemeMetricLargeProgressBarThickness, &size); - *widthPtr = *heightPtr = size; + *minWidth = *minHeight = size; } static void PbarElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + 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; @@ -751,20 +2127,35 @@ static void PbarElementDraw( HIThemeTrackDrawInfo info = { .version = 0, - .kind = (!strcmp("indeterminate", Tcl_GetString(pbar->modeObj)) && value) ? - kThemeIndeterminateBar : kThemeProgressBar, + .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), + (orientation == TTK_ORIENT_HORIZONTAL ? + kThemeTrackHorizontal : 0), .enableState = Ttk_StateTableLookup(ThemeTrackEnableTable, state), .trackInfo.progress.phase = phase, }; BEGIN_DRAWING(d) + if (TkMacOSXInDarkMode(tkwin)) { + CGRect bounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *trackColor = [NSColor colorWithColorSpace: deviceRGB + components: darkTrack + count: 4]; + if (orientation == TTK_ORIENT_HORIZONTAL) { + bounds = CGRectInset(bounds, 1, bounds.size.height / 2 - 3); + } else { + bounds = CGRectInset(bounds, bounds.size.width / 2 - 3, 1); + } + SolidFillRoundedRectangle(dc.context, bounds, 3, trackColor); + } ChkErr(HIThemeDrawTrack, &info, NULL, dc.context, HIOrientation); END_DRAWING } @@ -778,33 +2169,301 @@ static Ttk_ElementSpec PbarElementSpec = { }; /*---------------------------------------------------------------------- + * +++ Scrollbar elements + */ + +typedef struct +{ + Tcl_Obj *orientObj; +} ScrollbarElement; + +static Ttk_ElementOptionSpec ScrollbarElementOptions[] = { + {"-orient", TK_OPTION_STRING, + offsetof(ScrollbarElement, orientObj), "horizontal"}, + {0, 0, 0, 0} +}; +static void TroughElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + SInt32 thickness = 15; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + ChkErr(GetThemeMetric, kThemeMetricScrollBarWidth, &thickness); + if (orientation == TTK_ORIENT_HORIZONTAL) { + *minHeight = thickness; + if ([NSApp macMinorVersion] > 7) { + *paddingPtr = Ttk_MakePadding(4, 4, 4, 3); + } + } else { + *minWidth = thickness; + if ([NSApp macMinorVersion] > 7) { + *paddingPtr = Ttk_MakePadding(4, 4, 3, 4); + } + } +} + +static CGFloat lightTrough[4] = {250.0 / 255, 250.0 / 255, 250.0 / 255, 1.0}; +static CGFloat darkTrough[4] = {45.0 / 255, 46.0 / 255, 49.0 / 255, 1.0}; +static CGFloat lightInactiveThumb[4] = { + 200.0 / 255, 200.0 / 255, 200.0 / 255, 1.0 +}; +static CGFloat lightActiveThumb[4] = { + 133.0 / 255, 133.0 / 255, 133.0 / 255, 1.0 +}; +static CGFloat darkInactiveThumb[4] = { + 116.0 / 255, 117.0 / 255, 118.0 / 255, 1.0 +}; +static CGFloat darkActiveThumb[4] = { + 158.0 / 255, 158.0 / 255, 159.0 / 255, 1.0 +}; +static void TroughElementDraw( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + CGRect bounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *troughColor; + CGFloat *rgba = TkMacOSXInDarkMode(tkwin) ? darkTrough : lightTrough; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + if (orientation == TTK_ORIENT_HORIZONTAL) { + bounds = CGRectInset(bounds, 0, 1); + } else { + bounds = CGRectInset(bounds, 1, 0); + } + troughColor = [NSColor colorWithColorSpace: deviceRGB + components: rgba + count: 4]; + BEGIN_DRAWING(d) + if ([NSApp macMinorVersion] > 8) { + CGContextSetFillColorWithColor(dc.context, CGCOLOR(troughColor)); + } else { + ChkErr(HIThemeSetFill, kThemeBrushDocumentWindowBackground, NULL, + dc.context, HIOrientation); + } + CGContextFillRect(dc.context, bounds); + END_DRAWING +} + +static Ttk_ElementSpec TroughElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + TroughElementSize, + TroughElementDraw +}; +static void ThumbElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + if (orientation == TTK_ORIENT_HORIZONTAL) { + *minHeight = 8; + } else { + *minWidth = 8; + } +} + +static void ThumbElementDraw( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) +{ + ScrollbarElement *scrollbar = elementRecord; + int orientation = TTK_ORIENT_HORIZONTAL; + + Ttk_GetOrientFromObj(NULL, scrollbar->orientObj, &orientation); + + /* + * In order to make ttk scrollbars work correctly it is necessary to be + * able to display the thumb element at the size and location which the ttk + * scrollbar widget requests. The algorithm that HIToolbox uses to + * determine the thumb geometry from the input values of min, max, value + * and viewSize is, of course, undocumented. And this turns out to be a + * hard reverse engineering problem. A seemingly natural algorithm is + * implemented below, but it does not correctly compute the same thumb + * geometry as HITools (which also apparently does not agree with + * NSScrollbar). This code uses that algorithm for older OS versions, + * because using HITools also handles drawing the buttons and 3D thumb used + * on those systems. The incorrect geometry is annoying but not completely + * unusable. For newer systems the cleanest approach is to just draw the + * thumb directly. + */ + + if ([NSApp macMinorVersion] > 8) { + CGRect thumbBounds = BoxToRect(d, b); + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *thumbColor; + CGFloat *rgba; + if ((orientation == TTK_ORIENT_HORIZONTAL && + thumbBounds.size.width >= Tk_Width(tkwin) - 8) || + (orientation == TTK_ORIENT_VERTICAL && + thumbBounds.size.height >= Tk_Height(tkwin) - 8)) { + return; + } + int isDark = TkMacOSXInDarkMode(tkwin); + if ((state & TTK_STATE_PRESSED) || + (state & TTK_STATE_HOVER)) { + rgba = isDark ? darkActiveThumb : lightActiveThumb; + } else { + rgba = isDark ? darkInactiveThumb : lightInactiveThumb; + } + thumbColor = [NSColor colorWithColorSpace: deviceRGB + components: rgba + count: 4]; + BEGIN_DRAWING(d) + SolidFillRoundedRectangle(dc.context, thumbBounds, 4, thumbColor); + END_DRAWING + } else { + double thumbSize, trackSize, visibleSize, viewSize; + MacDrawable *macWin = (MacDrawable *) Tk_WindowId(tkwin); + CGRect troughBounds = {{macWin->xOff, macWin->yOff}, + {Tk_Width(tkwin), Tk_Height(tkwin)}}; + + /* + * The info struct has integer fields, which will be converted to + * floats in the drawing routine. All of values provided in the info + * struct, namely min, max, value, and viewSize are only defined up to + * an arbitrary scale factor. To avoid roundoff error we scale so + * that the viewSize is a large float which is smaller than the + * largest int. + */ + + viewSize = RangeToFactor(100.0); + HIThemeTrackDrawInfo info = { + .version = 0, + .bounds = troughBounds, + .min = 0, + .attributes = kThemeTrackShowThumb | + kThemeTrackThumbRgnIsNotGhost, + .enableState = kThemeTrackActive + }; + info.trackInfo.scrollbar.viewsize = viewSize * .8; + if (orientation == TTK_ORIENT_HORIZONTAL) { + trackSize = troughBounds.size.width; + thumbSize = b.width; + visibleSize = (thumbSize / trackSize) * viewSize; + info.max = viewSize - visibleSize; + info.value = info.max * (b.x / (trackSize - thumbSize)); + } else { + thumbSize = b.height; + trackSize = troughBounds.size.height; + visibleSize = (thumbSize / trackSize) * viewSize; + info.max = viewSize - visibleSize; + info.value = info.max * (b.y / (trackSize - thumbSize)); + } + if ((state & TTK_STATE_PRESSED) || + (state & TTK_STATE_HOVER)) { + info.trackInfo.scrollbar.pressState = kThemeThumbPressed; + } else { + info.trackInfo.scrollbar.pressState = 0; + } + if (orientation == TTK_ORIENT_HORIZONTAL) { + info.attributes |= kThemeTrackHorizontal; + } else { + info.attributes &= ~kThemeTrackHorizontal; + } + BEGIN_DRAWING(d) + HIThemeDrawTrack(&info, 0, dc.context, kHIThemeOrientationNormal); + END_DRAWING + } +} + +static Ttk_ElementSpec ThumbElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ThumbElementSize, + ThumbElementDraw +}; +static void ArrowElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + if ([NSApp macMinorVersion] < 8) { + *minHeight = *minWidth = 14; + } else { + *minHeight = *minWidth = -1; + } +} + +static Ttk_ElementSpec ArrowElementSpec = { + TK_STYLE_VERSION_2, + sizeof(ScrollbarElement), + ScrollbarElementOptions, + ArrowElementSize, + TtkNullElementDraw +}; + +/*---------------------------------------------------------------------- * +++ 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. + * 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) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { - *widthPtr = *heightPtr = 1; + *minWidth = *minHeight = 1; } static void SeparatorElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, unsigned int state) + 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), + /* Separator only supports kThemeStateActive, kThemeStateInactive */ + .state = Ttk_StateTableLookup(ThemeStateTable, + state & TTK_STATE_BACKGROUND), }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context, HIOrientation); + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkSeparator(bounds, dc.context, tkwin); + } else { + ChkErr(HIThemeDrawSeparator, &bounds, &info, dc.context, + HIOrientation); + } END_DRAWING } @@ -817,14 +2476,19 @@ static Ttk_ElementSpec SeparatorElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Size grip element. + * +++ Size grip elements -- (obsolete) */ + static const ThemeGrowDirection sizegripGrowDirection - = kThemeGrowRight|kThemeGrowDown; + = kThemeGrowRight | kThemeGrowDown; static void SizegripElementSize( - void *clientData, void *elementRecord, Tk_Window tkwin, - int *widthPtr, int *heightPtr, Ttk_Padding *paddingPtr) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { HIThemeGrowBoxDrawInfo info = { .version = 0, @@ -836,26 +2500,32 @@ static void SizegripElementSize( CGRect bounds = CGRectZero; ChkErr(HIThemeGetGrowBoxBounds, &bounds.origin, &info, &bounds); - *widthPtr = bounds.size.width; - *heightPtr = bounds.size.height; + *minWidth = bounds.size.width; + *minHeight = bounds.size.height; } static void SizegripElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, unsigned int state) + 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), + /* 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); + ChkErr(HIThemeDrawGrowBox, &bounds.origin, &info, dc.context, + HIOrientation); END_DRAWING } @@ -868,40 +2538,93 @@ static Ttk_ElementSpec SizegripElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Background and fill elements. + * +++ Background and fill elements -- + * + * Before drawing any ttk widget, its bounding rectangle is filled with a + * background color. This color must match the background color of the + * containing widget to avoid looking ugly. The need for care when doing + * this is exacerbated by the fact that ttk enforces its "native look" by + * not allowing user control of the background or highlight colors of ttk + * widgets. * - * 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. + * This job is made more complicated in recent versions of macOS by the + * fact that the Appkit GroupBox (used for ttk LabelFrames) and + * TabbedPane (used for the Notebook widget) both place their content + * inside a rectangle with rounded corners that has a color which + * contrasts with the dialog background color. Moreover, although the + * Apple human interface guidelines recommend against doing so, there are + * times when one wants to nest these widgets, for example placing a + * GroupBox inside of a TabbedPane. To have the right contrast, each + * level of nesting requires a different color. * - * 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. + * Previous Tk releases used the HIThemeDrawGroupBox routine to draw + * GroupBoxes and TabbedPanes. This meant that the best that could be + * done was to set the GroupBox to be of kind + * kHIThemeGroupBoxKindPrimaryOpaque, and set its fill color to be the + * system background color. If widgets inside the box were drawn with + * the system background color the backgrounds would match. But this + * produces a GroupBox with no contrast, the only visual clue being a + * faint highlighting around the top of the GroupBox. Moreover, the + * TabbedPane does not have an Opaque version, so while it is drawn + * inside a contrasting rounded rectangle, the widgets inside the pane + * needed to be enclosed in a frame with the system background + * color. This added a visual artifact since the frame's background color + * does not match the Pane's background color. That code has now been + * replaced with the standalone drawing procedure macOSXDrawGroupBox, + * which draws a rounded rectangle with an appropriate contrasting + * background color. + * + * Patterned backgrounds, which are now obsolete, should be aligned with + * the coordinate system of the top-level window. Apparently failing to + * do this used to cause graphics anomalies when drawing into an + * off-screen graphics port. The code for handling this is currently + * commented out. */ static void FillElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + 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) + + if ([NSApp macMinorVersion] > 8) { + NSColorSpace *deviceRGB = [NSColorSpace deviceRGBColorSpace]; + NSColor *bgColor; + CGFloat fill[4]; + BEGIN_DRAWING(d) + GetBackgroundColor(dc.context, tkwin, 0, fill); + bgColor = [NSColor colorWithColorSpace: deviceRGB components: fill + count: 4]; + CGContextSetFillColorSpace(dc.context, deviceRGB.CGColorSpace); + CGContextSetFillColorWithColor(dc.context, CGCOLOR(bgColor)); + CGContextFillRect(dc.context, bounds); + END_DRAWING + } else { + 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 + 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) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + unsigned int state) { - FillElementDraw( - clientData, elementRecord, tkwin, - d, Ttk_WinBox(tkwin), state); + FillElementDraw(clientData, elementRecord, tkwin, d, Ttk_WinBox(tkwin), + state); } static Ttk_ElementSpec FillElementSpec = { @@ -911,7 +2634,6 @@ static Ttk_ElementSpec FillElementSpec = { TtkNullElementSize, FillElementDraw }; - static Ttk_ElementSpec BackgroundElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), @@ -923,19 +2645,24 @@ static Ttk_ElementSpec BackgroundElementSpec = { /*---------------------------------------------------------------------- * +++ 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. + * This is very similar to the normal background element, but uses a + * different ThemeBrush in order to get the lighter pinstripe effect + * used in toolbars. We use SetThemeBackground() rather than + * ApplyThemeBackground() in order to get the right style. * - * <URL: http://developer.apple.com/documentation/Carbon/Reference/ - * Appearance_Manager/appearance_manager/constant_7.html#/ - * /apple_ref/doc/uid/TP30000243/C005321> + * <URL: http://developer.apple.com/documentation/Carbon/Reference/ + * Appearance_Manager/appearance_manager/constant_7.html#/ + * /apple_ref/doc/uid/TP30000243/C005321> * */ + static void ToolbarBackgroundElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + 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)); @@ -956,30 +2683,121 @@ static Ttk_ElementSpec ToolbarBackgroundElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Treeview header - * Redefine the header to use a kThemeListHeaderButton. + * +++ Field elements -- + * + * Used for the Treeview widget. This is like the BackgroundElement + * except that the fieldbackground color is configureable. + */ + +typedef struct { + Tcl_Obj *backgroundObj; +} FieldElement; + +static Ttk_ElementOptionSpec FieldElementOptions[] = { + {"-fieldbackground", TK_OPTION_BORDER, + offsetof(FieldElement, backgroundObj), "white"}, + {NULL, 0, 0, NULL} +}; + +static void FieldElementDraw( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) +{ + FieldElement *e = elementRecord; + Tk_3DBorder backgroundPtr = + Tk_Get3DBorderFromObj(tkwin, e->backgroundObj); + + XFillRectangle(Tk_Display(tkwin), d, + Tk_3DBorderGC(tkwin, backgroundPtr, TK_3D_FLAT_GC), + b.x, b.y, b.width, b.height); +} + +static Ttk_ElementSpec FieldElementSpec = { + TK_STYLE_VERSION_2, + sizeof(FieldElement), + FieldElementOptions, + TtkNullElementSize, + FieldElementDraw +}; + +/*---------------------------------------------------------------------- + * +++ Treeview headers -- + * + * On systems older than 10.9 The header is a kThemeListHeaderButton drawn + * by HIToolbox. On newer systems those buttons do not match the Apple + * buttons, so we draw them from scratch. */ -#define TTK_TREEVIEW_STATE_SORTARROW TTK_STATE_USER1 static Ttk_StateTable TreeHeaderValueTable[] = { - { kThemeButtonOn, TTK_STATE_ALTERNATE}, - { kThemeButtonOn, TTK_STATE_SELECTED}, - { kThemeButtonOff, 0} + {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} + {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 TreeAreaElementSize ( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + + /* + * Padding is needed to get the heading text to align correctly, since the + * widget expects the heading to be the same height as a row. + */ + + if ([NSApp macMinorVersion] > 8) { + paddingPtr->top = 4; + } +} + +static Ttk_ElementSpec TreeAreaElementSpec = { + TK_STYLE_VERSION_2, + sizeof(NullElement), + TtkNullElementOptions, + TreeAreaElementSize, + TtkNullElementDraw }; +static void TreeHeaderElementSize( + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) +{ + if ([NSApp macMinorVersion] > 8) { + *minHeight = 24; + } else { + ButtonElementSize(clientData, elementRecord, tkwin, minWidth, + minHeight, paddingPtr); + } +} static void TreeHeaderElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) { ThemeButtonParams *params = clientData; CGRect bounds = BoxToRect(d, b); @@ -992,7 +2810,23 @@ static void TreeHeaderElementDraw( }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + if ([NSApp macMinorVersion] > 8) { + + /* + * Compensate for the padding added in TreeHeaderElementSize, so + * the larger heading will be drawn at the top of the widget. + */ + + bounds.origin.y -= 4; + if (TkMacOSXInDarkMode(tkwin)) { + DrawDarkListHeader(bounds, dc.context, tkwin, state); + } else { + DrawListHeader(bounds, dc.context, tkwin, state); + } + } else { + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); + } END_DRAWING } @@ -1000,48 +2834,59 @@ static Ttk_ElementSpec TreeHeaderElementSpec = { TK_STYLE_VERSION_2, sizeof(NullElement), TtkNullElementOptions, - ButtonElementSizeNoPadding, + TreeHeaderElementSize, TreeHeaderElementDraw }; -/* - * Disclosure triangle: +/*---------------------------------------------------------------------- + * +++ Disclosure triangles -- */ -#define TTK_TREEVIEW_STATE_OPEN TTK_STATE_USER1 -#define TTK_TREEVIEW_STATE_LEAF TTK_STATE_USER2 + +#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 }, + {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) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + int *minWidth, + int *minHeight, + Ttk_Padding *paddingPtr) { SInt32 s; ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleWidth, &s); - *widthPtr = s; + *minWidth = s; ChkErr(GetThemeMetric, kThemeMetricDisclosureTriangleHeight, &s); - *heightPtr = s; + *minHeight = s; } static void DisclosureElementDraw( - void *clientData, void *elementRecord, Tk_Window tkwin, - Drawable d, Ttk_Box b, Ttk_State state) + void *clientData, + void *elementRecord, + Tk_Window tkwin, + Drawable d, + Ttk_Box b, + Ttk_State state) { if (!(state & TTK_TREEVIEW_STATE_LEAF)) { + int triangleState = TkMacOSXInDarkMode(tkwin) ? + kThemeStateInactive : kThemeStateActive; CGRect bounds = BoxToRect(d, b); const HIThemeButtonDrawInfo info = { .version = 0, - .state = Ttk_StateTableLookup(ThemeStateTable, state), + .state = triangleState, .kind = kThemeDisclosureTriangle, .value = Ttk_StateTableLookup(DisclosureValueTable, state), .adornment = kThemeAdornmentDrawIndicatorOnly, }; BEGIN_DRAWING(d) - ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, NULL); + ChkErr(HIThemeDrawButton, &bounds, &info, dc.context, HIOrientation, + NULL); END_DRAWING } } @@ -1055,7 +2900,7 @@ static Ttk_ElementSpec DisclosureElementSpec = { }; /*---------------------------------------------------------------------- - * +++ Widget layouts. + * +++ Widget layouts -- */ TTK_BEGIN_LAYOUT_TABLE(LayoutTable) @@ -1065,64 +2910,90 @@ TTK_LAYOUT("Toolbar", 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_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_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_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_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)))) + TTK_GROUP("Combobox.button", TTK_FILL_BOTH, + TTK_GROUP("Combobox.padding", TTK_FILL_BOTH, + TTK_NODE("Combobox.textarea", TTK_FILL_BOTH)))) /* 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)))) + TTK_GROUP("Notebook.padding", TTK_EXPAND | TTK_FILL_BOTH, + TTK_NODE("Notebook.label", TTK_EXPAND | TTK_FILL_BOTH)))) -/* Progress bars -- track only */ +/* Spinbox -- buttons 2px to the right of the field. */ 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_GROUP("Spinbox.buttons", TTK_PACK_RIGHT, + TTK_NODE("Spinbox.uparrow", TTK_PACK_TOP | TTK_STICK_E) + TTK_NODE("Spinbox.downarrow", TTK_PACK_BOTTOM | TTK_STICK_E)) + TTK_GROUP("Spinbox.field", TTK_EXPAND | TTK_FILL_X, + TTK_NODE("Spinbox.textarea", TTK_EXPAND | TTK_FILL_X))) +/* Progress bars -- track only */ TTK_LAYOUT("TProgressbar", - TTK_NODE("Progressbar.track", TTK_EXPAND|TTK_FILL_BOTH)) + TTK_NODE("Progressbar.track", TTK_EXPAND | TTK_FILL_BOTH)) + +/* Treeview -- no border. */ +TTK_LAYOUT("Treeview", + TTK_GROUP("Treeview.field", TTK_FILL_BOTH, + TTK_GROUP("Treeview.padding", TTK_FILL_BOTH, + TTK_NODE("Treeview.treearea", TTK_FILL_BOTH)))) /* Tree heading -- no border, fixed height */ TTK_LAYOUT("Heading", - TTK_NODE("Treeheading.cell", TTK_FILL_X) + TTK_NODE("Treeheading.cell", TTK_FILL_BOTH) TTK_NODE("Treeheading.image", TTK_PACK_RIGHT) - TTK_NODE("Treeheading.text", 0)) + TTK_NODE("Treeheading.text", TTK_PACK_TOP)) /* 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_NODE("Treeitem.indicator", TTK_PACK_LEFT) + TTK_NODE("Treeitem.image", TTK_PACK_LEFT) + TTK_NODE("Treeitem.text", TTK_PACK_LEFT))) + +/* Scrollbar Layout -- Buttons at the bottom (Snow Leopard and Lion only) */ + +TTK_LAYOUT("Vertical.TScrollbar", + TTK_GROUP("Vertical.Scrollbar.trough", TTK_FILL_Y, + TTK_NODE("Vertical.Scrollbar.thumb", + TTK_PACK_TOP | TTK_EXPAND | TTK_FILL_BOTH) + TTK_NODE("Vertical.Scrollbar.downarrow", TTK_PACK_BOTTOM) + TTK_NODE("Vertical.Scrollbar.uparrow", TTK_PACK_BOTTOM))) + +TTK_LAYOUT("Horizontal.TScrollbar", + TTK_GROUP("Horizontal.Scrollbar.trough", TTK_FILL_X, + TTK_NODE("Horizontal.Scrollbar.thumb", + TTK_PACK_LEFT | TTK_EXPAND | TTK_FILL_BOTH) + TTK_NODE("Horizontal.Scrollbar.rightarrow", TTK_PACK_RIGHT) + TTK_NODE("Horizontal.Scrollbar.leftarrow", TTK_PACK_RIGHT))) TTK_END_LAYOUT_TABLE /*---------------------------------------------------------------------- - * +++ Initialization. + * +++ Initialization -- */ -static int AquaTheme_Init(Tcl_Interp *interp) +static int AquaTheme_Init( + Tcl_Interp *interp) { Ttk_Theme themePtr = Ttk_CreateTheme(interp, "aqua", NULL); @@ -1133,8 +3004,11 @@ static int AquaTheme_Init(Tcl_Interp *interp) /* * Elements: */ - Ttk_RegisterElementSpec(themePtr, "background", &BackgroundElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "background", &BackgroundElementSpec, + 0); Ttk_RegisterElementSpec(themePtr, "fill", &FillElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "field", &FieldElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Toolbar.background", &ToolbarBackgroundElementSpec, 0); @@ -1148,8 +3022,10 @@ static int AquaTheme_Init(Tcl_Interp *interp) &ButtonElementSpec, &BevelButtonParams); Ttk_RegisterElementSpec(themePtr, "Menubutton.button", &ButtonElementSpec, &PopupButtonParams); - Ttk_RegisterElementSpec(themePtr, "Spinbox.spinbutton", - &SpinButtonElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.uparrow", + &SpinButtonUpElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Spinbox.downarrow", + &SpinButtonDownElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Combobox.button", &ComboboxElementSpec, 0); Ttk_RegisterElementSpec(themePtr, "Treeitem.indicator", @@ -1157,33 +3033,64 @@ static int AquaTheme_Init(Tcl_Interp *interp) Ttk_RegisterElementSpec(themePtr, "Treeheading.cell", &TreeHeaderElementSpec, &ListHeaderParams); + Ttk_RegisterElementSpec(themePtr, "Treeview.treearea", + &TreeAreaElementSpec, 0); 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, "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, "separator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "hseparator", &SeparatorElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "vseparator", &SeparatorElementSpec, 0); - Ttk_RegisterElementSpec(themePtr, "sizegrip",&SizegripElementSpec,0); + Ttk_RegisterElementSpec(themePtr, "sizegrip", &SizegripElementSpec, 0); /* * <<NOTE-TRACKS>> - * The Progressbar widget adjusts the size of the pbar element. - * In the Aqua theme, the appearance manager computes the bar geometry; - * we do all the drawing in the ".track" element and leave the .pbar out. + * In some themes the Layouts for a progress bar has a trough element and + *a + * pbar element. But in our case the appearance manager draws both parts + * of the progress bar, so we just have a single element called ".track". + */ + + Ttk_RegisterElementSpec(themePtr, "Progressbar.track", &PbarElementSpec, + 0); + + Ttk_RegisterElementSpec(themePtr, "Scale.trough", &TrackElementSpec, + &ScaleData); + Ttk_RegisterElementSpec(themePtr, "Scale.slider", &SliderElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.trough", + &TroughElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.thumb", + &ThumbElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.trough", + &TroughElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.thumb", + &ThumbElementSpec, 0); + + /* + * If we are not in Snow Leopard or Lion the arrows won't actually be + * displayed. */ - Ttk_RegisterElementSpec(themePtr,"Scale.trough", - &TrackElementSpec, &ScaleData); - Ttk_RegisterElementSpec(themePtr,"Scale.slider",&SliderElementSpec,0); - Ttk_RegisterElementSpec(themePtr,"Progressbar.track", &PbarElementSpec, 0); + + Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.uparrow", + &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Vertical.Scrollbar.downarrow", + &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.leftarrow", + &ArrowElementSpec, 0); + Ttk_RegisterElementSpec(themePtr, "Horizontal.Scrollbar.rightarrow", + &ArrowElementSpec, 0); /* * Layouts: */ + Ttk_RegisterLayouts(themePtr, LayoutTable); Tcl_PkgProvide(interp, "ttk::theme::aqua", TTK_VERSION); @@ -1191,11 +3098,12 @@ static int AquaTheme_Init(Tcl_Interp *interp) } MODULE_SCOPE -int Ttk_MacOSXPlatformInit(Tcl_Interp *interp) +int Ttk_MacOSXPlatformInit( + Tcl_Interp *interp) { return AquaTheme_Init(interp); } - + /* * Local Variables: * mode: objc @@ -1204,4 +3112,3 @@ int Ttk_MacOSXPlatformInit(Tcl_Interp *interp) * coding: utf-8 * End: */ - diff --git a/tests/canvas.test b/tests/canvas.test index e8dc332..e740fc8 100644 --- a/tests/canvas.test +++ b/tests/canvas.test @@ -1040,6 +1040,207 @@ test canvas-20.3 {canvas image with subsample and zoom} -setup { image delete testimage } -result 1 +test canvas-21.1 {canvas very small arc} -setup { + catch {destroy .c} + canvas .c +} -body { + # no Inf or NaN must be generated even for very small arcs + .c create arc 0 100 0 100 -height 100 -style arc -outline "" -tags arc1 + set arcBox [.c bbox arc1] + .c create arc 0 100 0 100 -height 100 -style arc -outline blue -tags arc2 + set outlinedArcBox [.c bbox arc2] + set coords [.c coords arc1] + set start [.c itemcget arc1 -start] + set extent [.c itemcget arc1 -extent] + set width [.c itemcget arc1 -width] + set height [.c itemcget arc1 -height] + list $arcBox $outlinedArcBox $coords $start $extent $width $height +} -result {{-1 99 1 101} {-2 98 2 102} {0.0 100.0 0.0 100.0} 0.0 0.0 1.0 0.0} + + +destroy .c +test canvas-21.1 {canvas rotate} -setup { + pack [canvas .c] +} -body { + .c create line 50 50 50 100 100 100 + .c rotate all 75 75 90 + lmap c [.c coords all] {format %.2f $c} +} -cleanup { + destroy .c +} -result {50.00 100.00 100.00 100.00 100.00 50.00} +test canvas-21.2 {canvas rotate} -setup { + pack [canvas .c] +} -body { + .c create line 50 50 50 100 100 100 + .c rotate all 75 75 -10 + lmap c [.c coords all] {format %.2f $c} +} -cleanup { + destroy .c +} -result {54.72 46.04 46.04 95.28 95.28 103.96} +test canvas-21.3 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate all 75 75 +} -returnCodes error -cleanup { + destroy .c +} -result {wrong # args: should be ".c rotate tagOrId x y angle"} +test canvas-21.4 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate all 75 75 123 123 +} -returnCodes error -cleanup { + destroy .c +} -result {wrong # args: should be ".c rotate tagOrId x y angle"} +test canvas-21.5 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate {!} 1 1 1 +} -returnCodes error -cleanup { + destroy .c +} -result {missing tag in tag search expression} +test canvas-21.6 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate all x 1 1 +} -returnCodes error -cleanup { + destroy .c +} -result {bad screen distance "x"} +test canvas-21.7 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate all 1 x 1 +} -returnCodes error -cleanup { + destroy .c +} -result {bad screen distance "x"} +test canvas-21.8 {canvas rotate: syntax} -setup { + pack [canvas .c] +} -body { + .c rotate all 1 1 x +} -returnCodes error -cleanup { + destroy .c +} -result {expected floating-point number but got "x"} +test canvas-21.9 {canvas rotate: nothing to rotate} -setup { + pack [canvas .c] +} -body { + .c rotate all 75 75 10 +} -cleanup { + destroy .c +} -result {} +test canvas-21.10 {canvas rotate: multiple things to rotate} -setup { + pack [canvas .c] +} -body { + .c create line 50 50 50 100 -tag a + .c create line 50 50 100 50 -tag b + .c rotate all 75 75 45 + list [lmap c [.c coords a] {format %.2f $c}] [lmap c [.c coords b] {format %.2f $c}] +} -cleanup { + destroy .c +} -result {{39.64 75.00 75.00 110.36} {39.64 75.00 75.00 39.64}} + +test canvas-22.1 {canvas rotate: arc item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create arc 50 50 75 75 -start 45 -extent 90 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {-start -extent} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 125.00 75.00 150.00} {45.0 90.0} {52 123 73 140}} +test canvas-22.2 {canvas rotate: bitmap item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create bitmap 50 50 -bitmap info -anchor se + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {-bitmap -anchor} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 150.00} {info se} {42 129 50 150}} +test canvas-22.3 {canvas rotate: image item rotation behaviour} -setup { + pack [canvas .c] + image create photo dummy -width 50 -height 50 +} -body { + .c create image 50 50 -image dummy -anchor se + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {-image -anchor} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c + image delete dummy +} -result {{50.00 150.00} {dummy se} {0 100 50 150}} +test canvas-22.4 {canvas rotate: line item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create line 50 50 75 50 50 75 75 75 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 150.00 50.00 125.00 75.00 150.00 75.00 125.00} {} {48 123 77 152}} +test canvas-22.5 {canvas rotate: oval item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create oval 50 50 65 85 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{60.00 125.00 75.00 160.00} {} {59 124 76 161}} +test canvas-22.6 {canvas rotate: polygon item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create polygon 50 50 75 50 50 75 75 75 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 150.00 50.00 125.00 75.00 150.00 75.00 125.00} {} {49 124 76 151}} +test canvas-22.7 {canvas rotate: rectangle item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create rectangle 50 50 75 75 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 125.00 75.00 150.00} {} {49 124 76 151}} +test canvas-22.8 {canvas rotate: text item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create text 50 50 -text foo -angle 45 + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {-text -angle} {.c itemcget all $o}] + # [.c bbox all] + # No testing of text bounding box; fonts too variable! +} -cleanup { + destroy .c +} -result {{50.00 150.00} {foo 45.0}} +test canvas-22.9 {canvas rotate: window item rotation behaviour} -setup { + pack [canvas .c] +} -body { + .c create window 50 50 -window [frame .c.f -width 25 -height 25] \ + -anchor se + .c rotate all 100 100 90 + list [lmap c [.c coords all] {format %.2f $c}] \ + [lmap o {} {.c itemcget all $o}] \ + [.c bbox all] +} -cleanup { + destroy .c +} -result {{50.00 150.00} {} {25 125 50 150}} + # cleanup imageCleanup cleanupTests diff --git a/tests/frame.test b/tests/frame.test index e1eb5e4..fe38128 100644 --- a/tests/frame.test +++ b/tests/frame.test @@ -668,6 +668,12 @@ test frame-3.9 {TkCreateFrame procedure, -use option} -constraints { [expr {[winfo rooty .x] - [winfo rooty .t]}] \ [winfo width .t] [winfo height .t] } -cleanup { +# This call to update idletasks was added to prevent a crash that was +# observed on OSX 10.12 (Sierra) only. Any change, such as using the +# Development version to make debugging symbols available, adding a print +# statement, or calling update idletasks here, would make the test pass +# with no segfault. + update idletasks deleteWindows } -result {0 0 140 300} test frame-3.10 {TkCreateFrame procedure, -use option} -constraints { @@ -678,8 +684,10 @@ test frame-3.10 {TkCreateFrame procedure, -use option} -constraints { toplevel .t -container 1 -width 300 -height 120 wm geometry .t +0+0 option add *x.use [winfo id .t] + update toplevel .x -width 140 -height 300 -bg green tkwait visibility .x + update list [expr {[winfo rootx .x] - [winfo rootx .t]}] \ [expr {[winfo rooty .x] - [winfo rooty .t]}] \ [winfo width .t] [winfo height .t] diff --git a/tests/grid.test b/tests/grid.test index 63bfe2a..53f8be5 100644 --- a/tests/grid.test +++ b/tests/grid.test @@ -80,6 +80,7 @@ test grid-1.9 {basic argument checking} -body { grid_reset 1.9 } -returnCodes ok -result {} + test grid-2.1 {bbox} -body { grid bbox . } -result {0 0 0 0} @@ -192,6 +193,30 @@ test grid-3.9 {configure: basic argument checking} -body { } -cleanup { grid_reset 3.9 } -returnCodes error -result {invalid window shortcut, "y" should be '-', 'x', or '^'} +test grid-3.10 {ConfigureSlave procedure, bad -in option} -body { + frame .f + grid .f -in .f +} -cleanup { + grid_reset 3.10 +} -returnCodes error -result {window can't be managed in itself} +test grid-3.11 {prevent management loops} -body { + frame .f1 + frame .f2 + grid .f1 -in .f2 + grid .f2 -in .f1 +} -cleanup { + grid_reset 3.11 +} -returnCodes error -result {can't put .f2 inside .f1, would cause management loop} +test grid-3.12 {prevent management loops} -body { + frame .f1 + frame .f2 + frame .f3 + grid .f1 -in .f2 + grid .f2 -in .f3 + grid .f3 -in .f1 +} -cleanup { + grid_reset 3.12 +} -returnCodes error -result {can't put .f3 inside .f1, would cause management loop} test grid-4.1 {forget: basic argument checking} -body { grid forget foo diff --git a/tests/imgSVGnano.test b/tests/imgSVGnano.test new file mode 100644 index 0000000..74c3318 --- /dev/null +++ b/tests/imgSVGnano.test @@ -0,0 +1,93 @@ +# This file is a Tcl script to test out the code in tkImgSVGnano.c, which reads +# and write SVG-format image files for photo widgets. The files is organized +# in the standard fashion for Tcl tests. +# +# Copyright (c) 2018 Rene Zaumseil +# All rights reserved. + +package require tcltest 2.2 +namespace import ::tcltest::* +eval tcltest::configure $argv +tcltest::loadTestedCommands +imageInit + +namespace eval svgnano { + variable data + set data(plus) {<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"> +<path fill="none" stroke="#000000" d="M0 0 h16 v16 h-16 z"/> +<path fill="none" stroke="#000000" d="M8 4 v 8 M4 8 h 8"/> +<circle fill="yellow" stroke="red" cx="10" cy="80" r="10" /> +<ellipse fill="none" stroke="blue" stroke-width="3" cx="60" cy="60" rx="10" ry="20" /> +<line x1="10" y1="90" x2="50" y2="99"/> +<rect fill="none" stroke="green" x="20" y="20" width="60" height="50" rx="3" ry="3"/> +<polyline fill="red" stroke="purple" points="80,10 90,20 85,40"/> +<polygon fill ="yellow" points="80,80 70,85 90,90"/> +</svg>} + set data(bad) {<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0:w +"> +</svg>} + +test imgSVGnano-1.1 {reading simple image} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $data(plus) + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {100 100} + +test imgSVGnano-1.2 {simple image with options} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $data(plus) -format {svg -dpi 100 -scale 3} + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {300 300} + +# test on crash found by Koen Danckaert +test imgSVGnano-1.3 {reformat image options} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $data(plus) + catch {foo configure -format {svg -scale}} + list {} +} -cleanup { + rename foo "" +} -result {{}} + +test imgSVGnano-1.4 {image options} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $data(plus) + foo configure -format {svg -scale 2} + foo configure -format {svg -unit pt} + foo configure -format {svg -unit mm} + foo configure -format {svg -unit cm} + foo configure -format {svg -unit in} + foo configure -format {svg -unit px} + foo configure -format {svg -dpi 600} + list [image width foo] [image height foo] +} -cleanup { + rename foo "" +} -result {100 100} + + +test imgSVGnano-2.1 {reading a bad image} -body { + image create photo foo -format svg -data $data(bad) +} -returnCodes error -result {couldn't recognize image data} +test imgSVGnano-2.2 {using bad option} -body { + image create photo -data $data(plus) -format {svg -scale 0} +} -returnCodes error -result {couldn't recognize image data} + +};# end of namespace svgnano + +namespace delete svgnano +imageFinish +cleanupTests +return + +# Local Variables: +# mode: tcl +# fill-column: 78 +# End: diff --git a/tests/pack.test b/tests/pack.test index 9d5964c..4a41516 100644 --- a/tests/pack.test +++ b/tests/pack.test @@ -965,6 +965,27 @@ test pack-10.4 {bad -in window does not change master} -setup { winfo manager .pack.a pack .pack.a -in .pack.a } -returnCodes error -result {can't pack .pack.a inside itself} +test pack-10.5 {prevent management loops} -body { + frame .f1 + frame .f2 + pack .f1 -in .f2 + pack .f2 -in .f1 +} -cleanup { + destroy .f1 + destroy .f2 +} -returnCodes error -result {can't put .f2 inside .f1, would cause management loop} +test pack-10.6 {prevent management loops} -body { + frame .f1 + frame .f2 + frame .f3 + pack .f1 -in .f2 + pack .f2 -in .f3 + pack .f3 -in .f1 +} -cleanup { + destroy .f1 + destroy .f2 + destroy .f3 +} -returnCodes error -result {can't put .f3 inside .f1, would cause management loop} test pack-11.1 {info option} -setup { pack forget .pack.a .pack.b .pack.c .pack.d diff --git a/tests/pkgconfig.test b/tests/pkgconfig.test new file mode 100644 index 0000000..e080b91 --- /dev/null +++ b/tests/pkgconfig.test @@ -0,0 +1,66 @@ +# -*- tcl -*- +# Commands covered: pkgconfig +# +# This file contains a collection of tests for one or more of the Tk +# built-in commands. Sourcing this file into Tk runs the tests and +# generates output for errors. No output means no errors were found. +# +# Copyright (c) 1991-1993 The Regents of the University of California. +# Copyright (c) 1994-1996 Sun Microsystems, Inc. +# Copyright (c) 1998-1999 by Scriptics Corporation. +# Copyright (c) 2017 Stuart Cassoff <stwo@users.sourceforge.net> +# +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. + +package require tcltest 2.2 +namespace import ::tcltest::* +eval tcltest::configure $argv +tcltest::loadTestedCommands + +test pkgconfig-1.1 {query keys} nonwin { + lsort [::tk::pkgconfig list] +} [list \ + 64bit bindir,install bindir,runtime debug demodir,install demodir,runtime \ + docdir,install docdir,runtime fontsystem includedir,install includedir,runtime \ + libdir,install libdir,runtime mem_debug optimized profiled \ + scriptdir,install scriptdir,runtime threaded \ +] +test pkgconfig-1.2 {query keys multiple times} { + string compare [::tk::pkgconfig list] [::tk::pkgconfig list] +} 0 +test pkgconfig-1.3 {query value multiple times} { + string compare \ + [::tk::pkgconfig get 64bit] \ + [::tk::pkgconfig get 64bit] +} 0 + + +test pkgconfig-2.0 {error: missing subcommand} { + catch {::tk::pkgconfig} msg + set msg +} {wrong # args: should be "::tk::pkgconfig subcommand ?arg?"} +test pkgconfig-2.1 {error: illegal subcommand} { + catch {::tk::pkgconfig foo} msg + set msg +} {bad subcommand "foo": must be get or list} +test pkgconfig-2.2 {error: list with arguments} { + catch {::tk::pkgconfig list foo} msg + set msg +} {wrong # args: should be "::tk::pkgconfig list"} +test pkgconfig-2.3 {error: get without arguments} { + catch {::tk::pkgconfig get} msg + set msg +} {wrong # args: should be "::tk::pkgconfig get key"} +test pkgconfig-2.4 {error: query unknown key} { + catch {::tk::pkgconfig get foo} msg + set msg +} {key not known} +test pkgconfig-2.5 {error: query with to many arguments} { + catch {::tk::pkgconfig get foo bar} msg + set msg +} {wrong # args: should be "::tk::pkgconfig subcommand ?arg?"} + +# cleanup +cleanupTests +return diff --git a/tests/place.test b/tests/place.test index 6a00192..e04ee0a 100644 --- a/tests/place.test +++ b/tests/place.test @@ -118,7 +118,26 @@ test place-4.4 {ConfigureSlave procedure, bad -in option} -setup { } -body { place .t.f2 -in . } -returnCodes error -result {can't place .t.f2 relative to .} - +test place-4.5 {ConfigureSlave procedure, bad -in option} -setup { +} -body { + frame .t.f1 + place .t.f1 -in .t.f1 +} -returnCodes error -result {can't place .t.f1 relative to itself} +test place-4.6 {prevent management loops} -setup { + place forget .t.f1 +} -body { + place .t.f1 -in .t.f2 + place .t.f2 -in .t.f1 +} -returnCodes error -result {can't put .t.f2 inside .t.f1, would cause management loop} +test place-4.7 {prevent management loops} -setup { + place forget .t.f1 + place forget .t.f2 +} -body { + frame .t.f3 + place .t.f1 -in .t.f2 + place .t.f2 -in .t.f3 + place .t.f3 -in .t.f1 +} -returnCodes error -result {can't put .t.f3 inside .t.f1, would cause management loop} test place-5.1 {ConfigureSlave procedure, -relwidth option} -body { place .t.f2 -relwidth abcd diff --git a/tests/textWind.test b/tests/textWind.test index e189663..b98250e 100644 --- a/tests/textWind.test +++ b/tests/textWind.test @@ -39,6 +39,11 @@ wm minsize . 1 1 wm positionfrom . user wm deiconify . +# This update is needed on MacOS to make sure that the window is mapped +# when the tests begin. + +update + set bw [.t cget -borderwidth] set px [.t cget -padx] set py [.t cget -pady] diff --git a/tests/ttk/entry.test b/tests/ttk/entry.test index d303446..0ff0f2f 100644 --- a/tests/ttk/entry.test +++ b/tests/ttk/entry.test @@ -103,10 +103,37 @@ test entry-3.1 "bbox widget command" -body { test entry-3.2 "xview" -body { .e delete 0 end; .e insert end [string repeat "0" 40] - update idletasks set result [.e xview] } -result {0.0 0.5} +test entry-3.3 "xview" -body { + .e delete 0 end; + .e insert end abcdefghijklmnopqrstuvwxyz + .e xview end + set result [.e index @0] +} -result {7} + +test entry-3.4 "xview" -body { + .e delete 0 end; + .e insert end abcdefghijklmnopqrstuvwxyz + .e xview moveto 1.0 + set result [.e index @0] +} -result {7} + +test entry-3.5 "xview" -body { + .e delete 0 end; + .e insert end abcdefghijklmnopqrstuvwxyz + .e xview scroll 5 units + set result [.e index @0] +} -result {5} + +test entry-3.6 "xview" -body { + .e delete 0 end; + .e insert end [string repeat abcdefghijklmnopqrstuvwxyz 5] + .e xview scroll 2 pages + set result [.e index @0] +} -result {40} + test entry-3.last "Series 3 cleanup" -body { destroy .e } diff --git a/tests/ttk/scrollbar.test b/tests/ttk/scrollbar.test index c7cab13..0743def 100644 --- a/tests/ttk/scrollbar.test +++ b/tests/ttk/scrollbar.test @@ -4,16 +4,26 @@ loadTestedCommands testConstraint coreScrollbar [expr {[tk windowingsystem] eq "aqua"}] -test scrollbar-swapout-1 "Use core scrollbars on OSX..." -constraints { - coreScrollbar +# Before 2019 the code in library/ttk/scrollbar.tcl would replace the +# constructor of ttk::scrollbar with the constructor of tk::scrollbar +# unless the -class or -style options were specified.. +# Now there is an implementation of ttk::scrollbar for macOS. The +# tests are left in place, though, except that scrollbar-swapout-1 +# test was changed to expect the class to be TScrollbar instead of +# Scrollbar. + +test scrollbar-swapout-1 "Don't use core scrollbars on OSX..." \ + -constraints { + coreScrollbar } -body { ttk::scrollbar .sb -command "yadda" list [winfo class .sb] [.sb cget -command] -} -result [list Scrollbar yadda] -cleanup { +} -result [list TScrollbar yadda] -cleanup { destroy .sb } -test scrollbar-swapout-2 "... unless -style is specified ..." -constraints { +test scrollbar-swapout-2 "... regardless of whether -style ..." \ +-constraints { coreScrollbar } -body { ttk::style layout Vertical.Custom.TScrollbar \ @@ -24,7 +34,7 @@ test scrollbar-swapout-2 "... unless -style is specified ..." -constraints { destroy .sb } -test scrollbar-swapout-3 "... or -class." -constraints { +test scrollbar-swapout-3 "... or -class is specified." -constraints { coreScrollbar } -body { ttk::scrollbar .sb -command "yadda" -class Custom.TScrollbar @@ -44,13 +54,19 @@ test scrollbar-1.1 "Set method" -body { test scrollbar-1.2 "Set orientation" -body { .tsb configure -orient vertical - set w [winfo reqwidth .tsb] ; set h [winfo reqheight .tsb] + pack .tsb -side right -anchor e -expand 1 -fill y + wm geometry . 200x200 + update + set w [winfo width .tsb] ; set h [winfo height .tsb] expr {$h > $w} } -result 1 test scrollbar-1.3 "Change orientation" -body { .tsb configure -orient horizontal - set w [winfo reqwidth .tsb] ; set h [winfo reqheight .tsb] + pack .tsb -side bottom -anchor s -expand 1 -fill x + wm geometry . 200x200 + update + set w [winfo width .tsb] ; set h [winfo height .tsb] expr {$h < $w} } -result 1 diff --git a/tests/ttk/treeview.test b/tests/ttk/treeview.test index aa7e64a..1863a95 100644 --- a/tests/ttk/treeview.test +++ b/tests/ttk/treeview.test @@ -471,6 +471,18 @@ test treeview-9.0 "scroll callback - empty tree" -body { set ::scrolldata } -result [list 0.0 1.0] +test treeview-9.1 "scrolling" -setup { + pack [ttk::treeview .tree -show tree] -fill y + for {set i 1} {$i < 100} {incr i} { + .tree insert {} end -text $i + } +} -body { + .tree yview scroll 5 units + .tree identify item 2 2 +} -cleanup { + destroy .tree +} -result {I006} + ### identify tests: # proc identify* {tv comps args} { @@ -636,4 +648,21 @@ test treeview-3085489-2 "tag remove, no -tags" -setup { destroy .tv } -result [list] +test treeview-368fa4561e "indicators cannot be clicked on leafs" -setup { + pack [ttk::treeview .tv] + .tv insert {} end -id foo -text "<-- (1) Click the blank space to my left" + update +} -body { + foreach {x y w h} [.tv bbox foo #0] {} + set res [.tv item foo -open] + # using $h even for x computation is intentional here in order to simulate + # a mouse click on the (invisible since we're on a leaf) indicator + event generate .tv <ButtonPress-1> \ + -x [expr ($x + $h / 2)] \ + -y [expr ($y + $h / 2)] + lappend res [.tv item foo -open] + .tv insert foo end -text "sub" + lappend res [.tv item foo -open] +} -result {0 0 0} + tcltest::cleanupTests diff --git a/tests/unixEmbed.test b/tests/unixEmbed.test index 99f7265..a29995f 100644 --- a/tests/unixEmbed.test +++ b/tests/unixEmbed.test @@ -1234,6 +1234,7 @@ test unixEmbed-10.1 {geometry propagation in tkUnixWm.c/UpdateGeometryInfo} -con deleteWindows } -body { frame .f1 -container 1 -width 200 -height 50 + update pack .f1 update toplevel .t1 -use [winfo id .f1] -width 150 -height 80 diff --git a/unix/Makefile.in b/unix/Makefile.in index 56c343b..0c331bf 100644 --- a/unix/Makefile.in +++ b/unix/Makefile.in @@ -95,7 +95,7 @@ HTML_INSTALL_DIR = $(INSTALL_ROOT)$(HTML_DIR) CONFIG_INSTALL_DIR = $(INSTALL_ROOT)$(libdir) # Directory in which to install the demo files: -DEMO_INSTALL_DIR = $(INSTALL_ROOT)$(TK_LIBRARY)/demos +DEMO_INSTALL_DIR = $(INSTALL_ROOT)@DEMO_DIR@ # The directory containing the Tcl sources and headers appropriate # for this version of Tk ("srcdir" will be replaced or has already @@ -356,7 +356,7 @@ CANV_OBJS = tkCanvas.o tkCanvArc.o tkCanvBmap.o tkCanvImg.o \ tkCanvUtil.o tkCanvWind.o tkRectOval.o tkTrig.o IMAGE_OBJS = tkImage.o tkImgBmap.o tkImgGIF.o tkImgPNG.o tkImgPPM.o \ - tkImgPhoto.o tkImgPhInstance.o tkImgListFormat.o + tkImgPhoto.o tkImgPhInstance.o tkImgListFormat.o tkImgSVGnano.o TEXT_OBJS = tkText.o tkTextBTree.o tkTextDisp.o tkTextImage.o tkTextIndex.o \ tkTextMark.o tkTextTag.o tkTextWind.o @@ -369,8 +369,9 @@ GENERIC_OBJS = tk3d.o tkArgv.o tkAtom.o tkBind.o tkBitmap.o tkBusy.o \ tkClipboard.o \ tkCmds.o tkColor.o tkConfig.o tkConsole.o tkCursor.o tkError.o \ tkEvent.o tkFocus.o tkFont.o tkGet.o tkGC.o tkGeometry.o tkGrab.o \ - tkGrid.o tkMain.o tkObj.o tkOldConfig.o tkOption.o tkPack.o tkPlace.o \ - tkSelect.o tkStyle.o tkUndo.o tkUtil.o tkVisual.o tkWindow.o + tkGrid.o tkMain.o tkObj.o tkOldConfig.o tkOption.o tkPack.o \ + tkPkgConfig.o tkPlace.o tkSelect.o tkStyle.o tkUndo.o tkUtil.o \ + tkVisual.o tkWindow.o TTK_OBJS = \ ttkBlink.o ttkButton.o ttkCache.o ttkClamTheme.o ttkClassicTheme.o \ @@ -431,6 +432,7 @@ GENERIC_SRCS = \ $(GENERIC_DIR)/tkGrid.c $(GENERIC_DIR)/tkConsole.c \ $(GENERIC_DIR)/tkMain.c $(GENERIC_DIR)/tkOption.c \ $(GENERIC_DIR)/tkPack.c $(GENERIC_DIR)/tkPlace.c \ + $(GENERIC_DIR)/tkPkgConfig.c \ $(GENERIC_DIR)/tkSelect.c $(GENERIC_DIR)/tkStyle.c \ $(GENERIC_DIR)/tkUndo.c $(GENERIC_DIR)/tkUtil.c \ $(GENERIC_DIR)/tkVisual.c $(GENERIC_DIR)/tkWindow.c \ @@ -449,6 +451,7 @@ GENERIC_SRCS = \ $(GENERIC_DIR)/tkTrig.c $(GENERIC_DIR)/tkImage.c \ $(GENERIC_DIR)/tkImgBmap.c $(GENERIC_DIR)/tkImgGIF.c \ $(GENERIC_DIR)/tkImgPNG.c $(GENERIC_DIR)/tkImgPPM.c \ + $(GENERIC_DIR)/tkImgSVGnano.c $(GENERIC_DIR)/tkImgSVGnano.c \ $(GENERIC_DIR)/tkImgPhoto.c $(GENERIC_DIR)/tkImgPhInstance.c \ $(GENERIC_DIR)/tkImgListFormat.c $(GENERIC_DIR)/tkText.c \ $(GENERIC_DIR)/tkTextBTree.c $(GENERIC_DIR)/tkTextDisp.c \ @@ -1010,6 +1013,32 @@ tkOption.o: $(GENERIC_DIR)/tkOption.c tkPack.o: $(GENERIC_DIR)/tkPack.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkPack.c +# TIP #59, embedding of configuration information into the binary library. +# +# Part of Tk's configuration information are the paths where it was installed +# and where it will look for its libraries (which can be different). We derive +# this information from the variables which can be overridden by the user. As +# every path can be configured separately we do not remember one general +# prefix/exec_prefix but all the different paths individually. + +tkPkgConfig.o: $(GENERIC_DIR)/tkPkgConfig.c + $(CC) -c $(CC_SWITCHES) \ + -DCFG_INSTALL_LIBDIR="\"$(LIB_INSTALL_DIR)\"" \ + -DCFG_INSTALL_BINDIR="\"$(BIN_INSTALL_DIR)\"" \ + -DCFG_INSTALL_SCRDIR="\"$(SCRIPT_INSTALL_DIR)\"" \ + -DCFG_INSTALL_INCDIR="\"$(INCLUDE_INSTALL_DIR)\"" \ + -DCFG_INSTALL_DOCDIR="\"$(MAN_INSTALL_DIR)\"" \ + -DCFG_INSTALL_DEMODIR="\"$(DEMO_INSTALL_DIR)\"" \ + \ + -DCFG_RUNTIME_LIBDIR="\"$(libdir)\"" \ + -DCFG_RUNTIME_BINDIR="\"$(bindir)\"" \ + -DCFG_RUNTIME_SCRDIR="\"$(TK_LIBRARY)\"" \ + -DCFG_RUNTIME_INCDIR="\"$(includedir)\"" \ + -DCFG_RUNTIME_DOCDIR="\"$(mandir)\"" \ + -DCFG_RUNTIME_DEMODIR="\"$(DEMO_INSTALL_DIR)\"" \ + \ + $(GENERIC_DIR)/tkPkgConfig.c + tkPlace.o: $(GENERIC_DIR)/tkPlace.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkPlace.c @@ -1118,6 +1147,9 @@ tkImgPNG.o: $(GENERIC_DIR)/tkImgPNG.c tkImgPPM.o: $(GENERIC_DIR)/tkImgPPM.c $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgPPM.c +tkImgSVGnano.o: $(GENERIC_DIR)/tkImgSVGnano.c + $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgSVGnano.c + tkImgPhoto.o: $(GENERIC_DIR)/tkImgPhoto.c $(GENERIC_DIR)/tkImgPhoto.h $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkImgPhoto.c diff --git a/unix/configure b/unix/configure index d7408c4..694cde3 100755 --- a/unix/configure +++ b/unix/configure @@ -661,6 +661,8 @@ TK_PATCH_LEVEL TK_MINOR_VERSION TK_MAJOR_VERSION TK_VERSION +TK_DEMO_DIR +DEMO_DIR UNIX_FONT_OBJS XFT_LIBS XFT_CFLAGS @@ -768,6 +770,7 @@ with_tcl enable_man_symlinks enable_man_compression enable_man_suffix +with_encoding enable_shared enable_64bit enable_64bit_vis @@ -1430,6 +1433,8 @@ Optional Packages: --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) --with-tcl directory containing tcl configuration (tclConfig.sh) + --with-encoding encoding for configuration values (default: + iso8859-1) --with-x use the X Window System Some influential environment variables: @@ -4090,6 +4095,31 @@ $as_echo "$tcl_cv_cc_pipe" >&6; } fi fi +#------------------------------------------------------------------------ +# Embedded configuration information, encoding to use for the values, TIP #59 +#------------------------------------------------------------------------ + + + +# Check whether --with-encoding was given. +if test "${with_encoding+set}" = set; then : + withval=$with_encoding; with_tcencoding=${withval} +fi + + + if test x"${with_tcencoding}" != x ; then + +cat >>confdefs.h <<_ACEOF +#define TCL_CFGVAL_ENCODING "${with_tcencoding}" +_ACEOF + + else + +$as_echo "#define TCL_CFGVAL_ENCODING \"iso8859-1\"" >>confdefs.h + + fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to build libraries" >&5 $as_echo_n "checking how to build libraries... " >&6; } @@ -8466,6 +8496,18 @@ TK_STUB_LIB_PATH="${TK_STUB_LIB_DIR}/${TK_STUB_LIB_FILE}" eval "TK_INCLUDE_SPEC=\"-I${includedir}\"" #------------------------------------------------------------------------ +# Demo dir +#------------------------------------------------------------------------ + +if test x"${DEMO_DIR}" = x; then : + DEMO_DIR='$(TK_LIBRARY)/demos' +fi +eval "TK_DEMO_DIR=\"`echo ${DEMO_DIR} | tr '()' '{}'`\"" +eval "TK_DEMO_DIR=\"`echo ${TK_DEMO_DIR} | tr '()' '{}'`\"" + + + +#------------------------------------------------------------------------ # tkConfig.sh refers to this by a different name #------------------------------------------------------------------------ diff --git a/unix/configure.ac b/unix/configure.ac index a8c403b..951eb99 100644 --- a/unix/configure.ac +++ b/unix/configure.ac @@ -112,6 +112,12 @@ if test -z "$no_pipe" && test -n "$GCC"; then fi fi +#------------------------------------------------------------------------ +# Embedded configuration information, encoding to use for the values, TIP #59 +#------------------------------------------------------------------------ + +SC_TCL_CFG_ENCODING + SC_ENABLE_SHARED #-------------------------------------------------------------------- @@ -773,6 +779,16 @@ TK_STUB_LIB_PATH="${TK_STUB_LIB_DIR}/${TK_STUB_LIB_FILE}" eval "TK_INCLUDE_SPEC=\"-I${includedir}\"" #------------------------------------------------------------------------ +# Demo dir +#------------------------------------------------------------------------ + +AS_IF([test x"${DEMO_DIR}" = x], [DEMO_DIR='$(TK_LIBRARY)/demos']) +eval "TK_DEMO_DIR=\"`echo ${DEMO_DIR} | tr '()' '{}'`\"" +eval "TK_DEMO_DIR=\"`echo ${TK_DEMO_DIR} | tr '()' '{}'`\"" +AC_SUBST(DEMO_DIR) +AC_SUBST(TK_DEMO_DIR) + +#------------------------------------------------------------------------ # tkConfig.sh refers to this by a different name #------------------------------------------------------------------------ diff --git a/unix/tcl.m4 b/unix/tcl.m4 index 99f5d29..e0d4778 100644 --- a/unix/tcl.m4 +++ b/unix/tcl.m4 @@ -494,7 +494,7 @@ AC_DEFUN([SC_TCL_FIND_SOURCES],[ set tclh $candidate/generic/tcl.h if {![file exists $tclh]} { - return {} + return {} } set version [tclhversion [cat $tclh]] @@ -506,7 +506,7 @@ AC_DEFUN([SC_TCL_FIND_SOURCES],[ } } - return {} + return {} } proc tclhversion data { @@ -559,7 +559,7 @@ AC_DEFUN([SC_TCL_LINDEX], EOF ` ]] - + ) #------------------------------------------------------------------------ diff --git a/unix/tk.pc.in b/unix/tk.pc.in index 68f2130..f1e7acd 100644 --- a/unix/tk.pc.in +++ b/unix/tk.pc.in @@ -4,6 +4,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ +demodir=@TK_DEMO_DIR@ Name: The Tk Toolkit Description: Tk is a cross-platform graphical user interface toolkit, the standard GUI not only for Tcl, but for many other dynamic languages as well. diff --git a/unix/tkConfig.h.in b/unix/tkConfig.h.in index d598ca3..c435f7c 100644 --- a/unix/tkConfig.h.in +++ b/unix/tkConfig.h.in @@ -136,6 +136,12 @@ /* Is this a static build? */ #undef STATIC_BUILD +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* What encoding should be used for embedded configuration info? */ +#undef TCL_CFGVAL_ENCODING + /* Is this a 64-bit build? */ #undef TCL_CFG_DO64BIT diff --git a/unix/tkConfig.sh.in b/unix/tkConfig.sh.in index bb85ad0..e08861b 100644 --- a/unix/tkConfig.sh.in +++ b/unix/tkConfig.sh.in @@ -95,3 +95,6 @@ TK_BUILD_STUB_LIB_PATH='@TK_BUILD_STUB_LIB_PATH@' # Path to the Tk stub library in the install directory. TK_STUB_LIB_PATH='@TK_STUB_LIB_PATH@' + +# Top-level directory in which Tk's demo files are installed. +TK_DEMO_DIR='@TK_DEMO_DIR@' diff --git a/unix/tkUnixDefault.h b/unix/tkUnixDefault.h index bc7cd93..d1c790e 100644 --- a/unix/tkUnixDefault.h +++ b/unix/tkUnixDefault.h @@ -31,8 +31,7 @@ #define ACTIVE_BG "#ececec" #define SELECT_BG "#c3c3c3" #define TROUGH "#b3b3b3" -#define CHECK_INDICATOR WHITE -#define MENU_INDICATOR BLACK +#define INDICATOR WHITE #define DISABLED "#a3a3a3" /* @@ -79,7 +78,7 @@ #define DEF_LABCHKRAD_RELIEF "flat" #define DEF_BUTTON_REPEAT_DELAY "0" #define DEF_BUTTON_REPEAT_INTERVAL "0" -#define DEF_BUTTON_SELECT_COLOR CHECK_INDICATOR +#define DEF_BUTTON_SELECT_COLOR INDICATOR #define DEF_BUTTON_SELECT_MONO BLACK #define DEF_BUTTON_SELECT_IMAGE NULL #define DEF_BUTTON_STATE "normal" @@ -287,7 +286,7 @@ #define DEF_MENU_FG BLACK #define DEF_MENU_POST_COMMAND "" #define DEF_MENU_RELIEF "raised" -#define DEF_MENU_SELECT_COLOR MENU_INDICATOR +#define DEF_MENU_SELECT_COLOR BLACK #define DEF_MENU_SELECT_MONO BLACK #define DEF_MENU_TAKE_FOCUS "0" #define DEF_MENU_TEAROFF "0" diff --git a/unix/tkUnixKey.c b/unix/tkUnixKey.c index baba11e..cbba7bb 100644 --- a/unix/tkUnixKey.c +++ b/unix/tkUnixKey.c @@ -110,7 +110,7 @@ TkpGetString( XEvent *eventPtr, /* X keyboard event. */ Tcl_DString *dsPtr) /* Initialized, empty string to hold result. */ { - size_t len; + TkSizeT len; Tcl_DString buf; TkKeyEvent *kePtr = (TkKeyEvent *) eventPtr; diff --git a/unix/tkUnixRFont.c b/unix/tkUnixRFont.c index 9c15369..7dcddc9 100644 --- a/unix/tkUnixRFont.c +++ b/unix/tkUnixRFont.c @@ -56,25 +56,28 @@ typedef struct ThreadSpecificData { static Tcl_ThreadDataKey dataKey; /* - * Package initialization: - * Nothing to do here except register the fact that we're using Xft in - * the TIP 59 configuration database. + *------------------------------------------------------------------------- + * + * TkpFontPkgInit -- + * + * This procedure is called when an application is created. It + * initializes all the structures that are used by the + * platform-dependant code on a per application basis. + * Note that this is called before TkpInit() ! + * + * Results: + * None. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------- */ -#ifndef TCL_CFGVAL_ENCODING -#define TCL_CFGVAL_ENCODING "ascii" -#endif - void TkpFontPkgInit( TkMainInfo *mainPtr) /* The application being created. */ { - static const Tcl_Config cfg[] = { - { "fontsystem", "xft" }, - { 0,0 } - }; - - Tcl_RegisterConfig(mainPtr->interp, "tk", cfg, TCL_CFGVAL_ENCODING); } static XftFont * diff --git a/unix/tkUnixSelect.c b/unix/tkUnixSelect.c index 6155f54..5b450f4 100644 --- a/unix/tkUnixSelect.c +++ b/unix/tkUnixSelect.c @@ -449,7 +449,7 @@ TkSelPropProc( if (srcLen > 4) { Tcl_Panic("selection conversion left too many bytes unconverted"); } - memcpy(incrPtr->converts[i].buffer, src, (size_t) srcLen+1); + memcpy(incrPtr->converts[i].buffer, src, srcLen + 1); Tcl_DStringFree(&ds); } else { /* diff --git a/unix/tkUnixWm.c b/unix/tkUnixWm.c index 3afa011..d06f38a 100644 --- a/unix/tkUnixWm.c +++ b/unix/tkUnixWm.c @@ -3005,7 +3005,7 @@ WmProtocolCmd( register ProtocolHandler *protPtr, *prevPtr; Atom protocol; const char *cmd; - size_t cmdLength; + TkSizeT cmdLength; if ((objc < 3) || (objc > 5)) { Tcl_WrongNumArgs(interp, 2, objv, "window ?name? ?command?"); @@ -5456,7 +5456,7 @@ SetNetWmType( for (n = 0; n < objc; ++n) { Tcl_DString ds, dsName; - size_t len; + TkSizeT len; char *name = TkGetStringFromObj(objv[n], &len); Tcl_UtfToUpper(name); diff --git a/win/Makefile.in b/win/Makefile.in index f035219..f857b7c 100644 --- a/win/Makefile.in +++ b/win/Makefile.in @@ -322,6 +322,7 @@ TK_OBJS = \ tkImgGIF.$(OBJEXT) \ tkImgPNG.$(OBJEXT) \ tkImgPPM.$(OBJEXT) \ + tkImgSVGnano.$(OBJEXT) \ tkImgPhoto.$(OBJEXT) \ tkImgPhInstance.$(OBJEXT) \ tkImgUtil.$(OBJEXT) \ @@ -338,6 +339,7 @@ TK_OBJS = \ tkOldConfig.$(OBJEXT) \ tkOption.$(OBJEXT) \ tkPack.$(OBJEXT) \ + tkPkgConfig.$(OBJEXT) \ tkPlace.$(OBJEXT) \ tkPointer.$(OBJEXT) \ tkRectOval.$(OBJEXT) \ diff --git a/win/makefile.vc b/win/makefile.vc index d02a94f..87fe366 100644 --- a/win/makefile.vc +++ b/win/makefile.vc @@ -219,6 +219,7 @@ TKOBJS = \ $(TMP_DIR)\tkImgGIF.obj \
$(TMP_DIR)\tkImgPNG.obj \
$(TMP_DIR)\tkImgPPM.obj \
+ $(TMP_DIR)\tkImgSVGnano.obj \
$(TMP_DIR)\tkImgPhoto.obj \
$(TMP_DIR)\tkImgPhInstance.obj \
$(TMP_DIR)\tkImgUtil.obj \
@@ -235,6 +236,7 @@ TKOBJS = \ $(TMP_DIR)\tkOldConfig.obj \
$(TMP_DIR)\tkOption.obj \
$(TMP_DIR)\tkPack.obj \
+ $(TMP_DIR)\tkPkgConfig.obj \
$(TMP_DIR)\tkPlace.obj \
$(TMP_DIR)\tkPointer.obj \
$(TMP_DIR)\tkRectOval.obj \
diff --git a/win/tkWinButton.c b/win/tkWinButton.c index f4039cf..0b71327 100644 --- a/win/tkWinButton.c +++ b/win/tkWinButton.c @@ -405,7 +405,10 @@ TkpDisplayButton( * Compute width of default ring and offset for pushed buttons. */ - if (butPtr->type == TYPE_BUTTON) { + if (butPtr->type == TYPE_LABEL) { + defaultWidth = butPtr->highlightWidth; + offset = 0; + } else if (butPtr->type == TYPE_BUTTON) { defaultWidth = ((butPtr->defaultState == DEFAULT_ACTIVE) ? butPtr->highlightWidth : 0); offset = 1; @@ -759,17 +762,24 @@ TkpDisplayButton( butPtr->borderWidth, relief); } if (defaultWidth != 0) { + int highlightColor; + dc = TkWinGetDrawableDC(butPtr->display, pixmap, &state); + if (butPtr->type == TYPE_LABEL) { + highlightColor = (int) Tk_3DBorderColor(butPtr->highlightBorder)->pixel; + } else { + highlightColor = (int) butPtr->highlightColorPtr->pixel; + } TkWinFillRect(dc, 0, 0, Tk_Width(tkwin), defaultWidth, - (int) butPtr->highlightColorPtr->pixel); + highlightColor); TkWinFillRect(dc, 0, 0, defaultWidth, Tk_Height(tkwin), - (int) butPtr->highlightColorPtr->pixel); + highlightColor); TkWinFillRect(dc, 0, Tk_Height(tkwin) - defaultWidth, Tk_Width(tkwin), defaultWidth, - (int) butPtr->highlightColorPtr->pixel); + highlightColor); TkWinFillRect(dc, Tk_Width(tkwin) - defaultWidth, 0, defaultWidth, Tk_Height(tkwin), - (int) butPtr->highlightColorPtr->pixel); + highlightColor); TkWinReleaseDrawableDC(pixmap, dc, &state); } diff --git a/win/tkWinMenu.c b/win/tkWinMenu.c index 18a1260..eebe671 100644 --- a/win/tkWinMenu.c +++ b/win/tkWinMenu.c @@ -1247,7 +1247,7 @@ TkWinHandleMenuEvent( hashEntryPtr = Tcl_FindHashEntry(&tsdPtr->winMenuTable, *plParam); if (hashEntryPtr != NULL) { - size_t i, len, underline; + TkSizeT i, len, underline; Tcl_Obj *labelPtr; WCHAR *wlabel; int menuChar; @@ -1262,10 +1262,10 @@ TkWinHandleMenuEvent( menuChar = Tcl_UniCharToUpper(LOWORD(*pwParam)); Tcl_DStringInit(&ds); - for (i = 0; i < (size_t)menuPtr->numEntries; i++) { + for (i = 0; i < menuPtr->numEntries; i++) { underline = menuPtr->entries[i]->underline; labelPtr = menuPtr->entries[i]->labelPtr; - if ((underline != (size_t)-1) && (labelPtr != NULL)) { + if ((underline != TCL_AUTO_LENGTH) && (labelPtr != NULL)) { /* * Ensure we don't exceed the label length, then check */ diff --git a/win/ttkWinXPTheme.c b/win/ttkWinXPTheme.c index 0a5ac30..22a41d9 100644 --- a/win/ttkWinXPTheme.c +++ b/win/ttkWinXPTheme.c @@ -826,7 +826,7 @@ static void TextElementSize( RECT rc = {0, 0}; HRESULT hr = S_OK; const char *src; - size_t len; + TkSizeT len; Tcl_DString ds; if (!InitElementData(elementData, tkwin, 0)) @@ -865,7 +865,7 @@ static void TextElementDraw( RECT rc = BoxToRect(b); HRESULT hr = S_OK; const char *src; - size_t len; + TkSizeT len; Tcl_DString ds; if (!InitElementData(elementData, tkwin, d)) @@ -1058,7 +1058,7 @@ GetSysFlagFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *resultPtr) "SM_CXBORDER", "SM_CYBORDER", "SM_CXVSCROLL", "SM_CYVSCROLL", "SM_CXHSCROLL", "SM_CYHSCROLL", "SM_CXMENUCHECK", "SM_CYMENUCHECK", "SM_CXMENUSIZE", "SM_CYMENUSIZE", "SM_CXSIZE", "SM_CYSIZE", "SM_CXSMSIZE", - "SM_CYSMSIZE" + "SM_CYSMSIZE", NULL }; int flags[] = { SM_CXBORDER, SM_CYBORDER, SM_CXVSCROLL, SM_CYVSCROLL, @@ -1116,7 +1116,7 @@ Ttk_CreateVsapiElement( Ttk_StateTable *stateTable; Ttk_Padding pad = {0, 0, 0, 0}; int flags = 0; - size_t length = 0; + TkSizeT length = 0; char *name; LPWSTR wname; Ttk_ElementSpec *elementSpec = &GenericElementSpec; |