//Advanced Delphi Systems Code: ads_DlgUserPreferences
unit ads_DlgUserPreferences;
{Copyright(c)2016 Advanced Delphi Systems

 Richard Maley
 Advanced Delphi Systems
 12613 Maidens Bower Drive
 Potomac, MD 20854 USA
 phone 301-840-1554
 dickmaley@advdelphisys.com

 The code herein can be used or modified by anyone.  Please retain references
 to Richard Maley at Advanced Delphi Systems.  If you make improvements to the
 code please send your improvements to dickmaley@advdelphisys.com so that the
 entire Delphi community can benefit.  All comments are welcome.
}
(*
UnitIndex Master Index Implementation Section Download Units
Description: ads_DlgUserPreferences.pas
This unit contains the following routines.

TfrmUserPreferences.btnCancelClick   TfrmUserPreferences.btnOKClick   TfrmUserPreferences.CheckListBoxDrawItem   TfrmUserPreferences.FormActivate   TfrmUserPreferences.FormClose   TfrmUserPreferences.FormCreate   TfrmUserPreferences.FormDestroy   TfrmUserPreferences.OptionsDrawItem   TfrmUserPreferences.OptionsInit   TfrmUserPreferences.Reset   TfrmUserPreferences.UpdateOptionsFromFile  UserPreferencesDlgDetail   UserPreferencesEdit   UserPreferencesInit   UserPreferencesReset  

*)
interface
Uses ActnList;
Type
  TUserPreferences=class
  Public
    Constructor Create();
    destructor Destroy();override;
    class Function Edit (var a:TActionList): Boolean;
    class Function Init (var a:TActionList): Boolean;
    class Function Reset(var a:TActionList): Boolean;
  End;
Var
  UserPreferences: TUserPreferences;
{ Description:
 - This unit facilitates the integration of Action Items into a user
   preferences system.  The objective is that it is simple and easy
   to use.
 - Each ActionItem should have a unique caption and icon.
 - The Tag value of the ActionItem tells the system how to present
   the action to the user for their user preference selection.
   Tag =0: This Action Item is available for user enabling/disabling
   Tag =2: This Action Item is not optional.
   Tag>=3: This Action Item is not visible ever.
 - User preferences are written to and read from file.  A separate file
   is created for each user.
 - Add ads_DlgUserPreferences to the uses clause of the application
   and any unit that calls TUserPreferences routines.
 - In the project file (*.dpr) add a call to UserPreferences.Init just
   before Application.Run.
   example:
     ...
     UserPreferences.Init(???????.ActionList);
     Application.Run;
   end.

   The assumption is that by this time all gui elements have been
   created and default to visible=True.  Only visible=false ActionItems
   are saved to file and persist between user sessions.  This entry
   in the project file restores saved user preferences.
 - To give the user the opportunity to set their user preferences
   use the following code:
   UserPreferences.Edit(???????.ActionList);

   If the user presses the OK button the values are automatically updated
   and saved to file.
 - To restore default settings call:
   UserPreferences.Reset(???????.ActionList);
 - In the examples above replace ???????.ActionList with the appropriate
   reference to your ActionList.  For example, Form1.ActionList1.
 - UserPreferences: TUserPreferences does not have to be created or destroyed
   because that is being handled in the initialization and finalization
   sections of this unit.
}
implementation

Uses
  Windows,
  ads_Exception,
  ads_File,
  Buttons,
  CheckLst,
  Classes,
  Controls,
Dialogs,
  ExtCtrls,
  Forms,
  Graphics,
  StdCtrls,
  SysUtils
  ;

Var
  UnitName : String='ads_DlgUserPreferences';

