When writing the Note Organizer application, we've used a very direct style of programming; when we wanted to move an idea around, we literally sent a message to the other users' canvas widgets to move the idea. This approach works very well for small, highly graphical applications, but doesn't always scale up well to larger applications, particularly if you'd like to allow different users to customize how they work with their applications. This section discusses an alternative approach to programming in GroupKit, using some features of environments.
To understand how environments can be used to build groupware programs, we'll have to first describe a paradigm called "Model-View-Controller" (MVC), which was first introduced in Smalltalk as a way to construct (single-user) user interfaces. The MVC paradigm suggests that an application should be divided into three distinct components:
Under this paradigm, the controller never changes the onscreen view directly. Instead it changes the model, and the changes to the model are picked up by the view which does cause a change onscreen. This is illustrated in Figure XX.
Figure XX. Model-View-Controller paradigm.
This approach can be a bit hard to follow at first, since input events only indirectly affect what is displayed onscreen, but has a number of advantages. First, by clearly separating out your underlying data from the view of that data and how it is manipulated, it encourages you to build a very clear data model, which can help you understand exactly what it is your program is manipulating. By doing this separation, you also find it easier to modularize your code, rather than trying to do everything in one place. Finally, the paradigm makes it easier to have multiple views on the same data structure, for example allowing you to view a table of numbers as either a spreadsheet or a graph.
So how does this relate back to GroupKit? Recall that we can use GroupKit's environments to store a fairly arbitrary set of data (such as names and colours in the gk::users environment), arranged in a tree structure, where we can associate a string value with any node in the tree. This can serve as an underlying data structure for a wide variety of applications, and so is an appropriate choice for our Model under the MVC paradigm. Environments can also generate events (similar to the conference events used for announcing new users etc.) when they are changed. These events can be received by other portions of your program, such as a View which updates the screen to match the change in the Model. Finally, parts of your program that handle user input (the Controller portion) can react to these changes by modifying the environment.
We've taken environments one step further, and allowed them to be shared between all the users in a conference. When any user makes a change to their own copy of the environment, that change is automatically propagated to all other users' copies. So while a user's input event may change their own environment, which generates an event to update their onscreen view, the change also gets sent to every other user's environment, generating the same event and updating their view - instant groupware.
These shared environments and the MVC paradigm really shine on larger applications, but here we'll just present a toy example to illustrate the mechanisms. Our data model will be held in an environment called stocks and consist of a single piece of information, the value of the fictional GroupKit Inc. stock. Our view will consist of an onscreen label showing the value, and an entry widget will allow us to change the value.
After initializing GroupKit, we begin by creating the stocks environment, using the gk_newenv command. The -peer flag tells GroupKit to create this as an environment shared with all its peers. We'll then give our GroupKit stock an initial value.
gk::menus addstandard gk::environment -peer stocks stocks set groupkit 1Next, we'll create our view, using a label widget. The "stocks bind changeEnvInfo" attaches a binding to the environment, so that whenever a change is made to it, the following code will be executed. There are also events for when a piece of information in the environment is first added (addEnvInfo), and when an item is removed (deleteEnvInfo)* . The event parameter %K refers to the name of the key in the environment that changed; we could have omitted they key here since there is only one key in the environment.
label .view -text "GroupKit Inc. value is [stocks get groupkit]" pack .view -side top stocks bind changeEnvInfo { if {%K=="groupkit"} { .view config -text "GroupKit Inc. value is [stocks get groupkit]" } }Finally, we create the controller, using an entry widget. When we hit the "Return" key in the entry, its value is retrieved and stored in the stocks environment. The view code should then notice the change and update the display. Finally, because it is a shared environment, the change will show up in all other users' environments, updating their views as well.
pack [entry .controller] -side top bind .controllerWe could easily replace either the controller or view with ones that behaved differently, without changing any other parts of the program (or they could even display several views, since multiple bindings on an event are allowed). Different users could also use different versions of the programs in the same conference. This works because only the models (the shared environment stocks) of different users directly communicate with each other, and not the views or controllers. So for example here is a view that uses a canvas widget to display a horizontal bar whose length depends on the value of the stock held in the environment:{stocks set groupkit [.controller get]}
pack [canvas .barview] -side top .barview create rectangle 0 0 [expr [stocks get groupkit]*5] 10 -anchor nw -tags bar model bind changeEnvInfo { if {%K=="groupkit"} { .barview coords bar 0 0 [expr [stocks get groupkit]*5] 10 } }In summary, GroupKit's shared environments can serve as an easy way to implement the Model-View-Controller paradigm, and extend it to support multi-user applications. Environments can take care of the housekeeping chores in updating and synchronizing multiple copies of a data structure (such as the multiple copies of the canvas widget used in the Note Organizer - though the canvas data structure had the advantage of having its view built-in!), and can help to modularize your application. Its a judgement call whether to use shared environments or the more direct RPC's in your application (they can be mixed for different parts of course). In general, if your application becomes larger, if you're dealing with information that can be changed in various ways by your program, or if it can be viewed in different ways, then environments are usually an appropriate choice.