unit UnitFrmPermanentNew;
{
    Purpose:
        This unit stores/reads/edits the permanent items.
        The form is not a dummy form.


    Updates:
        New: Permanent Items Groups sorted by name
        --------------
        Fix: Up button did not range check
        --------------
        Changes for complex item as permanent items,
        show them when it's shown or hovered

        Folder list not cleared when "LoadPermanent" was called
        ----------------
        Made scalable
        New Keystroke support

        -----------------
        Updates for autotamically switching PIGs for a specific program

        -------------------
        Complete Rewrite. Old form was too poorly designed
        to save.
}

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, INIFiles, ComCtrls {for THahsedStringList},
  UnitClipQueue, UnitFrmHotkey, Menus, Buttons, ClipBrd, Contnrs,
  ImgList, UnitFrmTooltipNew, UnitFrameClipDisplay, System.Generics.Collections;

const DEFAULT_FOLDER = 'Default';
      ADDNEW_FOLDER = '<add new>';
      SYSTEM_FOLDER = '_System';
      FORM_MODE_FOLDER = '_Form Mode';
      PERM0_FILE = 'perm0.ini';
      PERM1_FILE = 'perm1.ini';
      PERM0_UNICODEFILE = 'perm0_unicode.ini';
      PERM1_UNICODEFILE = 'perm1_unicode.ini';

      HOTKEY_FILE = 'hotkey.ini';
      EXEPIG_FILE = 'exepigs.ini';

type TDataFileName = (DFN_PERM0, DFN_PERM1, DFN_HOTKEYS);



type
  TFrmPermanent = class(TForm)
    {$REGION 'DEFAULT'}
    pcPermanent: TPageControl;
    TabSheet2: TTabSheet;
    lvAutoSwitch: TListView;
    Label5: TLabel;
    pmGetAs: TPopupMenu;
    PlaintText1: TMenuItem;
    DIBPicture1: TMenuItem;
    CopiedFiles1: TMenuItem;
    RichTExt1: TMenuItem;
    tsEditGroups: TTabSheet;
    tvGroups: TTreeView;
    TabSheet3: TTabSheet;
    pnlText: TPanel;
    pmTree: TPopupMenu;
    miDelete: TMenuItem;
    miMoveto: TMenuItem;
    btnFile: TSpeedButton;
    ilPerm: TImageList;
    Memo1: TMemo;
    Label1: TLabel;
    btnNewItem: TSpeedButton;
    btnEditItem: TSpeedButton;
    timCloseTooltip: TTimer;
    fClipDisplay: TFrameClipDisplay;
    txtCommands: TRichEdit;
    pnlHotkey: TPanel;
    Shape1: TShape;
    lblHotkey: TLabel;
    Label2: TLabel;
    btnSetHotkey: TSpeedButton;
    btnClearHotkey: TSpeedButton;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnAddPIGClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure tsEditGroupsShow(Sender: TObject);
    procedure tvGroupsExpanding(Sender: TObject; Node: TTreeNode;
      var AllowExpansion: Boolean);
    procedure tvGroupsDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure tvGroupsEndDrag(Sender, Target: TObject; X, Y: Integer);
    procedure tvGroupsCollapsing(Sender: TObject; Node: TTreeNode;
      var AllowCollapse: Boolean);
    procedure btnNewItemxClick(Sender: TObject);
    procedure miDeleteClick(Sender: TObject);
    procedure btnEditItemxClick(Sender: TObject);
    procedure miMovetoItemClick(Sender: TObject);
    procedure pmTreePopup(Sender: TObject);
    procedure btnFileClick(Sender: TObject);
    procedure tvGroupsCustomDraw(Sender: TCustomTreeView; const ARect: TRect;
      var DefaultDraw: Boolean);
    procedure tvGroupsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode;
      State: TCustomDrawState; var DefaultDraw: Boolean);
    procedure tvGroupsChange(Sender: TObject; Node: TTreeNode);
    procedure timCloseTooltipTimer(Sender: TObject);
    procedure btnSetHotkeyClick(Sender: TObject);
    procedure btnClearHotkeyClick(Sender: TObject);

    //procedure Button1Click(Sender: TObject);
    {$ENDREGION}
  private
  { Private declarations }
        SelectedIndex : integer;
        SelectedGroup : string;
        AppPath : string;
        CurrentPermPath : string;
        FolderList : TStringList;



        // legacy data
        ItemNameList : TStringList;
        ItemDataList : TStringList;
        ItemHotKeyList : TStringList;

        ItemClip : TObjectList<TClipItem>;

        EXEPigList : THashedStringList;
        EXEPasteList : THashedStringList;
        //Tooltip : TTooltipWindow;

        HotkeyIDToIndex : TStringList;
        HotkeyObjects2 : TStringList;
        PushpopPath : string;
        EditModeOn : boolean;
        hkd : THotkeyData;

        //treenodedrag : TTreeNode;
        accepteddrag : boolean;
        // util functions

        LastSavedGroup : string;
        LastSavedItemIndex : integer;
        ignorecollapse : boolean;
        isrightclick : boolean;

        hotkeyTooltip : TFrmTooltipNew;
        function IsComplexItem(s: string): boolean;
        function GetComplexItem(s: string): TClipItem;

        function GetOldDataFilename (i: integer): string;
        function GetDataFilename (i: TDataFileName; ForRead : boolean = true; overridefolder : string = ''): string;

        function ToHKString(hkd : THotkeyData) : string;
        function FromString(s : string) : THotKeyData;
        procedure UpdateFolderList;
        procedure LoadPermanentItemsGroup;
        procedure SavePermanentItemsGroup;
        function IsPermanentPathFolder(folder : string) : boolean;
        procedure RefreshFormData;


        function GetNextComplexIndex : integer;
        function ExtractComplexIndex(s : string) : integer;


        procedure RefreshPermanentGroups(selectCurrentGroup : boolean);
        procedure SelectTreeItem(group : string; index : integer);
        procedure ShowSelectedTreeItem;
        procedure HandleItemSelect;

        function MakeClipReference(i : integer) : string;

        procedure SaveHotkey(group : string; index:integer; hotkey : string);
        procedure MoveItem(index : integer; location : string; newLocation : string);

        // legacy
        procedure ShowWithNewItem(item : string; name : string = ''; hotkey : string = ''); overload;
        procedure ShowWithNewComplexItem(ci : TClipItem; name : string = ''; hotkey : string = ''); overload;
        procedure ShowAndSelect(groupID, itemID : integer);
        procedure ShowAndSelectSystem;
        procedure ShowForm;
  protected
        procedure CreateParams(var Params: TCreateParams); override;
  public
  { Public declarations }

        procedure SetPermanentPath( path : string );
        procedure SetPermanentGroup(i : integer);
        function GetPermanentPath : string;
        function GetPermanentPathIndex : integer;
        function GetPermanentPathFull : string;
        // to enumerate permanent item names
        function PermFoldersGetCount : cardinal;

        function PermFoldersGetItem(index : cardinal) : string;
        function PermFolderIsSystem(index : cardinal) : boolean;
        function PermFolderToIndex(path : string) : integer;
        procedure RefreshHotkeys;

        procedure PermFolderPush;
        procedure PermFolderPop;


        // to enumerate items in the current permanent folder
        function GetCount: integer;
        function GetItemFormat(i:integer) : TClipFormatType;
        function GetItemClip(i : integer) : TClipItem; overload;
        function GetItemClip(i : integer; group : integer) : TClipItem; overload;
        function GetItemName(i: integer): string;
        function GetItemText(i: integer): string; overload;
        function GetItemText(index : integer; group : integer):string; overload;
        function GetItemHotkeyName(i : integer) : string; overload;
        function GetItemHotkeyName(s: string): string;   overload;
        procedure GetNamesAndHotkeys(sl : TStringList);

        // display form with a newly created item

        procedure AutoSwitch(EXEName : string);
        procedure AssignPIG(EXEName : string);

        function GetHotkeyCount : integer;
        function GetHotkey(index : integer) : string;
        function GetHotkeyIdent(index : integer) : integer;
        function IsHotkeyIdent(ident : integer) : boolean;
        procedure ReportHotkey(index : integer);

        procedure MoveClip(oldi, newi : integer);

        procedure RefreshGroups;
  end;

