home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 April A / Pcwk4a98.iso / PROGRAM / DELPHI16 / Calmira / Src / SRC / DESK.PAS < prev    next >
Pascal/Delphi Source File  |  1997-02-15  |  21KB  |  664 lines

  1. {**************************************************************************}
  2. {                                                                          }
  3. {    Calmira shell for Microsoft« Windows(TM) 3.1                          }
  4. {    Source Release 1.0                                                    }
  5. {    Copyright (C) 1997  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 Desk;
  24.  
  25. { TDesktop
  26.  
  27.   TDesktop manages forms on the screen, and is an extension to Delphi's
  28.   TScreen component.
  29.  
  30.   Fields
  31.  
  32.   FormList - temporary list to hold forms during processing
  33.   WindowList - always contains a list of open icon windows
  34.   WindowMenu - a popup menu that mirrors WindowList
  35.   RefreshList - a list of folders that have had their contents
  36.     changed and need refreshing once an operation has finished
  37.   CursorStack - holds the previous TCursor values
  38.  
  39.   Methods
  40.  
  41.   Load - Loads the desktop from the INI file
  42.   Save - Saves the desktop to the INI file
  43.   Refresh - refreshes the given folder if it is on screen
  44.   RefreshNow - refreshes all windows in refresh list, then clears the list
  45.   WindowOf - returns the icon window displaying the given folder,
  46.     or nil if no such window exists
  47.   OpenFolder - opens an icon window of the given folder, or brings
  48.     an existing window to the front
  49.   CloseSubWindows - closes all windows which show the given directory
  50.     and all its subdirectories
  51.   CloseLowerWindows - closes all windows which show subdirectories
  52.     of the given directory
  53.   ClosePathWindows - closes all windows showing parent directories
  54.     of the given directory
  55.   CloseOtherWindows - closes all windows except the one passed as parameter
  56.   Cascade - cascades icon windows from the top left
  57.   CloseWindows - closes all icon windows
  58.   ArrangeIcons - mimics "Arrange Icons" from the Windows Task Manager
  59.     except that shortcuts etc. are not moved.
  60.   SnapToGrid - repositions icons so that they line up with an
  61.     invisible grid
  62.   RenameWindows - calls the FolderRenamed method for each icon window
  63.   AddWindow - adds an entry to the window list and a new menu item
  64.   RemoveWindow - reverses effects of AddWindow
  65.   WindowSelect - the event handler for menu items, which brings a
  66.     window to the front.
  67.   EnableForms - changes the Enabled property of all forms on screen
  68.     except those needed to interact with the user during a file
  69.     operation.  Simulates modal file operations.
  70.   Revert - reloads the minimized positions of extended forms (TExtForm).
  71.   NextForm - brings the bottom form to the front, typically when the
  72.     user presses Ctrl-Tab
  73.   SetCursor - saves the current cursor to the cursor stack and
  74.     changes the screen's cursor.
  75.   ReleaseCursor - Restores the previously displayed screen cursor
  76. }
  77.  
  78.  
  79. interface
  80.  
  81. uses Classes, IconWin, SysUtils, Graphics, FileCtrl, Forms, WinTypes,
  82.   Menus, Controls;
  83.  
  84. type
  85.   TDesktop = class(TComponent)
  86.   private
  87.     FormList: TList;
  88.     CursorStack : TList;
  89.     WindowList : TStringList;
  90.     function Each(FormClass: TFormClass): TList;
  91.   public
  92.     WindowMenu : TPopupMenu;
  93.     RefreshList : TStringList;
  94.     constructor Create(AOwner: TComponent); override;
  95.     destructor Destroy; override;
  96.     procedure Load;
  97.     procedure Save;
  98.     procedure Refresh(const foldername: TFilename);
  99.     procedure RefreshNow;
  100.     function WindowOf(const foldername : TFilename): TIconWindow;
  101.     procedure OpenFolder(foldername: TFilename);
  102.     procedure OpenFolderRefresh(foldername: TFilename);
  103.     procedure CloseSubWindows(const foldername: TFilename);
  104.     procedure CloseLowerWindows(const foldername: TFilename);
  105.     procedure ClosePathWindows(const foldername: TFilename);
  106.     procedure CloseOtherWindows(Sender : TIconWindow);
  107.     procedure Cascade;
  108.     procedure CloseWindows;
  109.     procedure ArrangeIcons;
  110.     procedure SnapToGrid;
  111.     procedure RenameWindows(const previous, current: TFilename);
  112.     procedure AddWindow(Win : TIconWindow);
  113.     procedure RemoveWindow(Win : TIconWindow);
  114.     procedure WindowSelect(Sender: TObject);
  115.     procedure EnableForms(Enable : Boolean);
  116.     procedure Revert;
  117.     procedure NextForm;
  118.     procedure SetCursor(Cursor : TCursor);
  119.     procedure ReleaseCursor;
  120.   end;
  121.  
  122.  
  123. var Desktop : TDesktop;
  124.  
  125. implementation
  126.  
  127. uses Directry, WinProcs, Shorts, WasteBin, Sys, Settings, Resource,
  128.   Strings, FileFind, Files, MiscUtil, Drives, Tree, Busy, Progress,
  129.   Replace, CalForm, Start, CalMsgs, ExtForm, FileMan, Dialogs;
  130.  
  131.  
  132. constructor TDesktop.Create(AOwner: TComponent);
  133. begin
  134.   inherited Create(AOwner);
  135.   FormList := TList.Create;
  136.   CursorStack := TList.Create;
  137.   RefreshList := TUniqueStrings.Create;
  138.   WindowList := TUniqueStrings.Create;
  139.   WindowMenu := TPopupMenu.Create(self);
  140. end;
  141.  
  142.  
  143. destructor TDesktop.Destroy;
  144. var i: Integer;
  145. begin
  146.   FormList.Free;
  147.   CursorStack.Free;
  148.   RefreshList.Free;
  149.   with WindowList do
  150.     for i := 0 to Count-1 do Objects[i].Free;
  151.   WindowList.Free;
  152.   ArrangeIconicWindows(GetDesktopWindow);
  153.   inherited Destroy;
  154. end;
  155.  
  156.  
  157. { The Each method is useful for finding all the forms of a particular
  158.   type.  It copies them to FormList and returns this list -- this is
  159.   important because we can't reliably close forms while iterating
  160.   through the Screen.Forms property, even when going backwards }
  161.  
  162. function TDesktop.Each(FormClass: TFormClass): TList;
  163. var i: Integer;
  164. begin
  165.   FormList.Clear;
  166.   with Screen do
  167.     for i := 0 to FormCount-1 do
  168.       if Forms[i] is FormClass then FormList.Add(Forms[i]);
  169.   Result := FormList;
  170. end;
  171.  
  172.  
  173. { OpenFolder encapsulates the process of displaying a new icon window.
  174.   If the window already exists, it is brought forward.  When the user
  175.   chooses to use a single window for all browsing, then any existing
  176.   window is forced to change its directory path.
  177.  
  178.   While it might seem useful to return the window object, TIconWindow
  179.   could raise an exception and destroy itself if an invalid path is
  180.   specified AND discard the exception, leaving the caller with an
  181.   invalid pointer.  So it is safer to not return anything. }
  182.  
  183. procedure TDesktop.OpenFolder(foldername: TFilename);
  184. var
  185.   IconWindow : TIconWindow;
  186.   i : Integer;
  187. begin
  188.   foldername := Lowercase(foldername);
  189.   IconWindow := WindowOf(foldername);
  190.  
  191.   if IconWindow = nil then begin
  192.     if BrowseSame xor (GetAsyncKeyState(VK_MENU) < 0) then
  193.       with Screen do
  194.       for i := 0 to FormCount-1 do
  195.         if Forms[i] is TIconWindow then begin
  196.           TIconWindow(Forms[i]).ChangeDir(foldername);
  197.           Exit;
  198.         end;
  199.  
  200.     TIconWindow.Init(Application, foldername, DefaultFilter).Show;
  201.   end
  202.   else IconWindow.ShowNormal;
  203. end;
  204.  
  205.  
  206. procedure TDesktop.OpenFolderRefresh(foldername: TFilename);
  207. var
  208.   w: TIconWindow;
  209. begin
  210.   if RefreshFolders then begin
  211.     w := Desktop.WindowOf(Lowercase(foldername));
  212.     if w <> nil then with w do begin
  213.       RefreshWin;
  214.       ShowNormal;
  215.       Exit;
  216.     end;
  217.   end;
  218.  
  219.   Desktop.OpenFolder(foldername)
  220. end;
  221.  
  222. function TDesktop.WindowOf(const foldername : TFilename): TIconWindow;
  223. var i : Integer;
  224. begin
  225.   i := WindowList.IndexOf(foldername);
  226.   if i <> -1 then Result := TIconWindow(WindowList.Objects[i])
  227.   else Result := nil;
  228. end;
  229.  
  230.  
  231. { A "subwindow" is thought of like a "subset", i.e., the window itself
  232.   or any windows showing subdirectories }
  233.  
  234. procedure TDesktop.CloseSubWindows(const foldername: TFilename);
  235. var f: TIconWindow;
  236. begin
  237.   f := WindowOf(foldername);
  238.   if f <> nil then f.Close;
  239.   CloseLowerWindows(foldername);
  240. end;
  241.  
  242.  
  243. procedure TDesktop.CloseLowerWindows(const foldername: TFilename);
  244. var i: Integer;
  245. begin
  246.   with Each(TIconWindow) do
  247.     for i := 0 to Count-1 do
  248.       if IsAncestorDir(foldername, TIconWindow(Items[i]).Dir.Fullname) then
  249.         TIconWindow(Items[i]).Close;
  250. end;
  251.  
  252.  
  253. procedure TDesktop.ClosePathWindows(const foldername: TFilename);
  254. var i: Integer;
  255. begin
  256.   with Each(TIconWindow) do
  257.     for i := 0 to Count-1 do
  258.       if IsAncestorDir(TIconWindow(Items[i]).Dir.Fullname, foldername) then
  259.         TIconWindow(Items[i]).Close;
  260. end;
  261.  
  262.  
  263. procedure TDesktop.Refresh(const foldername: TFilename);
  264. var f: TIconWindow;
  265. begin
  266.   f := WindowOf(Foldername);
  267.   if f <> nil then f.RefreshWin;
  268. end;
  269.  
  270. { TScreen is organised such that the topmost form has the lowest index.
  271.   To cascade using the current Z-order, the loop must go backwards to
  272.   prevent exposing windows underneath (they take a long time to redraw).
  273.   Try a forward loop and see! }
  274.  
  275. procedure TDesktop.Cascade;
  276. var
  277.   i, tl: Integer;
  278.   size : TPoint;
  279. begin
  280.   tl := 0;
  281.   size := TIconWindow.CalcSize(5, 4);
  282.   with Screen do
  283.     for i := FormCount-1 downto 0 do
  284.       if Forms[i] is TIconWindow then with TIconWindow(Forms[i]) do
  285.         if WindowState <> wsMinimized then begin
  286.           SetBounds(tl, tl, size.x, size.y);
  287.           Inc(tl, 24);
  288.           if tl + size.x > Screen.Width then tl := 0;
  289.         end;
  290. end;
  291.  
  292.  
  293. procedure TDesktop.CloseWindows;
  294. var i: Integer;
  295. begin
  296.   with Each(TIconWindow) do
  297.     for i := 0 to Count-1 do TIconWindow(Items[i]).Close;
  298. end;
  299.  
  300.  
  301. procedure TDesktop.CloseOtherWindows(Sender : TIconWindow);
  302. var i: Integer;
  303. begin
  304.   with Each(TIconWindow) do
  305.     for i := 0 to Count-1 do
  306.       if Items[i] <> Sender then TIconWindow(Items[i]).Close;
  307. end;
  308.  
  309.  
  310. function EnumMinWindows(Wnd: HWnd; List: TList): Bool; export;
  311. begin
  312.   if IsWindowVisible(Wnd) and
  313.     (GetWindowLong(Wnd, GWL_STYLE) and WS_MINIMIZEBOX > 0) then
  314.     List.Add(Pointer(Wnd));
  315.   Result := True;
  316. end;
  317.  
  318.  
  319. { Returns minimized icon coordinates.  Those which haven't been minimized
  320.   before can have -1 values, in which case Windows picks a suitable
  321.   position when required }
  322.  
  323. function GetMinPosition(Wnd: HWND): TPoint;
  324. var place: TWindowPlacement;
  325. begin
  326.   place.Length := sizeof(place);
  327.   GetWindowPlacement(Wnd, @place);
  328.   Result := place.ptMinPosition;
  329. end;
  330.  
  331.  
  332. { An icon is not moved if it is already at the desired position.  Otherwise,
  333.   SetWindowPlacement is called to move it.  Iconic windows are briefly
  334.   hidden to make sure that the transparent background is repainted.  If they
  335.   are moved while visible, Windows just does a blit and copies the old
  336.   wallpaper along with the icon }
  337.  
  338. procedure MoveDesktopIcon(Wnd: HWND; pt: TPoint);
  339. var
  340.   place: TWindowPlacement;
  341. begin
  342.   place.Length := sizeof(place);
  343.   GetWindowPlacement(Wnd, @place);
  344.   with place.ptMinPosition do
  345.     if (x = pt.x) and (y = pt.y) then Exit;
  346.   place.ptMinPosition := pt;
  347.   place.Flags := place.Flags or WPF_SETMINPOSITION;
  348.   if IsIconic(Wnd) then ShowWindow(Wnd, SW_HIDE);
  349.   SetWindowPlacement(Wnd, @place);
  350. end;
  351.  
  352.  
  353. { Firstly, this procedure calculates the dimensions of the icon grid, and
  354.   where to put the bottom row (depending on whether the taskbar is showing).
  355.   For each window, it checks that it doesn't belong to a fixed object.
  356.   Then it slots the icon into the right place, and calculates the position
  357.   of the next icon.
  358.  
  359.   Icons with a Y coordinate of Screen.Height are usually the ones hidden by
  360.   the taskbar, so they are left alone.  (-1, -1) tells Windows to find
  361.   a position when the form is next minimized }
  362.  
  363. procedure TDesktop.ArrangeIcons;
  364. var
  365.   list : TList;
  366.   NextPos : TPoint;
  367.   Spacing, FarLeft, i: Integer;
  368.   Wnd : HWND;
  369.   control : TWinControl;
  370. begin
  371.   Spacing := GetSystemMetrics(SM_CXICONSPACING);
  372.   FarLeft := (Spacing - 32) div 2;
  373.   NextPos.X := FarLeft;
  374.   NextPos.Y := Screen.Height;
  375.   if TaskBarWindow > 0 then Dec(NextPos.Y, 30 + MinAppHeight)
  376.   else Dec(NextPos.Y, Spacing + 16);
  377.  
  378.   list := TList.Create;
  379.   try
  380.     EnumWindows(@EnumMinWindows, Longint(list));
  381.     for i := 0 to list.Count-1 do begin
  382.       Wnd := Longint(list[i]);
  383.       control := FindControl(wnd);
  384.  
  385.       if (control is TShort) or (control = SysWindow) or (control = Bin) then
  386.         Continue;
  387.  
  388.       if GetMinPosition(wnd).y < Screen.Height then
  389.         if not IsIconic(Wnd) then
  390.           MoveDesktopIcon(wnd, Point(-1, -1))
  391.         else begin
  392.           MoveDesktopIcon(wnd, NextPos);
  393.           Inc(NextPos.X, spacing);
  394.           if NextPos.X > Screen.Width then begin
  395.             Dec(NextPos.Y, Spacing);
  396.             NextPos.X := FarLeft;
  397.           end
  398.         end;
  399.     end;
  400.   finally
  401.     list.Free;
  402.   end;
  403. end;
  404.  
  405.  
  406. { SnapToGrid uses a bit of modulo maths to determine the closest square.
  407.   The Snap function is given a coordinate and a grid size, and returns
  408.   where the coordinate should snap to. }
  409.  
  410. procedure TDesktop.SnapToGrid;
  411. var
  412.   list : TList;
  413.   i: Integer;
  414.   Wnd : HWND;
  415.   MinPos : TPoint;
  416.  
  417. function Nearest(value, lower, upper: Integer): Integer;
  418. begin
  419.   if value - lower < upper - value then Result := lower
  420.   else Result := upper;
  421. end;
  422.  
  423. function Snap(p, grid: Integer): Integer;
  424. begin
  425.   Result := p;
  426.   if p mod grid <> 0 then
  427.     Result := Nearest(p, p - (p mod grid), p + grid - (p mod grid));
  428. end;
  429.  
  430. begin
  431.   list := TList.Create;
  432.   try
  433.     EnumWindows(@EnumMinWindows, Longint(list));
  434.     for i := 0 to list.Count-1 do begin
  435.       Wnd := Longint(list[i]);
  436.       MinPos := GetMinPosition(wnd);
  437.       with MinPos do
  438.       if (x > -1) and (y > -1) and (y < Screen.Height) then begin
  439.         x := Snap(x, DeskGrid.x);
  440.         y := Snap(y, DeskGrid.y);
  441.         MoveDesktopIcon(wnd, MinPos);
  442.       end;
  443.     end;
  444.   finally
  445.     list.Free;
  446.   end;
  447. end;
  448.  
  449.  
  450.  
  451. procedure TDesktop.RefreshNow;
  452. var i: Integer;
  453. begin
  454.   if RefreshList.Count = 0 then Exit;
  455.  
  456.   with Each(TIconWindow) do begin
  457.     for i := 0 to Count-1 do
  458.       if RefreshList.IndexOf(TIconWindow(Items[i]).Dir.Fullname) <> -1 then
  459.         TIconWindow(Items[i]).RefreshWin;
  460.   end;
  461.   RefreshList.Clear;
  462. end;
  463.  
  464.  
  465. procedure TDesktop.RenameWindows(const previous, current: TFilename);
  466. var i: Integer;
  467. begin
  468.   with Each(TIconWindow) do
  469.     for i := 0 to Count-1 do
  470.       TIconWindow(Items[i]).FolderRenamed(previous, current);
  471. end;
  472.  
  473.  
  474. { Just as TForm informs TScreen when it is created or destroyed, so
  475.   TIconWindow informs TDesktop.  All icon windows are stored in a
  476.   sorted string list, and the sort ordering is useful when maintaining
  477.   a popup menu. }
  478.  
  479. procedure TDesktop.AddWindow(Win : TIconWindow);
  480. var m: TMenuItem;
  481. begin
  482.   m := TMenuItem.Create(self);
  483.   m.Caption := Win.Dir.Fullname;
  484.   m.OnClick := WindowSelect;
  485.   WindowMenu.Items.Insert(WindowList.AddObject(m.Caption, Win), m);
  486. end;
  487.  
  488. procedure TDesktop.RemoveWindow(Win : TIconWindow);
  489. var i: Integer;
  490. begin
  491.   with WindowList do begin
  492.     i := IndexOfObject(Win);
  493.     if i <> -1 then begin
  494.       WindowMenu.Items[i].Free;
  495.       Delete(i);
  496.     end;
  497.   end;
  498. end;
  499.  
  500.  
  501. { This is the OnClick handler for menu items showing current open windows }
  502.  
  503. procedure TDesktop.WindowSelect(Sender: TObject);
  504. begin
  505.   OpenFolder((Sender as TMenuItem).Caption);
  506. end;
  507.  
  508.  
  509. { EnableForms takes the place of EnableTaskWindows.  All forms are disabled
  510.   or enabled except the ones which can be active during a file operation.
  511.   This gives the appearance of a modal state. }
  512.  
  513. procedure TDesktop.EnableForms(Enable : Boolean);
  514. var
  515.   i: Integer;
  516.   f: TForm;
  517. begin
  518.   with Screen do
  519.     for i := 0 to FormCount-1 do begin
  520.       f := Forms[i];
  521.       if (f <> ProgressBox) and (f <> BusyBox) and (f <> ReplaceBox) then
  522.         f.Enabled := Enable;
  523.     end;
  524. end;
  525.  
  526.  
  527. { The desktop is responsible for loading shortcuts and previously
  528.   opened icon windows.  To prevent errors from slowing down the loading,
  529.   only folders which exist on fixed drives are processed }
  530.  
  531. procedure TDesktop.Load;
  532. var
  533.   i : Integer;
  534.   s: TShort;
  535.   strings : TStringList;
  536.   IconWindow : TIconWindow;
  537.   fname : TFilename;
  538. begin
  539.   for i := 0 to ini.ReadInteger('Desktop', 'NumShorts', 0)-1 do begin
  540.     s := TShort.Create(Application);
  541.     s.LoadFromIni(ini, 'Shortcut' + IntToStr(i));
  542.   end;
  543.  
  544.   strings := TStringList.Create;
  545.   try
  546.     ini.ReadStrings('Folders', strings);
  547.     for i := 0 to strings.Count-1 do begin
  548.       fname := strings[i];
  549.       if Desktop.WindowOf(fname) <> nil then Continue;
  550.       if not (dfRemoveable in GetDriveFlags(fname[1])) and
  551.         ((Length(fname) = 3) or HDirectoryExists(fname)) then begin
  552.         IconWindow := TIconWindow.Init(Application, fname, DefaultFilter);
  553.         with IconWindow do begin
  554.           LoadDimensions;
  555.           Show;
  556.           Update;
  557.         end;
  558.       end;
  559.     end;
  560.   finally
  561.     strings.Free;
  562.   end;
  563. end;
  564.  
  565.  
  566.  
  567. procedure TDesktop.Save;
  568. var
  569.   i: Integer;
  570. begin
  571.   SetCursor(crHourGlass);
  572.   with ini do begin
  573.     for i := 1 to ReadInteger('Desktop', 'NumShorts', 0) do
  574.       EraseSection('Shortcut' + IntToStr(i));
  575.  
  576.     with Each(TShort) do begin
  577.       for i := 0 to Count-1 do
  578.         TShort(Items[i]).SaveToIni(ini, 'Shortcut' + IntToStr(i));
  579.       WriteInteger('Desktop', 'NumShorts', Count);
  580.     end;
  581.  
  582.     EraseSection('Folders');
  583.     if SaveWindows then
  584.       with Each(TIconWindow) do begin
  585.         WriteInteger('Folders', 'Count', Count);
  586.         for i := 0 to Count-1 do begin
  587.           TIconWindow(Items[i]).SaveDimensions;
  588.           WriteString('Folders', 'S' + IntToStr(i),
  589.             TIconWindow(Items[i]).Caption);
  590.         end;
  591.       end;
  592.  
  593.   end;
  594.  
  595.   SysWindow.SavePosition(ini, 'System');
  596.   Bin.SavePosition(ini, 'Bin');
  597.   Bin.SaveTrash;
  598.   ini.WriteSectionValues('Window positions', WindowPos);
  599.   ReleaseCursor;
  600. end;
  601.  
  602.  
  603. { This is useful if the user accidentally presses Arrange Icons from the
  604.   Windows task manager (which also arranges shortcuts!).  Since TExtForm
  605.   saves its last icon position, it can be told to move itself back.  }
  606.  
  607. procedure TDesktop.Revert;
  608. var
  609.   i: Integer;
  610. begin
  611.   with Each(TExtForm) do
  612.     for i := 0 to Count-1 do
  613.       with TExtForm(Items[i]) do MinPosition := LastMinPosition;
  614. end;
  615.  
  616.  
  617. { NextForm is called when the user presses Ctrl-Tab.  When a form is
  618.   brought to the front, it sticks itself at the top of Screen.Forms.
  619.   This makes it difficult to select the next form in Z-order (you end
  620.   up flipping between two forms!), so we must bring forward the form
  621.   at the very bottom of the pack }
  622.  
  623. procedure TDesktop.NextForm;
  624. var
  625.   f: TForm;
  626.   i: Integer;
  627. begin
  628.   with Screen do
  629.     for i := FormCount-1 downto 0 do begin
  630.       f := Forms[i];
  631.       if f.Visible and IsWindowEnabled(f.Handle) and (f <> Screen.ActiveForm) and
  632.        (f <> Application.MainForm) and not (f is TShort) then begin
  633.         f.BringToFront;
  634.         f.WindowState := wsNormal;
  635.         Exit;
  636.       end;
  637.     end;
  638. end;
  639.  
  640. { SetCursor and ReleaseCursor are extremely useful to prevent the wrong
  641.   cursor from being displayed after a try...finally block.  If a "busy"
  642.   operation sets the hourglass cursor and calls another busy function,
  643.   the second function would reset the cursor to crDefault after it
  644.   finished.  Of course, the first operation might still be busy, so a
  645.   stack based approach is needed to maintain the right cursor. }
  646.  
  647. procedure TDesktop.SetCursor(Cursor : TCursor);
  648. begin
  649.   CursorStack.Add(Pointer(Screen.Cursor));
  650.   Screen.Cursor := Cursor;
  651. end;
  652.  
  653. procedure TDesktop.ReleaseCursor;
  654. begin
  655.   with CursorStack do
  656.     if Count > 0 then begin
  657.       Screen.Cursor := TCursor(Items[Count-1]);
  658.       Delete(Count-1);
  659.     end;
  660. end;
  661.  
  662.  
  663. end.
  664.