{***************************************************}
{                                                   }
{   Windows 3.1 DDEML Demonstration Program         }
{   Copyright (c) 1992 by Borland International     }
{                                                   }
{***************************************************}

program DDEMLServer;

{ This sample application uses the DDEML library in the server side of a
  cooperative application.  This server is a simple data-entry application
  which allows an operator to enter three data items, which are made
  available through DDE to interested clients.

  This server makes its service available under the following names:

       Service: 'DataEntry'
       Topic  : 'SampledData'
       Items  : 'DataItem1', 'DataItem2', 'DataItem3'

  Conceivably, other topics under this service could be defined.  Things
  such as historical data, information about the sampling, and so on
  might make useful topics.

  You must run this server BEFORE running the client (DDEMLCLI.PAS), or
  the client will fail the connection.

  The interface to this server is defined by the list of names (Service,
  Topic, and Items) in the separate unit called DataEntry (DATAENTR.TPU).
  The server makes the Items available in cf_Text format; they can be
  converted and stored locally as integers by the client.
}

uses Strings, WinTypes, WinProcs, OWindows, ODialogs, Win31, DDEML,
  ShellAPI, BWCC, DataEntry;

{$R DDEMLSRV}

const

{ Resource IDs }

  id_Menu    = 100;
  id_About   = 100;
  id_Icon    = 100;

  id_Value1  = 401;  { Used with the DataEntry Dialog }
  id_Value2  = 402;
  id_Value3  = 403;

  st_Message =   1;

{ Menu command IDs }

  cm_DataEnter = 201;
  cm_DataClear = 202;
  cm_HelpAbout = 300;

type

{ Application main window }

  PDDEServerWindow = ^TDDEServerWindow;
  TDDEServerWindow = object(TWindow)
    Inst       : Longint;
    CallBack   : TCallback;
    ServiceHSz : HSz;
    TopicHSz   : HSz;
    ItemHSz    : array [1..NumValues] of HSz;
    ConvHdl    : HConv;
    Advising   : array [1..NumValues] of Boolean;

    DataSample : TDataSample;

    constructor Init(AParent: PWindowsObject; ATitle: PChar);
    destructor  Done; virtual;

    procedure GetWindowClass(var AWndClass: TWndClass); virtual;
    function  GetClassName: PChar; virtual;
    procedure SetupWindow; virtual;
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;

    procedure CMDataEnter(var Msg: TMessage);
      virtual cm_First + cm_DataEnter;
    procedure CMDataClear(var Msg: TMessage);
      virtual cm_First + cm_DataClear;
    procedure CMHelpAbout(var Msg: TMessage);
      virtual cm_First + cm_HelpAbout;

    function  MatchTopicAndService(Topic, Service: HSz): Boolean; virtual;
    function  MatchTopicAndItem(Topic, Item: HSz): Integer; virtual;
    function  WildConnect(Topic, Service: HSz;
      ClipFmt: Word): HDDEData; virtual;
    function  AcceptPoke(Item: HSz; ClipFmt: Word;
      Data: HDDEData): Boolean; virtual;
    function  DataRequested(TransType: Word; ItemNum: Integer;
      ClipFmt: Word): HDDEData; virtual;
  end;


{ Application object }

  TDDEServerApp = object(TApplication)
    procedure InitMainWindow; virtual;
  end;


{ Initialized globals }

const
  DemoTitle   : PChar = 'DDEML Demo, Server Application';

  MaxAdvisories = 100;
  NumAdvLoops : Integer = 0;


{ Global variables }

var
  App: TDDEServerApp;


{ Local Function: CallBack Procedure for DDEML }

{ This callback procedure responds to all transactions generated by the
  DDEML.  The target Window object is obtained from the stored global,
  and the appropriate methods within that objects are used to respond
  to the given transaction, as indicated by the CallType parameter.
}
function CallbackProc(CallType, Fmt: Word; Conv: HConv; HSz1, HSz2: HSZ;
  Data: HDDEData; Data1, Data2: Longint): HDDEData; export;
var
  ThisWindow: PDDEServerWindow;
  ItemNum   : Integer;
