Newton 2.x Q&A Category: Views

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.

Views


How to Save the Contents of clEditView (10/4/93)

Q: How can I save the contents of a clEditView (the children paragraph, polygon, and picture views containing text, shapes, and ink) to a soup and restore it later?

A: Simply save the viewChildren array for the clEditView, probably in the viewQuitScript. To restore, assign the array from the soup to the viewChildren slot, either at viewSetupFormScript or viewSetupChildrenScript time; or later followed by RedoChildren.

You shouldn't try to know "all" the slots in a template in the viewChildren array. (For example, text has optional slots for fonts and tabs, shapes have optional slots for pen width, and new optional slots may be added in future versions.) Saving the whole array also allows you to gracefully handle templates in the viewChildren array that don't have an ink, points, or text slot. In the future, there may be children that represent other data types.


Adding Editable Text to clEditViews (6/9/94)

Q: How can I add editable text to a clEditView? If I drag out a clParagraphView child in NTK, the text is not selectable even if I turn on vGesturesAllowed.

A: clEditViews have special requirements. To create a text child of a clEditView that can be selected and modified by the user (as if it had been created by the user) you need to do the following:

    textTemplate := {
        viewStationery: 'para,
        viewBounds: RelBounds(20, 20, 100, 20),
        text: "Demo Text",
    };
    AddView(self, textTemplate);

The view must be added dynamically (with AddView), because the clEditView expects to be able to modify the contents as the user edits this item. The template (textTemplate above) should also be created at run time, because the clEditView adds some slots to this template when creating the view. (Specifically it fills in the _proto slot based on the viewStationery value. The _proto slot will be set to protoParagraph) If you try to create too much at compile time, you will get -48214 (object is read only) errors when opening the edit view.

The minimum requirements for the template are a viewStationery of 'para, a text slot, and a viewBounds slot. You can also set viewFont, styles, tabs, and other slots to make the text look as you would like.


TieViews and Untying Them (6/9/94)

Q: What triggers the pass of a message to a tied view? If I want to "untie" two views that have been tied with TieViews, do I simply remove the appropriate slots from the viewTie array?

A: The tied view's method will be executed as a result of the same actions that cause the main view's viewChangedScript to be called. This can happen without calling SetValue, for example, when the user writes into a view that has recognition enabled, the viewChangedScript will get called.

As of Newton 2.1 OS, there is no API for untying tied views. It may be wise to first check for the existance of an UntieViews function, and call it if it exists, but if it does not, removing the pair of elements from the tied view's viewTie array is fine.


Immediate Children of the Root View Are Special (11/17/94)

Q: In trying to make a better "modal" dialog, I am attempting to create a child of the root view that is full-screen and transparent. When I do this, the other views always disappear, and reappear when the window is closed. Why?

A: Immediate children of the root view are handled differently by the view system. They cannot be transparent, and will be filled white unless otherwise specified. Also, unlike other views in Newton 2.0 OS, their borders are considered part of the view and so taps in the borders will be sent to them.

This was done deliberately to discourage tap-stealing and other unusual view interaction. Each top level view (usually one application) is intended to stand on its own and operate independently of other applications.

So-called "application modal" dialogs can and should be implemented using the technique you describe with the transparent window as a child of the application's base view.

You can make system modal dialogs with the view methods FilterDialog and ModalDialog. (See the Q&A "FilterDialog and ModalDialog Limitations" for important information on those methods.)


ViewIdleScripts and clParagraphViews (8/1/95)

Q: Sometimes a clParagraphView's viewIdleScript is fired off automatically. (For example, an operation which results in the creation or changing of a keyboard's input focus within the view will trigger the viewIdleScript.) Why does this happen and what can I do about it?

A: The clParagraphView class internally uses the idle event mechanism to implement some of its features. Unfortunately, any viewIdleScripts provided by developers also execute when the system idle events are processed. Only the "heavyweight" views do this, "lightweight" paragraph views (in other words, simple static text views) do not.

There is no workaround in the Newton 1.x OS or Newton 2.0 OS while using clParagraphView viewIdleScript. You can either accept the extra idle script calls, or use another non-clParagraphView based view to implement your idle functions.


FilterDialog and ModalDialog Limitations (2/5/96)

Q: After closing a view that was opened with theView:FilterDialog(), the part of the screen that was not covered by the theView no longer accepts any pen input. theView is a protoFloatNGo. Is there some trick?

