lazarus/gtk/gtkthemes.pas
marc 722563eac0 Start of gtk1-gtk2 separation
git-svn-id: branches/gtk-splitup@25851 -
2010-06-02 22:46:14 +00:00

697 lines
22 KiB
ObjectPascal

unit GtkThemes;
{$mode objfpc}{$H+}
interface
uses
// rtl
Types, Classes, SysUtils,
// os
{$ifdef gtk1}
glib, gdk, gtk,
{$else}
glib2, gdk2, gtk2, Pango,
{$endif}
// lcl
InterfaceBase, LCLType, LCLProc, LCLIntf, Graphics, Themes, TmSchema,
// widgetset
gtkdef, gtkint, gtkproc, gtkglobals, gtkextra;
type
TGtkPainterType =
(
gptNone,
gptDefault,
gptHLine,
gptVLine,
gptShadow,
gptBox,
gptBoxGap,
gptFlatBox,
gptCheck,
gptOption,
gptTab,
gptSlider,
gptHandle,
gptExpander,
gptResizeGrip,
gptFocus,
gptArrow,
gptPixmap
);
TGtkStyleParams = record
Style : PGtkStyle; // paint style
Painter : TGtkPainterType; // type of paint handler
Widget : PGtkWidget; // widget
Window : PGdkWindow; // paint window
Origin : TPoint; // offset
State : TGtkStateType; // Style state
Shadow : TGtkShadowType; // Shadow
Detail : String; // Detail (button, checkbox, ...)
Orientation: TGtkOrientation; // Orientation (horizontal/vertical)
ArrowType : TGtkArrowType; // type of arrow
Fill : Boolean; // fill inside area
GapSide : TGtkPositionType;//
GapX : gint;
GapWidth : gint;
MaxWidth : gint; // max area width
{$ifdef gtk2}
Expander : TGtkExpanderStyle; // treeview expander
ExpanderSize: Integer;
Edge : TGdkWindowEdge;
{$endif}
IsHot : Boolean;
end;
{ TGtk1ThemeServices }
{ TGtkThemeServices }
TGtkThemeServices = class(TThemeServices)
private
protected
procedure DrawPixmap(DC: HDC; Area: PGdkRectangle; PixmapIndex: Byte); virtual;
function GetParamsCount(Details: TThemedElementDetails): Integer; virtual;
function GetGtkStyleParams(DC: HDC; Details: TThemedElementDetails; AIndex: Integer): TGtkStyleParams; virtual;
function InitThemes: Boolean; override;
function UseThemes: Boolean; override;
function ThemedControlsEnabled: Boolean; override;
procedure InternalDrawParentBackground(Window: HWND; Target: HDC; Bounds: PRect); override;
function GetBaseDetailsSize(Details: TThemedElementDetails): TSize;
public
function GetDetailSize(Details: TThemedElementDetails): TSize; override;
procedure DrawElement(DC: HDC; Details: TThemedElementDetails; const R: TRect; ClipRect: PRect); override;
procedure DrawIcon(DC: HDC; Details: TThemedElementDetails; const R: TRect; himl: HIMAGELIST; Index: Integer); override;
procedure DrawText(DC: HDC; Details: TThemedElementDetails; const S: String; R: TRect; Flags, Flags2: Cardinal); override;
function ContentRect(DC: HDC; Details: TThemedElementDetails; BoundingRect: TRect): TRect; override;
function HasTransparentParts(Details: TThemedElementDetails): Boolean; override;
end;
const
// most common maps
GtkButtonMap: array[0..6] of TGtkStateType =
(
{ filter ? } GTK_STATE_NORMAL,
{ normal } GTK_STATE_NORMAL,
{ hot } GTK_STATE_PRELIGHT,
{ pressed } GTK_STATE_ACTIVE,
{ disabled } GTK_STATE_INSENSITIVE,
{ defaulted/checked } GTK_STATE_ACTIVE,
{ hot + checked } GTK_STATE_ACTIVE
);
GtkRadioCheckBoxMap: array[0..12] of TGtkStateType =
(
{ Filler } GTK_STATE_NORMAL,
{ UNCHECKEDNORMAL } GTK_STATE_NORMAL,
{ UNCHECKEDHOT } GTK_STATE_PRELIGHT,
{ UNCHECKEDPRESSED } GTK_STATE_ACTIVE,
{ UNCHECKEDDISABLED } GTK_STATE_INSENSITIVE,
{ CHECKEDNORMAL } GTK_STATE_NORMAL,
{ CHECKEDHOT } GTK_STATE_PRELIGHT,
{ CHECKEDPRESSED } GTK_STATE_ACTIVE,
{ CHECKEDDISABLED } GTK_STATE_INSENSITIVE,
{ MIXEDNORMAL } GTK_STATE_NORMAL,
{ MIXEDHOT } GTK_STATE_PRELIGHT,
{ MIXEDPRESSED } GTK_STATE_ACTIVE,
{ MIXEDDISABLED } GTK_STATE_INSENSITIVE
);
GtkTitleButtonMap: array[0..5] of TGtkStateType =
(
{ filter ? } GTK_STATE_NORMAL,
{ normal } GTK_STATE_NORMAL,
{ hot } GTK_STATE_PRELIGHT,
{ pressed } GTK_STATE_ACTIVE,
{ disabled } GTK_STATE_INSENSITIVE,
{ inactive } GTK_STATE_INSENSITIVE
);
implementation
{$I gtkstdpixmaps.inc}
{ TGtkThemeServices }
procedure TGtkThemeServices.DrawPixmap(DC: HDC; Area: PGdkRectangle; PixmapIndex: Byte);
var
APixmap, APixmapMask: PGdkPixmap;
DevCtx: TGtkDeviceContext absolute DC;
w, h: gint;
begin
if (PixmapIndex >= Low(PixmapArray)) and (PixmapIndex <= High(PixmapArray)) then
begin
APixmapMask := nil;
APixmap := gdk_pixmap_create_from_xpm_d(DevCtx.Drawable,
APixmapMask, nil, PixmapArray[PixmapIndex]);
if APixmap <> nil then
begin
gdk_drawable_get_size(APixmap, @w, @h);
w := (Area^.Width - w) div 2;
if w < 0 then
w := 0;
h := (Area^.Height - h) div 2;
if h < 0 then
h := 0;
if APixmapMask <> nil then
begin
gdk_gc_set_clip_mask(DevCtx.GC, APixmapMask);
gdk_gc_set_clip_origin(DevCtx.GC, Area^.x + w, Area^.y + h);
end;
gdk_draw_pixmap(DevCtx.Drawable, DevCtx.GC, APixmap, 0, 0, Area^.x + w, Area^.y + h,
Area^.Width, Area^.Height);
if APixmapMask <> nil then
DevCtx.ResetGCClipping;
gdk_pixmap_unref(APixmap);
end;
if APixmapMask <> nil then
gdk_pixmap_unref(APixmapMask);
end;
end;
function TGtkThemeServices.GetParamsCount(Details: TThemedElementDetails): Integer;
begin
Result := 1;
if (Details.Element = teToolBar) and (Details.Part = TP_SPLITBUTTONDROPDOWN) then
inc(Result); // + Arrow
end;
function TGtkThemeServices.GetGtkStyleParams(DC: HDC;
Details: TThemedElementDetails; AIndex: Integer): TGtkStyleParams;
var
DevCtx: TGtkDeviceContext absolute DC;
ClientWidget: PGtkWidget;
begin
FillByte(Result, SizeOf(Result), 0);
if not GTKWidgetSet.IsValidDC(DC) then Exit;
Result.Widget := DevCtx.Widget;
if Result.Widget <> nil then
begin
ClientWidget := GetFixedWidget(Result.Widget);
if ClientWidget <> nil then
Result.Widget := ClientWidget;
Result.Style := gtk_widget_get_style(Result.Widget);
end;
Result.Window := DevCtx.Drawable;
Result.Origin := DevCtx.Offset;
Result.Painter := gptDefault;
Result.State := GTK_STATE_NORMAL;
Result.Detail := '';
Result.Shadow := GTK_SHADOW_NONE;
Result.ArrowType := GTK_ARROW_UP;
Result.Fill := False;
Result.IsHot := False;
Result.GapSide := GTK_POS_LEFT;
Result.GapWidth := 0;
Result.GapX := 0;
Result.MaxWidth := 0;
case Details.Element of
teButton:
begin
case Details.Part of
BP_PUSHBUTTON:
begin
Result.Widget := GetStyleWidget(lgsButton);
if Result.Style = nil then
Result.Style := GetStyle(lgsButton);
Result.State := GtkButtonMap[Details.State];
if Details.State = PBS_PRESSED then
Result.Shadow := GTK_SHADOW_IN
else
Result.Shadow := GTK_SHADOW_OUT;
Result.IsHot:= Result.State = GTK_STATE_PRELIGHT;
Result.Detail := 'button';
Result.Painter := gptBox;
end;
BP_RADIOBUTTON:
begin
Result.Widget := GetStyleWidget(lgsRadiobutton);
if Result.Style = nil then
Result.Style := GetStyle(lgsRadiobutton);
Result.State := GtkRadioCheckBoxMap[Details.State];
if Details.State >= RBS_CHECKEDNORMAL then
Result.Shadow := GTK_SHADOW_IN
else
Result.Shadow := GTK_SHADOW_OUT;
Result.Detail := 'radiobutton';
Result.Painter := gptOption;
end;
BP_CHECKBOX:
begin
Result.Widget := GetStyleWidget(lgsCheckbox);
if Result.Style = nil then
Result.Style := GetStyle(lgsCheckbox);
Result.State := GtkRadioCheckBoxMap[Details.State];
Result.Detail := 'checkbutton';
if Details.State >= CBS_MIXEDNORMAL then
result.Shadow := GTK_SHADOW_ETCHED_IN
else
if Details.State >= CBS_CHECKEDNORMAL then
Result.Shadow := GTK_SHADOW_IN
else
Result.Shadow := GTK_SHADOW_OUT;
Result.Painter := gptCheck;
end;
end;
end;
teHeader:
begin
Result.Widget := GetStyleWidget(lgsButton);
if Result.Style = nil then
Result.Style := GetStyle(lgsButton);
Result.State := GtkButtonMap[Details.State];
if Details.State = PBS_PRESSED then
Result.Shadow := GTK_SHADOW_IN
else
Result.Shadow := GTK_SHADOW_OUT;
Result.IsHot := Result.State = GTK_STATE_PRELIGHT;
Result.Detail := 'button';
Result.Painter := gptBox;
end;
teStatus:
begin
Result.Widget := GetStyleWidget(lgsStatusBar);
if Result.Style = nil then
Result.Style := GetStyle(lgsStatusBar);
Result.Detail := 'statubar';
Result.State := GTK_STATE_NORMAL;
case Details.Part of
SP_PANE:
begin
Result.Painter := gptShadow;
Result.Shadow := GTK_SHADOW_OUT;
end;
SP_GRIPPER:
begin
Result.Painter := gptResizeGrip;
{$IFDEF Gtk2}
Result.Edge := GDK_WINDOW_EDGE_SOUTH_EAST;
{$ELSE}
Result.Shadow := GTK_SHADOW_IN;
{$ENDIF}
end;
end;
end;
teToolBar:
begin
case Details.Part of
TP_BUTTON,
TP_DROPDOWNBUTTON,
TP_SPLITBUTTON,
TP_SPLITBUTTONDROPDOWN:
begin
if (Details.Part = TP_SPLITBUTTONDROPDOWN) and (AIndex = 1) then
begin
Result.Detail := 'arrow';
Result.ArrowType := GTK_ARROW_DOWN;
Result.Fill := True;
Result.Painter := gptArrow;
Result.MaxWidth := 10;
end
else
begin
Result.Widget := GetStyleWidget(lgsToolButton);
if Result.Style = nil then
Result.Style := GetStyle(lgsToolButton);
Result.State := GtkButtonMap[Details.State];
if Details.State in [TS_PRESSED, TS_CHECKED, TS_HOTCHECKED] then
Result.Shadow := GTK_SHADOW_IN
else
if Details.State in [TS_HOT] then
Result.Shadow := GTK_SHADOW_ETCHED_IN
else
Result.Shadow := GTK_SHADOW_NONE;
Result.IsHot := Details.State in [TS_HOT, TS_HOTCHECKED];
Result.Detail := 'button';
if Result.Shadow = GTK_SHADOW_NONE then
Result.Painter := gptNone
else
Result.Painter := gptBox;
end;
end;
TP_SEPARATOR,
TP_SEPARATORVERT:
begin
Result.State := GTK_STATE_NORMAL;
Result.Shadow := GTK_SHADOW_NONE;
Result.Detail := 'toolbar';
if Details.Part = TP_SEPARATOR then
Result.Painter := gptVLine
else
Result.Painter := gptHLine;
end;
end;
end;
teRebar:
begin
case Details.Part of
RP_GRIPPER, RP_GRIPPERVERT:
begin
Result.State := GTK_STATE_NORMAL;
Result.Shadow := GTK_SHADOW_NONE;
{ This code has problems with some (is not most) of gtk1 themes.
But at least Ubuntu >= 6.10 works fine. So it is commented out and switched
to alternate splitter painting}
if Details.Part = RP_GRIPPER then
begin
Result.Detail := 'hpaned';
Result.Widget := GetStyleWidget(lgsHorizontalPaned);
if Result.Style = nil then
Result.Style := GetStyle(lgsHorizontalPaned);
end
else
begin
Result.Detail := 'vpaned';
Result.Widget := GetStyleWidget(lgsVerticalPaned);
if Result.Style = nil then
Result.Style := GetStyle(lgsVerticalPaned);
end;
Result.Painter := gptBox;
{ Result.Detail := 'paned';
Result.Painter := gptHandle;
if Details.Part = RP_GRIPPER then
Result.Orientation := GTK_ORIENTATION_VERTICAL
else
Result.Orientation := GTK_ORIENTATION_HORIZONTAL;}
end;
RP_BAND:
begin
Result.State := GtkButtonMap[Details.State];
Result.Shadow := GTK_SHADOW_NONE;
Result.Detail := 'paned';
Result.Painter := gptFlatBox;
end;
end;
end;
teWindow:
begin
if Details.Part in [WP_MDIMINBUTTON, WP_MDIRESTOREBUTTON, WP_MDICLOSEBUTTON] then
begin
Result.State := GtkTitleButtonMap[Details.State];
Result.Shadow := GTK_SHADOW_NONE;
case Details.Part of
WP_MDIMINBUTTON: Result.Detail := #1;
WP_MDIRESTOREBUTTON: Result.Detail := #2;
WP_MDICLOSEBUTTON: Result.Detail := #3;
end;
Result.Painter := gptPixmap;
end;
end;
teTab:
begin
Result.Widget := GetStyleWidget(lgsNotebook);
if Result.Style = nil then
Result.Style := GetStyle(lgsNotebook);
Result.State := GTK_STATE_NORMAL;
Result.Shadow := GTK_SHADOW_OUT;
Result.Detail := 'notebook';
if Details.Part in [TABP_PANE, TABP_BODY] then
begin
{Result.GapSide := GTK_POS_TOP;
Result.GapX := 20;
Result.GapWidth := 40;}
Result.Painter := gptBox;
end;
end;
teToolTip:
begin
Result.Style := GetStyle(lgsTooltip);
Result.Widget := GetStyleWidget(lgsTooltip);
Result.State := GTK_STATE_NORMAL;
Result.Shadow := GTK_SHADOW_OUT;
Result.Detail := 'tooltip';
if Details.Part = TTP_STANDARD then
Result.Painter := gptFlatBox;
end;
end;
if Result.Style = nil then
Result.Style := gtk_widget_get_default_style();
end;
function TGtkThemeServices.InitThemes: Boolean;
begin
Result := True;
end;
function TGtkThemeServices.UseThemes: Boolean;
begin
Result := True;
end;
function TGtkThemeServices.ThemedControlsEnabled: Boolean;
begin
Result := True;
end;
function TGtkThemeServices.ContentRect(DC: HDC;
Details: TThemedElementDetails; BoundingRect: TRect): TRect;
var
StyleParams: TGtkStyleParams;
begin
Result := BoundingRect;
StyleParams := GetGtkStyleParams(DC, Details, 0);
if StyleParams.Style <> nil then
InflateRect(Result,
-StyleParams.Style^.{$ifndef gtk2}klass^.{$endif}xthickness,
-StyleParams.Style^.{$ifndef gtk2}klass^.{$endif}ythickness);
end;
procedure TGtkThemeServices.DrawElement(DC: HDC;
Details: TThemedElementDetails; const R: TRect; ClipRect: PRect);
var
DevCtx: TGtkDeviceContext absolute DC;
Area: TGdkRectangle;
StyleParams: TGtkStyleParams;
i: integer;
RDest: TRect;
begin
if IsRectEmpty(R) then
Exit;
for i := 0 to GetParamsCount(Details) - 1 do
begin
StyleParams := GetGtkStyleParams(DC, Details, i);
if StyleParams.Style <> nil then
begin
if DevCtx.HasTransf then
begin
if ClipRect <> nil then RDest := ClipRect^ else RDest := R;
RDest := DevCtx.TransfRectIndirect(R);
DevCtx.TransfNormalize(RDest.Left, RDest.Right);
DevCtx.TransfNormalize(RDest.Top, RDest.Bottom);
Area := GdkRectFromRect(RDest);
end
else if ClipRect <> nil then
Area := GdkRectFromRect(ClipRect^)
else
Area := GdkRectFromRect(R);
// move to origin
inc(Area.x, StyleParams.Origin.x);
inc(Area.y, StyleParams.Origin.y);
with StyleParams do
begin
{$ifndef gtk1}
if Painter = gptExpander then
begin
// Better to draw expander with the ExpanderSize, but sometimes it
// will not look very well. The best can we do is to use the same odd/even
// amount of pixels => expand/shrink area.width and area.height a bit
// Area.width := ExpanderSize;
if Odd(Area.width) <> Odd(ExpanderSize) then
if Area.width < ExpanderSize then
inc(Area.width)
else
dec(Area.width);
// Area.height := ExpanderSize;
if Odd(Area.height) <> Odd(ExpanderSize) then
if Area.height < ExpanderSize then
inc(Area.height)
else
dec(Area.height);
end;
{$endif}
if (MaxWidth <> 0) then
begin
if Area.width > MaxWidth then
begin
inc(Area.x, (Area.width - MaxWidth) div 2);
Area.width := MaxWidth;
end;
end;
case Painter of
gptDefault: inherited DrawElement(DC, Details, R, ClipRect);
gptBox:
gtk_paint_box(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptBoxGap:
gtk_paint_box_gap(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height,
GapSide, GapX, GapWidth);
gptHLine : gtk_paint_hline(
Style, Window,
State, @Area,
Widget, PChar(Detail),
Area.x, Area.x + Area.Width, Area.y);
gptVLine : gtk_paint_vline(
Style, Window,
State, @Area,
Widget, PChar(Detail),
Area.y, Area.y + Area.Height, Area.x);
gptShadow : gtk_paint_shadow(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptFlatBox: gtk_paint_flat_box(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptCheck : gtk_paint_check(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptOption : gtk_paint_option(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptTab : gtk_paint_tab(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptSlider : gtk_paint_slider(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height,
Orientation);
gptHandle : gtk_paint_handle(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height,
Orientation);
{$ifdef gtk2}
gptExpander: gtk_paint_expander(
Style, Window, State,
@Area, Widget, PChar(Detail),
Area.x + Area.width shr 1, Area.y + Area.height shr 1,
Expander);
gptResizeGrip: gtk_paint_resize_grip(
Style, Window, State,
@Area, Widget,
PChar(Detail), Edge,
Area.x, Area.y,
Area.Width, Area.Height);
{$endif}
gptFocus : gtk_paint_focus(
Style, Window, {$ifdef gtk2}State,{$endif}
@Area, Widget, PChar(Detail),
Area.x, Area.y,
Area.Width, Area.Height);
gptArrow: gtk_paint_arrow(
Style, Window,
State, Shadow,
@Area, Widget, PChar(Detail),
ArrowType, Fill,
Area.x, Area.y, Area.width, Area.height
);
gptPixmap: DrawPixmap(DC, @Area, Ord(Detail[1]));
end;
end;
end;
end;
end;
procedure TGtkThemeServices.DrawIcon(DC: HDC;
Details: TThemedElementDetails; const R: TRect; himl: HIMAGELIST;
Index: Integer);
begin
end;
function TGtkThemeServices.HasTransparentParts(Details: TThemedElementDetails): Boolean;
begin
Result := True; // ?
end;
procedure TGtkThemeServices.InternalDrawParentBackground(Window: HWND;
Target: HDC; Bounds: PRect);
begin
// ?
end;
function TGtkThemeServices.GetBaseDetailsSize(Details: TThemedElementDetails): TSize;
begin
Result := inherited GetDetailSize(Details);
end;
function TGtkThemeServices.GetDetailSize(Details: TThemedElementDetails): TSize;
begin
case Details.Element of
teRebar :
if Details.Part in [RP_GRIPPER, RP_GRIPPERVERT] then
Result := Size(-1, -1);
else
Result := GetBaseDetailsSize(Details);
end;
end;
procedure TGtkThemeServices.DrawText(DC: HDC; Details: TThemedElementDetails;
const S: String; R: TRect; Flags, Flags2: Cardinal);
var
StyleParams: TGtkStyleParams;
P: PChar;
tmpRect: TRect;
begin
StyleParams := GetGtkStyleParams(DC, Details, 0);
if StyleParams.Style <> nil then
with StyleParams do
begin
P := PChar(S);
tmpRect := R;
Widgetset.DrawText(DC, P, Length(S), tmpRect, Flags);
// TODO: parse flags
//gtk_draw_string(Style, Window, State, R.Left + Origin.x, R.Top + Origin.y, P);
end;
end;
end.