Now that our Note Organizer can create ideas that show up on everyones' screens, lets start looking at how we can move the ideas around to organize them. To accomplish this in a single-user program, we would change the doAddIdea procedure to attach a binding to each canvas item after it is created, and reposition the item when the mouse moved, as illustrated in the following code fragment.
set canvasid [.notepad create text $notes(x) $notes(y) -text $idea \ -anchor nw -font $notes(notefont)] .notepad bind $canvasid"noteDragged $canvasid %x %y" proc noteDragged {id x y} { .notepad coords $id [.notepad canvasx $x] [.notepad canvasy $y] }
As before, we could use "gk::to all" to execute that callback in all users' programs by changing the line in noteDragged to:
gk::to all .notepad coords $id [.notepad canvasx $x] [.notepad canvasy $y]This will work most of the time, but is very fragile and can break if users are entering ideas very quickly. Here's why. The code above assumes that copies of an idea on different users' screens all have the same canvasid. But that is not always true.
Tk assigns canvasid's in the order in which items are created. But if two users create ideas at almost the same time, they may end up being created in a different order on each users' screens. If the first user enters "foo", the "gk::to all" adds the idea to the local canvas and then sends it across the network. If at the same time a second user enters "bar", their "gk::to all" puts it on their canvas and sends it across the network. This will result in the first user adding "foo" then "bar", while the second user adds "bar" and then "foo." The two items will be created in different orders on the different systems, and therefore the canvasid's won't match. This will be a problem when it comes to moving the items around, because we need to correctly refer to the item to move. This is shown in Figure XX.
Figure XX. Ideas created in different orders on different machines.
To make matters worse, because the doAddIdea routine (which creates the text items on the canvas) also chooses the position on the canvas, if the ideas cross paths, not only will they have different canvasid's, but they will appear in different places on different users' screens.
Problems like these are fairly common in most groupware applications. They can be solved, but only if the programmer is aware of the nuances of these situations. Lets address these problems one at a time.
What we'll do is slightly modify that scheme, so that each conference process will keep a global counter, but those counters will be unique from those of other conference processes. We will do this by prefacing each counter with a special GroupKit id number guaranteed unique for that conference process.
GroupKit generates a user number for every user in a conference process. It stores that number (and a lot of other information) in a data structure called an environment. We'll discuss environments in a moment, but for now just assume that the following code fragment will return the user's unique id number:
set myid [gk::users get local.usernum]
We can now generate a unique id for every idea, by combining our user number (held in myid) with a global counter. We can then send that combined id to doAddIdea, and tag the canvas item with the combined id. Finally we can use this unique id when we're moving the idea around. This is shown below.
set notes(counter) 0 proc addNewIdea {} { global notes set idea [.newidea get] set myid [gk::users get local.usernum] set id ${myid}x$notes(counter) incr notes(counter) gk::to all doAddIdea $id $idea .newidea delete 0 end } proc doAddIdea {id idea} { global notes .notepad create text $notes(x) $notes(y) -text $idea -anchor nw -font \ $notes(notefont) -tags $id .notepad bind $id"noteDragged $id %x %y" ... } proc noteDragged {id x y} { gk::to all .notepad coords $id [.notepad canvasx $x] [.notepad canvasy $y] }
proc addNewIdea {} { ... global notes gk::to all doAddIdea $id $notes(x) $notes(y) $idea ... } proc doAddIdea {id x y idea} { ... .notepad create text $x $y -text $idea -anchor nw \ -font $notes(notefont) -tags $id ... }What happens now when ideas are entered at the same time by different users? If you trace through the order that messages will get executed, you'll see that both ideas will end up entered in exactly the same place, overlapping each other! When you think about it, this actually makes some sense. Imagine the real-life setting where users place real paper notes on a large board. If two people place notes at the same time, they'll both reach for the same place. Seeing their problem, they can then choose to move one of the notes somewhere else. In our computer version, users can do the same thing - see the overlapping ideas and move one of them out of the way. While its possible to build very elaborate algorithms into software to recover from situations such as these, it is often best just to let people deal naturally with these situations.
To illustrate relaxed-WYSIWIS, we'll add to our program the notion of a "selected" idea, just as you'd select text in an editor or an object in a drawing program. We'll display the selected idea in a larger font. For this application, we'll decide that each participant has their own selection. Users can select an idea by clicking on it. The code that actually manipulates the selection will keep track of the selected object by adding a "selected" tag to the canvas item. Here is the code:
set notes(selectedfont) [list helvetica 20 bold] proc doAddIdea {id x y idea} { ... .notepad bind $id <1> "selectNote $id" ... } proc selectNote id { global notes catch {.notepad itemconfig selected -font $notes(notefont)} catch {.notepad dtag selected selected} .notepad addtag selected withtag $id .notepad itemconfig $id -font $notes(selectedfont) }In this example, we made the conscious design decision to not share selection between users. However, it would of course be possible to have the selection shared, so that there is a single selection, shared among all users. This could be built by adding the appropriate "gk::to all" RPC when changing selection.
We now successfully have dealt with the problems of coordinating multiple users. Generating unique id's based on the users' unique user number let us refer to objects uniquely, while letting the sender decide on the idea's position resulted in ideas appearing in the correct location on all screens. Finally, we showed that it may be desireable to have some information, such as selection, that is not shared between all users.