1# vim: set fileencoding=utf-8 :
2
3# ***********************IMPORTANT NMAP LICENSE TERMS************************
4# *                                                                         *
5# * The Nmap Security Scanner is (C) 1996-2020 Insecure.Com LLC ("The Nmap  *
6# * Project"). Nmap is also a registered trademark of the Nmap Project.     *
7# *                                                                         *
8# * This program is distributed under the terms of the Nmap Public Source   *
9# * License (NPSL). The exact license text applying to a particular Nmap    *
10# * release or source code control revision is contained in the LICENSE     *
11# * file distributed with that version of Nmap or source code control       *
12# * revision. More Nmap copyright/legal information is available from       *
13# * https://nmap.org/book/man-legal.html, and further information on the    *
14# * NPSL license itself can be found at https://nmap.org/npsl. This header  *
15# * summarizes some key points from the Nmap license, but is no substitute  *
16# * for the actual license text.                                            *
17# *                                                                         *
18# * Nmap is generally free for end users to download and use themselves,    *
19# * including commercial use. It is available from https://nmap.org.        *
20# *                                                                         *
21# * The Nmap license generally prohibits companies from using and           *
22# * redistributing Nmap in commercial products, but we sell a special Nmap  *
23# * OEM Edition with a more permissive license and special features for     *
24# * this purpose. See https://nmap.org/oem                                  *
25# *                                                                         *
26# * If you have received a written Nmap license agreement or contract       *
27# * stating terms other than these (such as an Nmap OEM license), you may   *
28# * choose to use and redistribute Nmap under those terms instead.          *
29# *                                                                         *
30# * The official Nmap Windows builds include the Npcap software             *
31# * (https://npcap.org) for packet capture and transmission. It is under    *
32# * separate license terms which forbid redistribution without special      *
33# * permission. So the official Nmap Windows builds may not be              *
34# * redistributed without special permission (such as an Nmap OEM           *
35# * license).                                                               *
36# *                                                                         *
37# * Source is provided to this software because we believe users have a     *
38# * right to know exactly what a program is going to do before they run it. *
39# * This also allows you to audit the software for security holes.          *
40# *                                                                         *
41# * Source code also allows you to port Nmap to new platforms, fix bugs,    *
42# * and add new features.  You are highly encouraged to submit your         *
43# * changes as a Github PR or by email to the dev@nmap.org mailing list     *
44# * for possible incorporation into the main distribution. Unless you       *
45# * specify otherwise, it is understood that you are offering us very       *
46# * broad rights to use your submissions as described in the Nmap Public    *
47# * Source License Contributor Agreement. This is important because we      *
48# * fund the project by selling licenses with various terms, and also       *
49# * because the inability to relicense code has caused devastating          *
50# * problems for other Free Software projects (such as KDE and NASM).       *
51# *                                                                         *
52# * The free version of Nmap is distributed in the hope that it will be     *
53# * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of  *
54# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Warranties,        *
55# * indemnification and commercial support are all available through the    *
56# * Npcap OEM program--see https://nmap.org/oem.                            *
57# *                                                                         *
58# ***************************************************************************/
59
60import gtk
61import math
62import gobject
63
64import radialnet.util.geometry as geometry
65
66from radialnet.bestwidgets.boxes import *
67from radialnet.core.Coordinate import PolarCoordinate
68import radialnet.gui.RadialNet as RadialNet
69from radialnet.bestwidgets.expanders import BWExpander
70
71
72OPTIONS = ['address',
73           'hostname',
74           'icon',
75           'latency',
76           'ring',
77           'region',
78           'slow in/out']
79
80REFRESH_RATE = 500
81
82
83class ControlWidget(BWVBox):
84    """
85    """
86    def __init__(self, radialnet):
87        """
88        """
89        BWVBox.__init__(self)
90        self.set_border_width(6)
91
92        self.radialnet = radialnet
93
94        self.__create_widgets()
95
96    def __create_widgets(self):
97        """
98        """
99        self.__action = ControlAction(self.radialnet)
100        self.__interpolation = ControlInterpolation(self.radialnet)
101        self.__layout = ControlLayout(self.radialnet)
102        self.__view = ControlView(self.radialnet)
103
104        self.bw_pack_start_noexpand_nofill(self.__action)
105        self.bw_pack_start_noexpand_nofill(self.__interpolation)
106        self.bw_pack_start_noexpand_nofill(self.__layout)
107        self.bw_pack_start_noexpand_nofill(self.__view)
108
109
110def try_set_tooltip_text(widget, text):
111    try:
112        widget.set_tooltip_text(text)
113    except AttributeError:
114        # The set_tooltip_text method was introduced in PyGTK 2.12.
115        pass
116
117
118class ControlAction(BWExpander):
119    """
120    """
121    def __init__(self, radialnet):
122        """
123        """
124        BWExpander.__init__(self, _('Action'))
125        self.set_expanded(True)
126
127        self.radialnet = radialnet
128
129        self.__create_widgets()
130
131    def __create_widgets(self):
132        """
133        """
134        self.__tbox = BWTable(1, 4)
135        self.__tbox.bw_set_spacing(0)
136        self.__vbox = BWVBox()
137
138        self.__jump_to = gtk.RadioToolButton(None, gtk.STOCK_JUMP_TO)
139        try_set_tooltip_text(self.__jump_to, 'Change focus')
140        self.__jump_to.connect('toggled',
141                               self.__change_pointer,
142                               RadialNet.POINTER_JUMP_TO)
143
144        try:
145            # gtk.STOCK_INFO is available only in PyGTK 2.8 and later.
146            info_icon = gtk.STOCK_INFO
147        except AttributeError:
148            self.__info = gtk.RadioToolButton(self.__jump_to, None)
149            self.__info.set_label(_("Info"))
150        else:
151            self.__info = gtk.RadioToolButton(self.__jump_to, info_icon)
152        try_set_tooltip_text(self.__info, 'Show information')
153        self.__info.connect('toggled',
154                            self.__change_pointer,
155                            RadialNet.POINTER_INFO)
156
157        self.__group = gtk.RadioToolButton(self.__jump_to, gtk.STOCK_ADD)
158        try_set_tooltip_text(self.__group, 'Group children')
159        self.__group.connect('toggled',
160                             self.__change_pointer,
161                             RadialNet.POINTER_GROUP)
162
163        self.__region = gtk.RadioToolButton(self.__jump_to,
164                                            gtk.STOCK_SELECT_COLOR)
165        try_set_tooltip_text(self.__region, 'Fill region')
166        self.__region.connect('toggled',
167                              self.__change_pointer,
168                              RadialNet.POINTER_FILL)
169
170        self.__region_color = gtk.combo_box_new_text()
171        self.__region_color.append_text(_('Red'))
172        self.__region_color.append_text(_('Yellow'))
173        self.__region_color.append_text(_('Green'))
174        self.__region_color.connect('changed', self.__change_region)
175        self.__region_color.set_active(self.radialnet.get_region_color())
176
177        self.__tbox.bw_attach_next(self.__jump_to)
178        self.__tbox.bw_attach_next(self.__info)
179        self.__tbox.bw_attach_next(self.__group)
180        self.__tbox.bw_attach_next(self.__region)
181
182        self.__vbox.bw_pack_start_noexpand_nofill(self.__tbox)
183        self.__vbox.bw_pack_start_noexpand_nofill(self.__region_color)
184
185        self.bw_add(self.__vbox)
186
187        self.__jump_to.set_active(True)
188        self.__region_color.set_no_show_all(True)
189        self.__region_color.hide()
190
191    def __change_pointer(self, widget, pointer):
192        """
193        """
194        if pointer != self.radialnet.get_pointer_status():
195            self.radialnet.set_pointer_status(pointer)
196
197        if pointer == RadialNet.POINTER_FILL:
198            self.__region_color.show()
199        else:
200            self.__region_color.hide()
201
202    def __change_region(self, widget):
203        """
204        """
205        self.radialnet.set_region_color(self.__region_color.get_active())
206
207
208class ControlVariableWidget(gtk.DrawingArea):
209    """
210    """
211    def __init__(self, name, value, update, increment=1):
212        """
213        """
214        gtk.DrawingArea.__init__(self)
215
216        self.__variable_name = name
217        self.__value = value
218        self.__update = update
219        self.__increment_pass = increment
220
221        self.__radius = 6
222        self.__increment_time = 100
223
224        self.__pointer_position = 0
225        self.__active_increment = False
226
227        self.__last_value = self.__value()
228
229        self.connect('expose_event', self.expose)
230        self.connect('button_press_event', self.button_press)
231        self.connect('button_release_event', self.button_release)
232        self.connect('motion_notify_event', self.motion_notify)
233
234        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
235                        gtk.gdk.BUTTON_RELEASE_MASK |
236                        gtk.gdk.MOTION_NOTIFY |
237                        gtk.gdk.POINTER_MOTION_HINT_MASK |
238                        gtk.gdk.POINTER_MOTION_MASK)
239
240        gobject.timeout_add(REFRESH_RATE, self.verify_value)
241
242    def verify_value(self):
243        """
244        """
245        if self.__value() != self.__last_value:
246            self.__last_value = self.__value()
247
248        self.queue_draw()
249
250        return True
251
252    def button_press(self, widget, event):
253        """
254        """
255        self.__active_increment = False
256        pointer = self.get_pointer()
257
258        if self.__button_is_clicked(pointer) and event.button == 1:
259
260            event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
261            self.__active_increment = True
262            self.__increment_value()
263
264    def button_release(self, widget, event):
265        """
266        """
267        event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
268
269        self.__active_increment = False
270        self.__pointer_position = 0
271
272        self.queue_draw()
273
274    def motion_notify(self, widget, event):
275        """
276        Drawing callback
277        @type  widget: GtkWidget
278        @param widget: Gtk widget superclass
279        @type  event: GtkEvent
280        @param event: Gtk event of widget
281        @rtype: boolean
282        @return: Indicator of the event propagation
283        """
284        if self.__active_increment:
285
286            xc, yc = self.__center_of_widget
287            x, _ = self.get_pointer()
288
289            if x - self.__radius > 0 and x + self.__radius < 2 * xc:
290                self.__pointer_position = x - xc
291
292        self.queue_draw()
293
294    def expose(self, widget, event):
295        """
296        Drawing callback
297        @type  widget: GtkWidget
298        @param widget: Gtk widget superclass
299        @type  event: GtkEvent
300        @param event: Gtk event of widget
301        @rtype: boolean
302        @return: Indicator of the event propagation
303        """
304        self.set_size_request(100, 30)
305
306        self.context = widget.window.cairo_create()
307        self.__draw()
308
309        return True
310
311    def __draw(self):
312        """
313        """
314        allocation = self.get_allocation()
315
316        self.__center_of_widget = (allocation.width / 2,
317                                   allocation.height / 2)
318
319        xc, yc = self.__center_of_widget
320
321        # draw line
322        self.context.set_line_width(1)
323        self.context.set_dash([1, 2])
324        self.context.move_to(self.__radius,
325                             yc + self.__radius)
326        self.context.line_to(2 * xc - 5,
327                             yc + self.__radius)
328        self.context.stroke()
329
330        # draw text
331        self.context.set_dash([1, 0])
332        self.context.set_font_size(10)
333
334        self.context.move_to(5, yc - self.__radius)
335        self.context.show_text(self.__variable_name)
336
337        width = self.context.text_extents(str(self.__value()))[2]
338        self.context.move_to(2 * xc - width - 5, yc - self.__radius)
339        self.context.show_text(str(self.__value()))
340
341        self.context.set_line_width(1)
342        self.context.stroke()
343
344        # draw node
345        self.context.arc(xc + self.__pointer_position,
346                         yc + self.__radius,
347                         self.__radius, 0, 2 * math.pi)
348        if self.__active_increment:
349            self.context.set_source_rgb(0.0, 0.0, 0.0)
350        else:
351            self.context.set_source_rgb(1.0, 1.0, 1.0)
352        self.context.fill_preserve()
353        self.context.set_source_rgb(0.0, 0.0, 0.0)
354        self.context.stroke()
355
356    def __button_is_clicked(self, pointer):
357        """
358        """
359        xc, yc = self.__center_of_widget
360        center = (xc, yc + self.__radius)
361
362        return geometry.is_in_circle(pointer, 6, center)
363
364    def __increment_value(self):
365        """
366        """
367        self.__update(self.__value() + self.__pointer_position / 4)
368
369        self.queue_draw()
370
371        if self.__active_increment:
372
373            gobject.timeout_add(self.__increment_time,
374                                self.__increment_value)
375
376    def set_value_function(self, value):
377        """
378        """
379        self.__value = value
380
381    def set_update_function(self, update):
382        """
383        """
384        self.__update = update
385
386
387class ControlVariable(BWHBox):
388    """
389    """
390    def __init__(self, name, get_function, set_function, increment=1):
391        """
392        """
393        BWHBox.__init__(self, spacing=0)
394
395        self.__increment_pass = increment
396        self.__increment_time = 200
397        self.__increment = False
398
399        self.__name = name
400        self.__get_function = get_function
401        self.__set_function = set_function
402
403        self.__create_widgets()
404
405    def __create_widgets(self):
406        """
407        """
408        self.__control = ControlVariableWidget(self.__name,
409                                               self.__get_function,
410                                               self.__set_function,
411                                               self.__increment_pass)
412
413        self.__left_button = gtk.Button()
414        self.__left_button.set_size_request(20, 20)
415        self.__left_arrow = gtk.Arrow(gtk.ARROW_LEFT, gtk.SHADOW_NONE)
416        self.__left_button.add(self.__left_arrow)
417        self.__left_button.connect('pressed',
418                                   self.__pressed,
419                                   -self.__increment_pass)
420        self.__left_button.connect('released', self.__released)
421
422        self.__right_button = gtk.Button()
423        self.__right_button.set_size_request(20, 20)
424        self.__right_arrow = gtk.Arrow(gtk.ARROW_RIGHT, gtk.SHADOW_NONE)
425        self.__right_button.add(self.__right_arrow)
426        self.__right_button.connect('pressed',
427                                    self.__pressed,
428                                    self.__increment_pass)
429        self.__right_button.connect('released', self.__released)
430
431        self.bw_pack_start_noexpand_nofill(self.__left_button)
432        self.bw_pack_start_expand_fill(self.__control)
433        self.bw_pack_start_noexpand_nofill(self.__right_button)
434
435    def __pressed(self, widget, increment):
436        """
437        """
438        self.__increment = True
439        self.__increment_function(increment)
440
441    def __increment_function(self, increment):
442        """
443        """
444        if self.__increment:
445
446            self.__set_function(self.__get_function() + increment)
447            self.__control.verify_value()
448
449            gobject.timeout_add(self.__increment_time,
450                                self.__increment_function,
451                                increment)
452
453    def __released(self, widget):
454        """
455        """
456        self.__increment = False
457
458
459class ControlFisheye(BWVBox):
460    """
461    """
462    def __init__(self, radialnet):
463        """
464        """
465        BWVBox.__init__(self)
466        self.set_border_width(6)
467
468        self.radialnet = radialnet
469        self.__ring_max_value = self.radialnet.get_number_of_rings()
470
471        self.__create_widgets()
472
473    def __create_widgets(self):
474        """
475        """
476        self.__params = BWHBox()
477
478        self.__fisheye_label = gtk.Label(_('<b>Fisheye</b> on ring'))
479        self.__fisheye_label.set_use_markup(True)
480
481        self.__ring = gtk.Adjustment(0, 0, self.__ring_max_value, 0.01, 0.01)
482
483        self.__ring_spin = gtk.SpinButton(self.__ring)
484        self.__ring_spin.set_digits(2)
485
486        self.__ring_scale = gtk.HScale(self.__ring)
487        self.__ring_scale.set_size_request(100, -1)
488        self.__ring_scale.set_digits(2)
489        self.__ring_scale.set_value_pos(gtk.POS_LEFT)
490        self.__ring_scale.set_draw_value(False)
491        self.__ring_scale.set_update_policy(gtk.UPDATE_CONTINUOUS)
492
493        self.__interest_label = gtk.Label(_('with interest factor'))
494        self.__interest = gtk.Adjustment(0, 0, 10, 0.01)
495        self.__interest_spin = gtk.SpinButton(self.__interest)
496        self.__interest_spin.set_digits(2)
497
498        self.__spread_label = gtk.Label(_('and spread factor'))
499        self.__spread = gtk.Adjustment(0, -1.0, 1.0, 0.01, 0.01)
500        self.__spread_spin = gtk.SpinButton(self.__spread)
501        self.__spread_spin.set_digits(2)
502
503        self.__params.bw_pack_start_noexpand_nofill(self.__fisheye_label)
504        self.__params.bw_pack_start_noexpand_nofill(self.__ring_spin)
505        self.__params.bw_pack_start_expand_fill(self.__ring_scale)
506        self.__params.bw_pack_start_noexpand_nofill(self.__interest_label)
507        self.__params.bw_pack_start_noexpand_nofill(self.__interest_spin)
508        self.__params.bw_pack_start_noexpand_nofill(self.__spread_label)
509        self.__params.bw_pack_start_noexpand_nofill(self.__spread_spin)
510
511        self.bw_pack_start_noexpand_nofill(self.__params)
512
513        self.__ring.connect('value_changed', self.__change_ring)
514        self.__interest.connect('value_changed', self.__change_interest)
515        self.__spread.connect('value_changed', self.__change_spread)
516
517        gobject.timeout_add(REFRESH_RATE, self.__update_fisheye)
518
519    def __update_fisheye(self):
520        """
521        """
522        # adjust ring scale to radialnet number of nodes
523        ring_max_value = self.radialnet.get_number_of_rings() - 1
524
525        if ring_max_value != self.__ring_max_value:
526
527            value = self.__ring.get_value()
528
529            if value == 0 and ring_max_value != 0:
530                value = 1
531
532            elif value > ring_max_value:
533                value = ring_max_value
534
535            self.__ring.set_all(value, 1, ring_max_value, 0.01, 0.01, 0)
536            self.__ring_max_value = ring_max_value
537
538            self.__ring_scale.queue_draw()
539
540        # check ring value
541        ring_value = self.radialnet.get_fisheye_ring()
542
543        if self.__ring.get_value() != ring_value:
544            self.__ring.set_value(ring_value)
545
546        # check interest value
547        interest_value = self.radialnet.get_fisheye_interest()
548
549        if self.__interest.get_value() != interest_value:
550            self.__interest.set_value(interest_value)
551
552        # check spread value
553        spread_value = self.radialnet.get_fisheye_spread()
554
555        if self.__spread.get_value() != spread_value:
556            self.__spread.set_value(spread_value)
557
558        return True
559
560    def active_fisheye(self):
561        """
562        """
563        self.radialnet.set_fisheye(True)
564        self.__change_ring()
565        self.__change_interest()
566
567    def deactive_fisheye(self):
568        """
569        """
570        self.radialnet.set_fisheye(False)
571
572    def __change_ring(self, widget=None):
573        """
574        """
575        if not self.radialnet.is_in_animation():
576            self.radialnet.set_fisheye_ring(self.__ring.get_value())
577        else:
578            self.__ring.set_value(self.radialnet.get_fisheye_ring())
579
580    def __change_interest(self, widget=None):
581        """
582        """
583        if not self.radialnet.is_in_animation():
584            self.radialnet.set_fisheye_interest(self.__interest.get_value())
585        else:
586            self.__interest.set_value(self.radialnet.get_fisheye_interest())
587
588    def __change_spread(self, widget=None):
589        """
590        """
591        if not self.radialnet.is_in_animation():
592            self.radialnet.set_fisheye_spread(self.__spread.get_value())
593        else:
594            self.__spread.set_value(self.radialnet.get_fisheye_spread())
595
596
597class ControlInterpolation(BWExpander):
598    """
599    """
600    def __init__(self, radialnet):
601        """
602        """
603        BWExpander.__init__(self, _('Interpolation'))
604
605        self.radialnet = radialnet
606
607        self.__create_widgets()
608
609    def __create_widgets(self):
610        """
611        """
612        self.__vbox = BWVBox()
613
614        self.__cartesian_radio = gtk.RadioButton(None, _('Cartesian'))
615        self.__polar_radio = gtk.RadioButton(
616                self.__cartesian_radio, _('Polar'))
617        self.__cartesian_radio.connect('toggled',
618                                       self.__change_system,
619                                       RadialNet.INTERPOLATION_CARTESIAN)
620        self.__polar_radio.connect('toggled',
621                                   self.__change_system,
622                                   RadialNet.INTERPOLATION_POLAR)
623
624        self.__system_box = BWHBox()
625        self.__system_box.bw_pack_start_noexpand_nofill(self.__polar_radio)
626        self.__system_box.bw_pack_start_noexpand_nofill(self.__cartesian_radio)
627
628        self.__frames_box = BWHBox()
629        self.__frames_label = gtk.Label(_('Frames'))
630        self.__frames_label.set_alignment(0.0, 0.5)
631        self.__frames = gtk.Adjustment(self.radialnet.get_number_of_frames(),
632                                       1,
633                                       1000,
634                                       1)
635        self.__frames.connect('value_changed', self.__change_frames)
636        self.__frames_spin = gtk.SpinButton(self.__frames)
637        self.__frames_box.bw_pack_start_expand_fill(self.__frames_label)
638        self.__frames_box.bw_pack_start_noexpand_nofill(self.__frames_spin)
639
640        self.__vbox.bw_pack_start_noexpand_nofill(self.__frames_box)
641        self.__vbox.bw_pack_start_noexpand_nofill(self.__system_box)
642
643        self.bw_add(self.__vbox)
644
645        gobject.timeout_add(REFRESH_RATE, self.__update_animation)
646
647    def __update_animation(self):
648        """
649        """
650        active = self.radialnet.get_interpolation()
651
652        if active == RadialNet.INTERPOLATION_CARTESIAN:
653            self.__cartesian_radio.set_active(True)
654
655        else:
656            self.__polar_radio.set_active(True)
657
658        return True
659
660    def __change_system(self, widget, value):
661        """
662        """
663        if not self.radialnet.set_interpolation(value):
664
665            active = self.radialnet.get_interpolation()
666
667            if active == RadialNet.INTERPOLATION_CARTESIAN:
668                self.__cartesian_radio.set_active(True)
669
670            else:
671                self.__polar_radio.set_active(True)
672
673    def __change_frames(self, widget):
674        """
675        """
676        if not self.radialnet.set_number_of_frames(self.__frames.get_value()):
677            self.__frames.set_value(self.radialnet.get_number_of_frames())
678
679
680class ControlLayout(BWExpander):
681    """
682    """
683    def __init__(self, radialnet):
684        """
685        """
686        BWExpander.__init__(self, _('Layout'))
687
688        self.radialnet = radialnet
689
690        self.__create_widgets()
691
692    def __create_widgets(self):
693        """
694        """
695        self.__hbox = BWHBox()
696
697        self.__layout = gtk.combo_box_new_text()
698        self.__layout.append_text(_('Symmetric'))
699        self.__layout.append_text(_('Weighted'))
700        self.__layout.set_active(self.radialnet.get_layout())
701        self.__layout.connect('changed', self.__change_layout)
702        self.__force = gtk.ToolButton(gtk.STOCK_REFRESH)
703        self.__force.connect('clicked', self.__force_update)
704
705        self.__hbox.bw_pack_start_expand_fill(self.__layout)
706        self.__hbox.bw_pack_start_noexpand_nofill(self.__force)
707
708        self.bw_add(self.__hbox)
709
710        self.__check_layout()
711
712    def __check_layout(self):
713        """
714        """
715        if self.__layout.get_active() == RadialNet.LAYOUT_WEIGHTED:
716            self.__force.set_sensitive(True)
717
718        else:
719            self.__force.set_sensitive(False)
720
721        return True
722
723    def __force_update(self, widget):
724        """
725        """
726        self.__fisheye_ring = self.radialnet.get_fisheye_ring()
727        self.radialnet.update_layout()
728
729    def __change_layout(self, widget):
730        """
731        """
732        if not self.radialnet.set_layout(self.__layout.get_active()):
733            self.__layout.set_active(self.radialnet.get_layout())
734
735        else:
736            self.__check_layout()
737
738
739class ControlRingGap(BWVBox):
740    """
741    """
742    def __init__(self, radialnet):
743        """
744        """
745        BWVBox.__init__(self)
746
747        self.radialnet = radialnet
748
749        self.__create_widgets()
750
751    def __create_widgets(self):
752        """
753        """
754        self.__radius = ControlVariable(_('Ring gap'),
755                                        self.radialnet.get_ring_gap,
756                                        self.radialnet.set_ring_gap)
757
758        self.__label = gtk.Label(_('Lower ring gap'))
759        self.__label.set_alignment(0.0, 0.5)
760        self.__adjustment = gtk.Adjustment(self.radialnet.get_min_ring_gap(),
761                                           0,
762                                           50,
763                                           1)
764        self.__spin = gtk.SpinButton(self.__adjustment)
765        self.__spin.connect('value_changed', self.__change_lower)
766
767        self.__lower_hbox = BWHBox()
768        self.__lower_hbox.bw_pack_start_expand_fill(self.__label)
769        self.__lower_hbox.bw_pack_start_noexpand_nofill(self.__spin)
770
771        self.bw_pack_start_noexpand_nofill(self.__radius)
772        self.bw_pack_start_noexpand_nofill(self.__lower_hbox)
773
774    def __change_lower(self, widget):
775        """
776        """
777        if not self.radialnet.set_min_ring_gap(self.__adjustment.get_value()):
778            self.__adjustment.set_value(self.radialnet.get_min_ring_gap())
779
780
781class ControlOptions(BWScrolledWindow):
782    """
783    """
784    def __init__(self, radialnet):
785        """
786        """
787        BWScrolledWindow.__init__(self)
788
789        self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
790        self.set_shadow_type(gtk.SHADOW_NONE)
791
792        self.radialnet = radialnet
793
794        self.enable_labels = True
795
796        self.__create_widgets()
797
798    def __create_widgets(self):
799        """
800        """
801        self.__liststore = gtk.ListStore(gobject.TYPE_BOOLEAN,
802                                         gobject.TYPE_STRING)
803
804        self.__liststore.append([None, OPTIONS[0]])
805        self.__liststore.append([None, OPTIONS[1]])
806        self.__liststore.append([None, OPTIONS[2]])
807        self.__liststore.append([None, OPTIONS[3]])
808        self.__liststore.append([None, OPTIONS[4]])
809        self.__liststore.append([None, OPTIONS[5]])
810        self.__liststore.append([None, OPTIONS[6]])
811
812        self.__cell_toggle = gtk.CellRendererToggle()
813        self.__cell_toggle.set_property('activatable', True)
814        self.__cell_toggle.connect('toggled',
815                                   self.__change_option,
816                                   self.__liststore)
817
818        self.__column_toggle = gtk.TreeViewColumn('', self.__cell_toggle)
819        self.__column_toggle.add_attribute(self.__cell_toggle, 'active', 0)
820        self.__column_toggle.set_cell_data_func(self.__cell_toggle, self.__cell_toggle_data_method)
821
822        self.__cell_text = gtk.CellRendererText()
823
824        self.__column_text = gtk.TreeViewColumn(None,
825                                                self.__cell_text,
826                                                text=1)
827        self.__column_text.set_cell_data_func(self.__cell_text, self.__cell_text_data_method)
828
829        self.__treeview = gtk.TreeView(self.__liststore)
830        self.__treeview.set_enable_search(True)
831        self.__treeview.set_search_column(1)
832        self.__treeview.set_headers_visible(False)
833        self.__treeview.append_column(self.__column_toggle)
834        self.__treeview.append_column(self.__column_text)
835
836        self.add_with_viewport(self.__treeview)
837
838        gobject.timeout_add(REFRESH_RATE, self.__update_options)
839
840    def __cell_toggle_data_method(self, column, cell, model, it):
841        if not self.enable_labels and model.get_value(it, 1) == 'hostname':
842            cell.set_property('activatable', False)
843        else:
844            cell.set_property('activatable', True)
845
846    def __cell_text_data_method(self, column, cell, model, it):
847        if not self.enable_labels and model.get_value(it, 1) == 'hostname':
848            cell.set_property('strikethrough', True)
849        else:
850            cell.set_property('strikethrough', False)
851
852    def __update_options(self):
853        """
854        """
855        model = self.__liststore
856
857        model[OPTIONS.index('address')][0] = self.radialnet.get_show_address()
858        model[OPTIONS.index('hostname')][0] = self.enable_labels and \
859                self.radialnet.get_show_hostname()
860        model[OPTIONS.index('icon')][0] = self.radialnet.get_show_icon()
861        model[OPTIONS.index('latency')][0] = self.radialnet.get_show_latency()
862        model[OPTIONS.index('ring')][0] = self.radialnet.get_show_ring()
863        model[OPTIONS.index('region')][0] = self.radialnet.get_show_region()
864        model[OPTIONS.index('slow in/out')][0] = \
865                self.radialnet.get_slow_inout()
866
867        return True
868
869    def __change_option(self, cell, option, model):
870        """
871        """
872        option = int(option)
873        model[option][0] = not model[option][0]
874
875        if OPTIONS[option] == 'address':
876            self.radialnet.set_show_address(model[option][0])
877            if model[option][0]:
878                model[OPTIONS.index('hostname')][0] = self.radialnet.get_show_hostname()
879            else:
880                model[OPTIONS.index('hostname')][0] = False
881            self.enable_labels = model[option][0]
882
883        elif OPTIONS[option] == 'hostname':
884            self.radialnet.set_show_hostname(model[option][0])
885
886        elif OPTIONS[option] == 'icon':
887            self.radialnet.set_show_icon(model[option][0])
888
889        elif OPTIONS[option] == 'latency':
890            self.radialnet.set_show_latency(model[option][0])
891
892        elif OPTIONS[option] == 'ring':
893            self.radialnet.set_show_ring(model[option][0])
894
895        elif OPTIONS[option] == 'region':
896            self.radialnet.set_show_region(model[option][0])
897
898        elif OPTIONS[option] == 'slow in/out':
899            self.radialnet.set_slow_inout(model[option][0])
900
901
902class ControlView(BWExpander):
903    """
904    """
905    def __init__(self, radialnet):
906        """
907        """
908        BWExpander.__init__(self, _('View'))
909        self.set_expanded(True)
910
911        self.radialnet = radialnet
912
913        self.__create_widgets()
914
915    def __create_widgets(self):
916        """
917        """
918        self.__vbox = BWVBox(spacing=0)
919
920        self.__zoom = ControlVariable(_('Zoom'),
921                                      self.radialnet.get_zoom,
922                                      self.radialnet.set_zoom)
923
924        self.__ring_gap = ControlRingGap(self.radialnet)
925        self.__navigation = ControlNavigation(self.radialnet)
926
927        self.__options = ControlOptions(self.radialnet)
928        self.__options.set_border_width(0)
929
930        self.__vbox.bw_pack_start_expand_nofill(self.__options)
931        self.__vbox.bw_pack_start_noexpand_nofill(self.__navigation)
932        self.__vbox.bw_pack_start_noexpand_nofill(self.__zoom)
933        self.__vbox.bw_pack_start_noexpand_nofill(self.__ring_gap)
934
935        self.bw_add(self.__vbox)
936
937
938class ControlNavigation(gtk.DrawingArea):
939    """
940    """
941    def __init__(self, radialnet):
942        """
943        """
944        gtk.DrawingArea.__init__(self)
945
946        self.radialnet = radialnet
947
948        self.__rotate_node = PolarCoordinate()
949        self.__rotate_node.set_coordinate(40, 90)
950        self.__center_of_widget = (50, 50)
951        self.__moving = None
952        self.__centering = False
953        self.__rotating = False
954        self.__move_pass = 100
955
956        self.__move_position = (0, 0)
957        self.__move_addition = [(-1, 0),
958                                (-1, -1),
959                                (0, -1),
960                                (1, -1),
961                                (1, 0),
962                                (1, 1),
963                                (0, 1),
964                                (-1, 1)]
965
966        self.__move_factor = 1
967        self.__move_factor_limit = 20
968
969        self.__rotate_radius = 6
970        self.__move_radius = 6
971
972        self.__rotate_clicked = False
973        self.__move_clicked = None
974
975        self.connect('expose_event', self.expose)
976        self.connect('button_press_event', self.button_press)
977        self.connect('button_release_event', self.button_release)
978        self.connect('motion_notify_event', self.motion_notify)
979        self.connect('enter_notify_event', self.enter_notify)
980        self.connect('leave_notify_event', self.leave_notify)
981        self.connect('key_press_event', self.key_press)
982        self.connect('key_release_event', self.key_release)
983
984        self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
985                        gtk.gdk.BUTTON_RELEASE_MASK |
986                        gtk.gdk.ENTER_NOTIFY |
987                        gtk.gdk.LEAVE_NOTIFY |
988                        gtk.gdk.MOTION_NOTIFY |
989                        gtk.gdk.NOTHING |
990                        gtk.gdk.KEY_PRESS_MASK |
991                        gtk.gdk.KEY_RELEASE_MASK |
992                        gtk.gdk.POINTER_MOTION_HINT_MASK |
993                        gtk.gdk.POINTER_MOTION_MASK)
994
995        self.__rotate_node.set_coordinate(40, self.radialnet.get_rotation())
996
997    def key_press(self, widget, event):
998        """
999        """
1000        # key = gtk.gdk.keyval_name(event.keyval)
1001
1002        self.queue_draw()
1003
1004        return True
1005
1006    def key_release(self, widget, event):
1007        """
1008        """
1009        # key = gtk.gdk.keyval_name(event.keyval)
1010
1011        self.queue_draw()
1012
1013        return True
1014
1015    def enter_notify(self, widget, event):
1016        """
1017        """
1018        return False
1019
1020    def leave_notify(self, widget, event):
1021        """
1022        """
1023        self.queue_draw()
1024
1025        return False
1026
1027    def button_press(self, widget, event):
1028        """
1029        Drawing callback
1030        @type  widget: GtkWidget
1031        @param widget: Gtk widget superclass
1032        @type  event: GtkEvent
1033        @param event: Gtk event of widget
1034        @rtype: boolean
1035        @return: Indicator of the event propagation
1036        """
1037        pointer = self.get_pointer()
1038
1039        direction = False
1040
1041        if self.__rotate_is_clicked(pointer):
1042
1043            event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
1044            self.__rotating = True
1045
1046        direction = self.__move_is_clicked(pointer)
1047
1048        if direction is not None and self.__moving is None:
1049
1050            event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
1051            self.__moving = direction
1052            self.__move_in_direction(direction)
1053
1054        if self.__center_is_clicked(pointer):
1055
1056            event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
1057            self.__centering = True
1058            self.__move_position = (0, 0)
1059            self.radialnet.set_translation(self.__move_position)
1060
1061        self.queue_draw()
1062
1063        return False
1064
1065    def button_release(self, widget, event):
1066        """
1067        Drawing callback
1068        @type  widget: GtkWidget
1069        @param widget: Gtk widget superclass
1070        @type  event: GtkEvent
1071        @param event: Gtk event of widget
1072        @rtype: boolean
1073        @return: Indicator of the event propagation
1074        """
1075        self.__moving = None        # stop moving
1076        self.__centering = False
1077        self.__rotating = False     # stop rotate
1078        self.__move_factor = 1
1079
1080        event.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
1081
1082        self.queue_draw()
1083
1084        return False
1085
1086    def motion_notify(self, widget, event):
1087        """
1088        Drawing callback
1089        @type  widget: GtkWidget
1090        @param widget: Gtk widget superclass
1091        @type  event: GtkEvent
1092        @param event: Gtk event of widget
1093        @rtype: boolean
1094        @return: Indicator of the event propagation
1095        """
1096        xc, yc = self.__center_of_widget
1097        x, y = self.get_pointer()
1098
1099        status = not self.radialnet.is_in_animation()
1100        status = status and not self.radialnet.is_empty()
1101
1102        if self.__rotating and status:
1103
1104            r, t = self.__rotate_node.get_coordinate()
1105            t = math.degrees(math.atan2(yc - y, x - xc))
1106
1107            if t < 0:
1108                t = 360 + t
1109
1110            self.radialnet.set_rotation(t)
1111            self.__rotate_node.set_coordinate(r, t)
1112
1113            self.queue_draw()
1114
1115        return False
1116
1117    def expose(self, widget, event):
1118        """
1119        Drawing callback
1120        @type  widget: GtkWidget
1121        @param widget: Gtk widget superclass
1122        @type  event: GtkEvent
1123        @param event: Gtk event of widget
1124        @rtype: boolean
1125        @return: Indicator of the event propagation
1126        """
1127        self.set_size_request(120, 130)
1128
1129        self.context = widget.window.cairo_create()
1130        self.__draw()
1131
1132        return False
1133
1134    def __draw_rotate_control(self):
1135        """
1136        """
1137        xc, yc = self.__center_of_widget
1138        r, t = self.__rotate_node.get_coordinate()
1139        x, y = self.__rotate_node.to_cartesian()
1140
1141        # draw text
1142        self.context.set_font_size(10)
1143        self.context.move_to(xc - 49, yc - 48)
1144        self.context.show_text(_("Navigation"))
1145
1146        width = self.context.text_extents(str(int(t)))[2]
1147        self.context.move_to(xc + 49 - width - 2, yc - 48)
1148        self.context.show_text(str(round(t, 1)))
1149        self.context.set_line_width(1)
1150        self.context.stroke()
1151
1152        # draw arc
1153        self.context.set_dash([1, 2])
1154        self.context.arc(xc, yc, 40, 0, 2 * math.pi)
1155        self.context.set_source_rgb(0.0, 0.0, 0.0)
1156        self.context.set_line_width(1)
1157        self.context.stroke()
1158
1159        # draw node
1160        self.context.set_dash([1, 0])
1161        self.context.arc(xc + x, yc - y, self.__rotate_radius, 0, 2 * math.pi)
1162
1163        if self.__rotating:
1164            self.context.set_source_rgb(0.0, 0.0, 0.0)
1165
1166        else:
1167            self.context.set_source_rgb(1.0, 1.0, 1.0)
1168
1169        self.context.fill_preserve()
1170        self.context.set_source_rgb(0.0, 0.0, 0.0)
1171        self.context.set_line_width(1)
1172        self.context.stroke()
1173
1174        return False
1175
1176    def __draw_move_control(self):
1177        """
1178        """
1179        xc, yc = self.__center_of_widget
1180        pc = PolarCoordinate()
1181
1182        self.context.set_dash([1, 1])
1183        self.context.arc(xc, yc, 23, 0, 2 * math.pi)
1184        self.context.set_source_rgb(0.0, 0.0, 0.0)
1185        self.context.set_line_width(1)
1186        self.context.stroke()
1187
1188        for i in range(8):
1189
1190            pc.set_coordinate(23, 45 * i)
1191            x, y = pc.to_cartesian()
1192
1193            self.context.set_dash([1, 1])
1194            self.context.move_to(xc, yc)
1195            self.context.line_to(xc + x, yc - y)
1196            self.context.stroke()
1197
1198            self.context.set_dash([1, 0])
1199            self.context.arc(
1200                    xc + x, yc - y, self.__move_radius, 0, 2 * math.pi)
1201
1202            if i == self.__moving:
1203                self.context.set_source_rgb(0.0, 0.0, 0.0)
1204            else:
1205                self.context.set_source_rgb(1.0, 1.0, 1.0)
1206            self.context.fill_preserve()
1207            self.context.set_source_rgb(0.0, 0.0, 0.0)
1208            self.context.set_line_width(1)
1209            self.context.stroke()
1210
1211        self.context.arc(xc, yc, 6, 0, 2 * math.pi)
1212
1213        if self.__centering:
1214            self.context.set_source_rgb(0.0, 0.0, 0.0)
1215        else:
1216            self.context.set_source_rgb(1.0, 1.0, 1.0)
1217        self.context.fill_preserve()
1218        self.context.set_source_rgb(0.0, 0.0, 0.0)
1219        self.context.set_line_width(1)
1220        self.context.stroke()
1221
1222        return False
1223
1224    def __draw(self):
1225        """
1226        Drawing method
1227        """
1228        # Getting allocation reference
1229        allocation = self.get_allocation()
1230
1231        self.__center_of_widget = (allocation.width / 2,
1232                                   allocation.height / 2)
1233
1234        self.__draw_rotate_control()
1235        self.__draw_move_control()
1236
1237        return False
1238
1239    def __move_in_direction(self, direction):
1240        """
1241        """
1242        if self.__moving is not None:
1243
1244            bx, by = self.__move_position
1245            ax, ay = self.__move_addition[direction]
1246
1247            self.__move_position = (bx + self.__move_factor * ax,
1248                                    by + self.__move_factor * ay)
1249            self.radialnet.set_translation(self.__move_position)
1250
1251            if self.__move_factor < self.__move_factor_limit:
1252                self.__move_factor += 1
1253
1254            gobject.timeout_add(self.__move_pass,
1255                                self.__move_in_direction,
1256                                direction)
1257
1258        return False
1259
1260    def __rotate_is_clicked(self, pointer):
1261        """
1262        """
1263        xn, yn = self.__rotate_node.to_cartesian()
1264        xc, yc = self.__center_of_widget
1265
1266        center = (xc + xn, yc - yn)
1267        return geometry.is_in_circle(pointer, self.__rotate_radius, center)
1268
1269    def __center_is_clicked(self, pointer):
1270        """
1271        """
1272        return geometry.is_in_circle(pointer, self.__move_radius,
1273                self.__center_of_widget)
1274
1275    def __move_is_clicked(self, pointer):
1276        """
1277        """
1278        xc, yc = self.__center_of_widget
1279        pc = PolarCoordinate()
1280
1281        for i in range(8):
1282
1283            pc.set_coordinate(23, 45 * i)
1284            x, y = pc.to_cartesian()
1285
1286            center = (xc + x, yc - y)
1287            if geometry.is_in_circle(pointer, self.__move_radius, center):
1288                return i
1289
1290        return None
1291