Attention une mise à jour du service Gitlab va être effectuée le mardi 18 janvier (et non lundi 17 comme annoncé précédemment) entre 18h00 et 18h30. Cette mise à jour va générer une interruption du service dont nous ne maîtrisons pas complètement la durée mais qui ne devrait pas excéder quelques minutes.

chart_model.py 59.7 KB
Newer Older
VIGNET Pierre's avatar
VIGNET Pierre committed
1
2
3
4
##
## Filename    : chart_model.py
## Author(s)   : Michel Le Borgne
## Created     : 4/3/2010
5
6
## Revision    :
## Source      :
VIGNET Pierre's avatar
VIGNET Pierre committed
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
##
## Copyright 2009 - 2010 : IRISA/IRSET
##
## This library is free software; you can redistribute it and/or modify it
## under the terms of the GNU General Public License as published
## by the Free Software Foundation; either version 2.1 of the License, or
## any later version.
##
## This library is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF
## MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  The software and
## documentation provided hereunder is on an "as is" basis, and IRISA has
## no obligations to provide maintenance, support, updates, enhancements
## or modifications.
## In no event shall IRISA be liable to any party for direct, indirect,
## special, incidental or consequential damages, including lost profits,
## arising out of the use of this software and its documentation, even if
## IRISA have been advised of the possibility of such damage.  See
## the GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this library; if not, write to the Free Software Foundation,
## Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
##
## The original code contained here was initially developed by:
##
##     Michel Le Borgne.
##     IRISA
##     Symbiose team
##     IRISA  Campus de Beaulieu
37
38
##     35042 RENNES Cedex, FRANCE
##
VIGNET Pierre's avatar
VIGNET Pierre committed
39
40
41
42
43
44
45
46
47
48
49
##
##     http:
##     mailto:
##
## Contributor(s): Nolwenn Le Meur, Geoffroy Andrieux
##
"""
Classes for representing a guarded transition model
"""

from math import sqrt
50
from cadbiom.antlr3 import ANTLRStringStream, ANTLRFileStream, CommonTokenStream
VIGNET Pierre's avatar
VIGNET Pierre committed
51
52
53

from condexpLexer import condexpLexer
from condexpParser import condexpParser
54
55
from cadbiom.models.guard_transitions.translators.cadlangLexer import cadlangLexer
from cadbiom.models.guard_transitions.translators.cadlangParser import cadlangParser
VIGNET Pierre's avatar
VIGNET Pierre committed
56
57
58

# number of simple nodes before we draw macros as simple nodes
MAX_SIZE_MACRO = 50
59
MAX_SIZE = 2000 # max number of nodes we draw
VIGNET Pierre's avatar
VIGNET Pierre committed
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




class ChartModelException(Exception):
    """
    Exception for chart models
    """
    def __init__(self, mess):
        self.message = mess




class ChartModel(object):
    """
    Model of a chart - implements the observer pattern as subject
    observers must have an update method
    """
    def __init__(self, name):
        self.name = name
        self.simple_node_dict = dict()       # for quick finding - name -> node
        self.node_dict = dict()
        self.transition_list = []            # idem
        # idem - simple node name -> list of influenced transition
85
        self.signal_transition_dict = dict()
VIGNET Pierre's avatar
VIGNET Pierre committed
86
87
88
89
90
        self.__root = CTopNode(name, self)
        self.constraints = "" # string of biosignal clock constraints
        self.modified = False
        self.show_macro = True
        # default value for max number of nodes for drawing
91
        self.max_size = MAX_SIZE
VIGNET Pierre's avatar
VIGNET Pierre committed
92
        self.__observer_list = [] # for observer pattern
93

VIGNET Pierre's avatar
VIGNET Pierre committed
94
95
96
97
98
99
100
    # observer pattern methods
    def attach(self, obs):
        """
        observer pattern standard attach methods
        """
        if not obs in self.__observer_list:
            self.__observer_list.append(obs)
101

VIGNET Pierre's avatar
VIGNET Pierre committed
102
103
104
105
106
    def detach(self, obs):
        """
        observer pattern standard detach methods
        """
        self.__observer_list.remove(obs)
107

VIGNET Pierre's avatar
VIGNET Pierre committed
108
109
110
111
112
113
114
    def notify(self):
        """
        observer pattern standard notify methods
        """
        for obs in self.__observer_list:
            obs.update()

115

VIGNET Pierre's avatar
VIGNET Pierre committed
116
117
118
119
120
121
122
    def build_from_cadlang(self, file_name, reporter):
        """
        Build a model from a .cal file of PID database
        @param file_name: str - path of .cal file
        """
        crep = reporter
        fstream = ANTLRFileStream(file_name)
123
        lexer = cadlangLexer(fstream)
VIGNET Pierre's avatar
VIGNET Pierre committed
124
125
126
        lexer.set_error_reporter(crep)
        parser = cadlangParser(CommonTokenStream(lexer))
        parser.set_error_reporter(crep)
127
128
        parser.cad_model(self)

VIGNET Pierre's avatar
VIGNET Pierre committed
129
130
131
132
133
134
135
136
137
138
139
140
141
    # model methods
    def draw(self, view):
        """
        @param view: chart_view
        """
        # we don't update views which are not visible
        if not view.window:
            return

        nb_snodes = len(self.simple_node_dict)
        # if model size two large don't draw
        if  nb_snodes > self.max_size:
            return
142

VIGNET Pierre's avatar
VIGNET Pierre committed
143
144
145
146
147
148
149
150
151
152
153
154
155
        # if model size two large, don't show macros
        if  nb_snodes > MAX_SIZE_MACRO:
            self.show_macro = False
        self.__root.draw(view)

    def get_root(self):
        """
        @return: the root of the hierarchy
        """
        if self.__root.submodel:
            return self.__root.sub_nodes[0]
        else:
            return self.__root
156

VIGNET Pierre's avatar
VIGNET Pierre committed
157
158
159
160
    def find_element(self,  m_v_c, dstyle):
        """
        @param m_v_c :coordinates of the mouse in virtual screen
        @param dstyle: drawing style (gives virtual size of fixed size nodes)
161
162
163

        Given the window mouse coordinates, return (node, handle, center)
        where node is the node the mouse is in,  handle the handle of the node
VIGNET Pierre's avatar
VIGNET Pierre committed
164
        the mouse is in and c are the coordinates of the node center in view.
165
        If no handle, handle = 0, handle are 1,2,3,4 clockwise numbered
VIGNET Pierre's avatar
VIGNET Pierre committed
166
167
168
169
        from upper left corner
        If no node found returns (None,0,(0,0))
        """
        return self.__root.find_element(m_v_c[0], m_v_c[1], dstyle)
170

VIGNET Pierre's avatar
VIGNET Pierre committed
171
172
173
174
175
176
    def make_submodel(self, mnode):
        """
        make a submodel from a macronode (no check) of another model
        """
        self.__root.sub_nodes = []
        self.__root.add_submodel(mnode)
