CXDialog

alpha version, October 2000

The CXDialog class offers a complete API for preparing and using dialogues for diverse settings. At first look, it might seem strange that the dialogue--ie. principally user interface--methods are placed in the Foundation Kit; there are good reasons for that:

1) set the encoding
2) set the end of line
3) what to do with an Epoc result
Enter the desired number or 0 to continue:

Principles

So as to prepare a dialogue programmatically (for static dialogues, there will be luxurious visual tools to make them entirely without programming) you should, in principle, just create a CXArray of all the desired dialogue items, and use it as an argument of the runDialogue class method:

BOOL doit=NO; // the value to be set by user
CXArray *items=@([CXDialog yesno:@"Do it?",&doit]); // one item only
[CXDialog runDialogue:items];

This code will construct and run a dialogue without title nor buttons, containing only one item--the question "Do it?" with a default value of "No". The user will be able to select either "No" or "Yes" and close the dialogue using Enter. After the dialogue is closed, the variable doit will contain the YES or NO, depending on the user's selection.

Note that the CXDialog class server two different purposes here: firstly, it is used to construct CXDialog objects (actually, just one of them), which represent the dialogue items. Then, it offers the general runDialogue service which construct and run the dialogue (or, more precisely, it redirects the request to the actual UI provider; that will be described below).

Analogically, other value types can be edited. For example, to allow a user to enter a text string, you just use a string variable and a different CXDialog constructor:

CXString *name=@"John Doe"; // a default value
[CXDialog runDialogue:@([CXDialog string:@"Enter name",&name])];

More or less the same way any dialogue can be constructed. For example, let's make a more complicated dialogue containing two pages, the first one called "Name" and containing three edit boxes "First Name", "Last Name", and "Title"; the second page called "Address" and containing first boolean value "Foreign", then a separator line, and then strings "Street", "City", and "State". The dialogue should have two buttons, "OK" and "Cancel". In the Epoc GUI, the dialogue would look like this:

CXDialog1
CXDialog2

We can make such a dialogue using the following code:

// variables for the edited values, initialized with defaults:
CXString *firstName=@"",*lastName=@"",*title=@"";
BOOLforeign=NO;
CXString *street=@"",*city=@"",*state=@"";
// construct and run the dialogue
[CXDialog runDialogue:@(
  // first page
  [CXDialog page:@"Name"],
  [CXDialog string:@"First Name",&firstName],
  [CXDialog string:@"Last Name",&lastName],
  [CXDialog string:@"Title",&title],
  // second page
  [CXDialog page:@"Address"],
  [CXDialog yesno:@"Foreign",&foreign],
  [CXDialog separator],
  [CXDialog string:@"Street",&street],
  [CXDialog string:@"City",&city],
  [CXDialog string:@"State",&state],
  // buttons
  [CXDialog cancelOkButtons])];

As soon as the dialogue is closed (ie. the runDialogue method returns), all the variables will contain the values the user entered. That is, of course, in case the "OK" button was pressed or activated by the Enter key; in case the dialogue was cancelled, the variables are naturally unchanged. You can check how the dialogue was terminated using the runDialogue return value: it is a nil if and only if the dialogue was cancelled.

