root/network-scanner/branches/GSoC2010/umit/gui/radialnet/RadialNet.py @ 5786

Revision 5786, 55.4 kB (checked in by diogo, 3 years ago)

scan details removed from PreferencesWindow. Topology now reads settings from config file

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