177

VIGNET Pierre's avatar
VIGNET Pierre committed
178
179
180
181
182
183
184
185
186
187
188
    def is_submodel(self):
        """
        test if it is a submodel (for macro view)
        """
        return self.__root.submodel

    def is_modified(self):
        """
        Changes occur?
        """
        return self.modified
189

VIGNET Pierre's avatar
VIGNET Pierre committed
190
191
192
193
194
    def clean(self):
        """
        Clean markers
        """
        self.__root.clean()
195

VIGNET Pierre's avatar
VIGNET Pierre committed
196
197
198
199
200
    def clean_code(self):
        """
        Clean code attribute
        """
        self.__root.clean_code()
201

VIGNET Pierre's avatar
VIGNET Pierre committed
202
203
204
205
206
    def accept(self, visitor):
        """
        Visitors entrance
        """
        return visitor.visit_chart_model(self)
207

VIGNET Pierre's avatar
VIGNET Pierre committed
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    def get_simple_node_names(self):
        """
        @return: list of simple nodes in model
        """
        # refresh transition dictionary
        self.signal_transition_dict = dict()
        for trans in self.transition_list:
            ids = trans.get_influencing_places()
            for ident in ids:
                try:
                    id_tr = self.signal_transition_dict[ident]
                    self.signal_transition_dict[id].append(trans)
                except:
                    self.signal_transition_dict[ident] = [trans]
        return self.simple_node_dict.keys()
223

VIGNET Pierre's avatar
VIGNET Pierre committed
224
225
    def get_matching_node_names(self, regex_obj):
        """
226
        @return: return node names with name matching reg. expr.
VIGNET Pierre's avatar
VIGNET Pierre committed
227
228
229
230
231
232
        """
        lname = []
        for name in self.simple_node_dict.keys():
            if regex_obj.match(name):
                lname.append(name)
        return lname
233
234


VIGNET Pierre's avatar
VIGNET Pierre committed
235
236
237
238
239
240
241
242
243
244
    def search_unmark(self):
        """
        notify observers after unmarking nodes
        """
        for key in self.simple_node_dict:
            self.simple_node_dict[key].search_mark = False
        for key in self.signal_transition_dict:
            for trans in self.signal_transition_dict[key]:
                trans.search_mark = False
        self.notify()
245

VIGNET Pierre's avatar
VIGNET Pierre committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    def search_mark(self, lnode):
        """
        notify observers after marking nodes from lnode
        """
        # unmark first
        for key in self.simple_node_dict:
            self.simple_node_dict[key].search_mark = False
        for key in self.signal_transition_dict:
            for trans in self.signal_transition_dict[key]:
                trans.search_mark = False
        for name in lnode:
            try:
                self.simple_node_dict[name].search_mark = True
                # do not mark transition if node doesnt exist
                for trans in self.signal_transition_dict[name]:
                    trans.search_mark = True
            except:
                pass
        self.notify()
265

VIGNET Pierre's avatar
VIGNET Pierre committed
266
267
268
269
270
271
    def get_simple_node(self, name):
        """
        @param name: string - name of the node
        @return a simple node with given name
        """
        return self.simple_node_dict[name]
272

VIGNET Pierre's avatar
VIGNET Pierre committed
273
274
275
    def get_node(self, name):
        """
        @param name: string - name of the node
276
        @return: a node
VIGNET Pierre's avatar
VIGNET Pierre committed
277
278
        """
        return self.node_dict[name]
279

VIGNET Pierre's avatar
VIGNET Pierre committed
280
281
282
283
    def get_influenced_transition(self, node_name):
        """
        @param node_name: string
        @return: the transitions influenced by the node i.e. when
284
                 the node name appears in the condition
VIGNET Pierre's avatar
VIGNET Pierre committed
285
286
287
288
289
        """
        try:
            return self.signal_transition_dict[node_name]
        except:
            return []
290

VIGNET Pierre's avatar
VIGNET Pierre committed
291
292
293
    # method for model transformations
    def mark_as_frontier(self, node_name):
        """
294
        @param node_name: string
VIGNET Pierre's avatar
VIGNET Pierre committed
295
296
297
298
299
300
301
302
303
304
305
306
        Given the name of a simple node, add a start node and a transition
        from the start node to the simple node
        @warning: notify observers
        """
        try:
            snode = self.simple_node_dict[node_name]
            macro = snode.father
            start = macro.add_start_node(0, 0)
            macro.add_transition(start, snode)
        except KeyError:
            raise ChartModelException("Unknown simple node: "+node_name)
        self.notify()
307

VIGNET Pierre's avatar
VIGNET Pierre committed
308
309
310
    def __turn_into_other(self, node_name, ntype):
        """
        @param node_name: string
311
        @param ntype: node type - string
VIGNET Pierre's avatar
VIGNET Pierre committed
312
313
314
315
        turn a simple name into a permanent or input node
        The simple name must not have entering transitions
        @warning: notify observer
        """
316
        try:
VIGNET Pierre's avatar
VIGNET Pierre committed
317
318
319
320
321
322
323
324
325
326
327
328
329
330
            snode = self.simple_node_dict[node_name]
        except KeyError:
            raise ChartModelException("Unknown simple node: "+node_name)
        if len(snode.incoming_trans) != 0:
            raise ChartModelException("Incoming transition on node: "+node_name)
        macro = snode.father
        if ntype == 'perm':
            pnode = macro.add_perm_node(node_name, snode.xloc, snode.yloc)
        else:
            pnode = macro.add_input_node(node_name, snode.xloc, snode.yloc)
        for trans in snode.outgoing_tran:
            macro.add_transition(pnode, trans. ext)
        snode.remove()
        self.notify()
331

VIGNET Pierre's avatar
VIGNET Pierre committed
332
333
334
335
336
    def turn_into_input(self, node_name):
        """
        Turn a simple node into an input node
        """
        self.__turn_into_other(node_name, 'input')
337

VIGNET Pierre's avatar
VIGNET Pierre committed
338
339
340
341
342
    def turn_into_perm(self, node_name):
        """
        Turn a simple node into a perm node
        """
        self.__turn_into_other(node_name, 'perm')
343
344


VIGNET Pierre's avatar
VIGNET Pierre committed
345
346
347
348
349
350
351
class CNode(object):
    """
    Generic node for guarded transition models
    """
    wmin = 0.1
    hmin = 0.1
    depth_max = 3
352

VIGNET Pierre's avatar
VIGNET Pierre committed
353
354
355
356
357
358
359
360
361
    """
    Base class for  model components
    """
    def __init__(self, x_coord, y_coord, model):
        """
        The coordinates of a node are always in the space of its father.
        """
        self.model = model
        self.name = '$$'
362
        self.note = ""