begin
  CallbackProc := 0;    { See if proved otherwise }

  ThisWindow := PDDEServerWindow(App.MainWindow);

  case CallType of

    xtyp_WildConnect:
      CallbackProc := ThisWindow^.WildConnect(HSz1, HSz2, Fmt);

    xtyp_Connect:
      if Conv = 0 then
      begin
        if ThisWindow^.MatchTopicAndService(HSz1, HSz2) then
          CallbackProc := 1;   { Connected! }
      end;
{ When a connection is confirmed, record the conversation handle as the
  window's own.
}
    xtyp_Connect_Confirm:
      ThisWindow^.ConvHdl := Conv;

{ The client has requested data, either as a direct request or
  in response to an advisory.  Return the current state of the
  data.
}
    xtyp_AdvReq, xtyp_Request:
      begin
        ItemNum := ThisWindow^.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
          CallbackProc := ThisWindow^.DataRequested(CallType, ItemNum, Fmt);
      end;

{ Respond to Poke requests ... this demo only allows Pokes of DataItem1.
  Return dde_FAck to acknowledge the receipt, 0 otherwise.
}
    xtyp_Poke:
      begin
        if ThisWindow^.AcceptPoke(HSz2, Fmt, Data) then
          CallbackProc := dde_FAck;
      end;

{ The client has requested the start of an advisory loop.  Note
  that we assume a "hot" loop.  Set the Advising flag to indicate
  the open loop, which will be checked whenever the data is changed.
}
    xtyp_AdvStart:
      begin
        ItemNum := ThisWindow^.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
        begin
          if NumAdvLoops < MaxAdvisories then    { Arbitrary number }
          begin
            Inc(NumAdvLoops);
            ThisWindow^.Advising[ItemNum] := True;
            CallbackProc := 1;
          end;
        end;
      end;

{ The client has requested the advisory loop to terminate.
}
    xtyp_AdvStop:
      begin
        ItemNum := ThisWindow^.MatchTopicAndItem(HSz1, HSz2);
        if ItemNum > 0 then
        begin
          if NumAdvLoops > 0 then
          begin
            Dec(NumAdvLoops);
            if NumAdvLoops = 0 then
              ThisWindow^.Advising[ItemNum] := False;
            CallbackProc := 1;
          end;
        end;
      end;
  end;  { Case CallType }

end;


{ TDDEServerWindow Methods }

{ Constructs an instance of the DDE Server Window.  Calls on the
  inherited constructor, then sets up this objects own instandce
  data.
}
constructor TDDEServerWindow.Init(AParent: PWindowsObject; ATitle: PChar);
var
  I : Integer;
begin
  TWindow.Init(AParent, ATitle);

  Inst      := 0;      { Must be zero for first call to DdeInitialize }
  @CallBack := nil;    { MakeProcInstance is called in SetupWindow    }

  for I := 1 to NumValues do
  begin
    DataSample[I]:= 0;
    Advising[I]  := False;
  end;
end;

{ Destroys an instance of the DDE Server Window.  Checks to see if the
  Callback Proc Instance had been created, and frees it if so.  Also 
  calls DdeUninitialize to terminate the conversation.  Then just calls
  on the ancestral destructor to finish.
}
destructor TDDEServerWindow.Done;
var
  I : Integer;
begin
  if ServiceHSz <> 0 then
    DdeFreeStringHandle(Inst, ServiceHSz);
  if TopicHSz <> 0 then
    DdeFreeStringHandle(Inst, TopicHSz);
  for I := 1 to NumValues do
    if ItemHSz[I] <> 0 then
      DdeFreeStringHandle(Inst, ItemHSz[I]);

  if Inst <> 0 then
    DdeUninitialize(Inst);   { Ignore the return value }
    
  if @CallBack <> nil then
    FreeProcInstance(@CallBack);

  TWindow.Done;
end;

{ Redefines GetWindowClass to give this application its own Icon and
  default menu.
}
procedure TDDEServerWindow.GetWindowClass(var AWndClass: TWndClass);
begin
  TWindow.GetWindowClass(AWndClass);
  AWndClass.hIcon := LoadIcon(AWndClass.hInstance, PChar(id_Icon));
  AWndClass.lpszMenuName := PChar(id_Menu);
end;

{ Returns the class name of this window.  This is necessary since we
  redefine the inherited GetWindowClass method, above.
}
function TDDEServerWindow.GetClassName: PChar;
begin
  GetClassName := 'TDDEServerWindow';
