2.1 How to Write a C3PO

GroupWise Custom 3rd-Party Object (C3PO) allows you to alter the GroupWise client user interface. You can add buttons to the toolbar, add menus and menu items to a menu, and create custom functionality for predefined client commands, such as Open or Compose. There are over 20 predefined GroupWise commands whose functionality you can either replace or enhance.

This section provides generic instructions on how to write C3PO. You can also use the Tools section to create your C3PO.

2.1.1 Message Types

GroupWise has six message types. Each message type is defined as GW.MESSAGE. The types are:

  • GW.MESSAGE.APPOINTMENT
  • GW.MESSAGE.DOCUMENTREFERENCE
  • GW.MESSAGE.MAIL
  • GW.MESSAGE.NOTE
  • GW.MESSAGE.PHONE
  • GW.MESSAGE.TASK

In addition, GroupWise defines the following two subtypes of the GW.MESSAGE.MAIL context:

  • GW.MESSAGE.MAIL.Internet
  • GW.MESSAGE.MAIL.NGW.DISCUSS

Your C3PO can subclass any of the GroupWise message types to create a new custom class of your own. To do this, you pick one of the existing classes that fits the class you wish to create, then you subclass that class. For example:

  • GW.MESSAGE.NOTE.MYMESSAGE

You can use any name in place of MYMESSAGE. A new message of this type will have all the properties of a NOTE plus any others you give it.

Your C3PO can associate a custom icon with your custom class so that the user can distinguish between your custom message and others. Your C3PO can tell the C3PO Manager that you wish to be notified of certain events that occur. You can be notified of startup (eGW_CMDEVTID_READY), shutdown (eGW_CMDEVTID_SHUTDOWN), delivery of a certain message (eGW_CMDEVTID_DELIVERY), or overflow (eGW_CMDEVTID_OVERFLOW) conditions.

To summarize, with a C3PO you can create a custom message class of GW.MESSAGE.NOTE.MYMESSAGE and take control of the Open function of that class. Then, any time a user opens a message of your class, your C3PO will handle the Open.

You can control as much or as little of the manipulation of your custom class as you wish. You can add a menu item to File > New in the browser that, when selected, calls your C3PO to create a new message of your custom class type. Your C3PO can be seamlessly integrated into the GroupWise client user interface.

2.1.2 OLE COM Server

A C3PO is an OLE COM server. You can write a C3PO using any language that supports OLE or COM. But because a C3PO is a server, it makes writing a C3PO a little different than using most other APIs. You must create objects that the GroupWise C3PO Manager is looking for and that perform functions required by the manager.

The first C3PO OLE COM server object is C3POServer. It is the only object that is required.

In Visual Basic you create a C3POServer class with the following properties and methods:

  • Public Property Get CommandFactory( ) As CommandFactory
  • Public Property Get Description( ) As String
  • Public Property Get EventMonitor( ) As EventMonitor
  • Public Property Get IconFactory( ) As IconFactory
  • Public Function CanShutdown( ) As Boolean
  • Public Sub DeInit( )
  • Public Sub Init(objGWManager As Object)

In Delphi you define a class and then instantiate it. The class would look like the following:

C3POServer = class(TAutoObject) 
     private 
          { Private declarations } 
          function GetCmdFact: Variant; 
          function GetDescription : string; 
          function GetEventMonitor : variant; 
          function GetIconFactory : variant; 
     automated 
          { Automated declarations } 
          property CommandFactory : Variant read GetCmdFact; 
          property Description: string read GetDescription; 
          property EventMonitor: variant read GetEventMonitor; 
          property IconFactory: variant read GetIconFactory; 
          function CanShutdown: TOleBool; 
          procedure DeInit; 
          procedure Init(Manager: variant); 
     end;

Make sure that you create the routines associated with the new class.

2.1.3 Properties and Methods

The following are C3PO methods:

Method

Description

Init

This is called first by the C3PO Manager. The main purpose is to pass in the Manager object. One of the properties of the Manager object is the ClientState object, which object is used to find out the current state of the GroupWise client.