VIGNET Pierre's avatar
VIGNET Pierre committed
363
364
365
366
367
368
369
        self.xloc = x_coord
        self.yloc = y_coord
        self.father = None
        self.selected = False
        self.search_mark = False
        self.incoming_trans = []
        self.outgoing_trans = []
370

VIGNET Pierre's avatar
VIGNET Pierre committed
371
372
373
374
375
    def is_top_node(self):
        """
        As it says
        """
        return False
376

VIGNET Pierre's avatar
VIGNET Pierre committed
377
378
379
380
381
    def is_macro(self):
        """
        As it says
        """
        return False
382

VIGNET Pierre's avatar
VIGNET Pierre committed
383
384
385
386
387
    def is_start(self):
        """
        As it says
        """
        return False
388

VIGNET Pierre's avatar
VIGNET Pierre committed
389
390
391
392
393
    def is_input(self):
        """
        As it says
        """
        return False
394

VIGNET Pierre's avatar
VIGNET Pierre committed
395
396
397
398
399
    def is_perm(self):
        """
        As it says
        """
        return False
400

VIGNET Pierre's avatar
VIGNET Pierre committed
401
402
403
404
405
    def is_trap(self):
        """
        As it says
        """
        return False
406

VIGNET Pierre's avatar
VIGNET Pierre committed
407
408
409
410
411
    def is_simple(self):
        """
        As it says
        """
        return False
412

VIGNET Pierre's avatar
VIGNET Pierre committed
413
414
415
416
417
    def set_model(self, model):
        """
        As it says
        """
        self.model = model
418

VIGNET Pierre's avatar
VIGNET Pierre committed
419
420
421
422
423
    def set_name(self, name):
        """
        As it says
        """
        self.name = name
424

VIGNET Pierre's avatar
VIGNET Pierre committed
425
426
427
428
429
430
431
#    def set_coordinates(self, x, y):
#        """
#        As it says
#        """
#        self.xloc = x
#        self.yloc = y
#        self.model.notify()
432

VIGNET Pierre's avatar
VIGNET Pierre committed
433
434
435
436
437
    def get_coordinates(self):
        """
        As it says
        """
        return (self.xloc, self.yloc)
438

VIGNET Pierre's avatar
VIGNET Pierre committed
439
440
441
442
443
444
    def set_layout_coordinates(self, x_coord, y_coord):
        """
        As it says
        """
        self.xloc = x_coord
        self.yloc = y_coord
445
446


VIGNET Pierre's avatar
VIGNET Pierre committed
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
    def find_element(self, mox, moy, dstyle):
        """
        The mouse coordinates must be in the same frame than nodes coordinates
        """
        pass


    def remove(self):
        """
        Remove this node from its model - assume it is not a top-node
        """
        if self.father:
            nfath = self.father
            # remove transitions using this node
            to_remove = []
            for gtr in nfath.transitions:
                if gtr[0].ori == self :
                    to_remove.append(gtr)
465

VIGNET Pierre's avatar
VIGNET Pierre committed
466
467
                elif gtr[0].ext == self :
                    to_remove.append(gtr)
468

VIGNET Pierre's avatar
VIGNET Pierre committed
469
470
471
472
            for gtr in to_remove:
                nfath.transitions.remove(gtr)
                for trans in gtr :
                    trans.remove()
473

VIGNET Pierre's avatar
VIGNET Pierre committed
474
475
476
477
478
479
480
            nfath.sub_nodes.remove(self)
            if self.model.simple_node_dict.has_key(self.name):
                del self.model.simple_node_dict[self.name]
            self.model.modified = True
            self.model.notify()
        else:
            pass
481

VIGNET Pierre's avatar
VIGNET Pierre committed
482
483
484
    def clean(self):
        """
        Abstract
485
486
487
488
        """
        pass


VIGNET Pierre's avatar
VIGNET Pierre committed
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
class CStartNode(CNode):
    """
    Start node show macro-states initialisation
    """
    def __init__(self, x_coord, y_coord, model):
        CNode.__init__(self, x_coord, y_coord, model)
        self.name = "__start__"
        self.wloc = 1.0
        self.hloc = 1.0 # for compatibility with find

    def copy(self, model = None):
        """
        As says
        """
        if not model:
            model = self.model
        node = CStartNode(self.xloc, self.yloc, model)
        return node
507

VIGNET Pierre's avatar
VIGNET Pierre committed
508
509
510
511
512
    def is_start(self):
        """
        As says
        """
        return True
513

VIGNET Pierre's avatar
VIGNET Pierre committed
514
515
516
517
518
    def is_for_origin(self):
        """
        As says
        """
        return True
519

VIGNET Pierre's avatar
VIGNET Pierre committed
520
521
522
523
524
    def is_for_extremity(self):
        """
        As says
        """
        return False
525

VIGNET Pierre's avatar
VIGNET Pierre committed
526
527
    def get_center_loc_coord(self, dstyle, w_ratio, h_ratio):
        """
528
        @param dstyle: drawing style (not used here)
VIGNET Pierre's avatar
VIGNET Pierre committed
529
        @param w_ratio,h_ratio: affinity ratios for virtual screen (not used here)
530

VIGNET Pierre's avatar
VIGNET Pierre committed
531
532
533
        Returns center coordinate in surrounding node - here node coordinates
        """
        return (self.xloc, self.yloc)
534

VIGNET Pierre's avatar
VIGNET Pierre committed
535
536
537
538
539
540
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        depth is less than depth_max
        """
        # graphic style
        view.drawing_style.draw_start(self, xfr, yfr, wfr, hfr)
541

VIGNET Pierre's avatar
VIGNET Pierre committed
542
543
544
545
546
547
548
549
550
    def find_element(self, mox, moy , dstyle, w_coef, h_coef):
        """
        No handle
        """
        # bounding box in father's frame
        v_bb_size = self.accept(dstyle)
        snw = v_bb_size[0] * w_coef
        snh = v_bb_size[1] * h_coef
        hdec = v_bb_size[2] * h_coef
551

VIGNET Pierre's avatar
VIGNET Pierre committed
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
        # center coordinates in father's frame
        ccx = self.xloc
        ccy = self.yloc + hdec
        in_node = (mox>=ccx-snw) and (mox<=ccx+snw)
        in_node = in_node and  (moy>=ccy-snh) and (moy<=ccy+snh)
        if in_node:
            return (self, 0, (ccx, ccy), None)
        else:
            return (None, 0, (0, 0), None)

    def move(self, v_dx, v_dy, dstyle, top_node):
        """
        Move the node - mx_virt, my virt coordinates of the mouse in virtual screen frame
        click_loc is the location of the clic in node's frame
        """
567

VIGNET Pierre's avatar
VIGNET Pierre committed
568
569
570
571
        # move in reference frame (father's)
        (acx, acy) = self.father.v_affinity_coef(top_node)
        loc_dx = acx*v_dx
        loc_dy = acy*v_dy
572
        # translation vector in own's frame -> father's frame
VIGNET Pierre's avatar
VIGNET Pierre committed
573
574
        new_xloc = self.xloc + loc_dx
        new_yloc = self.yloc + loc_dy
575

VIGNET Pierre's avatar
VIGNET Pierre committed
576
577
578
579
580
581
582
583
        # TODO compute limits
        delta = 0.0
        if (new_xloc >= 0) and (new_xloc<1.0):
            self.xloc = new_xloc
        if (new_yloc > delta) and (new_yloc < 1.0):
            self.yloc = new_yloc
        self.model.modified = True
        self.model.notify()
584

VIGNET Pierre's avatar
VIGNET Pierre committed
585
586
    def intersect(self, node2, dstyle, nb_trans, w_ratio, h_ratio):
        """