var
  FrmPermanent: TFrmPermanent;
const EMPTY_HOTKEY_NAME = 'none';
{////////////////////}
{//}implementation{//}
{////////////////////}

uses UnitMisc, StrUtils, UnitFrmClipboardManager, UnitFrmMove, UnitHotKey,
   UnitFrmMainPopup, UnitPopupGenerate, UnitTWideChar,
  UnitFrmPermanentEdit, UnitKeyboardQuery,
  System.Character, UnitToken, UnitFrmConfig, UnitFrmDebug, UnitClipDatabase, UnitPaste;

{$R *.dfm}

const NEW_GROUP_TEXT = '[New Group ...]';

//
// public API
//

procedure TFrmPermanent.RefreshGroups;
begin
    self.UpdateFolderList;
end;

function TFrmPermanent.IsHotkeyIdent(ident: integer): boolean;
begin
    result := HotkeyIDToIndex.Values[IntToStr(ident)] <> '';
end;
procedure TFrmPermanent.ReportHotkey(index: integer);
var s, sname, shotkey, stext : string;
    i,j : integer;
    ci : TPermanentClipItem;
begin
    try
        s := HotkeyIDToIndex.Values[IntToStr(index)];
    except
    end;
    if (trim(s) <> '') then begin
        i := StrToInt('$' + copy(s,1,8));
        j := StrToInt('$' + copy(s,9,8));

        if (TClipDatabase.hasPermanentClips) then begin
            TClipDatabase.StartBatch;
            ci := TPermanentClipItem.Create;
            TClipDatabase.LoadPermanent(ci,j, PermFoldersGetItem(i));
            sname := ci.getDisplayText;

            shotkey := TClipDatabase.LoadPermanentHotkey(j, PermFoldersGetItem(i));
            shotkey := self.GetItemHotkeyName(shotkey);

            TClipDatabase.EndBatch;
        end else begin
            self.PermFolderPush;
            try
                self.SetPermanentPath( self.PermFoldersGetItem(i) );

                sname := self.GetItemName(j);
                shotkey := GetItemHotkeyName(j);
            finally
                self.PermFolderPop;
            end;
        end;
        if FrmConfig.cbHotkeyTooltip.checked then begin
            if not assigned(hotkeyTooltip) then begin
                hotkeyTooltip := TFrmTooltipNew.Create(application);
            end;
            hotkeyTooltip.HideHeader;
            hotkeyTooltip.ShowTooltip(sname, mouse.CursorPos,shotkey);


            Application.ProcessMessages;
            timCloseTooltip.enabled := true;
        end;
        if (ci<>nil) then begin
            FrmMainPopup.SendClip(ci);
            myfree(ci);
        end;


    end;
end;


procedure TFrmPermanent.SetPermanentGroup(i: integer);
begin
    self.SetPermanentPath( self.PermFoldersGetItem(i) );
end;
procedure TfrmPermanent.SetPermanentPath( path : string );
begin
    if (self.EditModeOn) then begin
        ShowMessage('Cannot switch groups while editing a permanent item.');
        EXIT;
    end;


    CurrentPermPath := path;
    self.LoadPermanentItemsGroup;
end;
function TfrmPermanent.GetPermanentPath : string;
begin
    result := self.CurrentPermPath;
end;
function TfrmPermanent.GetPermanentPathIndex : integer;
begin
    result := self.FolderList.IndexOf(self.CurrentPermPath);
end;
function TFrmPermanent.GetPermanentPathFull: string;
begin
    result := IncludeTrailingPathDelimiter(
        IncludeTrailingPathDelimiter(self.AppPath) + self.GetPermanentPath
    );
end;


function TfrmPermanent.PermFoldersGetCount : cardinal;
begin
    result := FolderList.Count;
end;
function TfrmPermanent.PermFoldersGetItem(index : cardinal) : string;
begin
    result := FolderList[index];
end;
function TfrmPermanent.PermFolderIsSystem(index : cardinal) : boolean;
begin
    result := (PermFoldersGetItem(index) =  SYSTEM_FOLDER) or
    (PermFoldersGetItem(index) =  FORM_MODE_FOLDER);
end;
function TfrmPermanent.PermFolderToIndex(path : string) : integer;
begin
    result := FolderList.IndexOf(path);
end;

function TfrmPermanent.GetCount: integer;
begin
    if (TClipDatabase.hasPermanentClips) then begin
        result := ItemClip.Count;
    end else begin
        result := ItemNameList.Count;
    end;
end;
function TfrmPermanent.GetItemClip(i : integer; group : integer) : TClipItem;
begin
    if (group = -1) then begin
        result := GetItemClip(i);
        EXIT;
    end;

    result := TClipITem.Create;
    TClipDatabase.LoadPermanent(result, i, PermFoldersGetItem(group));
end;
function TfrmPermanent.GetItemClip(i : integer) : TClipItem;
var
    s : string;
begin
    if (TClipDatabase.hasPermanentClips) then begin
        result := ItemCLip[i];
    end else begin
        s := GetItemText(i);
        if (FrmPermanent.IsComplexItem(s)) then begin
            result := FrmPermanent.GetComplexItem(s);
        end else begin
            result := TClipItem.Create;
            result.SetFromPlainText( s );
        end;
    end;
end;
function TfrmPermanent.GetItemName(i: integer): string;
begin
    if (TClipDatabase.hasPermanentClips) then begin
        result := ItemClip[i].getDisplayText;
    end else begin
        result := ItemNameList[i];
    end;
end;
function TfrmPermanent.GetItemText(index : integer; group : integer) : string;
var
    ci : TClipItem;
begin
    if (group = -1) then begin
        result := self.GetItemName(index);
        EXIT;
    end;

    ci := TClipITem.Create;
    TClipDatabase.LoadPermanent(ci,index, PermFoldersGetItem(group));

    result := ci.GetAsPlaintext;
    myfree(ci);
end;
function TfrmPermanent.GetItemText(i: integer): string;
begin
    if (TClipDatabase.hasPermanentClips) then begin
        result := GetItemClip(i).GetAsPlaintext;
    end else begin
        result := ItemDataList[i];
    end;
end;


function TFrmPermanent.GetItemHotkeyName(s: string): string;
var hkd : THotkeyData;
begin
    hkd := FromString(s);
    if hkd.name = EMPTY_HOTKEY_NAME then
        result := ''
    else
        result := hkd.name;
end;
function TFrmPermanent.GetItemHotkeyName(i: integer): string;
begin
    GetItemHotkeyName(ItemHotKeyList[i]);
end;

function TFrmPermanent.GetItemFormat(i:integer) : TClipFormatType;
begin
    result := ItemClip[i].GetFormatType;
end;

procedure TfrmPermanent.GetNamesAndHotkeys(sl : TStringList);
var i, j : integer;
    s, group : string;
    keys, captions : TStringList;
begin
    sl.Clear;
    keys := TStringList.Create;
    captions := TStringList.Create;

    for i := 0 to self.PermFoldersGetCount-1 do begin
        group := PermFoldersGetItem(i);

        keys.Clear;
        captions.Clear;

        TClipDatabase.LoadPermanentHotkeys(keys, group);
        TClipDatabase.LoadPermanentNames(captions, group);

        for j := 0 to keys.Count-1 do begin
            s := self.GetItemHotkeyName( keys[j] );
            if s <> '' then begin
                sl.Values[captions[j]] := s;
            end;
        end;
    end;
end;


procedure TFrmPermanent.ShowAndSelectSystem;
var
    s : string;
    i : integer;
begin
    for i := 0 to self.PermFoldersGetCount-1 do begin
        if self.PermFoldersGetItem(i) = SYSTEM_FOLDER then begin
            self.ShowAndSelect(i,0);
            EXIT;
        end;
    end;
end;
procedure TFrmPermanent.ShowAndSelect(groupID, itemID: integer);
var s : string;
begin
    self.Show;

    Windows.SetForegroundWindow(self.Handle); 

    self.pcPermanent.ActivePageIndex := 0;

    s := self.CurrentPermPath;
    if groupID <> -1 then
        s := self.PermFoldersGetItem(groupID);
    self.SelectTreeItem(s, itemID);
end;
procedure TFrmPermanent.ShowForm;
begin
    self.Show;
end;
procedure TfrmPermanent.ShowWithNewItem(item : string; name : string = '';hotkey : string = '');
begin
    self.Show;
    self.pcPermanent.ActivePageIndex := 0;
    self.SelectTreeItem(self.CurrentPermPath, -1);
    FrmPermanentEdit.SetNewItem(item, name);
    self.btnNewItem.Click;
end;
procedure TFrmPermanent.ShowWithNewComplexItem(ci: TClipItem; name : string = '';hotkey : string = '');
var i : integer;
begin
	if ci.GetFormatType = FT_UNICODE then begin
		self.ShowWithNewItem(ci.GetAsPlaintext);
    end else begin
        i := self.GetNextComplexIndex;
        if (ci <> nil) then begin
            ci.SaveToFile(self.GetPermanentPathFull, i, CI_FILEMASK_ALL);
            ci.FinishedWithStream;
        end;

        self.ShowWithNewItem(MakeClipReference(i), name, hotkey);
	end;
end;


//
// Create / Destroy
//
procedure TFrmPermanent.CreateParams(var Params: TCreateParams);
begin
  inherited;
    // allow context taskbar context menu and
    // show me on the taskbar - independant of main form
    with Params do begin
        ExStyle := ExStyle or WS_EX_APPWINDOW;
        WndParent := GetDesktopwindow;
    end;
end;
procedure TFrmPermanent.FormCreate(Sender: TObject);
var name: string;
    lc : TListColumn;
    sl : TStringList;
    i : integer;
    b : boolean;
    procedure EmptyFile(name : string);
    var t : textfile;
    begin
        assignfile(t,name);
        rewrite(t);
        closefile(t);
    end;
    procedure ConvertClipsToV2;
    var i,j : integer;
        s : string;

        procedure ConvertClip(s : string; i : integer);
        var idx, j, k : integer;
            ci : TClipITem;
        begin
            ci := TClipItem.Create;
            ci.LoadFromFile(self.GetPermanentPathFull, ExtractComplexIndex(s) );

            // find where reference to this clip
            idx := -1;
            for j := 0 to ItemDataList.Count - 1 do begin
                if ItemDataList[j] = s then begin
                    idx := j;
                    BREAK;
                end;
            end;
            if (idx <> -1) then begin
                // save to the new format and update the reference
                k := self.GetNextComplexIndex;
                ci.SaveToFile(self.GetPermanentPathFull, k, CI_FILEMASK_ALL);
                ItemDataList[idx] := self.MakeClipReference(k);
            end;
            myfree(ci);
        end;
    begin
        for i := 0 to self.PermFoldersGetCount-1 do begin
            self.SetPermanentGroup(i);
            for j := 0 to self.GetCount - 1 do begin
                s := self.GetItemText(j);
                if IsComplexItem(s) then begin
                    if TCharacter.ToLower(s[4])='l' then
                        convertclip(s, j); // trigers the conversion
                end;
            end;
            self.SavePermanentItemsGroup;
        end;
    end;
    procedure FormatTheHelpText;
    var
        s : string;
        line : string;
    begin
        s := txtCommands.Text;
        txtCommands.Text := '';
        txtCommands.Lines.Clear;
        UnitToken.TokenString(s, '{', false);

        while s <> '' do begin
            line := UnitToken.TokenString(s,'}', false);
            with txtCommands do begin
                SelStart := length(txtCommands.Text);
                SelAttributes.Style := [fsBold];
                Lines.Add(line);
                SelAttributes.Style := [];
                line := UnitToken.TokenString(s, '{', false);
                lines.Add(line);
            end;
        end;
    end;
    procedure SavePerm(caption, text : string; index : integer; location : string);
    var
        ci : TClipItem;

    begin
        ci := TClipItem.Create;
        ci.SetDisplayText(caption);
        ci.SetFromPlainText(text);
        TClipDatabase.SavePermanent(ci,index, location);
        MyFree(ci);
    end;
    procedure CreateSystemGroup;
    var i : integer;
    begin
        i := 0;
        TClipDatabase.SavePermanentGroup(SYSTEM_FOLDER,FolderList.Count);
        TClipDatabase.StartBatch;
            SavePerm('Paste PopupClip 1', '[KEYS][TOCLIPBOARD=0][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 2', '[KEYS][TOCLIPBOARD=1][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 3', '[KEYS][TOCLIPBOARD=2][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 4', '[KEYS][TOCLIPBOARD=3][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 5', '[KEYS][TOCLIPBOARD=4][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 6', '[KEYS][TOCLIPBOARD=5][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 7', '[KEYS][TOCLIPBOARD=6][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 8', '[KEYS][TOCLIPBOARD=7][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 9', '[KEYS][TOCLIPBOARD=8][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Paste PopupClip 10', '[KEYS][TOCLIPBOARD=9][PASTEDEFAULT]',i,SYSTEM_FOLDER);inc(i);

            SavePerm('Show Window: Search and Paste','[KEYS][WINDOWSEARCH]',i,SYSTEM_FOLDER); inc(i);
            SavePerm('Show Window: Configuration','[KEYS][WINDOWCONFIG]',i,SYSTEM_FOLDER); inc(i);
            SavePerm('Show Window: Edit Clips','[KEYS][WINDOWHISTORY]',i,SYSTEM_FOLDER); inc(i);
            SavePerm('Show Window: Edit Clipboard','[KEYS][WINDOWCLIPBOARD]',i,SYSTEM_FOLDER);  inc(i);
            SavePerm('Show Window: Permanent Clips','[KEYS][WINDOWPERMANENT]',i,SYSTEM_FOLDER); inc(i);
            SavePerm('Show Window: Paste Selected Text','[KEYS][WINDOWPASTESEL]',i,SYSTEM_FOLDER);  inc(i);

            SavePerm('Alter Clipboard: Upper Case', '[KEYS][CLIPBOARDUPPER]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Lower Case', '[KEYS][CLIPBOARDLOWER]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Capitalize Words', '[KEYS][CLIPBOARDCAPWORDS]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Inverse Case', '[KEYS][CLIPBOARDINVERSE]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Replace Tabs with Spaces', '[KEYS][CLIPBOARDFIND="\t" REPLACE="    "]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Remove Linefeeds', '[KEYS][CLIPBOARDFIND="\n" REPLACE=""]',i,SYSTEM_FOLDER);inc(i);
            SavePerm('Alter Clipboard: Delete', '[KEYS][CLEAR]',i,SYSTEM_FOLDER);inc(i);

            SavePerm('Run: Google Search Highlighted', '[KEYS][COPYWAIT=2000][RUN]http://www.google.com/search?q=[CLIPBRDCURRENT]',i,SYSTEM_FOLDER);inc(i);
        TClipDatabase.EndBatch;
    end;
    procedure CreateFormModeGroup;
    var i : integer;
    begin
        i := 0;
        TClipDatabase.SavePermanentGroup(FORM_MODE_FOLDER,FolderList.Count);
        TClipDatabase.StartBatch;
            SavePerm('>> Press TAB', '[KEYS][TAB]',i,FORM_MODE_FOLDER);inc(i);
            SavePerm('>> Press SPACE', '[KEYS][SPACE]',i,FORM_MODE_FOLDER);inc(i);
        TClipDatabase.EndBatch;
    end;
begin
    FrmDebug.AppendLog('FrmPermanent - creating');

    self.Font.Size := 8;

    self.FolderList := TStringList.Create;
    self.ItemNameList := TStringList.Create;
    self.ItemDataList := TStringList.Create;
    self.ItemHotKeyList := TStringList.Create;
    self.EXEPigList := THashedStringList.Create;
    self.EXEPasteList := THashedStringList.Create;

    self.ItemClip := TObjectList<TClipItem>.Create;
    self.ItemClip.OwnsObjects := true;

    self.HotkeyIDToIndex := TStringList.Create;
    self.HotkeyObjects2 := TStringList.Create;

    self.CurrentPermPath := DEFAULT_FOLDER;
    self.AppPath := UnitMisc.GetAppPath;

    // fix
    if FileExists(self.AppPath + EXEPIG_FILE) then begin
        self.EXEPigList.LoadFromFile(self.AppPath + EXEPIG_FILE);
    end;

    //
    // init the Auto Switch window
    //
    lc := lvAutoSwitch.Columns.Add;
    lc.Caption := 'Program Name';
    lc.Width := 100;

    lc := lvAutoSwitch.Columns.Add;
    lc.Caption := 'Group';
    lc.Width := 100;

    pcPermanent.ActivePageIndex := 0;
    pcPermanent.Align := alClient;

    self.PushpopPath := '';

    fClipDisplay.ShowClip('');
    fClipDisplay.PicStretched := true;
    tvGroups.DoubleBuffered := true;

    FormatTheHelpText;

    if (TClipDatabase.hasPermanentGroups) then begin
        self.UpdateFolderList;
        b := false;
        if (FolderList.IndexOf(SYSTEM_FOLDER) = -1) then begin
            CreateSystemGroup;
            b := true;
        end;
        if (FolderList.IndexOf(FORM_MODE_FOLDER) = -1) then begin
            CreateFormModeGroup;
            b := true;
        end;

        if (FolderList.IndexOf(DEFAULT_FOLDER) = -1) then begin
            TClipDatabase.SavePermanentGroup(DEFAULT_FOLDER,FolderList.Count);
            b := true;
        end;
        if (b) then self.UpdateFolderList;
        self.RefreshHotkeys;
    end else begin
        //
        // make the new Default directory and import and
        // current permanent items
        //
        {$region 'default folder create'}
        if not DirectoryExists( self.AppPath + DEFAULT_FOLDER) then begin
            mkdir(self.AppPath + DEFAULT_FOLDER);
            self.PermFolderPush;
            self.CurrentPermPath := DEFAULT_FOLDER;

            name := GetDataFilename(DFN_PERM0);
            EmptyFile(name);
            name := GetDataFilename(DFN_HOTKEYS);
            EmptyFile(name);
            name := GetDataFilename(dfn_perm1);
            EmptyFile(name);
            self.PermFolderPop;


            name := GetOldDataFilename(0);
            if FileExists(name) then
                copyfile(pchar(name), PChar(GetDataFileName(DFN_PERM0)), true);

            name := GetOldDataFilename(1);
            if fileExists(name) then
                copyfile(pchar(name), PChar(GetDataFilename(DFN_PERM1)), true);
        end;
        {$endregion}
        {$region 'system folder create'}
        if not DirectoryExists( self.AppPath+SYSTEM_FOLDER ) then begin
            mkdir(self.AppPath+SYSTEM_FOLDER);
            self.PermFolderPush;
                self.CurrentPermPath := SYSTEM_FOLDER;
                ItemNameList.Add('Paste PopupClip 1');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=0][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 2');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=1][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 3');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=2][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 4');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=3][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 5');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=4][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 6');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=5][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 7');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=6][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 8');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=7][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 9');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=8][PASTEDEFAULT]');
                ItemNameList.Add('Paste PopupClip 10');
                ItemDataList.Add('[KEYS][TOCLIPBOARD=9][PASTEDEFAULT]');

                ItemNameList.Add('Show Window: Search and Paste');
                ItemDataList.Add('[KEYS][WINDOWSEARCH]');
                ItemNameList.Add('Show Window: Configuration');
                ItemDataList.Add('[KEYS][WINDOWCONFIG]');
                ItemNameList.Add('Show Window: Edit Clips');
                ItemDataList.Add('[KEYS][WINDOWHISTORY]');
                ItemNameList.Add('Show Window: Edit Clipboard');
                ItemDataList.Add('[KEYS][WINDOWCLIPBOARD]');
                ItemNameList.Add('Show Window: Permanent Clips');
                ItemDataList.Add('[KEYS][WINDOWPERMANENT]');
                ItemNameList.Add('Show Window: Paste Selected Text');
                ItemDataList.Add('[KEYS][WINDOWPASTESEL]');

                ItemNameList.Add('Alter Clipboard: Upper Case');
                ItemDataList.Add('[KEYS][CLIPBOARDUPPER]');
                ItemNameList.Add('Alter Clipboard: Lower Case');
                ItemDataList.Add('[KEYS][CLIPBOARDLOWER]');
                ItemNameList.Add('Alter Clipboard: Capitalize Words');
                ItemDataList.Add('[KEYS][CLIPBOARDCAPWORDS]');
                ItemNameList.Add('Alter Clipboard: Inverse Case');
                ItemDataList.Add('[KEYS][CLIPBOARDINVERSE]');
                ItemNameList.Add('Alter Clipboard: Replace Tabs with Spaces');
                ItemDataList.Add('[KEYS][CLIPBOARDFIND="\t" REPLACE="    "]');
                ItemNameList.Add('Alter Clipboard: Remove Linefeeds');
                ItemDataList.Add('[KEYS][CLIPBOARDFIND="\n" REPLACE=""]');
                ItemNameList.Add('Alter Clipboard: Delete');
                ItemDataList.Add('[KEYS][CLEAR]');

                ItemNameList.Add('Run: Google Search Highlighted');
                ItemDataList.Add('[KEYS][COPYWAIT=2000][RUN]http://www.google.com/search?q=[CLIPBRDCURRENT]');

                self.SavePermanentItemsGroup;
            self.PermFolderPop;
        end;
        {$endregion}
        self.UpdateFolderList;
        self.RefreshHotkeys;
        ConvertClipsToV2;
    end;
