home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 March / Chip_1998-03_cd.bin / zkuste / delphi / ruzkomp / SPLIT32.ZIP / SplitterWnd.pas < prev   
Pascal/Delphi Source File  |  1997-07-03  |  40KB  |  1,132 lines

  1. (*************************************************)
  2. //  TSplitterWnd
  3. //    by Chris Monson - finished 06/03/97
  4. //    This is a very nice little control that you can
  5. //    use to split up windows in Delphi 2.0, 3.0, or
  6. //    C++ builder.  You should have received a document
  7. //    with this unit.  If not, please contact me at
  8. //    ckmonson@burgoyne.com.
  9. (***************************************************)
  10.  
  11. unit SplitterWnd;
  12.  
  13. interface
  14.  
  15. uses
  16.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  17.   ExtCtrls, DsgnIntf;
  18.  
  19. const
  20. (*********** Defaults ***************************)
  21.   DEFAULT_HORZPANESIZE = 50;
  22.   DEFAULT_VERTPANESIZE = 75;
  23.   DEFAULT_NUMPANES  = 2;
  24.   DEFAULT_THICKNESS = 3;
  25.   DEFAULT_HEIGHT = 200;
  26.   DEFAULT_WIDTH = 400;
  27. (************************************************)
  28.  
  29. (*********** Design time constants **************)
  30.   SplitterWndVerbs : array[0..1] of String =
  31.     ('New Pane',
  32.      'Equalize Panes');
  33.  
  34.   SplitterWndNumVerbs = 2;
  35. (************************************************)
  36.  
  37. type
  38.   TSplitterWnd = class;
  39.  
  40. (********************** TPane ***************************)
  41. (********************************************************)
  42.   TPane = class(TScrollBox)
  43.   private
  44.     FSplitterWnd  : TSplitterWnd;
  45.     FDivPercent : Extended;
  46.     FPaneIndex  : Integer;
  47.     FPaneSize,
  48.     FMinPaneSize   : Word;
  49.   protected
  50.     procedure WMMove( var Message : TWMMove ); message WM_MOVE;
  51.     procedure WMSize( var Message : TWMSize ); message WM_SIZE;
  52.     procedure SetSplitterWnd( sw : TSplitterWnd );
  53.     procedure SetPaneIndex( pIndex : Integer );
  54.     procedure SetPaneSize( ps : Word );
  55.   public
  56.     Constructor Create( AOwner : TComponent );override;
  57.     Destructor Destroy;override;
  58.   published
  59.     property SplitterWnd : TSplitterWnd read FSplitterWnd write SetSplitterWnd;
  60.     property PaneIndex   : Integer read FPaneIndex write SetPaneIndex;
  61.     property PaneSize    : Word read FPaneSize write SetPaneSize stored True;
  62.     property MinPaneSize : Word read FMinPaneSize write FMinPaneSize default 0;
  63.   end;
  64.  
  65. (********************** TSplitterWnd ********************)
  66. (********************************************************)
  67.   TOrientation = ( swHorizontal, swVertical );
  68.   TBarStyle    = ( sbCheckered, sbSolid );
  69.   TDrawDragRectEvent = procedure( Sender : TSplitterWnd;
  70.                                   var DrawRect : TRect;
  71.                                   var OwnerDraw : Boolean ) of object;
  72.  
  73.   TSplitterWnd = class(TCustomPanel)
  74.   private
  75.     FPanes : TList;
  76.     FOrientation : TOrientation;
  77.     FThickness : Byte;
  78.     FCursorVert,
  79.     FCursorHorz : TCursor;
  80.     FProportionalResize : Boolean;
  81.     FOnDrawDragRect     : TDrawDragRectEvent;
  82.     FOnEraseDragRect    : TDrawDragRectEvent;
  83.     FBarStyle           : TBarStyle;
  84.     FAllowSizing        : Boolean;
  85.  
  86.     AllPanesLoaded : Boolean;
  87.     BarDragging : Integer;
  88.     OldMousePosition : TPoint;
  89.   protected
  90.     procedure MouseDown( Button: TMouseButton; Shift: TShiftState;
  91.       X, Y: Integer); override;
  92.     procedure MouseMove( Shift: TShiftState; X, Y: Integer); override;
  93.     procedure MouseUp  ( Button: TMouseButton; Shift: TShiftState;
  94.       X, Y: Integer); override;
  95.  
  96.     procedure WMSize( var Message : TWMSize );message WM_SIZE;
  97.     procedure AdjustLastPaneSize;
  98.     procedure CreateWnd;override;
  99.  
  100.     procedure InsertPane( p : TPane );
  101.     procedure RemovePane( p : TPane );
  102.  
  103.     procedure InvertBarRect( curRect : TRect );
  104.  
  105.     procedure CheckMouseBounds( var pos : TPoint );
  106.  
  107.     function GetMinimumPaneSize( bIndex : Word ) : Integer;
  108.     function GetDifferenceBetweenPaneAndPoint( pIndex : Integer;
  109.       pt : TPoint ):Integer;
  110.     function GetDistance( bIndex : Integer ): Integer;
  111.     function GetDefaultPaneSize : Integer;
  112.     function GetRectAtDistance( d : Integer ) : TRect;
  113.     function GetRectAtPoint( p : TPoint ) : TRect;
  114.     function GetBarRect( bIndex : Integer ) : TRect;
  115.  
  116.     function GetPane( paneIndex : Byte):TPane;
  117.     function GetNumPanes : Byte;
  118.     function LeftBar( value : Integer ) : Integer;
  119.     function RightBar( value : Integer ) : Integer;
  120.     procedure SetOrientation( o : TOrientation );
  121.     procedure SetThickness( t : Byte );
  122.     procedure SetCursorHorz (ch : TCursor);
  123.     procedure SetCursorVert (ch : TCursor);
  124.  
  125.     procedure MovePane( oldIndex, newIndex : Integer );
  126.     procedure ResetPaneIndices;
  127.     procedure RecalculatePaneSizes( IncludeLast : Boolean );
  128.     procedure ResetPaneSizes;
  129.     procedure UpdatePaneRects;
  130.  
  131.     procedure GetChildren( Proc : TGetChildProc );override;
  132.     procedure SetChildOrder( Child : TComponent; Order : Integer );override;
  133.   public
  134.     Constructor Create( AOwner : TComponent );override;
  135.     Destructor Destroy;override;
  136.  
  137.     property Panes[ paneIndex : Byte ] : TPane read GetPane;
  138.     property NumPanes : Byte read GetNumPanes;
  139.   published
  140.     property Orientation : TOrientation read FOrientation write SetOrientation stored True;
  141.     property Thickness : Byte read FThickness write SetThickness stored True;
  142.     property CursorHorz : TCursor read FCursorHorz write SetCursorHorz stored True;
  143.     property CursorVert : TCursor read FCursorVert write SetCursorVert stored True;
  144.     property ProportionalResize : Boolean read FProportionalResize
  145.                                           write FProportionalResize default True;
  146.     property BarStyle : TBarStyle read FBarStyle write FBarStyle stored True;
  147.     property AllowSizing : Boolean read FAllowSizing write FAllowSizing default True;
  148.       {Events}
  149.     property OnDrawDragRect : TDrawDragRectEvent read FOnDrawDragRect
  150.                                                  write FOnDrawDragRect;
  151.     property OnEraseDragRect : TDrawDragRectEvent read FOnEraseDragRect
  152.                                                  write FOnEraseDragRect;
  153.     { Redeclared properties }
  154.       {Properties}
  155.     property Align;
  156.     property BevelInner;
  157.     property BevelOuter;
  158.     property BevelWidth;
  159.     property BorderWidth;
  160.     property BorderStyle;
  161.     property Enabled;
  162.     property Color;
  163.     property Ctl3D;
  164.     property ParentColor;
  165.     property ParentCtl3D;
  166.     property ParentShowHint;
  167.     property ShowHint;
  168.     property TabOrder;
  169.     property TabStop;
  170.     property Visible;
  171.       {Events}
  172.     property OnEnter;
  173.     property OnExit;
  174.     property OnMouseDown;
  175.     property OnMouseMove;
  176.     property OnMouseUp;
  177.     property OnResize;
  178.   end;
  179.  
  180. (****************** TSplitterWndEditor ******************)
  181. (********************************************************)
  182.   TSplitterWndEditor = class(TDefaultEditor)
  183.     procedure ExecuteVerb(Index: Integer); override;
  184.     function GetVerb(Index: Integer): string; override;
  185.     function GetVerbCount: Integer; override;
  186.   end;
  187.  
  188. (***************** Globals *****************************)
  189. (*******************************************************)
  190.  
  191. procedure Register;
  192.  
  193. (*************************************************************************)
  194. (*************************************************************************)
  195.  
  196. implementation
  197.  
  198. (*************************************************************************)
  199. (***************************** TPane *************************************)
  200. (*************************************************************************)
  201. // Constructor TPane.Create
  202. //   Initialize properties
  203. Constructor TPane.Create( AOwner : TComponent );
  204. begin
  205.   inherited Create( AOwner );
  206.   FMinPaneSize := 0;
  207. end;
  208.  
  209. // Destructor TPane.Destroy
  210. //   This makes sure that the pane is removed from the splitter window
  211. //   when it is removed.  Without doing that, we'll get all kinds of
  212. //   access violations when the pane tries to access the panes.
  213. Destructor TPane.Destroy;
  214. begin
  215.   if FSplitterWnd <> nil then FSplitterWnd.RemovePane(Self);
  216.   inherited;
  217. end;
  218.  
  219. // Procedure TPane.WMMove( var Message : TWMMove );
  220. //   Windows message trap.  Panes are not to be moved except by the
  221. //   splitter window.
  222. procedure TPane.WMMove( var Message : TWMMove );
  223. begin
  224.   inherited;
  225.   if (csDesigning in ComponentState) and (FSplitterWnd <> nil) then
  226.     FSplitterWnd.UpdatePaneRects;
  227. end;
  228.  
  229. // procedure TPane.WMSize( var Message : TWMSize );
  230. //   See reasons for this procedure in TPane.WMMove.
  231. procedure TPane.WMSize( var Message : TWMSize );
  232. begin
  233.   inherited;
  234.   if (csDesigning in ComponentState) and (FSplitterWnd <> nil) then
  235.     FSplitterWnd.UpdatePaneRects;
  236. end;
  237.  
  238. // procedure TPane.SetSplitterWnd( sw : TSplitterWnd );
  239. //   sw      : pointer to the splitter window to set
  240. //   This procedure removes the pane from its old splitter window (if
  241. //   one exists) and calls the new splitter window's InsertPane.
  242. procedure TPane.SetSplitterWnd( sw : TSplitterWnd );
  243. begin
  244.   if (sw <> FSplitterWnd) then begin
  245.     if FSplitterWnd <> nil then FSplitterWnd.RemovePane(Self);
  246.     Parent := sw;
  247.     // sw.InsertPane will set FSplitterWnd since these components are friends.
  248.     if sw <> nil then sw.InsertPane(Self);  // This will set FSplitterWnd
  249.   end;
  250. end;
  251.  
  252. // procedure TPane.SetPaneIndex( pIndex : Integer );
  253. //   pIndex  : new pane index.
  254. //   This acts a lot like the tabindex property of a TTabSheet.  It changes
  255. //   the order of the pane in the splitter window.
  256. procedure TPane.SetPaneIndex( pIndex : Integer );
  257. var
  258.   oldIndex : integer;
  259. begin
  260.   if FSplitterWnd <> nil then
  261.   begin
  262.     oldIndex := PaneIndex;
  263.     FSplitterWnd.MovePane(oldIndex, pIndex);
  264.   end;
  265. end;
  266.  
  267. // procedure TPane.SetPaneSize( ps : Word );
  268. //   This is one of the more complex procedures.   A short explanation
  269. //   on its organization follows:  The first few lines of code are going
  270. //   to be executed before the SplitterWnd has been assigned, ie, when
  271. //   the pane is being loaded.  If the pane is being loaded, then it is
  272. //   always the last one in the splitter window's list, which makes it
  273. //   a special case and it won't size correctly.  If the splitter window
  274. //   has indeed been assigned, then the panes are all loaded, and this is
  275. //   just being added to the splitter window.  It may take a few look-overs
  276. //   to understand just what I did here.
  277. procedure TPane.SetPaneSize( ps : Word );
  278. var
  279.   OldPaneSize,
  280.   MinPaneSizeThis,
  281.   MinPaneSizeNext,
  282.   MaxPaneSize,
  283.   DualPaneSize  : Integer;
  284.   NextPane     : TPane;
  285. begin
  286.   { if the SplitterWnd has not yet been assigned, that means that the
  287.     panes are being added, so no error checking and no pane size adjustment
  288.     will be done yet. }
  289.   if FSplitterWnd = nil then
  290.   begin
  291.     FPaneSize := ps;
  292.     Exit;
  293.   end;
  294.  
  295.   { if we get this far, that means a splitter window is assigned and the
  296.     panes have all been loaded.  The next section here makes sure that
  297.     none of the panes go over or under their max's and min's. }
  298.   if FSplitterWnd.NumPanes = 1 then
  299.   begin
  300.     Case FSplitterWnd.Orientation of
  301.       swHorizontal : FPaneSize := FSplitterWnd.Height;
  302.       swVertical   : FPaneSize := FSplitterWnd.Width;
  303.     end;
  304.   end
  305.   else if (FSplitterWnd.NumPanes > 1) and
  306.           (PaneIndex < (FSplitterWnd.NumPanes-1)) then
  307.   begin
  308.     OldPaneSize := FPaneSize;
  309.     NextPane := FSplitterWnd.Panes[PaneIndex+1];
  310.  
  311.     // Minimum is easy.  It's just a matter of deciding on the orientation
  312.     MinPaneSizeThis := FSplitterWnd.GetMinimumPaneSize(PaneIndex);
  313.     MinPaneSizeNext := FSplitterWnd.GetMinimumPaneSize(PaneIndex+1);
  314.  
  315.     // Maximum is a little more complex.  I have to check the size of the
  316.     // neighboring pane and make sure IT won't be too small when this one
  317.     // is enlarged.
  318.     MaxPaneSize := OldPaneSize +
  319.                    (NextPane.PaneSize -
  320.                     FSplitterWnd.Thickness -
  321.                     MinPaneSizeNext);
  322.     // Store the total size of this pane and the next pane so that both will
  323.     // be sized correctly.  ( Enlarging this one makes the next one smaller,
  324.     // and making it smaller enlarges the neighboring pane. )
  325.     DualPaneSize := PaneSize + NextPane.PaneSize;
  326.     FPaneSize := ps;  // ps was passed into the procedure
  327.     // Reset the sizes to max or min if they went over or under
  328.     if FPaneSize > MaxPaneSize then
  329.       FPaneSize := MaxPaneSize
  330.     else if FPaneSize < MinPaneSizeThis then
  331.       FPaneSize := MinPaneSizeThis;
  332.     // Make sure the pane next door is sized correctly
  333.     NextPane.FPaneSize := DualPaneSize - FPaneSize;
  334.   end;
  335.   // There may be some errors in pane sizing - the last pane in the splitter
  336.   // window will take up the slack
  337.   FSplitterWnd.AdjustLastPaneSize;
  338.   // Change the visual appearance of the panes.
  339.   FSplitterWnd.UpdatePaneRects;
  340. end;
  341.  
  342. (*************************************************************************)
  343. (***************************** TSplitterWnd ******************************)
  344. (*************************************************************************)
  345. { Overridden methods }
  346. // Constructor TSplitterWnd.Create( AOwner : TComponent );
  347. //   Initialize things like the size, etc.  Allocate memory
  348. //   for the pane list, and set the AllPanesLoaded variable to
  349. //   false.  Certain things can't be done if the panes aren't all
  350. //   loaded, yet.
  351. Constructor TSplitterWnd.Create( AOwner : TComponent );
  352. begin
  353.   inherited Create(AOwner);
  354.   // Visual stuff - caption is set later (CreateWnd) to prevent
  355.   // it from showing during design-time.
  356.   BevelOuter := bvNone;
  357.   Height := DEFAULT_HEIGHT;
  358.   Width  := DEFAULT_WIDTH;
  359.   FCursorVert := crHSplit;
  360.   FCursorHorz := crVSplit;
  361.   FThickness := DEFAULT_THICKNESS;
  362.   FOrientation := swVertical;
  363.   FProportionalResize := True;
  364.   FAllowSizing := True;
  365.   // Non-visual initialization
  366.   FPanes := TList.Create;
  367.   AllPanesLoaded := False;
  368.   BarDragging := -1;
  369. end;
  370.  
  371. // Destructor  TSplitterWnd.Destroy;
  372. //   Deallocate the panes list.
  373. Destructor  TSplitterWnd.Destroy;
  374. begin
  375.   FPanes.Free;
  376.   inherited;
  377. end;
  378.  
  379. // function TSplitterWnd.GetDifferenceBetweenPaneAndPoint(
  380. //   pIndex : Integer; pt : TPoint ):Integer;
  381. //
  382. //   pIndex   : index in pane list of pane to check
  383. //   pt       : current point.
  384. //   The purpose of this function is to find out how big
  385. //   a pane should be given the current cursor position.
  386. //   It returns a pixel value that is dependent on orientation.
  387. function TSplitterWnd.GetDifferenceBetweenPaneAndPoint(
  388.   pIndex : Integer; pt : TPoint ):Integer;
  389. begin
  390.   Case Orientation of
  391.     swHorizontal : Result := pt.Y - GetDistance( pIndex );
  392.     swVertical   : Result := pt.X - GetDistance( pIndex );
  393.   end;
  394. end;
  395.  
  396. // function TSplitterWnd.GetDistance( bIndex : Integer ): Integer;
  397. //   bIndex  : index of pane to find the "distance" of.
  398. //   The function returns the total distance from the left or top
  399. //   of the splitter window to the right or bottom of the pane
  400. //   specified in pixels.
  401. function TSplitterWnd.GetDistance( bIndex : Integer ): Integer;
  402. var
  403.   curPane : Integer;
  404. begin
  405.   Result := 0;
  406.   For curPane := 0 to bIndex do
  407.     Result := Result + Panes[curPane].PaneSize;
  408. end;
  409.  
  410. // procedure TSplitterWnd.MouseDown( Button: TMouseButton; Shift: TShiftState;
  411. //   Overridden mouseDown procedure.  If the SplitterWnd receives any mouse
  412. //   events at all, that means that the cursor is on a drag bar, since any
  413. //   of the non-draggable areas of the window are covered by panes.
  414. //   This sets the BarDragging variable, which specifies which bar is in
  415. //   the dragging mode. It also calls the InvertBarRect procedure, which
  416. //   draws an inverted bar on the splitter window and its children.
  417. procedure TSplitterWnd.MouseDown( Button: TMouseButton; Shift: TShiftState;
  418.   X, Y: Integer);
  419. var
  420.   curPane,
  421.   curDistance : Integer;
  422.   mousePoint : TPoint;
  423.   curRect    : TRect;
  424.   OwnerDraw : Boolean;
  425. begin
  426.   inherited MouseDown( Button, Shift, X, Y );
  427.  
  428.   BarDragging := -1;
  429.   if (AllowSizing) then
  430.   begin
  431.     curDistance := 0;
  432.     for curPane := 0 to NumPanes-2 do begin
  433.       curDistance := curDistance + Panes[curPane].PaneSize;
  434.       curRect := GetRectAtDistance( curDistance );
  435.       mousePoint := Point( X, Y );
  436.       if PtInRect( curRect, mousePoint ) then
  437.         BarDragging := curPane;
  438.     end;
  439.     OldMousePosition := POINT( X, Y );
  440.     // Draw the drag rectangle
  441.     OwnerDraw := True;
  442.     curRect := GetRectAtPoint( OldMousePosition );
  443.     if Assigned( OnDrawDragRect ) then
  444.       OnDrawDragRect( Self, curRect, OwnerDraw );
  445.     if OwnerDraw then
  446.       InvertBarRect( curRect );
  447.     end;
  448. end;
  449.  
  450. // procedure TSplitterWnd.MouseUp( Button: TMouseButton; Shift: TShiftState;
  451. //   Overridden MouseUp procedure.  If any splitter bars were in drag mode,
  452. //   the pane sizes need to be updated according to where the user has
  453. //   dropped the bar.
  454. procedure TSplitterWnd.MouseUp( Button: TMouseButton; Shift: TShiftState;
  455.   X, Y: Integer);
  456. var
  457.   OwnerDraw : Boolean;
  458.   curRect   : TRect;
  459. begin
  460.   if BarDragging <> -1 then
  461.   begin
  462.     // Draw the drag rectangle
  463.     OwnerDraw := True;
  464.     curRect := GetRectAtPoint( OldMousePosition );
  465.     if Assigned( OnEraseDragRect ) then
  466.       OnEraseDragRect( Self, curRect, OwnerDraw );
  467.     if OwnerDraw then
  468.       InvertBarRect( curRect );
  469.     // Change the pane size according to where the bar was dropped.
  470.     Panes[BarDragging].PaneSize :=
  471.       Panes[BarDragging].PaneSize +
  472.       GetDifferenceBetweenPaneAndPoint( BarDragging, OldMousePosition );
  473.   end;
  474.   BarDragging := -1;
  475.   inherited MouseUp( Button, Shift, X, Y );
  476. end;
  477.  
  478. // procedure TSplitterWnd.MouseMove( Shift: TShiftState; X, Y: Integer);
  479. //   Overridden MouseMove procedure.  If any bars are dragging, then the
  480. //   inverted rectangle needs to be updated.  Boundary checking is also done
  481. //   here so that the user can see just where the dragging limit is.
  482. procedure TSplitterWnd.MouseMove( Shift: TShiftState; X, Y: Integer);
  483. var
  484.   tempMousePos : TPoint;
  485.   oldRect, newRect,
  486.   curRect   : TRect;
  487.   OwnerDraw        : Boolean;
  488. begin
  489.   inherited MouseMove( Shift, X, Y );
  490.   if BarDragging <> -1 then
  491.   begin
  492.     tempMousePos := POINT( X, Y );
  493.     CheckMouseBounds( tempMousePos );
  494.     oldRect := GetRectAtPoint( OldMousePosition );
  495.     newRect := GetRectAtPoint( tempMousePos );
  496.     // Don't update the rectangles if out of bounds.
  497.     // This will eliminate the flickering.
  498.     if not EqualRect(oldRect,newRect) then begin
  499.       // Erase the old rectangle
  500.       OwnerDraw := True;
  501.       curRect   := GetRectAtPoint( OldMousePosition );
  502.       if Assigned( OnEraseDragRect ) then
  503.         OnEraseDragRect(Self, curRect, OwnerDraw);
  504.       if OwnerDraw then
  505.         InvertBarRect( curRect );
  506.       // Draw the new rectangle
  507.       OwnerDraw := True;
  508.       curRect   := GetRectAtPoint( tempMousePos );
  509.       if Assigned( OnDrawDragRect ) then
  510.         OnDrawDragRect(Self, curRect, OwnerDraw);
  511.       if OwnerDraw then
  512.         InvertBarRect( curRect );
  513.     end;
  514.     OldMousePosition := tempMousePos;
  515.   end;
  516. end;
  517.  
  518. // procedure TSplitterWnd.WMSize( var message : TWMSize );
  519. //   Windows size message sent to the splitter window.  All of the panes will
  520. //   need to be resized.  The recalculatePaneSizes procedure maintains
  521. //   the size percentage of all of the panes.
  522. procedure TSplitterWnd.WMSize( var message : TWMSize );
  523. begin
  524.   inherited;
  525.   if ProportionalResize then
  526.     RecalculatePaneSizes( True ) else
  527.     AdjustLastPaneSize;
  528.   UpdatePaneRects;
  529. end;
  530.  
  531. // procedure TSplitterWnd.AdjustLastPaneSize;
  532. //   This procedure is usually called after some pane sizes have been
  533. //   changed.  It makes the last pane exactly fill the remaining space
  534. //   in the splitter window so that no weird effects happen near the
  535. //   right/bottom edges of the window.
  536. procedure TSplitterWnd.AdjustLastPaneSize;
  537. var
  538.   TotalPaneSize,
  539.   FullWindowSize,
  540.   curPane         : Integer;
  541. begin
  542.   if NumPanes = 0 then exit;
  543.  
  544.   Case Orientation of
  545.     swHorizontal : FullWindowSize := Height;
  546.     swVertical   : FullWindowSize := Width;
  547.   end;
  548.   TotalPaneSize := 0;
  549.   for curPane := 0 to NumPanes - 1 do
  550.     TotalPaneSize := TotalPaneSize + Panes[curPane].PaneSize;
  551.  
  552.   if TotalPaneSize <> FullWindowSize
  553.     then Panes[NumPanes-1].FPaneSize :=
  554.       Panes[NumPanes-1].FPaneSize + (FullWindowSize - TotalPaneSize);
  555. end;
  556.  
  557. // procedure TSplitterWnd.CreateWnd;
  558. //   By the time this procedure is called, all of the panes have
  559. //   been loaded and the caption has been auto-set.  This overrides
  560. //   the caption and allows some of the special functions to work
  561. //   that only apply when all of the panes have been loaded.
  562. procedure TSplitterWnd.CreateWnd;
  563. begin
  564.   inherited;
  565.   Caption := '';
  566.   AllPanesLoaded := True;
  567.   UpdatePaneRects;
  568. end;
  569.  
  570. // procedure TSplitterWnd.InsertPane( p : TPane );
  571. //   This inserts a new pane into the control.  If the new pane is
  572. //   being inserted by the compiler (ie, being loaded), then the
  573. //   new pane sizes should NOT be recalculated.  If it is being inserted
  574. //   by the developer through the design interface or during runtime,
  575. //   the pane sizes will be recalculated to give it space.
  576. procedure TSplitterWnd.InsertPane( p : TPane );
  577. var
  578.   NewPosition : Integer;
  579. begin
  580.   NewPosition := FPanes.Add(p);
  581.   p.FSplitterWnd := Self;
  582.   p.FPaneIndex := NewPosition;
  583.   if (AllPanesLoaded) then
  584.     RecalculatePaneSizes(False);
  585. end;
  586.  
  587. // procedure TSplitterWnd.RemovePane( p : TPane );
  588. //   This takes a pane out of the control, but does NOT free up
  589. //   the resources associated with it.  It resets the pane indices
  590. //   so there are no "holes" in the PaneIndex properties, and it
  591. //   recalculates the pane sizes so they fill up the splitter window.
  592. procedure TSplitterWnd.RemovePane( p : TPane );
  593. begin
  594.   if FPanes.IndexOf(p) <> -1 then
  595.   begin
  596.     p.FSplitterWnd := nil;
  597.     p.FPaneIndex := -1;
  598.     FPanes.Remove(p);
  599.     ResetPaneIndices;
  600.     if ProportionalResize then
  601.       RecalculatePaneSizes( True ) else
  602.       AdjustLastPaneSize;
  603.   end;
  604. end;
  605.  
  606. // procedure TSplitterWnd.CheckMouseBounds( var pos : TPoint );
  607. //  This simply checks to see if the mouse can drag the bar any further
  608. //  than it already has.  If it can, it will.  Otherwise, it will not.
  609. procedure TSplitterWnd.CheckMouseBounds( var pos : TPoint );
  610. var
  611.   MinPaneSizeThis,
  612.   MinPaneSizeNext,
  613.   PrevDistance,
  614.   NextDistance : Integer;
  615. begin
  616.   MinPaneSizeThis := GetMinimumPaneSize( BarDragging );
  617.   MinPaneSizeNext := GetMinimumPaneSize( BarDragging + 1 );
  618.   if BarDragging > 0 then
  619.     PrevDistance := GetDistance( BarDragging - 1) + MinPaneSizeThis else
  620.     PrevDistance := MinPaneSizeThis;
  621.   NextDistance := GetDistance( BarDragging + 1 ) - MinPaneSizeNext;
  622.  
  623.   Case Orientation of
  624.     swHorizontal :
  625.       begin
  626.         if pos.Y < PrevDistance then
  627.           pos.Y := PrevDistance;
  628.         if pos.Y > NextDistance then
  629.           pos.Y := NextDistance;
  630.       end;
  631.     swVertical   :
  632.       begin
  633.         if pos.X < PrevDistance then
  634.           pos.X := PrevDistance;
  635.         if pos.X > NextDistance then
  636.           pos.X := NextDistance;
  637.       end;
  638.   end;
  639. end;
  640.  
  641. // function TSplitterWnd.GetMinimumPaneSize:Integer;
  642. //   This simply returns twice the size of a scroll bar if the
  643. //   MinPaneSize property is 0 and FMinPaneSize if not.  The type
  644. //   of scroll bar is determined by the orientation.
  645. function TSplitterWnd.GetMinimumPaneSize( bIndex : Word ):Integer;
  646. begin
  647.   if (Panes[bIndex].MinPaneSize = 0) then
  648.   begin
  649.     Case Orientation of
  650.       swHorizontal : Result := GetSystemMetrics( SM_CYHSCROLL )*2;
  651.       swVertical   : Result := GetSystemMetrics( SM_CXVSCROLL )*2;
  652.     end;
  653.   end
  654.   else
  655.     Result := Panes[bIndex].MinPaneSize;
  656. end;
  657.  
  658. // function TSplitterWnd.GetDefaultPaneSize:Integer;
  659. //   Just a wrapper for the constants found at the beginning of the
  660. //   unit.  It was cumbersome to put a lot of case statements into
  661. //   the code, so they are found in all of these little functions.
  662. function TSplitterWnd.GetDefaultPaneSize:Integer;
  663. begin
  664.   Case Orientation of
  665.     swHorizontal :
  666.       if NumPanes < 1 then
  667.         Result := Height else
  668.         Result := DEFAULT_HORZPANESIZE;
  669.     swVertical   :
  670.       if NumPanes < 1 then
  671.         Result := Width else
  672.         Result := DEFAULT_VERTPANESIZE;
  673.   end;
  674. end;
  675.  
  676. // procedure TSplitterWnd.InvertBarRect( pos : TPoint );
  677. //   This draws an inverted rectangle on the splitter window and its
  678. //   children.  I used a call to GetDCEx to make the rectangle draw
  679. //   itself over the children and the parent.  Otherwise, the rectangle
  680. //   would have been hidden by any children placed on the splitter window,
  681. //   including the panes themselves.
  682. procedure TSplitterWnd.InvertBarRect( curRect : TRect );
  683. var
  684.   DC      : HDC;
  685.   grayPattern : array [0..8] of WORD;  { I have added this }
  686.   grayBitmap  : HBITMAP;
  687.   halftoneBrush : HBRUSH;
  688.   oldobject     : HBRUSH;
  689.   i   : Integer;
  690. begin
  691.   DC := GetDCEx( Handle, 0, DCX_CACHE or DCX_PARENTCLIP );
  692.  
  693.   Case BarStyle of
  694.     sbSolid    : InvertRect( DC, curRect );
  695.     sbCheckered : begin
  696.       for i:= 0 to 8 do
  697.         grayPattern[i] := WORD($5555 shl (i AND 1));
  698.       grayBitmap  := CreateBitmap(8, 8, 1, 1, @grayPattern);
  699.       if (grayBitmap <> 0) then
  700.       begin
  701.         halftoneBrush := CreatePatternBrush(grayBitmap);
  702.         DeleteObject(grayBitmap);
  703.       end;
  704.       oldobject := SelectObject(DC, halftoneBrush);
  705.       PatBlt( DC,
  706.               curRect.Left,
  707.               curRect.Top,
  708.               curRect.Right - curRect.Left,
  709.               curRect.Bottom - curRect.Top,
  710.               PATINVERT);
  711.       SelectObject(DC,oldobject);
  712.       DeleteObject(halftoneBrush);
  713.     end;  // sbCheckered case
  714.   end; // Case
  715.   ReleaseDC( Handle, DC );
  716. end;
  717.  
  718. // function TSplitterWnd.GetRectAtDistance( d : Integer ) : TRect;
  719. //   d     : pixel distance from left or top of splitter window.
  720. //   This function returns a rectangle that will show when the mouse
  721. //   is dragging a bar around.
  722. function TSplitterWnd.GetRectAtDistance( d : Integer ) : TRect;
  723. begin
  724.   Case Orientation of
  725.     swHorizontal :
  726.       Result := RECT( 0,
  727.                       d - LeftBar(Thickness),
  728.                       Width,
  729.                       d + RightBar(Thickness)+1);
  730.     swVertical   :
  731.       Result := RECT( d - LeftBar(Thickness),
  732.                       0,
  733.                       d + RightBar(Thickness)+1,
  734.                       Height );
  735.   end;
  736. end;
  737.  
  738. // function TSplitterWnd.GetRectAtPoint( p : TPoint ) : TRect;
  739. //   p : point at which to find the rectangle.
  740. //   This is very similar to GetRectAtDistance, except it works
  741. //   on a point.
  742. function TSplitterWnd.GetRectAtPoint( p : TPoint ) : TRect;
  743. begin
  744.   Case Orientation of
  745.     swHorizontal :
  746.       Result := RECT( 0,
  747.                       p.Y - LeftBar(Thickness),
  748.                       Width,
  749.                       p.Y + RightBar(Thickness)+1);
  750.     swVertical   :
  751.       Result := RECT( p.X - LeftBar(Thickness),
  752.                       0,
  753.                       p.X + RightBar(Thickness)+1,
  754.                       Height );
  755.   end;
  756. end;
  757.  
  758. // function TSplitterWnd.GetBarRect( bIndex : Integer ) : TRect;
  759. //   bIndex   : Bar Index.  Which bar to get the rectangle for.
  760. //   This returns the bar's rectangle so that panes can be resized
  761. //   after the dragging operation has stopped.
  762. function TSplitterWnd.GetBarRect( bIndex : Integer ) : TRect;
  763. var
  764.   curPane,
  765.   distance : Integer;
  766. begin
  767.   curPane := 0;
  768.   distance := 0;
  769.   While (curPane <= bIndex) do
  770.   begin
  771.     distance := distance + Panes[curPane].PaneSize;
  772.     inc(curPane);
  773.   end;
  774.   Result := GetRectAtDistance( distance );
  775. end;
  776.  
  777. // function TSplitterWnd.GetPane( paneIndex : Byte ) : TPane;
  778. //   Read Property function for the Panes array property.
  779. function TSplitterWnd.GetPane( paneIndex : Byte ) : TPane;
  780. begin
  781.   Result := FPanes[paneIndex];
  782. end;
  783.  
  784.  
  785. // function TSplitterWnd.GetNumPanes : Byte;
  786. //   Gets the number of panes.  A read property function.
  787. function TSplitterWnd.GetNumPanes : Byte;
  788. begin
  789.   Result := FPanes.Count;
  790. end;
  791.  
  792. // procedure TSplitterWnd.SetOrientation( o : TOrientation );
  793. //   Changes the orientation of the splitter window.  It is
  794. //   the Orientation property's write procedure.  This procedure
  795. //   does a couple of important things.  It sets the pane sizes
  796. //   to the same relative percentage that they had in the previous
  797. //   orientation (with some error, I would imagine) and it changes
  798. //   the SplitterWnd cursor.
  799. procedure TSplitterWnd.SetOrientation( o : TOrientation );
  800. var
  801.   curPane,
  802.   TotalPaneSize : Integer;
  803.   Pane    : TPane;
  804. begin
  805.   if o = FOrientation then exit;
  806.   FOrientation := o;
  807.  
  808.   if NumPanes = 0 then exit;
  809.  
  810.   TotalPaneSize := 0;
  811.   For curPane := 0 to NumPanes - 1 do
  812.   begin
  813.     Pane := TPane(FPanes[curPane]);
  814.     Case FOrientation of
  815.       swHorizontal :  // Changing from vertical to horizontal
  816.         begin
  817.           Pane.FPaneSize := Round( (Pane.PaneSize / Width) * Height );
  818.           cursor := CursorHorz;
  819.         end;
  820.       swVertical  :  // Changing from horizontal to vertical
  821.         begin
  822.           Pane.FPaneSize := Round( (Pane.PaneSize / Height) * Width );
  823.           cursor := CursorVert;
  824.         end;
  825.     end; // Case
  826.     TotalPaneSize := TotalPaneSize + Pane.PaneSize;
  827.   end;
  828.   AdjustLastPaneSize;
  829.   { All errors have been accounted for, so now update the pane rectangles }
  830.   UpdatePaneRects;
  831. end;
  832.  
  833. // procedure TSplitterWnd.SetThickness( t : Byte );
  834. //   Thickness write property.  Updates the pane appearance
  835. //   to account for a thicker or thinner drag bar.
  836. procedure TSplitterWnd.SetThickness( t : Byte );
  837. begin
  838.   if t < 1 then t := 1;
  839.   FThickness := t;
  840.   UpdatePaneRects;
  841. end;
  842.  
  843. // procedure TSplitterWnd.SetCursorHorz( ch : TCursor );
  844. //   Sets the CursorHorz property.  If the orientation
  845. //   is correct, it also sets the cursor.
  846. procedure TSplitterWnd.SetCursorHorz( ch : TCursor );
  847. begin
  848.   if Orientation = swHorizontal
  849.     then Cursor := ch;
  850.   FCursorHorz := ch;
  851. end;
  852.  
  853. // procedure TSplitterWnd.SetCursorVert( ch : TCursor );
  854. //  Sets the CursorVert property.  See SetCursorHorz.
  855. procedure TSplitterWnd.SetCursorVert( ch : TCursor );
  856. begin
  857.   if Orientation = swVertical
  858.     then Cursor := ch;
  859.   FCursorVert := ch;
  860. end;
  861.  
  862. // function TSplitterWnd.LeftBar( value : Integer ):Integer;
  863. //   Gets the left half width (or top half) of the splitter bars.
  864. //   The bar positions are defined by their centers, so this
  865. //   function truncates the floating point value, and the right
  866. //   function rounds it to account for odd thicknesses.
  867. function TSplitterWnd.LeftBar( value : Integer ):Integer;
  868. begin
  869.   Result := Trunc( value / 2 );
  870. end;
  871.  
  872. // function TSplitterWnd.RightBar( value : Integer ):Integer;
  873. //   See TSplitterWnd.LeftBar.
  874. function TSplitterWnd.RightBar( value : Integer ):Integer;
  875. begin
  876.   Result := Round( value / 2 );
  877. end;
  878.  
  879. // procedure TSplitterWnd.MovePane( oldIndex, newIndex : Integer );
  880. //   Swaps pane positions in the list and changes their indices.
  881. //   This allows for panes to be moved around if desired.
  882. procedure TSplitterWnd.MovePane( oldIndex, newIndex : Integer );
  883. begin
  884.   FPanes.Move(oldIndex, newIndex);
  885.   ResetPaneIndices;
  886.   UpdatePaneRects;
  887. end;
  888.  
  889. // procedure TSplitterWnd.UpdatePaneRects;
  890. //   This procedure is the meat and bones of the visual appearance of the
  891. //   splitter window.  It checks all of the pane sizes and it draws them
  892. //   correctly on the splitter window.  Depending on the orientation and
  893. //   whether or not a pane is the first or last, special cases come into
  894. //   consideration, since the bar positions are calculated according to
  895. //   their middles and not their edges.
  896. procedure TSplitterWnd.UpdatePaneRects;
  897. var
  898.   curPane : Integer;
  899.   curRect : TRect;
  900.   Left, Top, Right, Bottom : Integer;
  901.   Pane,
  902.   PrevPane    : TPane;
  903. begin
  904.   if (NumPanes = 1) then begin
  905.     TPane(FPanes[0]).SetBounds(0,0,Width,Height);
  906.     exit;
  907.   end;
  908.  
  909.   For curPane := 0 to NumPanes-1 do begin
  910.     Pane := TPane(FPanes[curPane]);
  911.     if curPane = 0 then
  912.       PrevPane := TPane(FPanes[curPane]) else
  913.       PrevPane := TPane(FPanes[curPane-1]);
  914.  
  915.     case Orientation of
  916.       swHorizontal  :
  917.         begin
  918.           if curPane = 0 then
  919.             Pane.SetBounds(
  920.               0,
  921.               0,
  922.               Width,
  923.               Pane.PaneSize - LeftBar(Thickness) )
  924.           else if curPane = (NumPanes-1) then
  925.             Pane.SetBounds(
  926.               0,
  927.               PrevPane.Top + PrevPane.Height + Thickness,
  928.               Width,
  929.               Pane.PaneSize - RightBar(Thickness) )
  930.           else // one of the middle panes
  931.             Pane.SetBounds(
  932.               0,
  933.               PrevPane.Top + PrevPane.Height + Thickness,
  934.               Width,
  935.               Pane.PaneSize - Thickness);
  936.         end;
  937.       swVertical    :
  938.         begin
  939.           if curPane = 0 then
  940.             Pane.SetBounds(
  941.               0,
  942.               0,
  943.               Pane.PaneSize - LeftBar(Thickness),
  944.               Height )
  945.           else if curPane = (NumPanes-1) then
  946.             Pane.SetBounds(
  947.               PrevPane.Left + PrevPane.Width + Thickness,
  948.               0,
  949.               Pane.PaneSize - RightBar(Thickness),
  950.               Height )
  951.           else // one of the middle panes
  952.             Pane.SetBounds(
  953.               PrevPane.Left + PrevPane.Width + Thickness,
  954.               0,
  955.               Pane.PaneSize - Thickness,
  956.               Height );
  957.         end;
  958.     end; // Case
  959.   end;
  960. end;
  961.  
  962. // procedure TSplitterWnd.ResetPaneIndices;
  963. //   Very simple.  Goes through the list and assigns the pane indices
  964. //   to their indices in the list.  The panes are always in order.
  965. procedure TSplitterWnd.ResetPaneIndices;
  966. var
  967.   curPane : Integer;
  968. begin
  969.   For curPane := 0 to NumPanes-1 do begin
  970.     Panes[curPane].FPaneIndex := curPane;
  971.   end;
  972. end;
  973.  
  974. // procedure TSplitterWnd.RecalculatePaneSizes( IncludeLast : Boolean );
  975. //   IncludeLast   : this is set to True if ALL of the panes are to be
  976. //   recalculated, and it is set to False if all but the last pane are
  977. //   to be resized.  The IncludeLast parameter is always FALSE when a new
  978. //   pane has just been added and the others are shifting around to get
  979. //   out of its way.  This allows for immediate deletion of the new pane
  980. //   without disturbing the original positions of the preceding panes.
  981. procedure TSplitterWnd.RecalculatePaneSizes( IncludeLast : Boolean );
  982. var
  983.   TotalPaneSize,
  984.   FullWindowSize,
  985.   curPane,
  986.   HighPane       : Integer;
  987. begin
  988.   Case Orientation of
  989.     swHorizontal : FullWindowSize := Height;
  990.     swVertical   : FullWindowSize := Width;
  991.   end;
  992.   if IncludeLast then
  993.     HighPane := NumPanes - 1 else
  994.     begin
  995.       HighPane := NumPanes - 2;
  996.       FullWindowSize := FullWindowSize - Panes[NumPanes-1].PaneSize;
  997.     end;
  998.  
  999.   if HighPane >= 0 then
  1000.   begin
  1001.     TotalPaneSize := 0;
  1002.     for curPane := 0 to HighPane do
  1003.       TotalPaneSize := TotalPaneSize + Panes[curPane].PaneSize;
  1004.  
  1005.     { The total pane size will include all of the panes unless the last is
  1006.       excluded.  The FullWindowSize variable is the size of the full window
  1007.       size in which the specified panes should fit.  For example, if there
  1008.       are three panes, and then one is added, the three panes already fill
  1009.       the entire window.  The new window size for those three panes will be
  1010.       the size of the splitter window minus the size of the last pane that
  1011.       was recently added.  The remaining panes will fit inside of the new
  1012.       window size.  Their relative size percentages will remain the same. }
  1013.     for curPane := 0 to HighPane do
  1014.       Panes[curPane].FPaneSize := Round( Panes[curPane].FPaneSize *
  1015.         ( FullWindowSize / TotalPaneSize ) );
  1016.   end;
  1017.   AdjustLastPaneSize;
  1018.   UpdatePaneRects;
  1019. end;
  1020.  
  1021. // procedure TSplitterWnd.ResetPaneSizes;
  1022. //   This sets all of the pane sizes the same.
  1023. procedure TSplitterWnd.ResetPaneSizes;
  1024. var
  1025.   curPane,
  1026.   TotalPaneSize : Integer;
  1027.   Pane : TPane;
  1028. begin
  1029.   if NumPanes = 0 then exit;
  1030.  
  1031.   TotalPaneSize := 0;
  1032.   For curPane := 0 to NumPanes-1 do
  1033.   begin
  1034.     Case Orientation of
  1035.       swHorizontal :
  1036.         Panes[curPane].FPaneSize := Round(Height/NumPanes);
  1037.       swVertical   :
  1038.         Panes[curPane].FPaneSize := Round(Width /NumPanes);
  1039.     end;
  1040.     TotalPaneSize := TotalPaneSize + TPane(FPanes[curPane]).PaneSize;
  1041.   end;
  1042.  
  1043.   AdjustLastPaneSize;
  1044.   { All errors have been accounted for, so now update the pane rectangles }
  1045.   UpdatePaneRects;
  1046. end;
  1047.  
  1048. // procedure TSplitterWnd.GetChildren( Proc : TGetChildProc );
  1049. //   I don't know why this is in here, but it has to be
  1050. //   to avoid access violations at design time when loading
  1051. //   panes.  Oh, well.  It works.
  1052. procedure TSplitterWnd.GetChildren( Proc : TGetChildProc );
  1053. var
  1054.   I: Integer;
  1055. begin
  1056.   for I := 0 to FPanes.Count - 1 do Proc(TComponent(FPanes[I]));
  1057. end;
  1058.  
  1059. // procedure TSplitterWnd.SetChildOrder(Child: TComponent; Order: Integer);
  1060. //   Just sets the child order.  I am not sure it even needs to be here.
  1061. procedure TSplitterWnd.SetChildOrder(Child: TComponent; Order: Integer);
  1062. begin
  1063.   TPane(Child).PaneIndex := Order;
  1064. end;
  1065.  
  1066. (*************************************************************************)
  1067. (************************** TSplitterWndEditor ***************************)
  1068. (*************************************************************************)
  1069. procedure TSplitterWndEditor.ExecuteVerb(Index: Integer);
  1070. var
  1071.   SplitterWnd : TSplitterWnd;
  1072.   Pane        : TPane;
  1073.   Designer    : TFormDesigner;
  1074. begin
  1075.   // Make sure that the context menu will still work when clicking on a pane
  1076.   if Component is TSplitterWnd
  1077.     then SplitterWnd := TSplitterWnd(Component)
  1078.     else SplitterWnd := TPane(Component).SplitterWnd;
  1079.   Designer := Self.Designer;
  1080.   if Index = 0 then
  1081.   begin
  1082.     if SplitterWnd <> nil
  1083.     then begin
  1084.       // Create a new pane.
  1085.       Pane := TPane.Create(Designer.Form);
  1086.       try
  1087.         Pane.Name := Designer.UniqueName(TPane.ClassName);
  1088.         Pane.Parent := SplitterWnd;
  1089.         Pane.SplitterWnd := SplitterWnd;
  1090.         Pane.FPaneSize := SplitterWnd.GetDefaultPaneSize;
  1091.         SplitterWnd.RecalculatePaneSizes(False);
  1092.       except
  1093.         Pane.Free;
  1094.         raise;
  1095.       end;
  1096.       // Select the new pane
  1097.       Designer.SelectComponent(Pane);
  1098.       // Make the save icon change colors
  1099.       Designer.Modified;
  1100.     end;
  1101.   end
  1102.   // Set all of the pane sizes the same
  1103.   else if Index = 1 then
  1104.   begin
  1105.     SplitterWnd.ResetPaneSizes;
  1106.   end;
  1107. end;
  1108.  
  1109. function TSplitterWndEditor.GetVerb(Index: Integer): string;
  1110. begin
  1111.   Result := SplitterWndVerbs[Index];
  1112. end;
  1113.  
  1114. function TSplitterWndEditor.GetVerbCount: Integer;
  1115. begin
  1116.   Result := SplitterWndNumVerbs;
  1117. end;
  1118.  
  1119. (*************************************************************************)
  1120. (***************************** Register **********************************)
  1121. (*************************************************************************)
  1122. procedure Register;
  1123. begin
  1124.   RegisterComponents('Added', [TSplitterWnd]);
  1125.   RegisterClasses([TPane]);
  1126.  
  1127.   RegisterComponentEditor(TSplitterWnd, TSplitterWndEditor);
  1128.   RegisterComponentEditor(TPane, TSplitterWndEditor);
  1129. end;
  1130.  
  1131. end.
  1132.