type
  TfrmUserPreferences = Class(TScrollingWinControl)
  Public
    Constructor Create(AOwner: TComponent); Override;
    Destructor  Destroy; Override;
  Public
    Panel1: TPanel;
    Panel2: TPanel;
    btnCancel: TBitBtn;
    btnOK: TBitBtn;
    CheckListBox: TCheckListBox;
    //procedure Loaded;override;
    procedure FormCreate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
    procedure btnOKClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure CheckListBoxDrawItem(Control: TWinControl; Index: Integer;
      Rect: TRect; State: TOwnerDrawState);
    procedure FormDestroy(Sender: TObject);
  protected
    { Protected declarations }
    ActionList     : TActionList;
    DirLocalApp    : String;
    DirLocalThisApp: String;
    ExeName        : String;
    ExePath        : String;
    FilePersist    : String;
    List           : TStringList;
    procedure OptionsDrawItem(ActionList:TActionList;Mask:TColor;Control:TWinControl;Index:Integer;Rect:TRect;State:TOwnerDrawState);
  public
    { Public declarations }
    Success        : Boolean;
    procedure OptionsInit();
    procedure Reset(var ActionLst: TActionList);
    procedure UpdateOptionsFromFile(var ActionLst: TActionList);
  end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.FormCreate(Sender: TObject);
Var
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.FormCreate'; Try
  inherited;
  Success:=False;
  If List=nil Then List:= TStringList.Create();
  List.Duplicates:=dupIgnore;
  List.Sorted:=True;
  ExeName             := ExtractFileName(ParamStr(0));
  ExeName             := LowerCase(Copy(ExeName,1,Length(ExeName)-4));
  ExeName             := UpperCase(Copy(ExeName,1,1))+Copy(ExeName,2,Length(ExeName)-1);
  ExePath             := ExtractFilePath(ParamStr(0));
  DirLocalApp         := PathOfAppDataLocal();
  DirLocalThisApp     := DirLocalApp+ExeName+'\';
  If Not DirectoryExists(DirLocalThisApp) Then
    ForceDirectories(DirLocalThisApp);
  FilePersist         := DirLocalThisApp+ExeName+'.Options.txt';
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.FormActivate(Sender: TObject);
Var
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.FormActivate'; Try
  If Tag=0 Then
  Begin
    Success:=False;
    List.Clear;
    OptionsInit();
  End;
  btnCancel.Refresh;
  Application.ProcessMessages();
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.btnCancelClick(Sender: TObject);
Var
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.btnCancelClick'; Try
  Tag:=0;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.btnOKClick(Sender: TObject);
Var
  i         : Integer;
  inIndex   : Integer;
  ActionItem: TAction;
  sgCaption : String;
  sgPersist : String;
  ProcName  : String;
begin
  ProcName  := 'TfrmUserPreferences.btnOKClick'; Try
  Success:=True;
  Tag:=0;
  sgPersist := '';
  For i:=0 To CheckListBox.Count-1 Do
  Begin
    sgCaption:=CheckListBox.Items[i];
    If sgCaption='' Then Continue;
    If CheckListBox.State[i]=cbUnchecked Then
    Begin
      sgPersist:=sgPersist+sgCaption+#13+#10;
    End;
    inIndex:=List.IndexOf(sgCaption);
    If List.Objects[inIndex]=nil Then Continue;
    ActionItem:=TAction(List.Objects[inIndex]);
    If CheckListBox.State[i]=cbUnchecked Then
    Begin
      ActionItem.Visible:=False;
    End
    Else
    Begin
      If CheckListBox.State[i]=cbChecked Then
      Begin
        ActionItem.Visible:=True;
      End;
    End;
  End;
  StrToFile(sgPersist,FilePersist);
  UpdateOptionsFromFile(ActionList);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.FormClose(Sender: TObject;
  var Action: TCloseAction);
Var
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.FormClose'; Try
  Tag:=0;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.CheckListBoxDrawItem(Control: TWinControl;
  Index: Integer; Rect: TRect; State: TOwnerDrawState);
Var
  Mask         : TColor;
  ProcName     : String;
begin
  ProcName     := 'TfrmUserPreferences.CheckListBoxDrawItem'; Try
  Mask         := clNone;
  OptionsDrawItem(
    ActionList ,  //ActionList : TActionList;
    Mask       ,  //Mask       : TColor;
    Control    ,  //Control    : TWinControl;
    Index      ,  //Index      : Integer;
    Rect       ,  //Rect       : TRect;
    State      ); //State      : TOwnerDrawState);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.OptionsDrawItem(ActionList: TActionList;
  Mask: TColor; Control: TWinControl; Index: Integer; Rect: TRect;
  State: TOwnerDrawState);
