Print Dialogs
Volume Number: 8
Issue Number: 2
Column Tag: C Workshop
Related Info: Printing Manager Adding Items to the Print Dialogs
Modifying Print Dialogs
Tech Note #95 and beyond
By Greg Wilson, Etobicoke, Ontario, Canada
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
About the author
Greg Wilson is a Toronto area Macintosh consultant. He has been programming
the Macintosh since 1985. His first article for MacTutor shows how to add Filter
Routines and disable items in Print Dialogs.
The Problem
I programmed myself into a hole the other day. I was (am, seem to forever be)
writing a game which can be played by several people over the network. Every time I
passed through my main event loop I checked to see if there were any network messages
to be processed. Unfortunately, there was a problem if the user selected a menu
command which brought up a modal dialog, for example, when selecting the “About
menu item. I had used the Alert function to handle the modal dialog like this:
/* 1*/
void ShowAboutMeDialog(void)
{
short itemHit;
itemHit = Alert(rAboutAlert, nil);
} /* ShowAboutMeDialog */
The program would sit on the line
/* 2 */
itemHit = Alert(rAboutAlert, nil);
This handles the window until the user hit an item, and so my main event loop
network message check wouldn’t get executed and messages could be missed. I had to
ensure that network messages weren’t missed (or timed out) while the user was sitting
in a modal dialog.
The solution was to use a “Filter Proc” when calling ModalDialog directly, or
indirectly through Alert, SFGetFile, etc which pass the FilterProc to ModalDialog. This
Filter Proc would get called every time ModalDialog detected an event. My filter simply
checked to see if there were any messages, processed them and then let ModalDialog
handle the event in the usual way. Similar code has been seen in MacTutor numerous
times in the past, my version (Net_filter) is shown in the sample code which follows
the article. It gets invoked by changing the troublesome line to:
/* 3 */
itemHit = Alert(rAboutAlert, &Net_filter);
Pride Goeth
Before I got injured patting myself on the back, it became obvious that this would
not work for Print Dialogs. There are two routines to conduct the modal print dialogs.
The PrJobDialog and PrStlDialog functions conduct modal dialogs for defining the
characteristics of the Print Job (quality, copies, page range, etc) and the Page Setup
(paper orientation, paper size etc) respectively. Neither of these accept a Filter Proc
function. I could see no easy way around this, so I did the logical thing. I decided to leave
it for later and hope the problem went away.
Of course things went from bad to worse. The BitMaps that I painstaking added to
the display and printing didn’t print on an ImageWriter when the user selected Draft
Mode. This was because Draft Mode is designed to just print the characters in the native
character set of the device. BitMaps aren’t supported. PrGeneral was supposed to allow
for printing of draft mode BitMaps under certain restrictions, but it seemed like it
wouldn’t allow me to do what I needed. I wanted to disable the Draft Mode radio button;
that way the user could never select Draft Mode printing, and so my BitMaps would
always print. I knew from my experience, being unable to get a Filter Proc into these
dialogs, that there was no way to patch a hook into these routines. Yet there must be
some way to get at the dialogs since several commercial applications were disabling the
button, and others were adding items to the dialogs. I now had two problems, so some
action was required.
Salvation came in the form of Tech Note #95. This little beauty was built to
explain how to add items to the Printing Manager’s dialogs. But more importantly for
me, it showed the format of the data structures and described the procedures and
functions required by PrStlDialog and PrJobDialog used to create and display the dialog
information.
Inside PrJobDialog
When the PrJobDialog is called, it really only calls another function like this:
/* 4 */
ok = PrDlgMain(PrDlgStrctHndl,&PrJobinit);
(PrStlDialog operates similarly, except PrStlInit is used in the call to
PrDlgMain. From now on I’ll just refer to PrJobDialog since that’s where Draft mode is
used.)
PrDlgMain actually does all the work, and the first thing it does is call the
PrJobInit routine. PrJobInit sets up a dialog record and puts it into a data structure.
The data structure is defined in Printing.h like this:
/* 5 */
struct TPrDlg {
DialogRecord Dlg;
ModalFilterProcPtr pFltrProc;
PItemProcPtr pItemProc;
THPrint hPrintUsr;
Boolean fDoIt;
Boolean fDone;
long lUser1;
long lUser2;
long lUser3;
long lUser4;
/*Plus more stuff needed by the particular printing dialog.*/
};
typedef struct TPrDlg TPrDlg;
typedef TPrDlg *TPPrDlg;
typedef pascal TPPrDlg (*PDlgInitProcPtr)(THPrint hPrint);
Then the PrJobInit function returns a pointer of type TPPrDlg which points to
this structure. PrDlgMain then calls ShowWindow, passing the DialogRecord window
(Dlg), and finally calls ModalDialog (passing, luckily enough, the
ModalFilterProcPtr).
So, if I could get at this TPrDlg record, I could change the ModalFilterProcPtr
and disable the Draft mode item in the DialogRecord window. Two bugs fixed for the
price of one!
Today, On “How To Do It
As luck would have it, Tech Note #95 even showed how to get access to this
TPrDlg record. Instead of using the PrJobDialog routine, I call PrDlgMain myself,
substituting my own init routine in the call so it looks like this:
/* 6 */
if (!PrDlgMain(hPrintRec,&MyJobDlgInit))
/* there was an error */
My init routine, MyJobDlgInit, calls the original init routine, PrJobInit, lets it
set everything up, then modifies what we need.
/* 7 */
/* call PrJobInit to get pointer to the invisible job dialog */
PrtJobDialog = PrJobInit(hPrintRec);
if (PrError() != noErr)
return nil;
/* set up the filter function */
PrtJobDialog->pFltrProc = (ModalFilterProcPtr)&Net_filter;
/* disable the draft mode now */
DisableDraftMode(PrtJobDialog);
The routine DisableDraftMode is responsible for disabling the Draft Mode radio
button of the print dialog record pointed to by PrtJobDialog. DisableDraftMode
determines how many items in the list via ItemList, then loops for this number so each
item can be checked. GetDitem is used to determine what type of item it is.
Type Checking
Types can have the high bit set if they are disabled, so I dropped this bit by
ANDing the type with 7F (0111111). Radio buttons are controls, so the type check is
made for ctrlItem + radCtrl (Control item and Radio Button Control).
The title is then found via the lowercase version of getctitle. I did it like this so
that a C string is returned as opposed to Pascal format. By looping and checking for
“Draft” as the title of the control, we aren’t relying on the Draft item being a certain
item number in the Dialog. After all, Tech Note #95 has warned that “If you depend on
the Draft button being a particular number and we change the Draft button’s item
number, your program may not work”. When coding this for a real application, I would
put the “Draft” string in the resource file to make localization easier.
Once the draft item is found, it is unselected by SetCtlValue and finally disabled
by passing a value of 255 to HiliteControl.
Having disabled and possibly unselected the Draft item, the dialog is cleaned up
by making sure that at least one of the buttons is selected. The item numbers of the Best
and Faster modes are saved, so we try and turn one of these on.
SoapBox Derby
I should point out that there is a difference between a disabled item and an
inactive control. If the item is disabled (and we could disable it by SetDItem) it cannot
be selected. However, it is not drawn in dimmed text. The item looks like it can be
clicked on, and if the user clicks on it, the radio button turns on then off right away.
This means they might try again and again and start to think something is wrong with
the mouse etc. When a control is set to inactive via HiLiteControl, it cannot be
selected and IS dimmed. This gives feedback to the user and says “You can’t pick this, it
isn’t a valid option right now”. Furthermore, the consistency of the Mac interface is
preserved and the world is saved for peace, democracy and future generations. End of
soapbox.
Adding Items to the ItemList
In fact, my code is a lot simpler to write than Tech Note #95’s. The Tech Note
shows how to add items to the Dialog. The field Items in the Dialog Record contains a
handle to a list of items. This list is documented briefly in Inside Macintosh, but the
sample code in Tech Note #95 gives a more complete picture. The ItemList itself
contains an item, dlgmaxindex, which tells the number of Items in the list (minus 1).
Following dlgmaxindex is each of the DITLs. Each of these DITLs contains a Handle to the
item, it’s rect, type and datalength followed by datalength bytes of variable data. There
is no Trap to add a DITL to a DialogRecord’s ItemList and since each DITL is a variable
length field within the Item List, the AppendDITL routine must do some fancy pointer
manipulation. We will only look at and change the existing DITL’s so we can use the
existing Dialog and Control Manager routines, but we will use the ItemList definition to
get the variable. I’ve kept the ItemList structure and other definitions as in Tech Note
#95 in case you wish to expand the sample to include a C version of the AppendDITL
routine.
Example Source
The C source code which follows is composed of two main modules. The NoDraft.c
source is the part of the basic boilerplate shell that I use. It originated on some
developer CD somewhere and has been stripped down to the basics to conserve trees. It
really just sets up a window and menubar then waits for the user to select “Print”.
The important stuff all happens in the Dialogs.c module. This module contains the
DisableDraftMode, and MyJobDlgInit, as well as the routines needed to setup the print
dialogs, the filter function, etc.
Summary
The example which follows shows how to include a filter function and how to
disable the Draft Mode button on a print dialog. If you need to add items to a dialog, Tech
Note #95 shows you how.
Listing NoDraft.h
/*-----------------
# MultiFinder-Aware Simple NoDraft Application
#
# NoDraft
#
# NoDraft.h - noDraft header manifest
#
# NoDraft is an example application that
# demonstrates how to modify Print Dialogs.
#
----------------*/
#define kMinSize 128 /* min size (K) */
#define kPrefSize 128 /* pref'd size (K) */
/* Indicies into STR# resources */
#define eWrongMachine 1
#define eSmallSize 2
#define eNoMemory 3
#define eNoWindow 4
#define eUnknown 1
#define rMenuBar 128 /* app's menu bar */
#define rAboutAlert 128 /* about alert */
#define rUserAlert 129 /* error alert */
#define rWindow 128 /* A window */
#define kErrStrings 128 /* error strings */
#define kSysEnvironsVersion 1
#define kOSEvent app4Evt /* used by MF */
#define kSuspendResumeMessage 1
#define kResumeMask 1
#define kMouseMovedMessage 0xFA
#define kNoEvents 0
#define mApple 128 /* Apple menu */
#define iAbout 1
#define mFile 129 /* File menu */
#define iNew 1
#define iClose 4
#define iPSetUp 9
#define iPrint 10
#define iQuit 12
#define mEdit 130 /* Edit menu */
#define iUndo 1
#define iCut 3
#define iCopy 4
#define iPaste 5
#define iClear 6
#define abs(x) ( (x) < 0 ? -(x) : (x))
#define MAX(x, y) ( (x) > (y) ? (x) : (y) )
#define kMinHeap 21 * 1024
#define kMinSpace 8 * 1024
/* used to set enable/disable flags of a menu */
#define AllItems 0b1111111111111111111111111111111
/* 31 flags worth*/
#define NoItems 0b0000000000000000000000000000000
#define MenuItem1 0b0000000000000000000000000000001
#define MenuItem2 0b0000000000000000000000000000010
#define MenuItem3 0b0000000000000000000000000000100
#define MenuItem4 0b0000000000000000000000000001000
#define MenuItem5 0b0000000000000000000000000010000
#define MenuItem6 0b0000000000000000000000000100000
#define MenuItem7 0b0000000000000000000000001000000
#define MenuItem8 0b0000000000000000000000010000000
#define MenuItem9 0b0000000000000000000000100000000
#define MenuItem10 0b0000000000000000000001000000000
#define MenuItem11 0b0000000000000000000010000000000
#define MenuItem12 0b0000000000000000000100000000000
#define cr 0x0d
#define enter 0x03
#define delay_length 1024
Listing NoDraft_types.h
/*-----------------
# MultiFinder-Aware Simple NoDraft Application
#
# NoDraft_types.h - C type defs for NoDraft
#
---------------*/
struct DITLItem {
Handle itmHndl;
Rect itmRect;
SignedByte itmType;
SignedByte itmData;
};
typedef struct DITLItem DITLItem;
typedef DITLItem *pDITLItem, **hDITLItem;
struct ItemList {
short dlgMaxIndex;
DITLItem DITLItems;
};
typedef struct ItemList ItemList;
typedef ItemList *pItemList, **hItemList;
static TPPrDlg PrtJobDialog;
Listing Dialogs.c
/*----------------------
# MultiFinder-Aware Simple NoDraft Application
#
# Dialogs.c - Dialog handling code
#
---------------------- */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
pascal TPPrDlg MyJobDlgInit();
THPrint hPrintRec;
/*---------------------- */
void FlashDialogItem(the_ dialog, theItem)
DialogPtr the_ dialog;
short theItem;
{
short itemType;
Rect itemRect;
Handle itemHdl;
long longdelay, longticks;
GetDItem(the_ dialog, theItem, &itemType, &itemHdl, &itemRect);
if ( itemHdl != nil) {
HiliteControl( (ControlHandle) itemHdl, 1);
longdelay = longticks = 6;
Delay(longdelay, &longticks);
}
}
/*---------------------- */
void checknetwork()
{
static int count = 0;
/* I use this routine to check for AppleTalk™
messages rather than include all that code,
I just beep every once in a while to let
you know it is working, you can change
delay_length to increase/decrease
freq of Beeps */
if ( count++ > delay_length ) {
SysBeep(3);
count = 0;
}
} /* end of checking the network */
/*---------------------- */
pascal Boolean Net_filter(theDialog, theEvent, itemHit)
DialogPtr theDialog;
EventRecord *theEvent;
short *itemHit;
{
int key;
Boolean handled_ event;
handled_event = false; /* did we handle event */
/* do whatever you have to for event */
switch ( theEvent->what) {
case nullEvent:
checknetwork();
break;
case keyDown:
key = theEvent->message & charCodeMask;
if (( key == cr) || ( key == enter ))
{
FlashDialogItem(theDialog, ok);
*itemHit = ok;
handled_event = true;
}
break;
case updateEvt:
/* we get all kinds of update events here
some for our modal dialog and even some
for the different windows ( in some cases)
rather than try and process these so that
we do get real null events when
we can check the network, just check the
network and let the update events get
processed later */
checknetwork();
break;
case activateEvt:
break;
default:
/* Don't know what we got here, but */
/* but check the network in case we */
/* get stuck on this event like we */
/* did with updates */
checknetwork();
break;
}
/* return boolean to tell modal dialog to */
/* handle the event or not */
return ( handled_ event);
}
/*---------------------- */
void ShowAboutMeDialog(void)
/* my about dialog */
{
short itemHit;
itemHit = Alert(rAboutAlert, &Net_filter);
} /* ShowAboutMeDialog */
/*---------------------- */
void DisableDraftMode(thePrintDialog)
TPPrDlg thePrintDialog;
/* disable the ability to print out draft */
/* mode since we can't print our bitmaps */
{
DialogPeek theDialogPtr;
short i;
short num_ items; /* how many in list */
hItemList hItems; /* Handle to DLOG's list */
short item_no;
short draft_item_no;
short better_item_no;
short best_item_no;
short the_type; /* vars from GetDitem */
Handle the_item;
Rect the_box;
char *the_title = " ";
short the_and_type;
char *draft_title, *better_title;
char *best_title;
Boolean draft_selected;
short draft_value;
/* we are given a DialogPtr which is a ptr */
/* to the print dialog */
/* scan through all the items, and if you */
/* get one called draft, disable it */
/* how many items are there to scan */
theDialogPtr = (DialogPeek)thePrintDialog;
hItems = (hItemList) (theDialogPtr-> items);
num_items = (*hItems)->dlgMaxIndex + 1;
draft_title = "Draft";
better_title = "Faster";
best_title = "Best";
draft_selected = false;
for (i = 1; i <= num_ items; i++ )
{
/* get the item */
item_no = i;
GetDItem( (DialogPtr) theDialogPtr, item_no, &the_type,
&the_item, &the_box);
/* used to bit and with 7F */
the_and_type = the_type & 0X7F;
switch ( the_and_type ) {
/* is it a radio button */
case ctrlItem + radCtrl:
getctitle( (ControlHandle) the_item, the_title);
if ((*the_title) == (*draft_title))
{
draft_value = GetCtlValue((ControlHandle)
the_item);
if ( draft_value > 0 )
{
draft_selected = true;
draft_value = 0;
SetCtlValue((ControlHandle) the_item,
draft_value);
}
HiliteControl( (ControlHandle) the_item, 255);
/* comment the previous line and uncomment */
/* the next two lines of code to see the */
/* difference between disabling item */
/* and inactivating control */
/* the_and_type = the_type | 0X80; */
/* SetDItem( (DialogPtr) theDialogPtr, item_no,
the_and_type, the_item, &the_box); */
draft_item_no = item_no;
}
else
if ((*the_title) == (*better_title) )
better_item_no = item_no;
else
if ((*the_title) == (*best_title) )
best_item_no = item_no;
break;
} /* end of switch */
} /* of for loop */
} /* of disabling the printing in draft mode */
/*---------------------- */
pascal TPPrDlg MyJobDlgInit(hPrint)
THPrint hPrint;
{
/* call PrJobInit to get pointer to the */
/* invisible job dialog */
PrtJobDialog = PrJobInit(hPrintRec);
if (PrError() != noErr)
return nil;
/* set up the filter function */
PrtJobDialog->pFltrProc = (ModalFilterProcPtr)&Net_filter;
/* disable the draft mod now */
DisableDraftMode(PrtJobDialog);
/* PrDlgMain expects pointer to modified */
/* dialog to be returned.... */
return ( PrtJobDialog );
} /* myJobDlgInit */
/*----------------------*/
OSErr DoPrintDialog()
{
hPrintRec = (THPrint) NewHandle(sizeof(TPrint));
PrintDefault(hPrintRec);
PrValidate(hPrintRec);
if (PrError() != noErr)
return PrError();
/* Here's the line that does it all! */
if (!PrDlgMain(hPrintRec,&MyJobDlgInit))
return 2;
if (PrError() != noErr)
return PrError();
/* that's all for now */
}