home *** CD-ROM | disk | FTP | other *** search
/ Black Box 4 / BlackBox.cdr / w3_prog / tpwin31.arj / OLEDEMO.PAS < prev    next >
Pascal/Delphi Source File  |  1992-04-06  |  34KB  |  1,170 lines

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