end;
procedure TFrmPermanent.FormDestroy(Sender: TObject);
begin
    self.EXEPigList.SaveToFile(self.AppPath + EXEPIG_FILE );
    MyFree(self.EXEPasteList);
    MyFree(self.ItemNameList);
    MyFree(self.ItemDataList);
    MyFree(self.ItemHotkeyList);
    MyFree(self.FolderList);
    //MyFree(self.Tooltip);
//    MyFree(self.Clipboard);
end;
//
// Util Functions
//
function TfrmPermanent.GetOldDataFilename(i: integer): string;
begin
    result := self.AppPath + 'perm' + IntToStr(i) + '.ini';
end;
function TfrmPermanent.GetDataFilename(i: TDataFileName; ForRead : boolean = true; overridefolder : string = ''): string;
var
    s : string;
    folder : string;
begin
    folder := CurrentPermPath;
    if overridefolder <> '' then begin
        folder := overridefolder;
    end;

    case i of
    DFN_PERM0 : result := IncludeTrailingPathDelimiter(self.AppPath + folder) + PERM0_FILE;
    DFN_PERM1 : result := IncludeTrailingPathDelimiter(self.AppPath + folder) + PERM1_FILE;
    DFN_HOTKEYS : result := IncludeTrailingPathDelimiter(self.AppPath + folder) + HOTKEY_FILE;
    end;


    case i of
    DFN_PERM0: begin
        s := IncludeTrailingPathDelimiter(self.AppPath + folder) + PERM0_UNICODEFILE;
        if (forread and fileexists(s)) or (not forread) then begin
            result := s;
        end;
    end;
    DFN_PERM1: begin
        s := IncludeTrailingPathDelimiter(self.AppPath + folder) + PERM1_UNICODEFILE;
        if (forread and fileexists(s)) or (not forread) then begin
            result := s;
        end;
    end;
    end;
