home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue33 / undo / GraphWin.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-02-17  |  10.4 KB  |  385 lines

  1. unit GraphWin;
  2.  
  3. { This unit contains code for an undoable graphics window.
  4.  
  5.   Author : Warren Kovach (wlk@kovcomp.co.uk)
  6.   Published in The Delphi Magazine }
  7.  
  8. interface
  9.  
  10. uses wintypes, winprocs, Classes, Graphics, Forms, Controls, Undo, ExtCtrls, Buttons,
  11.      SysUtils, Dialogs;
  12.  
  13. {$IFDEF WIN32}
  14.   {$IFDEF VER90}
  15. const
  16.   {$ELSE}
  17. resourcestring
  18.   {$ENDIF}
  19. {$ELSE}
  20. const
  21. {$ENDIF}
  22.   sLine = 'line';
  23.   sEllipse = 'ellipse';
  24.   sRound = 'rounded rectangle';
  25.   sRectangle = 'rectangle';
  26.   sShapeUndoDescr = 'Undo last %s drawn';
  27.   sShapeShortUndoDescr = 'Undo %s';
  28.   sShaperedoDescr = 'Redo last %s drawn';
  29.   sShapeShortRedoDescr = 'Redo %s';
  30.   sShapeUndoMenu = '&Undo %s';
  31.   sShapeRedoMenu = '&Redo %s';
  32.  
  33.  
  34. type
  35.   TDrawingTool = (dtLine, dtRectangle, dtEllipse, dtRoundRect);
  36.  
  37.   TDrawShapeUndoItem = class(TUndoItem)
  38.     private
  39.       Location : TRect;
  40.       DrawingTool : TDrawingTool;
  41.       Canvas : TCanvas;
  42.     protected
  43.       procedure DoDrawing; virtual;
  44.       function ShapeString : string; virtual;
  45.       function GetUndoDescription : string; override;
  46.       function GetShortUndoDescription : string; override;
  47.       function GetRedoDescription : string; override;
  48.       function GetShortRedoDescription : string; override;
  49.       function GetUndoMenuText : string; override;
  50.       function GetRedoMenuText : string; override;
  51.     public
  52.       constructor Create(ACanvas : TCanvas; ALocation : TRect;
  53.                          ATool : TDrawingTool);
  54.       procedure DoCommand; override;
  55.       procedure Undo; override;
  56.       procedure Redo; override;
  57.   end;
  58.  
  59.   TDrawLineUndoItem = class(TDrawShapeUndoItem)
  60.     private
  61.       PointList : TList;
  62.       procedure AddPointToList(P : TPoint);
  63.     protected
  64.       procedure DoDrawing; override;
  65.       function ShapeString : string; override;
  66.     public
  67.       constructor Create(ACanvas : TCanvas; ALocation : TRect;
  68.                          ATool : TDrawingTool);
  69.       destructor Destroy; override;
  70.       procedure AddSegment(NextPoint : TPoint);
  71.       procedure Undo; override;
  72.       procedure Redo; override;
  73.   end;
  74.  
  75.   TGraphicWindow = class(TUndoForm)
  76.     Panel1: TPanel;
  77.     LineButton: TSpeedButton;
  78.     RectangleButton: TSpeedButton;
  79.     EllipseButton: TSpeedButton;
  80.     RoundRectButton: TSpeedButton;
  81.     Image1: TImage;
  82.     procedure FormClose(Sender: TObject; var Action: TCloseAction);
  83.     procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
  84.       Shift: TShiftState; X, Y: Integer);
  85.     procedure EllipseButtonClick(Sender: TObject);
  86.     procedure LineButtonClick(Sender: TObject);
  87.     procedure RectangleButtonClick(Sender: TObject);
  88.     procedure RoundRectButtonClick(Sender: TObject);
  89.     procedure FormCreate(Sender: TObject);
  90.     procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  91.       Y: Integer);
  92.     procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
  93.       Shift: TShiftState; X, Y: Integer);
  94.   private
  95.     { Private declarations }
  96.     DrawingTool: TDrawingTool;
  97.     MouseMoved,
  98.     LineDrawing : boolean;
  99.   public
  100.     { Public declarations }
  101.   end;
  102.  
  103. var
  104.   GraphicWindow : TGraphicWindow;
  105.  
  106. implementation
  107.  
  108. {$R *.DFM}
  109.  
  110. constructor TDrawShapeUndoItem.Create(ACanvas : TCanvas; ALocation : TRect;
  111.                                       ATool : TDrawingTool);
  112. begin
  113.   inherited Create;
  114.   Location := ALocation;
  115.   DrawingTool := ATool;
  116.   Canvas := ACanvas;
  117. end;
  118.  
  119. procedure TDrawShapeUndoItem.DoDrawing;
  120. begin
  121.   with Canvas,Location do begin
  122.     Pen.Mode := pmNotXOR;
  123.     case DrawingTool of
  124.       dtLine: begin
  125.                 MoveTo(Right,Top);
  126.                 LineTo(Left,Bottom);
  127.               end;
  128.       dtRectangle: Rectangle(Left,Top,Right,Bottom);
  129.       dtEllipse:   Ellipse(Left,Top,Right,Bottom);
  130.       dtRoundRect: RoundRect(Left,Top,Right,Bottom,
  131.                             (Left-Right) div 2,(Top-Bottom) div 2);
  132.     end;
  133.   end;
  134. end;
  135.  
  136. procedure TDrawShapeUndoItem.DoCommand;
  137. begin
  138.   DoDrawing;
  139. end;
  140.  
  141. procedure TDrawShapeUndoItem.Undo;
  142. begin
  143.   DoDrawing;
  144. end;
  145.  
  146. procedure TDrawShapeUndoItem.Redo;
  147. begin
  148.   DoCommand;
  149. end;
  150.  
  151. function TDrawShapeUndoItem.ShapeString : string;
  152. begin
  153.   case DrawingTool of
  154.     dtLine: Result := sLine;
  155.     dtRectangle: Result := sRectangle;
  156.     dtEllipse:   Result := sEllipse;
  157.     dtRoundRect: Result := sRound;
  158.   end;
  159. end;
  160.  
  161. function TDrawShapeUndoItem.GetUndoDescription : string;
  162. begin
  163.   Result := Format(sShapeUndoDescr,[ShapeString]);
  164. end;
  165.  
  166. function TDrawShapeUndoItem.GetShortUndoDescription : string;
  167. begin
  168.   Result := Format(sShapeShortUndoDescr,[ShapeString]);
  169. end;
  170.  
  171. function TDrawShapeUndoItem.GetRedoDescription : string;
  172. begin
  173.   Result := Format(sShapeRedoDescr,[ShapeString]);
  174. end;
  175.  
  176. function TDrawShapeUndoItem.GetShortRedoDescription : string;
  177. begin
  178.   Result := Format(sShapeShortRedoDescr,[ShapeString]);
  179. end;
  180.  
  181. function TDrawShapeUndoItem.GetUndoMenuText : string;
  182. begin
  183.   Result := Format(sShapeUndoMenu,[ShapeString]);
  184. end;
  185.  
  186. function TDrawShapeUndoItem.GetRedoMenuText : string;
  187. begin
  188.   Result := Format(sShapeRedoMenu,[ShapeString]);
  189. end;
  190. { -------------------------------------------------------- }
  191.  
  192. constructor TDrawLineUndoItem.Create(ACanvas : TCanvas; ALocation : TRect;
  193.                                      ATool : TDrawingTool);
  194. begin
  195.   inherited Create(ACanvas,ALocation,ATool);
  196.   PointList := TList.Create;
  197. end;
  198.  
  199. destructor TDrawLineUndoItem.Destroy;
  200. begin
  201.   PointList.Free;
  202.   inherited Destroy;
  203. end;
  204.  
  205. procedure TDrawLineUndoItem.AddPointToList(P : TPoint);
  206. { we can add 4-byte values to a TList by typecasting them
  207.   as pointers, then typecasting back to native type when
  208.   read back. The x and y of a 16-bit TPoint are integers
  209.   (2 bytes each) whereas in 32-bit they are longints (4
  210.   bytes) so they must be done separately }
  211. begin
  212. {$IFDEF Win32}
  213.   PointList.Add(pointer(p.x));
  214.   PointList.Add(pointer(p.y));
  215. {$ELSE}
  216.   PointList.Add(pointer(p));
  217. {$ENDIF}
  218. end;
  219.  
  220. procedure TDrawLineUndoItem.DoDrawing;
  221. begin
  222.   with Canvas,Location do begin
  223.     Pen.Mode := pmNotXOR;
  224.     MoveTo(Left,Top);
  225.     AddPointToList(TopLeft);
  226.   end;
  227. end;
  228.  
  229. procedure TDrawLineUndoItem.AddSegment(NextPoint : TPoint);
  230. begin
  231.   with Canvas do begin
  232.     Pen.Mode := pmNotXOR;
  233.     LineTo(NextPoint.x,NextPoint.y);
  234.   end;
  235.   AddPointToList(NextPoint);
  236. end;
  237.  
  238. procedure TDrawLineUndoItem.Undo;
  239. var
  240.   i : integer;
  241.   P : pointer;
  242. {$IFDEF Win32}
  243.   Point : TPoint;
  244. {$ENDIF}
  245. begin
  246. { a faster method for redoing is to store points in an array
  247.   of TPoints, then pass that to Polyline. Unfortunately, it is
  248.   difficult to do that in a dynamic fashion, creating
  249.   an array just large enough to hold the points. Can use the method
  250.   of declaring a pointer to an array type of the maximum size
  251.   (8192 under Delphi1),
  252.   then using GetMem to allocate just enough memory. Unfortunately,
  253.   if this is passed to Polygon it thinks it is suposed to use the
  254.   full 8192 elements, not just those allocated. Can use Slice to
  255.   make array just the right size, but this isn't available in Delphi 1.
  256.   In the end, TList is easier since it automatically grows in size when
  257.   needed, whereas with our own dynamically allocated array we
  258.   would have to take care of resizing ourselves, since we don't
  259.   know how big we will need to be until mouse button is released.
  260. }
  261.   with Canvas do begin
  262.     Pen.Mode := pmNotXOR;
  263. {$IFDEF Win32}
  264.     { can't typecast property directly, must copy to a variable }
  265.     P := PointList[0];
  266.     Point.X := Longint(P);
  267.     P := PointList[1];
  268.     Point.y := Longint(P);
  269.     with Point do Moveto(x,y);
  270.     for i := 2 to pred(PointList.Count) do begin
  271.       P := PointList[i];
  272.       if Odd(i) then begin
  273.         Point.y := Longint(P);
  274.         with Point do LineTo(x,y);
  275.       end
  276.       else
  277.         Point.X := Longint(P);
  278.     end;
  279. {$ELSE}
  280.     P := PointList[0];
  281.     with TPoint(P) do Moveto(x,y);
  282.     for i := 1 to pred(PointList.Count) do begin
  283.       P := PointList[i];
  284.       with TPoint(P) do LineTo(x,y);
  285.     end;
  286. {$ENDIF}
  287.   end;
  288. end;
  289.  
  290. procedure TDrawLineUndoItem.Redo;
  291. begin
  292.   Undo;
  293. end;
  294.  
  295. function TDrawLineUndoItem.ShapeString : string;
  296. begin
  297.   Result := sLine;
  298. end;
  299.  
  300. { -------------------------------------------------------- }
  301. procedure TGraphicWindow.FormCreate(Sender: TObject);
  302. begin
  303.   LineDrawing := false;
  304.   MouseMoved := false;
  305. end;
  306.  
  307. procedure TGraphicWindow.FormClose(Sender: TObject; var Action: TCloseAction);
  308. begin
  309.   Action := caFree;
  310. end;
  311.  
  312. procedure TGraphicWindow.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  313.   Shift: TShiftState; X, Y: Integer);
  314. var
  315.   Item : TUndoItem;
  316. begin
  317.   MouseMoved := false;
  318.   Case Button of
  319.     mbLeft  : begin
  320.                 if DrawingTool = dtLine then begin
  321.                   LineDrawing := true;
  322.                   Item := TDrawLineUndoItem.Create(Image1.Canvas,
  323.                                 Rect(x,y,x,y),DrawingTool);
  324.                 end
  325.                 else
  326.                   Item := TDrawShapeUndoItem.Create(Image1.Canvas,
  327.                                 Rect(x,y,x+50,y+50),DrawingTool);
  328.                 if UndoStack.Submit(Item) = ssFull then
  329.                   ShowMessage(Format(sStackFull,[UndoStack.MaxItems]));
  330.               end;
  331.  
  332.     mbMiddle : begin
  333.                 UndoStack.Redo(1);
  334.                end;
  335.     mbRight : begin
  336.                 UndoStack.Undo(1);
  337.               end;
  338.   end;
  339. end;
  340.  
  341. procedure TGraphicWindow.Image1MouseMove(Sender: TObject; Shift: TShiftState;
  342.   X, Y: Integer);
  343. begin
  344.   if LineDrawing then begin
  345.     MouseMoved := true;
  346.     with UndoStack.CurrentItem as TDrawLineUndoItem do
  347.       AddSegment(Point(x,y));
  348.   end;
  349. end;
  350.  
  351. procedure TGraphicWindow.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  352.   Shift: TShiftState; X, Y: Integer);
  353. begin
  354.   if LineDrawing and (Button = mbLeft) then begin
  355.     if MouseMoved = false then
  356.       UndoStack.RemoveLastItem
  357.     else with UndoStack.CurrentItem as TDrawLineUndoItem do
  358.       AddSegment(Point(x,y));
  359.     LineDrawing := false;
  360.     MouseMoved := false;
  361.   end;
  362. end;
  363.  
  364. procedure TGraphicWindow.EllipseButtonClick(Sender: TObject);
  365. begin
  366.   DrawingTool := dtEllipse;
  367. end;
  368.  
  369. procedure TGraphicWindow.LineButtonClick(Sender: TObject);
  370. begin
  371.   DrawingTool := dtLine;
  372. end;
  373.  
  374. procedure TGraphicWindow.RectangleButtonClick(Sender: TObject);
  375. begin
  376.   DrawingTool := dtRectangle;
  377. end;
  378.  
  379. procedure TGraphicWindow.RoundRectButtonClick(Sender: TObject);
  380. begin
  381.   DrawingTool := dtRoundRect;
  382. end;
  383.  
  384. end.
  385.