unit EasyCollectionEditor; // Version 1.7.0 // // The contents of this file are subject to the Mozilla Public License // Version 1.1 (the "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ // // Alternatively, you may redistribute this library, use and/or modify it under the terms of the // GNU Lesser General Public License as published by the Free Software Foundation; // either version 2.1 of the License, or (at your option) any later version. // You may obtain a copy of the LGPL at http://www.gnu.org/copyleft/. // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the // specific language governing rights and limitations under the License. // // The initial developer of this code is Jim Kueneman // Special thanks to the following in no particular order for their help/support/code // Danijel Malik, Robert Lee, Werner Lehmann, Alexey Torgashin, Milan Vandrovec // //---------------------------------------------------------------------------- interface {$I Compilers.inc} uses Windows, Messages, SysUtils, Classes, Controls, Graphics, {$IFDEF COMPILER_6_UP} DesignIntf, DesignEditors, DesignWindows, TreeIntf, VCLEditors, {$ELSE} DsgnIntf, DsgnWnds, {$ENDIF} {$IFDEF COMPILER_5} ParentageSupport, // Sprig support in DsnIDE5.dpk What about D4? {$ENDIF} TypInfo, Forms, Dialogs, StdCtrls, ComCtrls, EasyListview, ExtCtrls, CommCtrl, ImgList, ToolWin, ActnList, Menus; type TFormEasyCollectionEditor = class(TDesignWindow) ListView1: TListView; ToolBar1: TToolBar; ToolButton1: TToolButton; ToolButton2: TToolButton; ToolButton3: TToolButton; ToolButton4: TToolButton; ImageList1: TImageList; ToolButton5: TToolButton; ActionList1: TActionList; ActionNewItem: TAction; ActionDeleteItem: TAction; ActionUpItem: TAction; ActionDownItem: TAction; ToolButton6: TToolButton; PopupMenu1: TPopupMenu; Add1: TMenuItem; Delete1: TMenuItem; MoveUp1: TMenuItem; MoveDown1: TMenuItem; N1: TMenuItem; StatusBar1: TStatusBar; procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure ActionNewItemExecute(Sender: TObject); procedure ActionDeleteItemExecute(Sender: TObject); procedure ActionUpItemExecute(Sender: TObject); procedure ActionDownItemExecute(Sender: TObject); procedure ListView1Resize(Sender: TObject); procedure ListView1Change(Sender: TObject; Item: TListItem; Change: TItemChange); procedure ListView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure ListView1KeyPress(Sender: TObject; var Key: Char); private { Private declarations } FCollection: TEasyCollection; FListview: TCustomEasyListview; FRebuilding: Boolean; protected procedure DoShow; override; procedure Rebuild; procedure Refresh; procedure HandleDelete; {$IFNDEF COMPILER_6_UP} function UniqueName(Component: TComponent): string; override; {$ENDIF} public { Public declarations } {$IFDEF COMPILER_6_UP} procedure DesignerClosed(const Designer: IDesigner; AGoingDormant: Boolean); override; procedure ItemDeleted(const ADesigner: IDesigner; Item: TPersistent); override; procedure ItemsModified(const Designer: IDesigner); override; {$ENDIF} {$IFNDEF COMPILER_6_UP} procedure ComponentDeleted(Component: IPersistent); override; procedure FormClosed(AForm: TCustomForm); override; procedure FormModified; override; {$ENDIF} property Collection: TEasyCollection read FCollection write FCollection; property Listview: TCustomEasyListview read FListview write FListview; property Rebuilding: Boolean read FRebuilding write FRebuilding; end; PFormRegRec = ^TFormRegRec; TFormRegRec = record Form: TForm; Collection: TEasyCollection; Listview: TCustomEasyListview; end; TEditorList = class private FFormList: TThreadList; protected property FormList: TThreadList read FFormList write FFormList; public constructor Create; destructor Destroy; override; function CollectionRegistered(Collection: TEasyCollection; var Form: TForm): Boolean; procedure ListviewDestroying(Listview: TCustomEasyListview; DestroyAll: Boolean = False); procedure RegisterEditor(Form: TForm; Collection: TEasyCollection; Listview: TCustomEasyListview); procedure UnRegister(Form: TForm; Collection: TEasyCollection); end; TEasyCollectionEditor = class(TPropertyEditor) public procedure Edit; override; function GetAttributes: TPropertyAttributes; override; function GetValue: string; override; end; procedure ShowEasyCollectionEditor(ADesigner: {$IFDEF COMPILER_6_UP}IDesigner{$ELSE}IFormDesigner{$ENDIF}; ACollection: TEasyCollection); var EditorList: TEditorList; implementation {$R *.dfm} type TEasyCollectionItemHack = class(TEasyCollectionItem); {$IFDEF COMPILER_6_UP} // To make the transition between D5's IPersistent and D6 and D7's TPersistent function ExtractPersistent(Inst: TPersistent): TPersistent; begin Result := Inst; end; {$ENDIF COMPILER_6_UP} procedure ShowEasyCollectionEditor(ADesigner: {$IFDEF COMPILER_6_UP}IDesigner{$ELSE}IFormDesigner{$ENDIF}; ACollection: TEasyCollection); var F: TForm; Selections: IDesignerSelections; begin {$IFDEF COMPILER_6_UP} Selections := TDesignerSelections.Create; {$ELSE} Selections := CreateSelectionList; {$ENDIF COMPILER_6_UP} ADesigner.GetSelections(Selections); if Selections.Count = 1 then begin if (ExtractPersistent( Selections[0]) is TCustomEasyListview) or (ExtractPersistent( Selections[0]) is TEasyGroup) then begin if not EditorList.CollectionRegistered(ACollection, F) then begin F := TFormEasyCollectionEditor.Create(Application); {$IFDEF COMPILER_9_UP} F.FormStyle := fsStayOnTop; // bug in D2005 {$ENDIF COMPILER_9_UP} TFormEasyCollectionEditor(F).Collection := ACollection; TFormEasyCollectionEditor(F).Designer := ADesigner; if (ExtractPersistent( Selections[0]) is TCustomEasyListview) then begin TFormEasyCollectionEditor(F).Listview := TCustomEasyListview( Selections[0]); EditorList.RegisterEditor(F, ACollection, TCustomEasyListview( Selections[0])) end else begin TFormEasyCollectionEditor(F).Listview := (TEasyGroup( Selections[0])).OwnerListview; EditorList.RegisterEditor(F, ACollection, (TEasyGroup( Selections[0])).OwnerListview); end; F.Show end else begin F.Show; SetForegroundWindow(F.Handle); end end end end; { TEditorList } constructor TEditorList.Create; begin FormList := TThreadList.Create; end; destructor TEditorList.Destroy; begin FormList.Free; inherited Destroy; end; function TEditorList.CollectionRegistered(Collection: TEasyCollection; var Form: TForm): Boolean; var List: TList; i: Integer; RegRec: PFormRegRec; begin Result := False; Form := nil; List := FormList.LockList; try i := 0; while (i < List.Count) and not Result do begin RegRec := PFormRegRec(List[i]); if RegRec.Collection = Collection then begin Form := RegRec.Form; Result := True; end; Inc(i) end finally FormList.UnLockList; end end; procedure TEditorList.ListviewDestroying(Listview: TCustomEasyListview; DestroyAll: Boolean = False); var List: TList; i: Integer; RegRec: PFormRegRec; begin List := FormList.LockList; try i := List.Count - 1; while (i > -1) do begin RegRec := PFormRegRec(List[i]); if (RegRec.Listview = Listview) or DestroyAll then begin RegRec.Form.Hide; RegRec.Form.Release; Dispose( PFormRegRec(List[i])); List.Delete(i) end; Dec(i) end finally FormList.UnLockList; end end; procedure TEditorList.RegisterEditor(Form: TForm; Collection: TEasyCollection; Listview: TCustomEasyListview); var List: TList; RegRec: PFormRegRec; begin List := FormList.LockList; try New(RegRec); RegRec.Form := Form; RegRec.Collection := Collection; RegRec.Listview := Listview; List.Add(RegRec); finally FormList.UnLockList; end end; procedure TEditorList.UnRegister(Form: TForm; Collection: TEasyCollection); var List: TList; i: Integer; RegRec: PFormRegRec; begin List := FormList.LockList; try i := List.Count - 1; while (i > -1) do begin RegRec := PFormRegRec(List[i]); if (RegRec.Form = Form) and (RegRec.Collection = Collection) then begin Dispose( PFormRegRec(List[i])); List.Delete(i) end; Dec(i) end finally FormList.UnLockList; end end; { TEasyCollectionEditor } function TEasyCollectionEditor.GetAttributes: TPropertyAttributes; begin Result := [paDialog, paReadOnly{$IFDEF COMPILER_6_UP}, paVCL{$ENDIF COMPILER_6_UP}]; end; function TEasyCollectionEditor.GetValue: string; begin Result := 'TEasyCollection' end; procedure TEasyCollectionEditor.Edit; begin // WL, 2006/01/22: extracted code to separate method to be able to show // the editor on listview doubleclick. ShowEasyCollectionEditor( Designer, TEasyCollection(GetObjectProp(GetComponent(0), GetPropInfo))); end; procedure TFormEasyCollectionEditor.FormClose(Sender: TObject; var Action: TCloseAction); begin Designer.ClearSelection; EditorList.UnRegister(Self, Collection); Action := caFree end; {$IFNDEF COMPILER_6_UP} procedure TFormEasyCollectionEditor.ComponentDeleted(Component: IPersistent); begin inherited; if ExtractPersistent(Component) = Listview then begin EditorList.ListviewDestroying(Listview); Listview := nil; Collection := nil end end; function TFormEasyCollectionEditor.UniqueName(Component: TComponent): string; begin Result := Designer.UniqueName(Component.ClassName); end; procedure TFormEasyCollectionEditor.FormClosed(AForm: TCustomForm); begin inherited; // D4 and D5 have bug in this somehow so destroy all the editor when any form // is closed. if Designer = Self.Designer then begin EditorList.ListviewDestroying(Listview, True); Listview := nil; Collection := nil end end; procedure TFormEasyCollectionEditor.FormModified; begin Refresh; end; {$ENDIF} {$IFDEF COMPILER_6_UP} procedure TFormEasyCollectionEditor.DesignerClosed(const Designer: IDesigner; AGoingDormant: Boolean); begin inherited; if Designer = Self.Designer then begin EditorList.ListviewDestroying(Listview); Listview := nil; Collection := nil end end; procedure TFormEasyCollectionEditor.ItemDeleted(const ADesigner: IDesigner; Item: TPersistent); begin inherited; if Item = Listview then begin EditorList.ListviewDestroying(Listview); Listview := nil; Collection := nil end end; procedure TFormEasyCollectionEditor.ItemsModified(const Designer: IDesigner); begin Refresh; end; {$ENDIF} procedure TFormEasyCollectionEditor.ActionNewItemExecute(Sender: TObject); begin Collection.BeginUpdate(False); try Collection.Add finally Collection.EndUpdate; Rebuild; InvalidateRect(Collection.OwnerListview.Handle, nil, True); end end; procedure TFormEasyCollectionEditor.ActionDeleteItemExecute( Sender: TObject); var i: Integer; SelIndex: Integer; begin // We are deleting items that are selected in the Designer, clear this. Designer.SetSelections(nil); SelIndex := -1; Collection.BeginUpdate(True); try if Assigned(Listview1.Selected) then SelIndex := Listview1.Selected.Index; i := Listview1.Items.Count - 1; while i > -1 do begin if Listview1.Items[i].Selected then Collection.Delete(i); Dec(i) end finally Collection.EndUpdate(False); Rebuild; if (SelIndex > -1) and (Listview1.Items.Count > 0) then begin if SelIndex < Listview1.Items.Count then Listview1.Items[SelIndex].Selected := True else Listview1.Items[Listview1.Items.Count - 1].Selected := True end; end end; procedure TFormEasyCollectionEditor.ActionUpItemExecute(Sender: TObject); var SelIndex: Integer; begin if Listview1.SelCount = 1 then begin SelIndex := Listview1.Selected.Index; if SelIndex > 0 then begin Collection.Exchange(SelIndex, SelIndex - 1); Rebuild; Dec(SelIndex); Listview1.Items[SelIndex].Selected := True; end end end; procedure TFormEasyCollectionEditor.ActionDownItemExecute(Sender: TObject); var SelIndex: Integer; begin if Listview1.SelCount = 1 then begin if Listview1.Selected.Index < Listview1.Items.Count - 1 then begin SelIndex := Listview1.Selected.Index; Collection.Exchange(SelIndex, SelIndex + 1); Rebuild; Inc(SelIndex); Listview1.Items[SelIndex].Selected := True; end end end; procedure TFormEasyCollectionEditor.Rebuild; var i: Integer; Item: TListItem; begin Rebuilding := True; Listview1.Items.BeginUpdate; try Listview1.Items.Clear; for i := 0 to Collection.Count - 1 do begin Item := Listview1.Items.Add; Item.Caption := TEasyCollectionItemHack(Collection.Items[i]).GetDisplayName; end finally Listview1.Items.EndUpdate; Rebuilding := False; end; end; procedure TFormEasyCollectionEditor.ListView1Resize(Sender: TObject); begin Listview1.Columns[0].Width := Listview1.ClientWidth - 2 end; procedure TFormEasyCollectionEditor.ListView1Change(Sender: TObject; Item: TListItem; Change: TItemChange); var Selections: IDesignerSelections; i: Integer; begin if not(csDestroying in ComponentState) then begin Statusbar1.Panels[0].Text := 'Selected: ' + IntToStr(Listview1.SelCount); if (Change = ctState) and not Rebuilding then begin if Listview1.SelCount = 0 then Designer.SelectComponent(Collection) else if Listview1.SelCount = 1 then Designer.SelectComponent(Collection.Items[Listview1.Selected.Index]) else begin {$IFDEF COMPILER_6_UP} Selections := TDesignerSelections.Create; {$ELSE} Selections := CreateSelectionList; {$ENDIF COMPILER_6_UP} for i := 0 to Listview1.Items.Count - 1 do begin if Listview1.Items[i].Selected then begin {$IFDEF COMPILER_6_UP} Selections.Add( Collection.Items[i]) {$ELSE} Selections.Add( MakeIPersistent(Collection.Items[i])) {$ENDIF COMPILER_6_UP} end end; Designer.SetSelections(Selections); end end; ActionDeleteItem.Enabled := Listview1.SelCount > 0; ActionUpItem.Enabled := Listview1.SelCount > 0; ActionDownItem.Enabled := Listview1.SelCount > 0; end end; procedure TFormEasyCollectionEditor.DoShow; begin inherited; Rebuild; end; procedure TFormEasyCollectionEditor.ListView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if Key = VK_DELETE then ActionDeleteItemExecute(Self) end; procedure TFormEasyCollectionEditor.HandleDelete; begin end; procedure TFormEasyCollectionEditor.ListView1KeyPress(Sender: TObject; var Key: Char); begin {$IFDEF COMPILER_6_UP} Designer.ModalEdit(Key, Self); {$ENDIF} end; procedure TFormEasyCollectionEditor.Refresh; var i: Integer; begin if Assigned(Collection) then begin if Collection.Count = Listview1.Items.Count then begin Rebuilding := True; Listview1.Items.BeginUpdate; try for i := 0 to Collection.Count - 1 do begin Listview1.Items[i].Caption := TEasyCollectionItemHack(Collection.Items[i]).GetDisplayName; end finally Listview1.Items.EndUpdate; Rebuilding := False; end; end else Rebuild; InvalidateRect(Collection.OwnerListview.Handle, nil, True); end end; initialization EditorList := TEditorList.Create; finalization EditorList.Free; end.