{******************************************************************************}
{                       CnPack For Delphi/C++Builder                           }
{                     中国人自己的开放源码第三方开发包                         }
{                   (C)Copyright 2001-2016 CnPack 开发组                       }
{                   ------------------------------------                       }
{                                                                              }
{            本开发包是开源的自由软件，您可以遵照 CnPack 的发布协议来修        }
{        改和重新发布这一程序。                                                }
{                                                                              }
{            发布这一开发包的目的是希望它有用，但没有任何担保。甚至没有        }
{        适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。        }
{                                                                              }
{            您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果        }
{        还没有，可访问我们的网站：                                            }
{                                                                              }
{            网站地址：http://www.cnpack.org                                   }
{            电子邮件：master@cnpack.org                                       }
{                                                                              }
{******************************************************************************}

unit CnSkinMenu;

interface

uses
  Windows, SysUtils, Classes, Menus, Forms, Graphics, ImgList,
  CnSkinTheme, CnCommon;

type
  TCnSkinMenu = class(TComponent)
  private
    FMaxWidth: Integer;
    FColor: TColor;
    FHighLightColor: TColor;
    FForm: TForm;
    FSaveFormCreate: TNotifyEvent;
    procedure FormCreate(Sender: TObject);
    function GetImageList(MenuItem: TMenuItem): TCustomImageList;
    procedure AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
      ARect: TRect; State: TOwnerDrawState);
    procedure MeasureMenuItem(Sender: TObject; ACanvas: TCanvas;
      var Width, Height: Integer);
  protected
    procedure DrawBorder(Handle: HWND);
    procedure DoDrawText(MenuItem: TMenuItem; ACanvas: TCanvas;
      const ACaption: string; var Rect: TRect; Selected: Boolean; Flags: Longint);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure InstallMenuSkin;
    procedure UnInstallMenuSkin;
  published
    property Color: TColor read FColor write FColor;
    property HighLightColor: TColor read FHighLightColor write FHighLightColor;
    property MaxWidth: Integer read FMaxWidth write FMaxWidth;
  end;

implementation

var
  CnSkinMenus: TList;

constructor TCnSkinMenu.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FColor := clMenu;
  FHighLightColor := clMenuHighLight;
  FMaxWidth := -1;
  CnSkinMenus.Add(Self);
  FForm := AOwner as TForm;
  if not (csDesigning in FForm.ComponentState) then
  begin
    if Assigned(FForm.OnCreate) then
      FSaveFormCreate := FForm.OnCreate;
    FForm.OnCreate := FormCreate;
  end;
end;

destructor TCnSkinMenu.Destroy;
begin
  CnSkinMenus.Remove(Self);
  inherited Destroy;
end;

procedure TCnSkinMenu.FormCreate(Sender: TObject);
begin
  InstallMenuSkin;
  if Assigned(FSaveFormCreate) then
    FSaveFormCreate(Sender);
end;

function TCnSkinMenu.GetImageList(MenuItem: TMenuItem): TCustomImageList;
var
  LItem: TMenuItem;
  LMenu: TMenu;
begin
  Result := nil;
  LItem := MenuItem.Parent;
  while (LItem <> nil) and (LItem.SubMenuImages = nil) do
    LItem := LItem.Parent;
  if LItem <> nil then
    Result := LItem.SubMenuImages
  else
  begin
    LMenu := MenuItem.GetParentMenu;
    if LMenu <> nil then
      Result := LMenu.Images;
  end;
end;

procedure TCnSkinMenu.DrawBorder(Handle: HWND);
var
  Canvas: TCanvas;
  RC, RW: TRect;
  X, Y: Integer;
begin
  Canvas := TCanvas.Create;
  Canvas.Handle := GetWindowDC(Handle);
  try
    GetClientRect(Handle, RC);
    GetWindowRect(Handle, RW);
    OffsetRect(RW, -RW.Left, -RW.Top);
    X := (RW.Right - RC.Right) div 2;
    Y := (RW.Bottom - RC.Bottom) div 2;
    ExcludeClipRect(Canvas.Handle, X, Y, RW.Right - X, RW.Bottom - Y);
    Canvas.Pen.Color := CnSkinThemes.CurrentSkin.ShadowColor;
    Canvas.Brush.Color := FColor;
    Canvas.Rectangle(RW);
  finally
    ReleaseDC(Handle, Canvas.Handle);
    Canvas.Free;
  end;
end;

procedure TCnSkinMenu.DoDrawText(MenuItem: TMenuItem; ACanvas: TCanvas;
  const ACaption: string; var Rect: TRect; Selected: Boolean; Flags: Longint);
begin
  with ACanvas do
  begin
    Brush.Style := bsClear;
    if MenuItem.Default then
      Font.Style := Font.Style + [fsBold];
    if not MenuItem.Enabled then
    begin
      OffsetRect(Rect, 1, 1);
      Font.Color := CnSkinThemes.CurrentSkin.LightColor;
      DrawText(Handle, PChar(ACaption), -1, Rect, Flags);
      OffsetRect(Rect, -1, -1);
      Font.Color := CnSkinThemes.CurrentSkin.ShadowColor;
    end
    else if Selected then
      Font.Color := CnSkinThemes.CurrentSkin.LightColor;
    DrawText(Handle, PChar(ACaption), -1, Rect, Flags);
  end;
end;

procedure TCnSkinMenu.AdvancedDrawMenuItem(Sender: TObject; ACanvas: TCanvas;
  ARect: TRect; State: TOwnerDrawState);