CanShutdown

This is called when the GroupWise client needs to shut down. You need to return a TRUE or FALSE. You are telling the Manager if it is all right to shut down. If you return a TRUE value, the Client will proceed with the shutdown. If you return a FALSE value, the Manager will poll you until you return a TRUE value.

DeInit

This is called to allow the C3PO to release any holds that still exist on any objects. DeInit terminates the relationship of the C3PO Manager with the C3PO.

The following are C3PO properties:

Property

Description

Description

This contains a short description of the C3PO.

CommandFactory

This contains the CommandFactory object. If this property is NULL, the Manager assumes that the C3PO does not want to have any CommandFactory functionality. CommandFactory is the object that allows you to add menus and menu items, add buttons to the toolbar, or take over predefined GroupWise commands.

EventMonitor

This contains the EventMonitor object. If this property is NULL, the Manager assumes that the C3PO does not want to have any EventMonitor functionality. EventMonitor allows the C3PO to handle events such as Ready, Shutdown, Delivery, and Overflow.

IconFactory

This contains the IconFactory object. If this property is NULL, the Manager assumes that the C3PO does not want to have any IconFactory functionality. IconFactory allows the C3PO to associate icons with custom message classes.

2.1.4 Custom Icons

Suppose you want to have a custom icon associated with your custom message class. You would create an IconFactory object and return it to the Manager when it calls your Get IconFactory function. The only method in the GetIcons object is the GetIcons method. In Visual Basic it would look like the following:

Public Sub GetIcons(sGWObjClass As String, 
                    psGWIconFile As String, 
                    plGWUnOpenIcon As Long, 
                    plGWOpenIcon As Long) 
psGWIconFile = "icons.dll"    ' set the icon file name 
plGWUnOpenIcon = 1            ’ set the unopen icon index 
plGWOpenIcon = 0                ' set the open icon index

The variable passed in sGWObjClass is a string that is the class of the message that the client is going to paint. You must do the following:

  1. Check to make sure it is the class you want to have your new icon associated with.

  2. Pass back to the Manager the full path name of the .EXE or .DLL that contains the icon information for your custom icon.

  3. Define 16x16 and 32x32 icons.

  4. Return the index of the icon you want associated with both opened and unopened messages.

You also need to register your C3PO so that the Manager knows you want to have a custom icon associated with a custom message class. You need to register under:

[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\ 
GW.MESSAGE.MAIL.NEWCLASS\MYC3PO\Objects]

GW.MESSAGE.MAIL.NEWCLASS is the name of the new class that you want to have your icon associated with. MYC3PO is the name of your C3PO. The string value you need for this key is IconFactory.

2.1.5 C3PO Events

To set up your C3PO to handle GroupWise events, create an EventMonitor object and pass it back to the Manager in your GetEventMonitor function. EventMonitor has only one method: Notify. In Visual Basic it would look like the following:

Public Sub Notify(sGWContext As String, objGWEvent As Object) 
Dim res 
     Select Case objGWEvent.PersistentID 
          Case eGW_CMDEVTID_READY 
               'Check for Ready Event 
               ’This is were you put Ready code. 
          res = MsgBox(objGWEvent.PersistentID, vbOKOnly, sGWContext) 
          Case eGW_CMDEVTID_SHUTDOWN 
               'Check for Shutdown Event 
               ’This is were you put Shutdown code. 
          res = MsgBox(objGWEvent.PersistentID, vbOKOnly, sGWContext) 
          Case eGW_CMDEVTID_OVERFLOW 
               'Check for Overflow Event 
               ’This is were you put Overflow code. 
          res = MsgBox(objGWEvent.PersistentID, vbOKOnly, sGWContext) 
          Case eGW_CMDEVTID_DELIVERY 
               'Check for Delivery Event 
               ’This is were you put Delivery code. 
               If sGWContext = "GW.MESSAGE.MAIL.XXXX" Then 
                    ’Check for correct context 
               res = MsgBox(objGWEvent.PersistentID, vbOKOnly,
                                    sGWContext) 
               End If 
          Case Else 
               MsgBox "Unsupported Case" 
          End Select 
     End Sub

