2004. január 15., csütörtök

How to load a menu from a file


Problem/Question/Abstract:

How do I load or recreate a menu stored in a text file? I'm looking for a recursive function.

Answer:

The following seems to work if the data is always organized the way you gave (depth-first recursion).

Level|Name|Caption|
0|miItem1|Item 1|
1|miItem11|Sub Item 1-1|
1|miItem12|Sub Item 1-2|
2|miItem121|Sub sub  Item 1-2-1|
0|miItem2|Item 2|
1|miItem21|Sub Item 2-1|
1|miItem22|Sub Item 2-2|

I found it easier to use a stack instead of recursion, however:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, StdCtrls, Menus;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    MainMenu1: TMainMenu;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure MenuClick(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses contnrs;

{$R *.DFM}

function IScan(ch: Char; const S: string; fromPos: Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := fromPos to Length(S) do
  begin
    if S[i] = ch then
    begin
      Result := i;
      Break;
    end;
  end;
end;

procedure SplitString(const S: string; separator: Char; substrings: TStringList);
var
  i, n: Integer;
begin
  if Assigned(substrings) and (Length(S) > 0) then
  begin
    i := 1;
    repeat
      n := IScan(separator, S, i);
      if n = 0 then
        n := Length(S) + 1;
      substrings.Add(Copy(S, i, n - i));
      i := n + 1;
    until
      i > Length(S);
  end;
end;

procedure LoadMenuFromText(aMenu: TMenu; text: TStrings; aHandler: TNotifyEvent);
type
  TMenuData = record
    level: Integer;
    name: string;
    caption: string
  end;

  procedure SplitLine(const line: string; var data: TMenuData);
  var
    sl: TStringlist;
  begin
    sl := TStringlist.Create;
    try
      SplitString(line, '|', sl);
      Assert(sl.count >= 3);
      data.level := StrToInt(sl[0]);
      data.name := sl[1];
      data.caption := sl[2];
    finally
      sl.free
    end;
  end;

var
  itemStack: TStack;
  level: Integer;
  i: Integer;
  menudata: TMenuData;
  newitem: TMenuItem;
begin
  level := 0;
  itemstack := TStack.Create;
  try
    itemstack.Push(aMenu.Items);
    {skip header line}
    for i := 1 to text.count - 1 do
    begin
      SplitLine(text[i], menudata);
      newitem := Menus.NewItem(menudata.caption, 0, false, true, aHandler, 0,
        menudata.name);
      while level > menudata.level do
      begin
        itemstack.Pop;
        Dec(level);
      end;
      TMenuItem(itemstack.Peek).Add(newitem);
      Itemstack.Push(newitem);
      Inc(level)
    end;
  finally
    itemstack.free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  LoadMenuFromText(mainmenu1, memo1.lines, MenuClick);
end;

procedure TForm1.MenuClick(Sender: TObject);
begin
  label1.caption := (Sender as TMenuItem).Name;
end;

end.

2004. január 14., szerda

Function to Determine Oracle Version Number


Problem/Question/Abstract:

Function to Determine Oracle Version Number

Answer:

This function gets the connected Oracle version. It returns the version info in 3 OUT parameters.

        VerNum                        : double         eg. 7.23
        VerStrShort         : string                 eg. '7.2.3.0.0'
        VerStrLong         : string                 eg. 'Oracle7 Server Release 7.2.3.0.0 - Production Release'

I have tested it with Oracle 7.2 and 8.17. I assume it should work for the others (not too sure about Oracle 9 though). Any feedback and fixes for different versions would be appreciated.

The TQuery parameter that it recieves is a TQuery component that is connected to an open database connection.

Example :

var
  VNum: double;
  VShort: string;
  VLong: string;
begin
  GetOraVersion(MySql, VNum, VShort, VLong);
  Label1.Caption := FloatToStr(VNum);
  Label2.Caption := VShort;
  Label3.Caption := VLong;
end;

procedure GetOraVersion(Query: TQuery;
                                                                                          out VerNum: double;
                                                                                          out VerStrShort: string;
                                                                                          out VerStrLong: string);
var
  sTmp: string;
  cKey: char;
  i: integer;
begin
  Query.SQL.Text := 'select banner from v$version ' +
                                                                     'where banner like ' + QuotedStr('Oracle%');
  Query.Open;

  if not Query.Eof then
    VerStrLong := Query.Fields[0].AsString
  else
  begin
    // Don't know this version
    VerStrLong := '?';
    VerNum := 0.0;
    VerStrShort := '?.?.?.?';
  end;

  Query.Close;

  if VerStrLong <> '?' then
  begin
    cKey := VerStrLong[7]; // eg. Oracle7 or Oracle8i
    VerStrLong[7] := 'X'; // Mask it out
    sTmp := copy(VerStrLong, pos(cKey, VerStrLong), 1024);
    VerStrShort := copy(sTmp, 1, pos(' ', sTmp) - 1);
    sTmp := copy(VerStrShort, 1, pos('.', VerStrShort));

    for i := length(sTmp) + 1 to length(VerStrShort) do
    begin
      if VerStrShort[i] <> '.' then
        sTmp := sTmp + VerStrShort[i];
    end;

    VerNum := StrToFloat(sTmp);
    VerStrLong[7] := cKey; // Put correct character back
  end;
end;

2004. január 13., kedd

How to get the text width and height in a TRichEdit


Problem/Question/Abstract:

How to get the text width and height in a TRichEdit

Answer:

procedure TForm1.Button3Click(Sender: TObject);
var
  pt: TPoint;
begin
  with RichEdit1 do
  begin
    pt := point(0, 0);
    Perform(messages.EM_POSFROMCHAR, WPARAM(@pt), SelStart);
    label1.caption := Format('(%d, %d)', [pt.x, pt.y]);
  end;
end;

2004. január 12., hétfő

How to search for a string using the Soundex algorithm


Problem/Question/Abstract:

How to search for a string using the Soundex algorithm

Answer:

Solve 1:

unit SndxAlgs;

interface

uses
  SysUtils;

function Soundex(in_str: string): string;
function NumericSoundex(in_str: string): Smallint;
function ExtendedSoundex(in_str: string): string;

implementation

{Calculate a normal Soundex encoding.}

function Soundex(in_str: string): string;
var
  no_vowels, coded, out_str: string;
  ch: Char;
  i: Integer;
begin
  {Make upper case and remove leading and trailing spaces.}
  in_str := Trim(UpperCase(in_str));
  {Remove vowels, spaces, H, W, and Y except for the first character.}
  no_vowels := in_str[1];
  for i := 2 to Length(in_str) do
  begin
    ch := in_str[i];
    case ch of
      'A', 'E', 'I', 'O', 'U', ' ', 'H', 'W', 'Y':
        ; {Do nothing.}
    else
      no_vowels := no_vowels + ch;
    end;
  end;
  {Encode the characters.}
  for i := 1 to Length(no_vowels) do
  begin
    ch := no_vowels[i];
    case ch of
      'B', 'F', 'P', 'V': ch := '1';
      'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z': ch := '2';
      'D', 'T': ch := '3';
      'L': ch := '4';
      'M', 'N': ch := '5';
      'R': ch := '6';
    else {Vowels, H, W, and Y as the 1st letter.}
      ch := '0';
    end;
    coded := coded + ch;
  end;
  {Use the first letter.}
  out_str := no_vowels[1];
  {Find three non-repeating codes.}
  for i := 2 to Length(no_vowels) do
  begin
    {Look for a non-repeating code.}
    if (coded[i] <> coded[i - 1]) then
    begin
      {This one works.}
      out_str := out_str + coded[i];
      if (Length(out_str) >= 4) then
        Break;
    end;
  end;
  Soundex := out_str;
end;

{Calculate a numeric Soundex encoding.}

function NumericSoundex(in_str: string): Smallint;
var
  value: Integer;
begin
  {Calculate the normal Soundex encoding.}
  in_str := Soundex(in_str);
  {Convert this into a numeric value.}
  value := (Ord(in_str[1]) - Ord('A')) * 1000;
  if (Length(in_str) > 1) then
    value := value + StrToInt(Copy(in_str, 2, Length(in_str) - 1));
  NumericSoundex := value;
end;

{Calculate an extended Soundex encoding.}

function ExtendedSoundex(in_str: string): string;

{Replace instances of fr_str with to_str in str.}
  procedure ReplaceString(var str: string; fr_str, to_str: string);
  var
    fr_len, i: Integer;
  begin
    fr_len := Length(fr_str);
    i := Pos(fr_str, str);
    while (i > 0) do
    begin
      str := Copy(str, 1, i - 1) + to_str + Copy(str, i + fr_len, Length(str) - i - fr_len + 1);
      i := Pos(fr_str, str);
    end;
  end;

var
  no_vowels: string;
  ch, last_ch: Char;
  i: Integer;
begin
  {Make upper case and remove leading and trailing spaces.}
  in_str := Trim(UpperCase(in_str));
  {Remove internal spaces.}
  ReplaceString(in_str, ' ', '');
  {Convert CHR to CR.}
  ReplaceString(in_str, 'CHR', 'CR');
  {Convert PH to F.}
  ReplaceString(in_str, 'PH', 'F');
  {Convert Z to S.}
  ReplaceString(in_str, 'Z', 'S');
  {Remove vowels and repeats.}
  last_ch := in_str[1]; {The last character used.}
  no_vowels := last_ch;
  for i := 2 to Length(in_str) do
  begin
    ch := in_str[i];
    case ch of
      'A', 'E', 'I', 'O', 'U':
        ; {Do nothing.}
    else
      {Skip it if it's a duplicate.}
      if (ch <> last_ch) then
      begin
        no_vowels := no_vowels + ch;
        last_ch := ch;
      end;
    end;
  end;
  ExtendedSoundex := no_vowels;
end;

end.

Used like this:

unit Sndx;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, SndxAlgs;

type
  TForm1 = class(TForm)
    InputText: TEdit;
    Label1: TLabel;
    CmdEncode: TButton;
    Label2: TLabel;
    Label3: TLabel;
    Panel1: TPanel;
    SoundexLabel: TLabel;
    Panel2: TPanel;
    NumericLabel: TLabel;
    Label4: TLabel;
    Panel3: TPanel;
    ExtendedLabel: TLabel;
    procedure CmdEncodeClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.CmdEncodeClick(Sender: TObject);
begin
  SoundexLabel.Caption := Soundex(InputText.Text);
  NumericLabel.Caption := Format('%d', [NumericSoundex(InputText.Text)]);
  ExtendedLabel.Caption := ExtendedSoundex(InputText.Text);
end;

end.


Solve 2:

The code below is designed for use in English language and does not work for special characters like French accents or German Umlauts

function StrSoundEx(const OrgString: string): string;
var
  s: string;
  PrevCh: Char;
  Ch: Char;
  i: Integer;
begin
  s := UpperCase(Trim(OrgString));
  if s <> '' then
  begin
    PrevCh := #0;
    result := s[1];
    for i := 2 to Length(s) do
    begin
      if Length(result) = 4 then
        break;
      Ch := s[i];
      if (Ch <> PrevCh) then
      begin
        if Ch in ['B', 'P', 'F', 'V'] then
          result := result + '1'
        else if Ch in ['C', 'S', 'K', 'G', 'J', 'Q', 'X', 'Z'] then
          result := result + '2'
        else if Ch in ['D', 'T'] then
          result := result + '3'
        else if Ch in ['L'] then
          result := result + '4'
        else if Ch in ['M', 'N'] then
          result := result + '5'
        else if Ch in ['R'] then
          result := result + '6';
        PrevCh := Ch;
      end;
    end;
  end;
  while Length(result) < 4 do
    result := result + '0';
end;


Solve 3:

The following differs from the standard Russell Soundex algorithm in that it lets you set the size of the Soundex code to something other than four characters:

{Given a string this fuction returns the Russell Soundex code for that string. Although the Russell Soundex code is limited to four characters this function allows you to get a code up to 16 characters in length. For names a six to eight character code reduces the number of false matches significantly.

Parameters:
TheWord: The string to be encoded.
SoundexSize: The number of characters in the returned code.

Returns: The Soundex code.}

function dgGetSoundexCode(TheWord: string; SoundexSize: Integer): string;
const
  MaxSize = 16;
var
  I: Integer;
  WorkString1, WorkString2: string;
begin
  {Raise an exception if the SoundexSize parameter is not in the allowed range}
  if not SoundexSize in [1..MaxSize] then
    raise Exception.Create('Soundex size must in the range 1 - 16.');
  {Convert the word to upper case}
  TheWord := UpperCase(TheWord);
  {Copy the first letter}
  WorkString1 := TheWord[1];
  {Copy the rest of the word to WordString1 deleting duplicate letters}
  for I := 2 to Length(TheWord) do
    if TheWord[I - 1] <> TheWord[I] then
      AppendStr(WorkString1, TheWord[I]);
  {Move the first letter to WorkString2}
  WorkString2 := WorkString1[1];
  {Compute the Soundex codes for the remaining letters}
  for I := 2 to Length(WorkString1) do
    case WorkString1[I] of
      'B', 'F', 'P', 'V':
        AppendStr(WorkString2, '1');
      'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z':
        Appendstr(WorkString2, '2');
      'D', 'T':
        Appendstr(WorkString2, '3');
      'L':
        Appendstr(WorkString2, '4');
      'M', 'N':
        Appendstr(WorkString2, '5');
      'R':
        Appendstr(WorkString2, '6');
    end;
  {Pad the string with zeros}
  WorkString1 := '';
  WorkString1 := dgFillString('0', MaxSize);
  AppendStr(WorkString2, WorkString1);
  Result := Copy(WorkString2, 1, SoundexSize);
end;

2004. január 11., vasárnap

How to run the Netscape Navigator automatically after closing a form


Problem/Question/Abstract:

How to run the Netscape Navigator automatically after closing a form

Answer:

Do you definitely want to start Netscape, or just the user's default browser? To start Netscape, in preference to anything else, something like this would work in the form's onclose handler (add registry and ShellAPI to your unit's uses list):


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
  reg: TRegistry;
  NetscapeVer, NetscapeDir: string;
begin
  {This has a number of shortcomings, not least the lame error handlers}
  reg := TRegistry.Create;
  try
    reg.RootKey := HKEY_LOCAL_MACHINE;
    if not reg.OpenKey('SOFTWARE\Netscape\Netscape Navigator', false) then
      exit;
    NetscapeVer := reg.ReadString('CurrentVersion');
    if not reg.OpenKey(NetscapeVer + '\Main', false) then
      exit;
    showmessage(reg.CurrentPath);
    NetscapeDir := reg.ReadString('Install Directory') + '\program\';
    ShellExecute(0, 'open', PChar(NetscapeDir + 'netscape.exe'), nil, nil, SW_NORMAL);
  finally
    reg.free;
  end;
end;


If you just wish to start the users browser you could do something like (having added ShellAPI
to your uses list):


ShellExecute(0, 'open', 'http://www.yahoo.com', nil, nil, SW_NORMAL);

2004. január 10., szombat

How to know if loading is completed when a document contains an iFrame


Problem/Question/Abstract:

If I open a document using .Navigate(URL) this document is loaded. Now, OnDocumentComplete would normally tell me when its done loading, however this document contains an iframe, and in that case the OnDocumentComplete is already fired when the first document is complete.

Answer:

procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
var
  CurWebrowser: IWebBrowser;
  TopWebBrowser: IWebBrowser;
  Document: OleVariant;
  WindowName: string;
begin
  CurWebrowser := pDisp as IWebBrowser;
  TopWebBrowser := (Sender as TWebBrowser).DefaultInterface;
  if CurWebrowser = TopWebBrowser then
    ShowMessage('Complete document was loaded')
  else
  begin
    Document := CurWebrowser.Document;
    WindowName := Document.ParentWindow.Name;
    ShowMessage(Format('Frame "%s" was loaded', [WindowName]));
  end;
end;

2004. január 9., péntek

How to restore / set focus to an application after re-running the executable


Problem/Question/Abstract:

I'm trying to restore/ set focus to my app after re-running the exe. I've tried using the Windows.Setfocus(FormHandle) command without success. I've also tried using ShowWindow. Doing this doesn't set focus to the window. If the the window is minimizied it restores it to the screen ok but the application still believes it is minimized, thus you can't minimize the window. You can overcome this by first right clicking the app's taskbar button and selecting restore. The minimize button then works correctly.

Answer:

You have to deal with the first instances Application window, not with the main form.

{$R *.RES}

function AlreadyRunning: Boolean;
var
  wndmain, wndapp: HWND;
begin
  wndmain := FindWindow('TMDIForm', nil);
  {should really use a more unique classname}
  result := wndmain <> 0;
  if result then
  begin
    wndapp := GetWindowLong(wndmain, GWL_WNDPARENT);
    if IsIconic(wndapp) then
      SendMessage(wndapp, WM_SYSCOMMAND, SC_RESTORE, 0)
    else
      SetForegroundWindow(wndapp);
  end;
end;

begin
  if AlreadyRunning then
    Exit;
  Application.Initialize;
  Application.Title := 'J&S Library Manager';
  Application.CreateForm(TMDIForm, MDIForm);
  Application.CreateForm(TEditTextForm, EditTextForm);
  Application.CreateForm(TOptionForm, OptionForm);
  Application.CreateForm(TAboutBox, AboutBox);
  Application.Run;
end.

I have a deep aversion against directly manipulating a window from outside, so I usually don't restore/show the first instances window from the second instance but instead send a message to the first instances main form and have it restore/show itself in a handler for the message. Using WM_COPYDATA it is also easy to pass on a commandline to the first instance this way.

2004. január 8., csütörtök

Traverse the global list of all windows


Problem/Question/Abstract:

Traverse the global list of all windows

Answer:

Sometimes you may want to do something with all windows (and controls) on the screen, including non-Delphi windows.

For such a purpose, you will use the API function EnumWindows. The following code includes the calls MakeProcInstance/ FreeProcInstance, which are needed in 16bit-Windows (including Delphi 1 under Win95).

This sample code hides every existing window.. a rather useless example, but after all, it's just an example.


function NextWindow(Wnd: HWnd; Form: TForm1): Boolean; export;
{$IFDEF Win32} stdcall;
{$ENDIF}
begin
  ShowWindow(Wnd, SW_HIDE);
  NextWindow := true; { next window, please }
end;

procedure TForm1.Sample;
var
  EnumProc: TFarProc;
begin
  { this works in Win32 }
  EnumWindows(@NextWindow, LongInt(Self));

  { MakeProcInstance for Win16 }
  EnumProc := MakeProcInstance(@NextWindow, HInstance);
  EnumWindows(EnumProc, 0);
  FreeProcInstance(EnumProc);
end;

2004. január 7., szerda

How to calculate the approximate date of birth given the age


Problem/Question/Abstract:

How to calculate the approximate date of birth given the age

Answer:

function TFFuncs.CalcDateFromAge(Age: Integer): TDateTime;
var
  month, day, year, bmonth, bday, byear: word;
  CalcString: string;
begin
  DecodeDate(Date, byear, bmonth, bday);
  byear := byear - Age;
  if (100 * month + day) < (100 * bmonth + bday) then
    byear := byear - 1;
  CalcString := Copy(IntToStr(BMonth), 1, 2) + '/';
  CalcString := CalcString + Copy(IntToStr(BDay), 1, 2) + '/';
  CalcString := CalcString + Copy(IntToStr(BYear), 1, 4);
  Result := StrToDate(CalcString);
end;

2004. január 6., kedd

Using Anonymous Proxy Servers


Problem/Question/Abstract:

If I am blocked from accessing a website because my ip address is banned, how do I bypass this?

Answer:

If you are writing Internet applications, there may come across a time when your application is blocked from accessing a website. You will get error 403 &#8211; &#8220;your IP address is on a blocked list&#8221;. In my case it happened that we had been given permission to use the data (owned by D) except it was in a website (owned by W).  W didn&#8217;t like us pulling D&#8217;s data even though D had given us permission. The data was extracted every night by a Delphi web application.

For many people this will rarely be a problem because their IP address is allocated dynamically by their ISP. But if yours is static, you need to use an Anonymous Proxy Server. These are ip addresses which you plug into Internet Explorer or HTTP components (such as the ones provided by Winshoes). Anonymous Proxy Servers can be simple Perl scripts that people setup. They can last hours, days or months but do not rely on them. One day they are there- next day- gone. What is important is that the ip address that is logged by the server is the ip of the anonymous proxy, not yours.

You can set the proxy server manually. The code below lets you get the Proxy Server in Ie under Windows Nt 4.0 so that it can be plugged into the HTTPGET component. It has not been tested under Windows 95, 98 or 2000.  ieproxyip is the dotted quad part of the ip address and ieproxyport is the port (usually but not always 80).

References to 10.0.0.2 are the local proxy server. Change these to your own.

procedure GetIEProxy(var ieproxyip: string; var ieproxyport: Integer);
var
  Registry: TRegistry;
  S: string;
  Index: Integer;
  keylist: TStringList;
  KeyName: string;

  procedure GetProxyDetails;
  var
    S, AproxyStr: string;
    Lastfound: Boolean;

    function SkipTo(Marker: string; var Text: string): string;
    var
      P: Integer;
    begin
      Marker := UpperCase(Marker);
      Lastfound := False;
      P := Pos(Marker, UpperCase(Text));
      if P > 0 then
      begin
        result := Copy(Text, P, Length(Text));
        Lastfound := True;
      end
      else
        result := '';
    end;

    function skipforward(N: Integer; ftext: string): string;
    begin
      Result := Copy(ftext, N + 1, 1000);
    end;

    function Skippast(const Marker: string; var Text: string): string;
    var
      tlf: Boolean;
    begin
      Result := SkipTo(Marker, Text);
      tlf := Lastfound;
      if Lastfound then
        Result := skipforward(Length(Marker), Result);
      Lastfound := tlf;
    end;

    function Textupto(const Marker: string; var Text: string): string;
    var
      P: Integer;
    begin
      Result := '';
      Lastfound := False;
      P := Pos(UpperCase(Marker), UpperCase(Text));
      if P > 0 then
      begin
        Result := Copy(Text, 1, P - 1);
        Text := Copy(Text, P, Length(Text));
        Lastfound := True;
      end;
    end;

  begin
    S := Registry.ReadString('ProxyServer');

    if Pos('://', S) > 0 then
    begin
      repeat
        S := Skippast('://', S);
        if Pos(';', S) > 0 then
          AproxyStr := Textupto(';', S)
        else
          AproxyStr := S;
      until not Lastfound or (Pos('10.0.0.2', AproxyStr) = 0);
    end
    else
      AproxyStr := S;

    ieproxyip := '';
    ieproxyport := 80; // Default
    if Index > 0 then
    begin
      Index := Pos(':', AproxyStr); // find port
      if Index = 0 then
        ieproxyip := AproxyStr
      else
      begin
        ieproxyip := trim(Copy(AproxyStr, 1, Index - 1));
        try
          ieproxyport := StrToInt(trim(Copy(AproxyStr, Index + 1, 10)));
        except
        end;
      end;
    end;
  end;

begin
  Registry := TRegistry.Create;
  Registry.Access := Key_read;
  keylist := TStringList.Create;
  Registry.RootKey := HKEY_CURRENT_USER;
  if Registry.OpenKeyReadOnly('Software\Microsoft\Protected Storage System Provider')
    then
  begin
    Registry.GetKeyNames(keylist);
    S := keylist[0];
  end
  else
    Exit;
  KeyName := S;
  Registry.RootKey := HKEY_USERS;

  if Registry.OpenKey(KeyName, False) then
    if Registry.HasSubkeys then
    begin
      KeyName := 'Software\Microsoft\Windows\CurrentVersion\Internet Settings';
      if Registry.OpenKey(KeyName, False) then
      begin
        GetProxyDetails;
      end;
    end;
  Registry.Free;
end;

A good source of proxy server addresses is www.deny.de

2004. január 5., hétfő

Create menus from directory tree (advanced)


Problem/Question/Abstract:

The enhanced version of my CreateTreeMenus

Answer:

You nedd to create only a ImageList and a Menu.

procedure TfrmMain.CreateTreeMenus(Path: string; Root: TMenuItem; ListImage:
  TImageList);
type
  pHIcon = ^HIcon;
var
  SR: TSearchRec;
  Result: Integer;
  Item: TMenuItem;
  SmallIcon: HIcon;
  IconA: TIcon;
  BitMapA: TBitMap;
  Indice: Integer;
  procedure GetAssociatedIcon(FileName: TFilename; pLargeIcon, PSmallIcon: pHIcon);
  var
    IconIndex: Word;
    FileExt: string;
    FileType: string;
    Reg: TRegistry;
    p: Integer;
    p1: pChar;
    p2: pChar;
    function GetSystemDir: TFileName;
    var
      SysDir: array[0..MAX_PATH - 1] of Char;
    begin
      SetString(Result, SysDir, GetSystemDirectory(SysDir, MAX_PATH));
      if (Result = '') then
        raise Exception.Create(SysErrorMessage(GetLastError));
    end;
  label
    NoAssoc;
  begin
    IconIndex := 0;
    FileExt := UpperCase(ExtractFileExt(FileName));
    if (((FileExt <> '.EXE') and (FileExt <> '.ICO')) or (not (FileExists(FileName))))
      then
    begin
      Reg := nil;
      try
        Reg := TRegistry.Create(KEY_QUERY_VALUE);
        Reg.RootKey := HKEY_CLASSES_ROOT;
        if (FileExt = '.EXE') then
          FileExt := '.COM';
        if (Reg.OpenKeyReadOnly(FileExt)) then
        try
          FileType := Reg.ReadString('');
        finally
          Reg.CloseKey;
        end;
        if ((FileType <> '') and Reg.OpenKeyReadOnly(FileType + '\DefaultIcon')) then
        try
          FileName := Reg.ReadString('');
        finally
          Reg.CloseKey;
        end;
      finally
        Reg.Free;
      end;
      if (FileName = '') then
        goto NoAssoc;
      p1 := PChar(FileName);
      p2 := StrRScan(p1, ',');
      if (p2 <> nil) then
      begin
        p := p2 - p1 + 1;
        IconIndex := StrToInt(Copy(FileName, p + 1, Length(FileName) - p));
        SetLength(FileName, p - 1);
      end;
    end;
    if (ExtractIconEx(PChar(FileName), IconIndex, PLargeIcon^, PSmallIcon^, 1) <> 1)
      then
    begin
      NoAssoc:
      try
        FileName := IncludeTrailingBackslash(GetSystemDir) + 'SHELL32.DLL';
      except
        FileName := 'C:\WINDOWS\SYSTEM\SHELL32.DLL';
      end;
      if (FileExt = '.DOC') then
        IconIndex := 1
      else if ((FileExt = '.EXE') or (FileExt = '.COM')) then
        IconIndex := 2
      else if (FileExt = '.HLP') then
        IconIndex := 23
      else if ((FileExt = '.INI') or (FileExt = '.INF')) then
        IconIndex := 63
      else if (FileExt = '.TXT') then
        IconIndex := 64
      else if (FileExt = '.BAT') then
        IconIndex := 65
      else if ((FileExt = '.DLL') or (FileExt = '.SYS') or (FileExt = '.VBX') or
        (FileExt = '.OCX') or (FileExt = '.VXD')) then
        IconIndex := 66
      else if (FileExt = '.FON') then
        IconIndex := 67
      else if (FileExt = '.TTF') then
        IconIndex := 68
      else if (FileExt = '.FOT') then
        IconIndex := 69
      else
        IconIndex := 0;
      if ((ExtractIconEx(PChar(FileName), IconIndex, PLargeIcon^, PSmallIcon^, 1) <>
        1)) then
      begin
        if (PLargeIcon <> nil) then
          PLargeIcon^ := 0;
        if (PSmallIcon <> nil) then
          PSmallIcon^ := 0;
      end;
    end;
  end;
begin
  Path := IncludeTrailingBackSlash(Path);
  Result := FindFirst(Path + '*.*', faDirectory, SR);
  while (Result = 0) do
  begin
    if (((SR.Attr and faDirectory) <> 0) and (SR.Name <> '.') and (SR.Name <> '..'))
      then
    begin
      Item := TMenuItem.Create(Self);
      Item.Caption := SR.Name;
      Item.ImageIndex := 0;
      Root.Add(Item);
      CreateTreeMenus(Path + SR.Name, Item, ListImage);
    end;
    if (((SR.Attr and faAnyFile) <> 0) and (SR.Name <> '.') and (SR.Name <> '..'))
      then
    begin
      Item := TMenuItem.Create(Self);
      Item.Caption := SR.Name;
      GetAssociatedIcon(sr.Name, nil, @SmallIcon);
      IconA := TIcon.Create;
      IconA.Handle := SmallIcon;
      BitMapA := TBitMap.Create;
      BitMapA.Width := IconA.Width;
      BitMapA.Height := IconA.Height;
      BitMapA.Canvas.Draw(0, 0, IconA);
      BitMapA.TransparentMode := tmAuto;
      Indice := ListImage.Add(BitMapA, nil);
      Item.ImageIndex := Indice;
      Root.Add(Item);
    end;
    Result := FindNext(SR);
  end;
  SysUtils.FindClose(SR);
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  CreateTreeMenus('c:\projects\', directory1, ImageList1);
end;

You can also use shgetfileinfo with SHGFI_ICON parameter in the place of checking individual file extension.

2004. január 4., vasárnap

Undo - Redo using State (update 2)


Problem/Question/Abstract:

Do you need to implement undo and redo in your application?  Here is a simple method, with source, that does the job for small data (up to 20 or 100K in memory)

Answer:

There are 2 methods of Undo-Redo that I know of. The first is saving the current state of the system into a list before it is modified. There would be a GetState and SetState method of your editor.  The second method is to store commands, where each command can undo and redo itself.

Saving state is a good choice when your editor data is small such as 10 to 20K and your editor has many capabilities. Saving state is a simple solution. If you are doing image editing then you could get by with using a file to store your undo and redo information. A vector graphics editor would be a good choice here because vectors do not need much storage space.

The more complex solution of storing commands requires much more coding but is nessesary when your editor edits large amounts of data and storing its state would be too time consuming. A word processor is an example.

I have coded an Undo-Redo State class.. here is how it works. There is the main class that holds the state snapshots (TUndoRedoState), then there is the interface "IState" that has 2 methods, GetState and SetState. I implemented this by making my editor form implement the IState interface.

The main class is created and passed the IState interface. Calling Undo and Redo makes calls to GetState and SetState. If you do not like the way I use an interface then you can easily change the class to accept method pointers to some GetState and SetState method, but I prefer the Interface.

{
  Author William Egge, egge@eggcentric.com
         http://www.eggcentric.com

  Download this working example at http://www.eggcentric.com/UndoRedoState.htm

  This is a demo of using TUndoRedoState.
  Created June 13, 2001

  Enjoy!
}
unit Frm_UndoRedoExample;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, ExtCtrls, UndoRedoState, _State;

type
  // Make this form implement the IState interface to be used
  // by the UndoRedoState object.
  TForm_UndoRedoExample = class(TForm, IState)
    FDrawSurface: TImage;
    FRedoBtn: TSpeedButton;
    FUndoBtn: TSpeedButton;
    FDirections: TLabel;
    procedure Ev_FormCreate(Sender: TObject);
    procedure Ev_FUndoBtnClick(Sender: TObject);
    procedure Ev_FRedoBtnClick(Sender: TObject);
    procedure Ev_FDrawSurfaceMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Ev_FDrawSurfaceMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure Ev_FDrawSurfaceMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Ev_FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FUndoRedo: TUndoRedoState;
    FMouseDown: Boolean;
  public
    { Public declarations }
    // Methods that implement the IState interface
    procedure GetState(S: TStream);
    procedure SetState(S: TStream);
  end;

var
  Form_UndoRedoExample: TForm_UndoRedoExample;

implementation

{$R *.DFM}

procedure TForm_UndoRedoExample.GetState(S: TStream);
begin
  FDrawSurface.Picture.Bitmap.SaveToStream(S);
end;

procedure TForm_UndoRedoExample.SetState(S: TStream);
begin
  FDrawSurface.Picture.Bitmap.LoadFromStream(S);
end;

procedure TForm_UndoRedoExample.Ev_FormCreate(Sender: TObject);
begin
  // Create a bitmap to draw on
  with FDrawSurface.Picture.Bitmap do
  begin
    Width := FDrawSurface.Width;
    Height := FDrawSurface.Height;
  end;

  // Create the UndoRedo object, this form implements the state interface
  FUndoRedo := TUndoRedoState.Create(Self);
end;

procedure TForm_UndoRedoExample.Ev_FUndoBtnClick(Sender: TObject);
begin
  FUndoRedo.Undo;
end;

procedure TForm_UndoRedoExample.Ev_FRedoBtnClick(Sender: TObject);
begin
  FUndoRedo.Redo;
end;

procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  // It is possible to get 2 mouse down events with no mouse up event, but rarely
  // Get out when this happens and let mouse up reset it to false.
  if FMouseDown then
    Exit;

  FMouseDown := True;
  FUndoRedo.BeginModify;

  // Set our start point where you first click
  FDrawSurface.Canvas.MoveTo(X, Y);
end;

procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  // Draw
  if FMouseDown then
    FDrawSurface.Canvas.LineTo(X, Y);
end;

procedure TForm_UndoRedoExample.Ev_FDrawSurfaceMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  // Finished Editing
  if FMouseDown then
  begin
    FUndoRedo.EndModify;
    FMouseDown := False;
  end;
end;

procedure TForm_UndoRedoExample.Ev_FormDestroy(Sender: TObject);
begin
  FUndoRedo.Free;
end;

end.

Full Source of UndoRedoState.pas and _State.pas:
2 units:

unit _State;

interface
uses
  Classes;

type
  IState = interface
    procedure GetState(S: TStream);
    procedure SetState(S: TStream);
  end;

implementation

end.

[ver 2, update: fixed problem where setting state the stream needed to be set back to position 0 before calling setState]

unit UndoRedoState;
{
  Author William Egge
         egge@eggcentric.com
         http://www.eggcentric.com
}

interface
uses
  _State, Classes, SysUtils;

// A value of 0 for MaxMemoryUsage means unlimited (default).
type
  TUndoRedoState = class
  private
    FState: IState;
    FUndoRedoList: TList;
    FModifyCount: Integer;
    FUndoPos: Integer;
    FTailState: TStream;
    FMaxMemoryUsage: LongWord;
    FCurrMemUsage: LongWord;
    function CreateCurrentState: TStream;
    procedure SetMaxMemoryUsage(const Value: LongWord);
    procedure TruncToMem;
  public
    constructor Create(AState: IState);
    property MaxMemoryUsage: LongWord read FMaxMemoryUsage write SetMaxMemoryUsage;
    procedure BeginModify;
    procedure EndModify;
    procedure Undo;
    procedure Redo;
    destructor Destroy; override;
  end;

implementation

{ TUndoRedoState }

procedure TUndoRedoState.BeginModify;
var
  I: Integer;
  S: TStream;
begin
  Inc(FModifyCount);
  if FModifyCount = 1 then
  begin
    for I := FUndoRedoList.Count - 1 downto FUndoPos + 1 do
    begin
      S := FUndoRedoList[I];
      Dec(FCurrMemUsage, S.Size);
      FUndoRedoList.Delete(I);
      S.Free;
    end;
    S := CreateCurrentState;
    Inc(FCurrMemUsage, S.Size);
    FUndoRedoList.Add(S);
    FUndoPos := FUndoRedoList.Count - 1;
    if FTailState <> nil then
    begin
      Dec(FCurrMemUsage, FTailState.Size);
      FreeAndNil(FTailState);
    end;
    TruncToMem;
  end;
end;

constructor TUndoRedoState.Create(AState: IState);
begin
  Assert(AState <> nil, 'AState should not be nil for '
    + '"TUndoRedoState.Create(AState: IState)"');

  inherited Create;
  FState := AState;
  FUndoRedoList := TList.Create;
  FUndoPos := -1;
end;

function TUndoRedoState.CreateCurrentState: TStream;
begin
  Result := TMemoryStream.Create;
  try
    FState.GetState(Result);
  except
    Result.Free;
    raise;
  end;
end;

destructor TUndoRedoState.Destroy;
var
  I: Integer;
begin
  FState := nil;
  for I := 0 to FUndoRedoList.Count - 1 do
    TObject(FUndoRedoList[I]).Free;

  FTailState.Free;

  inherited Destroy;
end;

procedure TUndoRedoState.EndModify;
begin
  Assert(FModifyCount > 0, 'TUndoRedoState.EndModify: EndModify was called '
    + 'more times than BeginModify');

  Dec(FModifyCount);
end;

procedure TUndoRedoState.Redo;
var
  FRedoPos: Integer;
  S: TStream;
begin
  Assert(FModifyCount = 0, 'TUndoRedoState.Redo: should not be called while '
    + 'modifying');

  if (FUndoRedoList.Count > 0) and (FUndoPos < (FUndoRedoList.Count - 1)) then
  begin
    FRedoPos := FUndoPos + 2;
    if FRedoPos > (FUndoRedoList.Count - 1) then
    begin
      FTailState.Position := 0;
      FState.SetState(FTailState);
      Dec(FCurrMemUsage, FTailState.Size);
      FreeAndNil(FTailState);
    end
    else
    begin
      S := FUndoRedoList[FRedoPos];
      S.Position := 0;
      FState.SetState(S);
    end;
    Inc(FUndoPos);
  end;
end;

procedure TUndoRedoState.SetMaxMemoryUsage(const Value: LongWord);
begin
  FMaxMemoryUsage := Value;
end;

procedure TUndoRedoState.TruncToMem;
var
  S: TStream;
begin
  if (FMaxMemoryUsage > 0) and (FCurrMemUsage > FMaxMemoryUsage) then
  begin
    while (FUndoRedoList.Count > 0) and (FCurrMemUsage > FMaxMemoryUsage) do
    begin
      S := FUndoRedoList[0];
      FUndoRedoList.Delete(0);
      Dec(FCurrMemUsage, S.Size);
      Dec(FUndoPos);
      S.Free;
    end;

    if (FUndoRedoList.Count = 0) and (FCurrMemUsage > FMaxMemoryUsage) then
      if FTailState <> nil then
      begin
        Dec(FCurrMemUsage, FTailState.Size);
        FreeAndNil(FTailState);
      end;
  end;
end;

procedure TUndoRedoState.Undo;
var
  S: TStream;
begin
  Assert(FModifyCount = 0, 'TUndoRedoState.Undo: should not be called while '
    + 'modifying');

  if FUndoPos >= 0 then
  begin
    if FUndoPos = (FUndoRedoList.Count - 1) then
    begin
      FTailState := CreateCurrentState;
      Inc(FCurrMemUsage, FTailState.Size);
    end;
    S := FUndoRedoList[FUndoPos];
    S.Position := 0;
    Dec(FUndoPos);
    FState.SetState(S);
    TruncToMem;
  end;
end;

end.


Component Download: http://www.eggcentric.com/UndoRedoState.zip

2004. január 3., szombat

Differentiating Between the Two ENTER Keys


Problem/Question/Abstract:

How to find difference between the two ENTER keys?

Answer:

An application may find it useful to differentiate between the user pressing the ENTER key on the standard keyboard and the ENTER key on the numeric keypad. Either action creates a WM_KEYDOWN message and a WM_KEYUP message with wParam set to the virtual key code VK_RETURN. When the application passes these messages to TranslateMessage, the application receives a WM_CHAR message with wParam set to the corresponding ASCII code 13.

To differentiate between the two ENTER keys, test bit 24 of lParam sent with the three messages listed above. Bit 24 is set to 1 if the key is an extended key; otherwise, bit 24 is set to 0 (zero).

Because the keys in the numeric keypad (along with the function keys) are extended keys, pressing ENTER on the numeric keypad results in bit 24 of lParam being set, while pressing the ENTER key on  the standard keyboard results in bit 24 clear.

The following code sample demonstrates differentiating between these two ENTER keys:

procedure TForm1.WMKeyDown(var Message: TWMKeyDown);
begin
  inherited;
  case Message.CharCode of
    VK_RETURN:
      begin // ENTER pressed
        if (Message.KeyData and $1000000 <> 0) then // Test bit 24 of lParam
        begin
          // ENTER on numeric keypad

        end
        else
        begin
          // ENTER on the standard keyboard

        end;
      end;
  end;
end;

2004. január 1., csütörtök

Policy Register Administration Class W2000

Problem/Question/Abstract:

There are many registry settings that affect system policy on the local machine. This class encompasses several of them into a single class. Policy registry entries can be changed individually (via properties) or multiple (via EnableStates and DisableStates) methods. You, of course have to have permissions to write to the Registry.

Properties

TaskManagerEnabled    : Enable/Disable W2000 task manager from popping up.

LockComputerEnabled   : Enable/Disable "Lock Computer" button from Ctrl-Alt-Del Dialog Form.

ChangePasswordEnabled : Enable/Disable "Change Password" button from Ctrl-Alt-Del Dialog Form.

LogOffEnabled         : Enable/Disable "Log Off" button from Ctrl-Alt-Del Dialog Form.

ShutDownEnabled       : Enable/Disable "Shut Down" button from Ctrl-Alt-Del Dialog Form.

RegistryToolsEnabed   : Enable/Disable access to Registry Tools such as  REGEDIT.EXE etc.

DispPropertiesEnabled : Enable/Disable Display Properties dialog box.

Methods

EnableStates  : Enable multi states by passing a set of TRegPolicy

DisableStates : Disable multi states by passing a set of  TRegPolicy

Example

var
PolicyAdm: TPolicyAdmin;

begin
PolicyAdm := TPolicyAdmin.Create;
PolicyAdm.TaskManagerEnabled := false;

if PolicyAdm.LogOffEnabled then
label1.Caption := 'True'
else
label1.Caption := 'False';

PolicyAdm.DisableStates([rpTaskManager,
rpShutDown, rpLogOff]);
PolicyAdm.Free;
end.

Answer:

unit MahPolicyControl;
interface

uses Windows, SysUtils, Registry;

// ==========================================================================
// Class TPolicyAdmin : Encapsulate setting of registry for various Win 2000
// system policies.
//
// Mike Heydon 2004
//
// Properties
// ----------
// TaskManagerEnabled    : Enable/Disable W2000 task manager from popping up.
// LockComputerEnabled   : Enable/Disable "Lock Computer" button from
//                         Ctrl-Alt-Del Dialog Form.
// ChangePasswordEnabled : Enable/Disable "Change Password" button from
//                         Ctrl-Alt-Del Dialog Form.
// LogOffEnabled         : Enable/Disable "Log Off" button from
//                         Ctrl-Alt-Del Dialog Form.
// ShutDownEnabled       : Enable/Disable "Shut Down" button from
//                         Ctrl-Alt-Del Dialog Form.
// RegistryToolsEnabed   : Enable/Disable access to Registry Tools such as
//                         REGEDIT.EXE etc.
// DispPropertiesEnabled : Enable/Disable Display Properties dialog box.
//
// Methods
// -------
// EnableStates  : Enable multi states by passing a set of TRegPolicy
// DisableStates : Disable multi states by passing a set of TRegPolicy
//
// ==========================================================================

// ==========================================================================
// NOTES :
// -------
// There are other registry entries that may be set, but I have not had a
// need to implement them yet. Here is a listing if you wish to implement
// any of them.
//
// Hide display appearance tab in display properties
// C_REG_SYSTEM\NoDispAppearancePage
//
// Hide background tab in display properties
// C_REG_SYSTEM\NoDispBackgroundPage
//
// Hide screen-saver settings tab in display properties
// C_REG_SYSTEM\NoDispScrSavPage
//
// Hide display settings tab in display properties
// C_REG_SYSTEM\NoDispSettingsPage
//
// Remove Control Panel and Printers from Settings menu
// C_REG_EXPLORER\NoSetFolders
//
// Remove Taskbar settings from Settings menu
// C_REG_EXPLORER\NoSetTaskbar
//
// Disable context menus for taskbar
// C_REG_EXPLORER\NoTrayContextMenu
//
// Disable explorer's default context menus
// C_REG_EXPLORER\NoViewContextMenu
//
// ==========================================================================

type
// Registry Setting Type and Set
TRegPolicy = (rpTaskManager, rpLockComputer, rpChangePassword, rpLogOff,
rpShutDown, rpRegistryTools, rpDispProperties);
TRegPolicySet = set of TRegPolicy;

// Main Class TPolicyAdmin
TPolicyAdmin = class(TObject)
private
FReg, FKey: string;
FWinReg: TRegistry;
protected
// Internal Routines
procedure _SetRegKeyInfo(ARegPolicy: TRegPolicy);
procedure _SetState(ARegPolicy: TRegPolicy;
AState: boolean);
function _GetState(ARegPolicy: TRegPolicy): boolean;
procedure _DisableStates(ARegPolicySet: TRegPolicySet);
procedure _EnableStates(ARegPolicySet: TRegPolicySet);

// Set Methods
procedure SetTaskManagerEnabled(AValue: boolean);
procedure SetLockComputerEnabled(AValue: boolean);
procedure SetChangePasswordEnabled(AValue: boolean);
procedure SetLogOffEnabled(AValue: boolean);
procedure SetShutDownEnabled(AValue: boolean);
procedure SetRegistryToolsEnabled(AValue: boolean);
procedure SetDispPropertiesEnabled(AValue: boolean);

// Get Methods
function GetTaskManagerEnabled: boolean;
function GetLockComputerEnabled: boolean;
function GetChangePasswordEnabled: boolean;
function GetLogOffEnabled: boolean;
function GetShutDownEnabled: boolean;
function GetRegistryToolsEnabled: boolean;
function GetDispPropertiesEnabled: boolean;
public
constructor Create;
destructor Destroy; override;

// Methods
procedure DisableStates(ARegPolicySet: TRegPolicySet);
procedure EnableStates(ARegPolicySet: TRegPolicySet);

// Properties
property TaskManagerEnabled: boolean read GetTaskManagerEnabled
write SetTaskManagerEnabled;
property LockComputerEnabled: boolean read GetLockComputerEnabled
write SetLockComputerEnabled;
property ChangePasswordEnabled: boolean read GetChangePasswordEnabled
write SetChangePasswordEnabled;
property LogOffEnabled: boolean read GetLogOffEnabled
write SetLogOffEnabled;
property ShutDownEnabled: boolean read GetShutDownEnabled
write SetShutDownEnabled;
property RegistryToolsEnabled: boolean read GetRegistryToolsEnabled
write SetRegistryToolsEnabled;
property DispPropertiesEnabled: boolean read GetDispPropertiesEnabled
write SetDispPropertiesEnabled;
end;

// --------------------------------------------------------------------------
implementation

const
// Registry and Key constants
C_REG_POLICIES = '\Software\Microsoft\Windows\CurrentVersion\Policies';
C_REG_SYSTEM = C_REG_POLICIES + '\System';
C_REG_EXPLORER = C_REG_POLICIES + '\Explorer';
C_KEY_TASKMANAGER = 'DisableTaskMgr';
C_KEY_LOCKCOMPUTER = 'DisableLockWorkstation';
C_KEY_CHANGEPASSWORD = 'DisableChangePassword';
C_KEY_LOGOFF = 'NoLogoff';
C_KEY_SHUTDOWN = 'NoClose';
C_KEY_REGISTRYTOOLS = 'DisableRegistryTools';
C_KEY_DISPPROPERTIES = 'NoDispCPL';

// Reverse boolean logic for "ENABLED in proprties"
// to "DISABLED in Registry entries"
C_ENABLE = false;
C_DISABLE = true;

// =================================
// Create and Destroy the Class
// =================================

constructor TPolicyAdmin.Create;
begin
FWinReg := TRegistry.Create;
end;

destructor TPolicyAdmin.Destroy;
begin
FWinReg.Free;

inherited Destroy;
end;

// ====================================================
// Internal Procedures to handle Registry settings
// NOTE : We use ENABLED properties, but the Registry
//        stores the settings as Disabled TRUE/FALSE
//        so we use NOT logic to convert for our use
// ====================================================

// Set Registry key information into Privates

procedure TPolicyAdmin._SetRegKeyInfo(ARegPolicy: TRegPolicy);
begin
case ARegPolicy of
rpTaskManager:
begin
FReg := C_REG_SYSTEM;
FKey := C_KEY_TASKMANAGER;
end;

rpLockComputer:
begin
FReg := C_REG_SYSTEM;
FKey := C_KEY_LOCKCOMPUTER;
end;

rpChangePassword:
begin
FReg := C_REG_SYSTEM;
FKey := C_KEY_CHANGEPASSWORD;
end;

rpLogOff:
begin
FReg := C_REG_EXPLORER;
FKey := C_KEY_LOGOFF;
end;

rpShutDown:
begin
FReg := C_REG_EXPLORER;
FKey := C_KEY_SHUTDOWN;
end;

rpRegistryTools:
begin
FReg := C_REG_SYSTEM;
FKey := C_KEY_REGISTRYTOOLS;
end;

rpDispProperties:
begin
FReg := C_REG_SYSTEM;
FKey := C_KEY_DISPPROPERTIES;
end;
else
raise Exception.Create('Internal TPolicyAdmin Error');
end;
end;

// Read Current Enabled State

function TPolicyAdmin._GetState(ARegPolicy: TRegPolicy): boolean;
var
bResult: boolean;
begin
bResult := true;
_SetRegKeyInfo(ARegPolicy);
FWinReg.RootKey := HKEY_CURRENT_USER;

if FWinReg.OpenKey(FReg, false) then
begin
if FWinReg.ValueExists(FKey) then
bResult := boolean(FWinReg.ReadInteger(FKey))
else
bResult := true;
FWinReg.CloseKey;
end;

// Registry stores state related to "DISABLED", we requiire logic
// related to "ENABLED" - so reverse boolean result
Result := not bResult;
end;

// Set Current State (Using Disabled logic)

procedure TPolicyAdmin._SetState(ARegPolicy: TRegPolicy; AState: boolean);
begin
_SetRegKeyInfo(ARegPolicy);
FWinReg.RootKey := HKEY_CURRENT_USER;

if FWinReg.OpenKey(FReg, true) then
begin
FWinReg.WriteInteger(FKey, integer(AState));
FWinReg.CloseKey;
end;
end;

// Internal enable states from a TRegPolicySet

procedure TPolicyAdmin._EnableStates(ARegPolicySet: TRegPolicySet);
begin
if rpTaskManager in ARegPolicySet then
_SetState(rpTaskManager, C_ENABLE);
if rpLockComputer in ARegPolicySet then
_SetState(rpLockComputer, C_ENABLE);
if rpChangePassword in ARegPolicySet then
_SetState(rpChangePassword, C_ENABLE);
if rpLogOff in ARegPolicySet then
_SetState(rpLogOff, C_ENABLE);
if rpShutDown in ARegPolicySet then
_SetState(rpShutDown, C_ENABLE);
if rpRegistryTools in ARegPolicySet then
_SetState(rpRegistryTools, C_ENABLE);
if rpDispProperties in ARegPolicySet then
_SetState(rpDispProperties, C_ENABLE);
end;

// Internal disable states from a TRegPolicySet

procedure TPolicyAdmin._DisableStates(ARegPolicySet: TRegPolicySet);
begin
if rpTaskManager in ARegPolicySet then
_SetState(rpTaskManager, C_DISABLE);
if rpLockComputer in ARegPolicySet then
_SetState(rpLockComputer, C_DISABLE);
if rpChangePassword in ARegPolicySet then
_SetState(rpChangePassword, C_DISABLE);
if rpLogOff in ARegPolicySet then
_SetState(rpLogOff, C_DISABLE);
if rpShutDown in ARegPolicySet then
_SetState(rpShutDown, C_DISABLE);
if rpRegistryTools in ARegPolicySet then
_SetState(rpRegistryTools, C_DISABLE);
if rpDispProperties in ARegPolicySet then
_SetState(rpDispProperties, C_DISABLE);
end;

// ===============================
// Get/Set Property Methods
// ===============================

// Task Manager

procedure TPolicyAdmin.SetTaskManagerEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpTaskManager])
else
_DisableStates([rpTaskManager]);
end;

function TPolicyAdmin.GetTaskManagerEnabled: boolean;
begin
Result := _GetState(rpTaskManager);
end;

// Lock Computer Button

procedure TPolicyAdmin.SetLockComputerEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpLockComputer])
else
_DisableStates([rpLockComputer]);
end;

function TPolicyAdmin.GetLockComputerEnabled: boolean;
begin
Result := _GetState(rpLockComputer);
end;

// Change Password Button

procedure TPolicyAdmin.SetChangePasswordEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpChangePassword])
else
_DisableStates([rpChangePassword]);
end;

