diff --git a/applications/spready/mrumanager.pp b/applications/spready/mrumanager.pp
new file mode 100644
index 000000000..3c603bc93
--- /dev/null
+++ b/applications/spready/mrumanager.pp
@@ -0,0 +1,484 @@
+{ MRU (Most Recent Used) menu item manager
+
+ Copyright (C) 2011 Michael Van Canneyt (michael@freepascal.org)
+ Modifications by Werner Pamler
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version with the following modification:
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent modules,and
+ to copy and distribute the resulting executable under terms of your choice,
+ provided that you also meet, for each linked independent module, the terms
+ and conditions of the license of that module. An independent module is a
+ module which is not derived from or based on this library. If you modify
+ this library, you may extend this exception to your version of the library,
+ but you are not obligated to do so. If you do not wish to do so, delete this
+ exception statement from your version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
+ for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+}
+unit mrumanager;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, inifiles, menus;
+
+Type
+ { TRecentMenuItem }
+
+ TRecentMenuItem = Class(TMenuItem)
+ Private
+ FFileName : string;
+ Public
+ Property FileName : String Read FFileName;
+ end;
+ TRecentMenuItemClass = Class of TRecentMenuItem;
+
+ { TMRUMenuManager }
+
+ TOnRecentFileEvent = Procedure(Sender : TObject; Const AFileName : String) of object;
+
+ TMRUMenuManager = Class(TComponent)
+ Private
+ FIniFileName: String;
+ FIniSection: String;
+ FOnRecent: TOnRecentFileEvent;
+ FRecent : TStrings;
+ FMaxRecent : Integer;
+ FMenuCaptionMask : string;
+ FMIRecent : TMenuItem;
+ FPMRecent : TPopupMenu;
+ FMaxItemLength : integer;
+ procedure SetIniFileName(const AValue:string);
+ procedure SetIniSection(const AValue:string);
+ procedure SetMaxItemLength(const AValue:integer);
+ procedure SetMenuCaptionMask(const AValue:string);
+ procedure SetMIRecent(const AValue: TMenuItem);
+ procedure SetPMRecent(const AValue: TPopupMenu);
+ procedure SetRecent(const AValue: TStrings);
+ protected
+ // Overrides.
+ procedure Loaded; override;
+ procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+ // Return default name and section if either is empty.
+ procedure GetFileNameAndSection(Var AFileName,ASection : String); virtual;
+ // Override if you want to load additional values.
+ procedure LoadFromIni(Ini: TCustomIniFile; ASection: String); virtual;
+ // Override if you want to write additional values.
+ procedure SaveToIni(Ini: TCustomIniFile; ASection: String); virtual;
+ // Called when menu item is clicked.
+ procedure DoOnRecentClick(Sender: TObject); virtual;
+ // Override this if you want to create a custom class of menu itel.
+ function CreateMenuItem(AOwner: TComponent): TRecentMenuItem; virtual;
+ // Create a menu caption. Default is index followed by filename.
+ // Override if you want to customize.
+ Function CreateMenuCaption(AIndex : Integer; Const AFileName : String) : String; virtual;
+ Public
+ Constructor Create(AOwner : TComponent);override;
+ Destructor Destroy; override;
+ // Load files from ini file AFileName in section ASection. Calls ShowRecentFiles
+ // Need for explicit call only when IniFileName='' and IniSection='' and class created at run-time
+ procedure LoadRecentFilesFromIni(const AFileName: string=''; const ASection: String='');
+ // Saves files to ini file AFileName in section ASection.
+ procedure SaveRecentFilesToIni(const AFileName: string=''; const ASection: String='');
+ // Add a filename to the list of files.
+ // If an existing file is added, it is moved first in the list.
+ // If MaxRecent is attained, the last one is removed.
+ // Calls ShowRecentFiles.
+ procedure AddToRecent(AFileName: String);
+ // Re-populate the menu.
+ procedure ShowRecentFiles;
+ Published
+ // Max. items to be kept in the list.
+ Property MaxRecent : Integer Read FMaxRecent write FMaxRecent default 10;
+ // Menu item to create a submenu under. Existing items will be removed.
+ Property MenuItem : TMenuItem Read FMIRecent Write SetMIRecent;
+ // Popupmenu attached to a toolbar button. Existing items will be removed.
+ Property PopupMenu : TPopupMenu Read FPMRecent Write SetPMRecent;
+ // Default ini filename.
+ Property IniFileName : String Read FIniFileName Write SetIniFileName;
+ // Default ini section.
+ Property IniSection : String Read FIniSection Write SetIniSection;
+ // Maximum length of recent menu item
+ Property MaxItemLength : integer Read FMaxItemLength Write SetMaxItemLength default 80;
+ // Format mask for MenuCaption: first placeholder must be %d, second %s, e.g. '%d - %s'
+ Property MenuCaptionMask : string read FMenuCaptionMask Write SetMenuCaptionMask;
+ // Recent items. If adding manually to the list, ShowRecentFiles must be called manually.
+ Property Recent : TStrings Read FRecent Write SetRecent;
+ // Called when the user clicks an recent meu item.
+ Property OnRecentFile : TOnRecentFileEvent Read FOnRecent Write FOnRecent;
+ end;
+ EMRUManager = Class(Exception);
+
+Const
+ DefaultIniFile = 'recent.ini';
+ DefaultSection = 'Global';
+ KeyMaxRecent = 'MaxRecent';
+ KeyCount = 'Count';
+ KeyFile = 'File%d';
+
+implementation
+
+Resourcestring
+ SErrFailedToCreateDir = 'Failed to create directory "%s"';
+
+const
+ DEFAULT_MASK = '%0:d. %1:s';
+
+function MinimizeFileName(const AFileName:string; AMaxLen:integer) : string;
+
+ procedure SplitPath(const APath:String; Parts: TStrings);
+ { Splits the provided path into constituent folder names }
+ var
+ i, j : Integer;
+ begin
+ if APath = '' then exit;
+ if not Assigned(Parts) then exit;
+
+ i := Length(APath);
+ j := i;
+ while (i >= 1) do begin
+ if APath[i] = DirectorySeparator then begin
+ Parts.Insert(0, copy(APath, i+1, j-i));
+ j := i;
+ end;
+ dec(i);
+ end;
+ Parts.Insert(0, copy(APath, 1, j));
+ end;
+
+ function AddStringsFromTo(AList:TStrings; FromIndex,ToIndex:integer) : string;
+ var
+ i : integer;
+ begin
+ result := '';
+ for i:=FromIndex to ToIndex do
+ result := result + AList[i];
+ end;
+
+var
+ Parts : TStringList;
+ i : integer;
+ tmp : string;
+begin
+ result := AFileName;
+ if Length(AFileName) > AMaxLen then begin
+ Parts := TStringList.Create;
+ try
+ SplitPath(AFileName, Parts);
+ i := Parts.Count div 2;
+ while (i < Parts.Count) do begin
+ tmp := Format('%s...%s%s', [
+ AddStringsFromTo(Parts, 0, i-1),
+ DirectorySeparator,
+ AddStringsFromTo(Parts, i+1, Parts.Count-1)
+ ]);
+ if Length(tmp) < AMaxLen then begin
+ result := tmp;
+ exit;
+ end else
+ Parts.Delete(i);
+ i := Parts.Count div 2;
+ end;
+ result := ExtractFileName(AFileName);
+ finally
+ Parts.Free;
+ end;
+ end;
+end;
+
+procedure TMRUMenuManager.AddToRecent(AFileName : String);
+
+Var
+ I,J : Integer;
+ B : Boolean;
+
+begin
+ AFileName:=ExpandFileName(AFileName);
+ With FRecent do
+ begin
+ J:=IndexOf(AFileName);
+ If (J<>-1) then
+ begin
+ if (J>0) then
+ Exchange(0,J)
+ end
+ else
+ begin
+ While (Count>=FMaxRecent) do
+ Delete(Count-1);
+ Insert(0,AFileName)
+ end;
+ end;
+ ShowRecentFiles;
+end;
+
+function TMRUMenuManager.CreateMenuItem(AOwner :TComponent) : TRecentMenuItem;
+
+begin
+ Result:=TRecentMenuItem.Create(AOwner);
+end;
+
+function TMRUMenuManager.CreateMenuCaption(AIndex: Integer;
+ const AFileName: String): String;
+var
+ fn : string;
+ mask : string;
+begin
+ if FMaxItemLength > 0 then
+ fn := MinimizeFileName(AFileName, FMaxItemLength)
+ else
+ fn := AFileName;
+ if FMenuCaptionMask = '' then
+ mask := DEFAULT_MASK
+ else
+ mask := FMenuCaptionMask;
+ Result:=Format(mask, [AIndex+1,fn]);
+end;
+
+procedure TMRUMenuManager.ShowRecentFiles;
+
+Var
+ I : Integer;
+ M : TRecentMenuItem;
+
+begin
+ if Assigned(FMIRecent) then begin
+ FMIRecent.clear;
+ For I:=0 to FRecent.Count-1 do
+ begin
+ M:=CreateMenuItem(Self.Owner);
+ M.Caption:=CreateMenuCaption(I,FRecent[i]);
+ M.FFileName:=FRecent[i];
+ M.OnClick:=@DoOnRecentClick;
+ FMIRecent.Add(M);
+ end;
+ end;
+ if Assigned(FPMRecent) then begin
+ FPMRecent.Items.Clear;
+ for i:=0 to FRecent.Count-1 do
+ begin
+ M := CreateMenuItem(Self.Owner);
+ M.Caption := CreateMenuCaption(I, Recent[i]);
+ M.FFileName := FRecent[i];
+ M.OnClick := @DoOnRecentClick;
+ FPMRecent.Items.Add(M);
+ end;
+ end;
+end;
+
+procedure TMRUMenuManager.LoadFromIni(Ini : TCustomIniFile; ASection : String);
+
+Var
+ I,Count : Integer;
+ FN : String;
+
+begin
+ FRecent.Clear;
+ FMaxRecent:=Ini.ReadInteger(ASection,KeyMaxRecent,10);
+ Count:=Ini.ReadInteger(ASection,KeyCount,0);
+ For I:=1 to Count do
+ begin
+ FN:=Ini.ReadString(ASection,Format(KeyFile,[i]),'');
+ If (FN<>'') then
+ FRecent.Add(FN);
+ end;
+end;
+
+procedure TMRUMenuManager.GetFileNameAndSection(var AFileName, ASection: String);
+
+begin
+ if (AFileName='') then
+ begin
+ AFileName:=GetAppConfigDir(False);
+ AFileName:=IncludeTrailingPathDelimiter(AFileName)+DefaultIniFile;
+ end;
+ if (ASection='') then
+ ASection:=DefaultSection;
+end;
+
+procedure TMRUMenuManager.LoadRecentFilesFromIni(Const AFileName : string = ''; Const ASection : String = '');
+
+Var
+ DN,FN,Sec : String;
+ Ini : TIniFile;
+
+begin
+ FN:=AFileName;
+ Sec:=ASection;
+ GetFileNameAndSection(FN,Sec);
+ DN:=ExtractFilePath(FN);
+ If ForceDirectories(DN) then
+ begin
+ If FileExists(FN) then
+ begin
+ Ini:=TIniFile.Create(FN);
+ try
+ LoadFromIni(Ini,Sec);
+ finally
+ Ini.Free;
+ end;
+ end;
+ end;
+ ShowRecentFiles;
+end;
+
+procedure TMRUMenuManager.SaveToIni(Ini : TCustomIniFile; ASection : String);
+
+Var
+ I : Integer;
+begin
+ Ini.EraseSection(ASection);
+ Ini.WriteInteger(ASection,KeyMaxRecent,FMaxRecent);
+ Ini.WriteInteger(ASection,KeyCount,FRecent.Count);
+ For I:=0 to FRecent.Count-1 do
+ Ini.WriteString(ASection,Format(KeyFile,[i+1]),FRecent[i]);
+ Ini.UpdateFile;
+end;
+
+procedure TMRUMenuManager.SaveRecentFilesToIni(Const AFileName : string = ''; Const ASection : String = '');
+
+Var
+ DN,FN,Sec : String;
+ Ini : TMemIniFile;
+
+begin
+ FN:=AFileName;
+ Sec:=ASection;
+ GetFileNameAndSection(FN,Sec);
+ DN:=ExtractFilePath(FN);
+ If not ForceDirectories(DN) then
+ Raise EMRUManager.CreateFmt(SErrFailedToCreateDir,[DN]);
+ Ini:=TMemIniFile.Create(FN);
+ try
+ SaveToIni(Ini,Sec);
+ finally
+ Ini.Free;
+ end;
+end;
+
+procedure TMRUMenuManager.SetIniFileName(const AValue:string);
+begin
+ if AValue <> FIniFileName then begin
+ FIniFileName := AValue;
+ LoadRecentFilesFromIni(FIniFileName, FIniSection);
+ end;
+end;
+
+procedure TMRUMenuManager.SetIniSection(const AValue:string);
+begin
+ if AValue <> FIniSection then begin
+ FIniSection := AValue;
+ LoadRecentFilesFromini(FIniFileName, FIniSection);
+ end;
+end;
+
+procedure TMRUMenuManager.SetMaxItemLength(const AValue:integer);
+begin
+ if FMaxItemLength <> AValue then begin
+ FMaxItemLength := AValue;
+ ShowRecentFiles;
+ end;
+end;
+
+procedure TMRUMenuManager.SetMenuCaptionMask(const AValue:string);
+begin
+ if FMenuCaptionMask <> AValue then begin
+ FMenuCaptionMask := AValue;
+ ShowRecentFiles;
+ end;
+end;
+
+procedure TMRUMenuManager.SetMIRecent(const AValue: TMenuItem);
+begin
+ if FMIRecent=AValue then exit;
+ If Assigned(FMIRecent) then
+ FMIRecent.RemoveFreeNotification(Self);
+ FMIRecent:=AValue;
+ If Assigned(FMIRecent) then
+ FMIRecent.FreeNotification(Self);
+ ShowRecentFiles;
+end;
+
+procedure TMRUMenuManager.SetPMRecent(const AValue: TPopupMenu);
+begin
+ if FPMRecent=AValue then exit;
+ if Assigned(FPMRecent) then
+ FPMRecent.RemoveFreeNotification(self);
+ FPMRecent := AValue;
+ if Assigned(FPMRecent) then
+ FPMRecent.FreeNotification(self);
+ ShowRecentFiles;
+end;
+
+procedure TMRUMenuManager.SetRecent(const AValue: TStrings);
+begin
+ if FRecent=AValue then exit;
+ FRecent.Assign(AValue);
+ ShowRecentFiles;
+end;
+
+procedure TMRUMenuManager.loaded;
+begin
+ inherited loaded;
+ if (FRecent.Count>0) and (assigned(FMIRecent) or assigned(FPMRecent))then
+ LoadRecentFilesFromIni(FIniFileName, FIniSection);
+end;
+
+constructor TMRUMenuManager.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ FRecent:=TStringList.Create;
+ FMaxRecent := 10;
+ FMaxItemLength := 80;
+ FMenuCaptionMask := DEFAULT_MASK;
+end;
+
+destructor TMRUMenuManager.Destroy;
+begin
+ SaveRecentFilesToIni(FIniFileName, FIniSection);
+ FreeAndNil(FRecent);
+ inherited Destroy;
+end;
+
+procedure TMRUMenuManager.Notification(AComponent: TComponent;
+ Operation: TOperation);
+begin
+ inherited Notification(AComponent, Operation);
+ if (Operation = opRemove) then begin
+ if AComponent = FMIRecent then FMIRecent := nil;
+ if AComponent = FPMRecent then FPMRecent := nil;
+ end;
+ { original code - I think this is not correct:
+ inherited Notification(AComponent, Operation);
+ if (Operation=opRemove) and ((AComponent=FMIRecent) or (AComponent=FPMRecent)) then
+ exit;
+ }
+end;
+
+procedure TMRUMenuManager.DoOnRecentClick(Sender: TObject);
+Var
+ FN : String;
+begin
+ With (Sender as TRecentMenuItem) do
+ FN:=FileName;
+ if (FN<>'') and (OnRecentFile<>Nil) then
+ OnRecentFile(Self,FN);
+end;
+
+end.
+
diff --git a/applications/spready/spready.lpi b/applications/spready/spready.lpi
index 4f0c99328..dadf36732 100644
--- a/applications/spready/spready.lpi
+++ b/applications/spready/spready.lpi
@@ -56,19 +56,16 @@
-
+
-
+
-
-
-
-
+