end;

{ Completes the initialization of the DDE Server Window.  Initializes
  the use of the DDEML by registering the services provided by this
  application.  Recall that the actual names used to register are
  defined in a separate unit (DataEntry), so that they can be used
  by the client as well.
}
procedure TDDEServerWindow.SetupWindow;
var
  I : Integer;
begin
  TWindow.SetupWindow;

  @CallBack:= MakeProcInstance(@CallBackProc, HInstance);

  if DdeInitialize(Inst, CallBack, 0, 0) = dmlErr_No_Error then
  begin
    ServiceHSz:= DdeCreateStringHandle(Inst, DataEntryName, cp_WinAnsi);
    TopicHSz  := DdeCreateStringHandle(Inst, DataTopicName, cp_WinAnsi);
    for I := 1 to NumValues do
      ItemHSz[I] := DdeCreateStringHandle(Inst, DataItemNames[I],
        cp_WinAnsi);

    if DdeNameService(Inst, ServiceHSz, 0, dns_Register) = 0 then
    begin
      MessageBox(HWindow, 'Registration failed.', Application^.Name,
        mb_IconStop);
      PostQuitMessage(0);
    end;
  end
  else
    PostQuitMessage(0);
end;

procedure TDDEServerWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
type
  TDataItem = record
    Name: Pointer;
    Value: Integer;
  end;
  TData = array[1..NumValues] of TDataItem;
var
  R: TRect;
  S: array[0..255] of Char;
  S1: array[0..512] of Char;
  Len, I: Integer;
  Data: TData;
begin
  GetClientRect(HWindow, R);
  InflateRect(R, -10, 0);
  LoadString(hInstance, st_Message, S, SizeOf(S));
  for I := 1 to NumValues do
  begin
    Data[I].Name := DataItemNames[I];
    Data[I].Value := DataSample[I];
  end;
  Len := wvsPrintf(S1, S, Data);
  DrawText(PaintDC, S1, Len, R, dt_WordBreak);
end;

{ Returns True if the given Topic and Service match those supported
  by this application.  False otherwise.
}
function TDDEServerWindow.MatchTopicAndService(Topic, Service: HSz): Boolean;
begin
  MatchTopicAndService := False;
  if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
    if DdeCmpStringHandles(ServiceHSz, Service) = 0 then
      MatchTopicAndService := True;
end;

{ Determines if the given Topic and Item match one supported by this
  application.  Returns the Item Number of the supported item (in the
  range 1..NumValues) if one is found, and zero if no match.
}
function TDDEServerWindow.MatchTopicAndItem(Topic, Item: HSz): Integer;
var
  I : Integer;
begin
  MatchTopicAndItem := 0;
  if DdeCmpStringHandles(TopicHSz, Topic) = 0 then
    for I := 1 to NumValues do
      if DdeCmpStringHandles(ItemHSz[I], Item) = 0 then
        MatchTopicAndItem := I;
end;

{ Responds to wildcard connect requests.  These requests are generated
  whenever a client tries to connect to a server with either service or topic
  name set to 0.  If a server detects a wild card match, it returns a
  handle to an array of THSZPair's containing the matching supported Service
  and Topic.
}
function TDDEServerWindow.WildConnect(Topic, Service: HSz;
  ClipFmt: Word): HDDEData;
var
  TempPairs: array [0..1] of THSZPair;
  Matched  : Boolean;
begin
  TempPairs[0].hszSvc  := ServiceHSz;
  TempPairs[0].hszTopic:= TopicHSz;
  TempPairs[1].hszSvc  := 0;     { 0-terminate the list }
  TempPairs[1].hszTopic:= 0;

  Matched := False;

  if (Topic= 0) and (Service = 0) then
    Matched := True                    { Complete wildcard }
  else
    if (Topic = 0) and (DdeCmpStringHandles(Service, ServiceHSz) = 0) then
      Matched := True
    else
      if (DdeCmpStringHandles(Topic, TopicHSz) = 0) and (Service = 0) then
        Matched := True;

  if Matched then
    WildConnect := DdeCreateDataHandle(Inst, @TempPairs, SizeOf(TempPairs),
      0, 0, ClipFmt, 0)
  else
    WildConnect := 0;
