Newton 2.x Q&A Category: Pickers, Popups and Overviews

Copyright © 1997 Newton, Inc. All Rights Reserved. Newton, Newton Technology, Newton Works, the Newton, Inc. logo, the Newton Technology logo, the Light Bulb logo and MessagePad are trademarks of Newton, Inc. and may be registered in the U.S.A. and other countries. Windows is a registered trademark of Microsoft Corp. All other trademarks and company names are the intellectual property of their respective owners.


For the most recent version of the Q&As on the World Wide Web, check the URL: http://www.newton-inc.com/dev/techinfo/qa/qa.htm
If you've copied this file locally, click here to go to the main Newton Q&A page.
This document was exported on 7/23/97.

Pickers, Popups and Overviews


Determining Which ProtoSoupOverview Item Is Hit (2/5/96)

Q: How do I determine which item is hit in a protoSoupOverview?

A: There is a method called HitItem that gets called whenever an item is tapped. The method is defined by the overview and you should call the inherited one. Also note that HitItem gets called regardless of where in the line a tap occurs. If the tap occurs in the checkbox, you should do nothing, otherwise you should do something.

The method is passed the index of the hit item. The index is relative to the item displayed at the top of the displayed list. This item is always the current entry of the cursor used by protoSoupOverview. So, you can find the actual soup entry by cloning the cursor and moving it.

Here is an example of a HitItem method. If the item is selected (the checkbox is not tapped) then the code will set an inherited cursor (called myCursor) to the entry that was tapped on:

func(itemIndex, x, y)
begin
    // MUST call the inherited method for bookeeping
    inherited:HitItem(itemIndex, x, y);
    
    if x > selectIndent then
    begin
  // get a temporary cursor based on the cursor used
  // by soup overview
        local tCursor := cursor:Clone();

  // move it to the selected item
        tCursor:Move(itemIndex) ;

  // move the inherited cursor to the selected entry
        myCursor:Goto(tCursor:Entry());

  // usually you will close the overview and switch to
  // some other view
        self:Close();
    end;
    // otherwise, just let them check/uncheck
 // which is the default behavior
end


Displaying the ProtoSoupOverview Vertical Divider (2/5/96)

Q: How can I display the vertical divider in a protoSoupOverview?

A: The mechanism for bringing up the vertical divider line was not correctly implemented in protoSoupOverview. You can draw one in a viewDrawScript as follows:

    // setup a cached shape for efficiency
    mySoupOverview.cachedLine := nil;

    mySoupOverview.viewSetupDoneScript := func()
    begin
       inherited:?viewSetupDoneScript();
    
       local bounds := :LocalBox();
       cachedLine := MakeRect(selectIndent - 2, 0,
            selectIndent - 1, bounds.bottom);
    end;
    
    mySoupOverview.viewDrawScript := func()
    begin
       // MUST call inherited script
       inherited:?viewDrawScript();
    
       :DrawShape(cachedLine, 
            {penPattern: vfNone, fillPattern: vfGray});
    end;


Validation and Editing in ProtoListPicker (4/1/96)

Q: I am trying to use the ValidationFrame to validate and edit entries in a protoListPicker. When I edit certains slots I get an error that a path failed. All the failures occur on items that are nested frames in my soup entry. What is going on?

A: The built-in validation mechanism is not designed to deal with nested soup information. In general, you gain better flexibility by not using a validationFrame in your pickerDef, even if you have no nested entries. Instead, you can provide your own validation mechanism and editors:

Define a Validate method in your picker definition
Define an OpenEditor method in your picker definition
Draw a layout for each editor you require

pickerDef.Validate(nameRef, pathArray)

nameRef - nameRef to validate
pathArray - array of paths to validate in the nameRef
returns an array of paths that failed, or an empty array

Validate each path in pathArray in the given nameRef. Accumulate a list of paths that are not valid and return them.

The following example assumes that pickerDef.ValidateName and pickerDef.ValidatePager have been implemented:

