Changeset 1803
- Timestamp:
- 08/01/07 00:35:21 (1 year ago)
- Files:
-
- gaphas/trunk/README.txt (modified) (9 diffs)
- gaphas/trunk/state.txt (modified) (7 diffs)
- gaphas/trunk/undo.txt (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
gaphas/trunk/README.txt
r1766 r1803 6 6 The basic idea is: 7 7 8 - Items (canvas items) should be used as "adapter" for model elements. 9 (not a real adapter since they are stateful). 10 - The canvas determines the tree structure (which items are children 11 of some other item is maintained by the canvas itself). 12 - of course the constraint solver is present. 13 - more modular: e.g. handle support could be swapped in and swapped out. 14 - rendering using Cairo. 15 16 17 To do 18 ===== 19 20 This is it as far as stage 1 is concerned. I have implemented: 21 v a render cycle. 22 v zoom and move functionality (canvas2world). 23 v scroll-bars work. 24 v a set of tools and a ToolChain (to chain them together). 25 v rubberband selection 26 27 Stage 2: 28 v check the code with pylint for strange things. 29 v line item 30 v placement tool 31 v connection protocol 32 v make update cycle independent from render (expose) event. 33 This is something we might do if the response is getting bad. 34 ? rotating and shearing for Element items. 35 Do we need this? 36 37 Stage 3: 38 v make double and triple click work. 39 v text edit tool (gtk.Edit in popup window?) 40 41 Stage n: 42 - Drop-zone tool 43 the idea is that for example you have a Package and when you drag 44 a Class into it it automatically makes the Package its owning element. 45 v undo management 46 8 - Items (canvas items) should be used as "adapter" for model elements. 9 (not a real adapter since they are stateful). 10 - The canvas determines the tree structure (which items are children 11 of some other item is maintained by the canvas itself). 12 - of course the constraint solver is present. 13 - more modular: e.g. handle support could be swapped in and swapped out. 14 - rendering using Cairo. 15 16 Gaphas is released under the terms of the GNU Library General Public License 17 (LGPL). See the COPYING file for details. 18 19 .. contents:: 47 20 48 21 How it Works … … 63 36 64 37 View (from view.py) is used to visualize a canvas. On a View, a Tool 65 (from tool.py) can be a ssigned, which will handle user input (button presses,38 (from tool.py) can be applied, which will handle user input (button presses, 66 39 key presses, etc.). Painters (from painter.py) are used to do the actual 67 40 drawing. This way it should be easy do draw to other media than the screen, … … 73 46 (Canvas.request_update()). The canvas performs an update by calling: 74 47 75 1. Item.pre_update(context) 76 2. updating World-to-Item matrices, for fast transformation of coordinates 77 from the world to the items' coordinate system. 78 The w2i matrix is stored on the Item as Item._matrix_w2i. 79 3. solve constraints 80 4. normalize items by setting the coordinates of the first handle to (0, 0). 81 5. updating World-to-Item matrices again, just to be on the save side. 82 6. Item.post_update(context) 83 84 The idea is to do as much updating as possible in the (pre_)update() methods, 85 since they are called when the application is not handling user input. 48 1. Item.pre_update(context) for each item marked for update 49 2. Updating Canvas-to-Item matrices, for fast transformation of coordinates 50 from the canvas' to the items' coordinate system. 51 The c2i matrix is stored on the Item as Item._matrix_c2i. 52 3. Solve constraints. 53 4. Normalize items by setting the coordinates of the first handle to (0, 0). 54 5. Updating Canavs-to-Item matrices for items that have been changed by 55 normalizarion, just to be on the save side. 56 6. Item.post_update(context) for each item marked for update, including items 57 that have been marked during constraint solving. 58 59 The idea is to do as much updating as possible in the {pre|post}_update() 60 methods, since they are called when the application is not handling user input. 86 61 87 62 The context contains: 88 63 89 cairo:a CairoContext, this can be used to calculate the dimensions of text90 for example64 :cairo: a CairoContext, this can be used to calculate the dimensions of text 65 for example 91 66 92 67 NOTE: updating is done from the canvas, items should not update sub-items. 93 68 94 69 After an update, the Item should be ready to be drawn. 70 71 Constraint solving 72 ------------------ 73 A word about the constraint solver seems in place. It is one of the big 74 features of this library after all. The Solver is able to solve constraints. 75 Constraints can be applied to items (e.g. the Element item uses constraints to 76 maintain it's recangular shape) and constraints can be created *between* items 77 (for example a line that connects to a box). 78 79 Constaints that apply to one item are pretty straight forward, as all variables 80 live in the same coordinate system (of the item). The variables (in most cases 81 a Handle's x and y coordinate) can simply be put in a constraint. 82 83 When two items are connected to each other and constraints are created, a 84 problem shows up: variables live in separate coordinate systems. To overcome 85 this problem a Projection (from solver.py) has been defined. With a Projection 86 instance, a variable can be "projected" on another coordinate system. In this 87 case, where two items are connecting to each other, the Canvas' coordinate 88 system is used. 89 95 90 96 91 Drawing … … 104 99 The view context passed to the Items draw() method has the following properties: 105 100 106 view: the view we're drawing to107 cairo: the CairoContext to draw to108 selected: True if the item is actually selected in the view109 focused: True if the item has the focus110 hovered: True if the mouse pointer if over the item. Only the top-most item101 :view: the view we're drawing to 102 :cairo: the CairoContext to draw to 103 :selected: True if the item is actually selected in the view 104 :focused: True if the item has the focus 105 :hovered: True if the mouse pointer if over the item. Only the top-most item 111 106 is marked as hovered. 112 dropzone: The item is marked as drop zone. This happens then an item is107 :dropzone: The item is marked as drop zone. This happens then an item is 113 108 dragged over the item and (if dropped) will become a child of 114 109 this item. 115 draw_all: True if everything drawable on the item should be drawn (e.g. when110 :draw_all: True if everything drawable on the item should be drawn (e.g. when 116 111 calculating the bounding boxes). 117 112 118 113 The View automatically calculates the bounding box for the item, based on the 119 items drawn in the draw(context) function (this is only done once after each 120 Item.update()). The bounding box is in viewport coordinates. 114 items drawn in the draw(context) function (this is only done when really 115 necessary, e.g. after an update of the item). The bounding box is in viewport 116 coordinates. 121 117 122 118 The actual drawing is done by Painters (painter.py). A series of Painters have 123 been defined: one for handles, one for items .119 been defined: one for handles, one for items, etc. 124 120 125 121 Tools … … 129 125 Tools can be chained together in order to provide more complex behavior. 130 126 131 To mak eit easy a DefaultTool has been defined: a ToolChain instance with the127 To make it easy a DefaultTool has been defined: a ToolChain instance with the 132 128 tools added that are listed in the following sections. 133 129 134 130 ToolChain 135 ~~~~~~~~~ 136 The ToolChain does not do anything by itself. It delegates to a set of tools 137 and keeps track of which tool has grabbed the focus. This happens most of the 138 time when the uses presses a mouse button. The tool requests a grab() and 139 all upcoming events (e.g. motion or button release events) are directly sent 140 to the focused tool. 131 The ToolChain does not do anything by itself. It delegates to a set of 132 tools and keeps track of which tool has grabbed the focus. This happens 133 most of the time when the uses presses a mouse button. The tool requests a 134 grab() and all upcoming events (e.g. motion or button release events) are 135 directly sent to the focused tool. 141 136 142 137 HoverTool 143 ~~~~~~~~~ 144 A small and simple tool that does nothing more than making the item under the 145 mouse button the "hovered item". When such an item is drawn, its 146 context.hovered_item flag will be set to True. 138 A small and simple tool that does nothing more than making the item under 139 the mouse button the "hovered item". When such an item is drawn, its 140 context.hovered_item flag will be set to True. 147 141 148 142 HandleTool 149 ~~~~~~~~~~ 150 The HandleTool is used to deal with handles. Handles can be dragged around. 151 Clicking on a handle automatically makes the underlaying item the focuseditem.143 The HandleTool is used to deal with handles. Handles can be dragged around. 144 Clicking on a handle automatically makes the underlaying item the focused 145 item. 152 146 153 147 ItemTool 154 ~~~~~~~~ 155 The item tool takes care of selecting items and dragging items around. 148 The item tool takes care of selecting items and dragging items around. 156 149 157 150 TextEditTool 158 ~~~~~~~~~~~~ 159 This is a demo-tool, featuring a text-edit popup. 151 This is a demo-tool, featuring a text-edit popup. 160 152 161 153 RubberbandTool 162 ~~~~~~~~~~~~~~ 163 The last toolin line is the rubberband tool. It's invoked when the mouse button 164 is pressed on a section of the view where no items or handles are present. It 165 allows the user to select items using a selection box(rubberband).154 The last toolin line is the rubberband tool. It's invoked when the mouse 155 button is pressed on a section of the view where no items or handles are 156 present. It allows the user to select items using a selection box 157 (rubberband). 166 158 167 159 … … 177 169 One of the problems you'll face is what to do when an item is removed from the 178 170 canvas and there are other items (lines) connected to. This problem can be 179 solvedby providing a disconnect handler to the handle instance ones it is171 overcome by providing a disconnect handler to the handle instance ones it is 180 172 connected. A callable object (e.g. function) can be assigned to the handle. It 181 173 is called at the moment the item it's connected to is removed from the canvas. … … 201 193 202 194 tree.py: 203 Central tree structure (no more CanvasGroupable)195 Central tree structure (no more CanvasGroupable) 204 196 solver.py: 205 A constraint solver (infinite domain, based on diacanvas2's solver)197 A constraint solver (infinite domain, based on diacanvas2's solver) 206 198 constraint.py: 207 Constraint implementation.199 Constraint implementation. 208 200 geometry.py: 209 Matrix, Rectangle calculations.201 Matrix, Rectangle calculations. 210 202 quadtree.py: 211 A QuadTree spacial index. Used in the View to pick items quickly.203 A QuadTree spacial index. Used in the View to pick items quickly. 212 204 213 205 Canvas classes: 214 206 215 207 item.py: 216 Canvas item and handle208 Canvas item and handle 217 209 canvas.py: 218 Canvas class210 Canvas class 219 211 view.py: 220 Canvas view (renderer) class212 Canvas view (renderer) class 221 213 tool.py: 222 Base class for Tools (which handle events on the view).214 Base class for Tools (which handle events on the view). 223 215 224 216 Other: 225 217 226 218 examples.py: 227 Simple example classes.219 Simple example classes. 228 220 229 221 Guidelines 230 ---------- 231 Following the Python coding guidelines 232 <http://www.python.org/dev/peps/pep-0008/> indentation should be 4 spaces 233 (no tabs), function and method names should be lowercase_with_underscore(), 234 and files should contain a __version__ property, like this: 222 ========== 223 Following the `Python coding guidelines`_ indentation should be 4 spaces 224 (no tabs), function and method names should be ``lowercase_with_underscore()``, 225 and files should contain a ``__version__`` property, like this:: 235 226 236 227 __version__ = "$Revision$" … … 240 231 241 232 This inhibits that for each .py file, the svn:keywords property should be set 242 to "Revision HeadURL". This can be done manually:233 to ``Revision HeadURL``. This can be done manually:: 243 234 244 235 $ svn propset svn:keywords "Revision HeadURL" myfile.py 245 236 246 or by configuring your ~/.subversion/config file to use auto-props: 237 or by configuring your ~/.subversion/config file to use auto-props:: 247 238 248 239 [miscellany] … … 255 246 256 247 248 .. _Python coding guidelines: http://www.python.org/dev/peps/pep-0008/ gaphas/trunk/state.txt
r1716 r1803 7 7 The state system consists of two parts: 8 8 9 1. A basic observer (the @observed decorator) 10 2. A reverter 9 1. A basic observer (the ``@observed`` decorator) 10 2. A reverter 11 12 .. contents:: 11 13 12 14 Observer 13 15 -------- 14 16 15 The observer simply dispatches the function called (as <function ..>, not as16 <unbound method..>!) to each handler registered in an observers list.17 The observer simply dispatches the function called (as ``<function ..>``, not as 18 ``<unbound method..>``!) to each handler registered in an observers list. 17 19 18 20 >>> from gaphas import state … … 53 55 [] 54 56 55 The @observeddecorator can also be applied to properties, as is done in57 The ``@observed`` decorator can also be applied to properties, as is done in 56 58 gaphas/item.py's Handle class: 57 59 … … 72 74 What should you know: 73 75 74 1. The observer always generates events based on 'function' calls. Even for75 class method invokations. This is because, when calling a method (say76 Tree.add) it's the im_func field is executed, which is a function type77 object.78 79 2. It's important to know if an event came from invoking a method or a simple80 function. With methods, the first argument always is an instance. This can81 be handy when writing an undo management systems in case multiple calls82 from the same instance do not have to be registered (e.g. if a method83 set_point() is called with exact coordinates (in stead of deltas), only the84 first call to set_point needs to be remembered.76 1. The observer always generates events based on 'function' calls. Even for 77 class method invokations. This is because, when calling a method (say 78 Tree.add) it's the im_func field is executed, which is a function type 79 object. 80 81 2. It's important to know if an event came from invoking a method or a simple 82 function. With methods, the first argument always is an instance. This can 83 be handy when writing an undo management systems in case multiple calls 84 from the same instance do not have to be registered (e.g. if a method 85 set_point() is called with exact coordinates (in stead of deltas), only the 86 first call to set_point needs to be remembered. 85 87 86 88 … … 90 92 The reverser requires some registration. 91 93 92 1. Property setters should be declared with reversible_property()93 2. Method (or function) pairs that implement each others reverse operation94 (e.g. add and remove) should be registered as reversible_pair()'s in the95 reverser engine.96 The reverser will construct a tuple (callable, arguments) which are send97 to every handler registered in the subscribers list. Arguments is a dict().98 99 First thing to do is to actually enable the revert_handler:94 1. Property setters should be declared with reversible_property() 95 2. Method (or function) pairs that implement each others reverse operation 96 (e.g. add and remove) should be registered as reversible_pair()'s in the 97 reverser engine. 98 The reverser will construct a tuple (callable, arguments) which are send 99 to every handler registered in the subscribers list. Arguments is a dict(). 100 101 First thing to do is to actually enable the ``revert_handler``: 100 102 101 103 >>> state.observers.add(state.revert_handler) 102 104 103 105 This handler is not enabled by default because: 104 1. it generates quite a bit of overhead if it isn't used anyway 105 2. you might want to add some additional filtering. 106 107 1. it generates quite a bit of overhead if it isn't used anyway 108 2. you might want to add some additional filtering. 106 109 107 110 Point 2 may require some explanation. First of all observers have been added … … 129 132 applied to that function. 130 133 131 The inverse operation is easiest performed by the function saveapply(). Of course132 an inverse operation is emitting a change event too:134 The inverse operation is easiest performed by the function saveapply(). Of 135 course an inverse operation is emitting a change event too: 133 136 134 137 >>> state.saveapply(*events.pop()) # doctest: +ELLIPSIS … … 155 158 >>> state.observers.remove(state.revert_handler) 156 159 157 TODO158 ----159 160 Function wrappers should have an extra property indicating if a function needs161 to be dispatched or not. If it needs to be dispatched an extra flag should be162 set. This will prevent the system from slowing down due to emitting signals that163 are filtered out later on.164 165 160 What is Observed 166 161 ---------------- … … 170 165 to monitor the Matrix class (which is from Cairo). 171 166 172 canvas.py:173 Canvas:174 add() and remove()175 176 item.py:177 Handle:178 x, y, connectable, movable, visible, connected_to and disconnect properties179 Item:180 canvas and matrix properties181 Element:182 min_height and min_width properties183 Line:184 line_width, fuzziness, orthogonal and horizontal properties;185 split_segment() and merge_segment()186 187 solver.py:188 Variable:189 strength and value properties190 Solver:191 add_constraint() and remove_constraint()192 193 tree.py:194 Tree:195 add() and remove()196 197 matrix.py:198 Matrix:199 invert, translate, rotate and scale167 canvas.py: 168 Canvas: 169 add() and remove() 170 171 item.py: 172 Handle: 173 x, y, connectable, movable, visible, connected_to and disconnect properties 174 Item: 175 canvas and matrix properties 176 Element: 177 min_height and min_width properties 178 Line: 179 line_width, fuzziness, orthogonal and horizontal properties; 180 split_segment() and merge_segment() 181 182 solver.py: 183 Variable: 184 strength and value properties 185 Solver: 186 add_constraint() and remove_constraint() 187 188 tree.py: 189 Tree: 190 add() and remove() 191 192 matrix.py: 193 Matrix: 194 invert, translate, rotate and scale 200 195 201 196 Testcases are described in undo.txt. gaphas/trunk/undo.txt
r1716 r1803 9 9 10 10 See state.txt about how state is recorded. 11 12 .. contents:: 11 13 12 14 For this to work, some boilerplate has to be configured: … … 36 38 tree.py: Tree 37 39 ------------- 38 Tree's add() and remove()methods are disabled by default.40 Tree's ``add()`` and ``remove()`` methods are disabled by default. 39 41 40 42 >>> from gaphas.tree import Tree … … 121 123 ----------------- 122 124 Matrix is used by Item classes. 123 invert, translate, rotate and scale124 125 125 126 >>> from gaphas.matrix import Matrix … … 192 193 >>> canvas.add(item) 193 194 194 The request_update()method is observed:195 The ``request_update()`` method is observed: 195 196 196 197 >>> len(undo_list) … … 200 201 4 201 202 202 On the canvas only add() and remove()are monitored:203 On the canvas only ``add()`` and ``remove()`` are monitored: 203 204 204 205 >>> canvas.get_all_items() # doctest: +ELLIPSIS … … 271 272 item.py: Handle 272 273 --------------- 273 x, y, connectable, movable, visible, connected_to and disconnect properties274 274 Changing the Handle's position is reversible: 275 275 … … 316 316 while testing the Canvas class. 317 317 318 The Matrix has been tested in section matrix. y: Matrix.318 The Matrix has been tested in section matrix.py: Matrix. 319 319 320 320 item.py: Element 321 321 ---------------- 322 322 323 An element has min_height and min_widthproperties.323 An element has ``min_height`` and ``min_width`` properties. 324 324 325 325 >>> from gaphas import Element … … 343 343 ------------- 344 344 345 A line has the following properties: line_width, fuzziness, orthogonal and346 horizontal. Each one of then is observed for changes:345 A line has the following properties: ``line_width``, ``fuzziness``, 346 ``orthogonal`` and ``horizontal``. Each one of then is observed for changes: 347 347 348 348 >>> from gaphas import Line … … 404 404 ----------------- 405 405 406 Solvers add_constraint() and remove_constraint()are observed.406 Solvers ``add_constraint()`` and ``remove_constraint()`` are observed. 407 407 408 408 >>> from gaphas.solver import Solver