587
588
        @param dstyle: drawing style
        @param w_ratio, h_ratio: affinity ratios virtual -> local frame
VIGNET Pierre's avatar
VIGNET Pierre committed
589
590
591
592
593
594
595
596
597
598
599
600
        """
        (xc2, yc2) = node2.get_center_loc_coord(dstyle, w_ratio, h_ratio)
        # local axes size (ellipse because of affinities)
        v_size = self.accept(dstyle)
        rlocx = v_size[0]/w_ratio
        rlocy = v_size[1]/h_ratio
        uux = xc2 - self.xloc
        uuy = yc2 - self.yloc
        norm = sqrt(uux**2 + uuy**2)
        if norm != 0:
            uux = uux / norm
            uuy = uuy / norm
601
602
        isx = self.xloc + uux * rlocx
        isy = self.yloc + uuy * rlocy
VIGNET Pierre's avatar
VIGNET Pierre committed
603
        return (isx, isy, 0.0, 1)
604

VIGNET Pierre's avatar
VIGNET Pierre committed
605
606
607
608
609
610
611
612
    def clean(self):
        pass

    def accept(self, visitor):
        """
        Generic visitor acceptor
        """
        return visitor.visit_cstart_node(self)
613

VIGNET Pierre's avatar
VIGNET Pierre committed
614
615
616
617
618
619
620
621
622
623
class CTrapNode(CStartNode):
    """
    Dead end node
    """
    def __init__(self, xcoord, ycoord, model):
        CStartNode.__init__(self, xcoord, ycoord, model)
        self.name = "__trap__"

    def is_trap(self):
        return True
624

VIGNET Pierre's avatar
VIGNET Pierre committed
625
626
    def is_start(self):
        return False
627

VIGNET Pierre's avatar
VIGNET Pierre committed
628
629
630
631
632
    def copy(self, model = None):
        if not model:
            model = self.model
        node = CTrapNode(self.xloc, self.yloc, model)
        return node
633

VIGNET Pierre's avatar
VIGNET Pierre committed
634
635
    def is_for_origin(self):
        return False
636

VIGNET Pierre's avatar
VIGNET Pierre committed
637
638
    def is_for_extremity(self):
        return True
639

VIGNET Pierre's avatar
VIGNET Pierre committed
640
641
642
643
644
645
646
647
648
649
650
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        depth is less than depth_max
        """
        # graphic context
        view.drawing_style.draw_trap(self, xfr, yfr, wfr, hfr)

    def accept(self, visitor):
        """
        Standard acceptor
        """
651
652
        return visitor.visit_ctrap_node(self)

VIGNET Pierre's avatar
VIGNET Pierre committed
653
654
655
656
657
658
659
660
661
class CSimpleNode(CNode):
    """
    A simple node cannot have sub nodes
    Simple nodes have constant screen dimensions
    """
    def __init__(self, xcoord, ycoord, name, model):
        CNode.__init__(self, xcoord, ycoord, model)
        self.name = name.encode('ascii')
        self.father = None # double linkage for coordinate computations
662
        self.hloc = 1.0
VIGNET Pierre's avatar
VIGNET Pierre committed
663
664
        self.activated = False
        self.was_activated = False
665

VIGNET Pierre's avatar
VIGNET Pierre committed
666
667
668
669
670
671
672
673
    def copy(self, model = None):
        """
        As says
        """
        if not model:
            model = self.model
        node = CSimpleNode(self.xloc, self.yloc, self.name, model)
        return node
674

VIGNET Pierre's avatar
VIGNET Pierre committed
675
676
    def is_simple(self):
        return True
677

VIGNET Pierre's avatar
VIGNET Pierre committed
678
679
680
681
682
    def is_for_origin(self):
        """
        Can be used as transition origin
        """
        return True
683

VIGNET Pierre's avatar
VIGNET Pierre committed
684
685
686
687
688
    def is_for_extremity(self):
        """
        Can be used as transition extremity
        """
        return True
689

VIGNET Pierre's avatar
VIGNET Pierre committed
690
691
692
693
694
695
696
    def set_name(self, name):
        try:
            del self.model.simple_node_dict[self.name]
        except:
            pass
        self.name = name
        self.model.simple_node_dict[name] = self
697

VIGNET Pierre's avatar
VIGNET Pierre committed
698
699
700
701
702
703
704
705
706
    def get_center_loc_coord(self, dstyle, w_ratio, h_ratio):
        """
        Returns center coordinate in surrounding node
        """
        v_size = self.accept(dstyle)
        xco = self.xloc + (v_size[0] / w_ratio) / 2.0
        yco = self.yloc + (v_size[1] / h_ratio) / 2.0
        return (xco, yco)

707

VIGNET Pierre's avatar
VIGNET Pierre committed
708
709
710
711
712
713
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        depth is less than depth_max
        @param xfr: x coordinate of father in view screen
        @param yfr: y coordinate of father in view screen
        @param wfr: width of father in virtual screen
714
        @param hfr: height of father in virtual screen
VIGNET Pierre's avatar
VIGNET Pierre committed
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
        """
        view.drawing_style.draw_simple(self, xfr, yfr, wfr, hfr)


    def find_element(self, mox, moy, dstyle, w_coef, h_coef):
        """
        Simple node - No handle
        """
        # size of the node in father's frame
        v_bb = self.accept(dstyle)
        snw = v_bb[0] * w_coef
        snh = v_bb[1] * h_coef

        # center coordinates in father's frame
        ccx = self.xloc + 0.5 * snw
        ccy = self.yloc + 0.5 * snh
731
        in_node = (mox >= self.xloc) and (mox <= self.xloc + snw)
VIGNET Pierre's avatar
VIGNET Pierre committed
732
733
734
735
736
737
        in_node = in_node and (moy >= self.yloc) and (moy <= self.yloc+snh)
        if in_node:
            return (self, 0, (ccx, ccy), None)
        else:
            return (None, 0, (0, 0), None)

738

VIGNET Pierre's avatar
VIGNET Pierre committed
739
740
741
742
743
744
745
746
747
    def move(self, v_dx, v_dy, v_size, top_node):
        """
        Move the node - mx_virt, my virt are virtual coordinates of the mouse
        click_loc is the location of the clic in node's frame
        """
        # move in reference frame (father's)
        (ccx, ccy) = self.father.v_affinity_coef(top_node)
        loc_dx = ccx * v_dx
        loc_dy = ccy * v_dy
748
        # translation vector in reference frame i.e. father's frame
VIGNET Pierre's avatar
VIGNET Pierre committed
749
750
        new_xloc = self.xloc + loc_dx
        new_yloc = self.yloc + loc_dy
751

VIGNET Pierre's avatar
VIGNET Pierre committed
752
753
754
755
        # check limits
        wloc = v_size[0] * ccx
        hloc = v_size[1] * ccy
        delta = v_size[2] * ccy
756

VIGNET Pierre's avatar
VIGNET Pierre committed
757
758
759
760
761
762
763
        move = False
        if (new_xloc >= 0) and (new_xloc+wloc < 1.0):
            self.xloc = new_xloc
            move = True
        if (new_yloc > delta) and (new_yloc+hloc < 1.0):
            self.yloc = new_yloc
            move = True
764
        if move:
VIGNET Pierre's avatar
VIGNET Pierre committed
765
766
            self.model.modified = True
            self.model.notify()
767
768


VIGNET Pierre's avatar
VIGNET Pierre committed
769
770
771
772
773
    def intersect(self, node2, dstyle, nb_trans, w_ratio, h_ratio):
        """
        Gives the the first point where to branch a transition, the gap between two arrows
        and a boolean horizontal true if arrows start from horizontal edge
        Assume wloc and hloc computed (node drawn) - coordinates in container frame
774

VIGNET Pierre's avatar
VIGNET Pierre committed
775
        @param node2: second node of the transition
776
        @param dstyle: drawing style
VIGNET Pierre's avatar
VIGNET Pierre committed
777
778
779
780
781
        @param nb_trans: number of transitions to be drawn
        @param w_ratio, h_ratio: dimentions of the screen for fixed size nodes
        """
        return intersect_simple(self, node2, dstyle, nb_trans, w_ratio, h_ratio)

782

VIGNET Pierre's avatar
VIGNET Pierre committed
783
784
785
786
787
788
789
790
791
    def clean(self):
        self.activated = False
        self.was_activated = False

    def accept(self, visitor):
        """
        standard visitor acceptor
        """
        return visitor.visit_csimple_node(self)
792

VIGNET Pierre's avatar
VIGNET Pierre committed
793
794
795
796
class CPermNode(CSimpleNode):
    """
    permanent node are never unactivated
    """
797

VIGNET Pierre's avatar
VIGNET Pierre committed
798
799
800
801
802
803
804
805
806
807
808
    def __init__(self,  xcoord, ycoord, name, model):
        CSimpleNode.__init__(self, xcoord, ycoord, name, model)

    def copy(self, model = None):
        if not model:
            model = self.model
        node = CPermNode(self.xloc, self.yloc, self.name, model)
        return node

    def is_perm(self):
        return True
809

VIGNET Pierre's avatar
VIGNET Pierre committed
810
811
    def is_simple(self):
        return False
812

VIGNET Pierre's avatar
VIGNET Pierre committed
813
814
    def is_for_extremity(self):
        return False
815

VIGNET Pierre's avatar
VIGNET Pierre committed
816
817
    def accept(self, visitor):
        return visitor.visit_cperm_node(self)
818

VIGNET Pierre's avatar
VIGNET Pierre committed
819
820
821
822
823
824
825
826
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        depth is less than depth_max
        @param xfr, yfr: coordinates of father node (reference frame) in view
        @param wfr,hfr: width and height of father node in view (affinity ratios)
        """
        view.drawing_style.draw_perm(self, xfr, yfr, wfr, hfr)

827

VIGNET Pierre's avatar
VIGNET Pierre committed
828
829
830
831
832
833
834
class CInputNode(CSimpleNode):
    """
    An input node cannot have an in-transition
    """
    def __init__(self, xcoord, ycoord, name, model):
        CSimpleNode.__init__(self, xcoord, ycoord, name, model)
        self.father = None # double linkage for coordinate computations
835
        self.hloc = 1.0
VIGNET Pierre's avatar
VIGNET Pierre committed
836
        self.activated = False
837

VIGNET Pierre's avatar
VIGNET Pierre committed
838
839
    def is_input(self):
        return True
840

VIGNET Pierre's avatar
VIGNET Pierre committed
841
842
    def is_simple(self):
        return False
843

VIGNET Pierre's avatar
VIGNET Pierre committed
844
845
846
847
848
    def copy(self, model = None):
        if not model:
            model = self.model
        node = CInputNode(self.xloc, self.yloc, self.name, model)
        return node
849

VIGNET Pierre's avatar
VIGNET Pierre committed
850
851
    def is_for_extremity(self):
        return False
852

VIGNET Pierre's avatar
VIGNET Pierre committed
853
854
855
856
857
    def get_center_loc_coord(self, dstyle, w_ratio, h_ratio):
        """
        Returns center coordinate in surrounding node
        """
        return (self.xloc, self.yloc)
858

VIGNET Pierre's avatar
VIGNET Pierre committed
859
860
861
862
863
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        special drawing for diamond
        """
        view.drawing_style.draw_input(self, xfr, yfr, wfr, hfr)
864

VIGNET Pierre's avatar
VIGNET Pierre committed
865
866
867
868
869
870
871
872
873
874
875
876
    def find_element(self, mox, moy , dstyle, w_coef, h_coef):
        """
        Simple node - No handle
        """
        # size of the box in father's frame
        v_bb = self.accept(dstyle)
        snw = v_bb[0] * w_coef
        snh = v_bb[1] * h_coef

        # center coordinates in father frame
        ccx = self.xloc + 0.5 * snw
        ccy = self.yloc + 0.5 * snh
877

VIGNET Pierre's avatar
VIGNET Pierre committed
878
879
880
881
        # mouse coordinates in self frame
        mxl = (mox - self.xloc) / snw
        myl = (moy - self.yloc) / snh
        # test
882
        in_node = (mox >= self.xloc) and (mox <= self.xloc+snw)
VIGNET Pierre's avatar
VIGNET Pierre committed
883
        in_node = in_node and (moy >= self.yloc) and (moy <= self.yloc+snh)
884

VIGNET Pierre's avatar
VIGNET Pierre committed
885
886
887
888
        if in_node:
            return (self, 0, (ccx, ccy), None)
        else:
            return (None, 0, (0, 0), None)
889

VIGNET Pierre's avatar
VIGNET Pierre committed
890
891
892
893
894
895
896
897
898
    def move(self, v_dx, v_dy, v_size, top_node):
        """
        Move the node - mx_virt, my virt are virtual coordinates of the mouse
        click_loc is the location of the clic in node's frame
        """
        # move in reference frame (father's)
        (ccx, ccy) = self.father.v_affinity_coef(top_node)
        loc_dx = ccx * v_dx
        loc_dy = ccy * v_dy
899
        # translation vector in reference frame i.e. father's frame
VIGNET Pierre's avatar
VIGNET Pierre committed
900
901
902
903
904
905
        new_xloc = self.xloc + loc_dx
        new_yloc = self.yloc + loc_dy
        # check limits
        wloc = (v_size[0] * ccx)/2.0
        hloc = (v_size[1] * ccy)/2.0
        hlabel = v_size[2] * ccy
906

VIGNET Pierre's avatar
VIGNET Pierre committed
907
908
909
910
911
912
913
        move = False
        if (new_xloc >= wloc) and (new_xloc+wloc < 1.0):
            self.xloc = new_xloc
            move = True
        if (new_yloc - hloc> hlabel) and (new_yloc+hloc < 1.0):
            self.yloc = new_yloc
            move = True
914
        if move:
VIGNET Pierre's avatar
VIGNET Pierre committed
915
916
            self.model.modified = True
            self.model.notify()
917
918


VIGNET Pierre's avatar
VIGNET Pierre committed
919
920
921
922
923
924
925
926
927
928
929
930
    def intersect(self, node2, dstyle, nb_trans, w_ratio, h_ratio):
        """
        An input node can be the origin of some transition to a node.
        One transition by node.
        """
        # local coordinates of node2 center
        (xc2, yc2) = node2.get_center_loc_coord(dstyle, w_ratio, h_ratio)
        # simple nodes have constant screen dimensions - convert to local ones
        v_size = self.accept(dstyle)
        wloc = (v_size[0] / w_ratio) / 2.0
        hloc = (v_size[1] / h_ratio) / 2.0
        # coordinates of a diamond are center coordinates
931
        uux = xc2 - self.xloc
VIGNET Pierre's avatar
VIGNET Pierre committed
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
        uuy = yc2 - self.yloc
        if uux != 0.0:
            slope = uuy / uux
            if slope < -1:
                if uux < 0.0:
                    return (self.xloc, self.yloc + hloc, 0, True) # upper corner
                else:
                    return (self.xloc, self.yloc - hloc, 0, True) # low corner
            elif slope >= -1 and slope <= 1:
                if uux < 0.0:
                    return (self.xloc - wloc, self.yloc, 0, True) # left corner
                else:
                    return (self.xloc + wloc, self.yloc, 0, True) # right corner
            else:
                if uux < 0.0:
                    return (self.xloc, self.yloc - hloc, 0, True) # low corner
                else:
                    return (self.xloc, self.yloc + hloc, 0, True) # upper corner
        else:
            if uuy >= 0:
                return (self.xloc, self.yloc + hloc, 0, True) # upper corner
            else:
                return (self.xloc, self.yloc - hloc, 0, True) # low corner
955

VIGNET Pierre's avatar
VIGNET Pierre committed
956
957
    def accept(self, visitor):
        return visitor.visit_cinput_node(self)
958

VIGNET Pierre's avatar
VIGNET Pierre committed
959
960
961
962
963
964
965
966
967
968
969
class CMacroNode(CSimpleNode):
    """
    Main building block for charts
    """
    def __init__(self, xcoord, ycoord, width, height, name, model):
        CSimpleNode.__init__(self, xcoord, ycoord, name, model)
        self.count = 0 # for start and trap nodes naming
        self.wloc = width
        self.hloc = height
        self.sub_nodes = []
        # list<list<CTransitions>> Sublists: transitions with common extremities
970
971
        self.transitions = []

VIGNET Pierre's avatar
VIGNET Pierre committed
972
973
974
975
976
977
978
979
980
981
    def _find_in_subnodes(self, node):
        """
        Find a node in the list of sub nodes.
        assume two nodes have different coordinates
        @param node: node to be found
        """
        for snode in self.sub_nodes:
            if snode.xloc == node.xloc and snode.yloc == node.yloc:
                return snode
        return None
982

VIGNET Pierre's avatar
VIGNET Pierre committed
983
984
985
986
987
988
    def copy(self, model = None):
        """
        Duplicate a macronode - performs a deep copy
        """
        if not model:
            model = self.model
989
        node_c = CMacroNode(self.xloc, self.yloc, self.wloc, self.hloc,
VIGNET Pierre's avatar
VIGNET Pierre committed
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
                            self.name, model)
        # copy subnodes
        for snode in self.sub_nodes:
            snc = snode.copy(model)
            node_c.sub_nodes.append(snc)
            snc.father = node_c
        # copy transitions
        for gtr in self.transitions:
            gtr_c = []
            for trans in gtr:
                origin = trans.ori
                origin_c = node_c._find_in_subnodes(origin)
                extremity = trans.ext
                extremity_c = node_c._find_in_subnodes(extremity)
                trc = CTransition(origin_c, extremity_c)
                trc.macro_node = node_c
                trc.event = trans.event
                trc.condition = trans.condition
                trc.action = trans.action
                gtr_c.append(trc)
            node_c.transitions.append(gtr_c)
        return node_c
1012

VIGNET Pierre's avatar
VIGNET Pierre committed
1013
1014
1015
1016
1017
1018
1019
1020
    def set_model(self, model):
        """
        Set the model for a subtree of nodes - used for copy/paste from one model to another
        """
        for snode in self.sub_nodes:
            snode.set_model(model)
        self.model = model
        self.model.modified = True
1021

VIGNET Pierre's avatar
VIGNET Pierre committed
1022
1023
    def set_name(self, name):
        self.name = name
1024

VIGNET Pierre's avatar
VIGNET Pierre committed
1025
1026
    def is_macro(self):
        return True
1027

VIGNET Pierre's avatar
VIGNET Pierre committed
1028
1029
    def is_simple(self):
        return False
1030

VIGNET Pierre's avatar
VIGNET Pierre committed
1031
1032
1033
1034
1035
    def is_for_origin(self):
        """
        Legitimate transition origin
        """
        return True
1036

VIGNET Pierre's avatar
VIGNET Pierre committed
1037
1038
1039
1040
1041
    def is_for_extremity(self):
        """
        Legitimate transition extremity
        """
        return True
1042

VIGNET Pierre's avatar
VIGNET Pierre committed
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
    def get_center_loc_coord(self, dstyle, w_ratio, h_ratio):
        """
        Returns center coordinate in surrounding node (father)
        """
        if self.model.show_macro:
            xcc = self.xloc + self.wloc / 2.0
            ycc = self.yloc + self.hloc / 2.0
        else:
            v_size = self.accept(dstyle)
            xcc = self.xloc + (v_size[0] / w_ratio) / 2.0
            ycc = self.yloc + (v_size[1] / h_ratio) / 2.0
        return (xcc, ycc)
1055

VIGNET Pierre's avatar
VIGNET Pierre committed
1056
1057
1058
1059
1060
1061
    def virt_to_self_frame(self, xcoord, ycoord, s_width, s_height, top_node):
        """
        xcoord,ycoord must be virtual coordinates
        result is the coordinates of (x,y) in self frame
        @param xcoord, ycoord: coordinate in virtual window (screen -> 1.0 x 1.0 window)
        @param s_width, s_height: screen dimensions
1062
        @param  top_node: root of the sub_model
VIGNET Pierre's avatar
VIGNET Pierre committed
1063
        """
1064

VIGNET Pierre's avatar
VIGNET Pierre committed
1065
        if self != top_node:
1066
1067
1068
            (xfath, yfath) = self.father.virt_to_self_frame(xcoord,
                                                            ycoord,
                                                            s_width, s_height,
VIGNET Pierre's avatar
VIGNET Pierre committed
1069
1070
1071
1072
1073
1074
                                                            top_node)
            xloc = (xfath - self.xloc) / self.wloc
            yloc = (yfath - self.yloc) / self.hloc
            return (xloc, yloc)
        else: # top node
            return(xcoord, ycoord)
1075

VIGNET Pierre's avatar
VIGNET Pierre committed
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
    def self_to_virtual_frame(self, xcoord, ycoord, top_node):
        """
        Coordinate change
        """
        if self != top_node:
            xfath = self.xloc + xcoord * self.wloc
            yfath = self.yloc + ycoord * self.hloc
            return self.father.self_to_virtual_frame(xfath, yfath, top_node)
        else:
            return (xcoord, ycoord)
1086

VIGNET Pierre's avatar
VIGNET Pierre committed
1087
1088
1089
1090
    def v_affinity_coef(self, top_node):
        """
        Affinity ratios cw,ch: local_horizontal_length = screen_horizontal_length * cw
        Similar relation for vertical length.
1091
        @param  top_node: root of the sub_model
VIGNET Pierre's avatar
VIGNET Pierre committed
1092
1093
1094
1095
1096
        """
        if(self!= top_node):
            (acw, ach) = self.father.v_affinity_coef(top_node)
            return (acw / self.wloc, ach / self.hloc)
        else:
1097
1098
            return (1.0, 1.0)

VIGNET Pierre's avatar
VIGNET Pierre committed
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
    def add_macro_subnode(self, name, xcoord, ycoord, width, height):
        """
        Add a macro node as subnode with dimensions
        """
        node = CMacroNode(xcoord, ycoord, width, height, name, self.model)
        self.model.node_dict[name] = node
        node.father = self
        self.sub_nodes.append(node)
        self.model.modified = True
        self.model.notify()
        return node
1110

VIGNET Pierre's avatar
VIGNET Pierre committed
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
    def add_simple_node(self, name,  xcoord, ycoord):
        """
        Add a simple node
        """
        node = CSimpleNode(xcoord, ycoord, name, self.model)
        self.model.node_dict[name] = node
        node.father = self
        self.sub_nodes.append(node)
        self.model.simple_node_dict[name] = node
        self.model.modified = True
        self.model.notify()
        return node

    def add_input_node(self, name,  xcoord, ycoord):
        """
        Add input node
        """
        node = CInputNode(xcoord, ycoord, name, self.model)
        self.model.node_dict[name] = node
        node.father = self
        self.sub_nodes.append(node)
        self.model.modified = True
        self.model.notify()
        return node
1135

VIGNET Pierre's avatar
VIGNET Pierre committed
1136
1137
    def add_copy(self, node):
        """
1138
        add a node of the same type
VIGNET Pierre's avatar
VIGNET Pierre committed
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
        """
        nnode = node.copy(self.model)
        self.model.node_dict[nnode.name] = nnode
        nnode.father = self
        if nnode.is_simple():
            self.model.simple_node_dict[nnode.name] = nnode
        self.sub_nodes.append(nnode)
        self.model.modified = True
        self.model.notify()
        return nnode
1149

VIGNET Pierre's avatar
VIGNET Pierre committed
1150
1151
1152
    def add_transition(self, ori, targ):
        """
        Add a transition
1153

VIGNET Pierre's avatar
VIGNET Pierre committed
1154
        @param ori: a simple node
1155
        @param targ: a simple node
