////////////////////////////////////////////////////////////////////////////////
// CM2FormUnit.pas
// ---------------
// This unit contains the forms that CM2 will draw on.
//
// Last Edit: June, 20th 2006
// Certified: NA
//
// This unit is coded in : Delphi 7
//
// Unit is copyright (c) 2003 - 2006, The CoolMon Project
////////////////////////////////////////////////////////////////////////////////
{$D+}
unit CM2FormUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ShellApi, CM2VisPlugTypes, ExtCtrls, CM2PlugInTypes;

type
  TDBLClickHolder = Record
   X: Integer;
   Y: Integer;
   TimeStamp: TDateTime;
  end;
  TWindowMode = (wmUnknown, wmPinToDesktop, wmAOT);
  TCM2Form = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormPaint(Sender: TObject);
    procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure FormMouseWheel(Sender: TObject; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
    procedure FormMouseWheelDown(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
    procedure FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
      MousePos: TPoint; var Handled: Boolean);
    procedure FormActivate(Sender: TObject);
  private
    FVisualID: String;
    fBottom: Integer;
    fRight: Integer;
    function GetBlendValue: Byte;
    function GetPinToDesktop: Boolean;
    function GetTransparent: Boolean;
    procedure SetBlendValue(const Value: Byte);
    procedure SetTransparent(const Value: Boolean);
    function GetDCint: THandle;
    function GetAlwaysOnTop: Boolean;
    Function GetMouseOnForm: TPoint;
    Function GetVisualObjectsAtPoint(FormCoordinates: TPoint; Var VOList: TpVOList): Boolean;
    Function GetModState: TModifierKeyState;
    Procedure EventHandler(EventType: TEventType);
    procedure SetVisualID(const Value: String);
    procedure SetBottom(const Value: Integer);
    procedure SetRight(const Value: Integer);
    { Private declarations }
  public
    // Events
    TriggerList: TTriggerList;
    // Double click holder item
    DoubleClickHolder: TDBLClickHolder;
    // Window Modes
    WindowMode: TWindowMode;
    VISObjectList: TVISObjectList;
    MgrOwner: TObject;
    LeftBackup: Integer;
    TopBackup: Integer;
    WidthBackup: Integer;
    Heightbackup: Integer;
    Property PinToDesktop: Boolean read GetPinToDesktop;
    Property AlwaysOnTop: Boolean read GetAlwaysOnTop;
    procedure CheckforWM; // Window Mode
    // The rest
    Property Transparent: Boolean read GetTransparent write SetTransparent;
    Property BlendValue: Byte read GetBlendValue write SetBlendValue;
    Property DC: THandle read GetDCint;
    Function AddVisObject(GUID: TGUID): PVISObject;
    Function QueryVisObject(NAme: String; Var VisObj: PVisObject): Boolean;
    Property VisualID: String read FVisualID write SetVisualID;
    Procedure ReInitAllDC;
    Procedure ReInitWindowPosition;
    Property Right: Integer Read fRight write SetRight;
    Property Bottom: Integer read fBottom write SetBottom;
  end;

var
  CM2Form: TCM2Form;

Const
 SWP_NOCHANGE = SWP_NOMOVE or SWP_NOSIZE or SWP_SHOWWINDOW or SWP_NOACTIVATE;

implementation

uses verbose, CM2EventTypes, CM2FormManager;

{$R *.dfm}

{ TCM2Form }

function TCM2Form.GetBlendValue: Byte;
begin
 Result := Self.AlphaBlendValue;
end;

function TCM2Form.GetDCint: THandle;
begin
 Result := GetDC(Self.Handle);
end;

function TCM2Form.GetPinToDesktop: Boolean;
begin
 Result := WindowMode = wmPinToDesktop;
end;

function TCM2Form.GetTransparent: Boolean;
begin
 Result := Self.TransparentColor;
end;

procedure TCM2Form.SetBlendValue(const Value: Byte);
begin
 Self.AlphaBlendValue := Value;
 Self.AlphaBlend := Self.AlphaBlendValue < 255;
end;

procedure TCM2Form.SetTransparent(const Value: Boolean);
Var
 I: Integer;
begin
 Self.TransparentColor := Value;
 If Value then Self.TransparentColorValue := Self.Color;
 For I := 0 to Self.VISObjectList.Count - 1 do begin
  VISObjectList[I].SetDC(self.handle);
 End;
end;

procedure TCM2Form.FormCreate(Sender: TObject);
begin
 Visible := false;
 fRight := -1;
 fBottom := -1;
 TriggerList := TTriggerList.Create;
 VISObjectList := TVisObjectList.Create(Self.Handle, self);
 // Hide window from the alt+tab list
 SetWindowLong(Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
 // According to testing and forum posts, next line should be part of the hide
 // from alt+tab routine, however if included it will no longer be possible to
 // hide CM2 from the taskbar
 { SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE)
    or WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW); }