A: There is a problem with FilterDialog and ModalDialog when used to open views that are not immediate children of the root view. At this point we're not sure if we'll be able to fix the problem.

You must not use FilterDialog or ModalDialog to open more than one non-child-of-root view at a time. Opening more than one at a time with either of these messages causes the state information from the first to be overwritten with the state information from the second. The result will be a failure to exit the modality when the views are closed.

Here are some things you can do to avoid or fix the problem with FilterDialog.

Redesign your application so that your modal slips are all children of the root view, created with BuildContext. This is the best solution because it avoids awkward situations when the child of an application is system-modal. (Application subviews should normally be only application-modal.)

Use the ModalDialog message instead of FilterDialog. ModalDialog does not have the child-of-root bug. (FilterDialog is preferred, since it uses fewer system resources and is faster.)

Here is some code you can use to work around the problem much like a potential patch would. (This code should be safe if a patch is madethe body of the if statement should not execute on a corrected system.)

view:FilterDialog();
if view.modalState then
    begin
        local childOfRoot := view;
        while childOfRoot:Parent() <> GetRoot() do
            childOfRoot := childOfRoot:Parent();
        childOfRoot.modalState := view.modalState;
    end;

This only needs to be done if the view that you send the FilterDialog message to is not an immediate child of the root. You can probably improve the efficiency in your applications, since the root child is ususally your application's base view, which is a "well known" view. That is, you may be able to re-write the code as follows:

view:FilterDialog();
if view.modalState then
    base.modalState := view.modalState;


Using Proportional View Alignment Correctly (6/20/96)

Q: I am trying to use proportional view alignment but things don't seem to be working correctly. For instance, if I have a view which is full justified or center justified, proportional view alignment doesn't seem to work at all. Whats wrong?

A: Proportional justification only works if you are using left, right, top, or bottom justification. This is true for both sibling and parent justification.

Proportional justification is very similar to full justification. The view system needs some reference point at which to position the view. If you specify full or center justification, and you are also using proportional justification, the reference point is undefined.

Additionally, if you are using right or bottom justification, you will need to specify negative values for your proportional bounds. For instance, if you want a view to take up the right 30 percent of its parent, you would specify the following view bounds:
    {left: -30, top: <top>, right: 0, bottom: <bottom>}


and the following view justification:
    vjParentRightH + vjLeftRatio


Drag and Drop Caches the Background Bitmap (7/15/96)

Q: I am trying to implement drag scrolling. Although I can scroll the contents of the window, when I drag the item back into the window, it strips away the updated (scrolled) contents and leaves the original (unscrolled) contents behind. How can I get this to work?

A: Unfortunately, you have hit a design limitation of the Drag and Drop implementation. When you send the DragAndDrop message to a view, the bitmap for the pre-drag state is cached. Once the drag loop starts, you can not update that cached bitmap. When you "scroll" the view (probably using RefreshViews), you update the screen, but the cached bitmap is still there and is used by the DragAndDrop routine to update the screen as the dragged item is moved.


Default and Close Keys in Confirm Slips (2/28/97)

Q: Is there any way to put a keyboard default on a confirm dialog?

A: Yes. For both ModalConfirm and AsyncConfirm, the 2.0 Newton Programmer's Reference says you may pass three types of things as the buttonList argument: a symbol ('okCancel or 'yesNo), and array of strings, or an array of frames with 'value and 'text slots.

In the Newton 2.1 OS, this API has been extended to allow for default key and close key behavior. There are four new symbols that are allowed: 'okCancelDefaultOk, 'okCancelDefaultCancel, 'yesNoDefaultYes, and 'yesNoDefaultNo. They do the obvious thing, setting the default key as specified and the close key to Cancel or No if those aren't the default. However, using these symbols on a Newton 2.0 OS device will result in the "OK" and "Cancel" buttons always being displayed, even if you specify 'yesNoDefaultYes or 'yesNoDefaultNo.

The array-of-frames flavor for the buttonList argument allows an additional slot, called 'keyValue. Supported values for this slot are the symbols 'default and 'close, or NIL/not present. 'default makes the button the default key, and 'close makes the button activate with the close key. Any other value will cause a problem in the current Newton 2.1 implementation. The keyValue slot is ignored on the Newton 2.0 OS.