pickerDef.Validate := func(nameRef, pathArray)
begin
    // keep track of any paths that fail
    local failedPaths := [];
                                                        
    foreach index, path in pathArray do
    begin
        if path = 'name then
        begin
            // check if name validation fails
            if NOT :ValidateName(nameRef) then
                // if so, add it to array of failures
                AddArraySlot(failedPaths, path);
        end;
        else begin
            if NOT :ValidatePager(nameRef) then
                AddArraySlot(failedPaths, path);
        end;
    end;
    // return failed paths or empty array
    failedPaths;
end;


pickerDef.OpenEditor(tapInfo, context, why)

The arguments and return value are as per OpenDefaultEditor. However, you need to use this instead of DefaultOpenEditor.

pickerDef.OpenEditor := func(tapInfo, context, why)
begin
   local valid = :Validate(tapInfo.nameRef, tapInfo.editPaths) ;
    if (Length(valid) > 0) then
        // if not valid, open the editor
        // NOTE: returns the edit slip that is opened
        GetLayout("editor.t"):new(tapInfo.nameRef,
            tapInfo.editPaths, why, self, 'EditDone, context);
    else
    begin
        // the item is valid, so just toggle the selection
        context:Tapped('toggle);
        nil;                                        // Return <nil>.
    end;..
end;

The example above assumes that the layout "editor.t" has a New method that will open the editor and return the associated View.

The editor can be designed to fit your data. However, we suggest that you use a protoFloatNGo that is a child of the root view created with the BuildContext function. You are also likely to need a callback to the pickderDef so it can appropriately update the edited or new item. Finally, your editor will need to update your data soup uing an "Xmit" soup method so that the listPicker will update.

In the OpenEditor example above, the last three arguments are used by the editor to send a callback to the pickerDef from the viewQuitScript. The design of the callback function is up to you, here is an example:
pickerDef.EditDone := func(nameRef, context)
begin
   local valid = :Validate(tapInfo.nameRef, tapInfo.editPaths) ;
    if (Length(valid) > 0) then
    begin
    // Something failed. Try and revert back to original
        if NOT :ValidatePager(nameRef) AND
            self.('[pathExpr: savedPagerValue, nameRef]) = nameRef             then
            nameRef.pager := savedPagerValue.pager;
        
        context:Tapped(nil);        // Remove the checkmark
    end;
    else
        // The nameRef is valid, so select it.
        context:Tapped('select);
    
    // Clear the saved value for next time.
    savedPagerValue := nil;        
end;


Picker List is Too Short (4/29/96)

Q: I have items in my picker list with different heights that I set using the fixedHeight slot. When I bring up the picker, it is not tall enough to display all the items. Worse, I cannot scroll to the extra items. What is going on?

A: The fixedHeight slot is used for two separate things. Any given pick item can use the fixedHeight slot to specify a different height. This works fine.

However, the code in Newton 2.0 OS that determines how big the list should be also uses the fixedHeight slot of the first pick item (in other words, pickItems[0]) if it exists. It is as if the following code executes:

local itemHeight := kDefaultItemHeight;
if pickItems[0].fixedHeight then
   itemHeight := pickItems[0].fixedHeight;
local totalHeight := itemHeight * Length(pickItems);

This total height is used to figure out if scrolling is required. As you can see, this can cause problems if your first item is not the tallest one. The solution is to make sure the first item in your pickItems array has a fixedHeight slot that is sufficiently large to make scrolling work correctly. This may be fixed in future revisions of the NewtonOS.

Note that there will be similar problems if your pick items contain icons. The system will use the default height unless you specify a fixedHeight slot in your first item. The default height is not tall enough for most icons. In other words, if you have icons in your pick items, you must have a fixedHeight slot in the first item that is set to the height of your icon.


Tabs Do Not Work With ProtoTextList (5/8/96)

Q: I tried to use tabs to get columns in a protoTextList but they do not appear. How do I get columns?

A: The text view in protoTextList is based on a simple text view which does not support tabs. If you want scrolling selectable columns you can use shapes to represent the rows. If you need finer control, use the LayoutTable view method.


How to Avoid Problems with ProtoNumberPicker (8/23/96)

Q: I am thinking of using protoNumberPicker for input. (or) I have used protoNumberPicker and have encountered a bug/misfeature/problem. What should I use?

A: protoNumberPicker has several instabilities and bugs. We recommend that you use the DTS sample code "protoNumberPicker_TDS". It provides all of the features of protoNumberPicker with none of the bugs. It also provides additional functionality that is not in protoNumberPicker. See the sample code for more detail.


Single Selection in ProtoListPicker-based Views (9/20/96)

Q: How do I allow only one item to be selected in a protoListPicker, protoPeoplePicker, protoPeoplePopup, or protoAddressPicker?

A: The key to getting single selection is that single selection is part of the picker definition and not an option of protoListPicker. That means that the particular class of nameRef you use must include single selection. In general, this requires creating your own subclass of the particular name reference class.

The basic solution is to create a data definition that is a subclass of the particular class that your protoListPicker variant will view. That data definition will include the singleSelect slot. As an example, suppose you want to use a protoPeoplePopup that just picks individual people. You could use the following code to bring up a protoPeoplePopup that only allowed selecting one individual at one time:

    // register the modified data definition
    RegDataDef('|nameref.people.single:SIG|,
    {_proto: GetDataDefs('|nameRef.people|), singleSelect: true});

    // then pop the thing
    protoPeoplePopup:New('|nameref.people.single:SIG|,[],self,[]);

    // sometime later
    UnRegDataDef('|nameref.people.single:SIG|);

For other types of protoListPickers and classes, create the appropriate subclass. For example, a transport that uses protoAddressPicker for emails might create a subclass of '|nameRef.email| and put that subclass symbol in the class slot of the protoAddressPicker.

Since many people are likely to do this, you may cut down on code in your installScript and removeScript by registering your dataDef only for the duration of the picker. That would mean registering the class just before you pop the picker and unregistering after the picker has closed. You can use the pickActionScript and pickCanceledScript methods to be notified when to unregister the dataDef.


How to Change Font or LineHeight in ProtoListPicker (9/20/96)

Q: How do I set a different font for the items in the protoListPicker?

A: There is a way to change the font in the Newton 2.0 OS, however, we intend to change the mechanism in the future. Eventually, you will be able to set a viewFont slot in the protoListPicker itself and have that work (just like you can set viewLineSpacing slot now). In the meantime, you need a piece of workaround code. Warning: you must set the viewFont of the listPicker AND include this workaround code in the viewSetupDoneScript:

func()
    begin
       if listBase exists and listBase then
          SetValue(listBase, 'viewFont, viewFont) ;

           inherited:?viewSetupDoneScript();
    end;

This will set the viewFont slot of the listBase view to the viewFont of the protoListPicker. You cannot rely on the listbase view always being there, hence the test for its existence.

Note that you can use the same code to modify the lineHeight slot of the listPicker. Just substitute lineHeight for viewFont in the code snippet. The one caveat is that the lineHeight must be at least 13 pixels.


How to Preselect Items in ProtoListPicker (9/20/96)

Q: If I put name references in the selected array of a protoListPicker, it throws a -48402 error. How do I preselect items?

A: In the MessagePad 120/130 units it is not possible to preselect items in the listPicker and have it work correctly. We recommend that you use the "protoSlimPicker" DTS Sample Code instead.


How to Get ProtoSoupOverview Selections (10/3/96)

Q: How do I get the selected items in protoSoupOverview?

A: The final documentation inadvertantly left out the following documentation on the selected slot:

selected - Required. Initially set to nil; it is modified by protoSoupOverview as the user selects and deselects overview items.
This slot contains an array of soup entry aliases to the selected items when the overview is closed, or nil or the empty array if there is no selection. For example:
                    [[alias: NIL, 66282812, 84, "Names"], 
                 [alias: NIL, 66282812, 85, "Names"]]


Dynamically Adding to ProtoTextList Confuses Scrolling (1/15/97)

Q: I am adding items to a protoTextList after it is displayed. I add an item and scroll to highlight that item. However, the state of the scroll arrows does not correctly get updated. Sometimes it will indicate that there are more items to scroll when it is really at the end of the list.

A: There is a problem with Newton 2.0 OS devices (although not Newton 2.1 OS devices) that causes the protoTextList to reset the scroll distance when you update the listItems array. The workaround is to always scroll the list to the top before calling SetupList when you add items. Then you can scroll the list to where you want it. Note that this workaround is safe to use in Newton 2.1 OS as well. In other words, if you are adding items to a protoTextList, use this workaround unless your application is Newton 2.1 OS-specific. This method will add a single item to the protoTextList, set the highlighted item to the new item and scroll if required. It will also make sure the item is unique.

    AddListItem := func(newItem)
    begin
         // Insert the item if not already in Array
        local index := BInsert(listItems, item, '|str<|, nil, true);

        // item must be in the array and index will point to the item.

        if NOT index then
        begin
            :Notify(kNotifyAlert, kAppName, "Duplicate entry.");
            return nil;
        end;

        // workaround a bug in 2.0 that causes the
        // scroll arrows to get out of sync
        // do this by scrolling to the top
        :DoScrollScript(-viewOriginY) ;

        self:SetUpList();

        // Setting the selection slot will highlight the item
        selection := index;

        // scroll to show the new item
        if index >= viewLines then
            :DoScrollScript((index - viewLines + 1) * lineHeight) ;

        self:RedoChildren();

        return true;
    end ;


NEW: ProtoPicker Lists are Sometimes Blank (5/16/97)

Q: I have a protoPicker that I reuse in my application. Sometimes the picker will be blank when I open it. What's happening?

A: ProtoPicker does not correctly reset the viewOriginY slot if you display a list that requires scrolling, and then display one that does not require scrolling. The solution is to manually reset the viewOriginY slot to 0 if you dynamically change the contents of the picker.


NEW: ProtoPeoplePicker Name Display Changed in Newton 2.1 (7/2/97)

Q: In Newton OS 2.0, the protoPeoplePicker displayed names as "last, first", but in Newton 2.1 OS it displays "first last". How can I make protoPeoplePicker display the original way?

A: You will need to create a subclass of the nameRefDataDef for people that will display the name in "last, first" format. The good news is that this workaround will work on both Newton 2.0 and Newton 2.1. The basic steps are:

1. Register your own subclass of nameRefDataDef that does the right thing

2. Use this new data def class in the dataClass slot of your peoplePicker

3. Unregister your subclass when you are done (for instance, when quitting the application)


Here is some code that can be used to register a subclass. Put the following in a text file in your project:

    // create a unique symbol for the the data def
    DefineGlobalConstant('kMyDataDefSym,
        Intern("nameRef.people.lastFirst:" & kAppSymbol)) ;

    DefineGlobalConstant('kMyGetFunc,
        func(item, fieldPath, format)
        begin
            // if this is a person, not a company, modify stuff
            local entry := EntryFromObj(item) ;
    
            if fieldPath = 'name AND format = 'text AND entry AND 
                IsFrame(entry) AND ClassOf(entry) = 'person then
            begin
                local nameFrame := entry.name ;
                if nameFrame AND nameFrame.first AND nameFrame.last then
                    return nameFrame.last & ", " & nameFrame.first ;
                else
                    return inherited:Get(item, fieldPath, format) ;
            end 
            else
                return inherited:Get(item, fieldPath, format) ;
        end
    ) ;


Put this code into the viewSetupFormScript of the base view of your application:

            // register my modified people data def
        RegDataDef(kMyDataDefSym, {_proto: GetDataDefs('|nameRef.people|),
                        Get: kMyGetFunc}) ;


Put this code into the viewQuitScript of the base view of your application:

        // unregister my modified people data def
        UnRegDataDef(kMyDataDefSym) ;



Use the kMyDataDefSym constant as the value for the dataClass slot of your protoPeoplePicker or protoPeoplePopup


NEW: Setting Default Choices for Country or State Pickers (7/15/97)

Q: How do I set a default country for protoCountryTextPicker or default state for protoUSstatesTextPicker?

A: You can specify a default country/state by adding a slot called 'default to the params frame. The slot must contain the name of the country (protoCountryTextPicker) or state (protoUSstatesTextPicker) as a string. For example, if you wanted the protoCountryTextPicker to default to Canada, you could do the following in the viewSetupFormScript:

    // get a writeable copy of the params frame
    self.params := Clone(params);
    params.default := "Canada";