end;

procedure TCM2Form.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
 FreeAndNil(VISObjectList);
 FreeAndNil(TriggerList);
end;

function TCM2Form.AddVisObject(GUID: TGUID): PVISObject;
begin
 Result := Self.VISObjectList.AddP(GUID);
end;

function TCM2Form.QueryVisObject(Name: String;
  Var VisObj: PVisObject): Boolean;
Var
 I: Integer;
begin
 Result := False;
 For I := 0 to Self.VISObjectList.Count - 1 do begin
  If Name = Self.VISObjectList[I].Name Then begin
   VisObj := Self.VISObjectList.PVISObject[I];
   Result := True;
   Exit;
  End;
 End;
end;

procedure TCM2Form.FormPaint(Sender: TObject);
Var
 I: Integer;
begin
 For I := 0 to Self.VISObjectList.Count - 1 do begin
  VISObjectList[I].Paint;
 End;
end;

procedure TCM2Form.ReInitAllDC;
Var
 I: Integer;
begin
 For I := 0 to self.VISObjectList.Count - 1 do begin
  VISObjectList[I].SetDC(handle);
 End;
end;

procedure TCM2Form.FormMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
Var
 et: TEventType;
 Msec, W: Word;
begin
 CheckForWM;
 // Eventhandler
 et := etUnknown;
 Case Button of
  mbLeft: et := etMouseUpLeft;
  mbRight: et := etMouseUpRight;
  mbMiddle: et := etMouseUpMiddle;
 end;
 EventHandler(et);
 If (Button = mbLeft) and (DoubleclickHolder.X = X) and (DoubleClickHolder.Y = Y) then begin
  // Check for doubleclick (left)
  DecodeTime(Now - DoubleClickHolder.TimeStamp, w, w, w, MSec);
  If MSec <= GetDoubleClickTime then begin
   EventHandler(etDblClick);
  end;
  DoubleClickHolder.X := -1;
  DoubleClickHolder.Y := -1;
  DoubleClickHolder.TimeStamp := 0;
 end
  else
 begin
  DoubleClickHolder.X := X;
  DoubleClickHolder.Y := Y;
  DoubleClickHolder.TimeStamp := Now;
 end;
end;

procedure TCM2Form.CheckforWM;
begin
 If Debug then Form4.Add('TCM2Form.CheckForWM ['+ Self.NAme+ '] ... ', dlError);
 If not Visible then begin
  If Debug then Form4.Append('Form currently not showing', dlerror);
  exit;                        
 end;
 If PinToDesktop then begin
  SetWindowPos(handle, hwnd_bottom,0,0,0,0,SWP_NOCHANGE);
  If Debug Then Form4.Append('Pin To Desktop', dlError);
  exit;
 end;
 If AlwaysOnTop then begin
  SetWindowPos(handle, hwnd_TopMost,0,0,0,0,SWP_NOCHANGE);
  If Debug Then Form4.Append('Always on top', dlError);
  exit;
 end;
end;

function TCM2Form.GetAlwaysOnTop: Boolean;
begin
 Result := windowMode = wmAOT;
end;

function TCM2Form.GetMouseOnForm: TPoint;
Var
 Mouse: TMouse;
begin
 Mouse := TMouse.Create;
 Result.X := Mouse.CursorPos.X - Left;
 Result.Y := Mouse.CursorPos.Y - Top;
 FreeAndNil(Mouse);
end;

function TCM2Form.GetVisualObjectsAtPoint(FormCoordinates: TPoint;
  var VOList: TpVOList): Boolean;
Var
 I: Integer;
begin
 If not assigned(VOList) then VOList := TpVOList.Create;
 For I := 0 to self.VISObjectList.Count - 1 do begin
  If (FormCoordinates.X >= VISObjectList[I].Left) and (FormCoordinates.X <= VISObjectList[I].Left + VisObjectList[I].Width) then begin
   If (FormCoordinates.Y >= VISObjectList[I].Top) and (FormCoordinates.Y <= VISObjectList[I].Top + VISObjectList[I].Height) then begin
    VOList.Add(VISObjectList.PVISObject[I]);
   End;
  End;
 End;
 Result := VOList.Count > 0;
end;

function TCM2Form.GetModState: TModifierKeyState;
Var
 A, S, C: Boolean;