Var
  Bitmap     : TBitmap;
  Offset     : Integer;
  inCounter  : Integer;
  inMax      : Integer;
  sgText     : String;
  Images     : TImageList;
  Action     : TAction;
  ProcName   : String;
begin
  ProcName  := 'TfrmUserPreferences.OptionsDrawItem'; Try
  Bitmap     := TBitmap.Create();
  inMax      := ActionList.ActionCount - 1;
  Try
    Images   := TImageList(ActionList.Images);
    Bitmap.Mask(Mask);
    With (Control as TCheckListBox).Canvas Do
    Begin
      FillRect(Rect);       { clear the rectangle }
      Offset := 2;          { provide default offset }
      { get the bitmap }
      sgText := (Control as TCheckListBox).Items[Index];
      For inCounter := 0 To inMax Do
      Begin
        Action   := TAction(ActionList.Actions[inCounter]);
        If sgText = Action.Caption Then
        Begin
          Try Images.GetBitmap(Action.ImageIndex,Bitmap); Except End;
          Break;
        End;
      End;
      If Bitmap <> nil Then
      Begin
        BrushCopy(
          Bounds(Rect.Left + 2,
          Rect.Top,
          Bitmap.Width,
          Bitmap.Height),
          Bitmap,
          Bounds(0, 0,
          Bitmap.Width,
          Bitmap.Height),
          clRed);  {render bitmap}
        {add four pixels between bitmap and text}
        Offset := Bitmap.Width + 6;
      End;
      {display the text}
      TextOut(
        Rect.Left + Offset,
        Rect.Top,
        (Control as TCheckListBox).Items[Index])
    End;
  Finally
    Bitmap.Free;
  End;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.OptionsInit();
Var
  Action    : TAction;
  inActions : Integer;
  inActMax  : Integer;
  inActTag  : Integer;
  inIndex   : Integer;
  ProcName  : String;
  sgCaption : String;
Begin
  ProcName  := 'TfrmUserPreferences.OptionsInit'; Try
  CheckListBox.Items.Clear;
  CheckListBox.Sorted := True;
  inActMax       := ActionList.ActionCount-1;
  For inActions := 0 To inActMax Do
  Begin
    Action   := TAction(ActionList.Actions[inActions]);
    inActTag := Action.Tag;
    //A tag = 2 means this action is not optional
    If inActTag = 2 Then Continue;
    If inActTag > 2 Then
    Begin
      //These actions are disabled in this application.
      Action.Visible := False;
      Continue;
    End;
    sgCaption := Trim(Action.Caption);
    If sgCaption='' Then Continue;
    sgCaption:=StringReplace(sgCaption,'&','',[rfReplaceAll]);
    If List=nil Then List:=TStringList.Create();
    List.Add(sgCaption);
    inIndex:=List.IndexOf(sgCaption);
    List.Objects[inIndex]:=TAction(ActionList.Actions[inActions]);
    CheckListBox.Items.Add(sgCaption);
    CheckListBox.Refresh;
    inIndex := CheckListBox.Items.IndexOf(sgCaption);
    Try CheckListBox.Checked[inIndex] := Action.Visible;Except End;
  End;
  Tag:=1;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.FormDestroy(Sender: TObject);
Var
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.FormDestroy'; Try
  FreeAndNil(List);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.UpdateOptionsFromFile(var ActionLst: TActionList);
Var
  i,j       : Integer;
  sgCaption : String;
  inActMax  : Integer;
  Action    : TAction;
  ProcName: String;
begin
  ProcName  := 'TfrmUserPreferences.UpdateOptionsFromFile'; Try
  ActionList:=ActionLst;
  If FilePersist='' Then
  Begin
    ExeName             := ExtractFileName(ParamStr(0));
    ExeName             := LowerCase(Copy(ExeName,1,Length(ExeName)-4));
    ExeName             := UpperCase(Copy(ExeName,1,1))+Copy(ExeName,2,Length(ExeName)-1);
    ExePath             := ExtractFilePath(ParamStr(0));
    DirLocalApp         := PathOfAppDataLocal();
    DirLocalThisApp     := DirLocalApp+ExeName+'\';
    If Not DirectoryExists(DirLocalThisApp) Then
      ForceDirectories(DirLocalThisApp);
    FilePersist         := DirLocalThisApp+ExeName+'.Options.txt';
  End;
  If Not FileExists(FilePersist) Then Exit;
  If List=nil Then List:=TStringList.Create();
  List.LoadFromFile(FilePersist);
  If ActionList=nil Then Exit;
  inActMax       := ActionList.ActionCount-1;
  For j:=0 To inActMax Do
  Begin
    Action:=TAction(ActionList.Actions[j]);
    If Action.Tag<3 Then Action.Visible:=True;
  End;
  For i:=0 To List.Count-1 Do
  Begin
    sgCaption := List[i];

    For j:=0 To inActMax Do
    Begin
      Action:=TAction(ActionList.Actions[j]);
      If Trim(Action.Caption)='' Then Continue;
      If Trim(Action.Caption)=sgCaption Then
      Begin
        Action.Visible:=False;
      End;
    End;
  End;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

//
Unit Description UnitIndex Master Index
procedure TfrmUserPreferences.Reset(var ActionLst: TActionList);
Var
  ProcName: String;
  s       : String;
begin
  ProcName  := 'TfrmUserPreferences.Reset'; Try
  s       := '';
  If FilePersist='' Then
  Begin
    ExeName             := ExtractFileName(ParamStr(0));
    ExeName             := LowerCase(Copy(ExeName,1,Length(ExeName)-4));
    ExeName             := UpperCase(Copy(ExeName,1,1))+Copy(ExeName,2,Length(ExeName)-1);
    ExePath             := ExtractFilePath(ParamStr(0));
    DirLocalApp         := PathOfAppDataLocal();
    DirLocalThisApp     := DirLocalApp+ExeName+'\';
    If Not DirectoryExists(DirLocalThisApp) Then
      ForceDirectories(DirLocalThisApp);
    FilePersist         := DirLocalThisApp+ExeName+'.Options.txt';
  End;
  StrToFile(s,FilePersist);
  UpdateOptionsFromFile(ActionLst);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
end;

Constructor TfrmUserPreferences.Create(AOwner: TComponent);
  Function IsControl(Obj: TObject): Boolean;
  Begin
    Result := (Obj is TControl);
  End;
Var
  ProcName: String;  
Begin
  ProcName  := 'TfrmUserPreferences.Create'; Try
  inherited;
  Self.Parent := TWincontrol(AOwner);

  Panel1 := TPanel.Create(AOwner);
  With Panel1 Do
  Begin
    If IsControl(Panel1) Then
    Begin
      Parent      := Self;
    End;
    Left          := 0;
    Top           := 423;
    Width         := 210;
    Height        := 30;
    Align         := alBottom;
    BevelOuter    := bvNone;
    Caption       := '  ';
    TabOrder      := 0;
  End;

  Panel2 := TPanel.Create(AOwner);
  With Panel2 Do
  Begin
    Parent        := Panel1;
    Left          := 46;
    Top           := 0;
    Width         := 164;
    Height        := 30;
    Align         := alRight;
    BevelOuter    := bvNone;
    Caption       := '  ';
    TabOrder      := 0;
  End;

  btnCancel := TBitBtn.Create(AOwner);
  With btnCancel Do
  Begin
    Parent        := Panel2;
    Left          := 82;
    Top           := 2;
    Width         := 75;
    Height        := 25;
    Hint          := 'Make no changes.';
    TabOrder      := 0;
    OnClick       := btnCancelClick;
    Kind          := bkCancel;
  End;

  btnOK := TBitBtn.Create(AOwner);
  With btnOK Do
  Begin
    Parent        := Panel2;
    Left          := 2;
    Top           := 2;
    Width         := 75;
    Height        := 25;
    Hint          := 'Implement changes';
    TabOrder      := 1;
    OnClick       := btnOKClick;
    Kind          := bkOK;
  End;

  CheckListBox := TCheckListBox.Create(AOwner);
  With CheckListBox Do
  Begin
    If IsControl(CheckListBox) Then
    Begin
      Parent      := Self;
    End;
    Left          := 0;
    Top           := 0;
    Width         := 210;
    Height        := 423;
    Hint          := 'Check to enable, uncheck to disable.';
    Align         := alClient;
    ItemHeight    := 18;
    Style         := lbOwnerDrawVariable;
    TabOrder      := 1;
    OnDrawItem    := CheckListBoxDrawItem;
  End;

  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