This Notify routine handles all four possible C3PO events: Ready, Shutdown, Overflow, and Delivery.

Notice in the preceding Visual Basic example that Notify passes in sGWContext, which is the class of the message that was delivered. Notify also passes in the objGWEvent object, which has a PersistentID property that tells you which events your C3PO is being called for, so it can handle it. For delivery, the context is also checked to make sure it is the desired message class.

2.1.6 Adding Menus, Menu Items, Buttons and Predefined Commands

The CommandFactory object handles these functions. You must create a CommandFactory object and return it in your GetCommandFactory function in the C3POServer object. The CommandFactory object has six methods you need to support. They are:

Public Function Init(lGWLCID As Long) As Long

Public Function WantCommand(sGWContext As String, 
                            sGWPersistentID As String) 
                            As Boolean

Public Function BuildCommand(sGWContext As String, 
                             sGWPersistentID As String, 
                             objGWBaseCommand As Object, 
                             objGWParameter As Object) 
                             As Object

Public Function CustomizeMenu(sGWContext As String, 
                              objGWMenu As Object) 
                              As Boolean

Public Sub CustomizeContextMenu(sGWContext As String, 
                                objGWMenu As Object)

Public Function CustomizeToolbar(sGWContext As String, 
                                 objGWToolbar As Object) 
                                 As Boolean

Init

The Init routine is called first. It is an optimization to help speed up your C3PO. You pass back a set of flags that tell the Manager what to do. To only change menus, do the following:

Init = eGW_CMDINIT_MENUS

To add menus and buttons, use the following:

Init = eGW_CMDINIT_MENUS + eGW_CMDINIT_TOOLBARS

The possible flags are:

  • eGW_CMDINIT_MENUS
  • eGW_CMDINIT_TOOLBARS
  • eGW_CMDINIT_CONTEXT_MENUS
  • eGW_CMDINIT_NO_PREDEFINED

WantCommand, BuildCommand, and GWCommand

These methods work together. If you have registered your C3PO to handle one of the GroupWise pre-built commands your WantCommand method will be called. For a complete list of the possible pre-built commands, see C3PO Data Type Related Identifiers.

WantCommand

If you create a new custom message class of NEWCLASS that is sub-classed from the MAIL message class, then when a user opens any message of this class type, you will want to handle the open function. If you name your C3PO as MYC3PO, you would register your C3PO in the following manner:

[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\ 
GW.MESSAGE.MAIL.NEWCLASS\MYC3PO\Objects]
  • Under the Objects key you would have a string value of CommandFactory.
[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\ 
GW.MESSAGE.MAIL.NEWCLASS\MYC3PO\Events]
  • Under the Events key you would have a string value of GW#C#OPEN.

With your system registry set up like this, when a user opens a message of class GW.MESSAGE.MAIL.NEWCLASS, your WantCommand will be called. The WantCommand passes in two variables:

sGWContext

The class of the message.

sGWPersistentID

The type of predefined command that the user is attempting to perform.

A check is made to ensure that sGWContext contains the message class you are interested in and that sGWPeristentID is eGW_CMD_OPEN. If both of these conditions are TRUE, a TRUE value is returned for the method. By doing this, the Manager is informed that you are going to take over the Open function for this message.

BuildCommand

The BuildCommand method is called next. Again, sGWContext and sGWPeristentID are checked to ensure that the correct message and command are being used. You then need to build a GWCommand object and return it to the Manager. The code to build a GWCommand in the BuildCommand method would look like the following:

