1 unit Gtk2Themes;
2 
3 {$mode objfpc}{$H+}
4 
5 interface
6 
7 uses
8   // rtl
9   Types, Classes, SysUtils,
10   // os
11   glib2,  gdk2, gtk2, gdk2pixbuf,
12   // lcl
13   LCLType, LCLProc, LCLIntf, Graphics, Themes, TmSchema, Forms,
14   // widgetset
15   Gtk2Def, Gtk2Int, Gtk2Proc, Gtk2Globals;
16 
17 type
18   TGtkPainterType =
19   (
20     gptNone,
21     gptDefault,
22     gptHLine,
23     gptVLine,
24     gptShadow,
25     gptBox,
26     gptBoxGap,
27     gptFlatBox,
28     gptCheck,
29     gptOption,
30     gptTab,
31     gptSlider,
32     gptHandle,
33     gptExpander,
34     gptResizeGrip,
35     gptFocus,
36     gptArrow,
37     gptPixmap,
38     gptComboBox
39   );
40 
41   TGtkStyleParams = record
42     Style      : PGtkStyle;       // paint style
43     Painter    : TGtkPainterType; // type of paint handler
44     Widget     : PGtkWidget;      // widget
45     Window     : PGdkWindow;      // paint window
46     Origin     : TPoint;          // offset
47     State      : TGtkStateType;   // Style state
48     Shadow     : TGtkShadowType;  // Shadow
49     Detail     : String;          // Detail (button, checkbox, ...)
50     Orientation: TGtkOrientation; // Orientation (horizontal/vertical)
51     ArrowType  : TGtkArrowType;   // type of arrow
52     Fill       : Boolean;         // fill inside area
53     GapSide    : TGtkPositionType;//
54     GapX       : gint;
55     GapWidth   : gint;
56     MaxWidth   : gint;            // max area width
57     Expander   : TGtkExpanderStyle; // treeview expander
58     ExpanderSize: Integer;
59     Edge       : TGdkWindowEdge;
60     IsHot      : Boolean;
61   end;
62 
63 type
64   { TGtk2ThemeServices }
65 
66   TGtk2ThemeServices = class(TThemeServices)
67   protected
68     procedure DrawPixmap(DC: HDC; Area: PGdkRectangle; PixmapIndex: Byte); virtual;
69 
InitThemesnull70     function InitThemes: Boolean; override;
UseThemesnull71     function UseThemes: Boolean; override;
ThemedControlsEnablednull72     function ThemedControlsEnabled: Boolean; override;
73 
74     procedure InternalDrawParentBackground({%H-}Window: HWND; {%H-}Target: HDC; {%H-}Bounds: PRect); override;
GetBaseDetailsSizenull75     function GetBaseDetailsSize(Details: TThemedElementDetails): TSize;
76 
GetParamsCountnull77     function GetParamsCount(Details: TThemedElementDetails): Integer; virtual;
78 
79     procedure GtkDrawElement(DC: HDC; Details: TThemedElementDetails;
80       const R: TRect; ClipRect: PRect);
GetGtkStyleParamsnull81     function GetGtkStyleParams(DC: HDC; Details: TThemedElementDetails;
82       AIndex: Integer): TGtkStyleParams;
83   public
GetDetailSizenull84     function GetDetailSize(Details: TThemedElementDetails): TSize; override;
GetStockImagenull85     function GetStockImage(StockID: LongInt; out Image, Mask: HBitmap): Boolean; override;
GetOptionnull86     function GetOption(AOption: TThemeOption): Integer; override;
87     procedure DrawElement(DC: HDC; Details: TThemedElementDetails;
88        const R: TRect; ClipRect: PRect); override;
89 
90     procedure DrawText(DC: HDC; Details: TThemedElementDetails; const S: String; R: TRect; Flags, {%H-}Flags2: Cardinal); override;
91     procedure DrawText(ACanvas: TPersistent; Details: TThemedElementDetails; const S: String; R: TRect; Flags, Flags2: Cardinal); virtual; overload; reintroduce;
92 
ContentRectnull93     function ContentRect(DC: HDC; Details: TThemedElementDetails; BoundingRect: TRect): TRect; override;
HasTransparentPartsnull94     function HasTransparentParts({%H-}Details: TThemedElementDetails): Boolean; override;
95   end;
96 
97 const
98   // most common maps
99   GtkButtonMap: array[0..6] of TGtkStateType =
100   (
101 { filter ?          } GTK_STATE_NORMAL,
102 { normal            } GTK_STATE_NORMAL,
103 { hot               } GTK_STATE_PRELIGHT,
104 { pressed           } GTK_STATE_ACTIVE,
105 { disabled          } GTK_STATE_INSENSITIVE,
106 { defaulted/checked } GTK_STATE_ACTIVE,
107 { hot + checked     } GTK_STATE_ACTIVE
108   );
109   GtkRadioCheckBoxMap: array[0..12] of TGtkStateType =
110   (
111 { Filler            } GTK_STATE_NORMAL,
112 { UNCHECKEDNORMAL   } GTK_STATE_NORMAL,
113 { UNCHECKEDHOT      } GTK_STATE_PRELIGHT,
114 { UNCHECKEDPRESSED  } GTK_STATE_ACTIVE,
115 { UNCHECKEDDISABLED } GTK_STATE_INSENSITIVE,
116 { CHECKEDNORMAL     } GTK_STATE_NORMAL,
117 { CHECKEDHOT        } GTK_STATE_PRELIGHT,
118 { CHECKEDPRESSED    } GTK_STATE_ACTIVE,
119 { CHECKEDDISABLED   } GTK_STATE_INSENSITIVE,
120 { MIXEDNORMAL       } GTK_STATE_NORMAL,
121 { MIXEDHOT          } GTK_STATE_PRELIGHT,
122 { MIXEDPRESSED      } GTK_STATE_ACTIVE,
123 { MIXEDDISABLED     } GTK_STATE_INSENSITIVE
124   );
125   GtkTitleButtonMap: array[0..5] of TGtkStateType =
126   (
127 { filter ?          } GTK_STATE_NORMAL,
128 { normal            } GTK_STATE_NORMAL,
129 { hot               } GTK_STATE_PRELIGHT,
130 { pressed           } GTK_STATE_ACTIVE,
131 { disabled          } GTK_STATE_INSENSITIVE,
132 { inactive          } GTK_STATE_INSENSITIVE
133   );
134 
135 implementation
136 uses math;
137 
138 {$I gtk2stdpixmaps.inc}
139 
GetColumnButtonFromTreeViewnull140 function GetColumnButtonFromTreeView(AWidget: PGtkWidget; Part: Integer): PGtkWidget;
141 var
142   AColumn: PGtkTreeViewColumn;
143   AIndex: Integer;
144 begin
145   Result := nil;
146   if not GTK_IS_TREE_VIEW(AWidget) then
147     exit;
148 
149   if Part = HP_HEADERITEMLEFT then
150     AIndex := 0
151   else if Part = HP_HEADERITEMRIGHT then
152     AIndex := 2
153   else
154     AIndex := 1;
155 
156   AColumn := gtk_tree_view_get_column(PGtkTreeView(AWidget), AIndex);
157   if AColumn = nil then
158     Exit;
159   Result := AColumn^.button;
160 end;
161 
162 { TGtk2ThemeServices }
163 
164 procedure TGtk2ThemeServices.DrawPixmap(DC: HDC; Area: PGdkRectangle;
165   PixmapIndex: Byte);
166 var
167   APixmap, APixmapMask: PGdkPixmap;
168   DevCtx: TGtkDeviceContext absolute DC;
169   w, h: gint;
170 begin
171   if (PixmapIndex >= Low(PixmapArray)) and (PixmapIndex <= High(PixmapArray)) then
172   begin
173     APixmapMask := nil;
174     APixmap := gdk_pixmap_create_from_xpm_d(DevCtx.Drawable,
175       APixmapMask, nil, PixmapArray[PixmapIndex]);
176     if APixmap <> nil then
177     begin
178       gdk_drawable_get_size(APixmap, @w, @h);
179       w := (Area^.Width - w) div 2;
180       if w < 0 then
181         w := 0;
182       h := (Area^.Height - h) div 2;
183       if h < 0 then
184         h := 0;
185       if APixmapMask <> nil then
186       begin
187         gdk_gc_set_clip_mask(DevCtx.GC, APixmapMask);
188         gdk_gc_set_clip_origin(DevCtx.GC, Area^.x + w, Area^.y + h);
189       end;
190       gdk_draw_pixmap(DevCtx.Drawable, DevCtx.GC, APixmap, 0, 0, Area^.x + w, Area^.y + h,
191         Area^.Width, Area^.Height);
192       if APixmapMask <> nil then
193         DevCtx.ResetGCClipping;
194       gdk_pixmap_unref(APixmap);
195     end;
196     if APixmapMask <> nil then
197       gdk_pixmap_unref(APixmapMask);
198   end;
199 end;
200 
TGtk2ThemeServices.InitThemesnull201 function TGtk2ThemeServices.InitThemes: Boolean;
202 begin
203   Result:=True;
204 end;
205 
TGtk2ThemeServices.UseThemesnull206 function TGtk2ThemeServices.UseThemes: Boolean;
207 begin
208   Result:=True;
209 end;
210 
TGtk2ThemeServices.ThemedControlsEnablednull211 function TGtk2ThemeServices.ThemedControlsEnabled: Boolean;
212 begin
213   Result:=True;
214 end;
215 
216 procedure TGtk2ThemeServices.InternalDrawParentBackground(Window: HWND;
217   Target: HDC; Bounds: PRect);
218 begin
219   // ToDo: TGtk2ThemeServices.InternalDrawParentBackground: What to do?
220 end;
221 
TGtk2ThemeServices.GetBaseDetailsSizenull222 function TGtk2ThemeServices.GetBaseDetailsSize(Details: TThemedElementDetails): TSize;
223 begin
224   Result := inherited GetDetailSize(Details);
225 end;
226 
227 
GetGtkStyleParamsnull228 function TGtk2ThemeServices.GetGtkStyleParams(DC: HDC;
229   Details: TThemedElementDetails; AIndex: Integer): TGtkStyleParams;
230 var
231   DevCtx: TGtkDeviceContext absolute DC;
232   ClientWidget: PGtkWidget;
233 begin
234   FillByte(Result{%H-}, SizeOf(Result), 0);
235   if not Gtk2WidgetSet.IsValidDC(DC) then Exit;
236 
237   Result.Widget := DevCtx.Widget;
238   if Result.Widget <> nil then
239   begin
240     ClientWidget := GetFixedWidget(Result.Widget);
241     if ClientWidget <> nil then
242       Result.Widget := ClientWidget;
243     Result.Style := gtk_widget_get_style(Result.Widget);
244   end;
245   Result.Window := DevCtx.Drawable;
246   Result.Origin := DevCtx.Offset;
247 
248   Result.Painter := gptDefault;
249   Result.State := GTK_STATE_NORMAL;
250   Result.Detail := '';
251   Result.Shadow := GTK_SHADOW_NONE;
252   Result.ArrowType := GTK_ARROW_UP;
253   Result.Fill := False;
254   Result.IsHot := False;
255   Result.GapSide := GTK_POS_LEFT;
256   Result.GapWidth := 0;
257   Result.GapX := 0;
258   Result.MaxWidth := 0;
259 
260   case Details.Element of
261     teButton:
262       begin
263         case Details.Part of
264           BP_PUSHBUTTON:
265             begin
266               Result.Widget := GetStyleWidget(lgsButton);
267               Result.Style := GetStyle(lgsButton);
268               Result.State := GtkButtonMap[Details.State];
269               if Details.State = PBS_PRESSED then
270                 Result.Shadow := GTK_SHADOW_IN
271               else
272                 Result.Shadow := GTK_SHADOW_OUT;
273 
274               Result.IsHot:= Result.State = GTK_STATE_PRELIGHT;
275               Result.Detail := 'button';
276               Result.Painter := gptBox;
277             end;
278           BP_RADIOBUTTON:
279             begin
280               Result.Widget := GetStyleWidget(lgsRadiobutton);
281               if Result.Style = nil then
282                 Result.Style := GetStyle(lgsRadiobutton);
283               Result.State := GtkRadioCheckBoxMap[Details.State];
284               if Details.State >= RBS_CHECKEDNORMAL then
285                 Result.Shadow := GTK_SHADOW_IN
286               else
287                 Result.Shadow := GTK_SHADOW_OUT;
288               Result.Detail := 'radiobutton';
289               Result.Painter := gptOption;
290             end;
291           BP_CHECKBOX:
292             begin
293               Result.Widget := GetStyleWidget(lgsCheckbox);
294               if Result.Style = nil then
295                 Result.Style := GetStyle(lgsCheckbox);
296               Result.State := GtkRadioCheckBoxMap[Details.State];
297               Result.Detail := 'checkbutton';
298               if Details.State >= CBS_MIXEDNORMAL then
299                 result.Shadow := GTK_SHADOW_ETCHED_IN
300               else
301               if Details.State >= CBS_CHECKEDNORMAL then
302                 Result.Shadow := GTK_SHADOW_IN
303               else
304                 Result.Shadow := GTK_SHADOW_OUT;
305               Result.Painter := gptCheck;
306             end;
307         end;
308       end;
309     teComboBox:
310       begin
311         Result.Widget := GetStyleWidget(lgsComboBox);
312         if Result.Style = nil then
313           Result.Style := GetStyle(lgsComboBox);
314 
315         Result.Detail := 'button';
316         Result.State := GTK_STATE_NORMAL;
317         if Details.State = CBXS_DISABLED then
318           Result.State := GTK_STATE_INSENSITIVE
319         else
320         if Details.State = CBXS_HOT then
321           Result.State := GTK_STATE_PRELIGHT
322         else
323         if Details.State = CBXS_PRESSED then
324           Result.State := GTK_STATE_ACTIVE;
325 
326         Result.Painter := gptComboBox;
327       end;
328     teHeader:
329       begin
330         Result.Widget := GetColumnButtonFromTreeView(GetStyleWidget(lgsTreeView), Details.Part);
331         if Result.Widget = nil then
332           Result.Widget := GetStyleWidget(lgsTreeView);
333         Result.Style := gtk_widget_get_style(Result.Widget);
334         Result.State := GtkButtonMap[Details.State];
335         if Details.State = PBS_PRESSED then
336           Result.Shadow := GTK_SHADOW_IN
337         else
338           Result.Shadow := GTK_SHADOW_OUT;
339 
340         Result.IsHot:= Result.State = GTK_STATE_PRELIGHT;
341 
342         Result.Detail := 'button';
343         Result.Painter := gptBox;
344       end;
345     teStatus:
346       begin
347         Result.Widget := GetStyleWidget(lgsStatusBar);
348         if Result.Style = nil then
349           Result.Style := GetStyle(lgsStatusBar);
350         Result.Detail := 'statubar';
351         Result.State := GTK_STATE_NORMAL;
352         case Details.Part of
353           SP_PANE:
354             begin
355               Result.Painter := gptShadow;
356               Result.Shadow := GTK_SHADOW_OUT;
357             end;
358           SP_GRIPPER:
359             begin
360               Result.Painter := gptResizeGrip;
361               Result.Edge := GDK_WINDOW_EDGE_SOUTH_EAST;
362             end;
363         end;
364       end;
365     teToolBar:
366       begin
367         case Details.Part of
368           TP_BUTTON,
369           TP_DROPDOWNBUTTON,
370           TP_SPLITBUTTON,
371           TP_SPLITBUTTONDROPDOWN:
372             begin
373               Result.Widget := GetStyleWidget(lgsToolButton);
374               case Details.State of
375                 TS_PRESSED, TS_CHECKED, TS_HOTCHECKED:
376                   Result.Shadow := GTK_SHADOW_IN;
377                 TS_HOT:
378                   Result.Shadow := GTK_SHADOW_ETCHED_OUT;
379               else
380                 Result.Shadow := GTK_SHADOW_NONE;
381               end;
382               if Details.Part = TP_SPLITBUTTONDROPDOWN then
383               begin
384                 case Details.State of
385                   TS_DISABLED: Result.State := GTK_STATE_INSENSITIVE;
386                 else
387                   Result.State := GTK_STATE_NORMAL;
388                 end;
389               end else
390                 Result.State := GtkButtonMap[Details.State];
391 
392               Result.IsHot := Details.State in [TS_HOT, TS_HOTCHECKED];
393               if Result.Style = nil then
394                 Result.Style := GetStyle(lgsToolButton);
395               if (Details.Part = TP_SPLITBUTTONDROPDOWN) then
396               begin
397                 Result.Detail := 'arrow';
398                 Result.ArrowType := GTK_ARROW_DOWN;
399                 Result.Fill := True;
400                 Result.Painter := gptArrow;
401                 Result.MaxWidth := 10;
402               end
403               else
404               begin
405                 Result.Detail := 'button';
406                 if Result.Shadow = GTK_SHADOW_NONE then
407                   Result.Painter := gptNone
408                 else
409                   Result.Painter := gptBox;
410               end;
411             end;
412           TP_SEPARATOR,
413           TP_SEPARATORVERT:
414             begin
415               Result.State := GTK_STATE_NORMAL;
416               Result.Shadow := GTK_SHADOW_NONE;
417               Result.Detail := 'toolbar';
418               if Details.Part = TP_SEPARATOR then
419                 Result.Painter := gptVLine
420               else
421                 Result.Painter := gptHLine;
422             end;
423         end;
424       end;
425     teRebar:
426       begin
427         case Details.Part of
428           RP_GRIPPER, RP_GRIPPERVERT:
429             begin
430               Result.State := GTK_STATE_NORMAL;
431               Result.Shadow := GTK_SHADOW_NONE;
432               Result.Detail := 'paned';
433               Result.Painter := gptHandle;
434               if Details.Part = RP_GRIPPER then
435               begin
436                 Result.Orientation := GTK_ORIENTATION_VERTICAL;
437                 Result.Widget := GetStyleWidget(lgsVerticalPaned);
438               end
439               else
440               begin
441                 Result.Orientation := GTK_ORIENTATION_HORIZONTAL;
442                 Result.Widget := GetStyleWidget(lgsHorizontalPaned);
443               end;
444             end;
445           RP_BAND:
446             begin
447               Result.Widget := GetStyleWidget(lgsVerticalPaned);
448               Result.State := GtkButtonMap[Details.State];
449               Result.Shadow := GTK_SHADOW_NONE;
450               Result.Detail := 'paned';
451               Result.Painter := gptFlatBox;
452             end;
453         end;
454       end;
455     teWindow:
456       begin
457         if Details.Part in [WP_SMALLCLOSEBUTTON, WP_MDIMINBUTTON, WP_MDIRESTOREBUTTON, WP_MDICLOSEBUTTON] then
458         begin
459           Result.State := GtkTitleButtonMap[Details.State];
460           Result.Shadow := GTK_SHADOW_NONE;
461           case Details.Part of
462             WP_MDIMINBUTTON: Result.Detail := #1;
463             WP_MDIRESTOREBUTTON: Result.Detail := #2;
464             WP_SMALLCLOSEBUTTON,
465             WP_MDICLOSEBUTTON: Result.Detail := #3;
466           end;
467           Result.Painter := gptPixmap;
468         end;
469       end;
470     teTab:
471       begin
472         Result.Widget := GetStyleWidget(lgsNotebook);
473         if Result.Style = nil then
474           Result.Style := GetStyle(lgsNotebook);
475         Result.State := GTK_STATE_NORMAL;
476         Result.Shadow := GTK_SHADOW_OUT;
477         Result.Detail := 'notebook';
478         if Details.Part = TABP_PANE then
479           Result.Painter := gptShadow
480         else
481         if Details.Part = TABP_BODY then
482           Result.Painter := gptBox;
483       end;
484     teToolTip:
485       begin
486         Result.Style := GetStyle(lgsTooltip);
487         Result.Widget := GetStyleWidget(lgsTooltip);
488         Result.State := GTK_STATE_NORMAL;
489         Result.Shadow := GTK_SHADOW_OUT;
490         Result.Detail := 'tooltip';
491         if Details.Part = TTP_STANDARD then
492           Result.Painter := gptFlatBox;
493       end;
494     teTreeview:
495       begin
496         if Details.Part in [TVP_GLYPH, TVP_HOTGLYPH] then
497         begin
498           Result.Painter := gptExpander;
499           Result.Shadow := GTK_SHADOW_NONE;
500           if Details.Part = TVP_GLYPH then
501             Result.State := GTK_STATE_NORMAL
502           else
503             Result.State := GTK_STATE_PRELIGHT;
504           Result.Widget := GetStyleWidget(lgsTreeView);
505           Result.Detail := 'treeview';
506           if Details.State = GLPS_CLOSED then
507             Result.Expander := GTK_EXPANDER_COLLAPSED
508           else
509             Result.Expander := GTK_EXPANDER_EXPANDED;
510 
511           Result.ExpanderSize := GetDetailSize(Details).cx;
512         end
513         else
514         if Details.Part = TVP_TREEITEM then
515         begin
516           Result.Widget := GetStyleWidget(lgsTreeView);
517           Result.Shadow := GTK_SHADOW_NONE;
518           if AIndex = 0 then
519           begin
520             Result.Painter := gptFlatBox;
521             case Details.State of
522               TREIS_SELECTED,
523               TREIS_HOTSELECTED: Result.State := GTK_STATE_SELECTED;
524               TREIS_SELECTEDNOTFOCUS: Result.State := GTK_STATE_SELECTED; //Was: GTK_STATE_ACTIVE;
525               TREIS_HOT: Result.State := GTK_STATE_PRELIGHT;
526               TREIS_DISABLED: Result.State := GTK_STATE_INSENSITIVE;
527             else
528               Result.State := GTK_STATE_NORMAL;
529             end;
530             Result.Detail := 'cell_even';
531           end
532           else
533           if AIndex = 1 then
534           begin
535             Result.Detail := 'treeview';
536             if Details.State = TREIS_SELECTED then
537             begin
538               Result.State := GTK_STATE_SELECTED;
539               Result.Painter := gptFocus
540             end
541             else
542             begin
543               Result.State := GTK_STATE_NORMAL;
544               Result.Painter := gptNone;
545             end;
546           end;
547         end;
548       end;
549   end;
550   if Result.Style = nil then
551     Result.Style := gtk_widget_get_default_style();
552 end;
553 
554 
GetParamsCountnull555 function TGtk2ThemeServices.GetParamsCount(Details: TThemedElementDetails): Integer;
556 begin
557   if (Details.Element = teTreeview) and (Details.Part = TVP_TREEITEM) then
558     Result := 2
559   else
560   begin
561     Result := 1;
562   end;
563 end;
564 
GetDetailSizenull565 function TGtk2ThemeServices.GetDetailSize(Details: TThemedElementDetails): TSize;
566 var
567   AValue: TGValue;
568 begin
569   case Details.Element of
570     teTreeView:
571       if (Byte(Details.Part) in [TVP_GLYPH, TVP_HOTGLYPH]) then
572       begin
573         FillChar(AValue{%H-}, SizeOf(AValue), 0);
574         g_value_init(@AValue, G_TYPE_INT);
575         gtk_widget_style_get_property(GetStyleWidget(lgsTreeView), 'expander-size', @AValue);
576         Result := Size(AValue.data[0].v_int, AValue.data[0].v_int);
577       end else
578         Result := GetBaseDetailsSize(Details);
579     teButton:
580       if (Byte(Details.Part) in [BP_CHECKBOX, BP_RADIOBUTTON]) then
581       begin
582         FillChar(AValue{%H-}, SizeOf(AValue), 0);
583         g_value_init(@AValue, G_TYPE_INT);
584         if Details.Part = BP_CHECKBOX then
585           gtk_widget_style_get_property(GetStyleWidget(lgsCheckbox),'indicator-size', @AValue)
586         else
587           gtk_widget_style_get_property(GetStyleWidget(lgsRadioButton),'indicator-size', @AValue);
588         Result := Size(AValue.data[0].v_int, AValue.data[0].v_int);
589       end else
590         Result := GetBaseDetailsSize(Details);
591     {$IFDEF LINUX} // fix tbsButtonDrop arrow outside button bounds
592     teToolBar:
593       if (Details.Part = TP_DROPDOWNBUTTON) then
594       begin
595         Result.cy := -1;
596         Result.cx := 15;
597       end else
598         Result := GetBaseDetailsSize(Details);
599     {$ENDIF}
600     teHeader:
601       if Details.Part = HP_HEADERSORTARROW then
602         Result := Size(-1, -1) // not yet supported
603       else
604         Result := GetBaseDetailsSize(Details);
605     else
606       Result := GetBaseDetailsSize(Details);
607   end;
608 end;
609 
TGtk2ThemeServices.GetStockImagenull610 function TGtk2ThemeServices.GetStockImage(StockID: LongInt; out Image, Mask: HBitmap): Boolean;
611 var
612   GDIObj: PGDIObject;
613   StockName: PChar;
614   Style: PGtkStyle;
615   IconSet: PGtkIconSet;
616   Pixbuf: PGDKPixbuf;
617 begin
618   case StockID of
619     idButtonOk: StockName := GTK_STOCK_OK;
620     idButtonCancel: StockName := GTK_STOCK_CANCEL;
621     idButtonYes: StockName := GTK_STOCK_YES;
622     idButtonYesToAll: StockName := GTK_STOCK_YES;
623     idButtonNo: StockName := GTK_STOCK_NO;
624     idButtonNoToAll: StockName := GTK_STOCK_NO;
625     idButtonHelp: StockName := GTK_STOCK_HELP;
626     idButtonAbort: StockName := GTK_STOCK_STOP;
627     idButtonClose: StockName := GTK_STOCK_CLOSE;
628     // this is disputable but anyway stock icons looks like our own
629     idButtonAll: StockName := GTK_STOCK_APPLY;
630     idButtonIgnore: StockName := GTK_STOCK_DELETE;
631     idButtonRetry: StockName := GTK_STOCK_REFRESH;
632     idButtonOpen: StockName := GTK_STOCK_OPEN;
633     idButtonSave: StockName := GTK_STOCK_SAVE;
634     idButtonShield: StockName := GTK_STOCK_DIALOG_AUTHENTICATION;
635 
636     idDialogWarning : StockName := GTK_STOCK_DIALOG_WARNING;
637     idDialogError: StockName := GTK_STOCK_DIALOG_ERROR;
638     idDialogInfo: StockName := GTK_STOCK_DIALOG_INFO;
639     idDialogConfirm: StockName := GTK_STOCK_DIALOG_QUESTION;
640     idDialogShield: StockName := GTK_STOCK_DIALOG_AUTHENTICATION;
641   else
642     begin
643        Result := inherited GetStockImage(StockID, Image, Mask);
644        Exit;
645     end;
646   end;
647 
648   if (StockID >= idButtonBase) and (StockID <= idDialogBase) then
649     Style := GetStyle(lgsButton)
650   else
651     Style := GetStyle(lgsWindow);
652 
653   if (Style = nil) or (not GTK_IS_STYLE(Style)) then
654   begin
655     Result := inherited GetStockImage(StockID, Image, Mask);
656     Exit;
657   end;
658 
659   IconSet := gtk_style_lookup_icon_set(Style, StockName);
660 
661   if (IconSet = nil) then
662   begin
663     Result := inherited GetStockImage(StockID, Image, Mask);
664     Exit;
665   end;
666 
667   if (StockID >= idButtonBase) and (StockID <= idDialogBase) then
668     Pixbuf := gtk_icon_set_render_icon(IconSet, Style,
669       GTK_TEXT_DIR_NONE, GTK_STATE_NORMAL, GTK_ICON_SIZE_BUTTON, GetStyleWidget(lgsbutton), nil)
670   else
671     Pixbuf := gtk_icon_set_render_icon(IconSet, Style,
672       GTK_TEXT_DIR_NONE, GTK_STATE_NORMAL, GTK_ICON_SIZE_DIALOG, GetStyleWidget(lgswindow), nil);
673 
674   GDIObj := Gtk2Widgetset.NewGDIObject(gdiBitmap);
675   with GDIObj^ do
676   begin
677     GDIBitmapType := gbPixbuf;
678     visual := gdk_visual_get_system();
679     gdk_visual_ref(visual);
680     colormap := gdk_colormap_get_system();
681     gdk_colormap_ref(colormap);
682     GDIPixbufObject := Pixbuf;
683   end;
684 
685   Image := HBitmap({%H-}PtrUInt(GDIObj));
686   Mask := 0;
687   Result := True;
688 end;
689 
690 procedure ButtonImagesChange({%H-}ASettings: PGtkSettings; {%H-}pspec: PGParamSpec; Services: TGtk2ThemeServices); cdecl;
691 begin
692   Application.IntfThemeOptionChange(Services, toShowButtonImages);
693   Services.IntfDoOnThemeChange;
694 end;
695 
696 procedure MenuImagesChange({%H-}ASettings: PGtkSettings; {%H-}pspec: PGParamSpec; Services: TGtk2ThemeServices); cdecl;
697 begin
698   Application.IntfThemeOptionChange(Services, toShowMenuImages);
699   Services.IntfDoOnThemeChange;
700 end;
701 
GetOptionnull702 function TGtk2ThemeServices.GetOption(AOption: TThemeOption): Integer;
703 var
704   ASettings: PGtkSettings;
705   BoolSetting: gboolean;
706   Widget: PGtkWidget;
707   Signal: guint;
708 begin
709   case AOption of
710     toShowButtonImages:
711       begin
712         Widget := GetStyleWidget(lgsButton);
713         ASettings := gtk_widget_get_settings(Widget);
714         BoolSetting := True; // default
715         g_object_get(ASettings, 'gtk-button-images', @BoolSetting, nil);
716         Result := Ord(BoolSetting = True);
717         if g_object_get_data(PGObject(Widget), 'lcl-images-change-callback') = nil then
718         begin
719           Signal := g_signal_connect(ASettings, 'notify::gtk-button-images', TGCallback(@ButtonImagesChange), Self);
720           g_object_set_data(PGObject(Widget), 'lcl-images-change-callback', {%H-}Pointer(PtrUInt(Signal)))
721         end;
722       end;
723     toShowMenuImages:
724       begin
725         Widget := GetStyleWidget(lgsMenuitem);
726         ASettings := gtk_widget_get_settings(Widget);
727         BoolSetting := False; // default
728         g_object_get(ASettings, 'gtk-menu-images', @BoolSetting, nil);
729         Result := Ord(BoolSetting = True);
730         if g_object_get_data(PGObject(Widget), 'lcl-images-change-callback') = nil then
731         begin
732           Signal := g_signal_connect(ASettings, 'notify::gtk-menu-images', TGCallback(@MenuImagesChange), Self);
733           g_object_set_data(PGObject(Widget), 'lcl-images-change-callback', {%H-}Pointer(PtrUInt(Signal)))
734         end;
735       end;
736   else
737     Result := inherited GetOption(AOption);
738   end;
739 end;
740 
741 procedure TGtk2ThemeServices.GtkDrawElement(DC: HDC;
742   Details: TThemedElementDetails; const R: TRect; ClipRect: PRect);
743 var
744   DevCtx: TGtkDeviceContext absolute DC;
745   Area: TGdkRectangle;
746   ClipArea: TGdkRectangle;
747   StyleParams: TGtkStyleParams;
748   i: integer;
749   RDest: TRect;
750   ComboBoxHeight: gint;
751   ComboBoxWidth: gint;
752 begin
753   if IsRectEmpty(R) then
754     Exit;
755   for i := 0 to GetParamsCount(Details) - 1 do
756   begin
757     StyleParams := GetGtkStyleParams(DC, Details, i);
758     if StyleParams.Style <> nil then
759     begin
760       if DevCtx.HasTransf then
761       begin
762         if ClipRect <> nil then RDest := ClipRect^ else RDest := R;
763         RDest := DevCtx.TransfRectIndirect(R);
764         DevCtx.TransfNormalize(RDest.Left, RDest.Right);
765         DevCtx.TransfNormalize(RDest.Top, RDest.Bottom);
766         Area := GdkRectFromRect(RDest);
767       end else
768         Area := GdkRectFromRect(R);
769 
770       ClipArea := DevCtx.ClipRect;
771 
772       // move to origin
773       inc(Area.x, StyleParams.Origin.x);
774       inc(Area.y, StyleParams.Origin.y);
775 
776       with StyleParams do
777       begin
778         if Painter = gptExpander then
779         begin
780           // Better to draw expander with the ExpanderSize, but sometimes it
781           // will not look very well. The best can we do is to use the same odd/even
782           // amount of pixels => expand/shrink area.width and area.height a bit
783 
784           // Area.width := ExpanderSize;
785           if Odd(Area.width) <> Odd(ExpanderSize) then
786             if Area.width < ExpanderSize then
787               inc(Area.width)
788             else
789               dec(Area.width);
790           // Area.height := ExpanderSize;
791           if Odd(Area.height) <> Odd(ExpanderSize) then
792             if Area.height < ExpanderSize then
793               inc(Area.height)
794             else
795               dec(Area.height);
796         end;
797         if (MaxWidth <> 0) then
798         begin
799           if Area.width > MaxWidth then
800           begin
801             inc(Area.x, (Area.width - MaxWidth) div 2);
802             Area.width := MaxWidth;
803           end;
804         end;
805         case Painter of
806           gptDefault: inherited DrawElement(DC, Details, R, ClipRect);
807 
808           gptComboBox:
809             begin
810               {this is hack to paint combobox under gtk2}
811               if Details.State = CBXS_PRESSED then
812                 gtk_paint_focus(Style, Window, GTK_STATE_ACTIVE, @ClipArea, Widget, 'button', Area.X + 2, Area.y + 2, Area.Width - 4, Area.Height - 4);
813 
814               if not (Byte(Details.Part) in [CP_DROPDOWNBUTTON, CP_DROPDOWNBUTTONRIGHT, CP_DROPDOWNBUTTONLEFT]) then
815               begin
816                 gtk_paint_box(
817                   Style, Window,
818                   State, Shadow,
819                   @ClipArea, Widget, PChar(Detail),
820                   Area.x, Area.y,
821                   Area.Width, Area.Height);
822 
823                   // now we draw box with arrows
824                   RDest := RectFromGdkRect(Area);
825                   if Area.width > 17 then
826                     RDest.Left := RDest.Right - 16
827                   else
828                     RDest.Left := RDest.Right - (Area.Width div 2);
829                   ComboBoxHeight := Area.Height;
830                   ComboBoxWidth := Area.Width;
831                   if RDest.Left < 0 then
832                     RDest.Left := 0;
833 
834                 gtk_paint_vline(Style, Window, State, @ClipArea, Widget,'', Area.y + (ComboBoxHeight div 10), Area.Y + Area.Height - (ComboBoxHeight div 10), (RDest.Right - Min(23, (ComboBoxWidth div 2) + 1)) + 1);
835               end;
836 
837               if Byte(Details.Part) in [CP_DROPDOWNBUTTON, CP_DROPDOWNBUTTONRIGHT, CP_DROPDOWNBUTTONLEFT] then
838               begin
839                 RDest := RectFromGdkRect(Area);
840                 ComboBoxHeight := (RDest.Right - Min(23, (ComboBoxWidth div 2) + 1)) + 2;
841                 if RDest.Right - ComboBoxHeight < 8 then
842                   ComboBoxHeight := Area.X + (Area.Width div 4);
843                 gtk_paint_arrow(Style, Window, State, Shadow, @ClipArea, Widget,
844                   PChar(Detail), GTK_ARROW_UP, True, RDest.Left + ((RDest.Right - RDest.Left) div 4), RDest.Top + ((RDest.Bottom - RDest.Top) div 2) - 5, Min(8, RDest.Right - RDest.Left), Min(8, RDest.Bottom - RDest.Top));
845                 gtk_paint_arrow(Style, Window, State, Shadow, @ClipArea, Widget,
846                   PChar(Detail), GTK_ARROW_DOWN, True, RDest.Left + ((RDest.Right - RDest.Left) div 4), RDest.Top + ((RDest.Bottom - RDest.Top) div 2) + 1, Min(8, RDest.Right - RDest.Left), Min(8, RDest.Bottom - RDest.Top));
847               end;
848             end;
849           gptBox:
850             gtk_paint_box(
851               Style, Window,
852               State, Shadow,
853               @ClipArea, Widget, PChar(Detail),
854               Area.x, Area.y,
855               Area.Width, Area.Height);
856           gptBoxGap:
857             gtk_paint_box_gap(
858               Style, Window,
859               State, Shadow,
860               @ClipArea, Widget, PChar(Detail),
861               Area.x, Area.y,
862               Area.Width, Area.Height,
863               GapSide, GapX, GapWidth);
864           gptHLine  : gtk_paint_hline(
865               Style, Window,
866               State, @ClipArea,
867               Widget, PChar(Detail),
868               Area.x, Area.x + Area.Width, Area.y);
869           gptVLine  : gtk_paint_vline(
870               Style, Window,
871               State, @ClipArea,
872               Widget, PChar(Detail),
873               Area.y, Area.y + Area.Height, Area.x);
874           gptShadow : gtk_paint_shadow(
875               Style, Window,
876               State, Shadow,
877               @ClipArea, Widget, PChar(Detail),
878               Area.x, Area.y,
879               Area.Width, Area.Height);
880           gptFlatBox: gtk_paint_flat_box(
881               Style, Window,
882               State, Shadow,
883               @ClipArea, Widget, PChar(Detail),
884               Area.x, Area.y,
885               Area.Width, Area.Height);
886           gptCheck  : gtk_paint_check(
887               Style, Window,
888               State, Shadow,
889               @ClipArea, Widget, PChar(Detail),
890               Area.x, Area.y,
891               Area.Width, Area.Height);
892           gptOption : gtk_paint_option(
893               Style, Window,
894               State, Shadow,
895               @ClipArea, Widget, PChar(Detail),
896               Area.x, Area.y,
897               Area.Width, Area.Height);
898           gptTab    : gtk_paint_tab(
899               Style, Window,
900               State, Shadow,
901               @ClipArea, Widget, PChar(Detail),
902               Area.x, Area.y,
903               Area.Width, Area.Height);
904           gptSlider : gtk_paint_slider(
905               Style, Window,
906               State, Shadow,
907               @ClipArea, Widget, PChar(Detail),
908               Area.x, Area.y,
909               Area.Width, Area.Height,
910               Orientation);
911           gptHandle : gtk_paint_handle(
912               Style, Window,
913               State, Shadow,
914               @ClipArea, Widget, PChar(Detail),
915               Area.x, Area.y,
916               Area.Width, Area.Height,
917               Orientation);
918 
919           gptExpander: gtk_paint_expander(
920               Style, Window, State,
921               @ClipArea, Widget, PChar(Detail),
922               Area.x + Area.width shr 1, Area.y + Area.height shr 1,
923               Expander);
924           gptResizeGrip: gtk_paint_resize_grip(
925               Style, Window, State,
926               @ClipArea, Widget,
927               PChar(Detail), Edge,
928               Area.x, Area.y,
929               Area.Width, Area.Height);
930 
931           gptFocus : gtk_paint_focus(
932               Style, Window, State,
933               @ClipArea, Widget, PChar(Detail),
934               Area.x, Area.y,
935               Area.Width, Area.Height);
936           gptArrow: gtk_paint_arrow(
937              Style, Window,
938              State, Shadow,
939              @ClipArea, Widget, PChar(Detail),
940              ArrowType, Fill,
941              Area.x, Area.y, Area.width, Area.height
942            );
943           gptPixmap: DrawPixmap(DC, @Area, Ord(Detail[1]));
944         end;
945       end;
946     end;
947   end;
948 end;
949 
950 procedure TGtk2ThemeServices.DrawElement(DC: HDC;
951   Details: TThemedElementDetails; const R: TRect; ClipRect: PRect);
952 var
953   Widget: PGtkWidget;
954 begin
955   if (Details.Element = teTreeview) and (Details.Part = TVP_TREEITEM) and
956      (Details.State = TREIS_SELECTED) then
957   begin
958     // lie to cleanlooks theme
959     Widget := GetStyleWidget(lgsTreeView);
960     GTK_WIDGET_SET_FLAGS(Widget, GTK_HAS_FOCUS);
961     GtkDrawElement(DC, Details, R, ClipRect);
962     GTK_WIDGET_UNSET_FLAGS(Widget, GTK_HAS_FOCUS);
963   end
964   else
965     GtkDrawElement(DC, Details, R, ClipRect);
966 end;
967 
968 procedure TGtk2ThemeServices.DrawText(ACanvas: TPersistent;
969   Details: TThemedElementDetails; const S: String; R: TRect; Flags,
970   Flags2: Cardinal);
971 begin
972   if ThemesEnabled then
973     DrawText(TCanvas(ACanvas).Handle, Details, S, R, Flags, Flags2)
974   else
975     inherited;
976 end;
977 
978 procedure TGtk2ThemeServices.DrawText(DC: HDC; Details: TThemedElementDetails;
979   const S: String; R: TRect; Flags, Flags2: Cardinal);
980 var
981   StyleParams: TGtkStyleParams;
982   P: PChar;
983   tmpRect: TRect;
984 begin
985   StyleParams := GetGtkStyleParams(DC, Details, 0);
986   if StyleParams.Style <> nil then
987     with StyleParams do
988     begin
989       P := PChar(S);
990       tmpRect := R;
991       Gtk2Widgetset.DrawText(DC, P, Length(S), tmpRect, Flags);
992       // TODO: parse flags
993       //gtk_draw_string(Style, Window, State, R.Left + Origin.x, R.Top + Origin.y, P);
994     end;
995 end;
996 
ContentRectnull997 function TGtk2ThemeServices.ContentRect(DC: HDC;
998   Details: TThemedElementDetails; BoundingRect: TRect): TRect;
999 var
1000   StyleParams: TGtkStyleParams;
1001 begin
1002   Result := BoundingRect;
1003   StyleParams := GetGtkStyleParams(DC, Details, 0);
1004   if StyleParams.Style <> nil then
1005     InflateRect(Result,
1006       -StyleParams.Style^.xthickness,
1007       -StyleParams.Style^.ythickness);
1008 end;
1009 
HasTransparentPartsnull1010 function TGtk2ThemeServices.HasTransparentParts(Details: TThemedElementDetails): Boolean;
1011 begin
1012   Result := True; // ?
1013 end;
1014 
1015 end.
1016 
1017