end;

{ Accepts and acts upon Poke requests from the Client.  For this
  demonstration, allows only the value of DataItem1 to be changed by a Poke.
}
function TDDEServerWindow.AcceptPoke(Item: HSz; ClipFmt: Word;
  Data: HDDEData): Boolean;
var
  DataStr   : TDataString;
  Err       : Integer;
  TempSample: TDataSample;
begin
  if (DdeCmpStringHandles(Item, ItemHSz[1]) = 0) and (ClipFmt = cf_Text) then
  begin
    DdeGetData(Data, @DataStr, SizeOf(DataStr), 0);
    Val(DataStr, TempSample[1], Err);

    if TempSample[1] <> DataSample[1] then
    begin
      DataSample[1] := TempSample[1];
      if Advising[1] then
        DdePostAdvise(Inst, TopicHSz, ItemHSz[1]);
    end;
    InvalidateRect(HWindow, nil, True);
    AcceptPoke := True;
  end
  else
    AcceptPoke := False;
end;

{ Returns the data requested by the given TransType and ClipFmt values.
  This could happen either in response to either an xtyp_Request or an
  xtyp_AdvReq.  The ItemNum parameter indicates which of the supported
  items (in the range 1..NumValues) was requested (note that this method
  assumes that the caller has already established validity and ID of the
  requested item using MatchTopicAndItem).  The corresponding data from
  the DataSample instance variable is converted to text and returned.
}
function TDDEServerWindow.DataRequested(TransType: Word; ItemNum: Integer;
  ClipFmt: Word): HDDEData;
var
  ItemStr: TDataString;   { Defined in DataEntry.TPU }
begin
  if ClipFmt = cf_Text then
  begin
    Str(DataSample[ItemNum], ItemStr);
    DataRequested := DdeCreateDataHandle(Inst, @ItemStr, StrLen(ItemStr) + 1,
      0, ItemHSz[ItemNum], ClipFmt, 0);
  end
  else
    DataRequested := 0;
end;

{ Activates the data-entry dialog, and updates the stored
  data when complete.
}
procedure TDDEServerWindow.CMDataEnter(var Msg: TMessage);
const
  ValEditIds : array [1..NumValues] of Integer = (id_Value1,
    id_Value2, id_Value3);
var
  DataEntry  : PDialog;
  Err, I     : Integer;
  TempSample : TDataSample;
  Ed         : PEdit;
  TransferRec: array [1..NumValues] of record
                                         ValStr : array [0..19] of Char;
                                       end;
begin
  DataEntry := New(PDialog, Init(@Self, 'DATAENTRY'));

  for I := 1 to NumValues do
  begin
    Str(DataSample[I], TransferRec[I].ValStr);
    New(Ed, InitResource(DataEntry, ValEditIds[I],
      SizeOf(TransferRec[I].ValStr)));
  end;

  DataEntry^.TransferBuffer := @TransferRec;

  if Application^.ExecDialog(DataEntry) = IdOK then
  begin
    for I := 1 to NumValues do
    begin
      Val(TransferRec[I].ValStr, TempSample[I], Err);

      if TempSample[I] <> DataSample[I] then
      begin
        DataSample[I] := TempSample[I];
        if Advising[I] then
          DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
      end;
    end;
    InvalidateRect(HWindow, nil, True);
  end;
end;

{ Clears the current data.
}
procedure TDDEServerWindow.CMDataClear(var Msg: TMessage);
var
  I : Integer;
begin
  for I := 1 to NumValues do
  begin
    DataSample[I] := 0;
    if Advising[I] then
      DdePostAdvise(Inst, TopicHSz, ItemHSz[I]);
  end;
  InvalidateRect(HWindow, nil, True);
end;

{ Posts the about box dialog for the DDE Server.
}
procedure TDDEServerWindow.CMHelpAbout(var Msg: TMessage);
begin
  Application^.ExecDialog(New(PDialog, Init(@Self, PChar(id_About))));
end;


{ TDDEServerApp Methods }

procedure TDDEServerApp.InitMainWindow;
begin
  MainWindow := New(PDDEServerWindow, Init(nil, Application^.Name));
end;


{ Main program }

begin
  App.Init(DemoTitle);
  App.Run;
  App.Done;
end.