end;

procedure TfrmPermanent.LoadPermanentItemsGroup;
var name, itemText, s : string;
    lineCount : cardinal;
    i, cnt : integer;
    tf : textfile;
    ci : TClipItem;
    sl : TStringList;
begin

    // use default when none given
    // ensure the default exists
    if CurrentPermPath = '' then begin
        CurrentPermPath := DEFAULT_FOLDER;
    end;

    if (TClipDatabase.hasPermanentClips) then begin
        TClipDatabase.StartBatch;
            ItemClip.Clear;
            ItemHotKeyList.Clear;

            TClipDatabase.LoadPermanentHotkeys(ItemHotKeyList, CurrentPermPath);
            cnt := TClipDatabase.getCountPermanent(CurrentPermPath);
            for i := 0 to cnt-1 do begin
                ci := TClipITem.Create;
                TClipDatabase.LoadPermanent(ci, i, CurrentPermPath);
                ItemClip.Add(ci);

                // legacy stuff, not enought keyslots
                if (i>=ItemHotKeyList.Count) then begin
                    ItemHotKeyList.Add('');
                    TClipDatabase.SavePermanentHotkey('',i,CurrentPermPath);
                end;
            end;
        TClipDatabase.EndBatch;

        EXIT;
    end;
    if not DirectoryExists(self.AppPath + CurrentPermPath) then begin
        if CurrentPermPath = DEFAULT_FOLDER then
            ForceDirectories(self.AppPath + CurrentPermPath);
    end;



    //
    // load permanent items
    //
    ItemNameList.Clear;
        name := GetDataFilename(DFN_PERM0);
        if FileExists(name) then begin
            ItemNameList.LoadFromFile(name);
        end;

    ItemHotKeyList.Clear;
        name := GetDataFilename(DFN_HOTKEYS);
        if FileExists(name) then begin
            if ContainsText(name,'_unicode') then begin
                ItemHotKeyList.LoadFromFile(name, tencoding.Unicode);
            end else begin
                ItemHotKeyList.LoadFromFile(name);
            end;
        end;
    while ItemHotKeyList.Count < ItemNameList.Count do begin
        ItemHotKeyList.Add('');
    end;

    //
    // abort reading and show message on error
    // always close the file
    //
    ItemDataList.Clear;
    name := GetDataFilename(DFN_PERM1);
    if FileExists(name) then begin
    	if ContainsText(name, '_unicode.') then begin
            sl := TStringList.Create;
            sl.LoadFromFile(name, TEncoding.Unicode);

            i := 0;
            while i < sl.Count do begin
                s := sl[i];
                inc(i);
                cnt := StrToInt(s);

                itemtext := '';
                while cnt <> 0 do begin
                    s := sl[i];
                    inc(i);
                	if itemtext = '' then begin
                        itemtext := s;
                    end else begin
                    	itemtext := itemtext + #13#10 + s;
                    end;
                    dec(cnt);
                end;
                ItemDataList.Add(itemText);
            end;
        end else begin

            AssignFile(tf, name);
            Reset(tf);

            try
                while not eof(tf) do begin
                    try
                        Readln(tf, s);
                        itemText := '';
                        lineCount := StrToInt(s);

                        for i := 0 to lineCount - 1 do begin
                            Readln(tf, s);
                            if (itemText = '') then begin
                                itemText := s;
                            end else begin
                                itemText := itemText + chr(13) + chr(10) + s;
                            end;
                        end;

                        ItemDataList.Add(itemText);
                    except
                         on E: Exception do begin
                            ShowMessage('The "Permanent Item" file for group ' + CurrentPermPath + ' is corrupted - ' + name + #13#10#13#10 +
                                        'Error Message: ' + E.Message);
                            break;
                         end;
                    end;
                end;
            finally
                CloseFile(tf);
            end;
        end;
    end;
