summaryrefslogtreecommitdiff
path: root/cad/doc/splitting_a_mode.py
blob: 8672ff16c3ad45a8d858371d2fa958b5c59f8218 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# Copyright 2007 Nanorex, Inc.  See LICENSE file for details. 
"""
splitting_a_mode.py -- explanation/demo of how to split an old mode, while
maintaining temporary compatibility with non-split methods and subclass modes.

@author: Bruce
@version: $Id$
@copyright: 2007 Nanorex, Inc.  See LICENSE file for details.

This file can be deleted after all NE1 modes have been split.

Status:

I think it's right, but I didn't test it.

"""

# context & basic concepts: not described here.

# terminology: C = Command, GM = GraphicsMode.

# The goal is that for each old mode, we have 5 new classes -- 2 main classes
# (its C and GM parts), and 3 "glue" classes that help initialize it --
# one for C-alone, one for GM-alone, and one for a mixed object that has
# both C and GM parts in one instance.
#
# For example, for basicMode these classes are:
# - basicCommand     (main C class)
# - Command          (C-alone glue class)
# - basicGraphicsMode     (main GM class)
# - GraphicsMode          (GM-alone glue class)
# - basicMode     (mixed C/GM glue class)

# The reason we need this is that each glue class has to inherit different
# superclasses and have different __init__ methods. The main differences are
# in how they set up the "cross reference attributes", command.graphicsMode
# and graphicsMode.command, and in whether a command instance has to create
# a new graphicsMode instance. In a mixed object, the command *is* the
# graphicsMode, so it should not create a new one, and each cross-reference
# attribute should be a property pointing to self. In a used-alone object,
# the __init__ method should set up the cross-reference attribute to point
# to another object, and in the command case, it has to create that object
# as a new instance of self.GraphicsMode_class.

# (One other requirement might be some superclasses just for isinstance tests,
# or to revise isinstance tests in other code, probably to just look for the
# basicCommand or basicGraphicsMode part depending on whether they test the
# "current command" or the "current graphicsMode".)

# The mixed object is only needed for compatibility with non-split subclasses
# and to work around bugs from an incomplete split. When it's no longer needed,
# the mixed glue class can be removed, and the other glue classes can be merged
# into the corresponding main classes, with the simpler names retained.
#
# So when basicMode is no longer needed, we'll delete its file (modes.py),
# and merge basicCommand and Command (retaining the name Command),
# and merge basicGraphicsMode and GraphicsMode (retaining the name
# GraphicsMode). But this can only happen when all subclasses are split, i.e.
# when every mode in NE1 is split.

# ===

# So, here is how this should be done with selectMode.
# After that, we'll show how to do it with its subclass selectMolsMode.

# First define classes which can work in either split C & GM form, or mixed form,
# which have most of the methods for each side. Each one has an init method which
# can call its superclass's init method, but which should not call any of the
# "glue init methods" in the superclass. The init methods should have the same
# arg signature as in their superclass.

# (In real life, these would go into different files, described later.
#  For clarity in this demo, they are all in one file.)

# We do the graphics part first since the command part has to refer to it.
#
# Its superclass, in general, should be another _basicGraphicsMode class.
# (In fully split modes that don't need mixed versions, it can also be the
#  value of command_superclass.GraphicsMode_class where command_superclass
#  is what the corresponding Command class inherits, or it can just be a
#  non-basic GraphicsMode class named directly, which should be the
#  same one used in the command superclass. But for modes with mixed versions,
#  it should just be the basicGraphicsMode version of the superclass
#  GraphicsMode.)

class Select_basicGraphicsMode(basicGraphicsMode):
    def __init__(self, glpane):
        """
        """
        basicGraphicsMode.__init__(self, glpane)
        # Now do whatever might be needed to init the graphicsMode object,
        # or the graphicsMode-related attrs of the mixed object.
        #
        # (Similar comments apply as for Select_basicCommand.__init__, below,
        #  but note that for GraphicsModes, there is no Enter method.
        #  Probably we will need to add something like an Enter_graphicsMode
        #  method to the GraphicsMode API. In the meantime, the command's Enter
        #  method has to initialize the graphicsMode object's attrs (which are
        #  the graphics-related attrs of self, in the mixed case, but are
        #  referred to as self.graphicsMode.attr so this will work in the split
        #  case as well), which is a kluge.)
        return

    # Now put all the methods from the "graphics area half" of the original
    # mode -- anything related to graphics, mouse events, cursors (for use in
    # graphics area), key bindings or context menu (for use in graphics area).

    def Draw(self, args):
        pass
    def someMouseMethod(self, args):
        pass
    def someCursorMethod(self, args):
        pass
    # etc

# The command part should in general inherit a _basicCommand superclass.
# Since selectMode inherited basicMode, we have Select_basicCommand inherit
# basicCommand;
# in comparison (done below), selectMolsMode inherited selectMode, so we'd
# make SelectChunks_basicCommand and inherit Select_basicCommand.

class Select_basicCommand(basicCommand):
    def __init__(self, commandSequencer):
        """
        ...
        """
        basicCommand.__init__(self, commandSequencer)
        # Now do whatever might be needed to init the command object,
        # or in the mixed case, the command-related attrs of the mixed object.
        # That might be nothing, since most attrs can just be initialized in
        # Enter, since they should be reinitialized every time we enter the
        # command anyway.
        # (If it's nothing, we probably don't need this method, but it's ok
        #  to have it for clarity, especially if there is more than one
        #  superclass.)
        return
    
    # Now put all the methods from the "command half" of the original
    # mode -- anything related to its property manager, its settings or
    # state, the model operations it does (unless those are so simple
    # that the mouse event bindings in the _GM half can do them directly
    # and the code is still clean, *and* no command-half subclass needs
    # to override them).

    def Enter(self, args):
        pass
    # etc

    # Every method in the original mode goes into one of these halves,
    # or gets split into two methods (with different names), one for
    # each half. If a method gets split, old code that called it (perhaps
    # in other files) needs to call both split methods, or one of them
    # needs to call the other. This requires some thought in each case
    # when it has to be done.

    pass

# ==

# Now we have to make the 3 glue classes.
# Their __init__ methods and properties can each be copied
# from the pattern used to split basicMode.

# Note that we call a "glue" __init__ method only in the "sub-most class"
# (the class we're actually instantiating); all superclass __init__ methods
# we call are in one of the _basicCommand or _basicGraphicsMode superclasses.

class Select_GraphicsMode( Select_basicGraphicsMode):
    """
    Glue class for GM-alone usage only
    """
    # Imitate the init and property code in GraphicsMode, but don't inherit it.
    # (Remember to replace all the occurrences of its superclass with our own.)
    # (When this is later merged with its superclass, most of this can go away
    #  since we'll then be inheriting GraphicsMode here.)

    # (Or can we inherit GraphicsMode *after* the main superclass, and not have
    #  to do some of this? I don't know. ### find out! [I think I did this in PanLikeMode.]
    #  At least we'd probably still need this __init__ method.
    #  If this pattern works, then in *our* subclasses we'd instead post-inherit
    #  this class, Select_GraphicsMode.)

    
    def __init__(self, command):
        self.command = command
        glpane = self.command.glpane 
        Select_basicGraphicsMode.__init__(self, glpane)
        return
    
    # (the rest would come from GraphicsMode if post-inheriting it worked,
    #  or we could split it out of GraphicsMode as a post-mixin to use there and here)

    def _get_commandSequencer(self):
        return self.command.commandSequencer

    commandSequencer = property(_get_commandSequencer)

    def set_cmdname(self, name):
        self.command.set_cmdname(name)
        return

    def _get_hover_highlighting_enabled(self):
        return self.command.hover_highlighting_enabled

    def _set_hover_highlighting_enabled(self, val):
        self.command.hover_highlighting_enabled = val

    hover_highlighting_enabled = property(_get_hover_highlighting_enabled, _set_hover_highlighting_enabled)

    pass

class Select_Command( Select_basicCommand):
    """
    Glue class for C-alone usage only
    """

    # This is needed so the init code knows what kind of GM to make.
    GraphicsMode_class = Select_GraphicsMode

    # Imitate the init and property code in Command, but don't inherit it.
    # (Remember to replace all the occurrences of its superclass with our own.)
    # (When this is later merged with its superclass, most of this can go away
    #  since we'll then be inheriting Command here.)

    # (Or can we inherit Command *after* the main superclass, and not have
    #  to do some of this? I don't know. ### find out! [I think I did this in PanLikeMode.]
    #  At least we'd probably still need this __init__ method.
    #  If this pattern works, then in *our* subclasses we'd instead post-inherit
    #  this class, Select_Command.)

    def __init__(self, commandSequencer):
        Select_basicCommand.__init__(self, commandSequencer)
        self._create_GraphicsMode()
        self._post_init_modify_GraphicsMode()
        return
    
    # (the rest would come from Command if post-inheriting it worked,
    #  or we could split it out of Command as a post-mixin to use there and here)
    
    def _create_GraphicsMode(self):
        GM_class = self.GraphicsMode_class
        assert issubclass(GM_class, GraphicsMode_API)
        args = [self] 
        kws = {} 
        self.graphicsMode = GM_class(*args, **kws)
        pass

    def _post_init_modify_GraphicsMode(self):
        pass

    pass

class selectMode( Select_basicCommand, Select_basicGraphicsMode, anyMode): # might need more superclasses for isinstance?
    """
    Glue class for mixed C/GM usage only
    """
    # Imitate the init and property code in basicMode, but don't inherit it.
    # (Remember to replace all the occurrences of one of its two main
    #  superclasses with the corresponding one of our own.)
    
    # (When we no longer need the mixed-C/GM case, this class can be discarded.)

    # (Or can we inherit basicMode *after* the main superclasses, and not have
    #  to do some of this? I don't know. ### find out! [I think I did this in PanLikeMode.]
    #  At least we'd still need this __init__ method. )

    def __init__(self, glpane):
        assert GLPANE_IS_COMMAND_SEQUENCER:
        commandSequencer = glpane
        
        Select_basicCommand.__init__(self, commandSequencer)
            # was just basicCommand in original
        
        Select_basicGraphicsMode.__init__(self, glpane)
            # was just basicGraphicsMode in original
        return

    # (the rest would come from basicMode if post-inheriting it worked,
    #  or we could split it out of basicMode as a post-mixin to use there and here)
    
    def __get_command(self):
        return self

    command = property(__get_command)

    def __get_graphicsMode(self):
        return self

    graphicsMode = property(__get_graphicsMode)

    pass

# putting these into the right files:

# in Select_Command we want that class, but first its basic version, Select_basicCommand.

# in Select_GraphicsMode we want that class, but first, Select_basicGraphicsMode.

# and in selectMode.py (same name as original mode) we want class selectMode as above.

# And several imports are needed to make this work, but they should be obvious
# from the NameErrors that come from forgetting them, e.g. of GLPANE_IS_COMMAND_SEQUENCER,
# anyMode, other superclasses.

# ==

# For SelectChunks, we'd make, in 3 files, and following the above pattern,
# but revising all superclass occurrences:

# SelectChunks_Command.py:
# SelectChunks_basicCommand inheriting Select_basicCommand
# SelectChunks_Command inheriting SelectChunks_basicCommand

# SelectChunks_GraphicMode.py
# SelectChunks_basicGraphicsMode inheriting Select_basicGraphicsMode
# SelectChunks_GraphicMode inheriting SelectChunks_basicGraphicsMode

# selectMolsMode.py (same name as old file):
# selectMolsMode, inheriting SelectChunk_basicCommand, SelectChunks_basicGraphicsMode

# ==

# If this all works right, then recombined versions of split/recombined mode classes will
# keep working with no problem at all, and so will their non-split subclasses,
# with no changes at all (except perhaps in isinstance tests).

# Not covered above:
#
# - when you'd need to add more stuff to the glue classes than what is copied/modified from their superclasses
#
# - how to use debug_prefs to test either _Command or mixed classes -- basically,
#   maybe easiest to use a hardcoded assignment instead, to put the _Command class into the GLPane list of modes
#   (I think that's right -- check what's done for PanMode etc to be sure)
#
# - what will still not work in the split case -- uses of one part's method or attr from the other part --
#   easiest fix is to change self.attr to self.command.attr for a command attr used in the GM part,
#   and to change self.attr to self.graphicsMode.attr for a GM attr used in the C part.
#   or you can use a property in a single place in the alone-case glue class.
#   But you have to decide where an attr belongs; it's not always obvious,
#   e.g. hover_highlighting_enabled belongs in C part though it's mainly used in GM part,
#   since C part is what wants to set it in certain circumstances. 
#
# - I think there was something else I forgot.

# ==

# end