For compatibility, we recommend avoiding the 'yesNoDefaultYes and 'yesNoDefaultNo symbols if you intend to run on both Newton 2.0 and 2.1 devices. Instead, use one of these specifiers:
    '[{text: "Yes", value: TRUE, keyValue: default}, {text: "No", value: NIL, keyValue: close}]

    '[{text: "Yes", value: TRUE}, {text: "No", value: NIL, keyValue: default}]


Screen Rotation and Linked Views or BuildContext Slips (3/10/97)

Q: I've got a linked view open, and I'm trying have it rotate with ROM_DefRotateFunc. When I rotate the screen the base view rotates properly, but the linked view closes. Do I need to add a ReorientToScreen slot to the linked view?

A: When the user requests a screen rotation, the OS first checks each immediate child of the root view to see if it will still operate on the rotated screen. Having a ReorientToScreen slot in the view tells the OS that this view is OK, so the slot is used first as a token ("magic cookie") to tell the OS that this view knows about rotation. Later during the rotation operation, the ReorientToScreen message is sent to your application's base view and to other views that are immediate children of the root view. That method then performs its second function, which is to completely handle resizing the view and its children for the new screen size. (Even views which are small enough so that no resizing is necessary need a ReorientToScreen method. That method may need to move the base view to ensure that it remains on-screen after rotation.)

It's convenient to use the ROM_DefRotateFunc for this script, since it fills the magic cookie requirement and handles resizing for most views. ROM_DefRotateFunc is very simple: it send a close and then an open message to the view. Since well-written applications take screen size into account when they open, this works fine in most cases. However, applications that keep track of internal state that isn't preserved when the app is closed can't use ROM_DefRotateFunc, because when the app reopens on the rotated screen, it will look different. Opening a linked subview is one example of this; it doesn't usually make sense to remember that a slip is open, since it's usually closed when your application is closed.

Adding a ReorientToScreen method to your linked views wouldn't help; since they are descendents of your base view and not children of the root view, the OS wouldn't handle these views. (It's up to your application to keep its kids under control.) You could change your application so that it kept track of whether the linked views were open or closed, and restored them to the same state when it was reopened. However, this might be confusing to users who closed your app and then opened it again much later.

A better workaround is to implement your own ReorientToScreen method, which either resizes the views so they fit on the new screen, or which closes and reopens the views such that the floaters also re-open. By using the ReorientToScreen message to handle the special case, you get to do something different during rotation versus during opening at the user request (for example, after tapping on the Extras icon.)

Slips created with BuildContext also must be handled carefully during rotation. Because they are themselves children of the root view, they'll each need their own ReorientToScreen method or the screen may not be rotatable when they are open or they won't reopen after rotation. If you use ROM_DefRotateFunc, the slip itself will be closed and reopened, and care may need to be taken to ensure the slip properly handles being reopened, and that its connection to its controlling application is not lost.


How to Get Data From a ProtoTXView Externalized Data (4/3/97)

Q: I'm using the protoTXView text engine in Newton 2.1 OS. How can I get text, styles, pictures, etc. out of the object returned by protoTXView's Externalize method without either a) instantiating a protoTXView or b) digging in the data structure?

A: You must instantiate a view to get data from the externalized object that protoTXView produces. The data structures in that object are not documented or supported. You may be tempted to do this anyway, since it looks as though the data structure is obvious. Don't, it isn't. ProtoTXView actually uses several different data formats depending on the complexity and storage destination for the data.

It's actually very easy to instantiate a view based on protoTXView to get at the data. Here's one way:
    local textView := BuildContext(
        {
            _proto: protoTXView,
            viewBounds: SetBounds(0, 0, 0, 0),
            viewFlags: 0,
            ReorientToScreen: ROM_DefRotateFunc,
        });
    textView:Open();
    textView:Internalize(myExternalizedData);

You can now use all the protoTXView APIs to get the data from the textView object. Don't forget to clean up with textView:Close() when you're done.


Extracting All Text from a ProtoTXView Object (4/3/97)

Q: How can I get all the text out of the protoTXView data stored in a soup entry, for example to get the text for sending in email?