end;
procedure TfrmPermanent.SavePermanentItemsGroup;
var name: string;
    s : string;
    cnt : cardinal;
    sl : TStringList;
    i,j: longint;

begin
    if (CurrentPermPath = '') then
        EXIT;

    if (TClipDatabase.hasPermanentClips) then begin
        TClipDatabase.StartBatch;
            i := FolderList.IndexOf(CurrentPermPath);
            if (i=-1) then  begin
                i := FolderList.Count;
                TClipDatabase.SavePermanentGroup(CurrentPermPath, i);
            end else begin
                TClipDatabase.ClearPermanentGroup(CurrentPermPath);

                //TClipDatabase.SavePermanentGroup(CurrentPermPath, i);
                for i := 0 to ItemClip.Count-1 do begin
                    TClipDatabase.SavePermanent(ItemClip[i],i,CurrentPermPath);
                end;
                for i := 0 to ItemHotKeyList.Count-1 do begin
                    TClipDatabase.SavePermanentHotkey(ItemHotKeyList[i],i,CurrentPermPath);
                end;
            end;
        TClipDatabase.EndBatch;
        EXIT;
    end;


    if not DirectoryExists(IncludeTrailingPathDelimiter(AppPath) + CurrentPermPath) then begin
        if not TClipDatabase.hasPermanentClips then
            mkdir(IncludeTrailingPathDelimiter(AppPath) + CurrentPermPath);
    end;
    //
    // save items
    //
    name := GetDataFilename(DFN_PERM0, false);
    ItemNameList.SaveToFile(name, TEncoding.Unicode);

    name := GetDataFilename(DFN_HOTKEYS);
    ItemHotKeyList.SaveToFile(name);

    name := GetDataFilename(dfn_perm1, false);
    //AssignFile(tf, name);
    //Rewrite(tf);
    sl := TStringList.Create;

    for i := 0 to ItemDataList.Count - 1 do begin
        s := ItemDataList[i];

        cnt := 1;
        for j := 1 to length(s) - 1 do begin
