////////////////////////////////////////////////////////////////////////////////
//
// CM2Vis_Graph
// Proof-of-concept Plugin for coolmon2 to display graphs
//
// Version 1.0
// * Initial release with version info attached
//
// Version 0.X
// * No record
//
// Copyright (c) 2003 - 2019, C. Aaangel & X. Blackstar
////////////////////////////////////////////////////////////////////////////////
unit PlugServ;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  Windows, ActiveX, Classes, ComObj, CM2VisInterface, Graphics, Filter,
   SysUtils, CM2AutoUpdateInterface, CM2EditorSupportInterface;

type
  TSecondary = (sNone, sAvg, sExtended);
  TGraph = class(TComObject, ICM2VisPlug, ICM2AutoUpdate, ICM2EditorSupportVis10)
  private
   fLeft, fTop, fWidth, fHeight: Integer;
   AvgHis, History: array of integer;
   fDC: hDC;
   Len, Min, Max: Integer;
   Border, Background, Color, avg: TColor;
   LastErrorMsg: PChar;
   Secondary: TSecondary;
  protected
  // Declare Basics
   Function Name(OUT Name: Pchar): HResult; stdcall;
   Function Version(OUT Version: word): HResult; stdcall; // Uses MMRB Notation
   Function RequiredCoreVersion(OUT RCV: Word): HResult; stdcall; // Uses MMRB Notation - Please refer to CM2Plugtypes for details
  // Declare Error commands
   Function GetLastErrorMsg(OUT Msg: PChar): HResult; stdcall;
  // Declare Info system
   Function GetInfoCount(OUT InfoCount: Integer): HResult; stdcall;
   Function GetInfo(Index: Integer; OUT InfoName: PChar; OUT InfoVar: PChar): HResult; stdcall;
  // Declare Visual Basics
   Function SetDC(CONST NewDC: THandle): HResult; stdcall;
   Function SetDimensions(Left, Top, Width, Height : Integer): HResult; stdcall;
   Function SetConfigString(NewConfigString: PChar): HResult; stdcall;
  // Declare "Event" functions
   Function Redraw: HResult; stdcall;
   Function Update(SensorStyle: Byte; NewString: PChar; NewValue: Double): HResult; stdcall;
   Function Init(PaintDC: THandle): HResult; stdcall;
   Function Terminating: HResult; stdcall;
   Function Setup: HResult; stdcall;
  // Declare Support types
   // Default (Required)
   Function GetDefaultCount(OUT DefaultCount: Integer): HResult; stdcall;
   Function GetDefault(Index: Integer; OUT Name: Pchar; OUT Value: PChar; OUT Description: PChar): HResult; stdcall;
   // Optional
   Function GetOptionalCount(OUT OptionalCount: Integer): HResult; stdcall;
   Function GetOptional(Index: Integer; OUT Name: Pchar; OUT Value: PChar; OUT Description: PChar): HResult; stdcall;
   // Auto Update
   Function UpdateFileURL(OUT URL: Pchar): HResult; stdcall;
   // Editor Support Interface
   Function ObjIns(ParameterName: PChar; OUT ValueType: Byte; OUT ValueRange: PChar): HResult; stdcall;
  end;

const
  Class_Graph: TGUID = '{06F41C22-D432-4328-9999-E224502C30A4}';
  AutoUpdateFile = 'http://www.coolmon.org/update/graph.upd';

implementation

uses ComServ;

{ TGraph }

function TGraph.GetDefault(Index: Integer; out Name, Value,
  Description: PChar): HResult;
begin
 Case Index of
  0: Begin Name := 'Length'; value := '30'; Description := 'Length of graph history, max is width of graph minus two'; end;
  1: Begin Name := 'Max'; value := '100'; Description := 'Maximum graph value (minimum is always zero)'; end;
  2: Begin Name := 'Background'; value := 'clskyblue'; Description := 'Background Color (Delphi Color Only'; end;
  3: Begin Name := 'Border'; value := 'clwhite'; Description := 'Border Color (Delphi Color Only)'; end;
  4: Begin Name := 'Color'; value := 'clred'; Description := 'Graph color (Delphi Color Only)'; end;
  5: Begin Name := 'Avg'; Value := 'clsilver'; Description := 'Average Graph Color (Delphi Color Only)'; end;
 End;
 Result := S_OK;
end;

function TGraph.GetDefaultCount(out DefaultCount: Integer): HResult;
begin
 Result := S_OK;
 DefaultCount := 6;
end;

function TGraph.GetInfo(Index: Integer; out InfoName, InfoVar: PChar): HResult;
begin
 Result := S_OK;
 Case Index of
  0: Begin
      InfoName := 'Programmed By'; InfoVar := 'C. Aaangel';
     End;
  1: Begin
      InfoName := 'Updated'; InfoVar := 'September 14th, 2019';
     End;
  2: Begin
      InfoName := 'Copyright'; InfoVar := '(c) 2003 - 2019, by Christian Aaangel';
     End;
  else begin
   Result := E_FAIL;
   LastErrorMsg := 'Index out of bounds';
  End;
 End;