Dim GWCmd As New GWCommand 
     ’ Check for correct context before creating GWCommand object 
     If sGWContext = COMMANDCONTEXT0 Then 
    ’ Check for the correct persistent ID to create GWCommand object 
          If sGWPersistentID = eGW_CMD_OPEN Then 
                    ’ Set persistent ID for GWCommand object 
               Let GWCmd.PersistentID = 1 
                    ’ Save base GWCommand for later use 
               Set GWCmd.BaseCmd = objGWBaseCommand 
               Set BuildCommand = GWCmd 
                    ' Return GWCommand created 
          End If 
     End If

GWCommand

The GWCommand object is the object that the Manager uses to perform a GroupWise command. It looks like the following:

Public Property Get BaseCmd( ) As Object 
Public Property Set BaseCmd(objNewBaseCmd As Object) 
Public Property Get LongPrompt( ) As String 
Public Property Let LongPrompt(sNewLongPrompt As String) 
Public Property Get Parameters( ) As Object 
Public Property Get PersistentID( ) As String 
Public Property Let PersistentID(sNewPersistentID As String) 
Public Property Get ToolTip( ) As String 
Public Property Let ToolTip(sNewToolTip As String) 
Public Sub Execute( ) 
Public Sub Help( ) 
Public Sub UnDo( ) 
Public Function Validate( ) As Long

BaseCmd: A property containing the GWCommand for client functionality.

LongPrompt: The string that will be displayed in the client when the user puts the cursor on a menu item.

ToolTip: The string that will be displayed in the client when the user puts the cursor on the toolbar button.

PersistentID: The ID of the GWCommand.

CustomizeMenu

The CustomizeMenu method allows your C3PO to add menus and menu items to the GroupWise client menus. If in the Init method you have set the bit telling the Manager that you want to change menus, this routine will be called any time the menu that you have registered to change is built.

You first need to decide were you want to place the menu. There are several options:

Option

Description

GW.CLIENT

All client views.

GW.CLIENT.WINDOW.ATTACHVIEWER

Attachment viewer.

GW.CLIENT.WINDOW.BROWSER

Browser window.

GW.CLIENT.WINDOW.CALENDAR

All calendar views.

GW.CLIENT.WINDOW.DOCUMENTLIST

Document list window.

GW.CLIENT.WINDOW.FINDRESULTS

Query results window.

GW.CLIENT.WINDOWS.PROPERTIES

Properties window.

GW.MESSAGE

All message types and their windows.

GW.MESSAGE.APPOINTMENT[.xx]

Appointments and their windows.

GW.MESSAGE.DOCUMENTREFERENCE

Document references.

GW.MESSAGE.MAIL[.xx]

Mail messages and their windows.

GW.MESSAGE.NOTE[.xx]

Notes and their windows.

GW.MESSAGE.PHONE[.xx]

Phone messages and their windows.

GW.MESSAGE.TASK[.xx]

Tasks and their windows.

If you want to place a new client menu item under the File > New menu, you would register it in the following manner:

[HKEY_LOCAL_MACHINE\SOFTWARE\Novell\GroupWise\5.0\C3PO\DataTypes\ 
GW.CLIENT.WINdOW.BROWSER\MYC3PO\Objects]

The string under the Objects key would be CommandFactory. Registered this way, your CustomizeMenu routine will be called. The Manager passes in to the CustomizeMenu routine the context that is being called for, and you need to check to be sure it is the right context. Also, the Manager passes in the GroupWise main menu object. That object is taken and used to find the menu you want to add to, and then the new menu item is added. The following is how to do this in Visual Basic:

Public Function CustomizeMenu(sGWContext As String, objGWMenu As Object) 
            As Boolean 
     Dim Menu As Object 
     If sGWContext = "GW.CLIENT.WINDOW.BROWSER" Then 
               ' Check for correct context 
          Set Menu = objGWMenu 
               ' Get Main menu object 
          Set Menu = Menu.MenuItems.Item("File") 
                ' get menu File 
          Dim Cmd01 As New GWCommand  
               ' Build GWCommand object 
          Let Cmd01.PersistentID = XXXX 
               ' Set persistent ID for Custom menu in GWCommand object 
          Let Cmd01.LongPrompt = "Create a new message of type
                                           GW.MESSAGE.MAIL.XXXX" 
               ’ set long prompt for menu item 
          Call Menu.MenuItems.Add("Create New XXXX Message", Cmd01) 
               ’ add menu item to the end of menu 
     End If 