VIGNET Pierre's avatar
VIGNET Pierre committed
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
        """
        # transition between a node and itself are forbidden
        if ori == targ:
            return None

        # new code: at most one transition in each direction
        add = False
        for tlist in self.transitions:
            if len(tlist) == 1:
                trans = tlist[0]
                if (trans.ori ==  ori) and (trans.ext == targ):
                    return None
                if (trans.ori ==  targ) and (trans.ext == ori):
                    ntrans = CTransition(ori, targ)
                    ntrans.macro_node = self
                    tlist.append(ntrans)
                    add = True
            else: # len(tl) == 2
                trans = tlist[0]
                if (trans.ori ==  ori) and (trans.ext == targ):
                    return None
                trans = tlist[1]
                if (trans.ori ==  ori) and (trans.ext == targ):
                    return None
        if not add:
            ntrans = CTransition(ori, targ)
            ntrans.macro_node = self
            self.transitions.append([ntrans])
1184

VIGNET Pierre's avatar
VIGNET Pierre committed
1185
1186
1187
1188
1189
1190
1191
        if ntrans:
            ori.outgoing_trans.append(ntrans)
            targ.incoming_trans.append(ntrans)
            self.model.transition_list.append(ntrans)
            self.model.modified = True
            self.model.notify()
        return ntrans
1192

VIGNET Pierre's avatar
VIGNET Pierre committed
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
    def add_start_node(self, xcoord, ycoord, name = None):
        """
        Add a start node
        """
        nnode = CStartNode(xcoord, ycoord, self.model)
        self.model.node_dict[name] = nnode
        nnode.father = self
        if name:
            nnode.name = name
        else:
            # add a number to have different graphic start nodes
            nnode.name = nnode.name + "%s" % self.count
        self.count = self.count + 1
        self.sub_nodes.append(nnode)
        self.model.modified = True
        self.model.notify()
        return nnode
1210

VIGNET Pierre's avatar
VIGNET Pierre committed
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
    def add_trap_node(self, xcoord, ycoord, name = None):
        """
        Add a trap node
        """
        nnode = CTrapNode(xcoord, ycoord, self.model)
        self.model.node_dict[name] = nnode
        nnode.father = self
        if name:
            nnode.name = name
        else:
            # add a number to have different graphic trap nodes
1222
            nnode.name = nnode.name + "%s" % self.count
VIGNET Pierre's avatar
VIGNET Pierre committed
1223
1224
1225
1226
1227
        self.count = self.count + 1
        self.sub_nodes.append(nnode)
        self.model.modified = True
        self.model.notify()
        return nnode
1228

VIGNET Pierre's avatar
VIGNET Pierre committed
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
    def add_perm_node(self, name, xcoord, ycoord):
        """
        Add a perm node
        """
        nnode = CPermNode(xcoord, ycoord, name, self.model)
        self.model.node_dict[name] = nnode
        nnode.father = self
        self.sub_nodes.append(nnode)
        self.model.modified = True
        self.model.notify()
        return nnode

1241
1242


VIGNET Pierre's avatar
VIGNET Pierre committed
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
    def draw(self, view, xfr, yfr, wfr, hfr, depth):
        """
        depth is less than depth_max
        @param view: drawing area
        @param xfr: x coordinate of father in virtual screen
        @param yfr: y -
        @param wfr: father's width in virtual screen
        @param hfr: father's height in virtual
        @param depth: depth of the node
        """
1253
        dstyle = view.drawing_style
VIGNET Pierre's avatar
VIGNET Pierre committed
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
        # draw node
        dstyle.draw_macro(self, xfr, yfr, wfr, hfr)
        # draw sub graph
        if depth < CMacroNode.depth_max and self.model.show_macro:
            xxr = xfr + self.xloc * wfr
            yyr = yfr + self.yloc * hfr
            w_ratio = wfr * self.wloc
            h_ratio = hfr * self.hloc
            # edges
            for tgr in self.transitions:
1264
                dstyle.draw_transition_group(tgr, xxr, yyr, w_ratio, h_ratio)
VIGNET Pierre's avatar
VIGNET Pierre committed
1265
1266
1267
            # nodes
            for snode in self.sub_nodes:
                snode.draw(view, xxr, yyr, w_ratio, h_ratio, depth + 1)
1268
1269


VIGNET Pierre's avatar
VIGNET Pierre committed
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
    def move(self, v_dx, v_dy, v_size, top_node):
        """
        Move the node - mx_virt, my virt are virtual coordinates of the mouse
        click_loc (pair) is the location of the clic in node's frame
        """
        # don't try to move a root node
        if self == top_node:
            return
        # move in reference frame (father's)
        (ccx, ccy) = self.father.v_affinity_coef(top_node)
        loc_dx = ccx * v_dx
        loc_dy = ccy * v_dy
1282
        # translation vector in reference frame i.e. father's frame
VIGNET Pierre's avatar
VIGNET Pierre committed
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
        new_xloc = self.xloc + loc_dx
        new_yloc = self.yloc + loc_dy
        # check limits
        wloc = v_size[0] * ccx
        hloc = v_size[1] * ccy
        delta = v_size[2] * ccy

        if self.model.show_macro:
            wloc = self.wloc
            hloc = self.hloc
        else:
            wloc = v_size[0] * ccx
            hloc = v_size[1] * ccy
        move = False
        if (new_xloc >= 0) and (new_xloc + wloc < 1.0):
            self.xloc = new_xloc
            move = True
        if (new_yloc > delta) and (new_yloc + hloc < 1.0):
            self.yloc = new_yloc
            move = True
        if move:
1304
            self.model.modified = True
VIGNET Pierre's avatar
VIGNET Pierre committed
1305
            self.model.notify()
1306
1307


VIGNET Pierre's avatar
VIGNET Pierre committed
1308
1309
1310
    def find_element(self, mox, moy, dstyle, w_coef, h_coef):
        """
        @param mox, moy: mouse coordinate in container frame
1311
1312
        @param dstyle: drawing style
        @param w_coef, h_coef: affinity ratios for view -> container frame
VIGNET Pierre's avatar
VIGNET Pierre committed
1313
1314
1315
1316
1317
1318
1319
1320
        """
        # width and height of the node in container frame
        if self.model.show_macro:
            wloc = self.wloc
            hloc = self.hloc
        else:
            bb_size = self.accept(dstyle)
            wloc = bb_size[0] * w_coef
1321
1322
            hloc = bb_size[1] * h_coef
        in_node = (mox >= self.xloc) and (mox <= self.xloc + wloc)
VIGNET Pierre's avatar
VIGNET Pierre committed
1323
1324
1325
1326
1327
1328
        in_node = in_node and (moy >= self.yloc) and (moy <= self.yloc + hloc)
        if in_node:
            if not self.model.show_macro:
                ccx = self.xloc + 0.5 * wloc
                ccy = self.yloc + 0.5 * hloc
                return (self, 0, (ccx, ccy), None)
1329

VIGNET Pierre's avatar
VIGNET Pierre committed
1330
1331
1332
1333
1334
1335
1336
1337
1338
            # change coordinates and affinity ratio for self frame
            w_coefloc = w_coef / wloc
            h_coefloc = h_coef / hloc
            mxloc = (mox - self.xloc) / wloc
            myloc = (moy - self.yloc) / hloc
            # search sub node
            for snode in self.sub_nodes:
                (nnn, hhh, ccc, ttt) = snode.find_element(mxloc, myloc, dstyle,
                                               w_coefloc, h_coefloc)
1339
                if nnn:
VIGNET Pierre's avatar
VIGNET Pierre committed
1340
1341
1342
1343
                    # n center coordinates in container frame
                    ccx = self.xloc + ccc[0] * self.wloc
                    ccy = self.yloc + ccc[1] * self.hloc
                    return (nnn, hhh, (ccx, ccy), ttt)
1344

VIGNET Pierre's avatar
VIGNET Pierre committed
1345
1346
1347
1348

            # self center coordinate in container frame
            ccx = self.xloc + 0.5 * self.wloc
            ccy = self.yloc + 0.5 * self.hloc
1349
1350

            # not in a subnode, find a transition at this level
VIGNET Pierre's avatar
VIGNET Pierre committed
1351
1352
1353
1354
            trans = self.find_transition(mxloc, myloc, dstyle,
                                         w_coefloc, h_coefloc)
            if trans:
                return (self, 0, (ccx, ccy), trans)
1355

VIGNET Pierre's avatar
VIGNET Pierre committed
1356
1357
1358
1359
1360
            # not in a subnode or transition- find handler
            v_size = self.accept(dstyle)
            ddx = v_size[3]
            ddy = v_size[4]

1361
            if (mox < self.xloc + ddx) and (moy < self.yloc + ddy):
VIGNET Pierre's avatar
VIGNET Pierre committed
1362
                return (self, 1, (ccx, ccy), None)
1363
            if (mox > self.xloc + wloc - ddx) and (moy < self.yloc + ddy):
VIGNET Pierre's avatar
VIGNET Pierre committed
1364
                return (self, 2, (ccx, ccy), None)
1365
            cond = (mox > self.xloc + wloc - ddx)
VIGNET Pierre's avatar
VIGNET Pierre committed
1366
            cond = cond and (moy > self.yloc + hloc - ddy)