end;

function TGraph.GetInfoCount(out InfoCount: Integer): HResult;
begin
 Result := S_OK;
 InfoCount := 3;
end;

function TGraph.GetLastErrorMsg(OUT Msg: PChar): HResult;
begin
 If LastErrorMsg = '' Then LastErrorMsg := 'No error';
 Msg := LastErrorMsg;
 Result := S_OK;
end;

function TGraph.GetOptional(Index: Integer; out Name, Value,
  Description: PChar): HResult;
begin
 Case Index of
  0: Begin Name := 'Secondary'; value := 'Average'; Description := 'Secondary graph - None, Average (Show the average graph), Extra (Add an xtra graph, Seperate results in the content tag with a "|" char)'; end;
 End;
 Result := S_OK;
end;

function TGraph.GetOptionalCount(out OptionalCount: Integer): HResult;
begin
 Result := S_OK;
 OptionalCount := 1;
end;

function TGraph.Init(PaintDC: THandle): HResult;
Var
 I: Integer;
begin
 fDc := PaintDC;
 Min := 0; Max := 100; Len := 10; Border := clBlack; BAckground := clwhite; AVG := clred; Color := clyellow;
 Secondary := sAvg;
 SetLength(History, Len);
 SetLength(AvgHis, Len);
 For I := 0 to Len-1 do begin
  History[I] := -1;
  AvgHis[I] := -1;
 End;
 Result := S_OK;
end;

function TGraph.Name(out Name: Pchar): HResult;
begin
 Name := 'Graph';
 Result := S_OK;
end;

function TGraph.ObjIns(ParameterName: PChar; out ValueType: Byte;
  out ValueRange: PChar): HResult;
begin
 Result := S_OK;
 If uppercase(ParameterName) = 'LENGTH' then begin
  ValueType := vtUnknown;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'MAX' then begin
  ValueType := vtUnknown;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'BACKGROUND' then begin
  ValueType := vtColor;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'BORDER' then begin
  ValueType := vtColor;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'COLOR' then begin
  ValueType := vtColor;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'AVG' then begin
  ValueType := vtColor;
  ValueRange := '';
 End;
 If uppercase(ParameterName) = 'SECONDARY' then begin
  ValueType := vtFixed;
  ValueRange := 'NONE|AVERAGE|EXTRA';
 End;
end;

function TGraph.Redraw: HResult;
Var
 R: TRect;
 X, I: integer;
 xMultiplier, Multiplier: real;
 T: Integer;
 Canvas : TCanvas;
begin
 Result := S_OK;
 LastErrorMsg := 'No error';
 try
  // Set rect
  R.Left := fleft+1;
  R.Top := fTop+1;
  R.Right := fleft + fWidth-2;
  R.Bottom := fTop + fHeight-2;
  // Def error
  LastErrorMsg := 'Could not create Canvas';
  // Create and setup canvas
  Canvas := TCanvas.Create;
  Canvas.Handle := fDC;
  canvas.Brush.color := background;
  canvas.Pen.Color := border;
  LastErrorMsg := 'Could not draw rectangle';
  // Draw border
  Canvas.Rectangle(fLeft, fTop, fWidth + fleft, fHeight + fTop);
  // Calc multipliers
  multiplier := (fHeight-3) / (Max - Min);
  xmultiplier := (fWidth-3) / Len;
  // Draw Secondary
  If Secondary <> sNone Then begin
   LastErrorMsg := 'Could not draw secondary graph';
   T := AvgHis[0];
   If T > Max then T := Max;
   T := round(T * Multiplier);
   T := ftop + fHeight - T - 2;
   Canvas.Pen.Color := AVG;
   Canvas.MoveTo(fLeft+1, T);
   For I := 0 to len-1 do begin
    T := AvgHis[I];
    If T > Max then T := Max;
    If T < Min then T := Min;
    If T = -1 then T := round(max / 2);
    T := round(T * Multiplier);
    T := ftop + fHeight - T - 2;
    X := Round(I * xMultiplier);
    If I = Len-1 then X := fwidth - 3;
    Canvas.LineTo(X + 1 + fLeft, T);
   End;
  End;
  // Draw primary
  canvas.Pen.Color := color;
  T := History[0];
  If T > Max then T := Max;
  If T < Min then T := Min;
  If T = -1 then T := Max div 2;
  T := round(T * Multiplier);
  T := ftop + fHeight - T - 2;
  LastErrorMsg := 'Could not draw main graph';
  Canvas.MoveTo(fLeft+1, T);
  For I := 0 to len-1 do begin
   T := History[I];
   If T > Max then T := Max;
   If T < Min then T := Min;
   If T = -1 then T := round(max / 2);
   T := round(T * Multiplier);
   T := ftop + fHeight - T - 2;
   X := Round(I * xMultiplier);
   If I = Len-1 then X := fwidth - 3;
   Canvas.LineTo(X + 1 + fLeft, T);
  End;
  LastErrorMsg := 'Could not free canvas';
  Canvas.Free;
 except
  Result := E_FAIL;
 End;