A: First, instantiate a dummy view with the data from the soup, as described in the Q&A "How to Get Data From a ProtoTXView Externalized Data". The protoTXView method GetRangeData always allocates its storage from the NewtonScript heap, so you will need to copy the data into a destination VBO in chunks. Here's some code to do that. (This code uses a 4K block size, you may wish to adjust that as necessary, add error checking, etc.) StringFilter is used to remove the protoTXView graphic indicator.

    constant kChunkSize := 0x1000; // 4K chunks
    local start := 0;
    local theText := GetDefaultStore():NewVBO('string, length(""));

    // make VBO into a proper string
    BinaryMunger(theText,0,nil, "", 0, nil);    

    while numChars-start > kChunkSize do
        // strip out graphics characters
        StrMunger(theText,start, nil,
                    StringFilter(
                        textView:GetRangeData(
                            {
                            first: start, 
                            last: start := start + kChunkSize
                            }, 'text),
                        "\u2206\u", 'rejectAll),
                    0, nil);
    // copy remainder
    if start < numChars then
        StrMunger(theText,start,nil,
                    StringFilter(
                        textView:GetRangeData(
                            {first: start, last: numChars}, 'text),
                        "\u2206\u", 'rejectAll),
            0, nil);
    // theText now holds plain text from the protoTXView

For clarity, the code above does not use ClearVBOCache as mentioned in the Q&A, "How to Avoid Resets When Using VBOs". If you are having problems with large VBOs during code like that mentioned above, see that Q&A for more information.


NEW: Finding the Caret In a clParagraphView-based View (5/12/97)

Q: I have a view based on clParagraphView and would like to find the current position of the caret within that view so that I can scroll to it. Is there a way to do this?

A: Yes, there is a clParagraphView method called caretRelativeToVisibleRect that you can use.

This method takes the global coordinates of the visible region of the paragraph view as an argument. It returns either 'inbox, 'top, 'left, 'bottom, 'right, or nil. Below is a list of what each return value signifies.

paragraphView:CaretRelativeToVisibleRect( visibleBox )

Return Value Meaning
---------------------------------------------------
'inbox The caret is positioned inside the visible region.
'top The caret is above the visible region.
'bottom The caret is below the visible region.
'left The caret is to the left of the visible region.
'right The caret is to the right of the visible region.
nil The view has no caret.


NEW: Scrollers Sometimes Go Too Fast (6/9/97)

Q: When using protoUpDownScroller on an Apple MessagePad 2000 , tapping on the scroller once calls the viewScroll2DScript two or three times. Shouldn't the scroller call the script only once per tap?

A: The problem happens because the processor in the MessagePad 2000 is so fast. The scroller doesn't call the viewScroll2DScript once per tap, but rather calls it repeatedly while the pen is held down over the scroll arrow. This is normal scroller behavior, and has in fact been there all along. with the MP2000, the processor is so much faster that it can chug through many scroll operations before the pen is released.

That is, the scrolling code in the scroller itself does the equivalent of a "while not StrokeDone(unit) do targetView:viewScroll2dscript(....)". If it takes even 1 tick for the StrokeDone function to report true, it's possible that the viewScroll2DScript might have been called more than once.

If you need to slow down scrolling, add a delay in your viewScroll2DScript. Use the global function Sleep for this, as that call will yeild the NewtonScript task and let other parts of the OS work (or sleep the processor, saving battery power.)

A simple delay after each scroll event will work fine, and is probably the best solution overall. If you want to have the function operate consistently on all processors, you might do something like this;

    constant kMaxScrollSpeed := 60;  // ticks
    viewScroll2DScript: func(...)
        begin
            local t := ticks();
            ... // do scrolling stuff
            RefreshViews(); // show scrolled result
            Sleep(MAX(kMaxScrollSpeed - (ticks() - t), 0));
        end;


The above code should have the unit scrolling at a fairly consistent one-scroll-per-second unless the time it takes to do the scrolling stuff is more than 1 second, in which case it'll just go as fast as it can. The drawback to this approach is that the script will always delay for at least a second, so if for example the user taps the up arrow then the down arrow in less than a second, there will seem to be a delay between the up scroll and down scroll that would not necessarily need to be there.

You could certainly be more clever with your delays by counting ticks and watching the count paramater to your scroll script. This might even result in a better performing delay. However, it's probably not worth the effort it would take to implement this. The one-scroll-per-second approach works well, is simple, and it's unlikely that the user will ever notice the small extra delay. (Don't try to do one-scroll-every-5-seconds using this method though, your users may quickly grow annoyed at the extra delay!)