home *** CD-ROM | disk | FTP | other *** search
/ QBasic & Borland Pascal & C / Delphi5.iso / Pascal / BPASCAL.700 / D12 / OLE.ZIP / OLECLNT.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1992-10-01  |  34.9 KB  |  1,256 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. begin
  550.   Check(OleActivate(OleObject, Verb, True, True, 0, nil));
  551. end;
  552.  
  553. { Close the contained OLE object if it is open. }
  554.  
  555. procedure TObjectWindow.CloseObject;
  556. begin
  557.   if OleQueryOpen(OleObject) = ole_Ok then Check(OleClose(OleObject));
  558. end;
  559.  
  560. { Copy the contained OLE object to the clipboard. }
  561.  
  562. procedure TObjectWindow.CopyToClipboard;
  563. begin
  564.   Check(OleCopyToClipboard(OleObject));
  565. end;
  566.  
  567. { Delete an OLE object window. If the window is the main window's
  568.   current selection, it is unselected. The parent window is marked as
  569.   modified, and the contained OLE object is closed and deleted. }
  570.  
  571. procedure TObjectWindow.Delete;
  572. begin
  573.   with MainWindow^ do
  574.   begin
  575.     if ObjectWindow = @Self then SelectWindow(nil);
  576.     Modified := True;
  577.   end;
  578.   CloseObject;
  579.   Check(OleDelete(OleObject));
  580.   OleObject := nil;
  581.   Free;
  582. end;
  583.  
  584. { This method is called by the ClientCallBack routine whenever the
  585.   contained OLE object has changed. The client area of the OLE object
  586.   window is invalidated to force repainting, and the main window is
  587.   marked as modified. }
  588.  
  589. procedure TObjectWindow.Changed;
  590. begin
  591.   InvalidateRect(HWindow, nil, True);
  592.   MainWindow^.Modified := True;
  593. end;
  594.  
  595. { Bring an OLE object window to front. }
  596.  
  597. procedure TObjectWindow.BringToFront;
  598. begin
  599.   SetWindowPos(HWindow, 0, 0, 0, 0, 0, swp_NoMove + swp_NoSize);
  600. end;
  601.  
  602. { Return the bounds of an OLE object window using parent window
  603.   coordinates. The bounds include the window frame, if present. }
  604.  
  605. procedure TObjectWindow.GetBounds(var R: TRect);
  606. begin
  607.   GetWindowRect(HWindow, R);
  608.   ScreenToClient(Parent^.HWindow, PPoint(@R.left)^);
  609.   ScreenToClient(Parent^.HWindow, PPoint(@R.right)^);
  610. end;
  611.  
  612. { Set the bounds of an OLE object window within its parent window. }
  613.  
  614. procedure TObjectWindow.SetBounds(var R: TRect);
  615. begin
  616.   MoveWindow(HWindow, R.left, R.top,
  617.     R.right - R.left, R.bottom - R.top, True);
  618.   UpdateWindow(HWindow);
  619. end;
  620.  
  621. { Enable or disable an OLE object window's window frame. The frame is
  622.   added or removed by modifying the window's style flags and growing or
  623.   shrinking the window's bounds. }
  624.  
  625. procedure TObjectWindow.ShowFrame(EnableFrame: Boolean);
  626. const
  627.   Border = ws_Border + ws_ThickFrame;
  628. var
  629.   FX, FY: Integer;
  630.   Style: Longint;
  631.   R: TRect;
  632. begin
  633.   if EnableFrame <> Framed then
  634.   begin
  635.     Style := GetWindowLong(HWindow, gwl_Style);
  636.     FX := GetSystemMetrics(sm_CXFrame);
  637.     FY := GetSystemMetrics(sm_CYFrame);
  638.     GetBounds(R);
  639.     if EnableFrame then
  640.     begin
  641.       Style := Style or Border;
  642.       InflateRect(R, FX, FY);
  643.     end else
  644.     begin
  645.       Style := Style and not Border;
  646.       InflateRect(R, -FX, -FY);
  647.     end;
  648.     SetWindowLong(HWindow, gwl_Style, Style);
  649.     SetBounds(R);
  650.     Framed := EnableFrame;
  651.   end;
  652. end;
  653.  
  654. { wm_GetMinMaxInfo message handler. Modifies the minimum window size. }
  655.  
  656. procedure TObjectWindow.WMGetMinMaxInfo(var Msg: TMessage);
  657. type
  658.   PMinMaxInfo = ^TMinMaxInfo;
  659.   TMinMaxInfo = array[0..4] of TPoint;
  660. begin
  661.   PMinMaxInfo(Msg.LParam)^[3].X := 24;
  662.   PMinMaxInfo(Msg.LParam)^[3].Y := 24;
  663. end;
  664.  
  665. { wm_Move message handler. Updates the window location in the Attr field
  666.   and marks the main window as modified. }
  667.  
  668. procedure TObjectWindow.WMMove(var Msg: TMessage);
  669. begin
  670.   if (Attr.X <> Integer(Msg.LParamLo)) or
  671.     (Attr.Y <> Integer(Msg.LParamHi)) then
  672.   begin
  673.     Attr.X := Integer(Msg.LParamLo);
  674.     Attr.Y := Integer(Msg.LParamHi);
  675.     MainWindow^.Modified := True;
  676.   end;
  677. end;
  678.  
  679. { wm_Size message handler. Updates the window size in the Attr field and
  680.   marks the main window as modified. }
  681.  
  682. procedure TObjectWindow.WMSize(var Msg: TMessage);
  683. begin
  684.   if (Attr.W <> Msg.LParamLo) or (Attr.H <> Msg.LParamHi) then
  685.   begin
  686.     Attr.W := Msg.LParamLo;
  687.     Attr.H := Msg.LParamHi;
  688.     MainWindow^.Modified := True;
  689.   end;
  690. end;
  691.  
  692. { wm_LButtonDown message handler. Brings the window to front and selects
  693.   it, causing a frame to be drawn around the window. If a dragging
  694.   operation is not in effect, one is initiated by capturing the mouse
  695.   and recording the initial dragging location. }
  696.  
  697. procedure TObjectWindow.WMLButtonDown(var Msg: TMessage);
  698. begin
  699.   BringToFront;
  700.   MainWindow^.SelectWindow(@Self);
  701.   if not Dragging then
  702.   begin
  703.     Dragging := True;
  704.     SetCapture(HWindow);
  705.     DragPoint := TPoint(Msg.LParam);
  706.     ClientToScreen(HWindow, DragPoint);
  707.   end;
  708. end;
  709.  
  710. { wm_MouseMove message handler. If a dragging operation is in effect,
  711.   the window is moved and the client area of the parent window is
  712.   repainted. }
  713.  
  714. procedure TObjectWindow.WMMouseMove(var Msg: TMessage);
  715. var
  716.   P: TPoint;
  717.   R: TRect;
  718. begin
  719.   if Dragging then
  720.   begin
  721.     P := TPoint(Msg.LParam);
  722.     ClientToScreen(HWindow, P);
  723.     GetBounds(R);
  724.     OffsetRect(R, P.X - DragPoint.X, P.Y - DragPoint.Y);
  725.     SetBounds(R);
  726.     UpdateWindow(Parent^.HWindow);
  727.     DragPoint := P;
  728.   end;
  729. end;
  730.  
  731. { wm_LButtonUp message handler. Terminates a dragging operation. }
  732.  
  733. procedure TObjectWindow.WMLButtonUp(var Msg: TMessage);
  734. begin
  735.   if Dragging then
  736.   begin
  737.     ReleaseCapture;
  738.     Dragging := False;
  739.   end;
  740. end;
  741.  
  742. { wm_LButtonDblClk message handler. Opens the contained OLE object by
  743.   executing its primary verb. This is typically an 'Edit' or 'Play'
  744.   operation. }
  745.  
  746. procedure TObjectWindow.WMLButtonDblClk(var Msg: TMessage);
  747. begin
  748.   OpenObject(oleverb_Primary);
  749. end;
  750.  
  751. { TMainWindow methods }
  752.  
  753. { Construct the application's main window. Loads the main menu and
  754.   creates an OLE document. }
  755.  
  756. constructor TMainWindow.Init;
  757. var
  758.   P: PObjectWindow;
  759. begin
  760.   MainWindow := @Self;
  761.   TWindow.Init(nil, nil);
  762.   Attr.Menu := LoadMenu(HInstance, PChar(id_Menu));
  763.   ObjectWindow := nil;
  764. end;
  765.  
  766. { Destroy the application's main window. Destroys the contained OLE
  767.   document. }
  768.  
  769. destructor TMainWindow.Done;
  770. begin
  771.   DoneDocument;
  772.   TWindow.Done;
  773. end;
  774.  
  775. { Determine whether the main window can close. Checks whether the
  776.   contained OLE object windows can close, and then prompts the user if
  777.   any modifications have been made since the file was opened or saved. }
  778.  
  779. function TMainWindow.CanClose: Boolean;
  780. begin
  781.   CanClose := False;
  782.   if TWindow.CanClose then
  783.   begin
  784.     CanClose := True;
  785.     if Modified then
  786.       case Message('Save current changes?',
  787.         mb_IconExclamation + mb_YesNoCancel) of
  788.         id_Yes: CanClose := Save;
  789.         id_Cancel: CanClose := False;
  790.       end;
  791.   end;
  792. end;
  793.  
  794. { Set the initial file name to untitled }
  795.  
  796. procedure TMainWindow.SetupWindow;
  797. begin
  798.   inherited SetupWindow;
  799.   SetFilename('');
  800.   InitDocument;
  801. end; 
  802.  
  803. { Create the main window's OLE document. }
  804.  
  805. procedure TMainWindow.InitDocument;
  806. var
  807.   P: PChar;
  808. begin
  809.   P := Filename;
  810.   if P[0] = #0 then P := 'Untitled';
  811.   OleRegisterClientDoc('OleClntDemo', P, 0, ClientDoc);
  812.   Modified := False;
  813. end;
  814.  
  815. { Destroy the main window's OLE document. The contained OLE object
  816.   windows are destroyed before the document. }
  817.  
  818. procedure TMainWindow.DoneDocument;
  819.  
  820.   procedure FreeObjectWindow(P: PObjectWindow); far;
  821.   begin
  822.     P^.Free;
  823.   end;
  824.  
  825. begin
  826.   ForEach(@FreeObjectWindow);
  827.   OleRevokeClientDoc(ClientDoc);
  828. end;
  829.  
  830. { Update the main window's OLE document. Each object window is checked
  831.   to see if it contains a linked OLE object, and if so, the user is given
  832.   the option to update the link. }
  833.  
  834. procedure TMainWindow.UpdateDocument;
  835. var
  836.   Prompted, DoUpdate: Boolean;
  837.  
  838.   procedure UpdateObjectWindow(P: PObjectWindow); far;
  839.   begin
  840.     if P^.IsLinked then
  841.     begin
  842.       if not Prompted then
  843.       begin
  844.         DoUpdate := Message('This file contains linked objects.'#13 +
  845.           'Update links now?',
  846.           mb_IconExclamation + mb_YesNo) = id_Yes;
  847.         Prompted := True;
  848.       end;
  849.       if DoUpdate then P^.Update;
  850.     end;
  851.   end;
  852.  
  853. begin
  854.   Prompted := False;
  855.   ForEach(@UpdateObjectWindow);
  856. end;
  857.  
  858. { Set the name of the file in the main window. Updates the title of the
  859.   main window to include the base part of the filename. }
  860.  
  861. procedure TMainWindow.SetFilename(Name: PChar);
  862. var
  863.   Params: array[0..1] of PChar;
  864.   Title: array[0..63] of Char;
  865. begin
  866.   StrCopy(Filename, Name);
  867.   Params[0] := OleClntTitle;
  868.   if Name[0] = #0 then Params[1] := '(Untitled)' else
  869.   begin
  870.     Params[1] := StrRScan(Name, '\');
  871.     if Params[1] = nil then Params[1] := Name else Inc(Params[1]);
  872.   end;
  873.   wvsprintf(Title, '%s - %s', Params);
  874.   if hWindow <> 0 then SetCaption(Title);
  875. end;
  876.  
  877. { Load a file into the main window. If the file does not exist, a new
  878.   file is created. Otherwise, the file header is checked, and the
  879.   contained OLE object windows are read from the stream. }
  880.  
  881. function TMainWindow.LoadFile: Boolean;
  882. var
  883.   Header: TOleFileHeader;
  884.   S: TBufStream;
  885. begin
  886.   LoadFile := False;
  887.   S.Init(Filename, stOpenRead, 4096);
  888.   if S.Status = 0 then
  889.   begin
  890.     S.Read(Header, SizeOf(TOleFileHeader));
  891.     if Longint(Header) = Longint(OleFileHeader) then
  892.     begin
  893.       GetChildren(S);
  894.       if (S.Status = 0) and CreateChildren then
  895.       begin
  896.         UpdateDocument;
  897.         LoadFile := True;
  898.       end else
  899.         Error('Error reading file %s.', Filename);
  900.     end else
  901.       Error('File format error %s.', Filename);
  902.   end else
  903.     LoadFile := True;
  904.   S.Done;
  905. end;
  906.  
  907. { Save the file in the main window. The OLE client library is notified if
  908.   the file was successfully saved. }
  909.  
  910. function TMainWindow.SaveFile: Boolean;
  911. var
  912.   S: TBufStream;
  913. begin
  914.   SaveFile := False;
  915.   S.Init(Filename, stCreate, 4096);
  916.   if S.Status = 0 then
  917.   begin
  918.     S.Write(OleFileHeader, SizeOf(TOleFileHeader));
  919.     PutChildren(S);
  920.     if S.Status = 0 then
  921.     begin
  922.       OleSavedClientDoc(ClientDoc);
  923.       Modified := False;
  924.       SaveFile := True;
  925.     end else
  926.       Error('Error writing file %s.', Filename);
  927.   end else
  928.     Error('Error creating file %s.', Filename);
  929.   S.Done;
  930. end;
  931.  
  932. { Open a new or existing file. The current OLE document is destroyed, a
  933.   new document is created, and the file is loaded. }
  934.  
  935. function TMainWindow.NewFile(Name: PChar): Boolean;
  936. begin
  937.   DoneDocument;
  938.   SetFilename(Name);
  939.   InitDocument;
  940.   if Filename[0] <> #0 then NewFile := LoadFile else NewFile := True;
  941. end;
  942.  
  943. { Save the current file. If the file is untitled, prompt the user for a
  944.   name. }
  945.  
  946. function TMainWindow.Save: Boolean;
  947. begin
  948.   if Filename[0] = #0 then Save := SaveAs else Save := SaveFile;
  949. end;
  950.  
  951. { Save the current file under a new name. The OLE client library is
  952.   informed that the document has been renamed. }
  953.  
  954. function TMainWindow.SaveAs: Boolean;
  955. var
  956.   Name: TFilename;
  957. begin
  958.   SaveAs := False;
  959.   StrCopy(Name, Filename);
  960.   if FileDialog(HWindow, Name, True) then
  961.   begin
  962.     SetFilename(Name);
  963.     OleRenameClientDoc(ClientDoc, Name);
  964.     SaveAs := SaveFile;
  965.   end;
  966. end;
  967.  
  968. { Create a new OLE object window using data in the clipboard. The Link
  969.   parameter determines whether to create an embedded object or a linked
  970.   object. }
  971.  
  972. procedure TMainWindow.NewObjectWindow(Link: Boolean);
  973. begin
  974.   OpenClipboard(HWindow);
  975.   SelectWindow(PObjectWindow(Application^.MakeWindow(
  976.     New(PObjectWindow, Init(Link)))));
  977.   CloseClipboard;
  978. end;
  979.  
  980. { Select a given OLE object window. }
  981.  
  982. procedure TMainWindow.SelectWindow(Window: PObjectWindow);
  983. begin
  984.   if ObjectWindow <> Window then
  985.   begin
  986.     if ObjectWindow <> nil then ObjectWindow^.ShowFrame(False);
  987.     ObjectWindow := Window;
  988.     if ObjectWindow <> nil then ObjectWindow^.ShowFrame(True);
  989.   end;
  990. end;
  991.  
  992. { Update the Edit|Object menu. The Registration Database is queried to
  993.   find the readable version of the class name of the current OLE object,
  994.   along with the list of verbs supported by the class. If the class
  995.   supports more than one verb, the verbs are put on a popup submenu. }
  996.  
  997. procedure TMainWindow.UpdateObjectMenu;
  998. var
  999.   VerbFound: Boolean;
  1000.   VerbCount: Word;
  1001.   EditMenu, PopupMenu: HMenu;
  1002.   Size: Longint;
  1003.   Params: array[0..1] of Pointer;
  1004.   ClassName, ClassText, Verb: array[0..31] of Char;
  1005.   Buffer: array[0..255] of Char;
  1006. begin
  1007.   EditMenu := GetSubMenu(Attr.Menu, pos_Edit);
  1008.   DeleteMenu(EditMenu, pos_Object, mf_ByPosition);
  1009.   if ObjectWindow <> nil then
  1010.   begin
  1011.     ObjectWindow^.GetObjectClass(ClassName);
  1012.     if ClassName[0] <> #0 then
  1013.     begin
  1014.       Size := SizeOf(ClassText);
  1015.       if RegQueryValue(hkey_Classes_Root, ClassName,
  1016.         ClassText, Size) = 0 then
  1017.       begin
  1018.         PopupMenu := CreatePopupMenu;
  1019.         VerbCount := 0;
  1020.         repeat
  1021.           Params[0] := @ClassName;
  1022.           Params[1] := Pointer(VerbCount);
  1023.           wvsprintf(Buffer, '%s\protocol\StdFileEditing\verb\%d', Params);
  1024.           Size := SizeOf(Verb);
  1025.           VerbFound := RegQueryValue(hkey_Classes_Root,
  1026.             Buffer, Verb, Size) = 0;
  1027.           if VerbFound then
  1028.           begin
  1029.             InsertMenu(PopupMenu, VerbCount, mf_ByPosition,
  1030.               cm_VerbMin + VerbCount, Verb);
  1031.             Inc(VerbCount);
  1032.           end;
  1033.         until not VerbFound;
  1034.         if VerbCount <= 1 then
  1035.         begin
  1036.           if VerbCount = 0 then
  1037.             Params[0] := PChar('Edit') else
  1038.             Params[0] := @Verb;
  1039.           Params[1] := @ClassText;
  1040.           wvsprintf(Buffer, '%s %s &Object', Params);
  1041.           InsertMenu(EditMenu, pos_Object, mf_ByPosition,
  1042.             cm_VerbMin, Buffer);
  1043.           DestroyMenu(PopupMenu);
  1044.         end else
  1045.         begin
  1046.           Params[0] := @ClassText;
  1047.           wvsprintf(Buffer, '%s &Object', Params);
  1048.           InsertMenu(EditMenu, pos_Object, mf_ByPosition + mf_Popup,
  1049.             PopupMenu, Buffer);
  1050.         end;
  1051.         Exit;
  1052.       end;
  1053.     end;
  1054.   end;
  1055.   InsertMenu(EditMenu, pos_Object, mf_ByPosition + mf_Grayed,
  1056.     0, '&Object');
  1057. end;
  1058.  
  1059. { wm_LButtonDown message handler. Deselects the current OLE object
  1060.   window. }
  1061.  
  1062. procedure TMainWindow.WMLButtonDown(var Msg: TMessage);
  1063. begin
  1064.   SelectWindow(nil);
  1065. end;
  1066.  
  1067. { wm_InitMenu message handler. Updates the Edit menu. }
  1068.  
  1069. procedure TMainWindow.WMInitMenu(var Msg: TMessage);
  1070. var
  1071.   HasSelection: Boolean;
  1072.  
  1073.   procedure SetMenuItem(Item: Word; Enable: Boolean);
  1074.   var
  1075.     Flags: Word;
  1076.   begin
  1077.     if Enable then Flags := mf_Enabled else Flags := mf_Grayed;
  1078.     EnableMenuItem(Attr.Menu, Item, Flags);
  1079.   end;
  1080.  
  1081. begin
  1082.   HasSelection := ObjectWindow <> nil;
  1083.   SetMenuItem(cm_EditCut, HasSelection);
  1084.   SetMenuItem(cm_EditCopy, HasSelection);
  1085.   SetMenuItem(cm_EditClear, HasSelection);
  1086.   SetMenuItem(cm_EditPaste, OleQueryCreateFromClip(
  1087.     OleProtocol, olerender_Draw, 0) = ole_OK);
  1088.   SetMenuItem(cm_EditPasteLink, OleQueryLinkFromClip(
  1089.     OleProtocol, olerender_Draw, 0) = ole_OK);
  1090.   UpdateObjectMenu;
  1091. end;
  1092.  
  1093. { File|New command handler. Checks whether the current file can be
  1094.   closed, and creates a new untitled file if possible. }
  1095.  
  1096. procedure TMainWindow.CMFileNew(var Msg: TMessage);
  1097. begin
  1098.   if CanClose then NewFile('');
  1099. end;
  1100.  
  1101. { File|Open command handler. Checks whether the current file can be
  1102.   closed, and opens a new file if possible. }
  1103.  
  1104. procedure TMainWindow.CMFileOpen(var Msg: TMessage);
  1105. var
  1106.   Name: TFilename;
  1107. begin
  1108.   if CanClose then
  1109.   begin
  1110.     Name[0] := #0;
  1111.     if FileDialog(HWindow, Name, False) then
  1112.       if not NewFile(Name) then NewFile('');
  1113.   end;
  1114. end;
  1115.  
  1116. { File|Save command handler. }
  1117.  
  1118. procedure TMainWindow.CMFileSave(var Msg: TMessage);
  1119. begin
  1120.   Save;
  1121. end;
  1122.  
  1123. { File|Save as command handler. }
  1124.  
  1125. procedure TMainWindow.CMFileSaveAs(var Msg: TMessage);
  1126. begin
  1127.   SaveAs;
  1128. end;
  1129.  
  1130. { File|Exit command handler. }
  1131.  
  1132. procedure TMainWindow.CMFileExit(var Msg: TMessage);
  1133. begin
  1134.   CloseWindow;
  1135. end;
  1136.  
  1137. { Edit|Cut command handler. Performs a Copy followed by a Clear. }
  1138.  
  1139. procedure TMainWindow.CMEditCut(var Msg: TMessage);
  1140. begin
  1141.   CMEditCopy(Msg);
  1142.   CMEditClear(Msg);
  1143. end;
  1144.  
  1145. { Edit|Copy command handler. If an OLE object window is currently
  1146.   selected, the clipboard is emptied, and the OLE object window is
  1147.   instructed to copy the contained OLE object to the clipboard. }
  1148.  
  1149. procedure TMainWindow.CMEditCopy(var Msg: TMessage);
  1150. begin
  1151.   if ObjectWindow <> nil then
  1152.   begin
  1153.     OpenClipBoard(HWindow);
  1154.     EmptyClipBoard;
  1155.     ObjectWindow^.CopyToClipboard;
  1156.     CloseClipBoard;
  1157.   end;
  1158. end;
  1159.  
  1160. { Edit|Paste command handler. Creates an embedded OLE object. }
  1161.  
  1162. procedure TMainWindow.CMEditPaste(var Msg: TMessage);
  1163. begin
  1164.   NewObjectWindow(False);
  1165. end;
  1166.  
  1167. { Edit|Paste link command handler. Creates a linked OLE object. }
  1168.  
  1169. procedure TMainWindow.CMEditPasteLink(var Msg: TMessage);
  1170. begin
  1171.   NewObjectWindow(True);
  1172. end;
  1173.  
  1174. { Edit|Clear command handler. Deletes the currently selected OLE object
  1175.   window, if possible. }
  1176.  
  1177. procedure TMainWindow.CMEditClear(var Msg: TMessage);
  1178. begin
  1179.   if ObjectWindow <> nil then
  1180.     if ObjectWindow^.CanClose then ObjectWindow^.Delete;
  1181. end;
  1182.  
  1183. { Help|About command handler. Brings up the About box. }
  1184.  
  1185. procedure TMainWindow.CMHelpAbout(var Msg: TMessage);
  1186. begin
  1187.   Application^.ExecDialog(New(PDialog, Init(@Self, PChar(id_About))));
  1188. end;
  1189.  
  1190. { Default command handler method. Called when no explicit command handler
  1191.   can be found. If the command is within the range reserved for OLE
  1192.   object verbs, the current OLE object window is instructed to execute
  1193.   the verb. }
  1194.  
  1195. procedure TMainWindow.DefCommandProc(var Msg: TMessage);
  1196. begin
  1197.   if (Msg.WParam >= cm_VerbMin) and (Msg.WParam <= cm_VerbMax) then
  1198.   begin
  1199.     if ObjectWindow <> nil then
  1200.       ObjectWindow^.OpenObject(Msg.WParam - cm_VerbMin);
  1201.   end else
  1202.     TWindow.DefCommandProc(Msg);
  1203. end;
  1204.  
  1205. { TApp methods }
  1206.  
  1207. { Construct the application object. Queries the pixels-per-inch ratios
  1208.   of the display for later use in conversions between mm_HiMetric and
  1209.   mm_Text coordinates. Creates callback procedure instances for the OLE
  1210.   client and OLE stream virtual tables. Registers the OwnerLink and
  1211.   ObjectLink clipboard formats for later use in OleGetData calls.
  1212.   Registers TObjectWindow for stream I/O. }
  1213.  
  1214. constructor TApp.Init(AName: PChar);
  1215. var
  1216.   DC: HDC;
  1217. begin
  1218.   TApplication.Init(AName);
  1219.   DC := GetDC(0);
  1220.   PixPerInch.X := GetDeviceCaps(DC, logPixelsX);
  1221.   PixPerInch.Y := GetDeviceCaps(DC, logPixelsY);
  1222.   ReleaseDC(0, DC);
  1223.   @OleClientVTbl.CallBack := MakeProcInstance(@ClientCallBack, HInstance);
  1224.   @OleStreamVTbl.Get := MakeProcInstance(@StreamGet, HInstance);
  1225.   @OleStreamVTbl.Put := MakeProcInstance(@StreamPut, HInstance);
  1226.   CFOwnerLink := RegisterClipboardFormat('OwnerLink');
  1227.   CFObjectLink := RegisterClipboardFormat('ObjectLink');
  1228.   RegisterType(RObjectWindow);
  1229. end;
  1230.  
  1231. { Destroy the application object. Frees the OLE client and OLE stream
  1232.   virtual table procedure instances. }
  1233.  
  1234. destructor TApp.Done;
  1235. begin
  1236.   FreeProcInstance(@OleClientVTbl.CallBack);
  1237.   FreeProcInstance(@OleStreamVTbl.Get);
  1238.   FreeProcInstance(@OleStreamVTbl.Put);
  1239.   TApplication.Done;
  1240. end;
  1241.  
  1242. { Create the main window. }
  1243.  
  1244. procedure TApp.InitMainWindow;
  1245. begin
  1246.   MainWindow := New(PMainWindow, Init);
  1247. end;
  1248.  
  1249. { Main program }
  1250.  
  1251. begin
  1252.   App.Init('OleClntDemo');
  1253.   App.Run;
  1254.   App.Done;
  1255. end.
  1256.