lazarus/designer/menutemplates.pas

1239 lines
35 KiB
ObjectPascal

unit MenuTemplates;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, types, fgl,
Buttons, Controls, Dialogs, StdCtrls, ExtCtrls, Menus,
ComCtrls, Forms, Graphics, Themes, LCLType, LCLIntf, LCLProc,
// LazUtils
LazFileUtils, Laz2_XMLCfg,
// IdeIntf
IDEDialogs,
// IDE
LazarusIDEStrConsts, MenuShortcuts;
type
{ TMenuTemplate }
TMenuTemplate = class(TObject)
strict private
FDescription: string;
FIsStandardTemplate: boolean;
FPrimaryItem: string;
FSubList: TStringList;
function GetShortcut(index: integer): TShortCut;
function GetSubItem(index: integer): string;
function GetSubItemCount: integer;
public
class function MenuItemToString(aMenuItem: TMenuItem; aDescription: string): string;
constructor CreateFromString(const aMenuString: string);
destructor Destroy; override;
function ReadFromString(const aString: string): boolean;
property Description: string read FDescription write FDescription;
property IsStandardTemplate: boolean read FIsStandardTemplate write FIsStandardTemplate;
property PrimaryItem: string read FPrimaryItem;
property Shortcut[index: integer]: TShortCut read GetShortcut;
property SubItem[index: integer]: string read GetSubItem;
property SubItemCount: integer read GetSubItemCount;
end;
TDialogMode = (dmInsert, dmSave, dmDelete);
TMenuTemplateList = specialize TFPGObjectList<TMenuTemplate>;
{ TMenuTemplates }
TMenuTemplates = class(TObject)
strict private
FTemplateList: TMenuTemplateList;
function GetDescription(index: integer): string;
function GetMenu(index: integer): TMenuItem;
function GetMenuCount: integer;
function GetMenuTemplate(index: integer): TMenuTemplate;
function GetPrimaryItem(index: integer): string;
procedure CheckIndex(anIndex: integer);
procedure LoadDefaultTemplates;
procedure LoadSavedTemplates;
public
constructor CreateForMode(aDialogMode: TDialogMode=dmInsert);
destructor Destroy; override;
function GetIndexOfTemplate(aMT: TMenuTemplate): integer;
procedure AddTemplate(const aTemplateText: string; isStandard: boolean=True);
procedure SaveTemplateToConfig(aMenuTemplate: TMenuTemplate);
property Description[index: integer]: string read GetDescription;
property Menu[index: integer]: TMenuItem read GetMenu;
property MenuCount: integer read GetMenuCount;
property MenuTemplate[index: integer]: TMenuTemplate read GetMenuTemplate;
property PrimaryItem[index: integer]: string read GetPrimaryItem;
end;
{ TPreview }
TPreview = class(TGraphicControl)
strict private
FDisplayAsPopup: boolean;
FTemplate: TMenuTemplate;
function GetSize: TSize;
procedure SetDisplayAsPopup(AValue: boolean);
protected
procedure Paint; override;
procedure SetParent(NewParent: TWinControl); override;
public
property DisplayAsPopup: boolean read FDisplayAsPopup write SetDisplayAsPopup;
procedure Clear;
procedure LoadTemplate(aMenuTemplate: tmenuTemplate);
end;
{ TMenuTemplateDialog }
TMenuTemplateDialog = class(TForm)
strict private
// GUI
FBCancel: TBitBtn;
FBExecute: TBitBtn;
FCBDisplay: TCheckBox;
FEDescription: TEdit;
FGChoose: TGroupBox;
FLDescription: TLabel;
FMenuToSave: TMenuItem;
FPButtons: TCustomPanel;
FPDescription: TPanel;
FPreview: TPreview;
FPRight: TCustomPanel;
FScrollBoxPreview: TScrollBox;
FSplitter: TSplitter;
FTVTemplates: TTreeView;
// data
FDialogMode: TDialogMode;
FMenuToInsert: TMenuItem;
FNewMenuTemplate: TMenuTemplate;
FNoneSavedNode: TTreeNode;
FSavedNode: TTreeNode;
FStandardNode: TTreeNode;
FTemplates: TMenuTemplates;
FMenu: TMenu;
procedure BExecuteDeleteClick(Sender: TObject);
procedure BExecuteInsertClick(Sender: TObject);
procedure BExecuteSaveClick(Sender: TObject);
procedure CancelButtonClick(Sender: TObject);
procedure CBDisplayChange(Sender: TObject);
procedure DeleteSelectedFromConfigFile;
procedure DeleteSelectedTemplate;
procedure EDescriptionChange(Sender: TObject);
procedure PopulateTreeView;
procedure SaveMenuAsTemplate;
procedure SetupGUI;
procedure ShowPreview(aMenuTemplate: TMenuTemplate);
procedure TVAdvancedCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode;
State: TCustomDrawState; Stage: TCustomDrawStage;
var {%H-}PaintImages, DefaultDraw: Boolean);
procedure TVEditing(Sender: TObject; {%H-}Node: TTreeNode; var AllowEdit: Boolean);
procedure TVSelectionChanged(Sender: TObject);
protected
procedure DoShowWindow; override;
public
constructor CreateWithMode(aMenu: TMenu; aDialogMode: TDialogMode);
destructor Destroy; override;
property MenuToInsert: TMenuItem read FMenuToInsert;
property MenuToSave: TMenuItem read FMenuToSave write FMenuToSave;
end;
function SavedTemplatesExist: boolean;
function GetSavedTemplatesCount: integer;
function InsertMenuTemplateDlg(ParentMenuForInsert: TMenu): TMenuItem;
function DeleteMenuTemplateDlg: boolean;
function GetCfgPath: string;
procedure InitMenuBaseSizes;
const
MenuTemplatesFilename = 'menutemplates.xml';
var
MenuBar_Height: Integer = 20;
Separator_Height: Integer = 7;
Separator_Centre: Integer = 3;
DropDown_Height: Integer = 24;
MenuBar_Text_Offset: Integer = 7;
Double_MenuBar_Text_Offset: Integer = 14;
DropDown_Text_Offset: Integer = 35;
Double_DropDown_Text_Offset: Integer = 70;
Gutter_Offset: Integer = 6;
Gutter_X: Integer = 29;
Add_Icon_Width: Integer = 16;
implementation
function InvalidXML(aTemplateCfgName: string): boolean;
// perform a quick check, far from a full validation
var
sl: TStringList;
tr0, s: string;
begin
sl:=TStringList.Create;
try
sl.LoadFromFile(aTemplateCfgName);
if (sl.Count < 3) then
Exit(True);
tr0:=Trim(sl[0]);
s:=Copy(tr0, 1, 15);
if not SameText(s, '<?xml version="') then
Exit(True);
s:=Copy(tr0, Length(tr0) - 17, 18);
if not SameText(s, 'encoding="UTF-8"?>') then
Exit(True);
if not SameText(Trim(sl[1]), '<CONFIG>') then
Exit(True);
if not SameText(Trim(sl[Pred(sl.Count)]), '</CONFIG>') then
Exit(True);
Result:=False;
finally
sl.Free;
end;
end;
function SavedTemplatesExist: boolean;
var
XMLConfig: TXMLConfig;
cfgPath, s, templateCfgName: string;
begin
cfgPath:=GetCfgPath;
templateCfgName:=cfgPath + DirectorySeparator + MenuTemplatesFilename;
if not FileExistsUTF8(templateCfgName) then
Exit(False);
if InvalidXML(templateCfgName) then
// file is corrupted or not XML, so discard to prevent exception
DeleteFile(templateCfgName);
XMLConfig:=TXMLConfig.Create(templateCfgName);
try
s:=XMLConfig.GetValue('menu_1/Name/Value', 'missing_Menu_1_Name');
Result:=CompareText(s, 'missing_Menu_1_Name') <> 0;
finally
XMLConfig.Free;
end;
end;
function GetSavedTemplatesCount: integer;
var
mt: TMenuTemplates;
begin
mt:=TMenuTemplates.CreateForMode(dmDelete);
try
Result:=mt.MenuCount;
finally
mt.Free;
end;
end;
{
procedure SaveMenuTemplateDlg(aMenuItem: TMenuItem);
var
dlg: TMenuTemplateDialog;
begin
dlg:=TMenuTemplateDialog.CreateWithMode(nil, dmSave);
try
dlg.MenuToSave:=aMenuItem;
dlg.ShowModal;
finally
dlg.Free;
end;
end;
}
function InsertMenuTemplateDlg(ParentMenuForInsert: TMenu): TMenuItem;
var
dlg: TMenuTemplateDialog;
begin
dlg:=TMenuTemplateDialog.CreateWithMode(ParentMenuForInsert, dmInsert);
try
if (dlg.ShowModal = mrOK) then
Result:=dlg.MenuToInsert
else
Result:=nil;
finally
dlg.Free;
end;
end;
function DeleteMenuTemplateDlg: boolean;
var
dlg: TMenuTemplateDialog;
mr: TModalResult;
begin
dlg:=TMenuTemplateDialog.CreateWithMode(nil, dmDelete);
try
mr:=dlg.ShowModal;
Result:=(mr = mrOK);
finally
dlg.Free;
end;
end;
function GetCfgPath: string;
begin
Result:=ExtractFilePath(ChompPathDelim(GetAppConfigDirUTF8(False)))+'lazarus';
end;
procedure InitMenuBaseSizes;
begin
MenuBar_Height := ScaleY(20, 96);
Separator_Height := ScaleY(7, 96);
Separator_Centre := ScaleY(3, 96);
DropDown_Height := ScaleY(24, 96);
MenuBar_Text_Offset := ScaleX(7, 96);
Double_MenuBar_Text_Offset := MenuBar_Text_Offset shl 1;
DropDown_Text_Offset := ScaleX(35, 96);
Double_DropDown_Text_Offset := DropDown_Text_Offset shl 1;
Gutter_Offset := ScaleX(6, 96);
Gutter_X := DropDown_Text_Offset - Gutter_Offset;
Add_Icon_Width := ScaleX(16, 96);
end;
{ TMenuTemplate }
function TMenuTemplate.GetShortcut(index: integer): TShortCut;
begin
Result:=TShortCut(PtrUInt(FSubList.Objects[index]));
end;
function TMenuTemplate.GetSubItem(index: integer): string;
begin
Result:=FSubList[index];
end;
function TMenuTemplate.GetSubItemCount: integer;
begin
Result:=FSubList.Count;
end;
class function TMenuTemplate.MenuItemToString(aMenuItem: TMenuItem;
aDescription: string): string;
var
sc: TShortCut;
scStr: string;
i: integer;
mi: TMenuItem;
begin
if (aMenuItem = nil) then
Exit('');
Result:=Format('%s,%s,',[aMenuItem.Caption, aDescription]);
for i:=0 to aMenuItem.Count-1 do
begin
mi:=aMenuItem.Items[i];
sc:=mi.ShortCut;
if (sc = 0) then
begin
if (mi.ShortCutKey2 = 0) then
scStr:=''
else
scStr:=ShortCutToText(mi.ShortCutKey2);
end
else
scStr:=ShortCutToText(sc);
AppendStr(Result, Format('%s,%s,',[mi.Caption, scStr]));
end;
end;
constructor TMenuTemplate.CreateFromString(const aMenuString: string);
begin
FSubList:=TStringList.Create;
if not ReadFromString(aMenuString) then
Assert(False,'TMenuTemplate.CreateFromString: Attempt to read invalid menu template');
end;
destructor TMenuTemplate.Destroy;
begin
FSubList.Free;
inherited Destroy;
end;
function TMenuTemplate.ReadFromString(const aString: string): boolean;
var
sl: TStringList;
s: string;
i: integer;
begin
Result:=True;
sl:=TStringList.Create;
try
sl.StrictDelimiter:=True;
sl.CommaText:=aString;
case sl.Count of
0: Result:=False;
1: FPrimaryItem:=sl[0];
2: begin
FPrimaryItem:=sl[0];
FDescription:=sl[1];
end;
else begin
FPrimaryItem:=sl[0];
FDescription:=sl[1];
for i:=2 to sl.Count-1 do
begin
if not Odd(i) then
s:=sl[i]
else
FSubList.AddObject(s, TObject(PtrUInt(TextToShortCut(sl[i]))));
end;
end;
end; // case
finally
sl.Free;
end;
end;
{ TMenuTemplates }
function TMenuTemplates.GetDescription(index: integer): string;
begin
CheckIndex(index);
Result:=FTemplateList[index].Description;
end;
function TMenuTemplates.GetMenu(index: integer): TMenuItem;
var
mt: TMenuTemplate;
submi, mi: TMenuItem;
i: integer;
begin
CheckIndex(index);
mt:=FTemplateList[index];
mi:=TMenuItem.Create(nil);
mi.Caption:=mt.PrimaryItem;
for i:=0 to mt.SubItemCount-1 do
begin
submi:=TMenuItem.Create(nil);
submi.Caption:=mt.SubItem[i];
submi.ShortCut:=mt.Shortcut[i];
mi.Insert(i, submi);
end;
Result:=mi;
end;
function TMenuTemplates.GetMenuCount: integer;
begin
Result:=FTemplateList.Count;
end;
function TMenuTemplates.GetMenuTemplate(index: integer): TMenuTemplate;
begin
CheckIndex(index);
Result:=FTemplateList[index];
end;
function TMenuTemplates.GetPrimaryItem(index: integer): string;
begin
CheckIndex(index);
Result:=FTemplateList[index].PrimaryItem;
end;
procedure TMenuTemplates.CheckIndex(anIndex: integer);
begin
Assert((anIndex > -1) and (anIndex < FTemplateList.Count),
Format('TMenuTemplates.CheckIndex: index (%d) out of bounds[0-%d]',
[anIndex, Pred(FTemplateList.Count)]));
end;
procedure TMenuTemplates.LoadDefaultTemplates;
begin
AddTemplate(lisMenuEditorBasicEditMenuTemplate);
AddTemplate(lisMenuEditorBasicFileMenuTemplate);
AddTemplate(lisMenuEditorBasicWindowMenuTemplate);
AddTemplate(lisMenuEditorBasicHelpMenuTemplate);
end;
procedure TMenuTemplates.LoadSavedTemplates;
var
XMLConfig: TXMLConfig;
i, j: integer;
cfgPath, s, sc, sText, tmp: string;
begin
cfgPath:=GetCfgPath;
XMLConfig:=TXMLConfig.Create(cfgPath + DirectorySeparator + MenuTemplatesFilename);
try
i:=1;
s:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[i]), 'missing Name');
while (s <> 'missing Name') do
begin
tmp:=XMLConfig.GetValue(Format('menu_%d/Description/Value',[i]),
'missing Description');
sText:=Format('%s,%s,',[s, tmp]);
s:=XMLConfig.GetValue(Format('menu_%d/SubItems/Value',[i]), '');
if (s = 'true') then
begin
j:=0;
s:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Name/Value',[i,j]), 'nonexistent subitem');
sc:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Shortcut/Value',[i,j]), 'nonexistent shortcut');
while (s <> 'nonexistent subitem') do
begin
if (CompareText(sc, 'nonexistent shortcut') = 0) then
sc := '';
AppendStr(sText, s + ',' + sc + ',');
Inc(j);
s:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Name/Value',[i,j]), 'nonexistent subitem');
sc:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Shortcut/Value',[i,j]), 'nonexistent shortcut');
end;
end;
Inc(i);
s:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[i]), 'missing Name');
AddTemplate(sText, False);
end;
finally
XMLConfig.Free;
end;
end;
constructor TMenuTemplates.CreateForMode(aDialogMode: TDialogMode);
begin
inherited Create;
FTemplateList:=TMenuTemplateList.Create;
if (aDialogMode = dmInsert) then
LoadDefaultTemplates;
LoadSavedTemplates;
end;
destructor TMenuTemplates.Destroy;
begin
FreeAndNil(FTemplateList);
inherited Destroy;
end;
function TMenuTemplates.GetIndexOfTemplate(aMT: TMenuTemplate): integer;
begin
if (aMT = nil) then
Result:= -1
else
Result:=FTemplateList.IndexOf(aMT);
end;
procedure TMenuTemplates.AddTemplate(const aTemplateText: string;
isStandard: boolean);
var
mt: TMenuTemplate;
begin
if (aTemplateText = '') then
Exit;
mt:=TMenuTemplate.CreateFromString(aTemplateText);
mt.IsStandardTemplate:=isStandard;
FTemplateList.Add(mt);
end;
procedure TMenuTemplates.SaveTemplateToConfig(aMenuTemplate: TMenuTemplate);
var
XMLConfig: TXMLConfig;
i, j: integer;
cfgPath, s: string;
begin
cfgPath:=GetCfgPath;
XMLConfig:=TXMLConfig.Create(cfgPath + DirectorySeparator + MenuTemplatesFilename);
try
i:=1;
s:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[i]), 'missing');
while not SameText(s, 'missing') do
begin
Inc(i);
s:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[i]), 'missing');
end;
XMLConfig.SetValue(Format('menu_%d/Name/Value',[i]), aMenuTemplate.PrimaryItem);
XMLConfig.SetValue(format('menu_%d/Description/Value',[i]), aMenuTemplate.Description);
if (aMenuTemplate.SubItemCount > 0) then
XMLConfig.SetValue(Format('menu_%d/SubItems/Value', [i]), 'true');
for j:=0 to aMenuTemplate.SubItemCount-1 do
begin
XMLConfig.SetValue(Format('menu_%d/subitem_%d/Name/Value',[i,j]), aMenuTemplate.SubItem[j]);
XMLConfig.SetValue(Format('menu_%d/subitem_%d/Shortcut/Value',[i,j]), ShortCutToText(aMenuTemplate.Shortcut[j]));
end;
InvalidateFileStateCache;
XMLConfig.Flush;
finally
XMLConfig.Free;
end;
end;
{ TPreview }
function TPreview.GetSize: TSize;
var
w, h: integer;
i, tmp: integer;
s: string;
begin
if (FTemplate = nil) then
begin
FillChar(Result{%H-}, SizeOf(Result), 0);
SetBounds(0,0,0,0);
end
else
case FDisplayAsPopup of
True: begin
w:=5;
h:=10;
for i:=0 to FTemplate.SubItemCount-1 do
begin
s:=FTemplate.SubItem[i];
if (s = '-') then
Inc(h, Separator_Height)
else begin
Inc(h, DropDown_Height);
tmp:=Canvas.TextWidth(s);
if (FTemplate.Shortcut[i] <> 0) then
Inc(tmp, Canvas.TextWidth(ShortCutToText(FTemplate.Shortcut[i])) + Double_MenuBar_Text_Offset);
if (tmp > w) then
w:=tmp;
end;
end;
Result.cx:=w + 2*Double_DropDown_Text_Offset + Canvas.TextWidth(FTemplate.PrimaryItem);
Result.cy:=h + 2;
end;
False: begin
w:=0;
h:=MenuBar_Height;
for i:=0 to FTemplate.SubItemCount-1 do
begin
s:=FTemplate.SubItem[i];
if (s = '-') then
Inc(h, Separator_Height)
else begin
Inc(h, DropDown_Height);
tmp:=Canvas.TextWidth(s);
if (FTemplate.Shortcut[i] <> 0) then
Inc(tmp, Canvas.TextWidth(ShortCutToText(FTemplate.Shortcut[i])) + Double_MenuBar_Text_Offset);
if (tmp > w) then
w:=tmp;
end;
end;
Result.cx:=w + Double_DropDown_Text_Offset;
Result.cy:=h + 2;
end;
end;
end;
procedure TPreview.SetDisplayAsPopup(AValue: boolean);
var
sz: TSize;
begin
if FDisplayAsPopup=AValue then Exit;
FDisplayAsPopup:=AValue;
SetBounds(0,0,0,0);
sz:=GetSize;
SetBounds(0, 0, sz.cx, sz.cy);
end;
procedure TPreview.Paint;
var
r, rBar, rDrop: TRect;
dets: TThemedElementDetails;
textFlags: integer = DT_VCENTER or DT_SINGLELINE or DT_EXPANDTABS;
i, t, h, w, l: integer;
txt: string;
separator: boolean;
szB: TSize;
begin
r:=ClientRect;
Canvas.FillRect(r);
Canvas.Frame(r);
InflateRect(r, -1, -1);
rBar:=r;
if FDisplayAsPopup then
begin
rBar.Top:=rBar.Top+5;
rBar.Left:=rBar.Left+5;
rBar.Bottom:=rBar.Top + DropDown_Height;
rBar.Right:=rBar.Left + DropDown_Text_Offset - Gutter_Offset;
dets:=ThemeServices.GetElementDetails(tmPopupGutter);
ThemeServices.DrawElement(Canvas.Handle, dets, rBar);
w:=Canvas.TextWidth(FTemplate.PrimaryItem);
rBar.Right:=rBar.Left + w + Double_DropDown_Text_Offset;
rBar.Left:=rBar.Left + DropDown_Text_Offset;
dets:=ThemeServices.GetElementDetails(tmPopupItemNormal);
ThemeServices.DrawText(Canvas, dets, FTemplate.PrimaryItem, rBar, textFlags, 0);
rBar.Left:=r.Left+5;
Canvas.Pen.Color:=clLtGray;
Canvas.Frame(rBar);
if (FTemplate.SubItemCount > 0) then
begin
rDrop:=rBar;
dets:=ThemeServices.GetElementDetails(tmPopupSubmenuNormal);
rDrop.Left:=rDrop.Right - DropDown_Text_Offset;
ThemeServices.DrawElement(Canvas.Handle, dets, rDrop);
end;
rDrop:=rBar;
szB:=Size(rBar);
OffsetRect(rDrop, szB.cx + 1, 2);
rDrop.Right:=r.Right-1;
rDrop.Bottom:=r.Bottom-1;
Canvas.Frame(rDrop);
l:=rDrop.Left+1;
w:=r.Right-2;
t:=rDrop.Top+1;
for i:=0 to Pred(FTemplate.SubItemCount) do
begin
txt:=FTemplate.SubItem[i];
separator:=(txt = '-');
if separator then
h:=Separator_Height
else
h:=DropDown_Height;
rDrop:=Rect(l, t, w, t+h);
Inc(t, h);
rBar:=rDrop;
rBar.Right:=rBar.Left + DropDown_Text_Offset - Gutter_Offset;
dets:=ThemeServices.GetElementDetails(tmPopupGutter);
ThemeServices.DrawElement(Canvas.Handle, dets, rBar);
if separator then
begin
dets:=ThemeServices.GetElementDetails(tmPopupSeparator);
ThemeServices.DrawElement(Canvas.Handle, dets, rDrop);
end
else
begin
rDrop.Left:=rDrop.Left + DropDown_Text_Offset;
dets:=ThemeServices.GetElementDetails(tmPopupItemNormal);
ThemeServices.DrawText(Canvas, dets, txt, rDrop, textFlags, 0);
if (FTemplate.Shortcut[i] <> 0) then
begin
txt:=ShortCutToText(FTemplate.Shortcut[i]);
rDrop.Left:=r.Right - Canvas.TextWidth(txt) - Double_MenuBar_Text_Offset;
ThemeServices.DrawText(Canvas, dets, txt, rDrop, textFlags, 0);
end;
end;
end;
end
else
begin
rBar.Bottom:=rBar.Top + MenuBar_Height;
dets:=ThemeServices.GetElementDetails(tmBarBackgroundActive);
ThemeServices.DrawElement(Canvas.Handle, dets, rBar);
rBar.Left:=rBar.Left + MenuBar_Text_Offset;
ThemeServices.DrawText(Canvas, dets, FTemplate.PrimaryItem, rBar, textFlags, 0);
t:=MenuBar_Height + 1;
for i:=0 to Pred(FTemplate.SubItemCount) do
begin
rBar:=r;
rBar.Top:=t;
txt:=FTemplate.SubItem[i];
separator:=(txt = '-');
if separator then
h:=Separator_Height
else
h:=DropDown_Height;
rBar.Bottom:=rBar.Top + h;
rBar.Right:=DropDown_Text_Offset - Gutter_Offset;
dets:=ThemeServices.GetElementDetails(tmPopupGutter);
ThemeServices.DrawElement(Canvas.Handle, dets, rBar);
rBar.Left:=rBar.Left + DropDown_Text_Offset;
rBar.Right:=r.Right;
if separator then
begin
dets:=ThemeServices.GetElementDetails(tmPopupSeparator);
ThemeServices.DrawElement(Canvas.Handle, dets, rBar);
end
else
begin
dets:=ThemeServices.GetElementDetails(tmPopupItemNormal);
ThemeServices.DrawText(Canvas, dets, txt, rBar, textFlags, 0);
if (FTemplate.Shortcut[i] <> 0) then
begin
txt:=ShortCutToText(FTemplate.Shortcut[i]);
rBar.Left:=r.Right - Canvas.TextWidth(txt) - Double_MenuBar_Text_Offset;
ThemeServices.DrawText(Canvas, dets, txt, rBar, textFlags, 0);
end;
end;
inc(t, h);
end;
end;
end;
procedure TPreview.SetParent(NewParent: TWinControl);
var
sz: TSize;
begin
inherited SetParent(NewParent);
if (NewParent <> nil) then
begin
sz:=GetSize;
SetBounds(0, 0, sz.cx, sz.cy);
Canvas.Pen.Color:=clLtGray;
Canvas.Brush.Color:=clBtnFace;
end;
end;
procedure TPreview.Clear;
var
sz: TSize;
begin
FTemplate:=nil;
sz:=GetSize;
SetBounds(0, 0, sz.cx, sz.cy);
end;
procedure TPreview.LoadTemplate(aMenuTemplate: tmenuTemplate);
var
sz: TSize;
begin
FTemplate:=aMenuTemplate;
sz:=GetSize;
SetBounds(0, 0, sz.cx, sz.cy);
end;
{ TMenuTemplateDialog }
constructor TMenuTemplateDialog.CreateWithMode(aMenu: TMenu; aDialogMode: TDialogMode);
begin
inherited CreateNew(Nil);
FMenu:=aMenu;
FDialogMode:=aDialogMode;
BorderStyle:=bsSizeable;
SetInitialBounds(0, 0, 530, 380);
Position:=poScreenCenter;
case aDialogMode of
dmSave: Caption:=lisMenuEditorSaveMenuAsTemplate;
dmInsert: Caption:=Format(lisMenuEditorInsertMenuTemplateIntoRootOfS, [FMenu.Name]);
dmDelete: Caption:=lisMenuEditorDeleteSavedMenuTemplate;
end;
FTemplates:=TMenuTemplates.CreateForMode(FDialogMode);
SetupGUI;
PopulateTreeView;
end;
destructor TMenuTemplateDialog.Destroy;
begin
FreeAndNil(FNewMenuTemplate);
FreeAndNil(FTemplates);
FreeAndNil(FPreview);
inherited Destroy;
end;
procedure TMenuTemplateDialog.BExecuteDeleteClick(Sender: TObject);
begin
DeleteSelectedFromConfigFile;
end;
procedure TMenuTemplateDialog.BExecuteInsertClick(Sender: TObject);
var
mt: TMenuTemplate;
begin
Assert(FTVTemplates.Selected<>nil,'TMenuTemplateDialog.BExecuteInsertClick: FTVTemplates.Selected is nil');
mt:=TMenuTemplate(FTVTemplates.Selected.Data);
FMenuToInsert:=FTemplates.Menu[FTemplates.GetIndexOfTemplate(mt)];
end;
procedure TMenuTemplateDialog.BExecuteSaveClick(Sender: TObject);
var
trimmed: string;
begin
trimmed:=Trim(FEDescription.Text);
if (Length(trimmed) < 4) then
begin
IDEMessageDialogAb(lisMenuEditorInadequateDescription,
Format(lisMenuEditorSIsNotASufficientDescriptionPleaseExpand,[trimmed]),
mtWarning, [mbOK], False);
FEDescription.SelectAll;
FEDescription.SetFocus;
end
else
begin
FNewMenuTemplate.Description:=trimmed;
FTemplates.AddTemplate(TMenuTemplate.MenuItemToString(FMenuToSave, trimmed), False);
FEDescription.ReadOnly:=True;
SaveMenuAsTemplate;
end;
end;
procedure TMenuTemplateDialog.CancelButtonClick(Sender: TObject);
begin
Close;
end;
procedure TMenuTemplateDialog.CBDisplayChange(Sender: TObject);
begin
FPreview.DisplayAsPopup:=FCBDisplay.Checked;
end;
procedure TMenuTemplateDialog.DeleteSelectedFromConfigFile;
var
XMLConfig: TXMLConfig;
procedure ExchangeConfigTemplates(aLower, aHigher: integer);
var
j: integer;
valueN, valueS: string;
begin
j:=1;
while not SameText(XMLConfig.GetValue(Format('menu_%d/subitem_%d/Name/Value',
[aLower, j]), 'nonexistent'), 'nonexistent') do
begin
XMLConfig.DeletePath(Format('menu_%d/subitem_%d',[aLower, j]));
Inc(j);
end;
j:=1;
valueN:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Name/Value',
[aHigher, j]), 'nonexistent');
while not SameText(valueN, 'nonexistent') do
begin
XMLConfig.SetValue(Format('menu_%d/subitem_%d/Name/Value',[aLower, j]), valueN);
valueS:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Shortcut/Value',
[aHigher, j]), 'nonexistent');
if not SameText(valueS, 'nonexistent') then
XMLConfig.SetValue(Format('menu_%d/subitem_%d/Shortcut/Value',[aLower, j]), valueS);
Inc(j);
valueN:=XMLConfig.GetValue(Format('menu_%d/subitem_%d/Name/Value',
[aHigher, j]), 'nonexistent');
end;
valueN:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[aHigher]), 'nonexistent');
if not SameText(valueN, 'nonexistent') then
XMLConfig.SetValue(Format('menu_%d/Name/Value',[aLower]), valueN);
valueN:=XMLConfig.GetValue(Format('menu_%d/Description/Value',[aHigher]), 'nonexistent');
if not SameText(valueN, 'nonexistent') then
XMLConfig.SetValue(Format('menu_%d/Description/Value',[aLower]), valueN);
end;
var
highestOldIdx, i, idxToDelete: integer;
cfgPath, s, desc, currDesc: string;
begin
desc:=TMenuTemplate(FTVTemplates.Selected.Data).Description;
cfgPath:=GetCfgPath;
XMLConfig:=TXMLConfig.Create(cfgPath + DirectorySeparator + MenuTemplatesFilename);
try
i:=0;
idxToDelete:= -1;
repeat
Inc(i);
s:=XMLConfig.GetValue(Format('menu_%d/Name/Value',[i]), 'nonexistent');
currDesc:=XMLConfig.GetValue(Format('menu_%d/Description/Value',[i]), 'noDescription');
if (CompareText(desc, currDesc) = 0) then
idxToDelete:=i;
until CompareText(s, 'nonexistent') = 0;
highestOldIdx:=Pred(i);
Assert(idxToDelete>-1,'TMenuTemplateDialog.DeleteSelectedFromConfigFile: nonexistent template index');
if (idxToDelete < highestOldIdx) then
ExchangeConfigTemplates(idxToDelete, highestOldIdx);
XMLConfig.DeletePath(Format('menu_%d',[highestOldIdx]));
InvalidateFileStateCache;
XMLConfig.Flush;
finally
XMLConfig.Free;
end;
end;
procedure TMenuTemplateDialog.DeleteSelectedTemplate;
begin
Assert(FTVTemplates.Selected<>nil,'TMenuTemplateDialog.DeleteSelectedTemplate: FTVTemplates.Selected is nil');
DeleteSelectedFromConfigFile;
end;
procedure TMenuTemplateDialog.EDescriptionChange(Sender: TObject);
begin
if (Length(FEDescription.Text) > 3) and not FBExecute.Enabled then
FBExecute.Enabled:=True;
end;
procedure TMenuTemplateDialog.PopulateTreeView;
var
mt: TMenuTemplate;
i: integer;
processed: string;
begin
for i:=0 to FTemplates.MenuCount-1 do
begin
mt:=FTemplates.MenuTemplate[i];
processed:=AmpersandStripped(mt.PrimaryItem);
if mt.IsStandardTemplate then
FTVTemplates.Items.AddChildObject(FStandardNode, processed, mt)
else begin
AppendStr(processed, Format(' [%s]',[mt.Description]));
FTVTemplates.Items.AddChildObject(FSavedNode, processed, mt);
end;
end;
end;
procedure TMenuTemplateDialog.SaveMenuAsTemplate;
var
s: string;
begin
Assert(FMenuToSave<>nil,'TInsertTemplateDialog.SaveMenuAsTemplate: FMenuToSave is nil');
Assert(FNewMenuTemplate<>nil,'TInsertTemplateDialog.SaveMenuAsTemplate: FNewMenuTemplate is nil');
FTemplates.SaveTemplateToConfig(FNewMenuTemplate);
s:=AmpersandStripped(FNewMenuTemplate.PrimaryItem);
IDEMessageDialogAb(lisMenuEditorTemplateSaved, Format(
lisMenuEditorANewMenuTemplateHasBeenSaved,
[FNewMenuTemplate.Description, s, FNewMenuTemplate.SubItemCount]),
mtInformation, [mbOK], False);
end;
procedure TMenuTemplateDialog.SetupGUI;
begin
FPButtons:=TCustomPanel.Create(Self);
with FPButtons do begin
ControlStyle:=ControlStyle - [csSetCaption];
BevelInner:=bvNone;
BevelOuter:=bvNone;
BorderSpacing.Around:=Margin;
Align:=alBottom;
AutoSize:=True;
Parent:=Self;
end;
FBCancel:=TBitBtn.Create(Self);
with FBCancel do begin
Kind:=bkCancel;
AutoSize:=True;
BorderSpacing.Left:=2*Spacing;
Align:=alRight;
Left:=1;
OnClick:=@CancelButtonClick;
Parent:=FPButtons;
end;
FBExecute:=TBitBtn.Create(Self);
with FBExecute do begin
Kind:=bkOK;
case FDialogMode of
dmSave: begin
Caption:=lisMenuEditorSaveMenuShownAsANewTemplate;
OnClick:=@BExecuteSaveClick;
end;
dmInsert: begin
Caption:=lisMenuEditorInsertSelectedMenuTemplate;
OnClick:=@BExecuteInsertClick;
end;
dmDelete: begin
Caption:=lisMenuEditorDeleteSelectedMenuTemplate;
OnClick:=@BExecuteDeleteClick;
end;
end;
AutoSize:=True;
Align:=alRight;
Left:=0;
Enabled:=False;
Parent:=FPButtons;
end;
FGChoose:=TGroupBox.Create(Self);
with FGChoose do begin
Align:=alClient;
BorderSpacing.Around:=Margin;
Parent:=Self;
end;
FPDescription:=TPanel.Create(Self);
with FPDescription do begin
Align:=alTop;
BevelInner:=bvNone;
BevelOuter:=bvNone;
BorderSpacing.Bottom:=Margin;
BorderSpacing.Top:=Margin;
AutoSize:=True;
Parent:=FGChoose;
end;
FEDescription:=TEdit.Create(Self);
with FEDescription do begin
Align:=alClient;
BorderSpacing.Right:=Margin;
Parent:=FPDescription;
end;
FLDescription:=TLabel.Create(Self);
with FLDescription do begin
AutoSize:=True;
BorderSpacing.Around:=Margin;
Align:=alLeft;
Parent:=FPDescription;
end;
FTVTemplates:=TTreeView.Create(Self);
with FTVTemplates do begin
Align:=alLeft;
Width:=200;
Align:=alLeft;
Indent:=Margin;
Options:=[tvoAutoExpand, tvoAutoItemHeight, tvoKeepCollapsedNodes,
tvoShowRoot, tvoNoDoubleClickExpand];
BorderSpacing.Bottom:=Margin;
BorderSpacing.Left:=Margin;
OnAdvancedCustomDrawItem:=@TVAdvancedCustomDrawItem;
OnEditing:=@TVEditing;
Parent:=FGChoose;
end;
FSplitter:=TSplitter.Create(Self);
FSplitter.Left:=FTVTemplates.BoundsRect.Right;
FSplitter.Parent:=FGChoose;
FPRight:=TCustomPanel.Create(Self);
with FPRight do begin
Width:=200;
BevelInner:=bvNone;
BevelOuter:=bvNone;
BorderSpacing.Bottom:=Margin;
BorderSpacing.Right:=Margin;
Align:=alClient;
Parent:=FGChoose;
end;
FCBDisplay:=TCheckBox.Create(Self);
with FCBDisplay do begin
Align:=alTop;
Alignment:=taLeftJustify;
BorderSpacing.Bottom:=Margin;
BorderSpacing.Left:=Margin;
AutoSize:=True;
Caption:=lisMenuEditorDisplayPreviewAsPopupMenu;
OnChange:=@CBDisplayChange;
Parent:=FPRight;
end;
FScrollBoxPreview:=TScrollBox.Create(Self);
with FScrollBoxPreview do begin
Align:=alClient;
TabStop:=False;
Parent:=FPRight;
end;
FPreview:=TPreview.Create(Self);
FPreview.Parent:=FScrollBoxPreview;
case FDialogMode of
dmSave: begin
FSavedNode:=FTVTemplates.Items.AddFirst(nil, lisMenuEditorExistingSavedTemplates);
FGChoose.Caption:=lisMenuEditorSaveMenuAsTemplateForFutureUse;
FLDescription.Caption:=lisMenuEditorEnterAMenuDescription;
FLDescription.FocusControl:=FEDescription;
end;
dmInsert: begin
FStandardNode:=FTVTemplates.Items.AddFirst(nil, lisMenuEditorStandardTemplates);
FSavedNode:=FTVTemplates.Items.Add(FStandardNode, lisMenuEditorSavedTemplates);
FGChoose.Caption:=lisMenuEditorChooseTemplateToInsert;
FLDescription.Caption:=lisMenuEditorTemplateDescription;
FTVTemplates.OnSelectionChanged:=@TVSelectionChanged;
FEDescription.ReadOnly:=True;
end;
dmDelete: begin
FStandardNode:=nil;
FSavedNode:=FTVTemplates.Items.AddFirst(nil, lisMenuEditorExistingSavedTemplates);
FGChoose.Caption:=lisMenuEditorChooseTemplateToDelete;
FLDescription.Caption:=lisMenuEditorTemplateDescription;
FTVTemplates.OnSelectionChanged:=@TVSelectionChanged;
FEDescription.ReadOnly:=True;
end;
end;
end;
procedure TMenuTemplateDialog.ShowPreview(aMenuTemplate: TMenuTemplate);
begin
FPreview.Clear;
if (aMenuTemplate <> nil) then
FPreview.LoadTemplate(aMenuTemplate);
end;
procedure TMenuTemplateDialog.TVAdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage;
var PaintImages, DefaultDraw: Boolean);
var
nodeR, textR: TRect;
begin
DefaultDraw:=(Stage <> cdPrePaint);
if DefaultDraw then Exit;
nodeR:=Node.DisplayRect(False);
textR:=Node.DisplayRect(True);
if (Node.Level = 0) then begin
Sender.Canvas.Font.Color:=clGray;
Sender.Canvas.Font.Style:=[fsBold];
end
else begin
Sender.Canvas.Font.Color:=clBlack;
Sender.Canvas.Font.Style:=[];
end;
if (cdsSelected in State) and (Node.Level > 0) then
Sender.Canvas.Brush.Color:=Sender.SelectionColor
else Sender.Canvas.Brush.Color:=Sender.Color;
Sender.Canvas.FillRect(nodeR);
Sender.Canvas.TextOut(textR.Left, textR.Top, Node.Text);
end;
procedure TMenuTemplateDialog.TVEditing(Sender: TObject; Node: TTreeNode;
var AllowEdit: Boolean);
begin
AllowEdit:=False;
end;
procedure TMenuTemplateDialog.TVSelectionChanged(Sender: TObject);
var
tv: TTreeView absolute Sender;
node: TTreeNode;
mt: TMenuTemplate;
begin
if not (Sender is TTreeView) then
Exit;
node:=tv.Selected;
if (node = nil) or (node = FStandardNode) or (node = FSavedNode) or
(node = FNoneSavedNode) or (node.Data = nil) then
begin
FBExecute.Enabled:=False;
FEDescription.Text:='';
ShowPreview(nil);
end
else begin
Assert(node.Data<>nil,'TMenuTemplateDialog.TVSelectionChanged: node.Data is nil');
mt:=TMenuTemplate(node.Data);
FEDescription.Text:=mt.Description;
ShowPreview(mt);
if FDialogMode in [dmInsert, dmDelete] then
FBExecute.Enabled:=True;
end;
end;
procedure TMenuTemplateDialog.DoShowWindow;
var
menuString: string;
noneSaved: boolean;
begin
inherited DoShowWindow;
noneSaved:=not SavedTemplatesExist;
case FDialogMode of
dmSave: begin
menuString:=TMenuTemplate.MenuItemToString(FMenuToSave, '');
FNewMenuTemplate:=TMenuTemplate.CreateFromString(menuString);
FPreview.LoadTemplate(FNewMenuTemplate);
if noneSaved then
FNoneSavedNode:=FTVTemplates.Items.AddChild(FSavedNode,lisMenuEditorNone);
FEDescription.SetFocus;
FEDescription.OnChange:=@EDescriptionChange;
end;
dmInsert: begin
if noneSaved then
FNoneSavedNode:=FTVTemplates.Items.AddChild(FSavedNode,lisMenuEditorNone);
TVSelectionChanged(FTVTemplates);
FTVTemplates.Selected:=FTVTemplates.Items[1];
FTVTemplates.SetFocus;
end;
dmDelete: begin
if noneSaved then begin
IDEMessageDialogAb(lisMenuEditorNoUserSavedTemplates, lisMenuEditorThereAreNoUserSavedMenuTemplates,
mtInformation, [mbOK], False);
ModalResult:=mrCancel;
end
else begin
TVSelectionChanged(FTVTemplates);
FTVTemplates.Selected:=FTVTemplates.Items[1];
FTVTemplates.SetFocus;
end;
end;
end;
end;
end.