Creating a Workplace Shell "PileOf" Class

From EDM2
Jump to: navigation, search

by Bernie Arruza

Let's be creative and think about things in a new way. For instance, you know how objects in the world around you can be manipulated. Your fingers are like a form of user interface. You can pick things up and drop them. From experience, you have an idea of how much those things weigh (you adjust your muscles accordingly) or if they will break (from their composition).

Computers will someday be that smart, friendly, powerful, and three-dimensional. But we are not there yet.

With today's technology, OS/2 desktop objects represent a two-dimensional world fairly efficiently. Look at the objects you see on the OS/2 desktop; study their behavior. Can you think of new classes of objects? Can you think of new behaviors for those objects? Would these new behaviors enhance your desktop?

I receive a lot of magazines from different interest groups. What do I do with them? Well, some of them I read and throw away, but for the most part, when I read them I put them away in neatly arranged piles.

Unlike a folder, a pile "distinctively implies the laying of one thing...on top of another in a more or less orderly formation;...an assembly of like things or things of approximately the same size or shape" (from Webster's New Dictionary of Synonyms, Springfield, MA: GMerriam Co., 1973). The term pile is more adequate than heap, stack, mass, or bank to convey the idea behind this kind of behavior when we apply it to the OS/2 desktop.

This article discusses the Workplace Shell PileOf class, and provides sample code that demonstrates how you can quickly prototype the desired behavior of a Workplace Shell object.

Because the closest thing to a PileOf object is an OS/2 folder object, let's begin by looking at the differences between the two kinds of objects:

User's action A "regular" folder object A "regular" PileOf object
Drag and drop a folder from the desktop into a folder or PileOf object. Adds the new folder to its list of items.

If the folder object is closed, the folder is added at the end or according to a sorting criteria. If the folder holds other folders and it is opened, the new folder can be inserted between folders or placed inside another folder (its position can be modified by sorting criteria).

If the object is a "pile" of data files, or another non-folder class, the drop will not be allowed.

If the object is a "pile" of folders and the PileOf object is closed, the folder will go on top of the other folders. No other choices will be allowed. If the object is a "pile" of folders and the PileOf object is opened, the folder can be inserted in between folders or inside another folder (no sorting criteria is enforced for the folders in the pile).

Drag and drop a non-folder object from the desktop into a folder or PileOf object. Adds the new object to its list of items.

If the folder object is closed, the object is added at the end or according to sorting criteria. If the folder holds other objects and it is opened, the new object can be inserted between objects or placed inside another folder (its position can be modified by sorting criteria).

If the object is a "pile" of another object class or the object class being introduced is not in the set of permissible classes, the drop will fail.

If the object is a closed "pile" of objects of the same class as the object being introduced, or the object introduced is in the set of permissible classes, the object will go on top of the pile. No other choices will be allowed. If the object is an opened "pile" of objects of the same class as the object being introduced, or the object introduced is in the set of permissible classes, the object can be inserted between objects (no sorting criteria is enforced for the objects in the pile).

Drag two or more objects out of a folder or PileOf object and drop them on the desktop. All objects appear on the desktop. A new pile with those items is created on the desktop. Their order is maintained. The settings notebook should appear so you can name the pile.
Select sorting criteria for the PileOf piles object. Does not apply. Sorts the piles but not the contents of any of the piles.

Each pile maintains its own sort order.

Select sorting criteria. Destructive - All items in the folder are arranged according to sorting criteria. When the folder is closed or reopened, items will reflect sorting criteria. Non-destructive - All items in the pile can be arranged according to sorting criteria, but when you close the pile and reopen it, the original order is preserved.

You can drag and drop objects within the pile to arrange them the way you want. Also, a sort and save option can be included for larger piles.

Insert a PileOf object into a PileOf piles object. Does not apply. If the PileOf piles object is closed, the new pile goes on top. A dialog offering to change the name of the pile should appear.

If the PileOf piles object is opened, the new pile can be inserted between piles but not inside another pile.

Desktop area taken up by an opened folder object or PileOf object. Defaults to 2" x 7" (even if the folder contains only one object). Because objects appear as a list, which you can scroll down, the list can be made very small (for example, show only two objects).

Defaults to 1" x 3".

Resize the window so that it shows only two of the objects in the contained object. Some objects might become "hidden" because they are out of view, and the user can forget that the objects exist. No problem - the hidden objects can only belong to the classes supported by the PileOf object. You implicitly know what's there.

As you can see, the PileOf object class reuses a lot of the code from the WPFolder class. However, it looks and behaves differently.

Unlike a regular folder, the PileOf object:

  • Contains a settings page so the user can specify the type of pile and its behavior.
  • Validates drag/drop to ensure the object being dragged/dropped is the right type.
  • Contains extra items in its popup menu for the manipulation of its contents.

PileOf Settings Page

In the _wpAddSettingsPages method, call the parent first (WPFolder class) and then call the PileOf method:

/* Add PileOf settings page */
ulPageId = _AddPileOfPage(somSelf,hwndNotebook);

The _AddPileOfPage method fills up the PAGEINFO structure for the PileOf page and invokes the wpInsertSettingsPage method.

PAGEINFO pi;                               /* Variable declaration */

pi.cb                  = sizeof(PAGEINFO);
pi.hwndPage            = NULLHANDLE;
pi.usPageStyleFlags    = BKA_MAJOR;
pi.usPageInsertFlags   = BKA_FIRST;
pi.pfnwp               = MyDialogProc;     /* Pileof settings controls    */
                                           /* messages go here            */
pi.resid               = hmod;             /* from DosQueryModuleHandle of*/
                                           /* PILEOF.DLL                  */
pi.dlgid               = DLG_TITLE;
pi.pszName             = "PileOf";         /* PileOf tab text             */
pi.pCreateParams       = somSelf;          /* dialog create parms         */
pi.idDefaultHelpPanel  = 0;
pi.pszHelpLibraryName  = NULL;

ulPageID = _wpInsertSettingsPage ( somSelf, hwndNbk, &pi );

Note: You can/should override some of the wpAddFolderXXXXX (XXXXX=page type) methods and have them return SETTINGS_PAGE_REMOVED. It's up to you to decide what settings pages just plain don't belong for your PileOf object. The open view in my implementation (and the only view) is a modified Details view.

PileOF-Fig-1.gif

PileOF-Fig-2.gif

Figure 1. PileOf settings page and PileOf settings page with selected items

As you can see the settings page contains several controls:

  • A list box to display and choose from all the OS/2 system objects (you can choose one or more). If you choose to have a pile of bitmaps and icons, you select the Bitmap and Icon entries in the list box. Those are the only two classes of objects that you can drop into this PileOf object.
  • A set of check boxes for options:
    • One Class only - When checked, only one item can be selected from the list box (single selection). When unchecked, multiple items can be selected. After multiple items are selected, this button becomes disabled. To reactivate it, reduce the number of selected items in the list box to zero or one.
    • Spread contents - When checked, the popup menu for this PileOf object contains the Spread Contents menu item. When clicked, a folder/icon view of the contents of the pile is displayed.
    • Delete if empty - If selected, and if the PileOf object remains empty before shutdown, the PileOf object will be deleted. After all, an empty pile is not a pile. But then, a pile of one object is not a pile either. You decide!
  • A set of radio buttons for the way objects will be piled:
    • Left to Right (this would be like a bookcase)
    • Top to Bottom (more like a true pile)

Other PileOf settings pages/controls can be added as desired.

To extract a list of all OS/2 public class objects, do the following:

  • In the PILEOF.IDL file pass-through section:
    /* used in MyDialogProc */
    typedef  struct   _WINDATA
        {
             SOMObject   *somSelf;
             SOMObject   *somClassObj;
        } WINDATA, *PWINDATA;

    /* structure to keep track of PileOf class object list */
    /* there are several other ways of doing this !!!      */
    typedef  struct   _CLSINSERT1
        {
           PSZ            pszClass;
           SOMClass       *Class;
           SOMClass       *ClassParent;
           BOOL           flClassSelected;
        } CLSINSERT1, *PCLSINSERT1;

    /* PileOf instance data. Other info could/should be added */
    typedef struct _DATAPILEOF
        {
          ULONG           cClassIns;
          PCLSINSERT1 pClsInsert;
        } DATAPILEOF, *PDATAPILEOF;
  • In the source code file (PILEOF.C):
    /* Enumerate the object classes registered with workplace  */

    WinEnumObjectClasses(NULL,&ulSize);

    /* Initialize the structures */
    if (  ( pDtPileOf =
            (PDATAPILEOF)_wpAllocMem(somSelf, sizeof(DATAPILEOF),NULL) ) &&
          ( pObjClass = (POBJCLASS)_wpAllocMem(somSelf, ulSize,NULL) ) && 
            ( pClassIns = (PCLSINSERT1)_wpAllocMem(somSelf, ulSize,NULL) )
          )
       {
          memset((PBYTE)pClassIns,0,(USHORT)ulSize);
          pclsi = pClassIns;
          if ( WinEnumObjectClasses(pObjClass,&ulSize) )
          {
             poc = pObjClass;
             while (poc)
             {
                /* Find the NLS name for this class */
                ClassId   = SOM_IdFromString(poc->pszClassName);
                somPrintf( "The class name is: '%s'\n",poc->pszClassName);
                if ((Class =_somClassFromId(SOMClassMgrObject,ClassId)) &&
                    !(_wpclsQueryStyle(Class) & CLSSTYLE_PRIVATE))
                {
                    pszClass = _wpclsQueryTitle(Class);
                    if (!pszClass)
                       pszClass = poc->pszClassName;
                    somPrintf( "The private class name is: '%s'\n", pszClass);

                    /* Save the class in an entry in the CLSINSERT1 table that */
                    /* we construct, along with its parent class               */
                    pclsi->pszClass    = pszClass;
                    pclsi->Class       = Class;
                    pclsi->ClassParent = _somGetParent(Class);
                    pclsi->flClassSelected = FALSE;
                    pclsi     += 1;
                    cClassIns += 1;
                }
                SOMFree(ClassId);
                poc = poc->pNext;
             }
          }
       }

Validating Drag/Drop

Again, the behavior of the PileOf class demands that we restrict the user's ability to drop items into it. Only the objects from one of the classes selected in the PileOf settings page will be permitted into the pile.

To accomplish this, use the PileOf method ValidateDragAndDrop, which will know where the pile's instance data is, as follows:

/* Set pointer to PileOf data */
pDtPileOf = (PDATAPILEOF)_QueryPileOfInstData( somSelf );

/* Points to class list structure */
pClassIns  = pDtPileOf->pClsInsert;

/* Get the number of objects being dragged */
ulNumberOfObjects = DrgQueryDragitemCount( pdrgInfo);

/* Test each object */
for( ulCurrentObject = 0; ulCurrentObject<ulNumberOfObjects;
                                   ulCurrentObject++)
{
    pDragItem = DrgQueryDragitemPtr( pdrgInfo, ulCurrentObject);

    DrgQueryStrName( pDragItem->hstrRMF, sizeof( pszBuffer), pszBuffer);
    somPrintf( "The rendering mechanism is: '%s'\n", pszBuffer);

    /* Make sure that the rendering mechanism is DRM_OBJECT  */
    /* Only objects with a rendering mechanism of DRM_OBJECT */
    /* can be considered.                                    */
    if(DrgVerifyRMF( pDragItem, "DRM_OBJECT", NULL))
    {
      /* Get type of object */
      Object = OBJECT_FROM_DRAGITEM(pDragItem);

      /* Get class from object */
      somClassObj = _somGetClass ( Object );

      /* Get class title for this class */
      pszClass = _wpclsQueryTitle(somClassObj);

      /* See if this class title is in the list of classes in object's */
      /* instance data.                                                */
      for (j = 0; j < pDtPileOf->cClassIns; j++)
      {
        pclsi = pClassIns + j;

        if (pclsi->flClassSelected)

           /* See if class was selected for PileOf object */
           if (!strcmp(pclsi->pszClass, pszClass)) break;

        /* At least one of the objects being dragged is not in          */
        /* the list of allowed objects. DROP should fail !              */
        /* DESIGN: if one object in the DRAGINFO structure can          */
        /*         not be dropped in this PileOf object, can we somehow */
        /*         drop the rest? For now the DROP will fail for all.   */
        if ((j+1) == pDtPileOf->cClassIns)
           return (FALSE);

       }

      }
      else
       return ( FALSE );
   }

Next, override wpDragOver and wpDrop and call ValidateDragAndDrop, as follows:

/* Call the parent's method first   */
/* If the parent said it is okay to drop, then we will check it also. */

if( DOR_NEVERDROP != SHORT1FROMMR( mResult)){

        /* If the object is acceptable, return the mResult from the parent */
        if( FALSE == _ValidateDragAndDrop( somSelf, pdrgInfo))
            mResult = MRFROM2SHORT( DOR_NEVERDROP, DO_UNKNOWN);
}

PileOf Popup Menu Items

To limit the open view to one view (Details view), override wpFilterPopupMenu. Call the parent first and then do the following:

/* Remove the tree/icon view menu option. Make sure delete is available */
return ( ( ulFlags | CTXT_DELETE ) & ~CTXT_TREE ~CTXT_ICON);

Details view is the default when you open a PileOf object. You can change to a new view or let the user specify a view by adding some extra code. Override wpModifyPopupMenu to add a few new options for the PileOf class, as follows:

/* Get the module handle for the dll */
rc = DosQueryModuleHandle( "PILEOF.DLL", &hmod );

/* call the parent first */
fFlagsOut =   parent_wpModifyPopupMenu(somSelf,hwndMenu,hwndCnr,iPosition);

/* IDM_PRIMARYMENU is in my PILEOF.RC file. The items it represents  */
/* will be inserted at the end on WPMENUID_PRIMARY                   */
fSuccess = _wpInsertPopupMenuItems( somSelf
                                       ,hwndMenu
                                       ,iPosition
                                       ,hmod
                                       ,IDM_PRIMARYMENU
                                       ,WPMENUID_PRIMARY);

To control what happens when one of the items being added is selected, override wpMenuItemSelected.

Testing PILEOF.DLL

The makefile for this Workplace Shell class was designed to run on Intel machines. It uses the C Set compiler and the Developer's Toolkit for OS/2 Warp.

I created two files, INSTALL.EXE and UNINST.EXE, to test the PILEOF.DLL. INSTALL.EXE calls the following:

hab = WinInitialize ( 0 );
/* register the PileOf class */
fSuccess =
        WinRegisterObjectClass ( "PileOf", "C:\\OS2\\DLL\\PILEOF.DLL" );

if ( fSuccess == FALSE )
{
        printf ( "Unable to register object class PileOf, error: " );
        printf ( "%x\n", ERRORIDERROR ( WinGetLastError ( hab ) ) );
}
/* create an instance of the PileOf class */
hobjPileOf = WinCreateObject ( "PileOf"
                                  , "PileOf..."
                                  , "OBJECTID=<OBJECT_PILEOF>"
                                  , "<WP_DESKTOP>"
                                  , CO_REPLACEIFEXISTS );
if ( hobjPileOf == 0 )
{
         printf ( "Unable to create PileOf... object, error: " );
         printf ( "%x\n", ERRORIDERROR ( WinGetLastError ( hab ) ) );
}

WinTerminate ( hab );

UNINST.EXE calls the following:

hab = WinInitialize ( 0 );

  fSuccess = WinDeregisterObjectClass ( "PileOf" );

  if ( fSuccess == FALSE )
  {
     printf ( "Unable to de-register object class PileOf, error: " );
     printf ( "%d\n", ERRORIDERROR ( WinGetLastError ( hab ) ) );
  }

  WinTerminate (hab);

Conclusion

When developing your user interface, look closely at the world around you for ideas. Can you take advantage of OS/2's object technology to bring the behavior of your user interface closer to the way real objects behave? It may take some doing to verify that if an object looks like a hammer, it actually behaves like a hammer. But think how much easier you are making it for your customer. That is the final reward, and that is what really matters.

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation