[Omnis-Newsletter] Omnis Technical Newsletter

omnis-news-admin@omnis.net omnis-news-admin@omnis.net
Wed, 1 May 2002 16:58:34 +0100


 May 1st, 2002

========================================

UNSUBSCRIBE OPTIONS: You have been sent this email because you have directly
signed up for, or expressed an interest in receiving a technical newsletter
when you downloaded an evaluation of Omnis Studio, or registered Omnis
Studio via a magazine promotion. If, however you feel you have received this
email in error you can unsubscribe as well as change your subscription
options at www.omnis.net/newsletter.

N.B.  If you subscribed by checking a box on one of our forms, you will not
have received a password. You will need to submit your email address at
www.omnis.net/newsletter and select the 'email me my password' option on the
next page in order to receive this.

========================================

WELCOME!

Welcome and thank you for subscribing to the Omnis Technical Newsletter.
Published fortnightly, it is intended for Omnis developers of all levels and
experience, for those people evaluating Omnis Studio, or for developers
moving from a similar tool. We think you'll find the content both
interesting and useful for your Omnis development needs and hopefully it
will help you become more productive in Omnis application design.

In a previous issue of the newsletter Geir Fjaerli finished off his
tutorial, so now he moves onto a popular area of interest: Object
Orientation. Geir has presented on this subject at many American and
European conferences, and here in this technical newsletter, he introduces
the subject in a very readable and entertaining way.

In the second article, David Swain shows how you can build an organization
chart taking advantage of the Shape field, a special foreground object that
can behave like almost any other type of (built-in) Omnis background object.

You may find it easier to work through the exercises and examples in this
newsletter by printing it out before you begin.

CONTENTS:
-About the Authors
-Triple O - Object Oriented Omnis, by Geir Fjaerli.
-Omnis Studio promotion in MacTech magazine
-Building Charts: Exercises With Shape Fields, by David Swain
-Copyright and Unsubscribe details


========================================

About the Authors

The Omnis Technical Newsletter contains high quality content from two
leading and well respected Omnis developers, Geir Fjaerli and David Swain.

Geir Fjaerli is based in Norway and has been an Omnis professional developer
for many years as well as a Regional Sales Manager for Omnis. He is
currently working freelance again developing a range of products (in Omnis
of course) including his Prophet5 sales and customer relationship management
solution, now available for Mac OS X. Geir is a regular contributor to the
annual Omnis Developer conferences in the US and Europe, speaking about
Object-Oriented programming in Omnis and SQL development, amongst other
things.

David Swain is the founder and president of Polymath Business Systems, for
many years a leading provider of Omnis training. His expertise in Omnis
programming and his ability to make complex concepts understandable are
recognized throughout the worldwide Omnis community. His current schedule of
Omnis Studio training classes can be found on his web site at
http://www.polymath-bus-sys.com.


========================================

Triple O - Object Oriented Omnis.
By Geir Fjaerli, Sunshine Data
Email: geir@sunshinedata.net
Web: www.sunshinedata.net

This introduction is based on the Triple O presentation I have done at a
number of Omnis conferences since 1998, now adapted for the Omnis Technical
Newsletter. If you have been to one of these conferences, you may recognise
the story.

It borrows heavily from a number of sources, including some of the better
books on the subject of object orientation. I would like to mention:
An introduction to Object-oriented Programming, Timothy Budd
Object-oriented analysis and design, Grady Booch.
Also, for the Omnis OO implementation refer to the Studio manuals.
Thanks to Bob Mitchell and Leon Venter for their input,
Rudolf Bargholz for his assistance on numerous practical issues,
and all of the Mitford engineers for a great product.

Thanks also to the entire Underground list community, by now there are far
too many OO specialists to mention.

And to David Seaman for lending the definite authority when I needed it, and
for Omnis.

If you have comments or questions, direct them to geir@sunshinedata.net .

WHY DO AN OBJECT ORIENTED OMNIS INTRODUCTION?

Omnis Studio offers advantages on multiple levels. One is the addition of
new platforms, such as Linux, and new technology, such as the web client.
One is the obvious benefit of a number of new controls, ActiveX support and
more.

But to me the real benefit lies in the new object oriented approach to
programming that Studio offers. My humble ambition is to convince you all to
adapt these techniques. Studio, like all Omnis products, does not force you
into a certain programming style. That is one of the things that makes Omnis
great, but at the same time it means that it is too easy to end up writing
you Studio code just as your Omnis 5 or 7 code.

We did a Studio training a few years back, showing people how to use the new
SQL classes in Studio. So we set up a schema and a table, defined a row
variable and did a $select and a $fetch. To which one of the students said:
"All this work, when in Omnis 7 I would just have said Single file find?"

He made a valid point. Object Orientation adds complexity to the beginning,
but offers simplicity when the system grows, rather than the other way
around. You will face a learning curve before you get going, but you will be
set and equipped for even the toughest assignment.

HOW ARE WE GOING TO DO THIS?

This introduction has two main ambitions.

One is to place Omnis Studio in the object-oriented landscape. The question
we ask is simply "Is Studio Object Oriented?" To do that we must define
object orientation, and check how Omnis fits with these definitions. We will
expand on some of these issues to give an understanding on how and why, and
also compare Omnis to other tools, typically 3GLs that are touted as being
OO.

Having defined the map, it is time to move into the terrain. How do we do
this in practical applications, and what do we gain? What are the dos and
don'ts? This will obviously be my own ideas and feelings about the subject.

As we shall see, implementation varies, and so OO becomes something of an
abstract ideal. Be aware also that there is no such thing as an OO goalpost,
you will find that you can always improve on the OO'ness of your
application. But there is such a thing as too much Object Orientation. That
is simply where it stops you from completing the project and the application
from performing its task. After all, the application is the goal, and Object
Orientation and other techniques are just means to get there.

So while Object Orientation is the programming style of choice, it is quite
possible to create excellent applications without it (as many of you have
done over the years in earlier versions of Omnis), and it is quite possible
to fail miserably even when using OO techniques. You have to apply common
sense, as always in life. This is what I refer to as Practical Object
Orientation.

My main message is possibly this: The object orientation lies in the
application. An object oriented tool or language only facilitates OOP, it is
up to you to use these facilities to better your programming styles. Some of
you may already be doing parts of this in Omnis 7, consciously or not, if so
Studio will hopefully represent a far friendlier environment to implement
this in. Omnis 7 is not an OO tool, and will never give you the true
benefits of OO. Studio - as I hope to show you - does.

At the third American Geek Week there was long discussion on a component
standard for Omnis Studio. Much as I would like to see it happen, I think
the first hurdle to overcome is for each of you to see the personal benefit
of building component-based applications

WHAT'S THE BIG DEAL?

This can be answered by one simple example: Lego. This is what OO is all
about! Lego is maybe the most brilliant toy ever, simple yet unlimited in
its possibilities. The simplicity lies in the following factors:
- Any part fits with any other part (That is not true anymore, but for the
sake of argument.)
- Each basic building block is so simple that everyone understands how to
use it.
- There is multiple ways to get at the same result, but the result will work
with other parts all the same.
- Any simple or complex part may be reused in many different constructions
Anyone who has been to Legoland knows that the resulting constructions can
be enormously complex, still they are all built from the basic bricks that
you found under the Xmas tree when you were 5 years old.

BUILDING APPS THE LEGO WAY.

This introduction might just as well have been called Component Omnis.
Because this is what we want to achieve. To split any complex task into
easily maintainable basic parts. To divide the implementation of these parts
between developers, or even use shelfware just as easy as if you had built
it yourself. To hide the details of the implementation from the routines and
developers using the components, and to build larger components from these
basic building blocks.

All we want to care about is the general description of the component, and
the interface through which we can use it. This is the promise on which we
hope that an object-oriented tool shall deliver.

It is important to realise that object orientation is not about tools at
all, it is about methodology. As I have already said: A good OO tool, and we
shall see if Omnis is indeed a good OO tool, only facilitates the writing of
OO systems. It is up to you to make that happen! Object oriented theory
tells you how to do that.

Leave your Omnis 7 background at the door, please. This ride requires a new
set of skills.

OBJECT ORIENTATION - WHAT IS IT?

Its foundation was invented by a group of Norwegian scientists in the early
60s. (Dahl, Nygård) Their idea was to model programming after real life.
Real life has individuals of many species. Real life has inheritance and
specialisation.

The language was called Simula, while it didn't support all modern
mechanisms, it holds most of the OO principles even by today's standards.
However, as often is the case, they were ahead of their times, and also
didn't really try to exploit their inventions commercially. Other OO
languages and theories shared the same fate.

So only much later - around 1980 - the theories were revitalised, not
surprisingly at Xerox Park. They gave it the spark it needed, attracted wide
interest and implemented the OOP language standard through Smalltalk, which
has become much of a synonym for OO.

After that a number of languages popped up that used OO techniques. Some of
them were based on existing languages, thus hoping to attract existing users
of those languages, some were brand new. Among the first were Object Pascal,
the language created for the Macintosh. A decade later when Borland, makers
of Turbo Pascal, wanted to create a modern development tool, they used
Object Pascal as the foundation as well. But having the advantage of having
a large number of OO tools on the market by then, they could select freely
among the techniques, and Delphi does in fact in many areas resemble C++ and
others as much as AOP.

The other natural choice was C. Among the implementations the most popular
is Bjarne Stroustrups C++, available from a number of tool vendors today.
Another variant is Objective C, which again has similarities with Smalltalk,
though with a C syntax. Coming soon is Microsoft's C#.

Of recent fame is of course Java. Many people have seen it as a refined
subset of C, but in fact the OOP implementation is quite distinct.

There is also a number of languages that are merely of academic interest, as
they have no commercial impact, among them Beta (also Scandinavian) and
Eiffel.

The implementation of each of these obviously falls beyond the scope of a
short session like this one, though we shall give some examples to see that
they do in fact differ, and that Omnis Studio behave the way it does for
good reasons.

So we didn't answer the question: Does Omnis belong on this list?

IS OMNIS OBJECT ORIENTED?

It would be tempting to say yes, because Omnis is such a brilliant tool. But
doing that is too easy. In fact, the basis of many claims to OO'ness is just
that.

Back in the early 1700s, the Danish writer Ludvig Holberg wrote some great
satirical pieces. One of them was the story of the young peasant son named
Rasmus Berg who has been to Copenhagen to study. Coming back he has named
himself Erasmus Montanus (Berg means mountain in Scandinavian languages),
and dazzles his surrounding with cheap discussion techniques.
(Story from memory.):
Erasmus to his mother: I can prove that Mother Nille is a stone.
Mother Nille: What do you mean, I am not a stone!
Erasmus: Well, mother Nille cannot fly, can she?
Nille: No, that is true. I cannot fly.
Erasmus: And a stone cannot fly either.
Nille: No, last time I looked, a stone could not fly.
Erasmus: Well, Ergo Mother Nille is a stone.
(Mother Nille cries, and Erasmus hastily proves she is not a stone after
all, cause a stone cannot speak.)

250 years later his countryman Bjarne Stroustrup, father of C++, uses a
twist on this story to describe the current inflation of claims to OO among
tool vendors:
X is good.
Object Oriented is good.
Ergo, X is object oriented.

This is clearly not a scientific approach, and does not prove anything. If I
were to use the same methods to claim the OO fitness of Omnis Studio, you
would say "so what?" To position a tool as OO, we first need to define OO,
and then evaluate our tool against the definition.

But even that would be a bit hasty. We mentioned earlier that Object
Orientation was a way to model programming after real world principles Let
us briefly look at some of these. In fact, much of the basis of OO is in the
work done in botanical and biological science, starting as early as the
works of Linnea and Darwin.

OO: A REAL LIFE APPROACH TO PROGRAMMING.

In nature and science, we sort items according to the classes (race,
species) they belong to. So your nice little terrier belongs to the class
dogs, your (neighbours) Mercedes is a car. At the same time they are
individuals of that class. So while two cars may be the same make and model,
one is blue and one is red, one has two kids in the back on their way to
Disneyland and the other is parked in a garage. Just as we are all humans,
and share the same basic shape and functionality, but are nevertheless
clearly and distinctively individuals.

So we see one of the key principles of nature as well as OO: All items
belong to classes, and share the general characteristics of that class. Yet
what you see and use is never a class, it is an individual belonging to the
class. We shall see that in OOP the same is true, the class holds the
creators definition of everything, what the user sees and interacts with are
individual objects, what we call instances of the class.

Another key element in nature is specialisation through inheritance. Let us
start by defining an animal. Obviously we have already started defining some
features of our item. An animal doesn't have wheels, and doesn't grow
apples, so it clearly distinct from cars and trees. But other than that it
could be a lot of different types of creatures. So we create a specialised
level, and distinguish fish from mammals from birds. Our pet, being a dog,
is clearly a mammal. But even saying it is a dog isn't enough. Ours is a
terrier, maybe a Boston terrier or Airedale terrier. Now we know the
approximate size and shape, and we know that terriers bark more than they
bite.

We can use this principle of inheritance to ease our programming.  If two
windows are almost the same, let them inherit the common features, then just
add the different ones.  And once we have made something reasonably generic
for one project, why not use it in other applications and just adapt them to
the new purpose by adding or overriding features.

SAY WHAT YOU WANT.

A more recent element in OOP is messages. In fact Simula and other of the
first OO languages didn't have them.  But again we are seeing programming
modelled after real life. Just as you would ask someone to do something,
objects in a program send messages to each other, which triggers actions and
maybe answers.

One thing about asking someone to do something is not only do you not have
to do it yourself, but you don't even have to be overly concerned by how it
is done. Say you want bread for your lunch. Depending on whom you ask, that
person may go into the kitchen and bake some bread, or simply pop down to
the supermarket and buy some. The end result is the same, you get your
bread.

The same two benefits are present in OOP. By asking someone to do something
you don't have to do it yourself. And how it is done is not important as
long as the result is right. We build our objects for reuse.  I started by
saying that the aim is component building. By hiding the implementation we
have achieved that. Note that even if this is fine in principle, it may not
work in practice. Just as the bread from the supermarket may not at all be
as tasty as the fresh baked one, you may find that the component doesn't
quite cut it. Say a date component that doesn't treat year 2000 as a leap
year.

Another important element in communication is that of understanding. If you
say, "I want bread", and the baker only speaks Italian, you have a problem.
And unlike the real world, where you probably would get away with pointing
at the bread, in programming communication needs to be exact. So you need to
know what kind of messages the object understands, and which parameters it
requires.

SO, LET'S DEFINE OBJECT ORIENTATION:

Having introduced the basic elements of OO, let us try to set down a formal
definition against which we can measure Omnis and various programming
techniques. A good start would be Xerox Park, where much of the theoretical
work was done.  Alan Kay, one of the Xerox front men, published their
definition as the following 6 requirements:
1. Everything is an object
2. Objects communicate by messages
3. Objects have their own memory
4. Objects are instances of a class
5. The class is the repository for object behavior
6. The class tree is an inheritance hierarchy

Obviously, while these requirements seem to be generally accepted, each of
them hold varying importance, also their relevance may be questioned. (E.g.
do OO languages have to use messaging, to which the answer is probably not,
since in fact some of the early ones did not. Rather messaging was
implemented in Smalltalk as a way to enforce object-oriented principles.)
Nevertheless, while these rules may be subject to argument, they do
represent a good scale to measure Omnis against.

In the next issue we will look at the implications of each requirement, and
how Studio fits with them. Until then, take care!


========================================

Omnis Studio promotion in MacTech magazine

The May issue of MacTech Magazine is running a promotional ad offering
readers the chance to download Omnis Studio, register for their evaluation
serial number using a magazine code printed in the mag, and then upgrade to
Omnis Studio v3.1, or v3.2 when it is released, for a special upgrade price.

Established in 1984, MacTech Magazine claims to be the only monthly magazine
focused on Macintosh technology and development. It is circulated to
approximately 18,000 readers plus the May issue is given away to the 4,000
attendees of the World Wide Developer Conference (WWDC) held the first week
in May (6-10, 2002, San Jose CA). Check out your specialist news stand, or
go to the MacTech web site and subscribe to the mag
(http://www.mactech.com), or pick up your copy at WWDC
(http://developer.apple.com/wwdc2002)


========================================

Building Charts: Exercises With Shape Fields
By David Swain, Polymath Business Systems Inc
Email: dataguru@polymath-bus-sys.com
Web: www.polymath-bus-sys.com

------------------------------------------------------------------

Let's assume that we would like to create an Omnis Studio window that can be
used to build and manipulate a simple chart, like an organization chart.
Such charts contain shapes with labels and lines that connect shapes to show
relationships.

We might be tempted to use the various background objects in Omnis Studio
(rectangles, ovals, etc.) for this project, but we would soon discover that
there are too many limitations involved with trying to use those objects for
dynamic processes. The main problem is that background objects are inactive
in the runtime environment. That is, we can't directly detect clicks or
other events with them, allow them to be dragged around or even select one.

So if background objects are out, what can we use for this specialized
purpose?

"Shape" Fields

To compensate for this apparent deficit of functionality of background
objects, the creators of Omnis Studio have given us the "Shape Field". This
Protean component is a foreground object that can take on the appearance and
characteristics of any type of (built-in) Omnis Studio background object
(except "Label" - but it can still become a "Text" object, so no
functionality is lost). Since it is a foreground object, though, it also has
all the interactive qualities of any other foreground object: event
detection, drag-and-drop properties and context menus to name a few
important ones.

Unlike "real" background objects, it can even change shape. A background
rectangle is consigned to being only a rectangle forever, but a Shape Field
can switch from being a rectangle to being an oval to being a text object
with a simple change to its "shape" property.

So a Shape Field has the potential for being a useful building block for our
chart window. Let's start a project so we can explore a few alternatives.

And who knows what interesting things we might discover along the way...

First Moves

Create a Window class in some library and name it "orgchartTester" (or
whatever is meaningful to you). Place a Shape Field onto this window from
the Component Store and give it the name "box01". Unless you have changed
the "shape" property of the Shape Field component in your Component Library,
this new field should have a "3D Rectangle" shape with an "Inset" effect. We
can view and manipulate these property values in the "Appearance" tab of the
Property Manager for this component.

For comparison, also place a "3D Rectangle" background object next to the
Shape Field. Now using the Property Manager, compare back and forth between
the properties of these two objects. (This is one of those times when it
would be nice if the Property Manager window were multi-instantiable!) Under
every Property Manager tab, the properties of the Shape Field are a superset
of those of the 3D Rectangle object. The additional properties of the Shape
Field are those of a foreground object and those that determine which
background object "guise" it is assuming for the moment.

Take a moment to explore how changing the "shape" and "effect" properties
changes the appearance of the Shape Field. Also notice that with certain
selections, some properties, such as "text", "tile" and "cachepicture" (not
to mention "effect"), become enabled or disabled as they become appropriate
or inappropriate for the "shape" specified. This mirrors the availability of
those properties in the corresponding background object type.

For now, set "shape" back to "kRect3D" and "effect" back to "kBorderInset".

Dragging Objects

If we want our users to be able to rearrange the boxes on the window, we
must allow those boxes to be dragged. This is very easy to do for individual
Shape Fields. We simply set the "dragmode" property (found under the
"Action" tab or the Property Manager) to "kDragObject". This allows the
object itself, rather than some part of its content, to be dragged around.
To limit this action to the current window (because we don't want elements
of our organization chart moved to another window - at least for now...), we
also should set the "dragrange" property to "kRangeWindow".

After setting these values, we can open a test instance of our Window class
and try to move the rectangle around. That was easy! Now close the test
instance and return to design mode.

We should also set the "dropmode" property value to "kAcceptNone". This
disallows dropping one of our rectangles onto another. We set this value in
the Property Manager by deselecting all "dropmode" choices. When all those
checkboxes are deselected, the property value will display as
"[kAcceptNone]" (which should have been the default). For now, we don't want
to accept anything dropped onto one of our rectangles. Perhaps later we'll
have a use for this...

We will eventually want to tie two boxes together with a line, so for now
create a second box with the same properties and name it "box02". We can
duplicate the first one by performing a "drag duplicate" maneuver in design
mode. On the Macintosh platform, hold down the "Option" key, then click on
the field and drag the duplicate created to an appropriate position. On the
Windows platform, do the same thing, but hold down the "Control" key lieu of
the "Option" key. Now just change the "name" property. All other properties
(except those involving position and the "ident" property) should have been
duplicated in the process.

Oh, and remove the background rectangle object. We won't need it further.

Labels

For our organization chart to make any sense, the "boxes" should also have
"labels" to identify what each box represents. While a Shape Field with a
rectangular shape does have a "text" property, it is not used to display a
label on the rectangle. This property is, in fact, disabled for all values
of the "shape" property other than the "kText" value. This is also true of
the corresponding background objects: only "Text" and "Label" objects can
actually display text.

In order to put a label on our boxes, we must place another object on top of
them. We have two basic choices: a Shape Field with a "Text" appearance or
some sort of Entry Field. Each choice has its relative advantages and
drawbacks. We can't use a "Text" or "Label" background object as a label
because it cannot be put "on top of" a foreground object (other than a
"container" object). The foreground object that it is attempting to label
would always cover it.

Since we're working with Shape Fields already, let's create a label for
box01 using this technique. Drag a Shape Field from the Component Store onto
our window and set its "shape" property value to "kText". Notice that the
"effect" property value reverts to "kBorderNone" and becomes disabled. Give
the field the name "label01" and set the "text" property value to "Label".
Notice that we have the same full suite of tools for manipulating text
attributes on a character-by-character basis as does a "Text" background
object. Because of something we are about to do, though, don't use these at
this point. If you would like to set some special text color (like kRed) or
some formatting attributes (like bold and centered), do so using the
properties under the "Text" tab of the Property Manager and let them apply
to the entire label value.

Finally, make this field an appropriate size for a short single line of text
and place it over the box01 field (centered horizontally and vertically for
good measure). Since the "order" property value of the label field is higher
than that of the box01 field, the label is drawn last and therefore appears
on top of (or in front of) the box. So we now have a viable label.

User Editing Of Labels

One problem: A Shape Field is not an "enterable" object. How can we allow
our users to modify the text that the label displays?

Shape Fields do respond to click events, so perhaps we can use the "$event"
method to capture some user-based input for re-labeling this field. In fact,
perhaps we can even allow the user to "directly" modify the "text" value of
the field. We can do this using a relatively new dialog command in Omnis
Studio.

The "Prompt For Input" Command

When the "Prompt For Input" command is executed, it presents a dialog for
gathering input from the user. It must be supplemented with the name of a
variable, which will be the vessel for the user input. The dialog then
includes an Entry field for this variable. Based upon my definition for a
variable (cited in other articles) as "a container in RAM for a value",
field properties qualify as variables. Let's see how we use this.

In the "$event" method for our "Text" Shape Field, we will place these two
lines of code:

On evClick
    Prompt for input  Returns $cobj.$text (Cancel button)

We use the "text" property of the "current object" ("$cobj.$text") as the
"return variable" for the command. This automatically places the current
value of the "text" property in the dialog (suitably selected for
replacement by the next keystroke) and automatically places whatever the
user enters into the "text" property when the user clicks the "OK" button or
performs the platform-specific equivalent keystroke. We also included a
"Cancel" button that simply avoids replacing the value. (It would also set
the Flag to "kFalse", but we don't have to concern ourselves with that
here.)

Now open a test instance of the window and click on this field to see
whether it works as expected. If you had not followed my advice about
avoiding the use of character styles and colors in the "text" property
value, the value you would see by default in the "Prompt" dialog will
include the "style escape strings" for any styles you had imposed. This will
appear as so much gibberish to your users, so it should be avoided if this
simple method of editing the "text" property value is to be used. Try
changing the value and accepting the change. Then try changing the value and
canceling the change. It all works quite nicely with that single line of
code!

We could easily have made this process more complicated - and sometimes that
would be appropriate. We could have included a "local" or "instance"
variable to use as a transfer vessel for the user's input; we could have
trapped the "Cancel" state to abort the process, etc. None of that was
necessary in this simple case.

So now we have an "editable" label using a Shape Field in "Text" object
mode. We will also need to be able to drag this object, so set its
"dragmode" and "dragrange" properties as we did above for the rectangular
Shape Fields.

Entry Fields As Labels

Our other alternative is to use an Entry field as a label. In our interface
convention I am going to suggest that we only want the user to be able to
edit a label if they directly click upon it. We will also need to be able to
drag this field (as we will discover later), but this ability to drag the
field could potentially conflict with the need to drag-select part of the
text of the label during editing. This presents an interesting set of
problems.

Go ahead and bring an Entry field onto our window from the Component Store
and name it "entry02". Set the "dragmode" and "dragrange" properties as for
the other fields, then set the "effect" property to "kBorderNone" (so the
field "blends in" with the rectangle) and whatever text properties seem
appropriate. Finally, size the field appropriately and place it on top of
the box02 field.

This field will require a variable of some kind to contain the text it is to
display. Open the Method Editor for the Window class and create an instance
variable named "textVar02". Make this variable "Character" with a maximum
length of "20" and set the "Initial Value" to "'Label'". Then return to the
window layout view and set the "dataname" property value of the Entry field
to "textVar02". We then have some decisions to make...

One decision is whether our window should provide "modal" or "modeless" data
entry. "Modal" data entry requires some control (like a pushbutton) to set
the data entry process in motion. "Modeless" data entry means that the
window is already "hot" when it is opened. We want something in between: A
field that becomes "hot" when clicked upon, but then becomes uneditable once
the user finishes the process - until the next time it is clicked upon. If
we opt for the "traditional" "modal" way of launching the data entry
process, we'll need a control that reacts to a click. Disabled Entry fields
respond to clicks, so we could set the "enabled" property value of our field
to "kFalse" and use it as though it were a pushbutton. Its "$event" method
would then enable the field (reversibly), execute the "Enter data" command
(allowing the user to modify the field contents), and either accept or
reject the change based on some other user action (clicking an OK or Cancel
button, performing their keyboard equivalents, etc.).

Of course, disabled Entry fields also react to clicks on a modeless window.
This saves us the use of the "Enter data" command and its associated cleanup
operations. Here is the code I decided to use for this operation on a
"modeless" window:

On evClick
    Calculate $cobj.$enabled as kTrue
    Calculate $cobj.$firstsel as 0
    Calculate $cobj.$lastsel as len($cobj.$contents)
    Calculate $cobj.$dragmode as kNoDragging

Human translation: When the user clicks on the field, enable the field for
data entry (but only this field as all others will still be disabled),
select and highlight all the text the field currently displays (so the first
keystroke can replace everything, if desired) and disable dragging (so that
the user can drag-select portions of the displayed text).

This process also needs a "completion trigger" as well. I decided that the
best one would be an "After" event, since many things (attempting to tab out
of the field, pressing the "Enter" key, clicking elsewhere on the window,
etc.) trigger this event. My "evAfter" block is as follows:

On evAfter
    Calculate $cobj.$enabled as kFalse
    Calculate $cobj.$dragmode as kDragObject

All that happens is that the field becomes disabled and dragable again.

There is only one problem with this technique so far: There is no "Cancel"
equivalent - no way to return to the value that was originally displayed
before the process began.

We could rely upon the built-in "Undo" command (Cmnd/Ctrl-Z from the Edit
menu) in Omnis Studio. This will work fine if the user only made one
modification to the field contents. But if the user modified the value, then
selected some portion of that modified value and modified it, the "Undo"
command only toggles back and forth between the last two modifications. What
we really need it to do is to redraw the field, which will replace the field
contents with the value of the underlying variable (which hasn't been
changed yet because the "After" event has not occurred).

To do this, we must create our own version of "Undo", trapping the key
combination "Cmnd/Ctrl-Z" and redrawing the field. This requires that we set
the "keyevents" property value of the Entry field to "kTrue" and add another
"On" block to the fields "$event" method:

On evKey
    If upp(pKey)='Z'&#COMMAND
        Do $cobj.$redraw()
    End If

In testing, this yields an interesting result. On a multiple edit (as
described above), the first use of this event code does exactly what the
"Undo" command would have done: It reverts to the string most recently
modified (and even highlights the portion of the string that had been
replaced!). But a SECOND use of this key combination (directly following the
first, of course) reverts the field contents to the original value. This is
true no matter how many  "sub-modifications" have taken place.

My interpretation of this (and I can only guess, since I didn't write Omnis
Studio) is that there is an "Undo buffer" that keeps track of the most
recent change to the Entry fields contents since the last character
selection, insertion point click, arrow key or backspace. Redrawing the
field the first time replaces the field contents with the contents of this
buffer. But unlike the "Undo" command, which appears to SWAP field and
buffer contents, our command must EMPTY that buffer. So the SECOND use of
the command performs the NORMAL function of "redraw", which is to replace
the field contents with the value of the underlying variable (which hasn't
changed in all this time).

While this is academically interesting (and good to know from a debugging
standpoint), we still have some other problems to overcome in creating our
organization chart.

Dragging Objects Together

Since we must have two objects for each box in our organization chart (the
box itself and the label for it), we need to make sure that both objects are
dragged together. We don't have a direct means of "grouping" objects at
runtime (the "Group" command only affects design objects), so we have to
create our own method to move related objects when the current one is
dragged to a new position. For this to work , we need to know two things:
which object do we need to move with the one that was just dragged and where
do we need to put it?

In this preliminary work (we're just exploring technologies right now, after
all), we will "hard code" the name of the object to be dragged along. When
we get to the more "realistic" part of this exercise in the next issue, we
may use a combination of naming conventions and other techniques for
parameterizing this so we can write more generic code.

For determining where to put the other object, we can fall back on good old
algebra. (They always told me I would need this someday!) If we know the
beginning and ending position of the object being dragged and the beginning
(current) position of the "companion" object, we should be able to simply
calculate what the new position of the companion object must be and set it
using notation. If we move one of our pair of objects a certain distance,
then we need to move the other one the same distance. Let's break this down
into "x" (horizontal) and "y" (vertical) components:

newX1 - originalX1 = newX2 - originalX2
newY1 - originalY1 = newY2 - originalY2

Where object "1" is the object being dragged and object "2" is the companion
that we need to move along with it. Solving for the "new" X and Y values
yields:

newX2 = originalX2 + newX1 - originalX1
newY2 = originalY2 + newY1 - originalY1

Now we have to translate this into Omnis Studio properties and variables.

The position of an object in Omnis Studio is given by its "left"
(horizontal/x) property and its "top" (vertical/y) property. But we can only
know what the current value of each property is. Omnis Studio does not keep
a historical trail of past values for us to follow. If we need such
information, it is up to us to keep track of it.

So when we begin the dragging process, we should keep track of the original
X and Y position of the item being dragged in two variables. These should be
of "instance" scope since we may need to refer to them from a number of
different places (and at a number of different time), but only from within
our window instance. Let's call the "origX" and "origY" and make them "Long
integer" number variables. (We are keeping track of pixels, so "Integer" is
appropriate, but the value of a coordinate could be greater than 255 so we
have to use "Long integer".) But how do we know when to set these values?
This has to do with event detection.

Drag Events

When we first begin to drag one of our objects, we want to store its
original location. Since we have set a "dragmode" value for each field, we
can detect "drag" events with each fields "$event" method.

The first one of these we need to explore is "evDrag". This is the beginning
of the "drag event cycle" and it indicates that the current object ("$cobj")
is about to be dragged in some way. The way it is dragged is determined by
the value of "dragmode" for that object. Since we have set this property
value to "kDragObject", we know that the user is attempting to move the
object. As with all other events, the "evDrag" reports the ATTEMPT to
perform a certain action, but that action has not yet "happened" (though it
WILL happen at the end of the event cycle if the event is not discarded).
For us in this experiment, this means that the field is still in its
original position while "On evDrag" is the current event, so we can use this
opportunity to store the fields "left" and "top" coordinates in our "origX"
and "origY" variables. Our code looks like this:

On evDrag
    Calculate origX as $cobj.$left
    Calculate origY as $cobj.$top

This code is so generic that we can put it into the "$control" method of the
window class itself so it only needs to exist in one place. We just have to
be certain that we don't trap the "evDrag" event in the $event method of any
of our fields.

Now we need to deal with the completion of the "drag cycle". How do we tell
that the move has been completed from the user's standpoint?

This is an interesting question. We usually think in terms of "drag and
drop", but here the user doesn't "drop" the object (which would imply
dropping it ON something) so much as they "let go of" the object. The
"evDrop" event is sent to the "target" field of a drop (the one ONTO which
something is dropped), but that doesn't exist in this case.

The event we need is the "evWillDrop" event. This is sent to the field being
dragged when the user "lets go of" the object - but only if a drop is
allowed. In our project, a drop is allowed anywhere on our window, so all we
have to do is detect the "evWillDrop" event in each fields "$event" method
and then move the associated field based on the old and the new position of
the field being dragged. We only have two other issues to deal with.

The first of these is: How do we know which object is the "companion" object
that needs moving? For this article, we'll simply "hard code" that
information into our individual "$event" methods. In the next article when
we discuss how to manage these objects and relate them to real data, we'll
have a few other options.

The other issue is: How do we tell what the final position of the dragged
object is? This is not a silly question. Remember that an event indicates an
"intention" to do something, but that action has not yet taken place. In
this case, "evWillDrop" is the "intention" to let go of the field. The
action is not completed until the event method ends (assuming we don't
discard the event).

Process Event And Continue

This is a job for the "Process event and continue" command! This command
causes the current event to occur right then so that we can deal with the
"effect" of the command in the "$event" method. In our case, this will allow
us to use the "final" horizontal and vertical coordinates of the dragged
field and reposition the "companion" field at the same time. Here is the
code we would put into the "$event" method of the "box02" field, for example
(as a shorthand, I created an instance variable named "objRef" of "item
reference" type with an initial value of "$cinst.$objs"):

On evWillDrop
    Process event and continue
    Calculate objRef.entry02.$left as
objRef.entry02.$left-(origX-$cobj.$left)
    Calculate objRef.entry02.$top as objRef.entry02.$top-(origY-$cobj.$top)

Copy this code into the "$event" method of the other fields (including the
labels), changing the name of the "companion" field as appropriate for each
one. You should now be able to drag any of our four objects to new positions
and have the appropriate "companion" object follow along.

Line Objects

What about connecting lines? We can use a background "line" object for some
things, but this has similar limitations to the rectangles we discussed
earlier. Let's use another Shape Field with its "shape" property value set
to "kLine" for this purpose. But first, there are some things we should know
about "line" objects in Omnis Studio.

First, a "line" object is still a rectangular object, but one that only
displays a line connecting two opposing corner points of that rectangle.
This is true for both background and foreground "line" objects.

The "top" and "left" properties determine the "anchor point" for the line
(where the line begins), while the "width" and "height" properties determine
where the other (second) end point will be. They represent the horizontal
and vertical "offset" of the second endpoint from the first. For this
reason, both "width" and "height" can be negative (unlike any other Omnis
Studio object), so the "top" and "left" coordinates may not actually
represent the topmost and leftmost points of the object.

Drag another Shape Field to our window from the Component Store and
experiment a bit with this. Name the field "line01", set the "shape"
property value to "kLine" and then play with positive and negative values of
"width" and "height". Continue when you're comfortable with the concept.

An issue we must solve for our eventual project is: How do we know which
objects a specific line is to connect? This is another of those things we
can handle more elegantly once we are using data structures to track all of
the information about a chart, but for now there is a little-known trick we
can use:

"Line" objects (both background and Shape Field) have a "text" property.
This property is disabled in the Property Manager, but we can manipulate it
using notation to a certain extent. I added that qualification because it
does not appear to be available for an object in the window instance, but it
is still available from the "design object" in the window class. Try this
experiment:

Create a "$construct" method for the "line" Shape Field (or use its "$event"
method) and add this line of code:

Calculate $cclass.$objs.line01.$text as 'box02-box01'

Now double-click on that method line and click the "Step" button on the
Method Editor toolbar. Finally, select "Clear method stack" from the "Stack"
menu. Now comment out the method line you just executed and create another
that reads:

OK message  {[$cclass.$objs.line01.$text]}

Executing this line in the same manner should pop up an OK dialog that
states 'box02-box01', proving that we can store a string in the "text"
property of the field. In fact, we have now stored the names of the two
objects we wish the line to connect separated by a hyphen.

If the line then knows which objects it is to connect, perhaps we can give
it a method to reset itself to connect the center points of these two
objects. Create another method for this "line" object named "$reset". This
method will require three local variables: one to serve as a notational
reference for each of the two objects the line is to connect and a third
that is a reference to the "design" version of the "line" object in the
window class.

Create a local variable named "classLineRef" and give it this initial value:

$cclass().$objs.[$cfield.$name]

I wrote this expression in such a way that when the method is copied from
one "line" object to another, this value will automatically refer to the
object containing the method.

Now create the other two variables. Let's make them both Character variables
this time and name them "obj1ref" and "obj2ref". (We could also use "item
reference" variables and a slightly different technique, but I want to show
you an alternative approach.) In our method we can now build two method
lines to populate these two variables:

Calculate obj1ref as
con('$cinst.$objs.',mid(classLineRef.$text,1,pos('-',classLineRef.$text)-1))
Calculate obj2ref as
con('$cinst.$objs.',mid(classLineRef.$text,pos('-',classLineRef.$text)+1,len
(classLineRef.$text)))

Basically, the first line extracts the part of the "text" property value
that comes before the hyphen and the second extracts the part that comes
after it.

This is fine so far, but how does this help us find the coordinates of the
lines endpoints? What we are going to do next is to create methods that
allow each object to "tell us" the coordinates of its centerpoint - if we
ask it nicely. Each "box" object will be given two methods: one named
"$getcenterx" and "$getcentery". (Remember, the "$" indicates a "public"
method or "custom behavior or property" of an object.)

The code for "$getcenterx" is:

Quit method $cfield.$left+int($cfield.$width/2)

The code for "$getcentery" is:

Quit method $cfield.$top+int($cfield.$height/2)

We used the int() function to avoid fractional results when there is an odd
value for a width or height.

We can now complete the "$reset" method for our line with four more method
lines. The first two will "anchor" the line in the center of the first
object ("x" and "y" must be set separately) and the second two will use the
difference in the center coordinates between the two objects to determine
the "width" and "height" of the "line" object. Here is the completed
"$reset" method (including the two lines already given above):

Calculate obj1ref as
con('$cinst.$objs.',mid(classLineRef.$text,1,pos('-',classLineRef.$text)-1))
Calculate obj2ref as
con('$cinst.$objs.',mid(classLineRef.$text,pos('-',classLineRef.$text)+1,len
(classLineRef.$text)))
Calculate $cfield.$left as [obj1ref].$getcenterx()
Calculate $cfield.$top as [obj1ref].$getcentery()
Calculate $cfield.$width as [obj2ref].$getcenterx()-[obj1ref].$getcenterx()
Calculate $cfield.$height as [obj2ref].$getcentery()-[obj1ref].$getcentery()

Notice the use of square bracket notation in the four new method lines. This
is used because we set our references to the two "box" objects as strings.
If we had used item references, the square brackets would not have been
needed (and would not have worked!).

If you have "$getcenterx" and "$getcentery" methods in each of the "box"
fields and have faithfully entered all the rest of the code, there is only
one thing left to do before we test our technique: We must tell the "line
object to reset itself from within our "evWillDrop" code block. This way the
"companion" object and the "line" object will both be repositioned when an
object is dragged to a new spot. Here is the finished code for the "$event"
method of the "box02" object:

On evWillDrop
    Process event and continue
    Calculate objRef.entry02.$left as
objRef.entry02.$left-(origX-$cobj.$left)
    Calculate objRef.entry02.$top as objRef.entry02.$top-(origY-$cobj.$top)
    Do objRef.line01.$reset()

Go ahead and test this. Notice that now the line also moves with the dragged
object as does the objects "companion".

User Options

Perhaps we would also like to give our users the opportunity to make some
stylistic changes to the boxes we present them. For example, they might like
to change the "shape" of certain boxes (from rectangle to oval, for
example). We could even assign some significance to the shape of a box as is
done in some standardized charting systems.

To do this, we need to have some way of selecting a specific box and a tool
with which to change some property of the selected box. An easy way to do
this is by using "context menus".

Context Menus

These are menus that are instantiated when the user "context-clicks"
(Option-click on Macintosh, Right-click on Windows).on an object on a
window. We can designate for each field which menu class should be used as
its context menu by setting the "contextmenu" property value for the field
to the name of a menu class selected from the dropdown list provided by the
Property Manager.

Suppose we want to allow our users to reset the "shape" property of a "box"
in our organization chart from these options: Rectangle, Rounded Rectangle,
3D Rectangle or Oval. Let's make a menu to do this.

Create a new Menu class named "reshapeMenu". Type "Reshape" for the title
(even though this will not be seen when used as a context menu) and create
four lines named and labeled "Rectangle", "Rounded Rectangle", "3D
Rectangle" and "Oval". So far, so good - but how do we reference the object
whose "shape" property value the user wants to change?

The "$contextobj" Property Of A Context Menu

A context menu has a special property named "$contextobj" that is a
reference to the object that received the "context click". All each menu
lines needs is a single line of code like the following:

Calculate $cinst.$contextobj().$shape as kRect

The parentheses are needed to "resolve" the notation to the specific object
whose property we want to modify. The property constants we will need are
"kRect", "kRoundrect", "kRect3D" and "kOval".

All we need to do is set the "contextmenu" property value of each "box"
object to "reshapeMenu" and we're ready to test this.

Cascading Menus From A Context Menu

But what if we also want our users to be able to select a 3D effect if the
"shape" is set to "kRect3D"? Maybe what we really want to provide is a
context menu (named "appearanceMenu") with two lines labeled "Shape" and
"Effect" that each contain a submenu. Since a context menu is instantiated
each time it is used, we could disable the "Effect" line if the "shape"
property of the box receiving the context click is not set to "kRect3D". All
we need to complete this is another menu (named "effectMenu") that has
one-liner menu lines for setting the "effect" property value with a few
choices like "kInset", "kBevel" and "kChisel", etc.

There is only one (main) problem: Cascading menus from a context menu don't
receive the "$contextobj" property. We have to work out some way of passing
this to the submenus.

Passing Parameters To A Cascading Menu

Since a submenu is only "called" from the "cascade" property of the menu
line to which it is attached, that is from where we must send any parameters
to the "$construct" method of the submenu. In the "cascade" property, we
select the menu to be used from the dropdown list provided. But then we can
click on that menu name and type in some additional text (like a set of
parentheses following the menu name and enclosing a comma-delimited list of
parameter values). Our entry for the "reshapeMenu" line would be:

reshapeMenu ($cinst().$contextobj)

We need to "resolve" the "$cinst" at this point using those parentheses
inside the notation string or it will pass through as "$cinst" and point to
the "current instance" of the "reshapeMenu" menu (which does not contain a
"$contextobj" property) instead of the "appearanceMenu" menu (which does).

We will "receive" this parameter in the "$construct" method of the submenu
by reference. That is, we will create a parameter variable named
"pContextObj" of "Field reference" type. This keeps the reference "live" in
the submenu. But a parameter is only "local" in scope, so we must also
create an instance variable (named "contextObj") of "item reference" type
for the submenu. We can then "transfer" the "live" reference to this
variable using:

Set reference contextObj to pContextObj.$ref

Our method line for setting a new "shape" value in the submenu then becomes:

Calculate contextObj().$shape as kRect

The same technique is used for passing and receiving the "$contextobj"
reference to the "effectMenu" submenu. Go ahead and finish this off, then
test your result. It works for me, it should work for you if you follow the
instructions closely.

Wow! What took a couple of hours to create sure took a long time to explain!

Review Of What We've Accomplished So Far

This has been a pretty full lesson! We have explored a number of properties,
commands, techniques and otherwise undocumented features that could prove
useful in a variety of situations, but that are all necessary for building
our organization chart window. Now we have the basic GUI technology to move
forward on this project. Here is a brief summary:

Shape Fields that emulate geometric shapes but can react to events and
contain methods

Two types of field that can act as "labels" for the "boxes" of the chart and
techniques for editing the text they display

Techniques for "grouping" a "box" with a "label" so they move together when
either one is dragged

Techniques for determining the center of a "box" and placing the endpoints
of a "line" at the centers of two "boxes" it connects

Techniques for allowing the user to set different "shapes" and "effects" for
"boxes"

Well, that should be enough for one sitting. Next time we'll examine how we
can relate these objects to data and ways of dynamically creating and
keeping track of them. Some list variables will be involved...


========================================

I hope you've found this issue of the Omnis Tech Newsletter both interesting
and informative. Please send me your comments and feedback, and include
suggestions for future articles if you like. We would like to hear from
you...

Regards,
--Andrew Smith.
Omnis Technical Newsletter Editor
Email: editor@omnis.net

========================================

No part of this newsletter may be reproduced, transmitted, stored in a
retrieval system or translated into any language in any form by any means
without the written permission of Raining Data.
(c) Copyright Raining Data, Inc., and its licensors 2002. All rights
reserved.
Omnis(r) is a registered trademark and Omnis 7(tm), and Omnis Studio are
trademarks of Raining Data UK Ltd. Other products mentioned are trademarks
or registered trademarks of their corporations.

========================================

To unsubscribe from this newsletter or change your subscription options,
please go to:
www.omnis.net/newsletter