Destructor TfrmUserPreferences.Destroy;
Var
  ProcName: String;  
Begin
  ProcName  := 'TfrmUserPreferences.Destroy'; Try
  CheckListBox      .Free;
  btnOK             .Free;
  btnCancel         .Free;
  Panel2            .Free;
  Panel1            .Free;
  inherited Destroy;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

//
Unit Description UnitIndex Master Index
Function UserPreferencesDlgDetail(var ActionLst: TActionList;Init:Boolean;Reset:Boolean): Boolean;
Var
  Dialog    : TForm;
  Form      : TfrmUserPreferences;
  ProcName: String;
Begin
  Result    := False;
  Dialog    := nil;
  ProcName  := 'UserPreferencesDlg'; Try
  Try
    Dialog  := TForm.Create(nil);
    Form       := TfrmUserPreferences.Create(Dialog);
    Form.Parent:= Dialog;
    Form.Align := alClient;
    With Dialog Do
    Begin
      Left          := 359;
      Top           := 152;
      Width         := 218;
      Height        := 480;
      Caption       := 'User Preferences';
      Color         := clBtnFace;
      Font.Color    := clWindowText;
      Font.Height   := -11;
      Font.Name     := 'MS Sans Serif';
      Font.Style    := [];
      OldCreateOrder:= False;
      Position      := poScreenCenter;
      ShowHint      := True;
      OnActivate    := Form.FormActivate;
      OnClose       := Form.FormClose;
      OnCreate      := Form.FormCreate;
      OnDestroy     := Form.FormDestroy;
      PixelsPerInch := 96;
    End;
    If Reset Then
    Begin
      Form.Reset(ActionLst);
      Result:=True;
      Exit;
    End;
    Form.UpdateOptionsFromFile(ActionLst);
    Form.OptionsInit();
    If Init Then
    Begin
      Result:=True;
      Exit;
    End;
    Form.FormCreate(Dialog);
    Application.ProcessMessages();
    Dialog.ShowModal;
    Result:=Form.Success;
  Finally
    Dialog.Free;
  End;
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

//
Unit Description UnitIndex Master Index
Function UserPreferencesEdit(var ActionLst: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'UserPreferencesEdit'; Try
  Result:= UserPreferencesDlgDetail(ActionLst,False,False);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

//
Unit Description UnitIndex Master Index
Function UserPreferencesInit(var ActionLst: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'UserPreferencesInit'; Try
  Result:= UserPreferencesDlgDetail(ActionLst,True,False);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

//
Unit Description UnitIndex Master Index
Function UserPreferencesReset(var ActionLst: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'UserPreferencesReset'; Try
  Result:= UserPreferencesDlgDetail(ActionLst,False,True);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

{ TUserPreferences }
class function TUserPreferences.Edit(var a: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'TUserPreferences.Edit'; Try
  Result:= UserPreferencesDlgDetail(a,False,False);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

class function TUserPreferences.Init(var a: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'TUserPreferences.Init'; Try
  Result:= UserPreferencesDlgDetail(a,True,False);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

class function TUserPreferences.Reset(var a: TActionList): Boolean;
Var
  ProcName: String;
Begin
  Result    := False;
  ProcName  := 'TUserPreferences.Reset'; Try
  Result:= UserPreferencesDlgDetail(a,False,True);
  Except On E : Exception Do RaiseError(UnitName,ProcName,E); End;
End;

constructor TUserPreferences.Create;
begin
  inherited;
end;

destructor TUserPreferences.Destroy;
begin
  inherited;
end;

Initialization
  UserPreferences:=TUserPreferences.Create;
Finalization
  FreeAndNil(UserPreferences);
End.
//