begin
 A := AltDown;
 S := ShiftDown;
 C := CtrlDown;
 // mksNone, mksShift, mksAlt, mskCtrl, mksAltShift, mksAltCtrl, mksCtrlShift, mksAltCtrlShift
 Result := mksUnknown;
 If (Not A) and (Not C) and (Not S) Then Result := mksNone;
 If Not(A) and Not(C) and S then REsult := mksShift;
 If A and (Not C) and (Not S) Then Result := mksAlt;
 If Not(A) and C and Not(S) then Result := mksCtrl;
 If A and Not(C) and S Then Result := mksAltShift;
 If A and C and (Not S) then Result := mksAltCtrl;
 If (Not A) and C and S Then Result := mksCtrlShift;
 If A and C and S then Result := mksAltCtrlShift;
end;

procedure TCM2Form.EventHandler(EventType: TEventType);
Var
 MousePoint: TPoint;
 ModState: TModifierKeystate;
 VOList: TpVOList;
 DoEventsOnForm: Boolean;
 S: String;
 I, I2: Integer;
 Trigger: TTrigger;
begin
 MousePoint := GetMouseOnForm;
 ModState := GetModState;
 VOList := TpVOList.Create;

 DoEventsOnForm := Not(GetVisualObjectsAtPoint(MousePoint, VOList));

 If Debug then Begin
  S := Name + '.EventHandler; ';
  S := S + 'Event: ' + EventTypeToStr(EventType) + '; ';
  S := S + 'X:' + IntToStr(MousePoint.X) + '; Y:' + IntToStr(MousePoint.Y) + '; ';
  S := S + 'ModState: ' + ModStateToStr(ModState) + '; ';
  S := S + 'VO Count: ' + IntToStr(VOList.Count);
  If EventType <> etMouseMove then Form4.Add(S, dlError);
 End;
 If DoEventsOnForm Then begin
  // Process event on the form itself
  For I := 0 to TriggerList.Count - 1 do begin
   If Triggerlist[I].EventType = EventType then begin
    ExecuteEvent('', MousePoint.X, MousePoint.Y, TriggerList[I], MgrOwner as TCM2FormMgr);
   end;
  end;
 End
  else
 begin
  // Process event on VO's in the VOlist
  For I := 0 to VOList.Count - 1 do begin
   If VOList[I].InterfaceVersion > 2 then begin
    If VOList[I].DoNotifyOfEvent(EventType) then begin
     Trigger.EventType := EventType; Trigger.ModKeys := ModState;
     VOList[I].OnEvent('', MousePoint.X, MousePoint.Y, Trigger);
    end;
   end;
   For I2 := 0 to VOList[I].TriggerList.Count - 1 do begin
    If (VOList[I].TriggerList[I2].EventType = EventType) and ((ModState = mksAny) or (VOList[I].TriggerList[I2].ModKeys = ModState)) Then begin
     // Event located - Trigger here
     If (Self.MgrOwner Is TCM2FormMgr) then begin
      ExecuteEvent('', MousePoint.x, MousePoint.y, VOList[I].TriggerList[I2], (Self.MgrOwner As TCM2FormMgr));
     end
      else
     begin
      MessageDlg('Unable to perform Visual event, Form manager doesn''t exists', mtWarning, [mbOK], 0);
     end;
    End;
   end;
  end;
 End;

 FreeAndNil(VOList);
end;

procedure TCM2Form.FormMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
Var
 et: TEventType;
begin
 et := etUnknown;
 Case Button of
  mbLeft: et := etMouseDownLeft;
  mbRight: et := etMouseDownRight;
  mbMiddle: et := etMouseDownMiddle;
 end;
 EventHandler(et);
end;

procedure TCM2Form.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
 EventHandler(etMouseMove);
end;

procedure TCM2Form.FormMouseWheel(Sender: TObject; Shift: TShiftState;
  WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
begin
 EventHandler(etMouseWheelAny);
end;

procedure TCM2Form.FormMouseWheelDown(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin
 Handled := true;
 EventHandler(etMouseWheelDown);
end;

procedure TCM2Form.FormMouseWheelUp(Sender: TObject; Shift: TShiftState;
  MousePos: TPoint; var Handled: Boolean);
begin
 Handled := true;
 EventHandler(etMouseWheelUp);
end;

procedure TCM2Form.SetVisualID(const Value: String);
begin
  FVisualID := Value;
end;

procedure TCM2Form.ReInitWindowPosition;
begin
 Left := LeftBackup;
 Top := TopBackup;
 Width := WidthBackup;
 Height := HeightBackup;
end;

procedure TCM2Form.FormActivate(Sender: TObject);
begin
 CheckForWM;

end;

procedure TCM2Form.SetBottom(const Value: Integer);
begin
  fBottom := Value;
  Top := Screen.DesktopHeight - Value - Height;
end;

procedure TCM2Form.SetRight(const Value: Integer);
begin
  fRight := Value;
  Left := Screen.DesktopWidth - Value - Width;
end;

end.
