root/trunk/umit/gui/radialnet/RadialNet.py @ 4479

Revision 4479, 52.1 kB (checked in by getxsick, 4 years ago)

dict.keys() is no more needed for iterate. fix for #260

Line 
1# vim: set fileencoding=utf-8 :
2
3# Copyright (C) 2007 Adriano Monteiro Marques
4#
5# Author: João Paulo de Souza Medeiros <ignotus21@gmail.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21import gtk
22import math
23import time
24import copy
25import gobject
26
27
28from umit.core.radialnet.Coordinate import PolarCoordinate, CartesianCoordinate
29from umit.core.radialnet.Interpolation import Linear2DInterpolator
30from umit.core.radialnet.Graph import Graph, Node
31from umit.gui.radialnet.NodeWindow import NodeWindow
32from umit.gui.radialnet.Image import Icons
33
34
35REGION_COLORS = [(1.0, 0.0, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0)]
36REGION_RED    = 0
37REGION_YELLOW = 1
38REGION_GREEN  = 2
39
40SQUARE_TYPES = ['router', 'switch', 'wap']
41
42ICON_DICT = {'router':      'router',
43             'switch':      'switch',
44             'wap':         'wireless',
45             'firewall':    'firewall'}
46
47POINTER_JUMP_TO = 0
48POINTER_INFO    = 1
49POINTER_GROUP   = 2
50POINTER_FILL    = 3
51
52LAYOUT_SYMMETRIC = 0
53LAYOUT_WEIGHTED  = 1
54
55INTERPOLATION_CARTESIAN = 0
56INTERPOLATION_POLAR     = 1
57
58
59class RadialNet(gtk.DrawingArea):
60    """
61    Radial network visualization widget
62    """
63    def __init__(self, layout=LAYOUT_SYMMETRIC):
64        """
65        Constructor method of RadialNet widget class
66        @type  number_of_rings: number
67        @param number_of_rings: Number of rings in radial layout
68        """
69        self.__center_of_widget = (0, 0)
70        self.__graph = None
71
72        self.__number_of_rings = 0
73        self.__ring_gap = 30
74        self.__min_ring_gap = 10
75
76        self.__layout = layout
77        self.__interpolation = INTERPOLATION_POLAR
78        self.__interpolation_slow_in_out = True
79
80        self.__animating = False
81        self.__animation_rate = 1000 / 60 # 60Hz (human perception factor)
82        self.__number_of_frames = 60
83
84        self.__scale = 1.0
85        self.__rotate = 225
86        self.__translation = (0, 0)
87
88        self.__button1_press = False
89        self.__button2_press = False
90        self.__button3_press = False
91
92        self.__last_motion_point = None
93
94        self.__fisheye = False
95        self.__fisheye_ring = 0
96        self.__fisheye_spread = 0.5
97        self.__fisheye_interest = 2
98
99        self.__show_address = True
100        self.__show_hostname = True
101        self.__show_icon = True
102        self.__show_latency = False
103        self.__show_ring = True
104        self.__show_region = True
105        self.__region_color = REGION_RED
106
107        self.__node_views = dict()
108        self.__last_group_node = None
109
110        self.__pointer_status = POINTER_JUMP_TO
111
112        self.__sorted_nodes = list()
113        self.__reverse_sorted_nodes = list()
114
115        self.__icon = Icons()
116
117        super(RadialNet, self).__init__()
118
119        self.connect('expose_event', self.expose)
120        self.connect('button_press_event', self.button_press)
121        self.connect('button_release_event', self.button_release)
122        self.connect('motion_notify_event', self.motion_notify)
123        self.connect('enter_notify_event', self.enter_notify)
124        self.connect('leave_notify_event', self.leave_notify)
125        self.connect('key_press_event', self.key_press)
126        self.connect('key_release_event', self.key_release)
127        self.connect('scroll_event', self.scroll_event)
128
129        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
130                        gtk.gdk.BUTTON_RELEASE_MASK |
131                        gtk.gdk.ENTER_NOTIFY |
132                        gtk.gdk.LEAVE_NOTIFY |
133                        gtk.gdk.MOTION_NOTIFY |
134                        gtk.gdk.NOTHING |
135                        gtk.gdk.KEY_PRESS_MASK |
136                        gtk.gdk.KEY_RELEASE_MASK |
137                        gtk.gdk.POINTER_MOTION_HINT_MASK |
138                        gtk.gdk.POINTER_MOTION_MASK |
139                        gtk.gdk.SCROLL_MASK)
140
141        self.set_flags(gtk.CAN_FOCUS)
142        self.grab_focus()
143
144
145    def graph_is_not_empty(function):
146        """
147        Decorator function to prevent the execution when graph not is set
148        @type  function: function
149        @param function: Protected function
150        """
151        def check_graph_status(*args):
152            if args[0].__graph is None:
153                return False
154            return function(*args)
155
156        return check_graph_status
157
158
159    def not_is_in_animation(function):
160        """
161        Decorator function to prevent the execution when graph is animating
162        @type  function: function
163        @param function: Protected function
164        """
165        def check_animation_status(*args):
166            if args[0].__animating == True:
167                return False
168            return function(*args)
169
170        return check_animation_status
171
172
173    def get_slow_inout(self):
174        """
175        """
176        return self.__interpolation_slow_in_out
177
178
179    def set_slow_inout(self, value):
180        """
181        """
182        self.__interpolation_slow_in_out = value
183
184
185    def get_region_color(self):
186        """
187        """
188        return self.__region_color
189
190
191    def set_region_color(self, value):
192        """
193        """
194        self.__region_color = value
195
196
197    def get_show_region(self):
198        """
199        """
200        return self.__show_region
201
202
203    def set_show_region(self, value):
204        """
205        """
206        self.__show_region = value
207        self.queue_draw()
208
209
210    def get_pointer_status(self):
211        """
212        """
213        return self.__pointer_status
214
215
216    def set_pointer_status(self, pointer_status):
217        """
218        """
219        self.__pointer_status = pointer_status
220
221
222    def get_show_address(self):
223        """
224        """
225        return self.__show_address
226
227
228    def get_show_hostname(self):
229        """
230        """
231        return self.__show_hostname
232
233
234    def get_show_ring(self):
235        """
236        """
237        return self.__show_ring
238
239
240    def set_show_address(self, value):
241        """
242        """
243        self.__show_address = value
244        self.queue_draw()
245
246
247    def set_show_hostname(self, value):
248        """
249        """
250        self.__show_hostname = value
251        self.queue_draw()
252
253
254    def set_show_ring(self, value):
255        """
256        """
257        self.__show_ring = value
258        self.queue_draw()
259
260
261    def get_min_ring_gap(self):
262        """
263        """
264        return self.__min_ring_gap
265
266
267    @graph_is_not_empty
268    @not_is_in_animation
269    def set_min_ring_gap(self, value):
270        """
271        """
272        self.__min_ring_gap = int(value)
273
274        if self.__ring_gap < self.__min_ring_gap:
275            self.__ring_gap = self.__min_ring_gap
276
277        self.__update_nodes_positions()
278        self.queue_draw()
279
280        return True
281
282
283    def get_number_of_frames(self):
284        """
285        """
286        return self.__number_of_frames
287
288
289    @not_is_in_animation
290    def set_number_of_frames(self, number_of_frames):
291        """
292        """
293        if number_of_frames > 2:
294
295            self.__number_of_frames = int(number_of_frames)
296            return True
297
298        self.__number_of_frames = 3
299        return False
300
301
302    @not_is_in_animation
303    def update_layout(self):
304        """
305        """
306        self.__animating = True
307        self.__calc_interpolation(self.__graph.get_main_node())
308        self.__livens_up()
309
310
311    @not_is_in_animation
312    def set_layout(self, layout):
313        """
314        """
315        if self.__layout != layout:
316
317            self.__layout = layout
318
319            if self.__graph is not None:
320
321                self.__animating = True
322                self.__calc_interpolation(self.__graph.get_main_node())
323                self.__livens_up()
324
325            return True
326
327        return False
328
329
330    def get_layout(self):
331        """
332        """
333        return self.__layout
334
335
336    @not_is_in_animation
337    def set_interpolation(self, interpolation):
338        """
339        """
340        self.__interpolation = interpolation
341
342        return True
343
344
345    def get_interpolation(self):
346        """
347        """
348        return self.__interpolation
349
350
351    def get_number_of_rings(self):
352        """
353        """
354        return self.__number_of_rings
355
356
357    def get_fisheye_ring(self):
358        """
359        """
360        return self.__fisheye_ring
361
362
363    def get_fisheye_interest(self):
364        """
365        """
366        return self.__fisheye_interest
367
368
369    def get_fisheye_spread(self):
370        """
371        """
372        return self.__fisheye_spread
373
374
375    def get_fisheye(self):
376        """
377        """
378        return self.__fisheye
379
380
381    def set_fisheye(self, enable):
382        """
383        """
384        self.__fisheye = enable
385
386        self.__update_nodes_positions()
387        self.queue_draw()
388
389
390    def set_fisheye_ring(self, value):
391        """
392        """
393        self.__fisheye_ring = value
394        self.__check_fisheye_ring()
395
396        self.__update_nodes_positions()
397        self.queue_draw()
398
399
400    def set_fisheye_interest(self, value):
401        """
402        """
403        self.__fisheye_interest = value
404
405        self.__update_nodes_positions()
406        self.queue_draw()
407
408
409    def set_fisheye_spread(self, value):
410        """
411        """
412        self.__fisheye_spread = value
413
414        self.__update_nodes_positions()
415        self.queue_draw()
416
417
418    def get_show_icon(self):
419        """
420        """
421        return self.__show_icon
422
423
424    def set_show_icon(self, value):
425        """
426        """
427        self.__show_icon = value
428        self.queue_draw()
429
430
431    def get_show_latency(self):
432        """
433        """
434        return self.__show_latency
435
436
437    def set_show_latency(self, value):
438        """
439        """
440        self.__show_latency = value
441        self.queue_draw()
442
443
444    def get_scale(self):
445        """
446        """
447        return self.__scale
448
449
450    def get_zoom(self):
451        """
452        """
453        return int(round(self.__scale * 100))
454
455
456    def set_scale(self, scale):
457        """
458        """
459        if scale >= 0.01:
460
461            self.__scale = scale
462            self.queue_draw()
463
464
465    def set_zoom(self, zoom):
466        """
467        """
468        if float(zoom) >= 1:
469
470            self.set_scale( float(zoom) / 100.0 )
471            self.queue_draw()
472
473
474    def get_ring_gap(self):
475        """
476        """
477        return self.__ring_gap
478
479
480    @not_is_in_animation
481    def set_ring_gap(self, ring_gap):
482        """
483        """
484        if ring_gap >= self.__min_ring_gap:
485
486            self.__ring_gap = ring_gap
487            self.__update_nodes_positions()
488            self.queue_draw()
489
490
491    def scroll_event(self, widget, event):
492        """
493        """
494        if event.direction == gtk.gdk.SCROLL_UP:
495            self.set_scale(self.__scale + 0.01)
496
497        if event.direction == gtk.gdk.SCROLL_DOWN:
498            self.set_scale(self.__scale - 0.01)
499
500        self.queue_draw()
501
502
503    @graph_is_not_empty
504    @not_is_in_animation
505    def key_press(self, widget, event):
506        """
507        """
508        key = gtk.gdk.keyval_name(event.keyval)
509
510        if key == 'KP_Add':
511            self.set_ring_gap(self.__ring_gap + 1)
512
513        elif key == 'KP_Subtract':
514            self.set_ring_gap(self.__ring_gap - 1)
515
516        elif key == 'Page_Up':
517            self.set_scale(self.__scale + 0.01)
518
519        elif key == 'Page_Down':
520            self.set_scale(self.__scale - 0.01)
521
522        self.queue_draw()
523
524        return True
525
526
527    @graph_is_not_empty
528    def key_release(self, widget, event):
529        """
530        """
531        key = gtk.gdk.keyval_name(event.keyval)
532
533        if key == 'c':
534            self.__translation = (0, 0)
535
536        elif key == 'r':
537            self.__show_ring = not self.__show_ring
538
539        elif key == 'a':
540            self.__show_address = not self.__show_address
541
542        elif key == 'h':
543            self.__show_hostname = not self.__show_hostname
544
545        elif key == 'i':
546            self.__show_icon = not self.__show_icon
547
548        elif key == 'l':
549            self.__show_latency = not self.__show_latency
550
551        self.queue_draw()
552
553        return True
554
555
556    @graph_is_not_empty
557    @not_is_in_animation
558    def enter_notify(self, widget, event):
559        """
560        """
561        self.grab_focus()
562        return False
563
564
565    @graph_is_not_empty
566    @not_is_in_animation
567    def leave_notify(self, widget, event):
568        """
569        """
570        for node in self.__graph.get_nodes():
571            node.set_draw_info({'over':False})
572
573        self.queue_draw()
574
575        return False
576
577
578    @graph_is_not_empty
579    def button_press(self, widget, event):
580        """
581        Drawing callback
582        @type  widget: GtkWidget
583        @param widget: Gtk widget superclass
584        @type  event: GtkEvent
585        @param event: Gtk event of widget
586        @rtype: boolean
587        @return: Indicator of the event propagation
588        """
589        result = self.__get_node_by_coordinate(self.get_pointer())
590
591        if event.button == 1: self.__button1_press = True
592
593        # animate if node is pressed
594        if self.__pointer_status == POINTER_JUMP_TO and event.button == 1:
595
596            # prevent double animation
597            if self.__animating == True: return False
598
599            if result is not None:
600
601                node, point = result
602                main_node = self.__graph.get_main_node()
603
604                if node != main_node:
605
606                    if node.get_draw_info('group') == True:
607
608                        node.set_draw_info({'group':False})
609                        node.set_subtree_info({'grouped':False,
610                                               'group_node':None})
611
612                    self.__animating = True
613                    self.__calc_interpolation(node)
614                    self.__livens_up()
615
616        # group node if it's pressed
617        elif self.__pointer_status == POINTER_GROUP and event.button == 1:
618
619            # prevent group on animation
620            if self.__animating == True: return False
621
622            if result is not None:
623
624                node, point = result
625                main_node = self.__graph.get_main_node()
626
627                if node != main_node:
628
629                    if node.get_draw_info('group') == True:
630
631                        node.set_draw_info({'group':False})
632                        node.set_subtree_info({'grouped':False,
633                                               'group_node':None})
634
635                    else:
636
637                        self.__last_group_node = node
638
639                        node.set_draw_info({'group':True})
640                        node.set_subtree_info({'grouped':True,
641                                               'group_node':node})
642
643                self.__animating = True
644                self.__calc_interpolation(self.__graph.get_main_node())
645                self.__livens_up()
646
647        # setting to show node's region
648        elif self.__pointer_status == POINTER_FILL and event.button == 1:
649
650            if result is not None:
651
652                node, point = result
653
654                if node.get_draw_info('region') == self.__region_color:
655                    node.set_draw_info({'region': None})
656
657                else:
658                    node.set_draw_info({'region': self.__region_color})
659
660                self.queue_draw()
661
662        # show node details
663        elif event.button == 3 or self.__pointer_status == POINTER_INFO:
664
665            if event.button == 3:
666                self.__button3_press = True
667
668            if result is not None:
669
670                xw, yw = self.window.get_origin()
671                node, point = result
672                x, y = point
673
674                if node in self.__node_views:
675
676                    self.__node_views[node].restore(int(xw + x), int(yw + y))
677
678                elif node.get_info('scanned'):
679
680                    view = NodeWindow(node, (int(xw + x), int(yw + y)), self)
681                    view.show_all()
682                    self.__node_views[node] = view
683
684        return False
685
686
687    @graph_is_not_empty
688    def button_release(self, widget, event):
689        """
690        Drawing callback
691        @type  widget: GtkWidget
692        @param widget: Gtk widget superclass
693        @type  event: GtkEvent
694        @param event: Gtk event of widget
695        @rtype: boolean
696        @return: Indicator of the event propagation
697        """
698        if event.button == 1:
699            self.__button1_press = False
700
701        if event.button == 2:
702            self.__button2_press = False
703
704        if event.button == 3:
705            self.__button3_press = False
706
707        self.grab_focus()
708
709        return False
710
711
712    @graph_is_not_empty
713    def motion_notify(self, widget, event):
714        """
715        Drawing callback
716        @type  widget: GtkWidget
717        @param widget: Gtk widget superclass
718        @type  event: GtkEvent
719        @param event: Gtk event of widget
720        @rtype: boolean
721        @return: Indicator of the event propagation
722        """
723        xc, yc = self.__center_of_widget
724        pointer = self.get_pointer()
725
726        for node in self.__graph.get_nodes():
727            node.set_draw_info({'over':False})
728
729        result = self.__get_node_by_coordinate(self.get_pointer())
730
731        if result is not None:
732            result[0].set_draw_info({'over':True})
733
734        elif self.__button1_press == True and \
735                self.__last_motion_point is not None:
736
737            ax, ay = pointer
738            ox, oy = self.__last_motion_point
739            tx, ty = self.__translation
740
741            self.__translation = (tx + ax - ox, ty - ay + oy)
742
743        self.__last_motion_point = pointer
744
745        self.grab_focus()
746        self.queue_draw()
747       
748        return False
749
750
751    def expose(self, widget, event):
752        """
753        Drawing callback
754        @type  widget: GtkWidget
755        @param widget: Gtk widget superclass
756        @type  event: GtkEvent
757        @param event: Gtk event of widget
758        @rtype: boolean
759        @return: Indicator of the event propagation
760        """
761        self.context = widget.window.cairo_create()
762
763        self.context.rectangle(*event.area)
764        self.context.set_source_rgb(1.0, 1.0, 1.0)
765        self.context.fill()
766
767        self.__draw()
768
769        return False
770
771
772    @graph_is_not_empty
773    def __draw(self):
774        """
775        Drawing method
776        """
777        # getting allocation reference
778        allocation = self.get_allocation()
779
780        self.__center_of_widget = (allocation.width / 2,
781                                   allocation.height / 2)
782
783        aw, ah = allocation.width, allocation.height
784        xc, yc = self.__center_of_widget
785
786        ax, ay = self.__translation
787
788        # xc = 320 yc = 240
789
790        # -1.5 | -0.5 ( 480,  360)
791        # -1.0 |  0.0 ( 320,  240)
792        # -0.5 |  0.5 ( 160,  120)
793        #  0.0 |  1.0 (   0,    0)
794        #  0.5 |  1.5 (-160, -120)
795        #  1.0 |  2.0 (-320, -240)
796        #  1.5 |  2.5 (-480, -360)
797
798        # scaling and translate
799        factor = -(self.__scale - 1)
800
801        self.context.translate(xc * factor + ax, yc * factor - ay)
802
803        if self.__scale != 1.0:
804            self.context.scale(self.__scale, self.__scale)
805
806        # drawing over node's region
807        if self.__show_region and not self.__animating:
808
809            for node in self.__sorted_nodes:
810
811                not_grouped = not node.get_draw_info('grouped')
812
813                if node.get_draw_info('region') is not None and not_grouped:
814
815                    x, y = node.get_cartesian_coordinate()
816                    xc, yc = self.__center_of_widget
817                    r, g, b = REGION_COLORS[node.get_draw_info('region')]
818
819                    start, final = node.get_draw_info('range')
820
821                    i_radius = node.get_coordinate_radius()
822                    f_radius = self.__calc_radius(self.__number_of_rings - 1)
823
824                    is_fill_all = abs(final - start) == 360
825
826                    final = math.radians(final + self.__rotate)
827                    start = math.radians(start + self.__rotate)
828
829                    self.context.move_to(xc, yc)
830                    self.context.set_source_rgba(r, g, b, 0.1)
831                    self.context.new_path()
832                    self.context.arc(xc, yc, i_radius, -final, -start)
833                    self.context.arc_negative(xc, yc, f_radius, -start, -final)
834                    self.context.close_path()
835                    self.context.fill()
836                    self.context.stroke()
837
838                    if not is_fill_all:
839
840                        self.context.set_source_rgb(r, g, b)
841                        self.context.set_line_width(1)
842
843                        xa, ya = PolarCoordinate(i_radius, final).to_cartesian()
844                        xb, yb = PolarCoordinate(f_radius, final).to_cartesian()
845
846                        self.context.move_to(xc + xa, yc - ya)
847                        self.context.line_to(xc + xb, yc - yb)
848                        self.context.stroke()
849
850                        xa, ya = PolarCoordinate(i_radius, start).to_cartesian()
851                        xb, yb = PolarCoordinate(f_radius, start).to_cartesian()
852
853                        self.context.move_to(xc + xa, yc - ya)
854                        self.context.line_to(xc + xb, yc - yb)
855                        self.context.stroke()
856
857        # drawing network rings
858        if self.__show_ring == True and self.__animating != True:
859
860            for i in range(1, self.__number_of_rings):
861
862                radius = self.__calc_radius(i)
863
864                self.context.arc(xc, yc, radius, 0, 2 * math.pi)
865                self.context.set_source_rgb(0.8, 0.8, 0.8)
866                self.context.set_line_width(1)
867                self.context.stroke()
868
869        # drawing nodes and your connections
870        for edge in self.__graph.get_edges():
871
872            # check group constraints for edges
873            a, b = edge.get_nodes()
874
875            a_is_grouped = a.get_draw_info('grouped')
876            b_is_grouped = b.get_draw_info('grouped')
877
878            a_is_group = a.get_draw_info('group')
879            b_is_group = b.get_draw_info('group')
880
881            a_group = a.get_draw_info('group_node')
882            b_group = b.get_draw_info('group_node')
883
884            a_is_child = a in b.get_draw_info('children')
885            b_is_child = b in a.get_draw_info('children')
886
887            last_group = self.__last_group_node
888            groups = [a_group, b_group]
889
890            if last_group in groups and last_group is not None:
891                self.__draw_edge(edge)
892
893            elif not a_is_grouped or not b_is_grouped:
894           
895                if not (a_is_group and b_is_child or b_is_group and a_is_child):
896                    self.__draw_edge(edge)
897
898            elif a_group != b_group:
899                self.__draw_edge(edge)
900
901        for node in self.__reverse_sorted_nodes:
902
903            # check group constraints for nodes
904            group = node.get_draw_info('group_node')
905            grouped = node.get_draw_info('grouped')
906
907            if group == self.__last_group_node or not grouped:
908                self.__draw_node(node)
909
910
911    def __draw_edge(self, edge):
912        """
913        Draw the connection between two nodes
914        @type  : Edge
915        @param : The second node that will be connected
916        """
917        a, b = edge.get_nodes()
918
919        xa, ya = a.get_cartesian_coordinate()
920        xb, yb = b.get_cartesian_coordinate()
921        xc, yc = self.__center_of_widget
922
923        a_children = a.get_draw_info('children')
924        b_children = b.get_draw_info('children')
925
926        latency = edge.get_weigths_mean()
927
928        # check if isn't an hierarchy connection
929        if a not in b_children and b not in a_children:
930            self.context.set_source_rgba(1.0, 0.6, 0.1, 0.8)
931
932        elif a.get_draw_info('no_route') or b.get_draw_info('no_route'):
933            self.context.set_source_rgba(0.0, 0.0, 0.0, 0.8)
934
935        else:
936            self.context.set_source_rgba(0.1, 0.5, 1.0, 0.8)
937
938        # calculating line thickness by latency
939        if latency is not None:
940
941            min = self.__graph.get_min_edge_mean_weight()
942            max = self.__graph.get_max_edge_mean_weight()
943
944            if max != min:
945                thickness = (latency - min) * 4 / (max - min) + 1
946
947            else:
948                thickness = 1
949
950            self.context.set_line_width(thickness)
951
952        else:
953
954            self.context.set_dash([2, 2])
955            self.context.set_line_width(1)
956
957        self.context.move_to(xc + xa, yc - ya)
958        self.context.line_to(xc + xb, yc - yb)
959        self.context.stroke()
960
961        self.context.set_dash([1, 0])
962
963        if not self.__animating and self.__show_latency:
964
965            if latency is not None:
966
967                self.context.set_font_size(8)
968                self.context.set_line_width(1)
969                self.context.move_to(xc + (xa + xb) / 2 + 1,
970                                     yc - (ya + yb) / 2 + 4)
971                self.context.show_text(str(round(latency, 2)))
972                self.context.stroke()
973
974
975    def __draw_node(self, node):
976        """
977        Draw nodes and your informations
978        @type  : NetNode
979        @param : The node will be draw
980        """
981        x, y = node.get_cartesian_coordinate()
982        xc, yc = self.__center_of_widget
983        r, g, b = node.get_draw_info('color')
984        radius = node.get_draw_info('radius')
985
986        type = node.get_info('device_type')
987
988        x_gap = radius + 2
989        y_gap = 0
990
991        # draw group indication
992        if node.get_draw_info('group') == True:
993
994            x_gap += 5
995
996            if type in SQUARE_TYPES:
997                self.context.rectangle(xc + x - radius - 5,
998                                       yc - y - radius - 5,
999                                       2 * radius + 10,
1000                                       2 * radius + 10)
1001
1002            else:
1003                self.context.arc(xc + x, yc - y, radius + 5, 0, 2 * math.pi)
1004
1005            self.context.set_source_rgb(1.0, 1.0, 1.0)
1006            self.context.fill_preserve()
1007
1008            if node.deep_search_child(self.__graph.get_node_by_id(0)):
1009                self.context.set_source_rgb(0.0, 0.0, 0.0)
1010
1011            else:
1012                self.context.set_source_rgb(0.1, 0.5, 1.0)
1013
1014            self.context.set_line_width(2)
1015            self.context.stroke()
1016
1017        # draw over node
1018        if node.get_draw_info('over') == True:
1019
1020            self.context.set_line_width(0)
1021
1022            if type in SQUARE_TYPES:
1023                self.context.rectangle(xc + x - radius - 5,
1024                                       yc - y - radius - 5,
1025                                       2 * radius + 10,
1026                                       2 * radius + 10)
1027
1028            else:
1029                self.context.arc(xc + x, yc - y, radius + 5, 0, 2 * math.pi)
1030
1031            self.context.set_source_rgb(0.1, 0.5, 1.0)
1032            self.context.fill_preserve()
1033            self.context.stroke()
1034
1035        # draw node
1036        if type in SQUARE_TYPES:
1037            self.context.rectangle(xc + x - radius,
1038                                   yc - y - radius,
1039                                   2 * radius,
1040                                   2 * radius)
1041
1042        else:
1043            self.context.arc(xc + x, yc - y, radius, 0, 2 * math.pi)
1044
1045        # draw icons
1046        if not self.__animating and self.__show_icon:
1047
1048            icons = list()
1049
1050            if type in ICON_DICT:
1051                icons.append(self.__icon.get_pixbuf(ICON_DICT[type]))
1052
1053            if node.get_info('filtered'):
1054                icons.append(self.__icon.get_pixbuf('padlock'))
1055
1056            for icon in icons:
1057
1058                self.context.set_source_pixbuf(icon,
1059                                               round(xc + x + x_gap),
1060                                               round(yc - y + y_gap - 6))
1061                self.context.paint()
1062
1063                x_gap += 13
1064
1065        # draw node text
1066        self.context.set_source_rgb(r, g, b)
1067        self.context.fill_preserve()
1068
1069        if node.get_draw_info('valid') or node.get_id() == 0:
1070            self.context.set_source_rgb(0.0, 0.0, 0.0)
1071
1072        else:
1073            self.context.set_source_rgb(0.1, 0.5, 1.0)
1074
1075        if not self.__animating and self.__show_address:
1076
1077            self.context.set_font_size(8)
1078            self.context.move_to(round(xc + x + x_gap),
1079                                 round(yc - y + y_gap + 4))
1080
1081            hostname = node.get_info('hostname')
1082
1083            if hostname is not None and self.__show_hostname:
1084                self.context.show_text(hostname)
1085
1086            elif node.get_info('ip') is not None:
1087                self.context.show_text(node.get_info('ip'))
1088
1089            else:
1090                self.context.show_text('')
1091
1092        self.context.set_line_width(1)
1093        self.context.stroke()
1094
1095
1096    def __check_fisheye_ring(self):
1097        """
1098        """
1099        if self.__fisheye_ring >= self.__number_of_rings:
1100            self.__fisheye_ring = self.__number_of_rings - 1
1101
1102
1103    def __set_number_of_rings(self, value):
1104        """
1105        """
1106        self.__number_of_rings = value
1107        self.__check_fisheye_ring()
1108
1109
1110    def __fisheye_function(self, ring):
1111        """
1112        """
1113        distance = abs(self.__fisheye_ring - ring)
1114        level_of_detail = self.__ring_gap * self.__fisheye_interest
1115        spreaded_distance = distance - distance * self.__fisheye_spread
1116
1117        value = level_of_detail / (spreaded_distance + 1)
1118
1119        if value < self.__min_ring_gap:
1120            value = self.__min_ring_gap
1121
1122        return value
1123
1124
1125    @graph_is_not_empty
1126    @not_is_in_animation
1127    def __update_nodes_positions(self):
1128        """
1129        """
1130        for node in self.__sorted_nodes:
1131
1132            if node.get_draw_info('grouped') == True:
1133
1134                # deep group check
1135                group = node.get_draw_info('group_node')
1136
1137                while group.get_draw_info('group_node') is not None:
1138                    group = group.get_draw_info('group_node')
1139
1140                ring = group.get_draw_info('ring')
1141                node.set_coordinate_radius(self.__calc_radius(ring))
1142
1143            else:
1144                ring = node.get_draw_info('ring')
1145                node.set_coordinate_radius(self.__calc_radius(ring))
1146
1147
1148    @graph_is_not_empty
1149    def __get_node_by_coordinate(self, point):
1150        """
1151        """
1152        xc, yc = self.__center_of_widget
1153
1154        for node in self.__graph.get_nodes():
1155
1156            if node.get_draw_info('grouped') == True:
1157                continue
1158
1159            ax, ay = self.__translation
1160       
1161            xn, yn = node.get_cartesian_coordinate()
1162            center = (xc + xn * self.__scale + ax, yc - yn * self.__scale - ay)
1163            radius = node.get_draw_info('radius') * self.__scale
1164
1165            type = node.get_info('device_type')
1166
1167            if type in SQUARE_TYPES:
1168                if RadialNet.is_in_square(point, radius, center) == True:
1169                    return node, center
1170
1171            else:
1172                if RadialNet.is_in_circle(point, radius, center) == True:
1173                    return node, center
1174
1175        return None
1176
1177
1178    def __calc_radius(self, ring):
1179        """
1180        """
1181        if self.__fisheye:
1182
1183            radius = 0
1184
1185            while ring > 0:
1186
1187                radius += self.__fisheye_function(ring)
1188                ring -= 1
1189
1190        else:
1191            radius = ring * self.__ring_gap
1192
1193        return radius
1194
1195   
1196    @graph_is_not_empty
1197    def __arrange_nodes(self):
1198        """
1199        """
1200        new_nodes = [self.__graph.get_main_node()]
1201        old_nodes = list()
1202
1203        number_of_needed_rings = 1
1204        ring = 0
1205
1206        # while new nodes were found
1207        while len(new_nodes) > 0:
1208
1209            tmp_nodes = list()
1210
1211            # for each new nodes
1212            for node in new_nodes:
1213
1214                old_nodes.append(node)
1215
1216                # set ring location
1217                node.set_draw_info({'ring':ring})
1218
1219                # check group constraints
1220                if node.get_draw_info('group') or node.get_draw_info('grouped'):
1221                    children = node.get_draw_info('children')
1222
1223                else:
1224
1225                    # getting connections and fixing multiple fathers
1226                    children = self.__graph.get_node_connections(node)
1227
1228                    RadialNet.list_difference_update(children, old_nodes)
1229                    RadialNet.list_difference_update(children, tmp_nodes)
1230                    RadialNet.list_difference_update(children, new_nodes)
1231
1232                    # dropping foreign children
1233                    foreign_children = list()
1234
1235                    for child in children:
1236
1237                        if child.get_draw_info('grouped'):
1238                            foreign_children.append(child)
1239
1240                    RadialNet.list_difference_update(children, foreign_children)
1241
1242                    children = RadialNet.sort_children(children, node)
1243
1244                # setting father foreign
1245                for child in children:
1246                    child.set_draw_info({'father':node})
1247
1248                node.set_draw_info({'children':children})
1249                RadialNet.list_update(tmp_nodes, children)
1250
1251            # check group influence in number of rings
1252            for node in tmp_nodes:
1253
1254                if node.get_draw_info('grouped') != True:
1255
1256                    number_of_needed_rings += 1
1257                    break
1258
1259            # update new nodes set
1260            RadialNet.list_update(new_nodes, tmp_nodes)
1261            RadialNet.list_difference_update(new_nodes, old_nodes)
1262
1263            ring += 1
1264
1265        self.__set_number_of_rings(number_of_needed_rings)
1266
1267
1268    def __weighted_layout(self):
1269        """
1270        """
1271        # calculating the space needed by each node
1272        self.__graph.get_main_node().set_draw_info({'range':(0, 360)})
1273        new_nodes = [self.__graph.get_main_node()]
1274
1275        self.__graph.get_main_node().calc_needed_space()
1276
1277        while len(new_nodes) > 0:
1278
1279            tmp_nodes = list()
1280
1281            for node in new_nodes:
1282
1283                # add only no grouped nodes
1284                children = list()
1285
1286                for child in node.get_draw_info('children'):
1287
1288                    if child.get_draw_info('grouped') != True:
1289                        children.append(child)
1290
1291                if len(children) > 0:
1292
1293                    min, max = node.get_draw_info('range')
1294
1295                    node_total = max - min
1296                    children_need = node.get_draw_info('children_need')
1297
1298                    for child in children:
1299
1300                        child_need = child.get_draw_info('space_need')
1301                        child_total = node_total * child_need / children_need
1302
1303                        theta = child_total / 2 + min + self.__rotate
1304
1305                        child.set_coordinate_theta(theta)
1306                        child.set_draw_info({'range':(min, min + child_total)})
1307
1308                        min += child_total
1309
1310                RadialNet.list_update(tmp_nodes, children)
1311
1312            new_nodes = list()
1313            RadialNet.list_update(new_nodes, tmp_nodes)
1314
1315
1316    def __symmetric_layout(self):
1317        """
1318        """
1319        self.__graph.get_main_node().set_draw_info({'range':(0, 360)})
1320        new_nodes = [self.__graph.get_main_node()]
1321
1322        while len(new_nodes) > 0:
1323
1324            tmp_nodes = list()
1325
1326            for node in new_nodes:
1327
1328                # add only no grouped nodes
1329                children = list()
1330
1331                for child in node.get_draw_info('children'):
1332
1333                    if child.get_draw_info('grouped') != True:
1334                        children.append(child)
1335
1336                if len(children) > 0:
1337
1338                    min, max = node.get_draw_info('range')
1339                    factor = float(max - min) / len(children)
1340
1341                    for child in children:
1342
1343                        theta = factor / 2 + min + self.__rotate
1344
1345                        child.set_coordinate_theta(theta)
1346                        child.set_draw_info({'range':(min, min + factor)})
1347
1348                        min += factor
1349
1350                RadialNet.list_update(tmp_nodes, children)
1351
1352            new_nodes = list()
1353            RadialNet.list_update(new_nodes, tmp_nodes)
1354
1355
1356    @graph_is_not_empty
1357    def __calc_layout(self, reference):
1358        """
1359        """
1360        # selecting layout algorithm
1361        if self.__layout == LAYOUT_SYMMETRIC:
1362            self.__symmetric_layout()
1363
1364        elif self.__layout == LAYOUT_WEIGHTED:
1365            self.__weighted_layout()
1366
1367        # rotating focus' children to keep orientation
1368        if reference is not None:
1369
1370            father, angle = reference
1371            theta = father.get_coordinate_theta()
1372            factor = theta - angle
1373
1374            for node in self.__graph.get_nodes():
1375
1376                theta = node.get_coordinate_theta()
1377                node.set_coordinate_theta(theta - factor)
1378
1379                a, b = node.get_draw_info('range')
1380                node.set_draw_info({'range':(a - factor, b - factor)})
1381
1382
1383    @graph_is_not_empty
1384    def __calc_node_positions(self, reference=None):
1385        """
1386        """
1387        # set nodes' hierarchy
1388        self.__arrange_nodes()
1389        self.calc_sorted_nodes()
1390
1391        # set nodes' coordinate radius
1392        for node in self.__graph.get_nodes():
1393
1394            ring = node.get_draw_info('ring')
1395            node.set_coordinate_radius(self.__calc_radius(ring))
1396
1397        # set nodes' coordinate theta
1398        self.__calc_layout(reference)
1399
1400
1401    def __calc_interpolation(self, focus):
1402        """
1403        """
1404        old_main_node = self.__graph.get_main_node()
1405        self.__graph.set_main_node(focus)
1406
1407        # getting initial coordinates
1408        for node in self.__graph.get_nodes():
1409
1410            if self.__interpolation == INTERPOLATION_POLAR:
1411                coordinate = node.get_polar_coordinate()
1412
1413            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1414                coordinate = node.get_cartesian_coordinate()
1415
1416            node.set_draw_info({'start_coordinate':coordinate})
1417
1418        father = focus.get_draw_info('father')
1419
1420        # calculate nodes positions (and father orientation)?
1421        if father is not None:
1422
1423            xa, ya = father.get_cartesian_coordinate()
1424            xb, yb = focus.get_cartesian_coordinate()
1425
1426            angle = math.atan2(yb - ya, xb - xa)
1427            angle = math.degrees(angle)
1428
1429            self.__calc_node_positions((father, 180 + angle))
1430
1431        else:
1432            self.__calc_node_positions()
1433
1434        # steps for slow-in/slow-out animation
1435        steps = range(self.__number_of_frames)
1436
1437        for i in range(len(steps) / 2):
1438            steps[self.__number_of_frames - 1 - i] = steps[i]
1439
1440        # normalize angles and calculate interpolated points
1441        for node in self.__sorted_nodes:
1442
1443            l2di = Linear2DInterpolator()
1444
1445            # change grouped nodes coordinate
1446            if node.get_draw_info('grouped') == True:
1447
1448                group_node = node.get_draw_info('group_node')
1449                a, b = group_node.get_draw_info('final_coordinate')
1450
1451                if self.__interpolation == INTERPOLATION_POLAR:
1452                    node.set_polar_coordinate(a, b)
1453
1454                elif self.__interpolation == INTERPOLATION_CARTESIAN:
1455                    node.set_cartesian_coordinate(a, b)
1456
1457            # change interpolation method
1458            if self.__interpolation == INTERPOLATION_POLAR:
1459
1460                coordinate = node.get_polar_coordinate()
1461                node.set_draw_info({'final_coordinate':coordinate})
1462
1463                # adjusting polar coordinates
1464                ri, ti = node.get_draw_info('start_coordinate')
1465                rf, tf = node.get_draw_info('final_coordinate')
1466
1467                # normalization [0, 360]
1468                ti = RadialNet.normalize_angle(ti)
1469                tf = RadialNet.normalize_angle(tf)
1470
1471                # against longest path
1472                ti, tf = RadialNet.calculate_short_path(ti, tf)
1473
1474                # main node goes direct to center (no arc)
1475                if node == self.__graph.get_main_node(): tf = ti
1476
1477                # old main node goes direct to new position (no arc)
1478                if node == old_main_node: ti = tf
1479
1480                node.set_draw_info({'start_coordinate':(ri, ti)})
1481                node.set_draw_info({'final_coordinate':(rf, tf)})
1482
1483            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1484
1485                coordinate = node.get_cartesian_coordinate()
1486                node.set_draw_info({'final_coordinate':coordinate})
1487
1488            # calculate interpolated points
1489            ai, bi = node.get_draw_info('start_coordinate')
1490            af, bf = node.get_draw_info('final_coordinate')
1491
1492            l2di.set_start_point(ai, bi)
1493            l2di.set_final_point(af, bf)
1494
1495            if self.__interpolation_slow_in_out:
1496                points = l2di.get_weighed_points(self.__number_of_frames, steps)
1497
1498            else:
1499                points = l2di.get_points(self.__number_of_frames)
1500
1501            node.set_draw_info({'interpolated_coordinate':points})
1502
1503        return True
1504
1505
1506    def __livens_up(self, index=0):
1507        """
1508        """
1509        # prepare interpolated points
1510        if index == 0:
1511
1512            # prevent unnecessary animation
1513            no_need_to_move = True
1514
1515            for node in self.__graph.get_nodes():
1516
1517                ai, bi = node.get_draw_info('start_coordinate')
1518                af, bf = node.get_draw_info('final_coordinate')
1519
1520                start_c = round(ai), round(bi)
1521                final_c = round(af), round(bf)
1522
1523                if start_c != final_c:
1524                    no_need_to_move = False
1525
1526            if no_need_to_move:
1527
1528                self.__animating = False
1529                return False
1530
1531        # move all nodes for pass 'index'
1532        for node in self.__graph.get_nodes():
1533
1534            a, b = node.get_draw_info('interpolated_coordinate')[index]
1535           
1536            if self.__interpolation == INTERPOLATION_POLAR:
1537                node.set_polar_coordinate(a, b)
1538
1539            elif self.__interpolation == INTERPOLATION_CARTESIAN:
1540                node.set_cartesian_coordinate(a, b)
1541
1542        self.queue_draw()
1543
1544        # animation continue condition
1545        if index < self.__number_of_frames - 1:
1546            gobject.timeout_add(self.__animation_rate, # time to recall
1547                                self.__livens_up,      # recursive call
1548                                index + 1)             # next iteration
1549        else:
1550            self.__last_group_node = None
1551            self.__animating = False
1552
1553        return False
1554
1555
1556    @not_is_in_animation
1557    def set_graph(self, graph):
1558        """
1559        Set graph to be displayed in layout
1560        @type  : Graph
1561        @param : Set the graph used in visualization
1562        """
1563        if graph.get_number_of_nodes() > 0:
1564
1565            self.__graph = graph
1566
1567            self.__calc_node_positions()
1568            self.queue_draw()
1569
1570        else:
1571            self.__graph = None
1572
1573
1574    def get_scanned_nodes(self):
1575        """
1576        """
1577        nodes = list()
1578
1579        for node in self.__graph.get_nodes():
1580
1581            if node.get_info('scanned'):
1582                nodes.append(node)
1583
1584        return nodes
1585
1586
1587    def get_graph(self):
1588        """
1589        """
1590        return self.__graph
1591
1592
1593    def set_empty(self):
1594        """
1595        """
1596        del(self.__graph)
1597        self.__graph = None
1598
1599        self.queue_draw()
1600
1601
1602    def get_rotation(self):
1603        """
1604        """
1605        return self.__rotate
1606
1607
1608    @graph_is_not_empty
1609    def set_rotation(self, angle):
1610        """
1611        """
1612        delta = angle - self.__rotate
1613        self.__rotate = angle
1614
1615        for node in self.__graph.get_nodes():
1616
1617            theta = node.get_coordinate_theta()
1618            node.set_coordinate_theta(theta + delta)
1619
1620        self.queue_draw()
1621
1622
1623    def get_translation(self):
1624        """
1625        """
1626        return self.__translation
1627
1628
1629    @graph_is_not_empty
1630    def set_translation(self, translation):
1631        """
1632        """
1633        self.__translation = translation
1634        self.queue_draw()
1635
1636
1637    def is_empty(self):
1638        """
1639        """
1640        if self.__graph is None:
1641            return True
1642
1643        return False
1644
1645
1646    def is_in_animation(self):
1647        """
1648        """
1649        return self.__animating
1650
1651
1652    def calc_sorted_nodes(self):
1653        """
1654        """
1655        nodes = list()
1656
1657        for node in self.__graph.get_nodes():
1658
1659            ring = node.get_draw_info('ring')
1660            count = 0
1661
1662            for s_node in nodes:
1663           
1664                if ring < s_node.get_draw_info('ring'): break
1665                count +=1
1666
1667            nodes.insert(count, node)
1668
1669        self.__sorted_nodes = nodes
1670        self.__reverse_sorted_nodes = copy.copy(nodes)
1671        self.__reverse_sorted_nodes.reverse()
1672
1673    ###### Static Methods ############
1674   
1675    # Misc
1676    def list_difference_update(list, difference):
1677        """
1678        """
1679        for item in difference:
1680   
1681            if item in list:
1682                list.remove(item)
1683   
1684   
1685    def list_update(list, append):
1686        """
1687        """
1688        for item in append:
1689   
1690            if item not in list:
1691                list.append(item)
1692   
1693   
1694    def swap(list, a, b):
1695        """
1696        """
1697        list[a], list[b] = list[b], list[a]
1698   
1699   
1700    def sort_children(children, father):
1701        """
1702        """
1703        if len(children) < 2:
1704            return children
1705   
1706        # create angle reference
1707        f_x, f_y = father.get_cartesian_coordinate()
1708   
1709        for child in children:
1710   
1711            c_x, c_y = child.get_cartesian_coordinate()
1712            _, angle = CartesianCoordinate(c_x - f_x, c_y - f_y).to_polar()
1713   
1714            child.set_draw_info({'angle_from_father': math.degrees(angle)})
1715   
1716        return RadialNet.sort_children_by_angle(children)
1717   
1718   
1719    def sort_children_by_angle(children):
1720        """
1721        """
1722        if len(children) < 2:
1723            return children
1724   
1725        vector = list()
1726        vector.append(children.pop())
1727   
1728        for a in children:
1729   
1730            theta_a = RadialNet.normalize_angle(\
1731                a.get_draw_info('angle_from_father'))
1732   
1733            for i in range(len(vector) -1, -1, -1):
1734   
1735                b = vector[i]
1736   
1737                theta_b = RadialNet.normalize_angle(\
1738                    b.get_draw_info('angle_from_father'))
1739   
1740                if theta_b <= theta_a <= theta_b + 180:
1741   
1742                    vector.insert(i + 1, a)
1743                    break
1744   
1745            else:
1746                vector.insert(0, a)
1747   
1748        return vector
1749   
1750    # End of misc
1751   
1752    # Geometry
1753   
1754    def is_in_square(point, half_side, center=(0, 0)):
1755        """
1756        """
1757        x, y = point
1758        a, b = center
1759   
1760        if a + half_side >= x >= a - half_side:
1761            if b + half_side >= y >= b - half_side:
1762                return True
1763   
1764        return False
1765
1766   
1767    def is_in_circle(point, radius=1, center=(0, 0)):
1768        """
1769        """
1770        x, y = point
1771        a, b = center
1772   
1773        if ((x - a)**2 + (y - b)**2) <= (radius**2):
1774            return True
1775   
1776        return False
1777   
1778   
1779    def atan_scale(point, scale_ceil):
1780        """
1781        """
1782        new_point = float(10.0 * point / scale_ceil) - 5
1783        return math.atan(abs(new_point))
1784   
1785   
1786    def normalize_angle(angle):
1787        """
1788        """
1789        new_angle = 360.0 * (float(angle / 360) - int(angle / 360))
1790   
1791        if new_angle < 0:
1792            return 360 + new_angle
1793   
1794        return new_angle
1795   
1796   
1797    def is_between_angles(a, b, c):
1798        """
1799        """
1800        a = normalize_angle(a)
1801        b = normalize_angle(b)
1802        c = normalize_angle(c)
1803   
1804        if a > b:
1805   
1806            if c >= a and c <= 360 or c <= b:
1807                return True
1808   
1809            return False
1810   
1811        else:
1812   
1813            if c >= a and c <= b:
1814                return True
1815   
1816            return False
1817   
1818   
1819    def angle_distance(a, b):
1820        """
1821        """
1822        distance = abs(normalize_angle(a) - normalize_angle(b))
1823   
1824        if distance > 180:
1825            return 360 - distance
1826   
1827        return distance
1828   
1829   
1830    def calculate_short_path(iangle, fangle):
1831        """
1832        """
1833        if iangle - fangle > 180:
1834            fangle += 360
1835   
1836        if iangle - fangle < -180:
1837            fangle -= 360
1838   
1839        return iangle, fangle
1840   
1841   
1842    def angle_from_object(distance, size):
1843        """
1844        """
1845        return math.degrees(math.atan2(size / 2.0, distance))
1846       
1847    # End of Geometry
1848   
1849    ########### End of Static Methods ##############
1850   
1851    list_difference_update = staticmethod(list_difference_update)
1852    list_update = staticmethod(list_update)
1853    swap = staticmethod(swap)
1854    sort_children = staticmethod(sort_children)
1855    sort_children_by_angle = staticmethod(sort_children_by_angle)
1856   
1857    is_in_square = staticmethod(is_in_square)
1858    is_in_circle = staticmethod(is_in_circle)
1859    atan_scale = staticmethod(atan_scale)
1860    normalize_angle = staticmethod(normalize_angle)
1861    is_between_angles = staticmethod(is_between_angles)
1862    angle_distance = staticmethod(angle_distance)
1863    calculate_short_path = staticmethod(calculate_short_path)
1864    angle_from_object = staticmethod(angle_from_object)
1865   
1866   
1867
1868class NetNode(Node):
1869    """
1870    Node class for radial network widget
1871    """
1872    def __init__(self, id=Node):
1873        """
1874        """
1875        self.__draw_info = dict()
1876        """Hash with draw information"""
1877        self.__coordinate = PolarCoordinate()
1878
1879        super(NetNode, self).__init__(id)
1880
1881
1882    def get_coordinate_theta(self):
1883        """
1884        """
1885        return self.__coordinate.get_theta()
1886
1887
1888    def get_coordinate_radius(self):
1889        """
1890        """
1891        return self.__coordinate.get_radius()
1892
1893
1894    def set_coordinate_theta(self, value):
1895        """
1896        """
1897        self.__coordinate.set_theta(value)
1898
1899
1900    def set_coordinate_radius(self, value):
1901        """
1902        """
1903        self.__coordinate.set_radius(value)
1904
1905
1906    def set_polar_coordinate(self, r, t):
1907        """
1908        Set polar coordinate
1909        @type  r: number
1910        @param r: The radius of coordinate
1911        @type  t: number
1912        @param t: The angle (theta) of coordinate in radians
1913        """
1914        self.__coordinate.set_coordinate(r, t)
1915
1916
1917    def get_polar_coordinate(self):
1918        """
1919        Get cartesian coordinate
1920        @rtype: tuple
1921        @return: Cartesian coordinates (x, y)
1922        """
1923        return self.__coordinate.get_coordinate()
1924
1925
1926    def set_cartesian_coordinate(self, x, y):
1927        """
1928        Set cartesian coordinate
1929        """
1930        cartesian = CartesianCoordinate(x, y)
1931        r, t = cartesian.to_polar()
1932
1933        self.set_polar_coordinate(r, math.degrees(t))
1934
1935
1936    def get_cartesian_coordinate(self):
1937        """
1938        Get cartesian coordinate
1939        @rtype: tuple
1940        @return: Cartesian coordinates (x, y)
1941        """
1942        return self.__coordinate.to_cartesian()
1943
1944
1945    def get_draw_info(self, info=None):
1946        """
1947        Get draw information about node
1948        @type  : string
1949        @param : Information name
1950        @rtype: mixed
1951        @return: The requested information
1952        """
1953        if info is None:
1954            return self.__draw_info
1955
1956        if self.__draw_info.has_key(info):
1957            return self.__draw_info[info]
1958           
1959        return None
1960
1961
1962    def set_draw_info(self, info):
1963        """
1964        Set draw information
1965        @type  : dict
1966        @param : Draw information dictionary
1967        """
1968        for key in info:
1969            self.__draw_info[key] = info[key]
1970
1971
1972    def deep_search_child(self, node):
1973        """
1974        """
1975        for child in self.get_draw_info('children'):
1976
1977            if child == node:
1978                return True
1979
1980            elif child.deep_search_child(node):
1981                return True
1982
1983        return False
1984
1985
1986    def set_subtree_info(self, info):
1987        """
1988        """
1989        for child in self.get_draw_info('children'):
1990
1991            child.set_draw_info(info)
1992
1993            if child.get_draw_info('group') != True:
1994                child.set_subtree_info(info)
1995
1996
1997    def calc_needed_space(self):
1998        """
1999        """
2000        number_of_children = len(self.get_draw_info('children'))
2001
2002        sum_angle = 0
2003        own_angle = 0
2004
2005        if number_of_children > 0 and self.get_draw_info('group') != True:
2006
2007            for child in self.get_draw_info('children'):
2008
2009                child.calc_needed_space()
2010                sum_angle += child.get_draw_info('space_need')
2011       
2012        distance = self.get_coordinate_radius()
2013        size = self.get_draw_info('radius') * 2
2014        own_angle = RadialNet.angle_from_object(distance, size)
2015
2016        self.set_draw_info({'children_need':sum_angle})
2017        self.set_draw_info({'space_need':max(sum_angle, own_angle)})
2018
Note: See TracBrowser for help on using the browser.