// Unix LF causing issues
//            if (s[j] = #13) and (s[j+1]= #10) then inc(cnt);
            if (s[j+1]= #10) then inc(cnt);
        end;


        sl.Add(IntToStr(cnt));
        sl.Add(s);
        //writeln(tf, cnt);
        //writeln(tf, s);
    end;

    //CloseFile(tf);
    sl.SaveToFile(name, TEncoding.Unicode);
    sl.Free;
end;


procedure TfrmPermanent.UpdateFolderList;
var rec : TSearchRec;
    r : integer;
begin

    if (TClipDatabase.hasPermanentGroups) then begin
        FolderList.Clear;
        TClipDatabase.LoadPermanentGroups(FolderList);
        EXIT;
    end;

    //
    // scan each subfolder and look for permanent item config files
    // this will generate a list of permanent item groups (using their
    // folder name)
    //
    FolderList.clear;
    FolderList.Sorted := true;
    r := FindFirst(AppPath + '*.*', faDirectory, rec);
    while (r = 0) do begin
        // is file a subfolder?
        if (rec.Attr and faDirectory) > 0 then begin
            if (rec.name <> '.') and (rec.name <> '..') then begin
                if IsPermanentPathFolder(rec.name) then begin
                    FolderList.Add(rec.name);
                end;
            end;
        end;

        r := FindNext(rec);
    end;
end;

function TFrmPermanent.IsPermanentPathFolder(folder : string) : boolean;
begin
    if (TClipDatabase.hasPermanentGroups) then begin
        result := FolderList.IndexOf(folder) <> -1;
        EXIT;
    end;
    result := fileexists( GetDataFilename(DFN_PERM0, true, folder) );
end;

// Show Event
procedure TFrmPermanent.FormShow(Sender: TObject);
begin
    self.LoadPermanentItemsGroup;
    self.RefreshFormData;
end;
procedure TFrmPermanent.RefreshFormData;
var i : integer;
    li : TListItem;
begin
    // load the current item names
    //
    lvAutoSwitch.Items.clear;
    for i := 0 to EXEPigList.Count - 1 do begin
        li := lvAutoSwitch.items.Add;
        li.Caption := EXEPigList.Names[i];
        li.SubItems.Add(EXEPigList.Values[EXEPigList.Names[i]]);
    end;
end;
procedure TFrmPermanent.btnEditItemxClick(Sender: TObject);
var s : string;
    b : boolean;
    prevPerm : string;
begin
    FrmPermanentEdit.top := self.top;
    FrmPermanentEdit.left := self.left;

    prevPerm := self.CurrentPermPath;
    FrmPermanentEdit.showform(self.SelectedGroup, self.SelectedIndex );
    if FrmPermanentEdit.ModalResult <> mrCancel then begin
        //self.CurrentPermPath := self.SelectedGroup;
        //self.tsEditGroupsShow(self);
        //self.ShowSelectedTreeItem;
        self.RefreshPermanentGroups(true);
        self.SelectTreeItem(self.LastSavedGroup, self.LastSavedItemIndex );
    end;
end;

procedure TFrmPermanent.btnFileClick(Sender: TObject);
begin
    UnitMisc.ShowPopupRight(btnFile, pmTree);
end;

procedure TFrmPermanent.SelectTreeItem(group: string; index: integer);
var i : integer;
    tn : TTreeNode;
begin

    for i  := 0 to tvgroups.Items.count - 1 do begin
        tn := tvgroups.Items[i];
        if tn.Parent = nil then begin
            if tn.Text = group then begin
                tvgroups.Selected := tn;
                tn.Expand(false);
                if index >= 0 then tvgroups.Selected := tn.item[index];

                self.ShowSelectedTreeItem;

                BREAK;
            end;
        end;
    end;
end;
procedure TFrmPermanent.ShowSelectedTreeItem;
var tn : TTreeNode;
    txt : string;
    ci : TClipItem;
    hotkey : THotkeyData;
begin
    tn := tvGroups.selected;
    if tn <> nil then begin
        btnEditItem.Enabled := false;
        btnNewItem.enabled := false;
        lblhotkey.Caption := EMPTY_HOTKEY_NAME;
        pnltext.Visible := false;
        pnlhotkey.Visible := false;

        if tn.Parent <> nil then begin
            pnltext.Visible := true;
            pnlhotkey.Visible := true;

            self.PermFolderPush;
            self.SetPermanentPath(tn.Parent.text);
            self.SelectedGroup := tn.Parent.text;
            self.SelectedIndex := Integer(tn.Data);

            hotkey := self.FromString(ItemHotKeyList[self.selectedindex]);
            lblHotkey.Caption := hotkey.name;
            txt := self.GetItemText(self.SelectedIndex);
            btnEditItem.Enabled := true;

            ci := getItemClip(self.SelectedIndex);
            fClipDisplay.PicStretched := true;
            fClipDisplay.ShowClip(ci);


            self.PermFolderPop;
        end else begin
            btnNewItem.Enabled := true;
        end;
    end;

end;
procedure TFrmPermanent.HandleItemSelect;
begin
    btnNewItem.Enabled := false;
    btnEditItem.Enabled := false;
    if tvgroups.Selected = nil then EXIT;

    if tvgroups.Selected.Text = NEW_GROUP_TEXT  then begin
        self.btnAddPIGClick(self);
        self.tsEditGroupsShow(self);
        EXIT;
    end;

    if tvgroups.selected.Parent = nil then begin
        btnNewItem.Enabled := true;
    end else begin
        btnEditItem.Enabled := true
    end;

    self.ShowSelectedTreeItem;
end;



//
// add / delete Permanent Item Group
//
procedure TFrmPermanent.btnAddPIGClick(Sender: TObject);
var newgroup : string;
begin
    if
    Dialogs.InputQuery('Add Group','Please enter a new group name:', newgroup)
    then begin
        if not IsPermanentPathFolder(newgroup) then begin
            TClipDatabase.SavePermanentGroup(newgroup, 0);
            self.UpdateFolderList;
            self.RefreshFormData;
        end else begin
            Dialogs.showmessage('Group name already exists');
        end;
    end;
end;

procedure TFrmPermanent.AutoSwitch(EXEName: string);
var path : string;
begin
    // given an EXE name, change to a PIG if it's associated with one
    // Associate the current PIG with the EXE if no association exist

    path := EXEPigList.Values[EXEName];
    if (path <> '') then begin
        self.SetPermanentPath(path);
    end else begin
        if EXEName <> '' then begin
            EXEPigList.Values[EXEName] := self.GetPermanentPath;
        end;
    end;
end;
procedure TFrmPermanent.AssignPIG(EXEName: string);
begin
    EXEPigList.Values[EXEName] := self.GetPermanentPath;
end;

procedure TFrmPermanent.PermFolderPop;
begin
    if self.PushpopPath <> self.CurrentPermPath then begin
        self.SetPermanentPath(self.PushpopPath);
    end;
    self.PushpopPath := '';
end;
procedure TFrmPermanent.PermFolderPush;
begin
    if self.PushpopPath <> '' then begin
        self.PushpopPath := '';
        raise Exception.Create('ERROR: Permanent Item folder restore failed');
    end;
    self.PushpopPath := self.CurrentPermPath;
end;

// Clip Routines
function TfrmPermanent.MakeClipReference(i : integer) : string;
begin
    result := '[FIV2=' +IntToStr(i)+ ']';
end;
function TFrmPermanent.GetNextComplexIndex: integer;
var i, j : integer;
    sl : TList<integer>;
begin
    if (TClipDatabase.hasPermanentClips) then begin
        result := ItemClip.Count;
        EXIT;
    end;

    sl := TList<Integer>.create;
    for i := 0 to (ItemDataList.Count - 1) do begin
        if self.IsComplexItem(ItemDataList[i]) then begin
            j := self.ExtractComplexIndex(ItemDataList[i]);
            sl.Add(j);
        end;
    end;

    result := 0;
    while sl.IndexOf(result) <> -1 do inc(result);
end;
function TFrmPermanent.ExtractComplexIndex(s: string): integer;
begin
    s := midstr(s,7,length(s)-7);
    result := StrToInt(s);
end;
function TFrmPermanent.GetComplexItem(s: string): TClipItem;
var i : integer;
    prefix, indexstr : string;
begin
    result := TClipItem.Create;

    prefix := uppercase(leftstr(s,6));

    indexstr := midstr(s,7,length(s)-7);
    i := StrToInt(indexstr);
    if (prefix='[FILE=') then begin
        // unused
    end else if (prefix='[FIV2=') then begin
        result.LoadFromFile(self.GetPermanentPathFull, i, CI_FILEMASK_ALL);
    end;
end;
function TFrmPermanent.IsComplexItem(s: string): boolean;
var s2 : string;
begin
    s2 := uppercase(leftstr(s,6));
    result :=  (s2='[FILE=') or (s2='[FIV2=');
end;


procedure TFrmPermanent.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
{    if btnCancel.Visible then begin
        btnCancel.Click;
    end;
}
    CanClose := true;
end;




//
// Hotkey Management / Enumeration
//
function TFrmPermanent.GetHotkeyCount: integer;
begin
    result := HotkeyIDToIndex.Count;
end;
function TFrmPermanent.GetHotkey(index : integer) : string;
begin
    result := ItemHotKeyList[index];
end;
function TFrmPermanent.GetHotkeyIdent(index: integer): integer;
begin
    result := TACHotKey(HotkeyObjects2.Objects[index]).Getid;
end;
procedure TFrmPermanent.RefreshHotkeys;
var i, j, k : integer;
    s, group : string;
    hk : TACHotKey;
    hkd : THotkeyData;
    sl : TStringList;
    procedure ClearHotkeyData;
    var i : integer;
    begin
        // unregister the global hotkeys
        for i := 0 to HotkeyObjects2.count - 1 do begin
            hk := TACHotKey(
                HotkeyObjects2.Objects[i]
            );
            MyFree(hk);
        end;
        HotkeyObjects2.Clear;
        HotkeyIDToIndex.Clear;
    end;
begin
    ClearHotkeyData;

    if (TClipDatabase.hasPermanentClips) then begin
        sl := TStringList.Create;
        k := 0;
        TClipDatabase.StartBatch;
        for i := 0 to FolderList.Count - 1 do begin
            group := FolderList[i];
            sl.Clear;
            TClipDatabase.LoadPermanentHotkeys(sl, group);
            for j := 0 to sl.count - 1 do begin
                s := sl[j];
                if s <> '' then begin
                    hkd := self.FromString(s);
                    try
                        if (hkd.name <> EMPTY_HOTKEY_NAME) and
                            (hkd.alt or hkd.ctrl or hkd.shft or hkd.win) then begin

                            hk := TACHotKey.Create(
                                frmmainpopup.Handle,
                                'ArsClip.PermItem' + hkd.name,
                                hkd.ctrl, hkd.alt, hkd.shft, hkd.win, hkd.key
                            );
                            HotkeyIDToIndex.Values[IntToStr(hk.GetID)] := IntToHex(i,8) + IntToHex(j,8);
                            HotkeyObjects2.AddObject(IntToStr(k), hk);
                            inc(k);

                            FrmDebug.AppendLog('perm hotkey: ' + hkd.name);
                        end;
                    except
                    end;
                end;
            end;
        end;
        TClipDatabase.EndBatch;

        EXIT;
    end;


    self.PermFolderPush;
    k := 0;


    for i := 0 to self.PermFoldersGetCount - 1 do begin
        self.SetPermanentPath(self.PermFoldersGetItem(i));

        for j := 0 to self.GetCount - 1 do begin
            s := ItemHotKeyList[j];
            if s <> '' then begin
                hkd := self.FromString(s);
                try
                    if (hkd.name <> EMPTY_HOTKEY_NAME) and
                        (hkd.alt or hkd.ctrl or hkd.shft or hkd.win) then begin

                        hk := TACHotKey.Create(
                            frmmainpopup.Handle,
                            'ArsClip.PermItem' + hkd.name,
                            hkd.ctrl, hkd.alt, hkd.shft, hkd.win, hkd.key
                        );
                        HotkeyIDToIndex.Values[IntToStr(hk.GetID)] := IntToHex(i,8) + IntToHex(j,8);

                        HotkeyObjects2.AddObject(IntToStr(k), hk);
                        inc(k);

                        FrmDebug.AppendLog('perm hotkey: ' + hkd.name);
                    end;
                except
                end;
            end;
        end;
    end;

    self.PermFolderPop;
end;
function TFrmPermanent.ToHKString(hkd: THotkeyData): string;
begin
    result :=
        IntToStr(Integer(hkd.alt)) +
        IntToStr(Integer(hkd.ctrl)) +
        IntToStr(Integer(hkd.shft)) +
        IntToStr(Integer(hkd.win)) +
        IntToHex(hkd.key,2) +
        hkd.name;

end;
function TFrmPermanent.FromString(s: string): THotKeyData;
begin
    result.name := EMPTY_HOTKEY_NAME;
    if length(s) < 6 then begin
        EXIT;
    end;

    result.alt := Boolean(StrToInt(s[1]));
    result.ctrl  := Boolean(StrToInt(s[2]));
    result.shft  := Boolean(StrToInt(s[3]));
    result.win  := Boolean(StrToInt(s[4]));
    result.key := StrToInt('$' +  copy(s,5,2));
    delete(s,1,6);
    result.name := s;
end;
procedure TFrmPermanent.timCloseTooltipTimer(Sender: TObject);
begin
    timCloseTooltip.enabled := false;
    hotkeyTooltip.hide;
    application.ProcessMessages;
end;

//===========================================================
//  new Edit form
//===========================================================

procedure TFrmPermanent.tsEditGroupsShow(Sender: TObject);
begin
    self.RefreshPermanentGroups(true);
end;
procedure TFrmPermanent.tvGroupsChange(Sender: TObject; Node: TTreeNode);
begin
    if self.visible and tvgroups.Visible then begin
        KeyboardQuery.WaitUntilRelease(leftButton);
        tvGroups.SetFocus;
        self.HandleItemSelect;
    end;
end;
procedure TFrmPermanent.tvGroupsExpanding(Sender: TObject; Node: TTreeNode; var AllowExpansion: Boolean);
begin
    //self.HandleItemSelect;
    KeyboardQuery.WaitUntilRelease(leftButton);
    AllowExpansion := not isrightclick;

    if (AllowExpansion) then begin
        self.ignorecollapse := true;
        tvGroups.FullCollapse;
        self.ignorecollapse := false;
    end;
end;

procedure TFrmPermanent.tvGroupsCollapsing(Sender: TObject; Node: TTreeNode; var AllowCollapse: Boolean);
begin
    //self.HandleItemSelect;

    AllowCollapse := false;
    if ignorecollapse  then begin
        KeyboardQuery.WaitUntilRelease(leftButton);
        AllowCollapse := true;
        EXIT;
    end;
    if isrightclick then begin
        EXIT;
    end;

    {AllowCollapse := false;
    if tvGroups.Selected <> nil then begin
        AllowCollapse := tvGroups.Selected.HasChildren
        and (tvGroups.selected <> node);
    end;}
    AllowCollapse := true;
end;
procedure TFrmPermanent.tvGroupsCustomDraw(Sender: TCustomTreeView; const ARect: TRect; var DefaultDraw: Boolean);
begin
    defaultdraw := true;
end;
procedure TFrmPermanent.tvGroupsCustomDrawItem(Sender: TCustomTreeView; Node: TTreeNode; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
    defaultdraw := true;
    if (cdsFocused in state) or
        (cdsHot in state) or
        (cdsSelected in state)  then begin
        if (cdsHot in state) then begin
                sender.canvas.brush.Color := dimColor(ColorToRGB(clInactiveCaption),1.05);
            end else begin
                sender.canvas.brush.Color := dimColor(ColorToRGB(cl3DLight ));
            end;
        //sender.canvas.brush.Color := DimColor(ColorToRGB(clInactiveCaption),1.05);
        if (cdsHot in state)  then begin
                //sender.Canvas.font.color := ColorToRGB(clHighlighttext);
            end else begin
                sender.Canvas.font.color := ColorToRGB(clBtnText);
            end;
    end;
    if node.parent = nil then begin
        sender.Canvas.Brush.color := ColorToRGB(clBtnFace);
        if (cdsSelected in state) or
        (cdsHot in state) then begin
            if (cdsHot in state) then begin
                sender.canvas.brush.Color := dimColor(ColorToRGB(clInactiveCaption),1.05);
            end else begin
                sender.canvas.brush.Color := dimColor(ColorToRGB(cl3DLight ));
            end;
            sender.Canvas.font.color := ColorToRGB(clBtnText);
            {
            if (cdsHot in state)  then begin
                sender.Canvas.font.color := ColorToRGB(clHighlighttext);
            end else begin
                sender.Canvas.font.color := ColorToRGB(clBtnText);
            end;
            }
        end;
    end;
end;
procedure TFrmPermanent.pmTreePopup(Sender: TObject);
var i : integer;
    mi : TMenuItem;
    s : string;
    cur : string;
begin
   miMoveto.Enabled := false;
    if tvgroups.Selected.Parent = nil then begin
        midelete.Caption := '&Delete Group';
    end else begin
        miDelete.Caption := '&Delete Item' ;
        miMoveto.Enabled := true;
        miMoveto.Clear;
        cur := tvgroups.selected.parent.text;

        for i := 0 to FrmPermanent.PermFoldersGetCount - 1 do begin
            s := FrmPermanent.PermFoldersGetItem(i);
            if cur <> s then begin
                mi := pmTree.CreateMenuItem;
                mi.Caption := s;
                mi.OnClick := miMovetoItemClick;
                mi.AutoHotkeys := maManual;
                mimoveto.Add(mi);

            end;
        end;
    end;
    //tvgroups.Selected := tn;
end;
procedure TFrmPermanent.tvGroupsDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean);
var tn : TTreeNode;
begin
    tn := tvGroups.GetNodeAt(x,y);
    Accept := tn <> nil;
    Accept := Accept and (tvgroups.Selected <> nil); //
    Accept := Accept and (tvgroups.selected.parent <> nil); // root nodes don't move
    Accept := Accept and (tn.Text <> NEW_GROUP_TEXT);

    accepteddrag := Accept;
    if accept then begin


    end;
end;
procedure TFrmPermanent.tvGroupsEndDrag(Sender, Target: TObject; X, Y: Integer);
var drag, drop : TTreeNode;
    dragi, dropi : integer;
    group, prevgroup : string;
    ci : TClipItem;

    hotkey : string;
    usekeys : boolean;
begin
    if not self.accepteddrag  then EXIT;

    drop := tvGroups.GetNodeAt(x,y);
    drag := tvGroups.Selected;
    //showmessage('selected:' + tvGroups.Selected.Text + ' drop:' + drop.text);

    if (drop <> nil) then begin

        if (drop.Parent <> nil) then begin
            // move item within group

            dragi := drag.Index;
            dropi := drop.Index;
            group := drop.Parent.text;

            self.PermFolderPush;
                self.SetPermanentPath(group);
                ItemClip.Move(dragi, dropi);
                ItemHotKeyList.Move(dragi, dropi);;

                self.SavePermanentItemsGroup;
            self.PermFolderPop;
        end else begin
            // move to new group
            // - delete from old location
            // - add to new location
            dropi := -1;
            dragi := drag.Index;
            group := drop.Text;
            prevgroup := drag.Parent.Text;
            self.MoveItem(dragi, prevgroup, group);
        end;
        self.RefreshPermanentGroups(false);
        self.SelectTreeItem(group, dropi);
    end;


end;








procedure TFrmPermanent.RefreshPermanentGroups(selectCurrentGroup: boolean);
var i, j : integer;
    tn, subtn : TTreeNode;
    ci : TClipItem;
    cl : TClipList;
    keys : TStringList;
    s : string;
    location : string;

begin
    TClipDatabase.StartBatch;
    tvGroups.Items.clear;

    cl := TClipList.create;
    keys := TStringList.create;

    for i := 0 to FolderList.Count-1 do begin
        location := FolderList[i];
        TClipDatabase.LoadPermanentClips(cl, location);
        TClipDatabase.LoadPermanentHotkeys(keys, location);
        tn := tvGroups.items.Add(nil, location);
        tn.ImageIndex := 0;
        tn.SelectedIndex := tn.ImageIndex;

        for j := 0 to cl.Count-1 do begin
            s := keys[j];
            ci := cl[j];
            if s <> '' then begin
                subtn := tvGroups.Items.AddChild(tn, ci.getDisplayText + '  ['+s+']');
            end else begin
                subtn := tvGroups.Items.AddChild(tn, ci.getDisplayText);
            end;
            subtn.ImageIndex := 1;
            subtn.SelectedIndex := subtn.ImageIndex;
            subtn.Data := Pointer(j);


            if ci.GetFormat = CF_DIB then begin
                subtn.ImageIndex := 3;
                subtn.SelectedIndex := subtn.ImageIndex;
            end else if (ci.GetFormat = CF_UNICODETEXT) or (ci.getformat = GetCF_HTML) or (ci.GetFormat= GetCF_RICHTEXT)  then begin
                subtn.ImageIndex := 2;
                subtn.SelectedIndex := subtn.ImageIndex;
            end;
        end;

        if (lowercase(tn.Text) = lowercase(CurrentPermPath)) and
            selectCurrentGroup then begin
            tn.Selected := true;
            tn.Expand(true);
            tvGroups.Selected := tn;
        end else begin
            tn.Selected := false;
        end;
    end;
    TClipDatabase.EndBatch;


    tn := tvGroups.items.add(nil, NEW_GROUP_TEXT);
    tn.ImageIndex := 4;
    tn.SelectedIndex := tn.ImageIndex;

    btnNewItem.Enabled := false;
    btnEditItem.Enabled := false;

    self.HandleItemSelect;
    self.ShowSelectedTreeItem;
end;
procedure TFrmPermanent.btnNewItemxClick(Sender: TObject);
begin
    FrmPermanentEdit.top := self.top;
    FrmPermanentEdit.left := self.left;

    self.SelectedGroup := tvGroups.Selected.Text;

    FrmPermanentEdit.ShowFormNew(self.SelectedGroup);
    if FrmPermanentEdit.ModalResult <> mrCancel then begin
        self.RefreshPermanentGroups(false);
        self.SelectTreeItem(self.SelectedGroup, -1);
    end;
end;


procedure TFrmPermanent.miDeleteClick(Sender: TObject);
var
    sr : TSearchRec;
    i : integer;
    ci : TClipItem;
    group : string;
begin
     if MessageDlg('Delete the selected item "' +tvGroups.selected.text+'"?', mtConfirmation,[mbYes, mbNo],0,mbNo) <> mrYes  then
        EXIT;

    if tvGroups.Selected.parent = nil then begin
        group := tvGroups.Selected.text;
        TClipDatabase.ClearPermanentGroup(group, true);
        self.UpdateFolderList;
        self.RefreshPermanentGroups(false);
    end else begin
        self.PermFolderPush;
            group := tvGroups.Selected.parent.text;
            self.SetPermanentPath(group);
            i := tvgroups.Selected.Index;
            ItemClip.Delete(i);
            ItemHotKeyList.Delete(i);
            self.SavePermanentItemsGroup;
        self.PermFolderPop;
        self.RefreshPermanentGroups(false);
        self.SelectTreeItem(group,-1);
        self.RefreshHotkeys;
    end;
end;
procedure TFrmPermanent.miMovetoItemClick(Sender: TObject);
var mi : TMenuITem;
    i : integer;
    g, newgroup, name, text : string;
    ci : TClipItem;
    hotkey : THotkeydata;
    usekeys : boolean;
begin
    ci := nil;
    mi := TMenuItem(sender);
    newgroup := mi.Caption;
    g := tvGroups.Selected.parent.text;
    i := tvgroups.Selected.Index;

    self.MoveItem(i,g,newgroup);

    self.SetPermanentPath(newgroup);
    self.RefreshPermanentGroups(true);
end;

procedure TFrmPermanent.MoveClip(oldi: Integer; newi: Integer);
begin
    ItemClip.Move(oldi, newi);
    ItemHotKeyList.Move(oldi, newi);
    self.SavePermanentItemsGroup;
end;
procedure TFrmPermanent.MoveItem(index : integer; location : string; newLocation : string);
var
    ci : TClipItem;
    hotkey : string;
begin
    if location = newLocation then EXIT;

    PermFolderPush;
        SetPermanentPath(location);
        ci := ItemClip[index].CloneClip;
        ItemClip.Delete(index);
        hotkey := ItemHotKeyList[index];
        ItemHotKeyList.Delete(index);

        SavePermanentItemsGroup;
    PermFolderPop;

    PermFolderPush;
        SetPermanentPath(newLocation);
        ItemClip.Add(ci);
        ItemHotKeyList.Add(hotkey);

        SavePermanentItemsGroup;
    PermFolderPop;
end;
//
// Set/Clear hotkey
//
procedure TFrmPermanent.SaveHotkey(group : string; index:integer; hotkey : string);
begin
    PermFolderPush;
    SetPermanentPath(group);
        self.ItemHotKeyList[index] := hotkey;
        self.SavePermanentItemsGroup;
    PermFolderPop;
    self.RefreshPermanentGroups(false);
    self.SelectTreeItem(group,index);
    self.RefreshHotkeys;
end;
procedure TFrmPermanent.btnSetHotkeyClick(Sender: TObject);
var
    hkd : THotkeyData;
    g : string;
    i : integer;
begin
    if FrmHotkey.GetHotkey(
        hkd.alt, hkd.shft, hkd.ctrl, hkd.win,
        hkd.key,
        hkd.name
    ) then begin
        SaveHotkey(tvGroups.Selected.parent.text, tvgroups.Selected.Index,Self.ToHKString(hkd));
    end;
end;
procedure TFrmPermanent.btnClearHotkeyClick(Sender: TObject);
begin
    if MessageDlg('Clear the hotkey?', mtConfirmation,[mbYes, mbNo],0,mbNo) <> mrYes  then
        EXIT;

    SaveHotkey(tvGroups.Selected.parent.text, tvgroups.Selected.Index,'');
end;



end.