Note also you do not need to consider the screen height of the current machine (actually, at the XFoundation level you do not even know it--the code might run on the netBook or on the Revo, or on a Linux PC with a 22" monitor, on on the Ericsson R380, or anything). The dialogue system will take it automatically into account, and in case there is too many dialogue lines to fit the screen, the dialogue will automatically contain a scroller.

On the other hand, you should try to keep the dialogue lines reasonably short. There is a provision for too wide dialogues (they are automatically aligned to the left edge of the screen, instead of centered, so as the user sees them well and can move them by the title bar), but even thus a too wide dialogue does not look nice and is somewhat incinvenient for the user.

Labels

So as to be identifiable for different purposes, some object can have labels. The whole dialogue can be labelled; any page can have a label of its own, just like each particular item, and each button can have one.

Generally, the label is a text string. In some conditions, any string will do; otherwise, strings with some particular values of first character (like '#' or '-'), or empty string, or alike, can have a special meaning. Therefore, the safe way is always to use labels which start by a letter, and are at least two characters long; such a string will be recognized as a label for sure anywhere.

The particular API for specifying and using labels will be described below.

Delegates and validation

Often we need some form of feedback, for different purposes:

Traditionally in C++ this is supported by subclassing: for each dialogue, you make a subclass of some general dialogue class, and implement some virtual methods the appropriate way. The Epoc SDK uses exactly this technique--it is virtually impossible to make even the simplest dialogue without subclassing the CEikDialog class.

The solution is quite bad, though. Firstly, it is a definitely bad thing to make a new class for each trifle; the project then literally swarms by classes, and quickly becomes quite unclear. Besides, for many dialogues the subclass solution is superfluously strong anyway: just to make the dialogue to change the contents of three or four variables to make a class is inconvenient, and costs the time the programmer could and should use better. Finally, such a solution is not flexible enough: so as to keep the dialogues portable, we do not even know which class to subclass here--one code can be used in two or more different conditions, in which the particular dialogue is performed by a quite different classes!

Therefore, a much better mechanism, called delegation, is used here (it is used quite frequently in the XSdk): the dialogue is given one or more objects, called delegates. Whenever the dialogue code is about to perform some important service--like closing the dialogue or activating a button--the appropriate dialogue is found, and (if any) asked what to do. The dynamic services respondsTo, conformsTo and perform:with: (see the preprocessor and the CXObject) are used to send the delegate only the methods it does implement, and to skip others. The advantages are serious:

Just as with labels, the particular ways to specify and use delegates we shall learn a bit later.

Though, we might as well show an example here: let's presume the sample Name/Address dialogue should not allow the user to close it (using "OK") unless the surname is given. In such a plain case it would be quite superfluous to prepare a dedicated delegate; instead, the very object which calls the dialogue can serve as one. All we need to do is to implement a method validateDialogue this way (presuming the variable lastName has been placed between object properties, to be accessible from any object method):

-id validateDialogue
{
  if ([lastName length]==0)
    return @"You must specify the name!";
  return nil; // means "all right"
}

Then, the runDialogue method will be called with just one more argument:

[CXDialog runDialogue:..., this];

That's all! Since this object is now the dialogue delegate, it will be checked before closing the dialogue whether it implements the validateDialogue method--which it does--and if so, the method will be called. In case it returns a string, the string will be shown as an InfoPrint, and the dialogue will not be closed; only after the delegate method returns nil the dialogue can be terminated. It's quite easy, isn't it?

Dialogue items

The detail dialogue data are constructed as an array of the CXDialog objects. They are created via the CXDialog class creator methods.

All the item types have a title string (which is shown to the user) specified in the title argument, and some way to show the current data value and feedback the newly set one. That way is often a pointer to a variable of an appropriate type; the code should set the variable to a default value before the runDialogue method is run, and use the (potentially) new value after it returns.

Each of the constructors can contain another CXString* argument as the last one. If used, it is the label, which uniquely identifies the line in the details dialogue or any nested subdialogue. Using the label, the line can be recognised in the delegate methods.

There is one special value with a special meaning: an empty string (@"") label marks the line as unfocusable.

Currently, the following constructor methods are available (in a near future, we will provide more methods for specifying more item types):

+CXDialog *string(CXString *title,CXString **value,CXString *label=nil);
+CXDialog *string(CXString *title,CXArray *inArray,int atIndex,CXString *label=nil);
+CXDialog *string(CXString *title,CXString *defaultName,CXUserDefaults *defaults,CXString *label=nil);

These constructors prepare a string editor--the standard one-line text field. They differ in the means of the specification of the value to be edited.

The first one just edits a string value in a plain string variable, whose address is set in the value argument. The string must exist (ie. the variable must be initialized and must not contain nil), and should be retained, for the dialogue editor will release it in case of any change:

CXString *aString;
...
[aString=some_other_string retain]; // or aString=@"..."
...
[CXDialog runDialogue:@([CXDialog string:@"Prompt",&aString])];
...
XLog(@"Entered value: %@",aString);

Such a dialogue will in standard Epoc Eikon GUIlook like this:

CXDialog3

Note a few things:

The second variant allows to edit directly a string, which is part of an array. Just specify the array and the appropriate index. Since all the array items are retained automatically, there is no need to retain the edited value explicitly (like it was in the previous case):

CXArray *array=...;
...
// 4th object in array (ie. at index 3) is to be edited:
[CXDialog runDialogue:@([CXDialog string:@"Prompt",array,3])];
...
XLog(@"Entered value: %@",[array objectAtIndex:3]);

Finally, the third modification allows to edit the string directly in the defaults (ie. an INI file, see the CXUserDefaults class). You specify the default key and the appropriate CXUserDefaults object. This would probably be the most frequently used one:

CXUserDefaults *def=...;
[def registerDefaults:@{
  ...
  
@"SomeString"=@"A Default String Value",
  ...
}];
...
[CXDialog runDialogue:@([CXDialog string:@"Prompt",@"SomeString",def])];
...
XLog(@"Entered value: %@",[def stringForKey:@"SomeString"]);

This way the user will be able to edit directly the default value for the key "SomeString" in the INI file, represented by the def object. In case the INI file did not exist, or there was no value inside for the key "SomeString", the default value shown to the user will be "A Default String Value".

+CXDialog *list(CXString *title,CXArray *items,int *index,CXString *label=nil);
+CXDialog *list(CXString *title,CXArray *items,CXString **value,CXString *label=nil);
+CXDialog *list(CXString *title,CXArray *items,CXArray *inArray,int atIndex,CXString *label=nil);
+CXDialog *list(CXString *title,CXArray *items,CXString *defaultName,CXUserDefaults *defaults,CXString *label=nil);

These constructors specify a choice list to be shown in the dialogue. The first one is the most primitive one, and works more or less like the Epoc choice list dialogue: you just specify an array of values to be shown in the dialogue, and an integer variable to contain the index of the selected value:

CXArray *choices=@(@"First choice",@"Second choice",@"Third choice",@"Fourth choice");
...
int index=2; // "Third choice" is the default
...
[CXDialog runDialogue:@([CXDialog list:@"Prompt",choices,&index])];
...
XLog(@"Selected index %d, value: %@",index,[choices objectAtIndex:index]);

As soon as the dialogue is finished (ie. the method runDialogue returns) the index variable will contain the index of the selected item. In the standard Epoc Eikon GUI the dialogue would look like this:

CXDialog4

This is naturally too primitive to be useable conveniently (just like the whole Symbian SDK). Usually, we do not give a damn which index the selected value was at; we are interested of the value itself. Therefore, three more constructors are available. They represent the choice not by an index, but by its value: in a sense, it is alike to the string constructors, for the user does actually edit a string--this time not freely, but selecting from given alternatives.

Therefore, the actual string to be edited can be specified using any of the ways described above with the string constructor: via a string variable (retained), via an array and index, or via a default name and CXUserDefaults object. Let us show an example of the string in an array; the string variable or defaults work just as well:

// the 'choices' array from the last sample'll be used
CXArray *someValues=@(@"Third choice",@"Second choice",@"Fourth choice");
...
[CXDialog runDialogue:@(
  [CXDialog list:@"First Item",choices,someValues,0],
  [CXDialog list:@"Second Item",choices,someValues,1],
  [CXDialog list:@"Third Item",choices,someValues,2],
)];
...
XLog(@"Selected values: %@",someValues);

This time the user will be presented by a dialogue with three choice lists, each of them the same as in the previous example:

CXDialog5

The default value for the first choice list will be "Third choice", for the second one "Second choice", and for the last one "Fourth choice". The values are edited directly in the array someValues. Therefore, presumed the user selected the "First choice" for all the three choice lists, the array someValues will, as soon as the dialogue is finished, contain ("First choice", "First choice", "First choice").

There is one more trick: the constructor allows to show in the dialogue different values than those which will be stored (in the string variable, array, or defaults). That is quite a natural thing: suppose you want to select a type of the end of line for plain ASCII file. There are three natural choices: the line feed character (LF, '\n') as in a sensible operating environment, or the carriage return character (CR, '\r') as in the Mac OS, or the CR/LF pair, as in the MeSsy DOS or windoze. Naturally, you would want to place one of the strings "\n", "\r" or "\r\n" into defaults, so as it is readily useable; though, the user should be presented by some more intelligible prompts for the three variants.

Thanks to the sensible design of the typeless containers in the XSdk it is quite easy: just replace any string in the choice list array by another (nested) array, which contains exactly two items: the first is to be shown to the user, whilst the second is to be stored:

CXUserDefaults *def=...;
[def registerDefaults:@{
  ...
  
@"End of line"=@"\n",
  ...
}];
...
[CXDialog runDialogue:@([CXDialog list:@"End of line",@( // choices... each a nested array:
  @(@"Unix LF",@"\n"), // "Unix LF" shown for the value "\n"
  @(@"Mac CR",@"\r"), // "Mac CR" shown for the value "\r"
  @(@"MeSsy CRLF",@"\r\n")), // "MeSsy CRLF" shown for the value "\r\n"
  @"End of line",def])];
...
[output writeString:@"a text line..."];
// write out the selected line delimiter:
[output writeString:[def stringForKey:@"End of line"]];
[output writeString:@"other text line..."];

+CXDialog *yesno(CXString *title,BOOL *value,CXString *label=nil);
+CXDialog *yesno(CXString *title,CXString *defaultName,CXUserDefaults *defaults,CXString *label=nil);

The yesno constructors allow to directly edit a boolean value, either directly in a variable (the first constructor), or in defaults (the second one). There is no array editor since it is hardly even needed; though, it might be added in future XFoundation releases just to keep the APIortogonal.

Functionally the yesno-constructed dialogue item can be considered to be an equivalent to a list with choices (("Yes",1), ("No",0)). The UI provider can decide, though, to represent it with a checkbox (which would be naturally impossible with the list constructor).

In the future XFoundation releases there will be much more item types (like date editors, number editors, latitude/longitude editors etc.). Meanwhile, for number editors you can quite easily use string editors with the methods intValue or doubleValue.

Pages and separators

So as to be more intelligible, the dialogues can use separator lines--just a plain horizontal lines between items, used to group the items in some logical way, or labels--text items, which does not edit anything, just bring some information. Besides, the items can be distributed in pages; at any moment, just one page is visible to the user, while all the other pages are represented by their titles only.

The CXDialog class provides an API to specify both of them programmatically:

+CXDialog *separator;

The separator just places the separator line between two dialogue items:

[CXDialog runDialogue:@(
  [CXDialog list:@"First Item",choices,someValues,0],
  [CXDialog list:@"Second Item",choices,someValues,1],
  [CXDialog separator],
  [CXDialog list:@"Third Item",choices,someValues,2],
)];

The dialogue is quite like the previous sample one, which we have used for illustration of the lists; this time, though, there will be a separator between its second and third line:

CXDialog6

+CXDialog *label(CXString *title,int alignment=XDLabelLeft,int font=XDLabelNormalFont,BOOL breakLine=YES);

This constructor adds a label into the dialogue or a page. The title is the label text; the remaining arguments allow to set the label attributes.

For alignment, XDLabelLeft, XDLabelCenter, or XDLabelRight can be used with the obvious meaning. The font can be either XDLabelNormalFont (the same font used for the normal dialogue items), or XDLabelSmallFont (a small non-bold font good for descriptions), or XDLabelSmallTitleFont (the same small font, but bolded; can be used for subtitles).

Too long labels are automatically broken into lines (at a word boundary, represented by a space) so as the dialogue does not grow too wide. In case you want to switch the behaviour off, set the breakLine argument to NO.

Incidentally, the symbolic values for the label attributes are selected so that you do not need to abide to the argument names: you can set the font in the alignment attribute or vice versa, and all will work properly.

+CXDialog *page(CXString *title,id delegate=nil,id label=nil,id valmetname=nil);

The page constructor just makes a new page, with the specified title. All the following items will be part of the new page. Thus, the previous dialogue can be re-written--quite inconveniently for the user!--to two pages, instead of just one with a separator:

[CXDialog runDialogue:@(
  [CXDialog page:@"Page One"],
  [CXDialog list:@"First Item",choices,someValues,0],
  [CXDialog list:@"Second Item",choices,someValues,1],
  [CXDialog page:@"Page Two"],
  [CXDialog list:@"Third Item",choices,someValues,2],
)];

The result would look like this:

CXDialog7a CXDialog7b

(We have added a button here: there is a strange error somewhere in the Epoc dialogue code, which causes dialogues with pages and without buttons to be strangely formatted. Try the "Machine information" dialogue on the S5mx, for example--it has this superfulous button as well <grin>. )

Note that in case the dialogue has some pages, the first one must be defined before the first item: it is impossible to have some items in pages, and some outside of them. Therefore, in case there is a page in the item array, but any items are before it, no page will be made, and the page object will be replaced by just a separator. Particularly, the following code is valid:

[CXDialog runDialogue:@(
  [CXDialog list:@"First Item",choices,someValues,0],
  [CXDialog list:@"Second Item",choices,someValues,1],
  [CXDialog page:@"Page Too Late"],
  [CXDialog list:@"Third Item",choices,someValues,2],
)];

but will be presented in a dialogue without any pages, which will look exactly the same way the separator sample (two figures back, the last one without a button).

Finally, the three id arguments allow to specify the page label, the page delegate, and the page validation method. They can be specified in any order, for they are recognised by contents: any string with a '-' prefix is the method, any other string is the label, and any non-string object is the delegate.

Refer above to "Labels" or "Delegates and validation" for a general discussion of what the delegate or label is; see below the "Line validation" and "Dialogue validation" for detailed description how these values are used. In general, the page delegate will get the method with the label as an argument to validate the page contents.

In case the delegate or the method is not specified, the dialogue default setting (the runDialogue method has the same three arguments) will be used for the page. Finally, in case the label is unspecified, the page will be unlabelled.

Buttons (basic services)

So as to specify which buttons should be placed into the dialogue, and how they should behave, the last item in the items array should be made by one of the ...buttons methods. There is a number of argumentless methods with a predefined set of standard buttons, and two general ones to specify programmatically any button set.

+CXDialog *cancelDoneButtons;
+CXDialog *browseCancelOkButtons;
+CXDialog *cancelOkButtons;
+CXDialog *continueButtons;
+CXDialog *cancelButtons;
+CXDialog *noYesButtons;

All the predefined button set methods just make the set of buttons as appropriate to the method name.

The button used can be determined using the runDialogue return value:

So as to show a query dialogue like this one

CXDialog8

the following code might be used:

if ([CXDialog runDialogue:@"Do you want XYZ?",@([CXDialog noYesButtons])]){ // "Yes" was selected...
  ...
} else { // "No" was selected...
  ...
}

Note one undescribed yet part of the APIwas used here: the possibility to specify the dialogue title as a string argument of the runDialogue method. It will be described below, in "Running a dialogue". There you will also learn that for such dialogues a runAlertPanel special method is available.

+CXDialog *buttons(CXArray *buttons,BOOL defaults=YES,BOOL direct=NO);

The generic method with an array argument allows to construct any button set, with arbitrary names, hot keys, and behaviour.

In the simplest possible case, the buttons is just an array of strings, which specify the button titles. So, a dialogue with the last item

...
[CXDialog buttons:@(@"Foo Bar",@"Others",@"Cancel",@"OK")],
...

will have four buttons with the appropriate titles:

CXDialog9

Note that the buttons have got a quite reasonable set of hot keys; that is a result of a comprehensible set of default assignments, which will be described in details below. Generally, though, the last button gets the Enter, the one before the Esc, and all other letters derived from their respective titles. Should you not want these assignments to occur, you can swith them off by specifying NO for the defaults argument (or by using the buttonsNoDefaults method, see below).

The default assignments not only assign the hot keys, but understand some predefined names as well, and automatically replace them by the appropriate ones. Thus,

titlewill be replaced by the standard string for
~caCancel
~oOK
~coContinue
~yYes
~nNo
~dDone

The previous example therefore might have been also written as

...
[CXDialog buttons:@(@"Foo Bar",@"Others",@"~ca",@"~ok")],
...

the only difference would be easier localization. Note also that we used the "~ok" instead of just "~o"; that is all right, for any string with the prefix given will be recognized. For example, instead of "~ca" we could have written "~cancel" (or perhaps "~california" or even "~carry for such a freedom?") with quite the same result.

In case you want to set a hot key directly to some button, just use an array instead of a string. Place the title as the first item in the array, and the letter to become the hotkey as the second one. For example, the settings

...
[CXDialog buttons:@(@"Foo Bar",@(@"Others",@"F"),@"~ca",@"~ok")],
...

will make for the following buttons:

CXDialog10

Note that the default assignment system is aware of the fact that the Ctrl+F combination is used already, and cannot be assigned to the "Foo Bar" button.

Apart from letters, you can use the following strings with special meaning:

stringhot key
"\0"Escape
"\n"Enter
"\t"Tab
" "Space
"\b"Delete

Note that you can set this way the hot keys Esc and Enter to anything, even the Esc to a title "OK" and Enter to a title "Cancel". Needless to say that such a usage is extremely discommended; unless you have extremely strong reasons to do otherwise, let the "OK" and "Cancel" buttons--if used at all--to have their default hot keys.

Now, let's see the ways how the programmer can enquire which of the buttons was selected by user. It is quite simple: the runDialogue method returns either nil (in case the dialogue was cancelled), or the button label. You can also get the value returned by the previous dialogue using the subdialogueHistory method, described below. The label you can either set for each button manually, or it can be assigned to it automatically.

The manual setting is straighforward: you set the label the very same way the hot key is specified, ie. using an array instead of a string to specify the button, and place the label inside it. The framework can distinguish the label and the hot key by contents (since the key string length is always one, while the label must have at least two characters); thence, you can use either of them or both at once:

...
[CXDialog buttons:@(
  ...
  @(@"One",@"A"), // hot key only
  @(@"Two",@"button 2"), // label only
  @(@"Three",@"X",@"bx"), // both
  ...)],
...

Buttons which do not have any label will get one automatically. For the standard button "OK" it is its hot key "\n" (incidentally, the button "Cancel"--if any, and if no label is specified for it--gets its own hot key "\0" for label as well, but the value is not returned, for the cancelled dialogue returns nil).

For other buttons the hot key would not do, for it can be localizable. Therefore, any other button without label will be labelled by its index in the buttons array, converted to an object by the standard XNUM2OBJ macro.

You can choose your own way. For dialogues whose buttons are pretty static (ie. always the same, and improbable to be changed in future software versions) the default labels would easily do:

CXString *retval=[CXDialog runDialogue:@([CXDialog buttons:@(
  @"One",@"Two",@"Three",@"~cancel",@"~ok")])];
if (retval)// not cancelled
  if ([retval isEqual:@"\n"]){ // "OK"
    ...
  } else switch (XOBJ2NUM(retval)) {
    case 0: // "One"
      ...
      break;
    case 1: // "Two"
      ...
      break;
    case 2: // "Three"
      ...
      break;
  }

On the other hand, in case the buttons are added dynamically (or are likely to be changed in future versions), the labels are much more convenient:

CXArray *buttons=@();
// add some buttons dynamically
if (...add button X?...)
[array addObject:@(@"X",@"bX")];
if (...add button Y?...)
[array addObject:@(@"Y",@"bY")];
if (...add button Z?...)
[array addObject:@(@"Z",@"bZ")];
if (...add button OK?...)
[array addObject:@"~ok"]; // automatic label's good enough
...
CXString *retval=[CXDialog runDialogue:@([CXDialog buttons:buttons])];
if ([retval isEqual:@"\n"]){ // "OK"
  ...
} else if ([retval isEqual:@"bX"]){ // "X"
  ...
} else if ([retval isEqual:@"bY"]){ // "Y"
  ...
} else if ([retval isEqual:@"bZ"]){ // "Z"
  ...
}

Note that in this case we did not check explicitly whether the dialogue was cancelled or not. That is quite right, for we use the XSdk [obj method] primitive, which, unlike the C++ rot, can cope with the situation the obj is nil: it returns nil again, and does nothing. Therefore, the code is completely valid: in case the retval was nil (dialogue was cancelled), all the isEqual methods return nil (which is just a zero value, the same as NO), and none of the button branches will be called.

The direct buttons' hot keys are checked whenever a key is pressed, regardless the modifiers. For normal, non-direct buttons, though, the Ctrl modifier is to be hold.

Before end of this paragraph, we should describe how the automatic hot keys are assigned when the defaults argument is set:

There is much more to the buttons; the more sophisticated services will be described below in "Nested dialogues" and "Buttons (delegation)".

+CXDialog *buttonsNoDefaults(CXArray *buttons,BOOL direct=NO);
+CXDialog *directButtonsNoDefaults(CXArray *buttons);

The buttonsNoDefaults is just a convenience method, which does nothing else than to call [self buttons:buttons,NO,direct]. Analogically, the directButtonsNoDefaults just calls [self buttons:buttons,NO,YES].

+CXDialog *buttons(int rsid);

The last button creator can be used in case you have got a button panel in the standard Epoc resource, and want to use it in a dialogue. Note that using of this method is strongly discommended, for the button panel from a resource would not be portable; you should use it only in case you get somehow the buttons packed in a resource and you cannot easily replace them by buttons generated by one of the more flexible way described above.

It is even possible this method will be removed from future XFoundation releases! So, do use it only when absolutely necessary.

Running a dialogue

We already know from the samples how the dialogues are run: the runDialogue class method is to be called, with an array of the dialogue lines for an argument. Therefore, let's just add a few details, along with the method declarations:

+id runDialogue(CXString *title,CXArray *items,id defaultDelegate=nil,id label=nil,id valmetname=nil);
+id runDialogue(CXArray *items,id defaultDelegate=nil,id label=nil,id valmetname=nil);

The two methods differ only in the first argument, which can be used to specify the dialogue title. There is another way to set the title, though: in case the first item in the items array is a string, it will be used for the dialogue title. Therefore, the two following source lines are functionally equivalent:

[CXDialog runDialogue:@"Title",@([CXDialog string:...],...)];
[CXDialog runDialogue:@(@"Title",[CXDialog string:...],...)];

Normally you would probably prefer the first way of specifying the title by the runDialogue method's argument; the second one, though, is quite handy for cases the dialogue contents--represented by the items array--is constructed independently of its usage. A typical example might be a nested dialogue, described below.

The three last arguments have more or less the same usage as their equivalents in the page method: they allow to specify the dialogue label, the default dialogue delegate, and the default dialogue validation method. They can be specified in any order, for they are recognised by contents: any string with a '-' prefix is the method, any other string is the label, and any non-string object is the delegate.

Refer above to "Labels" or "Delegates and validation" for a general discussion of what the delegate or label is; see below the "Line validation" and "Dialogue validation" for detailed description how these values are used. In general, the dialogue delegate will get the method with the label as an argument to validate the dialogue contents, for the dialogue as a whole, and for each page which does not have its own delegate and method.

In case the delegate is not specified, the dialogue will have no delegate; unless one is specified for some page or by the way described below, no delegate validation will occur. In case the method is not specified, a validateDialogue one will be sent to the delegate (in case it does implement it). Finally, in case the label is unspecified, the dialogue will be unlabelled.

Finally, for cases when the dialogue contents is prepared independently of its usage, there is a way to specify the delegate, method and label via the items contents (just like it is for the title): in case the first non-string item in the items array is a nested array, its contents are interpreted the very same way as the three last runDialogue arguments.

Let us to summarize: the dialogue title, default delegate and validation method, and the dialogue label, are pre-set from the runDialogue arguments. Then the items array is interpreted:

The runDialogue methods return nil or the selected button's label, as described in "Buttons (basic services)" above.

+id runAlertPanel(CXString *title,CXString *contents,CXString *defaultButton,id cancelButton,id otherButton,...);
+id runAlertPanelWithList(
CXString *title,CXString *contents,CXString *defaultButton,id cancelButton,id otherButton,VA_LIST arglist);

The runAlertPanel convenience method automatize the most common task of showing an alert dialogue: you can specify any title (even a nil one, but that is not recommended so as the user is not confused), any contents (using the printf-like formats including the "%@"), and up to three buttons.

Do please read the User Interface Guildelines in the Epoc SDK! They are pretty reasonable, and they show--among others--why the very common Windoze-like usage of three alert buttons is quite discommendable. Even that, there still remain situations when the three buttons come handy, thus we support them.

You can omit (using a nil) up to all three buttons. The default button is always shown; in case you use a nil, a standard "Continue" button will be used. The two remaining buttons are used only if you set them non-nil; you can use either a string to become the button title (the "~..." predefined titles are allowed), or an array containing a title string and a hot key string, just as you would use them with standard dialogues. In case you do not set any hot key, the cancel button will get the Esc, the other button Space (and the default one, naturally, Enter).

Regardless the hot keys you might set, the method returns a nil for the cancel button, @"\n" for the default button, and @"Other" for the other button.

You can freely use '\n' in the contents to make more-lines alerts. Besides, in case the alert would become too wide for the screen, the contents (generated from the contents argument used as a format, and the arglist or the arguments after the third button) will get broken to lines at spaces automatically. Finally, in case the alert gets too high, it would automatically use a scroller (just like any other XSdk dialogue).

+CXString *backgroundDialogueMessage;
+void setBackgroundDialogueMessage(CXString *s);

The XSdk dialogues automatically take care for cases the application runs in the background. If so, and a dialogue is opened, the user is shown an info message "<application name> needs your attention", whatever application he is currently using.

You can change the message using these services. The message you set can contain one argument "%@"; it will get replaced by the current application name automatically. In case you want to switch the message off, set @"". In case you want to return the default message, set nil.

Nested dialogues

Quite often a button in a dialogue is used to open a nested dialogue, which is run over the original one. When a nested dialogue is closed, the original dialogue continues running.

It would be possible to make nested dialogues programmatically via the button delegation, described below (the Symbian SDK does it analogically, making you to program another dialogue in the original dialogue's OkToExitL method). For such an often used technique it is quite inconvenient, though, again depriving the programmer of time which could and should be used better. You still can do so whenever appropriate, but for the majority of cases there is much more straighforward solution:

The CXDialog class allows to link a nested dialogue directly to a button. Done so, the programmer needs to concern no more: when the button is pressed, the nested dialogue will be automatically run. Even the ellipsis after such button's title is added automatically.

The API is straighforward: just prepare the nested dialogue's item array, and add it to the desired button the very same way a button label could have been added:

[CXDialog runDialog:@( // the original dialogue items:
  [CXDialog ...],
  ...
  [CXDialog buttons:@(
    ...
    @(@"Nested",@"N",@( // the nested dialogue items:
      @"Nested dialogue title",
      [CXDialog ...],
      ...
    ))]
  )];

Among the dialogue's buttons there will be a button with a title "Nested" and hotkey Ctrl+N. In case it is selected, a nested dialogue will be constructed using the item list from that button definition, and run. When the nested dialogue ends, the original dialogue continues working.

Here's why the dialogue title (and/or its delegate, validation method, and label as well) can be specified through the items array: we would need some special trick here to set them for the nested dialogue otherwise, and that would unnecessarily mist the dialogue API. In case the dialogue delegate, validation method, and label are not set in the nested dialogue, they will be inherited from the original dialogue.

Quite often, the return value of the nested dialogue is unimportant. Though, for cases you would need to know it, there is a special API for that: the subdialogueHistory method keeps track of all the values returned from a dialogue, whantever deep it is nested:

+CXArray *subdialogueHistory;

The return value is an array of items, which represent return values of all the recently called dialogues (see the autoCleansHistory below to know how many of them there will be).

Each item is an array, containing up to three objects. The first one is always present, and contain the dialogue return value; in case the dialogue returned nil, the Cancel string "\0" is used instead. The second object is the dialogue label, and is present only in case the dialogue is labelled. The last object--if present--is an array, and contains an item for each nested dialogue; these items have the very same structure.

Therefore, just after closing by a button with label "Exit" a main dialogue with label "Main", which ran twice a subdialogue labelled "Sub", while first time the subdialogue was cancelled, and second time closed using Enter (ouch! Read it at least twice, did you not?) the subdialogueHistory array will contain

(
  ("Exit","Main",(
    ("\0","Sub"),
    ("\n","Sub")
  )
)

Normally, the history is cleaned whenever a root level dialogue is called. Therefore, the subdialogueHistory array will contain normally no item (in case no dialogue was run yet), or just one item (for the last dialogue called). Though, you can override this:

+BOOL autoCleansHistory;
+void setAutoCleansHistory(BOOL cleans);

In case the autoCleansHistory is set to NO, the history is never cleaned; the subdialogueHistory array will contain as many items as the number of dialogues opened since the application started.

Obviously, it is meant to be just a temporary solution, to be used locally this way:

...
[CXDialog setAutoCleansHistory:NO];
...
// a number of haphazardly called dialogues...
...
[CXDialog setAutoCleansHistory:YES];
CXArray *retvals=[CXDialog subdialogueHistory];
// read and interpret the returned values
...

Line validation

There are two validations: the line validation, which occurs whenever a dialogue line contants is changed, and the dialogue validation, which occurs just before the dialogue is terminated. This paragraph describes the line validation.

Only lines which have a non-empty label (see the "Dialogue items" above) are validated. The reasons are straighforward: non-labelled lines would be difficult to identify. Whenever a line with a non-empty label is changed, though, the validation occurs.

The line validation is performed through the delegate. Primarily, the delegate for the changed line is determined. In case the line is part of a page which has a delegate, this delegate is used. Otherwise, the dialogue delegate is used. (There is one quite rare exception to these rules, described below.)

Then, the delegate is queried whether it conforms to the XDialogueLineValidation protocol, which is defined this way:

@protocol XDialogueLineValidation
-void dialogueLineChanged(CXString *dialLabel,CXString *pageLabel,CXString *itemLabel,int line,id value,XDialogueAccess *access);
@end

In case the delegate conforms to the protocol, its dialogueLineChanged method is called. The arguments specify the dialogue, page and line, for which the validation occurs: the dialogue and page labels can be nil, the item label will always be non-empty.

The line argument allows easy access to other dialogue lines. Quite often we would need to change somehow the lines adjacent to the one which is just validated; imagine the following dialogue:

CXDialog11

Quite probably we would want to disable the "State" line in case the "Foreign" one contains "No". So as to fo that, the "Foreign" line must be labelled, and when it is validated, it will disable the "State" line when set to "No". The question is, how the "State" line will be identified to be disabled here?

It would be possible to use a label for the "State" line as well, and identify it by the label. Though, in such a plain case it would be quite superfluous; instead, we just disable line with index line+4. As for the actual way to disable the line, the access can be used; see the "Access to a running dialogue" immediately below.

Finally, the value line contains the new value of the line validated. In case it is a numeric value (or a BOOL), it is converted to an object using the standard XNUM2OBJ macro.

Unlike Symbian, we believe that should the programmer raise an uncatched exception, he means it, even inside a dialogue! Therefore, in case an exception is raised inside the dialogueLineChanged method, the dialogue will be aborted, and the nearest X_HANDLERexception harness will get the exception (in case there is no X_HANDLER harness, the application kit code will catch it, and the event will be discarded).

In some very special cases, you might need to use more different delegates even inside one page. The APIallows for that as well: between the CXDialog objects in the items array there can be other objects. In case such an object is a CXArray, its first item becomes the delegate; otherwise the object itself becomes the delegate. The delegate is valid for all subsequent dialogue items up to the end of the current page, or the next non-CXDialog object, whichever comes first.

Access to a running dialogue

When a dialogue line is validated, we often need to change the dialogue accordingly to its new value; the most often actions needed are to enable/disable other lines, and to enable/disable the dialogue buttons. The access argument of the dialogueLineChanged method ensures that; it implements the  protocol:

@protocol XDialogueAccess
-id valueForDetailLine(CXString *label);
-id valueForDetailLine(int line);
-void setEnabledDetailLines(id lines,BOOL enabled=YES);
-void setEnabledDetailLine(int line,BOOL enabled=YES);
-void setEnabledButton(CXString *label,BOOL enabled=YES);
-void setEnabledButton(int index,BOOL enabled=YES);
@end

Note: for historical reasons the access might not be a CXObject!It is just any C++ object, accessed thru the protocol. That imposes one limitation: you cannot use the XSdk [obj method] primitive to call these methods--for example, [access setEnabledButton:1] would raise an exception. You must use the C++ primitive here: access->setEnabledButton(1).

The meaning of the protocol services should be obvious enough: both the lines and the buttons can be identified either by the label, or by the index--for lines, the index should be derived from the line argument of the dialogueLineChanged method; for buttons it is the absolute index in the buttons array.

The setEnabledDetailLines method is able to accept quite a variety of arguments: it can be either a string label, or a string "#<line number>", or an array of them.

Thus, the dialogueLineChanged method to ensure the proper enabling/disabling the "State" line for the dialogue from the last example would--assumed the "Foreign" line has label "foreign"--look like this:

-void dialogueLineChanged(CXString *dialLabel,CXString *pageLabel,CXString *itemLabel,int line,id value,XDialogueAccess *access)
{
  if ([itemLabel isEqual:@"foreign"])
    access->setEnableDetailLine(line+4);
}

It should be emphasized, though, that in case the dialogue is likely to be changed in future releases, it would be better to use a label. With the line number, the code obviously must be changed in case eg. new line is inserted, or the separator is deleted.

Dialogue validation

Unlike the line validation, described above, the dialogue validation occurs before the dialogue is terminated (otherwise than cancelled). Generally, all the delegates set for the dialogue, its pages, or its items are asked whether the dailogue can be terminated.

So as the dialogue validation is even more flexible than the line validation, no protocol is used. Instead, an action is sent to the delegates (more precisely, to those which responds to the action). See the preprocessor and the CXObject for more on actions and responding.

Each of the delegates can specify the name of the method which should be sent to it to validate the dialogue. We have already seen how to specify the method names for the dialogue or page delegate: the name is given with a "-" prefix. In case we want to specify the method for a delegate set freely between dialogue items (see the end of the "Line validation" paragraph), an array containing the delegate and the method name can be used here.

The validation action gets in its value argument a dictionary, which contain detailed information of the dialogue validated. It can contain the following keys and values:

key  value
"button": the label (or general contents, see below) of the button which caused the dialogue termination
"dialogue": the dialogue label, if any
"page": the current paga label, if any
"item": the current item label, if any
"line": XNUM2OBJ, the current item index in the whole dialogue
"value": the current item value
"access": XNUM2OBJ, the access object address (access might not be a CXObject).

The validation action should return a nil in case the dialogue is all right and can be terminated. Otherwise it should return either a string or an array of one or two strings; in both cases the dialogue will not be closed, and

In case the dialogue is to be cancelled, the delegates cannot "validate" it (for a dialogue must be cancellable anytime), but, if need be, they can be informed: each dialogue, page or item delegate, which responds to the dialogueCancelled action, will get it before the dialogue is cancelled.

Buttons (delegation)

Although the so far described button API is flexible enough, it still does not allow to attach an arbitrary action to a button in the dialogue; to do so we need the button delegation technique.

Whenever a button is pressed, its label is found first (see above for button labels), and then its delegate and action method are found:

Then, the delegate is asked whether it responds to the action method. If so, the method is called; in its value argument it will get the button's label (if any). The return value of the action method will determine what is to be done with the dialogue:

Dialogue UI providers

Finally, we are more or less through: this API will be rarely used by your code. Normally, it is used by the XApplication framework only: it assigns the class, which provides the actual user interface for the dialogue services.

+Class dialogueUIProvider;
+void setDialogueUIProvider(Class provider);

The class which is set to be the dialogue UI provider must implement the protocol XDialogueUIProvider:

@protocol XDialogueUIProvider
+id _runDialogue(CXArray *items,id defaultDelegate,CXString *label,CXString *valmetname);
@end


Copyright © 1999-2000 X.soft, all rights reserved