home *** CD-ROM | disk | FTP | other *** search
/ Programming Languages Suite / ProgramD2.iso / Borland / Borland Pascal with Objects 7.0 / OLE.ZIP / OLECLNT.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1992-10-27  |  35.0 KB  |  1,260 lines

  1. {**************************************************}
  2. {                                                  }
  3. {   Object Linking and Embedding demo program      }
  4. {   Copyright (c) 1992 by Borland International    }
  5. {                                                  }
  6. {**************************************************}
  7.  
  8. program OleClnt;
  9.  
  10. { This program demonstrates how to implement an OLE client application.
  11.   The program uses the new Ole, ShellAPI, and CommDlg units, and requires
  12.   that the OLECLI.DLL, SHELL.DLL, and COMMDLG.DLL libraries are present.
  13.   The program allows you to create embedded and linked objects using the
  14.   Edit|Paste and Edit|Paste link commands. The OLE objects can be moved
  15.   and resized, and they can be activated through double clicks or using
  16.   the Edit|Object menu. Workspaces can be saved and loaded using the
  17.   File menu. }
  18.  
  19. uses Strings, WinTypes, WinProcs, Objects, OWindows, ODialogs, Ole, 
  20.   ShellAPI, CommDlg;
  21.  
  22. {$R OLECLNT}
  23.  
  24. const
  25.  
  26. { Resource IDs }
  27.  
  28.   id_Menu  = 100;
  29.   id_About = 100;
  30.  
  31. { Menu command IDs }
  32.  
  33.   cm_FileNew       = 100;
  34.   cm_FileOpen      = 101;
  35.   cm_FileSave      = 102;
  36.   cm_FileSaveAs    = 103;
  37.   cm_FileExit      = 104;
  38.   cm_EditCut       = 200;
  39.   cm_EditCopy      = 201;
  40.   cm_EditPaste     = 202;
  41.   cm_EditPasteLink = 203;
  42.   cm_EditClear     = 204;
  43.   cm_HelpAbout     = 300;
  44.   cm_VerbMin       = 900;
  45.   cm_VerbMax       = 999;
  46.  
  47. { Menu item positions }
  48.  
  49.   pos_Edit   = 1;  { Position of Edit item on main menu }
  50.   pos_Object = 6;  { Position of Object item on Edit menu }
  51.  
  52. type
  53.  
  54. { Pointer types }
  55.  
  56.   PAppClient    = ^TAppClient;
  57.   PAppStream    = ^TAppStream;
  58.   PObjectWindow = ^TObjectWindow;
  59.   PMainWindow   = ^TMainWindow;
  60.  
  61. { Filename string }
  62.  
  63.   TFilename = array[0..255] of Char;
  64.  
  65. { OLE file header }
  66.  
  67.   TOleFileHeader = array[1..4] of Char;
  68.  
  69. { Application client structure }
  70.  
  71.   TAppClient = record
  72.     OleClient: TOleClient;
  73.     ObjectWindow: PObjectWindow;
  74.   end;
  75.  
  76. { Application stream structure }
  77.  
  78.   TAppStream = record
  79.     OleStream: TOleStream;
  80.     OwlStream: PStream;
  81.   end;
  82.  
  83. { OLE object window }
  84.  
  85.   TObjectWindow = object(TWindow)
  86.     AppClient: TAppClient;
  87.     OleObject: POleObject;
  88.     Framed: Boolean;
  89.     constructor Init(Link: Boolean);
  90.     constructor Load(var S: TStream);
  91.     destructor Done; virtual;
  92.     function GetClassName: PChar; virtual;
  93.     procedure GetWindowClass(var AWndClass: TWndClass); virtual;
  94.     procedure SetupWindow; virtual;
  95.     procedure Store(var S: TStream); virtual;
  96.     function CanClose: Boolean; virtual;
  97.     procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
  98.     procedure Check(OleStatus: TOleStatus);
  99.     procedure GetObjectClass(ClassName: PChar);
  100.     function IsLinked: Boolean;
  101.     procedure Update;
  102.     procedure OpenObject(Verb: Word);
  103.     procedure CloseObject;
  104.     procedure CopyToClipboard;
  105.     procedure Delete;
  106.     procedure Changed;
  107.     procedure BringToFront;
  108.     procedure GetBounds(var R: TRect);
  109.     procedure SetBounds(var R: TRect);
  110.     procedure ShowFrame(EnableFrame: Boolean);
  111.     procedure WMGetMinMaxInfo(var Msg: TMessage);
  112.       virtual wm_First + wm_GetMinMaxInfo;
  113.     procedure WMMove(var Msg: TMessage);
  114.       virtual wm_First + wm_Move;
  115.     procedure WMSize(var Msg: TMessage);
  116.       virtual wm_First + wm_Size;
  117.     procedure WMLButtonDown(var Msg: TMessage);
  118.       virtual wm_First + wm_LButtonDown;
  119.     procedure WMMouseMove(var Msg: TMessage);
  120.       virtual wm_First + wm_MouseMove;
  121.     procedure WMLButtonUp(var Msg: TMessage);
  122.       virtual wm_First + wm_LButtonUp;
  123.     procedure WMLButtonDblClk(var Msg: TMessage);
  124.       virtual wm_First + wm_LButtonDblClk;
  125.   end;
  126.  
  127. { Application main window }
  128.  
  129.   TMainWindow = object(TWindow)
  130.     ObjectWindow: PObjectWindow;
  131.     ClientDoc: LHClientDoc;
  132.     Modified: Boolean;
  133.     Filename: TFilename;
  134.     constructor Init;
  135.     destructor Done; virtual;
  136.     function CanClose: Boolean; virtual;
  137.     procedure SetupWindow; virtual;
  138.     procedure InitDocument;
  139.     procedure DoneDocument;
  140.     procedure UpdateDocument;
  141.     procedure SetFilename(Name: PChar);
  142.     function NewFile(Name: PChar): Boolean;
  143.     function LoadFile: Boolean;
  144.     function SaveFile: Boolean;
  145.     function Save: Boolean;
  146.     function SaveAs: Boolean;
  147.     procedure NewObjectWindow(Link: Boolean);
  148.     procedure SelectWindow(Window: PObjectWindow);
  149.     procedure UpdateObjectMenu;
  150.     procedure WMLButtonDown(var Msg: TMessage);
  151.       virtual wm_First + wm_LButtonDown;
  152.     procedure WMInitMenu(var Msg: TMessage);
  153.       virtual wm_First + wm_InitMenu;
  154.     procedure CMFileNew(var Msg: TMessage);
  155.       virtual cm_First + cm_FileNew;
  156.     procedure CMFileOpen(var Msg: TMessage);
  157.       virtual cm_First + cm_FileOpen;
  158.     procedure CMFileSave(var Msg: TMessage);
  159.       virtual cm_First + cm_FileSave;
  160.     procedure CMFileSaveAs(var Msg: TMessage);
  161.       virtual cm_First + cm_FileSaveAs;
  162.     procedure CMFileExit(var Msg: TMessage);
  163.       virtual cm_First + cm_FileExit;
  164.     procedure CMEditCut(var Msg: TMessage);
  165.       virtual cm_First + cm_EditCut;
  166.     procedure CMEditCopy(var Msg: TMessage);
  167.       virtual cm_First + cm_EditCopy;
  168.     procedure CMEditPaste(var Msg: TMessage);
  169.       virtual cm_First + cm_EditPaste;
  170.     procedure CMEditPasteLink(var Msg: TMessage);
  171.       virtual cm_First + cm_EditPasteLink;
  172.     procedure CMEditClear(var Msg: TMessage);
  173.       virtual cm_First + cm_EditClear;
  174.     procedure CMHelpAbout(var Msg: TMessage);
  175.       virtual cm_First + cm_HelpAbout;
  176.     procedure DefCommandProc(var Msg: TMessage); virtual;
  177.   end;
  178.  
  179. { Application object }
  180.  
  181.   TApp = object(TApplication)
  182.     constructor Init(AName: PChar);
  183.     destructor Done; virtual;
  184.     procedure InitMainWindow; virtual;
  185.   end;
  186.  
  187. { Initialized globals }
  188.  
  189. const
  190.   Dragging: Boolean = False;
  191.   OleFileHeader: TOleFileHeader = 'TPOF';
  192.   OleProtocol: PChar = 'StdFileEditing';
  193.   OleObjectName: PChar = 'Object';
  194.   OleClntTitle: PChar = 'OLE Client Demo';
  195.  
  196. { Global variables }
  197.  
  198. var
  199.   App: TApp;
  200.   DragPoint: TPoint;
  201.   MainWindow: PMainWindow;
  202.   OleClientVTbl: TOleClientVTbl;
  203.   OleStreamVTbl: TOleStreamVTbl;
  204.   PixPerInch: TPoint;
  205.   CFObjectLink, CFOwnerLink: Word;
  206.  
  207. { TObjectWindow stream registration record }
  208.  
  209. const
  210.   RObjectWindow: TStreamRec = (
  211.     ObjType: 999;
  212.     VmtLink: Ofs(TypeOf(TObjectWindow)^);
  213.     Load: @TObjectWindow.Load;
  214.     Store: @TObjectWindow.Store);
  215.  
  216. { Display a message using the MessageBox API routine. }
  217.  
  218. function Message(S: PChar; Flags: Word): Word;
  219. begin
  220.   Message := MessageBox(MainWindow^.HWindow, S, OleClntTitle, Flags);
  221. end;
  222.  
  223. { Display an error message. }
  224.  
  225. procedure Error(ErrorStr, ErrorArg: PChar);
  226. var
  227.   S: array[0..255] of Char;
  228. begin
  229.   wvsprintf(S, ErrorStr, ErrorArg);
  230.   Message(S, mb_IconExclamation + mb_Ok);
  231. end;
  232.  
  233. { Display OLE operation error message. }
  234.  
  235. procedure OleError(Status: TOleStatus);
  236. var
  237.   S: array[0..7] of Char;
  238. begin
  239.   wvsprintf(S, '%d', Status);
  240.   Error('Warning: OLE operation failed, error code = %s.', S);
  241. end;
  242.  
  243. { Display an Open or Save As file dialog using the Common Dialog DLL. }
  244.  
  245. function FileDialog(Owner: HWnd; Filename: PChar; Save: Boolean): Boolean;
  246. const
  247.   DefOpenFilename: TOpenFilename = (
  248.     lStructSize: SizeOf(TOpenFilename);
  249.     hwndOwner: 0;
  250.     hInstance: 0;
  251.     lpstrFilter: 'OLE files (*.OLE)'#0'*.ole'#0;
  252.     lpstrCustomFilter: nil;
  253.     nMaxCustFilter: 0;
  254.     nFilterIndex: 0;
  255.     lpstrFile: nil;
  256.     nMaxFile: SizeOf(TFilename);
  257.     lpstrFileTitle: nil;
  258.     nMaxFileTitle: 0;
  259.     lpstrInitialDir: nil;
  260.     lpstrTitle: nil;
  261.     Flags: 0;
  262.     nFileOffset: 0;
  263.     nFileExtension: 0;
  264.     lpstrDefExt: 'ole');
  265. var
  266.   OpenFilename: TOpenFilename;
  267. begin
  268.   OpenFilename := DefOpenFilename;
  269.   OpenFilename.hwndOwner := Owner;
  270.   OpenFilename.lpstrFile := Filename;
  271.   if Save then
  272.   begin
  273.     OpenFilename.Flags := ofn_PathMustExist + ofn_NoChangeDir +
  274.       ofn_OverwritePrompt;
  275.     FileDialog := GetSaveFilename(OpenFilename);
  276.   end else
  277.   begin
  278.     OpenFileName.Flags := ofn_PathMustExist + ofn_HideReadOnly;
  279.     FileDialog := GetOpenFilename(OpenFilename);
  280.   end;
  281. end;
  282.  
  283. { OLE client callback routine. Called by the OLE client library to notify
  284.   the application of any changes to an object. In this application, the
  285.   Client parameter is always a PAppClient, so a typecast can be used to
  286.   find the corresponding TObjectWindow. The OLE object window's Changed
  287.   method is called whenever the contained OLE object is changed, saved,
  288.   or renamed. The callback routine returns 1 to satisfy ole_Query_Paint
  289.   and ole_Query_Retry notifications. }
  290.  
  291. function ClientCallBack(Client: POleClient; Notification:
  292.   TOle_Notification; OleObject: POleObject): Integer; export;
  293. begin
  294.   ClientCallBack := 1;
  295.   case Notification of
  296.     ole_Changed, ole_Saved, ole_Renamed:
  297.       PAppClient(Client)^.ObjectWindow^.Changed;
  298.   end;
  299. end;
  300.  
  301. { Selector increment. This is not a true procedure. Instead, it is an
  302.   external symbol whose offset represents the value to add to a selector
  303.   to increment a pointer by 64K bytes. }
  304.  
  305. procedure AHIncr; far; external 'KERNEL' index 114;
  306.  
  307. { Read or write to or from a stream. This function supports transfers of
  308.   blocks larger than 64K bytes. It guards against segment overruns, and
  309.   transfers data in blocks of up to 32K bytes. }
  310.  
  311. function StreamInOut(var S: TStream; Buffer: Pointer; Size: Longint;
  312.   Writing: Boolean): Longint;
  313. var
  314.   N: Longint;
  315. begin
  316.   StreamInOut := Size;
  317.   while Size <> 0 do
  318.   begin
  319.     N := $10000 - PtrRec(Buffer).Ofs;
  320.     if N > $8000 then N := $8000;
  321.     if N > Size then N := Size;
  322.     if Writing then S.Write(Buffer^, N) else S.Read(Buffer^, N);
  323.     Inc(PtrRec(Buffer).Ofs, N);
  324.     if PtrRec(Buffer).Ofs = 0 then Inc(PtrRec(Buffer).Seg, Ofs(AHIncr));
  325.     Dec(Size, N);
  326.   end;
  327.   if S.Status <> 0 then StreamInOut := 0;
  328. end;
  329.  
  330. { OLE stream read callback function. In this application, the Stream
  331.   parameter is always a PAppStream, so a typecast can be used to find the
  332.   corresponding ObjectWindows stream. }
  333.  
  334. function StreamGet(Stream: POleStream; Buffer: PChar;
  335.   Size: LongInt): LongInt; export;
  336. begin
  337.   StreamGet := StreamInOut(PAppStream(Stream)^.OwlStream^,
  338.     Buffer, Size, False);
  339. end;
  340.  
  341. { OLE stream write callback function. In this application, the Stream
  342.   parameter is always a PAppStream, so a typecast can be used to find the
  343.   corresponding ObjectWindows stream. }
  344.  
  345. function StreamPut(Stream: POleStream; Buffer: PChar;
  346.   Size: LongInt): LongInt; export;
  347. begin
  348.   StreamPut := StreamInOut(PAppStream(Stream)^.OwlStream^,
  349.     Buffer, Size, True);
  350. end;
  351.  
  352. { TObjectWindow methods }
  353.  
  354. { Construct an OLE object window. The AppClient structure is initialized
  355.   to reference the newly created TObjectWindow so that the ClientCallBack
  356.   routine can later locate it when notifications are received. If the OLE
  357.   object is successfully created, its bounds are queried to determine the
  358.   initial bounds of the OLE object window. Notice that the bounds are
  359.   returned in mm_HiMetric units, which are converted to mm_Text units. }
  360.  
  361. constructor TObjectWindow.Init(Link: Boolean);
  362. var
  363.   R: TRect;
  364.   Cursor: HCursor;
  365. begin
  366.   TWindow.Init(MainWindow, nil);
  367.   Attr.Style := ws_Child + ws_ClipSiblings;
  368.   AppClient.OleClient.lpvtbl := @OleClientVTbl;
  369.   AppClient.ObjectWindow := @Self;
  370.   OleObject := nil;
  371.   Framed := False;
  372.   Cursor := SetCursor(LoadCursor(0, idc_Wait));
  373.   if Link then
  374.     Check(OleCreateLinkFromClip(OleProtocol, @AppClient.OleClient,
  375.       MainWindow^.ClientDoc, OleObjectName, OleObject,
  376.       olerender_Draw, 0))
  377.   else
  378.     Check(OleCreateFromClip(OleProtocol, @AppClient.OleClient,
  379.       MainWindow^.ClientDoc, OleObjectName, OleObject,
  380.       olerender_Draw, 0));
  381.   SetCursor(Cursor);
  382.  
  383.   if OleObject = nil then Status := -1 else
  384.   begin
  385.     OleQueryBounds(OleObject, R);
  386.     Attr.X := 0;
  387.     Attr.Y := 0;
  388.     Attr.W := MulDiv(R.right, PixPerInch.X, 2540);
  389.     Attr.H := MulDiv(-R.bottom, PixPerInch.Y, 2540);
  390.   end;
  391. end;
  392.  
  393. { Load an OLE object window from a stream. Loads the contained OLE object
  394.   from the stream, using a TAppStream for I/O. }
  395.  
  396. constructor TObjectWindow.Load(var S: TStream);
  397. var
  398.   ObjectType: Longint;
  399.   AppStream: TAppStream;
  400. begin
  401.   TWindow.Load(S);
  402.   AppClient.OleClient.lpvtbl := @OleClientVTbl;
  403.   AppClient.ObjectWindow := @Self;
  404.   OleObject := nil;
  405.   Framed := False;
  406.   AppStream.OleStream.lpstbl := @OleStreamVTbl;
  407.   AppStream.OwlStream := @S;
  408.   Check(OleLoadFromStream(@AppStream.OleStream, OleProtocol,
  409.     @AppClient.OleClient, MainWindow^.ClientDoc, OleObjectName,
  410.     OleObject));
  411.   if OleObject = nil then Status := -1;
  412. end;
  413.  
  414. { Destroy an OLE object window. Closes and releases the contained OLE
  415.   object. }
  416.  
  417. destructor TObjectWindow.Done;
  418. begin
  419.   if OleObject <> nil then
  420.   begin
  421.     CloseObject;
  422.     Check(OleRelease(OleObject));
  423.   end;
  424.   TWindow.Done;
  425. end;
  426.  
  427. { Return the OLE object window class name }
  428.  
  429. function TObjectWindow.GetClassName: PChar;
  430. begin
  431.   GetClassName := 'OleWindow';
  432. end;
  433.  
  434. { Return the OLE object window class structure. Enables double click
  435.   processing. }
  436.  
  437. procedure TObjectWindow.GetWindowClass(var AWndClass: TWndClass);
  438. begin
  439.   TWindow.GetWindowClass(AWndClass);
  440.   AWndClass.Style := AWndClass.Style or cs_DblClks;
  441. end;
  442.  
  443. { Initialize an OLE object window. Called following successful creation
  444.   of the MS-Windows window. The window is brought to front and shown. }
  445.  
  446. procedure TObjectWindow.SetupWindow;
  447. begin
  448.   TWindow.SetupWindow;
  449.   BringToFront;
  450.   ShowWindow(HWindow, sw_Show);
  451. end;
  452.  
  453. { Store an OLE object window on a stream. Stores the contained OLE object
  454.   on the stream, using a TAppStream for I/O. }
  455.  
  456. procedure TObjectWindow.Store(var S: TStream);
  457. var
  458.   AppStream: TAppStream;
  459. begin
  460.   TWindow.Store(S);
  461.   AppStream.OleStream.lpstbl := @OleStreamVTbl;
  462.   AppStream.OwlStream := @S;
  463.   Check(OleSaveToStream(OleObject, @AppStream.OleStream));
  464. end;
  465.  
  466. { Paint an OLE object window. The contained OLE object is instructed to
  467.   draw itself to fill the entire client area. }
  468.  
  469. procedure TObjectWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
  470. var
  471.   R: TRect;
  472. begin
  473.   GetClientRect(HWindow, R);
  474.   Check(OleDraw(OleObject, PaintDC, R, R, 0));
  475. end;
  476.  
  477. { Determine whether an OLE object window can close. If the contained OLE
  478.   object is currently open, the user must confirm before the window can
  479.   be closed. }
  480.  
  481. function TObjectWindow.CanClose: Boolean;
  482. begin
  483.   CanClose := True;
  484.   if OleQueryOpen(OleObject) = ole_Ok then
  485.     CanClose := Message('Object is currently open. Continue anyway?',
  486.       mb_IconExclamation + mb_OkCancel) = id_Ok;
  487. end;
  488.  
  489. { Check the status of an OLE operation. If an OLE operation returns
  490.   ole_Wait_For_Release, indicating that it is executing acsynchronously,
  491.   the Check method will enter a message loop, waiting for the OLE object
  492.   to be released by the server. }
  493.  
  494. procedure TObjectWindow.Check(OleStatus: TOleStatus);
  495. var
  496.   M: TMsg;
  497. begin
  498.   if OleStatus = ole_Wait_For_Release then
  499.   begin
  500.     repeat
  501.       OleStatus := OleQueryReleaseStatus(OleObject);
  502.       if OleStatus = ole_Busy then
  503.         if GetMessage(M, 0, 0, 0) then
  504.         begin
  505.           TranslateMessage(M);
  506.           DispatchMessage(M);
  507.         end;
  508.     until OleStatus <> ole_Busy;
  509.   end;
  510.   if OleStatus <> ole_Ok then OleError(OleStatus);
  511. end;
  512.  
  513. { Return the class name of the contained OLE object. The first string in
  514.   an OLE object's ObjectLink or OwnerLink data is the class name. }
  515.  
  516. procedure TObjectWindow.GetObjectClass(ClassName: PChar);
  517. var
  518.   H: THandle;
  519. begin
  520.   ClassName[0] := #0;
  521.   if (OleGetData(OleObject, CFObjectLink, H) = ole_Ok) or
  522.     (OleGetData(OleObject, CFOwnerLink, H) = ole_Ok) then
  523.   begin
  524.     StrCopy(ClassName, GlobalLock(H));
  525.     GlobalUnlock(H);
  526.   end;
  527. end;
  528.  
  529. { Return True if the contained OLE object is a linked object. }
  530.  
  531. function TObjectWindow.IsLinked: Boolean;
  532. var
  533.   ObjectType: Longint;
  534. begin
  535.   IsLinked := (OleQueryType(OleObject, ObjectType) = ole_Ok) and
  536.     (ObjectType = ot_Link);
  537. end;
  538.  
  539. { Update the contained OLE object. }
  540.  
  541. procedure TObjectWindow.Update;
  542. begin
  543.   Check(OleUpdate(OleObject));
  544. end;
  545.  
  546. { Open the contained OLE object. }
  547.  
  548. procedure TObjectWindow.OpenObject(Verb: Word);
  549. var
  550.   Cursor: HCursor;
  551. begin
  552.   Cursor := SetCursor(LoadCursor(0, idc_Wait));
  553.   Check(OleActivate(OleObject, Verb, True, True, 0, nil));
  554.   SetCursor(Cursor);
  555. end;
  556.  
  557. { Close the contained OLE object if it is open. }
  558.  
  559. procedure TObjectWindow.CloseObject;
  560. begin
  561.   if OleQueryOpen(OleObject) = ole_Ok then Check(OleClose(OleObject));
  562. end;
  563.  
  564. { Copy the contained OLE object to the clipboard. }
  565.  
  566. procedure TObjectWindow.CopyToClipboard;
  567. begin
  568.   Check(OleCopyToClipboard(OleObject));
  569. end;
  570.  
  571. { Delete an OLE object window. If the window is the main window's
  572.   current selection, it is unselected. The parent window is marked as
  573.   modified, and the contained OLE object is closed and deleted. }
  574.  
  575. procedure TObjectWindow.Delete;
  576. begin
  577.   with MainWindow^ do
  578.   begin
  579.     if ObjectWindow = @Self then SelectWindow(nil);
  580.     Modified := True;
  581.   end;
  582.   CloseObject;
  583.   Check(OleDelete(OleObject));
  584.   OleObject := nil;
  585.   Free;
  586. end;
  587.  
  588. { This method is called by the ClientCallBack routine whenever the
  589.   contained OLE object has changed. The client area of the OLE object
  590.   window is invalidated to force repainting, and the main window is
  591.   marked as modified. }
  592.  
  593. procedure TObjectWindow.Changed;
  594. begin
  595.   InvalidateRect(HWindow, nil, True);
  596.   MainWindow^.Modified := True;
  597. end;
  598.  
  599. { Bring an OLE object window to front. }
  600.  
  601. procedure TObjectWindow.BringToFront;
  602. begin
  603.   SetWindowPos(HWindow, 0, 0, 0, 0, 0, swp_NoMove + swp_NoSize);
  604. end;
  605.  
  606. { Return the bounds of an OLE object window using parent window
  607.   coordinates. The bounds include the window frame, if present. }
  608.  
  609. procedure TObjectWindow.GetBounds(var R: TRect);
  610. begin
  611.   GetWindowRect(HWindow, R);
  612.   ScreenToClient(Parent^.HWindow, PPoint(@R.left)^);
  613.   ScreenToClient(Parent^.HWindow, PPoint(@R.right)^);
  614. end;
  615.  
  616. { Set the bounds of an OLE object window within its parent window. }
  617.  
  618. procedure TObjectWindow.SetBounds(var R: TRect);
  619. begin
  620.   MoveWindow(HWindow, R.left, R.top,
  621.     R.right - R.left, R.bottom - R.top, True);
  622.   UpdateWindow(HWindow);
  623. end;
  624.  
  625. { Enable or disable an OLE object window's window frame. The frame is
  626.   added or removed by modifying the window's style flags and growing or
  627.   shrinking the window's bounds. }
  628.  
  629. procedure TObjectWindow.ShowFrame(EnableFrame: Boolean);
  630. const
  631.   Border = ws_Border + ws_ThickFrame;
  632. var
  633.   FX, FY: Integer;
  634.   Style: Longint;
  635.   R: TRect;
  636. begin
  637.   if EnableFrame <> Framed then
  638.   begin
  639.     Style := GetWindowLong(HWindow, gwl_Style);
  640.     FX := GetSystemMetrics(sm_CXFrame);
  641.     FY := GetSystemMetrics(sm_CYFrame);
  642.     GetBounds(R);
  643.     if EnableFrame then
  644.     begin
  645.       Style := Style or Border;
  646.       InflateRect(R, FX, FY);
  647.     end else
  648.     begin
  649.       Style := Style and not Border;
  650.       InflateRect(R, -FX, -FY);
  651.     end;
  652.     SetWindowLong(HWindow, gwl_Style, Style);
  653.     SetBounds(R);
  654.     Framed := EnableFrame;
  655.   end;
  656. end;
  657.  
  658. { wm_GetMinMaxInfo message handler. Modifies the minimum window size. }
  659.  
  660. procedure TObjectWindow.WMGetMinMaxInfo(var Msg: TMessage);
  661. type
  662.   PMinMaxInfo = ^TMinMaxInfo;
  663.   TMinMaxInfo = array[0..4] of TPoint;
  664. begin
  665.   PMinMaxInfo(Msg.LParam)^[3].X := 24;
  666.   PMinMaxInfo(Msg.LParam)^[3].Y := 24;
  667. end;
  668.  
  669. { wm_Move message handler. Updates the window location in the Attr field
  670.   and marks the main window as modified. }
  671.  
  672. procedure TObjectWindow.WMMove(var Msg: TMessage);
  673. begin
  674.   if (Attr.X <> Integer(Msg.LParamLo)) or
  675.     (Attr.Y <> Integer(Msg.LParamHi)) then
  676.   begin
  677.     Attr.X := Integer(Msg.LParamLo);
  678.     Attr.Y := Integer(Msg.LParamHi);
  679.     MainWindow^.Modified := True;
  680.   end;
  681. end;
  682.  
  683. { wm_Size message handler. Updates the window size in the Attr field and
  684.   marks the main window as modified. }
  685.  
  686. procedure TObjectWindow.WMSize(var Msg: TMessage);
  687. begin
  688.   if (Attr.W <> Msg.LParamLo) or (Attr.H <> Msg.LParamHi) then
  689.   begin
  690.     Attr.W := Msg.LParamLo;
  691.     Attr.H := Msg.LParamHi;
  692.     MainWindow^.Modified := True;
  693.   end;
  694. end;
  695.  
  696. { wm_LButtonDown message handler. Brings the window to front and selects
  697.   it, causing a frame to be drawn around the window. If a dragging
  698.   operation is not in effect, one is initiated by capturing the mouse
  699.   and recording the initial dragging location. }
  700.  
  701. procedure TObjectWindow.WMLButtonDown(var Msg: TMessage);
  702. begin
  703.   BringToFront;
  704.   MainWindow^.SelectWindow(@Self);
  705.   if not Dragging then
  706.   begin
  707.     Dragging := True;
  708.     SetCapture(HWindow);
  709.     DragPoint := TPoint(Msg.LParam);
  710.     ClientToScreen(HWindow, DragPoint);
  711.   end;
  712. end;
  713.  
  714. { wm_MouseMove message handler. If a dragging operation is in effect,
  715.   the window is moved and the client area of the parent window is
  716.   repainted. }
  717.  
  718. procedure TObjectWindow.WMMouseMove(var Msg: TMessage);
  719. var
  720.   P: TPoint;
  721.   R: TRect;
  722. begin
  723.   if Dragging then
  724.   begin
  725.     P := TPoint(Msg.LParam);
  726.     ClientToScreen(HWindow, P);
  727.     GetBounds(R);
  728.     OffsetRect(R, P.X - DragPoint.X, P.Y - DragPoint.Y);
  729.     SetBounds(R);
  730.     UpdateWindow(Parent^.HWindow);
  731.     DragPoint := P;
  732.   end;
  733. end;
  734.  
  735. { wm_LButtonUp message handler. Terminates a dragging operation. }
  736.  
  737. procedure TObjectWindow.WMLButtonUp(var Msg: TMessage);
  738. begin
  739.   if Dragging then
  740.   begin
  741.     ReleaseCapture;
  742.     Dragging := False;
  743.   end;
  744. end;
  745.  
  746. { wm_LButtonDblClk message handler. Opens the contained OLE object by
  747.   executing its primary verb. This is typically an 'Edit' or 'Play'
  748.   operation. }
  749.  
  750. procedure TObjectWindow.WMLButtonDblClk(var Msg: TMessage);
  751. begin
  752.   OpenObject(oleverb_Primary);
  753. end;
  754.  
  755. { TMainWindow methods }
  756.  
  757. { Construct the application's main window. Loads the main menu and
  758.   creates an OLE document. }
  759.  
  760. constructor TMainWindow.Init;
  761. var
  762.   P: PObjectWindow;
  763. begin
  764.   MainWindow := @Self;
  765.   TWindow.Init(nil, nil);
  766.   Attr.Menu := LoadMenu(HInstance, PChar(id_Menu));
  767.   ObjectWindow := nil;
  768. end;
  769.  
  770. { Destroy the application's main window. Destroys the contained OLE
  771.   document. }
  772.  
  773. destructor TMainWindow.Done;
  774. begin
  775.   DoneDocument;
  776.   TWindow.Done;
  777. end;
  778.  
  779. { Determine whether the main window can close. Checks whether the
  780.   contained OLE object windows can close, and then prompts the user if
  781.   any modifications have been made since the file was opened or saved. }
  782.  
  783. function TMainWindow.CanClose: Boolean;
  784. begin
  785.   CanClose := False;
  786.   if TWindow.CanClose then
  787.   begin
  788.     CanClose := True;
  789.     if Modified then
  790.       case Message('Save current changes?',
  791.         mb_IconExclamation + mb_YesNoCancel) of
  792.         id_Yes: CanClose := Save;
  793.         id_Cancel: CanClose := False;
  794.       end;
  795.   end;
  796. end;
  797.  
  798. { Set the initial file name to untitled }
  799.  
  800. procedure TMainWindow.SetupWindow;
  801. begin
  802.   inherited SetupWindow;
  803.   SetFilename('');
  804.   InitDocument;
  805. end; 
  806.  
  807. { Create the main window's OLE document. }
  808.  
  809. procedure TMainWindow.InitDocument;
  810. var
  811.   P: PChar;
  812. begin
  813.   P := Filename;
  814.   if P[0] = #0 then P := 'Untitled';
  815.   OleRegisterClientDoc('OleClntDemo', P, 0, ClientDoc);
  816.   Modified := False;
  817. end;
  818.  
  819. { Destroy the main window's OLE document. The contained OLE object
  820.   windows are destroyed before the document. }
  821.  
  822. procedure TMainWindow.DoneDocument;
  823.  
  824.   procedure FreeObjectWindow(P: PObjectWindow); far;
  825.   begin
  826.     P^.Free;
  827.   end;
  828.  
  829. begin
  830.   ForEach(@FreeObjectWindow);
  831.   OleRevokeClientDoc(ClientDoc);
  832. end;
  833.  
  834. { Update the main window's OLE document. Each object window is checked
  835.   to see if it contains a linked OLE object, and if so, the user is given
  836.   the option to update the link. }
  837.  
  838. procedure TMainWindow.UpdateDocument;
  839. var
  840.   Prompted, DoUpdate: Boolean;
  841.  
  842.   procedure UpdateObjectWindow(P: PObjectWindow); far;
  843.   begin
  844.     if P^.IsLinked then
  845.     begin
  846.       if not Prompted then
  847.       begin
  848.         DoUpdate := Message('This file contains linked objects.'#13 +
  849.           'Update links now?',
  850.           mb_IconExclamation + mb_YesNo) = id_Yes;
  851.         Prompted := True;
  852.       end;
  853.       if DoUpdate then P^.Update;
  854.     end;
  855.   end;
  856.  
  857. begin
  858.   Prompted := False;
  859.   ForEach(@UpdateObjectWindow);
  860. end;
  861.  
  862. { Set the name of the file in the main window. Updates the title of the
  863.   main window to include the base part of the filename. }
  864.  
  865. procedure TMainWindow.SetFilename(Name: PChar);
  866. var
  867.   Params: array[0..1] of PChar;
  868.   Title: array[0..63] of Char;
  869. begin
  870.   StrCopy(Filename, Name);
  871.   Params[0] := OleClntTitle;
  872.   if Name[0] = #0 then Params[1] := '(Untitled)' else
  873.   begin
  874.     Params[1] := StrRScan(Name, '\');
  875.     if Params[1] = nil then Params[1] := Name else Inc(Params[1]);
  876.   end;
  877.   wvsprintf(Title, '%s - %s', Params);
  878.   if hWindow <> 0 then SetCaption(Title);
  879. end;
  880.  
  881. { Load a file into the main window. If the file does not exist, a new
  882.   file is created. Otherwise, the file header is checked, and the
  883.   contained OLE object windows are read from the stream. }
  884.  
  885. function TMainWindow.LoadFile: Boolean;
  886. var
  887.   Header: TOleFileHeader;
  888.   S: TBufStream;
  889. begin
  890.   LoadFile := False;
  891.   S.Init(Filename, stOpenRead, 4096);
  892.   if S.Status = 0 then
  893.   begin
  894.     S.Read(Header, SizeOf(TOleFileHeader));
  895.     if Longint(Header) = Longint(OleFileHeader) then
  896.     begin
  897.       GetChildren(S);
  898.       if (S.Status = 0) and CreateChildren then
  899.       begin
  900.         UpdateDocument;
  901.         LoadFile := True;
  902.       end else
  903.         Error('Error reading file %s.', Filename);
  904.     end else
  905.       Error('File format error %s.', Filename);
  906.   end else
  907.     LoadFile := True;
  908.   S.Done;
  909. end;
  910.  
  911. { Save the file in the main window. The OLE client library is notified if
  912.   the file was successfully saved. }
  913.  
  914. function TMainWindow.SaveFile: Boolean;
  915. var
  916.   S: TBufStream;
  917. begin
  918.   SaveFile := False;
  919.   S.Init(Filename, stCreate, 4096);
  920.   if S.Status = 0 then
  921.   begin
  922.     S.Write(OleFileHeader, SizeOf(TOleFileHeader));
  923.     PutChildren(S);
  924.     if S.Status = 0 then
  925.     begin
  926.       OleSavedClientDoc(ClientDoc);
  927.       Modified := False;
  928.       SaveFile := True;
  929.     end else
  930.       Error('Error writing file %s.', Filename);
  931.   end else
  932.     Error('Error creating file %s.', Filename);
  933.   S.Done;
  934. end;
  935.  
  936. { Open a new or existing file. The current OLE document is destroyed, a
  937.   new document is created, and the file is loaded. }
  938.  
  939. function TMainWindow.NewFile(Name: PChar): Boolean;
  940. begin
  941.   DoneDocument;
  942.   SetFilename(Name);
  943.   InitDocument;
  944.   if Filename[0] <> #0 then NewFile := LoadFile else NewFile := True;
  945. end;
  946.  
  947. { Save the current file. If the file is untitled, prompt the user for a
  948.   name. }
  949.  
  950. function TMainWindow.Save: Boolean;
  951. begin
  952.   if Filename[0] = #0 then Save := SaveAs else Save := SaveFile;
  953. end;
  954.  
  955. { Save the current file under a new name. The OLE client library is
  956.   informed that the document has been renamed. }
  957.  
  958. function TMainWindow.SaveAs: Boolean;
  959. var
  960.   Name: TFilename;
  961. begin
  962.   SaveAs := False;
  963.   StrCopy(Name, Filename);
  964.   if FileDialog(HWindow, Name, True) then
  965.   begin
  966.     SetFilename(Name);
  967.     OleRenameClientDoc(ClientDoc, Name);
  968.     SaveAs := SaveFile;
  969.   end;
  970. end;
  971.  
  972. { Create a new OLE object window using data in the clipboard. The Link
  973.   parameter determines whether to create an embedded object or a linked
  974.   object. }
  975.  
  976. procedure TMainWindow.NewObjectWindow(Link: Boolean);
  977. begin
  978.   OpenClipboard(HWindow);
  979.   SelectWindow(PObjectWindow(Application^.MakeWindow(
  980.     New(PObjectWindow, Init(Link)))));
  981.   CloseClipboard;
  982. end;
  983.  
  984. { Select a given OLE object window. }
  985.  
  986. procedure TMainWindow.SelectWindow(Window: PObjectWindow);
  987. begin
  988.   if ObjectWindow <> Window then
  989.   begin
  990.     if ObjectWindow <> nil then ObjectWindow^.ShowFrame(False);
  991.     ObjectWindow := Window;
  992.     if ObjectWindow <> nil then ObjectWindow^.ShowFrame(True);
  993.   end;
  994. end;
  995.  
  996. { Update the Edit|Object menu. The Registration Database is queried to
  997.   find the readable version of the class name of the current OLE object,
  998.   along with the list of verbs supported by the class. If the class
  999.   supports more than one verb, the verbs are put on a popup submenu. }
  1000.  
  1001. procedure TMainWindow.UpdateObjectMenu;
  1002. var
  1003.   VerbFound: Boolean;
  1004.   VerbCount: Word;
  1005.   EditMenu, PopupMenu: HMenu;
  1006.   Size: Longint;
  1007.   Params: array[0..1] of Pointer;
  1008.   ClassName, ClassText, Verb: array[0..31] of Char;
  1009.   Buffer: array[0..255] of Char;
  1010. begin
  1011.   EditMenu := GetSubMenu(Attr.Menu, pos_Edit);
  1012.   DeleteMenu(EditMenu, pos_Object, mf_ByPosition);
  1013.   if ObjectWindow <> nil then
  1014.   begin
  1015.     ObjectWindow^.GetObjectClass(ClassName);
  1016.     if ClassName[0] <> #0 then
  1017.     begin
  1018.       Size := SizeOf(ClassText);
  1019.       if RegQueryValue(hkey_Classes_Root, ClassName,
  1020.         ClassText, Size) = 0 then
  1021.       begin
  1022.         PopupMenu := CreatePopupMenu;
  1023.         VerbCount := 0;
  1024.         repeat
  1025.           Params[0] := @ClassName;
  1026.           Params[1] := Pointer(VerbCount);
  1027.           wvsprintf(Buffer, '%s\protocol\StdFileEditing\verb\%d', Params);
  1028.           Size := SizeOf(Verb);
  1029.           VerbFound := RegQueryValue(hkey_Classes_Root,
  1030.             Buffer, Verb, Size) = 0;
  1031.           if VerbFound then
  1032.           begin
  1033.             InsertMenu(PopupMenu, VerbCount, mf_ByPosition,
  1034.               cm_VerbMin + VerbCount, Verb);
  1035.             Inc(VerbCount);
  1036.           end;
  1037.         until not VerbFound;
  1038.         if VerbCount <= 1 then
  1039.         begin
  1040.           if VerbCount = 0 then
  1041.             Params[0] := PChar('Edit') else
  1042.             Params[0] := @Verb;
  1043.           Params[1] := @ClassText;
  1044.           wvsprintf(Buffer, '%s %s &Object', Params);
  1045.           InsertMenu(EditMenu, pos_Object, mf_ByPosition,
  1046.             cm_VerbMin, Buffer);
  1047.           DestroyMenu(PopupMenu);
  1048.         end else
  1049.         begin
  1050.           Params[0] := @ClassText;
  1051.           wvsprintf(Buffer, '%s &Object', Params);
  1052.           InsertMenu(EditMenu, pos_Object, mf_ByPosition + mf_Popup,
  1053.             PopupMenu, Buffer);
  1054.         end;
  1055.         Exit;
  1056.       end;
  1057.     end;
  1058.   end;
  1059.   InsertMenu(EditMenu, pos_Object, mf_ByPosition + mf_Grayed,
  1060.     0, '&Object');
  1061. end;
  1062.  
  1063. { wm_LButtonDown message handler. Deselects the current OLE object
  1064.   window. }
  1065.  
  1066. procedure TMainWindow.WMLButtonDown(var Msg: TMessage);
  1067. begin
  1068.   SelectWindow(nil);
  1069. end;
  1070.  
  1071. { wm_InitMenu message handler. Updates the Edit menu. }
  1072.  
  1073. procedure TMainWindow.WMInitMenu(var Msg: TMessage);
  1074. var
  1075.   HasSelection: Boolean;
  1076.  
  1077.   procedure SetMenuItem(Item: Word; Enable: Boolean);
  1078.   var
  1079.     Flags: Word;
  1080.   begin
  1081.     if Enable then Flags := mf_Enabled else Flags := mf_Grayed;
  1082.     EnableMenuItem(Attr.Menu, Item, Flags);
  1083.   end;
  1084.  
  1085. begin
  1086.   HasSelection := ObjectWindow <> nil;
  1087.   SetMenuItem(cm_EditCut, HasSelection);
  1088.   SetMenuItem(cm_EditCopy, HasSelection);
  1089.   SetMenuItem(cm_EditClear, HasSelection);
  1090.   SetMenuItem(cm_EditPaste, OleQueryCreateFromClip(
  1091.     OleProtocol, olerender_Draw, 0) = ole_OK);
  1092.   SetMenuItem(cm_EditPasteLink, OleQueryLinkFromClip(
  1093.     OleProtocol, olerender_Draw, 0) = ole_OK);
  1094.   UpdateObjectMenu;
  1095. end;
  1096.  
  1097. { File|New command handler. Checks whether the current file can be
  1098.   closed, and creates a new untitled file if possible. }
  1099.  
  1100. procedure TMainWindow.CMFileNew(var Msg: TMessage);
  1101. begin
  1102.   if CanClose then NewFile('');
  1103. end;
  1104.  
  1105. { File|Open command handler. Checks whether the current file can be
  1106.   closed, and opens a new file if possible. }
  1107.  
  1108. procedure TMainWindow.CMFileOpen(var Msg: TMessage);
  1109. var
  1110.   Name: TFilename;
  1111. begin
  1112.   if CanClose then
  1113.   begin
  1114.     Name[0] := #0;
  1115.     if FileDialog(HWindow, Name, False) then
  1116.       if not NewFile(Name) then NewFile('');
  1117.   end;
  1118. end;
  1119.  
  1120. { File|Save command handler. }
  1121.  
  1122. procedure TMainWindow.CMFileSave(var Msg: TMessage);
  1123. begin
  1124.   Save;
  1125. end;
  1126.  
  1127. { File|Save as command handler. }
  1128.  
  1129. procedure TMainWindow.CMFileSaveAs(var Msg: TMessage);
  1130. begin
  1131.   SaveAs;
  1132. end;
  1133.  
  1134. { File|Exit command handler. }
  1135.  
  1136. procedure TMainWindow.CMFileExit(var Msg: TMessage);
  1137. begin
  1138.   CloseWindow;
  1139. end;
  1140.  
  1141. { Edit|Cut command handler. Performs a Copy followed by a Clear. }
  1142.  
  1143. procedure TMainWindow.CMEditCut(var Msg: TMessage);
  1144. begin
  1145.   CMEditCopy(Msg);
  1146.   CMEditClear(Msg);
  1147. end;
  1148.  
  1149. { Edit|Copy command handler. If an OLE object window is currently
  1150.   selected, the clipboard is emptied, and the OLE object window is
  1151.   instructed to copy the contained OLE object to the clipboard. }
  1152.  
  1153. procedure TMainWindow.CMEditCopy(var Msg: TMessage);
  1154. begin
  1155.   if ObjectWindow <> nil then
  1156.   begin
  1157.     OpenClipBoard(HWindow);
  1158.     EmptyClipBoard;
  1159.     ObjectWindow^.CopyToClipboard;
  1160.     CloseClipBoard;
  1161.   end;
  1162. end;
  1163.  
  1164. { Edit|Paste command handler. Creates an embedded OLE object. }
  1165.  
  1166. procedure TMainWindow.CMEditPaste(var Msg: TMessage);
  1167. begin
  1168.   NewObjectWindow(False);
  1169. end;
  1170.  
  1171. { Edit|Paste link command handler. Creates a linked OLE object. }
  1172.  
  1173. procedure TMainWindow.CMEditPasteLink(var Msg: TMessage);
  1174. begin
  1175.   NewObjectWindow(True);
  1176. end;
  1177.  
  1178. { Edit|Clear command handler. Deletes the currently selected OLE object
  1179.   window, if possible. }
  1180.  
  1181. procedure TMainWindow.CMEditClear(var Msg: TMessage);
  1182. begin
  1183.   if ObjectWindow <> nil then
  1184.     if ObjectWindow^.CanClose then ObjectWindow^.Delete;
  1185. end;
  1186.  
  1187. { Help|About command handler. Brings up the About box. }
  1188.  
  1189. procedure TMainWindow.CMHelpAbout(var Msg: TMessage);
  1190. begin
  1191.   Application^.ExecDialog(New(PDialog, Init(@Self, PChar(id_About))));
  1192. end;
  1193.  
  1194. { Default command handler method. Called when no explicit command handler
  1195.   can be found. If the command is within the range reserved for OLE
  1196.   object verbs, the current OLE object window is instructed to execute
  1197.   the verb. }
  1198.  
  1199. procedure TMainWindow.DefCommandProc(var Msg: TMessage);
  1200. begin
  1201.   if (Msg.WParam >= cm_VerbMin) and (Msg.WParam <= cm_VerbMax) then
  1202.   begin
  1203.     if ObjectWindow <> nil then
  1204.       ObjectWindow^.OpenObject(Msg.WParam - cm_VerbMin);
  1205.   end else
  1206.     TWindow.DefCommandProc(Msg);
  1207. end;
  1208.  
  1209. { TApp methods }
  1210.  
  1211. { Construct the application object. Queries the pixels-per-inch ratios
  1212.   of the display for later use in conversions between mm_HiMetric and
  1213.   mm_Text coordinates. Creates callback procedure instances for the OLE
  1214.   client and OLE stream virtual tables. Registers the OwnerLink and
  1215.   ObjectLink clipboard formats for later use in OleGetData calls.
  1216.   Registers TObjectWindow for stream I/O. }
  1217.  
  1218. constructor TApp.Init(AName: PChar);
  1219. var
  1220.   DC: HDC;
  1221. begin
  1222.   TApplication.Init(AName);
  1223.   DC := GetDC(0);
  1224.   PixPerInch.X := GetDeviceCaps(DC, logPixelsX);
  1225.   PixPerInch.Y := GetDeviceCaps(DC, logPixelsY);
  1226.   ReleaseDC(0, DC);
  1227.   @OleClientVTbl.CallBack := MakeProcInstance(@ClientCallBack, HInstance);
  1228.   @OleStreamVTbl.Get := MakeProcInstance(@StreamGet, HInstance);
  1229.   @OleStreamVTbl.Put := MakeProcInstance(@StreamPut, HInstance);
  1230.   CFOwnerLink := RegisterClipboardFormat('OwnerLink');
  1231.   CFObjectLink := RegisterClipboardFormat('ObjectLink');
  1232.   RegisterType(RObjectWindow);
  1233. end;
  1234.  
  1235. { Destroy the application object. Frees the OLE client and OLE stream
  1236.   virtual table procedure instances. }
  1237.  
  1238. destructor TApp.Done;
  1239. begin
  1240.   FreeProcInstance(@OleClientVTbl.CallBack);
  1241.   FreeProcInstance(@OleStreamVTbl.Get);
  1242.   FreeProcInstance(@OleStreamVTbl.Put);
  1243.   TApplication.Done;
  1244. end;
  1245.  
  1246. { Create the main window. }
  1247.  
  1248. procedure TApp.InitMainWindow;
  1249. begin
  1250.   MainWindow := New(PMainWindow, Init);
  1251. end;
  1252.  
  1253. { Main program }
  1254.  
  1255. begin
  1256.   App.Init('OleClntDemo');
  1257.   App.Run;
  1258.   App.Done;
  1259. end.
  1260.