var
  MenuItem: TMenuItem;
  Y: Integer;
  Image: TCustomImageList;
  Selected: Boolean;
  Text: string;

  procedure DrawTopLevelMenuItem;
  begin
    Selected := Selected or (odHotLight in State);
    if Selected then
    begin
      ACanvas.Brush.Color := FHighLightColor;
      ACanvas.Pen.Color := CnSkinThemes.CurrentSkin.ShadowColor;
    end else
    begin
      ACanvas.Brush.Color := clMenuBar;
      ACanvas.Pen.Color := clMenuBar;
    end;
    ACanvas.Rectangle(ARect);
    DoDrawText(MenuItem, ACanvas, MenuItem.Caption, ARect, Selected,
      DT_CENTER or DT_VCENTER or DT_SINGLELINE);
  end;
begin
  MenuItem := Sender as TMenuItem;
  Image := GetImageList(MenuItem);
  Selected := odSelected in State;
  if MenuItem.GetParentComponent is TMainMenu then
    DrawTopLevelMenuItem
  else
  begin
    DrawBorder(WindowFromDC(ACanvas.Handle));
    with ACanvas do
    begin
      Pen.Color := CnSkinThemes.CurrentSkin.ShadowColor;
      if Selected and MenuItem.Enabled then
      begin
        Brush.Color := FHighLightColor;
        Rectangle(ARect);
      end else
      begin
        Brush.Color :=  FColor;
        FillRect(ARect);
      end;
      if MenuItem.Caption = '-' then
      begin
        Y := ARect.Top + (ARect.Bottom - ARect.Top -1) div 2;
        MoveTo(ARect.Left, Y);
        LineTo(ARect.Right, Y);
      end else
      begin
        if Assigned(Image) and (MenuItem.ImageIndex >= 0) then
            Image.Draw(ACanvas, ARect.Left + 1, ARect.Top + 1,
              MenuItem.ImageIndex, MenuItem.Enabled)
        else
        begin
          if MenuItem.Enabled then
            Pen.Color := clBlack else
            Pen.Color := clGray;
          if MenuItem.Checked then
          begin
            MoveTo(ARect.Left + 3, ARect.Top + 8);
            LineTo(ARect.Left + 7, ARect.Top + 12);
            LineTo(ARect.Left + 14, ARect.Top + 4);
            MoveTo(AREct.Left + 2, ARect.Top + 9);
            LineTo(ARect.Left + 6, ARect.Top + 13);
            LineTo(ARect.Left + 15, ARect.Top + 5);
          end;
        end;
        if Assigned(Image) then
          Inc(ARect.Left, Image.Width + 4) else
          Inc(ARect.Left, 20);
        Dec(ARect.Right, 16);
        Text := ShortCutToText(MenuItem.ShortCut);
        if Text <> '' then
        begin
          DoDrawText(MenuItem, ACanvas, Text, ARect, Selected,
            DT_VCENTER or DT_SINGLELINE or DT_RIGHT);
          Dec(ARect.Right, ACanvas.TextWidth(Text) + 5);
        end;
        DoDrawText(MenuItem, ACanvas, MenuItem.Caption, ARect, Selected,
          DT_VCENTER or DT_SINGLELINE or DT_LEFT or DT_END_ELLIPSIS);
      end;
    end;
  end;
end;

procedure TCnSkinMenu.MeasureMenuItem(Sender: TObject; ACanvas: TCanvas;
  var Width, Height: Integer);
var
  MenuItem: TMenuItem;
begin
  MenuItem := Sender as TMenuItem;
  if MenuItem.GetParentComponent is TMainMenu then Exit;
  if GetImageList(MenuItem) = nil then
  begin
    if Assigned(Menuitem.Bitmap) and not MenuItem.Bitmap.Empty then
      Inc(Width, 23) else
      Inc(Width, 38);
  end;
  if (FMaxWidth >= 0) and (Width > FMaxWidth) then
    Width := FMaxWidth;
end;

procedure TCnSkinMenu.InstallMenuSkin;
var
  I: Integer;
  MenuItem: TMenuItem;
begin
  if FForm = nil then
    Exit;

  for I := 0 to FForm.ComponentCount -1 do
    if FForm.Components[I] is TMenuItem then
    begin
      MenuItem := FForm.Components[I] as TMenuItem;
      MenuItem.OnMeasureItem := MeasureMenuItem;
      MenuItem.OnAdvancedDrawItem := AdvancedDrawMenuItem;
    end
    else if FForm.Components[I] is TMenu then
      (FForm.Components[I] as TMenu).OwnerDraw := True;
end;

procedure TCnSkinMenu.UnInstallMenuSkin;
var
  I: Integer;
  MenuItem: TMenuItem;
begin
  if FForm = nil then
    Exit;

  for I := 0 to FForm.ComponentCount -1 do
    if FForm.Components[I] is TMenuItem then
    begin
      MenuItem := FForm.Components[I] as TMenuItem;
      if TMethod(MenuItem.OnMeasureItem).Code = @TCnSkinMenu.MeasureMenuItem then
        MenuItem.OnMeasureItem := nil;
      if TMethod(MenuItem.OnAdvancedDrawItem).Code = @TCnSkinMenu.AdvancedDrawMenuItem then
        MenuItem.OnAdvancedDrawItem := nil;
    end
    else if FForm.Components[I] is TMenu then
      (FForm.Components[I] as TMenu).OwnerDraw := False;
end;

initialization
  CnSkinMenus := TList.Create;

finalization
  CnSkinMenus.Free;

end.
