(*---------------------------------------------------------------------------
:Program.    Hotkey.mod
:Author.     Thomas Igracki
:Address.    Obstallee 45, 1000 Berlin 20, W-Germany
:E-Mail.     T.IGRACKI@BAMP.ZER
:Version.    V1.0  28-Apr-92  Thomas Igracki
:Version.		      first release
:Version.    V2.0  21-Dec-92  Thomas Wagner [tom]
:Version.		      removed some bugs (no ReplyMsg ...)
:Version.		      and completely revised. Warning:
:Version.		      Interface has changed!
:Copyright.  Thomas Igracki
:Language.   Oberon
:Translator. Amiga Oberon 3.00d
:Remark.     OS2.04 or higher Only!
---------------------------------------------------------------------------*)

MODULE HotKey;
IMPORT
   s: SYSTEM, c: Commodities, e: Exec;
VAR
   nb	    : c.NewBroker;
   broker,
   filter   : c.CxObjPtr;
   ComPort  : e.MsgPortPtr;
   Error -  : SHORTINT;      (* Look here to determine why Init()/InitX() failed *)
   ErrorSet-: LONGSET;	     (* Look here to determine why AddKey failed *)
CONST

(* Error returns in global variable Error from Init() and InitX() *)
  noError	* = c.errOk	;  (* No error                             *)
  systemError	* = c.errSysErr ;  (* System error, no memory, etc         *)
  uniqueError	* = c.errDup	;  (* uniqueness violation                 *)
  wrongVersion	* = c.errVersion;  (* didn't understand NewBroker.version  *)(* should not appear *)
  (* own definitions *)
  calledTwice	   * = -1;	   (* called Init() or InitX() twice *)
  createPortFailed * = -2;	   (* CreateMsgPort() failed *)
  brokerNIL	   * = -3;	   (* No Broker created *)

(* Error returns in global variable ErrorSet from AddKey(). NOTE: multiple might be set! *)
  filterIsNIL	 * = c.coErrIsNull    ;  (* not enough memory to create filter  *)
  nilAttach	 * = c.coErrNullAttach;  (* someone attached NULL to my list    *)
  badDescription * = c.coErrBadFilter ;  (* a bad filter description was given; wrong hotkey descr.  *)
  badType	 * = c.coErrBadType   ;  (* unmatched type-specific operation   *)
  (* own definitions *)
  noPort	 * = MAX(LONGSET);       (* Call Init() or Initx() first *)

(* Message Types *)
  hotkey  * = c.cxmIEvent;
  command * = c.cxmCommand;

(* Commands *)
  cDisable  *= c.cmdDisable;	(* handled automaticly, you should ignore it *)
  cEnable   *= c.cmdEnable;	(* handled automaticly, you should ignore it *)
  cAppear   *= c.cmdAppear;	(* open your window, if you can  *)
  cDisappear*= c.cmdDisappear;	(* go dormant                    *)
  cKill     *= c.cmdKill;	(* go away for good              *)
  cListChg  *= c.cmdListChg;	(* Someone changed the broker list *)
  cUnique   *= c.cmdUnique;	(* someone tried to create a broker
				   with your name. Suggest you appear. *)

 (*NOTE*)(* the following constants are defined wrong in AmigaOberon 3.00 Interfaces *)

 (* Flags for Unique *)
   duplicate  * = {};
   unique     * = 0;   (* will not allow duplicates           *)
   notify     * = 1;   (* sends cUnique to existing broker *)

(****** HotKey/--background-- ******************************************
*
*   DESCRIPTION
*	This module allows easy access to hotkey-specific functions of the
*	commodity.library.
*
*   NOTE
*	DeleteBroker() and CheckCVersion() are the only procedures which
*	are very kind to OS1.x, because ONLY they are supposed to be called on
*	Operating Systems other than Release 2+.
*
*	Therefore you MUST NOT call any of the module's
*	procedures except DeleteBroker() and CheckCVersion() on
*	Systems without commodities.library.
*
*	In addition the version of commodities.library must be 37
*	or higher, because this version is demanded by Oberon 3.00 
*	Interfaces for the commodities.library
*	( OpenLibrary(commodities.library,37) ).
*
*	After version check failed, you may gracefully exit, e.g.
*	  IF ~ CheckCVersion(0) THEN HALT(20) END;
*
*	There should be no need to IMPORT Commodities, because all
*	important constants are redefined. Some constants are even
*	defined wrong in Interfaces for Oberon3.00!
*
*
*   EXAMPLE
*	Refer to HotKeyTest for a example how to use this module.
*
*   AUTHOR
*	Program:  V1.0	28-Apr-92  Thomas Igracki
*			first release
*		  V2.0	21-Dec-92  Thomas Wagner [tom]
*			removed some bugs (no ReplyMsg ...)
*			and completely revised.
*			NOTE: ** Interface has changed! **
*
*	Autodocs: Thomas Wagner [tom]
*
******************************************************************************)

(****** HotKey/CheckCVersion ******************************************
*
*   NAME
*	CheckCVersion -- Checks the version of the Commodities.library.
*
*   SYNOPSIS
*	CheckCVersion (version: SHORTINT): BOOLEAN;
*
*   FUNCTION
*	Check whether the required version is available.
*
*   INPUTS
*	version - The required version. 0 to check if there is
*		  any usable version of commodities.library available.
*		  (See NOTE!).
*
*   RESULT
*	BOOLEAN - TRUE	if Commodity has the required version or higher
*		  FALSE if not available
*
*   NOTE
*	V36 is considered out of date. So if you use the Interfaces for
*	commodity.library delivered with Oberon3.00 (or later versions)
*	CheckCVersion(0) will return FALSE!
*
******************************************************************************)

PROCEDURE CheckCVersion*(version: SHORTINT):BOOLEAN;
BEGIN
  IF c.base = NIL THEN RETURN(FALSE) END;
  RETURN(c.base.version >= version);
END CheckCVersion;


(****** HotKey/Init ******************************************
*
*   NAME
*	Init -- Initiate a broker.
*	InitX -- Initiate a broker (extended parameters).
*
*   SYNOPSIS
*	InitX (name,title,descr: ARRAY OF CHAR;
*	       unique	       : SET;
*	       window	       : BOOLEAN;
*	       pri	       : SHORTINT	): SHORTINT;
*
*	Init (name,title,descr: ARRAY OF CHAR): SHORTINT;
*
*   FUNCTION
*	Initiates some structures needed by all other procedures
*	of this module and installs a broker.
*
*   INPUTS
*	name   - The name of the broker. You should not exceed the length
*		 defined in Commodities.nameLen.
*	title  - Name, version, ... of the commodity. You should not exceed
*		 the maximum length defined in Commodities.titleLen.
*	descr  - A short summary of the functionality of this commodity.
*		 You should not exceed the length defined in
*		 Commodities.descrLength.
*
*	Only for InitX():
*
*	unique - Set to one or more of Flags for Unique.
*		 Init: set to {notify}.
*	window - Set this to TRUE when your commodity is able to open
*		 a window. If this is set to FALSE the Show/Hide gadgets
*		 of the exchange program are deactivated.
*		 Init: set to TRUE.
*	pri    - The priority of the broker. Init: set to 0.
*
*   RESULT
*	SHORTINT - A positive number is the Signalbit for hotkey
*		   activity. -1 indicates an error. See the global
*		   readonly variable Error for the cause.
*		   Here are some important errors:
*		    systemError : usually no memory
*		    calledTwice : called this routine twice
*		    brokerNIL	: usually already another broker with
*				  same name
*
*   NOTES
*	- After Init() or InitX() you must call Activate() to activate
*	  the broker.
*	- You can't call Init() twice.
*
*   SEE ALSO
*	DeleteBroker()
*
******************************************************************************)

PROCEDURE InitX* (name,title,descr: ARRAY OF CHAR;
		  unique	  : SET;
		  window	  : BOOLEAN;
		  pri		  : SHORTINT	   ): SHORTINT;
		 (* $CopyArrays- *)
VAR err: LONGINT;
BEGIN
     IF ComPort # NIL THEN Error := calledTwice; RETURN -1 END;
     ComPort := e.CreateMsgPort();
     IF ComPort = NIL THEN Error := createPortFailed; RETURN -1 END;
     nb.version := c.nbVersion;
     nb.name := s.ADR(name);
     nb.title := s.ADR(title);
     nb.descr := s.ADR(descr);
     nb.unique := unique;
     IF window THEN nb.flags := {c.showHide} END;
     nb.pri := pri;
     nb.port := ComPort;
     broker := c.CxBroker (nb,err);
     IF err = c.errOk THEN
       IF broker # NIL THEN
	 RETURN ComPort.sigBit;
       ELSE
	 Error := brokerNIL; RETURN -1;
       END;
     ELSE
       Error := SHORT(SHORT(err)); RETURN -1;
     END;

END InitX;

PROCEDURE Init* (name,title,descr: ARRAY OF CHAR): SHORTINT;
		(* $CopyArrays- *)
BEGIN
  RETURN(InitX(name,title,descr,{notify},TRUE,0));
END Init;


(****** HotKey/AddKey ******************************************
*
*   NAME
*	AddKey -- Add a hotkey description to the broker
*
*   SYNOPSIS
*	AddKey (descr: ARRAY OF CHAR; ID: LONGINT):BOOLEAN
*
*   FUNCTION
*	Adds the given hotkey to the broker.
*
*   INPUTS
*	descr - containing the hotkey description
*	ID    - any number to recognize this hotkey. Not
*		used by the module or by the operating system.
*
*   RESULT
*	BOOLEAN - indicates the success of the hotkey installation. If
*		  FALSE, ErrorSet contains the error code.
*		  Note: Multiple error code bits are possible.
*		  The most important error:
*		   badDescription: wrong hotkey description
*
*   SEE ALSO
*	GetCMsg()
*
******************************************************************************)

PROCEDURE AddKey* (descr: ARRAY OF CHAR; ID: LONGINT):BOOLEAN; (* $CopyArrays- *)
VAR filter: c.CxObjPtr;
BEGIN
     ErrorSet := LONGSET{};
     IF ComPort = NIL THEN ErrorSet := LONGSET{noPort}; RETURN(FALSE) END;
     filter := c.CxFilter (s.ADR(descr));
     (* No NIL-check of filter, because addErr = c.coErrIsNull in this case *)
     ErrorSet := c.CxObjError(filter);
     IF ErrorSet # LONGSET{} THEN RETURN(FALSE) END;
     c.AttachCxObj (filter, c.CxSender (ComPort, ID));
     c.AttachCxObj (filter, c.CxTranslate (NIL));
     c.AttachCxObj (broker,filter);
     RETURN(TRUE);
END AddKey;


(****** HotKey/Activate ******************************************
*
*   NAME
*	Activate -- (De)activates hotkeys
*
*   SYNOPSIS
*	Activate (ON: BOOLEAN)
*
*   FUNCTION
*	This function activates or deactivates the broker and its hotkeys.
*
*   INPUTS
*	ON - TRUE to activate, FALSE to deactivate
*
******************************************************************************)

PROCEDURE Activate* (ON: BOOLEAN);
BEGIN	  s.SETREG (0,c.ActivateCxObj (broker,s.VAL(SHORTINT,ON)));
END Activate;


(****** HotKey/GetCMsg ******************************************
*
*   NAME
*	GetCMsg -- Get commodity message
*
*   SYNOPSIS
*	GetCMsg(VAR type: LONGSET; VAR id: INTEGER):BOOLEAN
*
*   FUNCTION
*	If you get the signal (value returned by Init()) then you must
*	call GetCMsg() to determine which hotkey was pressed or which
*	command was sent.
*
*   INPUTS
*	VAR type - Contains hotkey if an Hotkey was pressed or command
*		   if a command was sent. Note: cDisable and cEnable
*		   are handled automatically by this module. So you
*		   should ignore them, except you want to add some
*		   more functionality to them.
*	VAR id	 - An integer value to store the id of a hotkey (if there
*		   is any Msg). Only valid if type equals command.
*
*   RESULT
*	BOOLEAN - TRUE if there was a Msg and type and id is valid.
*		  Call GetCMsg() again, because there might be more Msg's.
*
*   EXAMPLE
*	IMPORT Exec, HotKey;
*	VAR HotID  : LONGINT;
*	    HotType: LONGSET;
*	    HotSig : SHORTINT;
*	    Signals: SHORTINT;
*	...
*	IF ~HotKey.CheckCVersion(0) THEN HALT(20) END;
*	HotSig := HotKey.Init(...);
*	...
*	Signals := Exec.Wait(LONGSET{HotSig});
*	...
*	IF HotSig IN Signals THEN
*	  WHILE HotKey.GetCMsg(HotType,HotID) DO
*	    IF HotKey.hotkey IN HotType THEN
*	      CASE HotID OF
*		...
*	      END; (* CASE *)
*	    ELSIF HotKey.command IN HotType THEN
*	      CASE HotID OF
*		hot.cAppear   : (* ShowInterface *) |
*		hot.cDisappear: (* HideInterface *) |
*		hot.cKill     : (* Quit          *) |
*		hot.cUnique   : (* ShowInterface or Quit *) |
*		ELSE		(* ignore cDisable and cEnable *)
*	      END; (* CASE *)
*	    END; (* IF *)
*	  END; (* WHILE *)
*	END; (* IF *)
*
*	For a full example see HotKeyTest.mod.
*
******************************************************************************)

PROCEDURE GetCMsg*(VAR type: LONGSET; VAR id: LONGINT):BOOLEAN;
VAR msg : s.ADDRESS; (* should be c.CxMsgPtr, but CxMsg is NO expansion of Msg *)
BEGIN
     IF ComPort = NIL THEN RETURN(FALSE) END;
     msg := e.GetMsg (ComPort);
     IF msg # NIL THEN
       type := c.CxMsgType(msg);
       id := c.CxMsgID(msg);
       e.ReplyMsg(msg);
       IF command IN type THEN
	 CASE id OF
	   cDisable : Activate(FALSE) |
	   cEnable  : Activate(TRUE)  |
	   ELSE
	 END;
       END;
       RETURN(TRUE)
     ELSE
       RETURN(FALSE);
     END;
END GetCMsg;


(****** HotKey/DeleteBroker ******************************************
*
*   NAME
*	DeleteBroker -- Removes a broker
*
*   SYNOPSIS
*	DeleteBroker()
*
*   FUNCTION
*	This function removes the broker (and all attached hotkeys) and
*	closes the MsgPort.
*
*   NOTES
*	This function is automatically called. No need to call it
*	yourself except you want to delete the broker earlier.
*
*   SEE ALSO
*	Init()
*
******************************************************************************)

PROCEDURE DeleteBroker*;
VAR msg : e.MessagePtr;
BEGIN
    IF c.base # NIL THEN (* Skip these library calls if OS < 2.04 *)
      c.DeleteCxObjAll (broker) ; broker  := NIL;
      IF ComPort # NIL THEN
	REPEAT
	  msg:=e.GetMsg(ComPort);
	  IF msg # NIL THEN e.ReplyMsg(msg) END;
	UNTIL msg = NIL;
      END;
      e.DeleteMsgPort (ComPort) ; ComPort := NIL;
    END;
END DeleteBroker;

CLOSE
    DeleteBroker
END HotKey.
