'EngiGraphModel' is a model which supports graph editing. The source is in Appendix 29. When we talk about MVC, the interaction is always handled by the Controller, but 'EngiGraphModel' is a Model which handles interaction for another model.
I think a suitable super class for 'EngiGraphModel' is 'EngiDisplayModel' which we have covered previously. The class definition of 'EngiGraphMode' is:
-------------------------------------------------------------------- EngiDisplayModel subclass: #EngiGraphModel instanceVariableNames: 'graphSelections ' classVariableNames: '' poolDictionaries: '' category: 'Engi-Graph' --------------------------------------------------------------------
Remenber that 'EngiDisplayModel' is a subclass of 'EngiVariable' which can store a value. 'EngiGraphModel' stores a graph (directed or undirected) in the inherited 'value' instance variable. 'graphSelections' stores the user-selected arcs and nodes.
File in Appendix 29 and evaluate Program 45-1
Program-45-1: (EngiDirectedGraph, EngiGraphModel; graph, directed graph) -------------------------------------------------------------------- | directedGraph graphModel | directedGraph := EngiDirectedGraph exampleGraph. graphModel := EngiGraphModel openOn: directedGraph. ^graphModel --------------------------------------------------------------------
The program opens a window containing a directed graph. The MVC components for the window are 'EngiDisplayModel', 'EngiDisplayView' and 'EngiDisplayController'. The view and the contoller are reused. Program 45-1 creates an instance of EngiGraphModel which has a directed graph as its value. (Try it with 'EngiUndirectedGraph').
Node and arc selection is done with the red button. Use shift-click to add objects to a selection group.
If you click-and-hold a vertex, a rectangle appears around the vertex and the cursor shape changes to a cross-hair. You can now move the rectangle around and when you release the mouse, the vertex is moved to the final location and all its arcs are redrawn. You can also move several items at once.
Program 45-2 opens three windows which have the same directed graph as their model.
Program-45-2: (EngiDirectedGraph, EngiGraphModel, ColorValue; graph, directed graph) -------------------------------------------------------------------- | directedGraph graphModel | directedGraph := EngiDirectedGraph exampleGraph. directedGraph doVertices: [:vertex | vertex strokeColor: ColorValue blue. vertex fillColor: ColorValue green]. directedGraph doBranches: [:branch | branch strokeColor: ColorValue red. branch lineWidth: 2]. graphModel := EngiGraphModel on: directedGraph. 3 timesRepeat: [graphModel open]. ^graphModel --------------------------------------------------------------------
When you change one view, the other two reflect the change because the model broadcasts changes to its dependents. Its easy to see how this dependency mechanism can be conceptually extended to a distributed Smalltalk environment with windows on the different machines throughout the network. This technology is important for building groupware or CSCW systems.
Let's now take a closer look. I do not think it is nessesary to describe display methods or display area calculation. Instead I will focus on mouse interaction. When the red or yellow button is pressed, the 'redButtonActivity:' or 'yellowButtonActivity:' message is sent to the 'graphModel'. If the red button is held for a short time, it is assumed that the user is trying to move something, but if the button is released quickly, i.e. a click instead of a hold, then we assume the user is selecting something rather than trying to move it. And in this case the yellowButton, which usually leads to a menu, just does nothing.
The logic for selection, movement, and menu handling seems at first complex, but it is just a matter of attending to the possibilities of mouse interaction than handling any great complexity. An important message is 'areaVerticesAndBranches: aCollection at: aPoint' which answers a Rectangle necessary to display a set of given nodes and arcs. Its purpose is to minimize the number of vertices and branches which are redrawn when any objects are moved. One could just redraw the whole graph each time, but a little experimentation will convince you that that results in unacceptably slow user interaction.