home *** CD-ROM | disk | FTP | other *** search
/ PC Open 19 / pcopen19.iso / Zipped / CALMIR21.ZIP / SOURCE.ZIP / SRC / START.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-02-20  |  26.6 KB  |  969 lines

  1. {**************************************************************************}
  2. {                                                                          }
  3. {    Calmira shell for Microsoft« Windows(TM) 3.1                          }
  4. {    Source Release 2.1                                                    }
  5. {    Copyright (C) 1997-1998 Li-Hsin Huang                                 }
  6. {                                                                          }
  7. {    This program is free software; you can redistribute it and/or modify  }
  8. {    it under the terms of the GNU General Public License as published by  }
  9. {    the Free Software Foundation; either version 2 of the License, or     }
  10. {    (at your option) any later version.                                   }
  11. {                                                                          }
  12. {    This program is distributed in the hope that it will be useful,       }
  13. {    but WITHOUT ANY WARRANTY; without even the implied warranty of        }
  14. {    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         }
  15. {    GNU General Public License for more details.                          }
  16. {                                                                          }
  17. {    You should have received a copy of the GNU General Public License     }
  18. {    along with this program; if not, write to the Free Software           }
  19. {    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             }
  20. {                                                                          }
  21. {**************************************************************************}
  22.  
  23. unit Start;
  24.  
  25. { Start Menu
  26.  
  27.   TStartMenu is a popup menu that is owner-drawn, so that it has a 3D
  28.   effect and small icons.  The small icons are stored in one large bitmap
  29.   to conserve memory.  They are ordered in tree-traversal order, so when
  30.   the start menu next loads, all the small icons are in the right places.
  31.  
  32.   TStartMenuItem is the class of menu item that is used for the start
  33.   menu.  It has a Data property which holds a string containing
  34.   encoded information about the item.  This data can be expanded with
  35.   the ExtractStartInfo function.
  36. }
  37.  
  38. interface
  39.  
  40. uses Classes, SysUtils, Menus, Outline, Messages, WinTypes, Graphics, Forms;
  41.  
  42. type
  43.   TStartFlags = (sfTop, sfBottom, sfSeparator, sfLarge, sfNoRightEdge);
  44.  
  45.   TStartMenuItem = class(TMenuItem)
  46.   private
  47.     FData : PString;
  48.     FHeight : Integer;
  49.     TextLeft : Integer;
  50.     function GetData: string;
  51.     procedure SetData(const Value: string);
  52.   public
  53.     Flags : set of TStartFlags;
  54.     constructor Create(AOwner: TComponent); override;
  55.     destructor Destroy; override;
  56.     procedure Click; override;
  57.     procedure LoadGraphic; virtual; abstract;
  58.     function GetWidth : Integer; virtual; abstract;
  59.     procedure Paint(Canvas : TCanvas; Rect : TRect; state : Word);
  60.     procedure PaintIcon(Canvas : TCanvas; const Rect : TRect; state : Word); virtual; abstract;
  61.     property Data : string read GetData write SetData;
  62.     property Height : Integer read FHeight write FHeight;
  63.   end;
  64.  
  65.  
  66.   TLargeMenuItem = class(TStartMenuItem)
  67.   private
  68.     FIcon : TIcon;
  69.   public
  70.     constructor Create(AOwner: TComponent); override;
  71.     destructor Destroy; override;
  72.     procedure LoadGraphic; override;
  73.     function GetWidth : Integer; override;
  74.     procedure PaintIcon(Canvas : TCanvas; const Rect : TRect; state : Word); override;
  75.   end;
  76.  
  77.   TSmallMenuItem = class(TStartMenuItem)
  78.   private
  79.     FImgOffset : Word;
  80.   public
  81.     constructor Create(AOwner: TComponent); override;
  82.     procedure LoadGraphic; override;
  83.     function GetWidth : Integer; override;
  84.     procedure PaintIcon(Canvas : TCanvas; const Rect : TRect; state : Word); override;
  85.     property ImgOffset : Word read FImgOffset;
  86.   end;
  87.  
  88.   TStartMenuItemClass = class of TStartMenuItem;
  89.  
  90.   TStartMacroEvent = procedure (Sender : TObject;
  91.     const macro: string; params : string) of object;
  92.  
  93.   TMenuItemKind = (miSubMenu, miLeafNode, miAll);
  94.  
  95.   TStartMenu = class(TPopupMenu)
  96.   private
  97.     Canvas : TCanvas;
  98.     Window: HWND;
  99.     FOnStartMacro : TStartMacroEvent;
  100.     procedure WndProc(var Message: TMessage);
  101.     procedure SetOwnerDraw(menu : TMenuItem);
  102.     function GetHeight: Integer;
  103.     procedure HandleClick(Sender : TObject);
  104.   public
  105.     constructor Create(AOwner: TComponent); override;
  106.     destructor Destroy; override;
  107.     procedure Configure;
  108.     procedure Popup(X, Y: Integer; TrackLeft : Boolean);
  109.     procedure Clear;
  110.     function Find(const cap : string; Kind: TMenuItemKind): TMenuItem;
  111.     procedure Load;
  112.     procedure RunStartup;
  113.     procedure RebuildFromOutline(Outline : TOutline);
  114.     procedure AssignToOutline(Outline : TOutline);
  115.     procedure PopupMenuItem(h: HMenu; X, Y: Integer; TrackLeft : Boolean);
  116.     property OnStartMacro : TStartMacroEvent read FOnStartMacro write FOnStartMacro;
  117.     property Height : Integer read GetHeight;
  118.     property Helper : HWND read Window;
  119.   end;
  120.  
  121.   TStartImages = class(TBitmap)
  122.   private
  123.     FNext : Integer;
  124.   public
  125.     function Add(bmp : TBitmap): Integer;
  126.     property Next: Integer read FNext write FNext;
  127.   end;
  128.  
  129.   { TStartInfo is only slightly larger than a 255 char string, so
  130.     placing it on the stack is OK, provided that there is no recursion  }
  131.  
  132.   TStartInfo = record
  133.     Command: TFilename;
  134.     Directory : TFilename;
  135.     ShowMode : Integer;
  136.     IconFile : TFilename;
  137.     IconIndex : Integer;
  138.   end;
  139.  
  140. const
  141.   YieldDuringLoad : Boolean = False;
  142.  
  143.  
  144. function ExtractStartInfo(const s: string): TStartInfo;
  145. function PackStartInfo(const command, directory, iconfile: TFilename;
  146.   showmode, iconindex: Integer): string;
  147.  
  148. var StartMenu : TStartMenu;
  149.  
  150. implementation
  151.  
  152. uses Strings, IniFiles, Desk, Files, Directry, Dialogs, FileMan, Environs,
  153.   Controls, MiscUtil, WinProcs, CompSys, Referenc, Settings, Resource;
  154.  
  155. { TStartMenu and its items need to share some graphics objects, so they
  156.   are global rather than parameters }
  157.  
  158. var
  159.   Images : TStartImages;
  160.   TempIcon : TIcon;
  161.   SmallBmp : TBitmap;
  162.   UsingCache : Boolean;
  163.   FillMenu : Boolean;           
  164.   SmallItemHeight : Integer;
  165.   MenuFontHeight : Integer;
  166.   Banner : TResBitmap;
  167.   BannerWidth : Integer;
  168.  
  169.  
  170. const
  171.   HighlightSilver : array[Boolean] of TColor = (clSilver, clNavy);
  172.  
  173.  
  174. function TStartImages.Add(bmp : TBitmap): Integer;
  175. begin
  176.   if FNext + 16 > Width then Width := Width + (64 * 16);
  177.   Result := FNext;
  178.   Canvas.Draw(FNext, 0, bmp);
  179.   Inc(FNext, 16);
  180. end;
  181.  
  182.  
  183.  
  184. { TStartMenuItem }
  185.  
  186. constructor TStartMenuItem.Create(AOwner: TComponent);
  187. begin
  188.   inherited Create(AOwner);
  189.   FData := NullStr;
  190. end;
  191.  
  192. destructor TStartMenuItem.Destroy;
  193. begin
  194.   DisposeStr(FData);
  195.   inherited Destroy;
  196. end;
  197.  
  198. procedure TStartMenuItem.Click;
  199. begin
  200.   if not (sfSeparator in Flags) then inherited Click;
  201. end;
  202.  
  203. function TStartMenuItem.GetData: string;
  204. begin
  205.   Result := FData^;
  206. end;
  207.  
  208. procedure TStartMenuItem.SetData(const Value: string);
  209. begin
  210.   if FData^ <> Value then AssignStr(FData, Value);
  211. end;
  212.  
  213.  
  214. procedure TStartMenuItem.Paint(Canvas : TCanvas; Rect : TRect;
  215.   state : Word);
  216. const
  217.   GraySilver : array[Boolean] of TColor = (clSilver, clGray);
  218.   WhiteBlack : array[Boolean] of TColor = (clBlack, clWhite);
  219. var
  220.   y: Integer;
  221.   text : TFilename;
  222. begin
  223.   with Canvas, Rect do begin
  224.     { grab the DC that Windows provides }
  225.  
  226.     if sfLarge in Flags then Inc(Rect.Left, BannerWidth);
  227.  
  228.     if sfSeparator in Flags then
  229.       state := state and not ODS_SELECTED;
  230.  
  231.     if ColouredBar then
  232.       Brush.Color := HighlightSilver[state and ODS_SELECTED > 0];
  233.  
  234.     if FillMenu then FillRect(Rect);
  235.  
  236.     if state and ODS_SELECTED > 0 then
  237.       { upper shadow for selected item }
  238.       Pen.Color := clGray
  239.     else if sfTop in Flags then begin
  240.       { top item -- draw over menu border with gray }
  241.       Pen.Color := clGray;
  242.       MoveTo(Left-1, Top-1);
  243.       LineTo(Right+1, Top-1);
  244.       Pen.Color := clWhite;
  245.     end
  246.     else
  247.       Pen.Color := clSilver;
  248.  
  249.     { Draw top of menu item }
  250.     MoveTo(Left, Top);
  251.     LineTo(Right, Top);
  252.  
  253.     { Prepare font for output, and prepare pen for drawing the
  254.       bottom of the menu item }
  255.  
  256.     if state and ODS_SELECTED > 0 then begin
  257.       if BoldSelect then Font.Style := [fsBold];
  258.       Font.Color := WhiteBlack[ColouredBar or not BoldSelect];
  259.       Pen.Color := clWhite;
  260.     end
  261.     else begin
  262.       if BoldSelect then Font.Style := [];
  263.       Font.Color := clBlack;
  264.       Pen.Color := GraySilver[sfBottom in Flags];
  265.     end;
  266.  
  267.     { draw bottom of item }
  268.     MoveTo(Left, Bottom-1);
  269.     LineTo(Right, Bottom-1);
  270.  
  271.     if sfSeparator in Flags then begin
  272.       Pen.Color := clGray;
  273.       y := (Top + Bottom) div 2;
  274.       MoveTo(Left, y);
  275.       LineTo(Right, y);
  276.       Pen.Color := clWhite;
  277.       MoveTo(Left, y+1);
  278.       LineTo(Right, y+1);
  279.     end;
  280.  
  281.     if not (sfLarge in Flags) then begin
  282.       { draw the left side highlight }
  283.       Pen.Color := clWhite;
  284.       MoveTo(Left, Top);
  285.       LineTo(Left, Bottom);
  286.     end;
  287.  
  288.     if not (sfNoRightEdge in Flags) then begin
  289.       { draw the far right edge }
  290.       Pen.Color := clBlack;
  291.       MoveTo(Right+1, Top);
  292.       LineTo(Right+1, Bottom);
  293.     end;
  294.  
  295.     Pen.Color := clGray;
  296.     { Draw the right side shadow }
  297.     MoveTo(Right, Top);
  298.     LineTo(Right, Bottom);
  299.  
  300.     if not (sfLarge in Flags) then begin
  301.       { Draw the left side border }
  302.       MoveTo(Left-1, Top);
  303.       LineTo(Left-1, Bottom);
  304.     end
  305.     else
  306.       Dec(Rect.Left, BannerWidth);
  307.  
  308.     if not (sfSeparator in Flags) then begin
  309.       text := Caption;
  310.       if text[1] = '*' then begin
  311.         Move(text[3], text[1], Length(text)-2);
  312.         Dec(text[0], 2);
  313.       end;
  314.       DrawText(Canvas.Handle, @text[1], Ord(text[0]),
  315.         Bounds(Left + TextLeft, Top + (Bottom - Top - MenuFontHeight) div 2, 1, 1),
  316.           DT_LEFT or DT_NOCLIP);
  317.     end;
  318.  
  319.     PaintIcon(Canvas, Rect, State);
  320.   end;
  321. end;
  322.  
  323.  
  324. const
  325.   CommandTable : array[0..7] of string[17] =
  326.   ('$Folder', '$Find', '$Run', '$Explore', '$Shutdown',
  327.    '$LargeIconFolder', '$SmallIconFolder', '$ListFolder');
  328.  
  329. const
  330.   CommandToIcon : array[0..7] of string[15] =
  331.   ('Folder', 'FindDialog', 'RunDialog', 'Explorer', 'ShutDownDialog',
  332.    'Folder', 'Folder', 'Folder');
  333.  
  334. { TLargeMenuItem }
  335.  
  336. constructor TLargeMenuItem.Create(AOwner: TComponent);
  337. begin
  338.   inherited Create(AOwner);
  339.   FIcon := TIcon.Create;
  340.   FHeight := 36;
  341.   TextLeft := 80;
  342.   Flags := [sfLarge];
  343. end;
  344.  
  345. destructor TLargeMenuItem.Destroy;
  346. begin
  347.   FIcon.Free;
  348.   inherited Destroy;
  349. end;
  350.  
  351.  
  352. procedure TLargeMenuItem.LoadGraphic;
  353. var
  354.   Command, IconFile: TFilename;
  355.   i, IconIndex: Integer;
  356. begin
  357.   Command := '';
  358.   IconFile := '';
  359.   IconIndex := 0;
  360.   Unformat(Data, '%s;%S;%D;%s;%d', [@Command, 79, @IconFile, 79, @IconIndex]);
  361.  
  362.   if (Command[1] = '$') and (IconFile = '') then begin
  363.     { Find an auxiliary image }
  364.     i := 0;
  365.     while i <= High(CommandTable) do
  366.       if CompareText(Command, CommandTable[i]) = 0 then begin
  367.         FIcon.Assign(Icons.Get(CommandToIcon[i]));
  368.         Exit;
  369.       end
  370.       else Inc(i);
  371.   end;
  372.  
  373.  
  374.   { Use a reference object to do the icon searching }
  375.   FileRef.Target := Lowercase(command);
  376.   FileRef.IconFile := IconFile;
  377.   FileRef.IconIndex := IconIndex;
  378.   FileRef.AssignIcon(FIcon);
  379. end;
  380.  
  381.  
  382. function TLargeMenuItem.GetWidth : Integer;
  383. begin
  384.   Result := Images.Canvas.TextWidth(Caption) + 70;
  385. end;
  386.  
  387. procedure TLargeMenuItem.PaintIcon(Canvas : TCanvas; const Rect : TRect; state : Word);
  388. var
  389.   i, y, w, h, t: Integer;
  390. begin
  391.   if not (sfSeparator in Flags) then
  392.     Canvas.Draw(Rect.Left + 40, Rect.Top + 2, FIcon);
  393.  
  394.   y := Banner.Height;
  395.   i := Parent.Count-1;
  396.  
  397.   with Parent do begin
  398.     while (i >= 0) and (Items[i] <> self) do begin
  399.       Dec(y, TStartMenuItem(Items[i]).Height);
  400.       Dec(i);
  401.     end;
  402.   end;
  403.  
  404.   t := Rect.Top;
  405.   h := Height;
  406.   w := BannerWidth;
  407.   if sfTop in Flags then with Canvas do begin
  408.     Inc(t);
  409.     Dec(h);
  410.     Pen.Color := clWhite;
  411.     MoveTo(Rect.Left, Rect.Top);
  412.     LineTo(Rect.Left + w, Rect.Top);
  413.     Pen.Color := clGray;
  414.     MoveTo(Rect.Left, Rect.Top-1);
  415.     LineTo(Rect.Left + w, Rect.Top-1);
  416.   end;
  417.  
  418.   BitBlt(Canvas.Handle, Rect.Left, t, w, h,
  419.     Banner.Canvas.Handle, 0, Max(y - Height, 0), SRCCOPY);
  420. end;
  421.  
  422. { TSmallMenuItem }
  423.  
  424. constructor TSmallMenuItem.Create(AOwner: TComponent);
  425. begin
  426.   inherited Create(AOwner);
  427.   FHeight := SmallItemHeight;
  428.   TextLeft := 40;
  429. end;
  430.  
  431. procedure TSmallMenuItem.LoadGraphic;
  432. var
  433.   Command, IconFile: TFilename;
  434.   src, dest : THandle;
  435.   i, j, IconIndex: Integer;
  436.   c: TColor;
  437. begin
  438.   Command := '';
  439.   IconFile := '';
  440.   IconIndex := 0;
  441.   Unformat(Data, '%s;%S;%D;%s;%d', [@Command, 79, @IconFile, 79, @IconIndex]);
  442.  
  443.   if IconFile = '<none>' then begin
  444.     FImgOffset := 0;
  445.     Exit;
  446.   end;
  447.  
  448.   i := Pos(' ', Command);
  449.   if i > 0 then Command[0] := Chr(i-1);
  450.  
  451.   if (Count > 0) and (IconFile = '') then begin
  452.     { default group bitmap }
  453.     FImgOffset := 16;
  454.     Exit;
  455.   end;
  456.  
  457.   if (Command > '') and (Command[1] = '$') and (IconFile = '') then begin
  458.     { Find an auxiliary image }
  459.     i := 0;
  460.     while i <= High(CommandTable) do
  461.       if CompareText(Command, CommandTable[i]) = 0 then System.Break else Inc(i);
  462.     if (i >= 3) and (i <= 7) then i:=0;
  463.     FImgOffset := 32 + i * 16;
  464.     Exit;
  465.   end;
  466.  
  467.   if UsingCache then begin
  468.     { When this function is called, Images.Next points to where the
  469.       small icon image should be placed }
  470.     FImgOffset := Images.Next;
  471.     Images.Next := Images.Next + 16;
  472.     Exit;
  473.   end;
  474.  
  475.   if CompareText(ExtractFileExt(IconFile), '.bmp') = 0 then
  476.     SmallBmp.LoadFromFile(EnvironSubst(IconFile))
  477.   else begin
  478.     { Use a reference object to do the icon searching }
  479.     FileRef.Target := Lowercase(command);
  480.     FileRef.IconFile := IconFile;
  481.     FileRef.IconIndex := IconIndex;
  482.     FileRef.AssignIcon(TempIcon);
  483.     ShrinkIcon(TempIcon.Handle, SmallBmp);
  484.   end;
  485.  
  486.   { Add the new 16 x 16 image to the list and remember where you put it }
  487.   FImgOffset := Images.Add(SmallBmp);
  488. end;
  489.  
  490.  
  491.  
  492. function TSmallMenuItem.GetWidth : Integer;
  493. begin
  494.   Result := Images.Canvas.TextWidth(Caption) + 40;
  495. end;
  496.  
  497.  
  498. procedure TSmallMenuItem.PaintIcon(Canvas : TCanvas; const Rect : TRect; state : Word);
  499. begin
  500.   if not (sfSeparator in Flags) then
  501.     if ColouredBar and (state and ODS_SELECTED > 0) then
  502.       Canvas.BrushCopy(Bounds(Rect.Left + 16, Rect.Top + 2, 16, 16),
  503.         Images, Bounds(ImgOffset, 0, 16, 16), clSilver)
  504.     else
  505.     BitBlt(Canvas.Handle, Rect.Left + 16, Rect.Top + 2, 16, 16,
  506.       Images.Canvas.Handle, ImgOffset, 0, SRCCOPY);
  507. end;
  508.  
  509. { TStartMenu }
  510.  
  511. constructor TStartMenu.Create(AOwner: TComponent);
  512. begin
  513.   inherited Create(AOwner);
  514.   Window := AllocateHWnd(WndProc);
  515.   Canvas := TCanvas.Create;
  516.   Canvas.Brush.Color := clSilver;
  517.  
  518.   { A convenient place to create global objects! }
  519.   Images := TStartImages.Create;
  520.   TempIcon := TIcon.Create;
  521.   SmallBmp := InitBitmap(TBitmap.Create, 16, 16, clSilver);
  522.   Banner := TResBitmap.AlternateLoad('BANNER', 'banner.bmp');
  523.   BannerWidth := Banner.Width;
  524.   Configure;
  525. end;
  526.  
  527.  
  528. destructor TStartMenu.Destroy;
  529. begin
  530.   Canvas.Free;
  531.   Images.Free;
  532.   TempIcon.Free;
  533.   SmallBmp.Free;
  534.   Banner.Free;
  535.   DeallocateHWnd(Window);
  536.   inherited Destroy;
  537. end;
  538.  
  539.  
  540. procedure TStartMenu.Configure;
  541. const
  542.   FontStyles : array[Boolean] of TFontStyles = ([], [fsBold]);
  543. begin
  544.   FillMenu := BoldSelect or ColouredBar or (ColorToRGB(clMenu) <> clSilver);
  545.   ini.ReadFont('Start menu', Canvas.Font);
  546.   HighlightSilver[True] := Colors[ccStartHighlight];
  547.  
  548.   { When Windows sends WM_MEASUREITEM messages, the start menu has
  549.     no valid canvas to measure the text widths.  So it utilises
  550.     the canvas from a bitmap by setting its font and, later,
  551.     calling its TextWidth method }
  552.  
  553.   with Images.Canvas.Font do begin
  554.     Assign(Canvas.Font);
  555.     Style := FontStyles[BoldSelect];
  556.     MenuFontHeight := Abs(Height);
  557.     SmallItemHeight := MenuFontHeight + 10;
  558.   end;
  559.  
  560.   { When BoldSelect is on, menu items need to be wider to accomodate
  561.     the font.  So when BoldSelect changes, the start menu must be
  562.     "invalidated" so that Windows sends more WM_MEASUREITEM messages
  563.     to find the new widths }
  564.  
  565.   if StartMenu3D then SetOwnerDraw(Items);
  566. end;
  567.  
  568.  
  569. procedure TStartMenu.Clear;
  570. begin
  571.   with Items do while Count > 0 do Items[0].Free;
  572. end;
  573.  
  574.  
  575. procedure TStartMenu.SetOwnerDraw(menu : TMenuItem);
  576. const
  577.   Breaks : array[Boolean] of Word = (0, MF_MENUBREAK);
  578. var
  579.   i, NumSections, NumItems, FinalBreak: Integer;
  580.   item : TMenuItem;
  581.   IsBreak : Boolean;
  582.   s : string[79];
  583. begin
  584.   { Recurses through the menu tree, setting each item to owner-draw.
  585.     With the 4th parameter of ModifyMenu, don't confuse the Handle
  586.     with the Command }
  587.  
  588.   NumItems := menu.Count;
  589.   s := menu.Caption;
  590.  
  591.   if (Length(s) >= 2) and (s[1] = '*') then begin
  592.     NumSections := Max(Ord(s[2]) - Ord('0'), 2);
  593.     FinalBreak := (NumItems div NumSections) * (NumSections-1);
  594.   end
  595.   else
  596.     NumSections := 1;
  597.  
  598.   for i := 0 to NumItems-1 do begin
  599.     item := menu.Items[i];
  600.  
  601.     IsBreak := (i > 0) and (NumSections > 1) and
  602.       (i mod (NumItems div NumSections) = 0);
  603.  
  604.     if item.Count > 0 then begin
  605.       ModifyMenu(menu.Handle, i, MF_BYPOSITION or MF_OWNERDRAW or MF_POPUP or
  606.         Breaks[IsBreak], item.Handle, Pointer(item));
  607.       SetOwnerDraw(item);
  608.     end
  609.     else
  610.       ModifyMenu(menu.Handle, i, MF_BYPOSITION or MF_OWNERDRAW or
  611.         Breaks[IsBreak], item.Command, Pointer(item));
  612.  
  613.     if IsBreak then begin
  614.       Include(TStartMenuItem(item).Flags, sfTop);
  615.       if i > 1 then Include(TStartMenuItem(menu.Items[i-1]).Flags, sfBottom);
  616.     end;
  617.  
  618.     if (NumSections > 1) and (i < FinalBreak) then
  619.       Include(TStartMenuItem(item).Flags, sfNoRightEdge);
  620.   end;
  621. end;
  622.  
  623.  
  624. procedure TStartMenu.Load;
  625. var
  626.   startini: TIniFile;
  627.  
  628. procedure AddToMenu(menu: TMenuItem; const section: string);
  629. const
  630.   LargeSmall : array[Boolean] of TStartMenuItemClass =
  631.     (TSmallMenuItem, TLargeMenuItem);
  632. var
  633.   names: TStringList;
  634.   s    : string;
  635.   item : TStartMenuItem;
  636.   i    : Integer;
  637. begin
  638.   { Reads an entire INI file section, turns each entry into
  639.     a menu item, and adds the items to the menu parameter }
  640.  
  641.   names := TStringList.Create;
  642.   menu.Caption := ExtractFilename(section);
  643.  
  644.   try
  645.     startini.ReadSection(section, names);
  646.  
  647.     for i := 0 to names.Count-1 do begin
  648.       item := LargeSmall[LargeRootMenu and (menu = Items)].Create(self);
  649.  
  650.       s := names[i];
  651.       item.Data := startini.ReadString(section, s, '');
  652.  
  653.       if s[Length(s)] = '*' then begin
  654.         Dec(s[0]);
  655.         AddToMenu(item, Format('%s\%s', [section, s]));
  656.       end
  657.       else
  658.         item.OnClick := HandleClick;
  659.  
  660.       item.Caption := s;
  661.       menu.Add(item);
  662.       if YieldDuringLoad then Application.ProcessMessages;
  663.     end;
  664.   finally
  665.     names.Free;
  666.   end;
  667. end;
  668.  
  669.  
  670. procedure AssignBitmaps(menu : TMenuItem);
  671. var
  672.   item: TStartMenuItem;
  673.   i : Integer;
  674. begin
  675.   { AssignBitmaps recursively travels the tree, calling PutBitmap
  676.     for each menu item.  It also calculates the menu item's flags
  677.     used when painting.  The Tag stores the item's height. }
  678.  
  679.   for i := 0 to menu.Count-1 do begin
  680.     item := TStartMenuItem(menu.Items[i]);
  681.     with item do begin
  682.       if i = 0 then Include(Flags, sfTop);
  683.       if i = menu.Count-1 then Include(Flags, sfBottom);
  684.       if (Caption > '') and (Caption[1] = '-') then begin
  685.         Include(Flags, sfSeparator);
  686.         Height := SmallItemHeight div 2;
  687.       end;
  688.       LoadGraphic;
  689.     end;
  690.     if item.Count > 0 then AssignBitmaps(item);
  691.     if YieldDuringLoad then Application.ProcessMessages;
  692.   end;
  693. end;
  694.  
  695.  
  696. const
  697.   BusyCursors : array[Boolean] of TCursor = (crHourGlass, crBusyPointer);
  698. var
  699.   Defaults : TResBitmap;
  700.   CacheFile : TFilename;
  701. begin { TStartMenu.Load }
  702.   Clear;
  703.   startini := TIniFile.Create(StartFile);
  704.   Desktop.SetCursor(BusyCursors[YieldDuringLoad]);
  705.   AddToMenu(Items, 'Start');
  706.   CacheFile := FileWritePath + 'bmpcache.bmp';
  707.   try
  708.     if StartMenu3D then begin
  709.       with Banner do
  710.         if LargeRootMenu and Empty then Reload;
  711.  
  712.       if FileExists(CacheFile) then begin
  713.         UsingCache := True;
  714.         Images.LoadFromFile(CacheFile);
  715.       end
  716.       else begin
  717.         { copy preset pictures into cache bitmap }
  718.         UsingCache := False;
  719.         InitBitmap(Images, 128 * 16, 16, clSilver);
  720.         Defaults := TResBitmap.AlternateLoad('STARTBMPS', 'startdef.bmp');
  721.         Images.Canvas.Draw(0, 0, Defaults);
  722.         Defaults.Free;
  723.       end;
  724.       Images.Next := 128; { skip over preset pictures }
  725.       AssignBitmaps(Items);
  726.       SetOwnerDraw(Items);
  727.       if not UsingCache then with Images do begin
  728.         { chop off any empty space at the end before saving file }
  729.         Width := Next;
  730.         SaveToFile(CacheFile);
  731.       end;
  732.     end
  733.     else begin
  734.       DeleteFile(CacheFile);
  735.     end;
  736.  
  737.     if not (StartMenu3D and LargeRootMenu) then begin
  738.       Banner.Dormant;
  739.       Banner.FreeImage;
  740.     end;
  741.  
  742.   finally
  743.     startini.Free;
  744.     Desktop.ReleaseCursor;
  745.   end;
  746. end;
  747.  
  748.  
  749. function TStartMenu.GetHeight : Integer;
  750. var
  751.   i: Integer;
  752. begin
  753.   Result := 2;
  754.   if StartMenu3D then
  755.     for i := 0 to Items.Count-1 do Inc(Result, TStartMenuItem(Items[i]).Height)
  756.   else
  757.     Inc(Result, Items.Count * GetSystemMetrics(SM_CYMENU));
  758. end;
  759.  
  760.  
  761. procedure TStartMenu.RebuildFromOutline(Outline : TOutline);
  762. var
  763.   startini : TIniFile;
  764.   i : Integer;
  765.   section : string[127];
  766. begin
  767.   DeleteFile(StartFile);
  768.  
  769.   { This routine works on the outline from the Start Properties dialog.
  770.     It assumes that each outline node has a dynamic string pointed to by
  771.     the Data property }
  772.  
  773.   startini := TIniFile.Create(StartFile);
  774.   try
  775.     with Outline do
  776.     for i := 1 to ItemCount do with Items[i] do begin
  777.       if Level = 1 then section := 'Start'
  778.       else section := 'Start\' + Parent.FullPath;
  779.  
  780.       if HasItems then
  781.         startini.WriteString(section, Text + '*', PString(Data)^)
  782.       else
  783.         startini.WriteString(section, Text, PString(Data)^);
  784.     end;
  785.   finally
  786.     startini.Free;
  787.     Load;
  788.   end;
  789. end;
  790.  
  791.  
  792. procedure TStartMenu.AssignToOutline(Outline : TOutline);
  793.  
  794. procedure Translate(menu: TMenuItem; dest : Longint);
  795. var
  796.   node : Longint;
  797.   p: PString;
  798.   i : Integer;
  799.   item : TStartMenuItem;
  800. begin
  801.   with menu do
  802.     for i := 0 to Count-1 do begin
  803.       New(p);
  804.       item := Items[i] as TStartMenuItem;
  805.       p^ := item.Data;
  806.       node := Outline.AddChildObject(dest, item.Caption, TObject(p));
  807.       if item.Count > 0 then Translate(item, node);
  808.     end;
  809. end;
  810.  
  811. begin
  812.   Translate(Items, 0);
  813. end;
  814.  
  815.  
  816.  
  817. procedure TStartMenu.HandleClick(Sender : TObject);
  818. const
  819.   ShowCmdsEx : array[TWindowState] of Word =
  820.     (SW_SHOWNORMAL, SW_SHOWMINNOACTIVE, SW_SHOWMAXIMIZED);
  821. var
  822.   filename, params: TFilename;
  823. begin
  824.   with ExtractStartInfo((Sender as TStartMenuItem).Data) do begin
  825.  
  826.     filename := command;
  827.     params := '';
  828.     Unformat(command, '%s %s', [@filename, 79, @params, 79]);
  829.  
  830.     if (filename[1] = '$') and Assigned(FOnStartMacro) then
  831.       FOnStartMacro(self, EnvironSubst(filename), EnvironSubst(params))
  832.     else begin
  833.       LastIconFile := iconfile;
  834.       LastIconIndex := iconindex;
  835.       LastInstance := DefaultExec(filename, params, directory,
  836.         ShowCmdsEx[TWindowState(Abs(showmode) mod 3)]);
  837.     end;
  838.   end;
  839. end;
  840.  
  841.  
  842. procedure TStartMenu.RunStartup;
  843. var
  844.   i: Integer;
  845.   item : TMenuItem;
  846. begin
  847.   item := Find(ini.ReadString('Start Menu', 'StartupGroup', 'Startup'), miSubMenu);
  848.   if item <> nil then with item do
  849.     for i := 0 to Count-1 do Items[i].Click;
  850. end;
  851.  
  852.  
  853. procedure TStartMenu.PopupMenuItem(h: HMenu; X, Y: Integer; TrackLeft : Boolean);
  854. const
  855.   Flags: array[TPopupAlignment] of Word = (TPM_LEFTALIGN, TPM_RIGHTALIGN,
  856.     TPM_CENTERALIGN);
  857.  
  858.   Tracks : array[Boolean] of Word =
  859.     (TPM_RIGHTBUTTON, TPM_LEFTBUTTON);
  860.  
  861. var
  862.   SaveFocus : HWnd;
  863. begin
  864.   if YieldDuringLoad then Exit;
  865.   SaveFocus := GetFocus;
  866.   SetFocus(Window);
  867.   TrackPopupMenu(h, Flags[Alignment] or Tracks[TrackLeft], X, Y,
  868.     0, Window, nil);
  869.   if IsWindow(SaveFocus) and IsWindowVisible(SaveFocus) then
  870.     WinProcs.SetFocus(SaveFocus);
  871. end;
  872.  
  873. procedure TStartMenu.Popup(X, Y: Integer; TrackLeft : Boolean);
  874. begin
  875.   PopupMenuItem(Items.Handle, X, Y, TrackLeft);
  876. end;
  877.  
  878.  
  879.  
  880. procedure TStartMenu.WndProc(var Message: TMessage);
  881. var
  882.   m: TMenuItem;
  883.   i: Integer;
  884. begin
  885.   try
  886.     case Message.Msg of
  887.       WM_DRAWITEM:
  888.         with TDRAWITEMSTRUCT(Pointer(Message.lParam)^) do begin
  889.           Canvas.Handle := hDC;
  890.           TStartMenuItem(itemData).Paint(Canvas, rcItem, itemState);
  891.           Canvas.Handle := 0;
  892.         end;
  893.  
  894.       WM_MEASUREITEM:
  895.         with TMEASUREITEMSTRUCT(Pointer(Message.lParam)^) do begin
  896.           itemHeight := TStartMenuItem(itemData).Height;
  897.           itemWidth := TStartMenuItem(itemData).GetWidth;
  898.         end;
  899.  
  900.       WM_COMMAND:
  901.           DispatchCommand(Message.wParam);
  902.  
  903.       WM_MENUCHAR:
  904.         begin
  905.           if Items.Handle = HiWord(Message.lParam) then m := Items
  906.           else m := FindItem(HiWord(Message.lParam), fkHandle);
  907.           if m <> nil then
  908.             for i := 0 to m.Count-1 do
  909.               if IsAccel(Message.wParam, TStartMenuItem(m[i]).Caption) then begin
  910.                 Message.Result := MakeLong(i, 2);
  911.                 Exit;
  912.               end;
  913.           Message.Result := 0;
  914.           Exit;
  915.         end;
  916.     end;
  917.     with Message do Result := DefWindowProc(Window, Msg, wParam, lParam);
  918.   except
  919.     Application.HandleException(Self);
  920.   end;
  921. end;
  922.  
  923.  
  924. function ExtractStartInfo(const s: string): TStartInfo;
  925. begin
  926.   FillChar(Result, SizeOf(Result), 0);
  927.  
  928.   if Unformat(s, '%s;%s;%d;%s;%d',
  929.     [@Result.command, 79, @Result.directory, 79, @Result.showmode,
  930.      @Result.iconfile, 79, @Result.iconindex]) = 1
  931.     then Result.command := '';
  932. end;
  933.  
  934.  
  935. function PackStartInfo(const command, directory, iconfile: TFilename;
  936.  showmode, iconindex: Integer): string;
  937. begin
  938.   Result := Format('%s;%.79s;%d;%.79s;%d',
  939.     [command, directory, showmode, iconfile, iconindex]);
  940. end;
  941.  
  942.  
  943. function TStartMenu.Find(const cap : string; Kind: TMenuItemKind): TMenuItem;
  944.  
  945. function FindCap(node : TMenuItem): TMenuItem;
  946. var
  947.   i: Integer;
  948.   item : TMenuItem;
  949. begin
  950.   Result := nil;
  951.   for i := 0 to node.Count-1 do begin
  952.     item := node.Items[i];
  953.     if ((Kind = miAll) or ((item.Count = 0) = (Kind = miLeafNode)))
  954.      and (CompareText(item.Caption, cap) = 0) then
  955.       Result := item
  956.     else if item.Count > 0 then
  957.       Result := FindCap(item);
  958.  
  959.     if Result <> nil then Exit;
  960.   end;
  961. end;
  962.  
  963. begin
  964.   Result := FindCap(items);
  965. end;
  966.  
  967. end.
  968.  
  969.