End Function

Notice that once again a GWCommand object is built. Set the Persistent ID and the Long prompt for the new menu item. When a user selects your new menu item, the Manager calls your GWCommand Execute method. You then should check the Persistent ID to see that it is this menu item being clicked. Then you can continue with whatever you want your C3PO to do.

You must provide the Execute method. This method is called by the Manager when a button is clicked, a menu item is selected or a predefined command is executed.

You must provide the Validate method. This method is called to ask the C3PO if the GWCommand is valid under the current conditions. Validate can return:

eGW_CMDVAL_CHECKED

The command has a check mark.

eGW_CMDVAL_DISABLED

The command is disabled.

CustomizeContextMenu

The CustomizeContextMenu method allows your C3PO to add menus and menu items to the GroupWise client context menus. If in the Init method you have set the bit telling the Manager that you want to change context menus, this routine will be called anytime the menu that you have registered to change is built.

You first need to decide were you want to place the menu. There are several options to choose from that are similar to the CustomizeMenu method, except that the GW.CLIENT.WINDOW.PROPERTIES and the GW.CLIENT.WINDOW.ATTACHVIEWER contexts are not available. Also, the following three additional contexts have been added for GroupWise 5.5.EP and GroupWise 6.x:

  • GW.CLIENT.WINDOW.ATTACHMENTCONTROL is the attachment window.
  • GW.CLIENT.WINDOW.ATTACHMENTCONTROL.STATICFILE is the incoming attachment in the attachment window.
  • GW.CLIENT.WINDOW.ATTACHMENTCONTROL.EDITABLEFILE is the outgoing attachment in the attachment window.

Another difference is that CustomizeContextMenu passes in the context menu object instead of the menu object itself.

The execution and validation functions for customizing context menus operate in the same manner as the functions for customizing regular menus, and the registration information is the same.

CustomizeToolbar

To add a button to the toolbar you need to add code to the CustomizeToolbar method. If in the Init method you have set the bit telling the Manager that you want to change the toolbar and you are registered correctly, your CustomizeToolbar method will be called each time that toolbar is built.

You need to decide to which toolbar you want to add a toolbar button. The options are the same as for the CustomizeMenu method, with the addition of the GW.CLIENT.WINDOW.QUICKVIEWER context. The registration for adding a button is also the same. Do the following in Visual Basic:

Public Function CustomizeToolbar(sGWContext As String, objGWToolbar As Object) 
          As Boolean 
     Dim Button As Object 
     Dim FilePath As String 
     If sGWContext = "GW.CLIENT.WINDOW.BROWSER" Then 
               ’ Check for correct context 
          Dim Cmd00 As New GWCommand 
               ’ Build GWCommand object 
          Let Cmd00.PersistentID = XXXX 
               ’ Set persistent ID for GWCommand object 
          Let Cmd00.ToolTip = "Create a new message of type
                              GW.MESSAGE.MAIL.XXXX" 
               ' Set Button tooltip 
          Set Button = objGWToolbar.ToolbarItems.Add("New XXXX Message", Cmd00) 
               ' Add button to toolbar 
          FilePath = App.Path & "\icons.dll" 
               ' Set bitmap for Button 
               ’ icons.dll can be replaced by the full path name of any
               '  .exe or  .dll that contains a 16x16 and a 32x32 pixel
               ’  bitmap
               ' BUTTON_1 can be replaced with the name of the bitmap 
               ’  contained in the .exe or .dll 
          Call Button.SetBitmap(FilePath, "BUTTON_1") 
               ’ set were the bitmap is found and its name 
     End If 
     CustomizeToolbar = False 
End Function

In this routine, you check to see if it is the context that you are interested in. Then you create a new GWCommnad object. You set the Persistent ID, set the ToolTip, and the add the button. You then set the bitmap for the button.