function TPolicyAdmin.GetChangePasswordEnabled: boolean;
begin
Result := _GetState(rpChangePassword);
end;

// Log Off Button

procedure TPolicyAdmin.SetLogOffEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpLogOff])
else
_DisableStates([rpLogOff]);
end;

function TPolicyAdmin.GetLogOffEnabled: boolean;
begin
Result := _GetState(rpLogOff);
end;

// Shut Down Button

procedure TPolicyAdmin.SetShutDownEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpShutDown])
else
_DisableStates([rpShutDown]);
end;

function TPolicyAdmin.GetShutDownEnabled: boolean;
begin
Result := _GetState(rpShutDown);
end;

// Registry Tools (REGEDIT)

procedure TPolicyAdmin.SetRegistryToolsEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpRegistryTools])
else
_DisableStates([rpRegistryTools]);
end;

function TPolicyAdmin.GetRegistryToolsEnabled: boolean;
begin
Result := _GetState(rpRegistryTools);
end;

// Display Properties Dialog

procedure TPolicyAdmin.SetDispPropertiesEnabled(AValue: boolean);
begin
if AValue then
_EnableStates([rpDispProperties])
else
_DisableStates([rpDispproperties]);
end;

function TPolicyAdmin.GetDispPropertiesEnabled: boolean;
begin
Result := _GetState(rpDispProperties);
end;

// ==============================
// User Callabel Methods
// ==============================

procedure TPolicyAdmin.DisableStates(ARegPolicySet: TRegPolicySet);
begin
_DisableStates(ARegPolicySet);
end;

procedure TPolicyAdmin.EnableStates(ARegPolicySet: TRegPolicySet);
begin
_EnableStates(ARegPolicySet);
end;

end.