End;

function TGraph.RequiredCoreVersion(out RCV: Word): HResult;
begin
 RCV := 0500;
 Result := S_OK;
end;

function TGraph.SetConfigString(NewConfigString: PChar): HResult;
Var
 I, II: Integer;
 C,V,S: String;
begin
 S := NewConfigString;
 For I := 0 to CountFilt(NewConfigString, '|') do begin
  S := uppercase(GetFilt(NewConfigString, I, '|'));
  C := GetFilt(S, 1, '=');
  V := GetFilT(S, 2, '=');
{  If C = 'MIN' Then begin
   try // Catch converter errors
    Min := StrToInt(V);
   except
    Min := 0;
   end;
  End; }
  If C = 'LENGTH' Then Begin
   try // Catch converter errors
    Len := StrToInt(V);
   except
    Len := 10;
   end;
   If len > fwidth-2 then len := fWidth-2;
   Setlength(History, Len);
   SetLength(AvgHis, Len);
   For II := 0 to Len-1 do begin
    AvgHis[II] := -1;
    History[II] := -1;
   End;
  End;
  If C = 'MAX' Then Begin
   try // Catch converter errors
    Max := StrToInt(V);
   except
    Max := 10;
   end;
  End;
  If C = 'BACKGROUND' Then begin
   Try // Catch converter errors
    Background := StringToColor(V);
   except
    Background := clwhite;
   end;
  end;
  If C = 'BORDER' Then begin
   Try // Catch converter errors
    Border := StringToColor(V);
   except
    Border := clblack;
   end;
  end;
  If C = 'COLOR' Then begin
   Try // Catch converter errors
    Color := StringToColor(V);
   except
    Color := clnavy;
   end;
  end;
  If C = 'AVG' Then begin
   Try // Catch converter errors
    AVG := StringToColor(V);
   except
    AVG := clyellow;
   end;
  end;
  If C = 'SECONDARY' Then begin
   If Uppercase(V) = 'NONE' then Secondary := sNone;
   If (Uppercase(V) = 'AVG') or (Uppercase(V) = 'AVERAGE') Then Secondary := sAvg;
   If (Uppercase(V) = 'EXTENDED') or (Uppercase(V) = 'EXTRA') or (Uppercase(V) = 'GRAPH') Then Secondary := sExtended;
  End;
 End;
 Result := S_OK;
end;

function TGraph.SetDC(const NewDC: THandle): HResult;
begin
 fDc := NewDC;
 Result := S_OK;
end;

function TGraph.SetDimensions(Left, Top, Width, Height: Integer): HResult;
begin
 fLeft := Left;
 fTop := Top;
 fWidth := Width;
 fHeight := Height;
 Result := S_OK;
end;

function TGraph.Setup: HResult;
begin
Result := E_NOTIMPL;
end;

function TGraph.Terminating: HResult;
begin
 Result := S_OK;
end;

function TGraph.Update(SensorStyle: Byte; NewString: PChar; NewValue: Double): HResult;
Var
 A,I: Integer;
 V1, V2: Double;
begin
 V1 := 0; V2 := 0;
 Result := S_OK;
// info('SensorStyle = ' + inttostr(sensorstyle));
 If SensorStyle = 0 then V1 := NewValue;
 If SensorStyle = 1 then begin
  If CountFilt(NewString, '|') = 2 then begin
   Try
    V1 := StrToFloat(GetFilt(NewString, 1, '|'));
    V2 := StrToFloat(GetFilt(NewString, 2, '|'));
   except
   end;
  end;
 End;
// info('V1 = ' + FloatToStr(V1) + #13#10 + 'V2 = ' + FloatToStr(V2));
 // Set first graph
 A := 0;
 For I := 0 to length(History) - 2 do begin
  History[I] := History[I+1];
  A := A + History[I+1];
 End;
 History[Length(History)-1] := Round(V1);
 // If using AVG
 If Secondary = sAVG then begin
  A := A + Round(V1);
  A := Round(A div len);
  For I := 0 to length(AvgHis) - 2 do begin
   AvgHis[I] := AvgHis[I+1];
  End;
  AvgHis[Length(AvgHis)-1] := A;
 End;
 If Secondary = sExtended then begin
  For I := 0 to length(AvgHis) - 2 do begin
   AvgHis[I] := AvgHis[I+1];
  End;
  AvgHis[Length(AvgHis)-1] := Round(V2);
 End;
end;

function TGraph.UpdateFileURL(out URL: Pchar): HResult;
begin
 URL := PChar(AutoUpdateFile);
 Result := S_OK;
end;

function TGraph.Version(out Version: word): HResult;
begin
 Version := 0620;
 Result := S_OK;
end;

initialization
  TComObjectFactory.Create(ComServer, TGraph, Class_Graph,
    'Graph', 'Graph Plugin For CM2', ciMultiInstance, tmApartment);
end.
