A Discrete-Event Network Simulator
API
core.py
Go to the documentation of this file.
1# -*- Mode: python; coding: utf-8 -*-
2from ctypes import c_double
3
4LAYOUT_ALGORITHM = 'neato' # ['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
5REPRESENT_CHANNELS_AS_NODES = 1
6DEFAULT_NODE_SIZE = 1.0 # default node size in meters
7DEFAULT_TRANSMISSIONS_MEMORY = 5 # default number of of past intervals whose transmissions are remembered
8BITRATE_FONT_SIZE = 10
9
10# internal constants, normally not meant to be changed
11SAMPLE_PERIOD = 0.1
12PRIORITY_UPDATE_MODEL = -100
13PRIORITY_UPDATE_VIEW = 200
14
15import warnings
16import platform
17if platform.system() == "Windows":
18 SHELL_FONT = "Lucida Console 9"
19else:
20 SHELL_FONT = "Luxi Mono 10"
21
22import math
23import os
24import sys
25
26try:
27 import threading
28except ImportError:
29 import dummy_threading as threading
30
31try:
32 import pygraphviz
33except ImportError:
34 print("Pygraphviz is required by the visualizer module and could not be found")
35 exit(1)
36
37try:
38 import cairo
39except ImportError:
40 print("Pycairo is required by the visualizer module and could not be found")
41 exit(1)
42
43try:
44 import gi
45except ImportError:
46 print("PyGObject is required by the visualizer module and could not be found")
47 exit(1)
48
49try:
50 import svgitem
51except ImportError:
52 svgitem = None
53
54try:
55 gi.require_version('GooCanvas', '2.0')
56 gi.require_version('Gtk', '3.0')
57 gi.require_version('Gdk', '3.0')
58 gi.require_foreign("cairo")
59 from gi.repository import GObject
60 from gi.repository import GLib
61 from gi.repository import Gtk
62 from gi.repository import Gdk
63 from gi.repository import Pango
64 from gi.repository import GooCanvas
65 from . import hud
66except ImportError as e:
67 _import_error = e
68else:
69 _import_error = None
70
71try:
72 import ipython_viewxxxxxxxxxx
73except ImportError:
74 ipython_view = None
75
76from .base import InformationWindow, PyVizObject, Link, lookup_netdevice_traits, PIXELS_PER_METER
77from .base import transform_distance_simulation_to_canvas, transform_point_simulation_to_canvas
78from .base import transform_distance_canvas_to_simulation, transform_point_canvas_to_simulation
79from .base import load_plugins, register_plugin, plugins
80
81PI_OVER_2 = math.pi/2
82PI_TIMES_2 = math.pi*2
83
84
86
120
121
124 __gsignals__ = {
125 'query-extra-tooltip-info': (GObject.SignalFlags.RUN_LAST, None, (object,)),
126 }
127
128 def __init__(self, visualizer, node_index):
129 """! Initialize function.
130 @param self The object pointer.
131 @param visualizer visualizer object
132 @param node_index node index
133 """
134 super(Node, self).__init__()
135
136 self.visualizer = visualizer
137 self.node_index = node_index
138 self.canvas_item = GooCanvas.CanvasEllipse()
139 self.canvas_item.pyviz_object = self
140 self.links = []
141 self._has_mobility = None
142 self._selected = False
143 self._highlighted = False
144 self._color = 0x808080ff
145 self._size = DEFAULT_NODE_SIZE
146 self.canvas_item.connect("enter-notify-event", self.on_enter_notify_event)
147 self.canvas_item.connect("leave-notify-event", self.on_leave_notify_event)
148 self.menu = None
149 self.svg_item = None
150 self.svg_align_x = None
151 self.svg_align_y = None
152 self._label = None
154
155 self._update_appearance() # call this last
156
157 def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5):
158 """!
159 Set a background SVG icon for the node.
160
161 @param file_base_name: base file name, including .svg
162 extension, of the svg file. Place the file in the folder
163 src/contrib/visualizer/resource.
164
165 @param width: scale to the specified width, in meters
166 @param height: scale to the specified height, in meters
167
168 @param align_x: horizontal alignment of the icon relative to
169 the node position, from 0 (icon fully to the left of the node)
170 to 1.0 (icon fully to the right of the node)
171
172 @param align_y: vertical alignment of the icon relative to the
173 node position, from 0 (icon fully to the top of the node) to
174 1.0 (icon fully to the bottom of the node)
175
176 @return a ValueError exception if invalid dimensions.
177
178 """
179 if width is None and height is None:
180 raise ValueError("either width or height must be given")
181 rsvg_handle = svgitem.rsvg_handle_factory(file_base_name)
182 x = self.canvas_item.props.center_x
183 y = self.canvas_item.props.center_y
184 self.svg_item = svgitem.SvgItem(x, y, rsvg_handle)
185 self.svg_item.props.parent = self.visualizer.canvas.get_root_item()
186 self.svg_item.props.pointer_events = GooCanvas.CanvasPointerEvents.NONE
187 self.svg_item.lower(None)
188 self.svg_item.props.visibility = GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD
189 if width is not None:
190 self.svg_item.props.width = transform_distance_simulation_to_canvas(width)
191 if height is not None:
192 self.svg_item.props.height = transform_distance_simulation_to_canvas(height)
193
194 #threshold1 = 10.0/self.svg_item.props.height
195 #threshold2 = 10.0/self.svg_item.props.width
196 #self.svg_item.props.visibility_threshold = min(threshold1, threshold2)
197
198 self.svg_align_x = align_x
199 self.svg_align_y = align_y
200 self._update_svg_position(x, y)
201 self._update_appearance()
202
203 def set_label(self, label):
204 """!
205 Set a label for the node.
206
207 @param self: class object.
208 @param label: label to set
209
210 @return: an exception if invalid parameter.
211 """
212 assert isinstance(label, basestring)
213 self._label = label
214 self._update_appearance()
215
216 def _update_svg_position(self, x, y):
217 """!
218 Update svg position.
219
220 @param self: class object.
221 @param x: x position
222 @param y: y position
223 @return none
224 """
225 w = self.svg_item.width
226 h = self.svg_item.height
227 self.svg_item.set_properties(x=(x - (1-self.svg_align_x)*w),
228 y=(y - (1-self.svg_align_y)*h))
229
230
231 def tooltip_query(self, tooltip):
232 """!
233 Query tooltip.
234
235 @param self: class object.
236 @param tooltip: tooltip
237 @return none
238 """
239 self.visualizer.simulation.lock.acquire()
240 try:
241 ns3_node = ns.NodeList.GetNode(self.node_index)
242 ipv4 = ns.cppyy.gbl.getNodeIpv4(ns3_node)
243 ipv6 = ns.cppyy.gbl.getNodeIpv6(ns3_node)
244
245 name = '<b><u>Node %i</u></b>' % self.node_index
246 node_name = ns.Names.FindName (ns3_node)
247 if len(node_name)!=0:
248 name += ' <b>(' + node_name + ')</b>'
249
250 lines = [name]
251 lines.append('')
252
253 self.emit("query-extra-tooltip-info", lines)
254
255 mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
256 if mob:
257 mobility_model_name = ns.cppyy.gbl.getMobilityModelName(ns3_node)
258 lines.append(' <b>Mobility Model</b>: %s' % ns.cppyy.gbl.getMobilityModelName(ns3_node))
259
260 for devI in range(ns3_node.GetNDevices()):
261 lines.append('')
262 lines.append(' <u>NetDevice %i:</u>' % devI)
263 dev = ns3_node.GetDevice(devI)
264 name = ns.Names.FindName(dev)
265 if name:
266 lines.append(' <b>Name:</b> %s' % name)
267 devname = dev.GetInstanceTypeId().GetName()
268 lines.append(' <b>Type:</b> %s' % devname)
269
270 if ipv4 is not None:
271 ipv4_idx = ipv4.GetInterfaceForDevice(dev)
272 if ipv4_idx != -1:
273 addresses = [
274 '%s/%s' % (ipv4.GetAddress(ipv4_idx, i).GetLocal(),
275 ipv4.GetAddress(ipv4_idx, i).GetMask())
276 for i in range(ipv4.GetNAddresses(ipv4_idx))]
277 lines.append(' <b>IPv4 Addresses:</b> %s' % '; '.join(addresses))
278
279 if ipv6 is not None:
280 ipv6_idx = ipv6.GetInterfaceForDevice(dev)
281 if ipv6_idx != -1:
282 addresses = [
283 '%s/%s' % (ipv6.GetAddress(ipv6_idx, i).GetAddress(),
284 ipv6.GetAddress(ipv6_idx, i).GetPrefix())
285 for i in range(ipv6.GetNAddresses(ipv6_idx))]
286 lines.append(' <b>IPv6 Addresses:</b> %s' % '; '.join(addresses))
287
288 lines.append(' <b>MAC Address:</b> %s' % (dev.GetAddress(),))
289
290 tooltip.set_markup('\n'.join(lines))
291 finally:
292 self.visualizer.simulation.lock.release()
293
294 def on_enter_notify_event(self, view, target, event):
295 """!
296 On Enter event handle.
297
298 @param self: class object.
299 @param view: view
300 @param target: target
301 @param event: event
302 @return none
303 """
304 self.highlighted = True
305 def on_leave_notify_event(self, view, target, event):
306 """!
307 On Leave event handle.
308
309 @param self: class object.
310 @param view: view
311 @param target: target
312 @param event: event
313 @return none
314 """
315 self.highlighted = False
316
317 def _set_selected(self, value):
318 """!
319 Set selected function.
320
321 @param self: class object.
322 @param value: selected value
323 @return none
324 """
325 self._selected = value
326 self._update_appearance()
327 def _get_selected(self):
328 """!
329 Get selected function.
330
331 @param self: class object.
332 @return selected status
333 """
334 return self._selected
335
336 selected = property(_get_selected, _set_selected)
337
338 def _set_highlighted(self, value):
339 """!
340 Set highlighted function.
341
342 @param self: class object.
343 @param value: selected value
344 @return none
345 """
346 self._highlighted = value
347 self._update_appearance()
349 """!
350 Get highlighted function.
351
352 @param self: class object.
353 @return highlighted status
354 """
355 return self._highlighted
356
357 highlighted = property(_get_highlighted, _set_highlighted)
358
359 def set_size(self, size):
360 """!
361 Set size function.
362
363 @param self: class object.
364 @param size: selected size
365 @return none
366 """
367 self._size = size
368 self._update_appearance()
369
371 """!
372 Update the node aspect to reflect the selected/highlighted state
373
374 @param self: class object.
375 @return none
376 """
377
379 if self.svg_item is not None:
380 alpha = 0x80
381 else:
382 alpha = 0xff
383 fill_color_rgba = (self._color & 0xffffff00) | alpha
384 self.canvas_item.set_properties(radius_x=size, radius_y=size,
385 fill_color_rgba=fill_color_rgba)
386 if self._selected:
387 line_width = size*.3
388 else:
389 line_width = size*.15
390 if self.highlighted:
391 stroke_color = 'yellow'
392 else:
393 stroke_color = 'black'
394 self.canvas_item.set_properties(line_width=line_width, stroke_color=stroke_color)
395
396 if self._label is not None:
397 if self._label_canvas_item is None:
398 self._label_canvas_item = GooCanvas.CanvasText(visibility_threshold=0.5,
399 font="Sans Serif 10",
400 fill_color_rgba=0x808080ff,
401 alignment=Pango.Alignment.CENTER,
402 anchor=GooCanvas.CanvasAnchorType.N,
403 parent=self.visualizer.canvas.get_root_item(),
404 pointer_events=GooCanvas.CanvasPointerEvents.NONE)
405 self._label_canvas_item.lower(None)
406
407 self._label_canvas_item.set_properties(visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
408 text=self._label)
409 self._update_position()
410
411 def set_position(self, x, y):
412 """!
413 Set position function.
414
415 @param self: class object.
416 @param x: x position
417 @param y: y position
418 @return none
419 """
420 self.canvas_item.set_property("center_x", x)
421 self.canvas_item.set_property("center_y", y)
422 if self.svg_item is not None:
423 self._update_svg_position(x, y)
424
425 for link in self.links:
426 link.update_points()
427
428 if self._label_canvas_item is not None:
429 self._label_canvas_item.set_properties(x=x, y=(y+self._size*3))
430
431 # If the location of the point is now beyond the bounds of the
432 # canvas then those bounds now need to be increased
433 try:
434 bounds = self.visualizer.canvas.get_bounds()
435
436 (min_x, min_y, max_x, max_y) = bounds
437
438 min_x = min(x, min_x)
439 min_y = min(y, min_y)
440 max_x = max(x, max_x)
441 max_y = max(y, max_y)
442
443 new_bounds = (min_x, min_y, max_x, max_y)
444
445 if new_bounds != bounds:
446 self.visualizer.canvas.set_bounds(*new_bounds)
447 except TypeError:
448 # bug 2969: GooCanvas.Canvas.get_bounds() inconsistency
449 pass
450
451 def get_position(self):
452 """!
453 Get position function.
454
455 @param self: class object.
456 @return x and y position
457 """
458 return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
459
461 """!
462 Update position function.
463
464 @param self: class object.
465 @return none
466 """
467 x, y = self.get_position()
468 self.set_position(x, y)
469
470 def set_color(self, color):
471 """!
472 Set color function.
473
474 @param self: class object.
475 @param color: color to set.
476 @return none
477 """
478 if isinstance(color, str):
479 color = Gdk.color_parse(color)
480 color = ((color.red>>8) << 24) | ((color.green>>8) << 16) | ((color.blue>>8) << 8) | 0xff
481 self._color = color
482 self._update_appearance()
483
484 def add_link(self, link):
485 """!
486 Add link function.
487
488 @param self: class object.
489 @param link: link to add.
490 @return none
491 """
492 assert isinstance(link, Link)
493 self.links.append(link)
494
495 def remove_link(self, link):
496 """!
497 Remove link function.
498
499 @param self: class object.
500 @param link: link to add.
501 @return none
502 """
503 assert isinstance(link, Link)
504 self.links.remove(link)
505
506 @property
507 def has_mobility(self):
508 """!
509 Has mobility function.
510
511 @param self: class object.
512 @return modility option
513 """
514 if self._has_mobility is None:
515 node = ns.NodeList.GetNode(self.node_index)
516 self._has_mobility = ns.cppyy.gbl.hasMobilityModel(node)
517 return self._has_mobility
518
519
520
522
529 def __init__(self, channel):
530 """!
531 Initializer function.
532
533 @param self: class object.
534 @param channel: channel.
535 """
536 self.channel = channel
537 self.canvas_item = GooCanvas.CanvasEllipse(radius_x=30, radius_y=30,
538 fill_color="white",
539 stroke_color="grey", line_width=2.0,
540 line_dash=GooCanvas.CanvasLineDash.newv([10.0, 10.0 ]),
541 visibility=GooCanvas.CanvasItemVisibility.VISIBLE)
542 self.canvas_item.pyviz_object = self
543 self.links = []
544
545 def set_position(self, x, y):
546 """!
547 Initializer function.
548
549 @param self: class object.
550 @param x: x position.
551 @param y: y position.
552 @return
553 """
554 self.canvas_item.set_property("center_x", x)
555 self.canvas_item.set_property("center_y", y)
556
557 for link in self.links:
558 link.update_points()
559
560 def get_position(self):
561 """!
562 Initializer function.
563
564 @param self: class object.
565 @return x / y position.
566 """
567 return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
568
569
570
572
579 def __init__(self, node1, node2):
580 """!
581 Initializer function.
582
583 @param self: class object.
584 @param node1: class object.
585 @param node2: class object.
586 """
587 assert isinstance(node1, Node)
588 assert isinstance(node2, (Node, Channel))
589 self.node1 = node1
590 self.node2 = node2
591 self.canvas_item = GooCanvas.CanvasPath(line_width=1.0, stroke_color="black")
592 self.canvas_item.pyviz_object = self
593 self.node1.links.append(self)
594 self.node2.links.append(self)
595
596 def update_points(self):
597 """!
598 Update points function.
599
600 @param self: class object.
601 @return none
602 """
603 pos1_x, pos1_y = self.node1.get_position()
604 pos2_x, pos2_y = self.node2.get_position()
605 self.canvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y))
606
607
608
609class SimulationThread(threading.Thread):
610
624 def __init__(self, viz):
625 """!
626 Initializer function.
627
628 @param self: class object.
629 @param viz: class object.
630 """
631 super(SimulationThread, self).__init__()
632 assert isinstance(viz, Visualizer)
633 self.viz = viz # Visualizer object
634 self.lock = threading.Lock()
635 self.go = threading.Event()
636 self.go.clear()
637 self.target_time = 0 # in seconds
638 self.quit = False
639 self.sim_helper = ns.PyViz()
641
642 def set_nodes_of_interest(self, nodes):
643 """!
644 Set nodes of interest function.
645
646 @param self: class object.
647 @param nodes: class object.
648 @return
649 """
650 self.lock.acquire()
651 try:
652 self.sim_helper.SetNodesOfInterest(nodes)
653 finally:
654 self.lock.release()
655
656 def run(self):
657 """!
658 Initializer function.
659
660 @param self: class object.
661 @return none
662 """
663 while not self.quit:
664 #print "sim: Wait for go"
665 self.go.wait() # wait until the main (view) thread gives us the go signal
666 self.go.clear()
667 if self.quit:
668 break
669 #self.go.clear()
670 #print "sim: Acquire lock"
671 self.lock.acquire()
672 try:
673 if 0:
675 self.viz.play_button.set_sensitive(False)
676 break
677 #print "sim: Current time is %f; Run until: %f" % (ns3.Simulator.Now ().GetSeconds (), self.target_time)
678 #if ns3.Simulator.Now ().GetSeconds () > self.target_time:
679 # print "skipping, model is ahead of view!"
680 self.sim_helper.SimulatorRunUntil(ns.Seconds(self.target_time))
681 #print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds ()
682 self.pause_messages.extend(self.sim_helper.GetPauseMessages())
683 GLib.idle_add(self.viz.update_model, priority=PRIORITY_UPDATE_MODEL)
684 #print "sim: Run until: ", self.target_time, ": finished."
685 finally:
686 self.lock.release()
687 #print "sim: Release lock, loop."
688
689
691
697
698
699 __slots__ = []
700ShowTransmissionsMode.ALL = ShowTransmissionsMode()
701ShowTransmissionsMode.NONE = ShowTransmissionsMode()
702ShowTransmissionsMode.SELECTED = ShowTransmissionsMode()
703
704
705class Visualizer(GObject.GObject):
706
708 INSTANCE = None
709
710 if _import_error is None:
711 __gsignals__ = {
712
713 # signal emitted whenever a right-click-on-node popup menu is being constructed
714 'populate-node-menu': (GObject.SignalFlags.RUN_LAST, None, (object, Gtk.Menu,)),
715
716 # signal emitted after every simulation period (SAMPLE_PERIOD seconds of simulated time)
717 # the simulation lock is acquired while the signal is emitted
718 'simulation-periodic-update': (GObject.SignalFlags.RUN_LAST, None, ()),
719
720 # signal emitted right after the topology is scanned
721 'topology-scanned': (GObject.SignalFlags.RUN_LAST, None, ()),
722
723 # signal emitted when it's time to update the view objects
724 'update-view': (GObject.SignalFlags.RUN_LAST, None, ()),
725
726 }
727
728 def __init__(self):
729 """!
730 Initializer function.
731
732 @param self: class object.
733 @return none
734 """
735 assert Visualizer.INSTANCE is None
736 Visualizer.INSTANCE = self
737 super(Visualizer, self).__init__()
738 self.nodes = {} # node index -> Node
739 self.channels = {} # id(ns3.Channel) -> Channel
740 self.window = None # toplevel window
741 self.canvas = None # GooCanvas.Canvas
742 self.time_label = None # Gtk.Label
743 self.play_button = None # Gtk.ToggleButton
744 self.zoom = None # Gtk.Adjustment
745 self._scrolled_window = None # Gtk.ScrolledWindow
746
747 self.links_group = GooCanvas.CanvasGroup()
748 self.channels_group = GooCanvas.CanvasGroup()
749 self.nodes_group = GooCanvas.CanvasGroup()
750
751 self._update_timeout_id = None
752 self.simulation = SimulationThread(self)
753 self.selected_node = None # node currently selected
754 self.speed = 1.0
755 self.information_windows = []
756 self._transmission_arrows = []
757 self._last_transmissions = []
758 self._drop_arrows = []
759 self._last_drops = []
760 self._show_transmissions_mode = None
761 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
762 self._panning_state = None
763 self.node_size_adjustment = None
764 self.transmissions_smoothing_adjustment = None
765 self.sample_period = SAMPLE_PERIOD
766 self.node_drag_state = None
767 self.follow_node = None
768 self.shell_window = None
769
770 self.create_gui()
771
772 for plugin in plugins:
773 plugin(self)
774
775 def set_show_transmissions_mode(self, mode):
776 """!
777 Set show transmission mode.
778
779 @param self: class object.
780 @param mode: mode to set.
781 @return none
782 """
783 assert isinstance(mode, ShowTransmissionsMode)
784 self._show_transmissions_mode = mode
785 if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
786 self.simulation.set_nodes_of_interest(list(range(ns.NodeList.GetNNodes())))
787 elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
788 self.simulation.set_nodes_of_interest([])
789 elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
790 if self.selected_node is None:
791 self.simulation.set_nodes_of_interest([])
792 else:
793 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
794
795 def _create_advanced_controls(self):
796 """!
797 Create advanced controls.
798
799 @param self: class object.
800 @return expander
801 """
802 expander = Gtk.Expander.new("Advanced")
803 expander.show()
804
805 main_vbox = GObject.new(Gtk.VBox, border_width=8, visible=True)
806 expander.add(main_vbox)
807
808 main_hbox1 = GObject.new(Gtk.HBox, border_width=8, visible=True)
809 main_vbox.pack_start(main_hbox1, True, True, 0)
810
811 show_transmissions_group = GObject.new(Gtk.HeaderBar,
812 title="Show transmissions",
813 visible=True)
814 main_hbox1.pack_start(show_transmissions_group, False, False, 8)
815
816 vbox = Gtk.VBox(homogeneous=True, spacing=4)
817 vbox.show()
818 show_transmissions_group.add(vbox)
819
820 all_nodes = Gtk.RadioButton.new(None)
821 all_nodes.set_label("All nodes")
822 all_nodes.set_active(True)
823 all_nodes.show()
824 vbox.add(all_nodes)
825
826 selected_node = Gtk.RadioButton.new_from_widget(all_nodes)
827 selected_node.show()
828 selected_node.set_label("Selected node")
829 selected_node.set_active(False)
830 vbox.add(selected_node)
831
832 no_node = Gtk.RadioButton.new_from_widget(all_nodes)
833 no_node.show()
834 no_node.set_label("Disabled")
835 no_node.set_active(False)
836 vbox.add(no_node)
837
838 def toggled(radio):
839 if radio.get_active():
840 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
841 all_nodes.connect("toggled", toggled)
842
843 def toggled(radio):
844 if radio.get_active():
845 self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
846 no_node.connect("toggled", toggled)
847
848 def toggled(radio):
849 if radio.get_active():
850 self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
851 selected_node.connect("toggled", toggled)
852
853 # -- misc settings
854 misc_settings_group = GObject.new(Gtk.HeaderBar, title="Misc Settings", visible=True)
855 main_hbox1.pack_start(misc_settings_group, False, False, 8)
856 settings_hbox = GObject.new(Gtk.HBox, border_width=8, visible=True)
857 misc_settings_group.add(settings_hbox)
858
859 # --> node size
860 vbox = GObject.new(Gtk.VBox, border_width=0, visible=True)
861 scale = GObject.new(Gtk.HScale, visible=True, digits=2)
862 vbox.pack_start(scale, True, True, 0)
863 vbox.pack_start(GObject.new(Gtk.Label, label="Node Size", visible=True), True, True, 0)
864 settings_hbox.pack_start(vbox, False, False, 6)
865 self.node_size_adjustment = scale.get_adjustment()
866 def node_size_changed(adj):
867 for node in self.nodes.values():
868 node.set_size(adj.get_value())
869 self.node_size_adjustment.connect("value-changed", node_size_changed)
870 self.node_size_adjustment.set_lower(0.01)
871 self.node_size_adjustment.set_upper(20)
872 self.node_size_adjustment.set_step_increment(0.1)
873 self.node_size_adjustment.set_value(DEFAULT_NODE_SIZE)
874
875 # --> transmissions smooth factor
876 vbox = GObject.new(Gtk.VBox, border_width=0, visible=True)
877 scale = GObject.new(Gtk.HScale, visible=True, digits=1)
878 vbox.pack_start(scale, True, True, 0)
879 vbox.pack_start(GObject.new(Gtk.Label, label="Tx. Smooth Factor (s)", visible=True), True, True, 0)
880 settings_hbox.pack_start(vbox, False, False, 6)
881 self.transmissions_smoothing_adjustment = scale.get_adjustment()
882 adj = self.transmissions_smoothing_adjustment
883 adj.set_lower(0.1)
884 adj.set_upper(10)
885 adj.set_step_increment(0.1)
886 adj.set_value(DEFAULT_TRANSMISSIONS_MEMORY*0.1)
887
888 return expander
889
890
891 class _PanningState(object):
892
894 __slots__ = ['initial_mouse_pos', 'initial_canvas_pos', 'motion_signal']
895
896 def _begin_panning(self, widget, event):
897 """!
898 Set show trnamission mode.
899
900 @param self: class object.
901 @param mode: mode to set.
902 @return none
903 """
904 display = self.canvas.get_window().get_display()
905 cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.FLEUR)
906 self.canvas.get_window().set_cursor(cursor)
907 self._panning_state = self._PanningState()
908 pos = widget.get_window().get_device_position(event.device)
909 self._panning_state.initial_mouse_pos = (pos.x, pos.y)
910 x = self._scrolled_window.get_hadjustment().get_value()
911 y = self._scrolled_window.get_vadjustment().get_value()
912 self._panning_state.initial_canvas_pos = (x, y)
913 self._panning_state.motion_signal = self.canvas.connect("motion-notify-event", self._panning_motion)
914
915 def _end_panning(self, event):
916 """!
917 End panning function.
918
919 @param self: class object.
920 @param event: active event.
921 @return none
922 """
923 if self._panning_state is None:
924 return
925 self.canvas.get_window().set_cursor(None)
926 self.canvas.disconnect(self._panning_state.motion_signal)
927 self._panning_state = None
928
929 def _panning_motion(self, widget, event):
930 """!
931 Panning motion function.
932
933 @param self: class object.
934 @param widget: widget.
935 @param event: event.
936 @return true if successful
937 """
938 assert self._panning_state is not None
939 if event.is_hint:
940 pos = widget.get_window().get_device_position(event.device)
941 x, y = pos.x, pos.y
942 else:
943 x, y = event.x, event.y
944
945 hadj = self._scrolled_window.get_hadjustment()
946 vadj = self._scrolled_window.get_vadjustment()
947 mx0, my0 = self._panning_state.initial_mouse_pos
948 cx0, cy0 = self._panning_state.initial_canvas_pos
949
950 dx = x - mx0
951 dy = y - my0
952 hadj.set_value(cx0 - dx)
953 vadj.set_value(cy0 - dy)
954 return True
955
956 def _canvas_button_press(self, widget, event):
957 if event.button == 2:
958 self._begin_panning(widget, event)
959 return True
960 return False
961
962 def _canvas_button_release(self, dummy_widget, event):
963 if event.button == 2:
964 self._end_panning(event)
965 return True
966 return False
967
968 def _canvas_scroll_event(self, dummy_widget, event):
969 if event.direction == Gdk.ScrollDirection.UP:
970 self.zoom.set_value(self.zoom.get_value() * 1.25)
971 return True
972 elif event.direction == Gdk.ScrollDirection.DOWN:
973 self.zoom.set_value(self.zoom.get_value() / 1.25)
974 return True
975 return False
976
977 def get_hadjustment(self):
978 return self._scrolled_window.get_hadjustment()
979 def get_vadjustment(self):
980 return self._scrolled_window.get_vadjustment()
981
982 def create_gui(self):
983 self.window = Gtk.Window()
984 vbox = Gtk.VBox()
985 vbox.show()
986 self.window.add(vbox)
987
988 # canvas
989 self.canvas = GooCanvas.Canvas()
990 self.canvas.connect_after("button-press-event", self._canvas_button_press)
991 self.canvas.connect_after("button-release-event", self._canvas_button_release)
992 self.canvas.connect("scroll-event", self._canvas_scroll_event)
993 self.canvas.props.has_tooltip = True
994 self.canvas.connect("query-tooltip", self._canvas_tooltip_cb)
995 self.canvas.show()
996 sw = Gtk.ScrolledWindow(); sw.show()
997 self._scrolled_window = sw
998 sw.add(self.canvas)
999 vbox.pack_start(sw, True, True, 4)
1000 self.canvas.set_size_request(600, 450)
1001 self.canvas.set_bounds(-10000, -10000, 10000, 10000)
1002 self.canvas.scroll_to(0, 0)
1003
1004
1005 self.canvas.get_root_item().add_child(self.links_group, -1)
1006 self.links_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1007
1008 self.canvas.get_root_item().add_child(self.channels_group, -1)
1009 self.channels_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1010 self.channels_group.raise_(self.links_group)
1011
1012 self.canvas.get_root_item().add_child(self.nodes_group, -1)
1013 self.nodes_group.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1014 self.nodes_group.raise_(self.channels_group)
1015
1016 self.hud = hud.Axes(self)
1017
1018 hbox = Gtk.HBox(); hbox.show()
1019 vbox.pack_start(hbox, False, False, 4)
1020
1021 # zoom
1022 zoom_adj = Gtk.Adjustment(value=1.0, lower=0.01, upper=10.0,
1023 step_increment=0.02,
1024 page_increment=1.0,
1025 page_size=1.0)
1026 self.zoom = zoom_adj
1027 def _zoom_changed(adj):
1028 self.canvas.set_scale(adj.get_value())
1029 zoom_adj.connect("value-changed", _zoom_changed)
1030 zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
1031 zoom.set_digits(3)
1032 zoom.show()
1033 hbox.pack_start(GObject.new(Gtk.Label, label=" Zoom:", visible=True), False, False, 4)
1034 hbox.pack_start(zoom, False, False, 4)
1035 _zoom_changed(zoom_adj)
1036
1037 # speed
1038 speed_adj = Gtk.Adjustment(value=1.0, lower=0.01, upper=10.0,
1039 step_increment=0.02,
1040 page_increment=1.0, page_size=0)
1041 def _speed_changed(adj):
1042 self.speed = adj.get_value()
1043 self.sample_period = SAMPLE_PERIOD*adj.get_value()
1044 self._start_update_timer()
1045 speed_adj.connect("value-changed", _speed_changed)
1046 speed = Gtk.SpinButton.new(speed_adj, 1, 0)
1047 speed.set_digits(3)
1048 speed.show()
1049 hbox.pack_start(GObject.new(Gtk.Label, label=" Speed:", visible=True), False, False, 4)
1050 hbox.pack_start(speed, False, False, 4)
1051 _speed_changed(speed_adj)
1052
1053 # Current time
1054 self.time_label = GObject.new(Gtk.Label, label=" Speed:", visible=True)
1055 self.time_label.set_width_chars(20)
1056 hbox.pack_start(self.time_label, False, False, 4)
1057
1058 # Screenshot button
1059 screenshot_button = GObject.new(Gtk.Button,
1060 label="Snapshot",
1061 relief=Gtk.ReliefStyle.NONE, focus_on_click=False,
1062 visible=True)
1063 hbox.pack_start(screenshot_button, False, False, 4)
1064
1065 def load_button_icon(button, icon_name):
1066 if not Gtk.IconTheme.get_default().has_icon(icon_name):
1067 print(f"Could not load icon {icon_name}", file=sys.stderr)
1068 return
1069 image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
1070 button.set_image(image)
1071 button.props.always_show_image = True
1072
1073 load_button_icon(screenshot_button, "applets-screenshooter")
1074 screenshot_button.connect("clicked", self._take_screenshot)
1075
1076 # Shell button
1077 if ipython_view is not None:
1078 shell_button = GObject.new(Gtk.Button,
1079 label="Shell",
1080 relief=Gtk.ReliefStyle.NONE, focus_on_click=False,
1081 visible=True)
1082 hbox.pack_start(shell_button, False, False, 4)
1083 load_button_icon(shell_button, "gnome-terminal")
1084 shell_button.connect("clicked", self._start_shell)
1085
1086 # Play button
1087 self.play_button = GObject.new(Gtk.ToggleButton,
1088 label="Simulate (F3)",
1089 relief=Gtk.ReliefStyle.NONE, focus_on_click=False,
1090 visible=True)
1091 load_button_icon(self.play_button, "media-playback-start")
1092 accel_group = Gtk.AccelGroup()
1093 self.window.add_accel_group(accel_group)
1094 self.play_button.add_accelerator("clicked", accel_group,
1095 Gdk.KEY_F3, 0, Gtk.AccelFlags.VISIBLE)
1096 self.play_button.connect("toggled", self._on_play_button_toggled)
1097 hbox.pack_start(self.play_button, False, False, 4)
1098
1099 self.canvas.get_root_item().connect("button-press-event", self.on_root_button_press_event)
1100
1101 vbox.pack_start(self._create_advanced_controls(), False, False, 4)
1102
1103 display = Gdk.Display.get_default()
1104 try:
1105 monitor = display.get_primary_monitor()
1106 geometry = monitor.get_geometry()
1107 scale_factor = monitor.get_scale_factor()
1108 except AttributeError:
1109 screen = display.get_default_screen()
1110 monitor_id = screen.get_primary_monitor()
1111 geometry = screen.get_monitor_geometry(monitor_id)
1112 scale_factor = screen.get_monitor_scale_factor(monitor_id)
1113 width = scale_factor * geometry.width
1114 height = scale_factor * geometry.height
1115 self.window.set_default_size(width * 2 / 3, height * 2 / 3)
1116 self.window.show()
1117
1118 def scan_topology(self):
1119 print("scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1120 graph = pygraphviz.AGraph()
1121 seen_nodes = 0
1122 for nodeI in range(ns.NodeList.GetNNodes()):
1123 seen_nodes += 1
1124 if seen_nodes == 100:
1125 print("scan topology... %i nodes visited (%.1f%%)" % (nodeI, 100*nodeI/ns.NodeList.GetNNodes()))
1126 seen_nodes = 0
1127 node = ns.NodeList.GetNode(nodeI)
1128 node_name = "Node %i" % nodeI
1129 node_view = self.get_node(nodeI)
1130
1131 mobility = ns.cppyy.gbl.hasMobilityModel(node)
1132 if mobility:
1133 node_view.set_color("red")
1134 pos = ns.cppyy.gbl.getNodePosition(node)
1135 node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1136 #print "node has mobility position -> ", "%f,%f" % (pos.x, pos.y)
1137 else:
1138 graph.add_node(node_name)
1139
1140 for devI in range(node.GetNDevices()):
1141 device = node.GetDevice(devI)
1142 device_traits = lookup_netdevice_traits(type(device.__deref__()))
1143 if device_traits.is_wireless:
1144 continue
1145 if device_traits.is_virtual:
1146 continue
1147 channel = device.GetChannel()
1148 if channel.GetNDevices() > 2:
1149 if REPRESENT_CHANNELS_AS_NODES:
1150 # represent channels as white nodes
1151 if mobility is None:
1152 channel_name = "Channel %s" % id(channel)
1153 graph.add_edge(node_name, channel_name)
1154 self.get_channel(channel)
1155 self.create_link(self.get_node(nodeI), self.get_channel(channel))
1156 else:
1157 # don't represent channels, just add links between nodes in the same channel
1158 for otherDevI in range(channel.GetNDevices()):
1159 otherDev = channel.GetDevice(otherDevI)
1160 otherNode = otherDev.GetNode()
1161 otherNodeView = self.get_node(otherNode.GetId())
1162 if otherNode is not node:
1163 if mobility is None and not otherNodeView.has_mobility:
1164 other_node_name = "Node %i" % otherNode.GetId()
1165 graph.add_edge(node_name, other_node_name)
1166 self.create_link(self.get_node(nodeI), otherNodeView)
1167 else:
1168 for otherDevI in range(channel.GetNDevices()):
1169 otherDev = channel.GetDevice(otherDevI)
1170 otherNode = otherDev.GetNode()
1171 otherNodeView = self.get_node(otherNode.GetId())
1172 if otherNode is not node:
1173 if mobility is None and not otherNodeView.has_mobility:
1174 other_node_name = "Node %i" % otherNode.GetId()
1175 graph.add_edge(node_name, other_node_name)
1176 self.create_link(self.get_node(nodeI), otherNodeView)
1177
1178 print("scanning topology: calling graphviz layout")
1179 graph.layout(LAYOUT_ALGORITHM)
1180 for node in graph.iternodes():
1181 #print node, "=>", node.attr['pos']
1182 node_type, node_id = node.split(' ')
1183 pos_x, pos_y = [float(s) for s in node.attr['pos'].split(',')]
1184 if node_type == 'Node':
1185 obj = self.nodes[int(node_id)]
1186 elif node_type == 'Channel':
1187 obj = self.channels[int(node_id)]
1188 obj.set_position(pos_x, pos_y)
1189
1190 print("scanning topology: all done.")
1191 self.emit("topology-scanned")
1192
1193 def get_node(self, index):
1194 try:
1195 return self.nodes[index]
1196 except KeyError:
1197 node = Node(self, index)
1198 self.nodes[index] = node
1199 self.nodes_group.add_child(node.canvas_item, -1)
1200 node.canvas_item.connect("button-press-event", self.on_node_button_press_event, node)
1201 node.canvas_item.connect("button-release-event", self.on_node_button_release_event, node)
1202 return node
1203
1204 def get_channel(self, ns3_channel):
1205 try:
1206 return self.channels[id(ns3_channel)]
1207 except KeyError:
1208 channel = Channel(ns3_channel)
1209 self.channels[id(ns3_channel)] = channel
1210 self.channels_group.add_child(channel.canvas_item, -1)
1211 return channel
1212
1213 def create_link(self, node, node_or_channel):
1214 link = WiredLink(node, node_or_channel)
1215 self.links_group.add_child(link.canvas_item, -1)
1216 link.canvas_item.lower(None)
1217
1218 def update_view(self):
1219 #print "update_view"
1220
1221 self.time_label.set_text("Time: %f s" % ns.Simulator.Now().GetSeconds())
1222
1223 self._update_node_positions()
1224
1225 # Update information
1226 for info_win in self.information_windows:
1227 info_win.update()
1228
1229 self._update_transmissions_view()
1230 self._update_drops_view()
1231
1232 self.emit("update-view")
1233
1234 def _update_node_positions(self):
1235 for node in self.nodes.values():
1236 if node.has_mobility:
1237 ns3_node = ns.NodeList.GetNode(node.node_index)
1238 mobility = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1239 if mobility:
1240 pos = ns.cppyy.gbl.getNodePosition(ns3_node)
1241 x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
1242 node.set_position(x, y)
1243 if node is self.follow_node:
1244 hadj = self._scrolled_window.get_hadjustment()
1245 vadj = self._scrolled_window.get_vadjustment()
1246 px, py = self.canvas.convert_to_pixels(x, y)
1247 hadj.set_value(px - hadj.get_page_size() / 2)
1248 vadj.set_value(py - vadj.get_page_size() / 2)
1249
1250 def center_on_node(self, node):
1251 if isinstance(node, ns.Node):
1252 node = self.nodes[node.GetId()]
1253 elif isinstance(node, int):
1254 node = self.nodes[node]
1255 elif isinstance(node, Node):
1256 pass
1257 else:
1258 raise TypeError("expected int, viz.Node or ns.Node, not %r" % node)
1259
1260 x, y = node.get_position()
1261 hadj = self._scrolled_window.get_hadjustment()
1262 vadj = self._scrolled_window.get_vadjustment()
1263 px, py = self.canvas.convert_to_pixels(x, y)
1264 hadj.set_value(px - hadj.get_page_size() / 2)
1265 vadj.set_value(py - vadj.get_page_size() / 2)
1266
1267 def update_model(self):
1268 self.simulation.lock.acquire()
1269 try:
1270 self.emit("simulation-periodic-update")
1271 finally:
1272 self.simulation.lock.release()
1273
1274 def do_simulation_periodic_update(self):
1275 smooth_factor = int(self.transmissions_smoothing_adjustment.get_value()*10)
1276
1277 transmissions = self.simulation.sim_helper.GetTransmissionSamples()
1278 self._last_transmissions.append(transmissions)
1279 while len(self._last_transmissions) > smooth_factor:
1280 self._last_transmissions.pop(0)
1281
1282 drops = self.simulation.sim_helper.GetPacketDropSamples()
1283 self._last_drops.append(drops)
1284 while len(self._last_drops) > smooth_factor:
1285 self._last_drops.pop(0)
1286
1287 def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
1288 hadj = self._scrolled_window.get_hadjustment()
1289 vadj = self._scrolled_window.get_vadjustment()
1290 bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1291 bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(hadj.get_value() + hadj.get_page_size(),
1292 vadj.get_value() + vadj.get_page_size())
1293 try:
1294 pos1_x, pos1_y, pos2_x, pos2_y = ns.PyViz.LineClipping(bounds_x1, bounds_y1,
1295 bounds_x2, bounds_y2,
1296 pos1_x, pos1_y,
1297 pos2_x, pos2_y)
1298 except:
1299 res = (0,0,0,0)
1300 pos1_x, pos1_y, pos2_x, pos2_y = res
1301 return (pos1_x + pos2_x)/2, (pos1_y + pos2_y)/2
1302
1303 def _update_transmissions_view(self):
1304 transmissions_average = {}
1305 for transmission_set in self._last_transmissions:
1306 for transmission in transmission_set:
1307 key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
1308 rx_bytes, count = transmissions_average.get(key, (0, 0))
1309 rx_bytes += transmission.bytes
1310 count += 1
1311 transmissions_average[key] = rx_bytes, count
1312
1313 old_arrows = self._transmission_arrows
1314 for arrow, label in old_arrows:
1315 arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1316 label.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1317 new_arrows = []
1318
1319 k = self.node_size_adjustment.get_value()/5
1320
1321 for (transmitter_id, receiver_id), (rx_bytes, rx_count) in transmissions_average.items():
1322 transmitter = self.get_node(transmitter_id)
1323 receiver = self.get_node(receiver_id)
1324 try:
1325 arrow, label = old_arrows.pop()
1326 except IndexError:
1327 arrow = GooCanvas.CanvasPolyline(line_width=2.0, stroke_color_rgba=0x00C000C0, close_path=False, end_arrow=True, pointer_events=GooCanvas.CanvasPointerEvents.NONE)
1328 arrow.set_property("parent", self.canvas.get_root_item())
1329 arrow.raise_(None)
1330
1331 label = GooCanvas.CanvasText(parent=self.canvas.get_root_item(), pointer_events=GooCanvas.CanvasPointerEvents.NONE)
1332 label.raise_(None)
1333
1334 arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1335 line_width = max(0.1, math.log(float(rx_bytes)/rx_count/self.sample_period)*k)
1336 arrow.set_property("line-width", line_width)
1337
1338 pos1_x, pos1_y = transmitter.get_position()
1339 pos2_x, pos2_y = receiver.get_position()
1340 points = GooCanvas.CanvasPoints.new(2)
1341 points.set_point(0, pos1_x, pos1_y)
1342 points.set_point(1, pos2_x, pos2_y)
1343 arrow.set_property("points", points)
1344
1345 kbps = float(rx_bytes*8)/1e3/rx_count/self.sample_period
1346 label.set_properties(visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1347 visibility_threshold=0.5,
1348 font=("Sans Serif %f" % int(1+BITRATE_FONT_SIZE*k)))
1349 angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1350 if -PI_OVER_2 <= angle <= PI_OVER_2:
1351 label.set_properties(text=("%.2f kbit/s →" % (kbps,)),
1352 alignment=Pango.Alignment.CENTER,
1353 anchor=GooCanvas.CanvasAnchorType.S,
1354 x=0, y=-line_width/2)
1355 else:
1356 label.set_properties(text=("← %.2f kbit/s" % (kbps,)),
1357 alignment=Pango.Alignment.CENTER,
1358 anchor=GooCanvas.CanvasAnchorType.N,
1359 x=0, y=line_width/2)
1360 M = cairo.Matrix()
1361 lx, ly = self._get_label_over_line_position(c_double(pos1_x), c_double(pos1_y),
1362 c_double(pos2_x), c_double(pos2_y))
1363 M.translate(lx, ly)
1364 M.rotate(angle)
1365 try:
1366 label.set_transform(M)
1367 except KeyError:
1368 # https://gitlab.gnome.org/GNOME/pygobject/issues/16
1369 warnings.warn("PyGobject bug causing label position error; "
1370 "should be fixed in PyGObject >= 3.29.1")
1371 label.set_properties(x=(lx + label.props.x),
1372 y=(ly + label.props.y))
1373
1374 new_arrows.append((arrow, label))
1375
1376 self._transmission_arrows = new_arrows + old_arrows
1377
1378
1379 def _update_drops_view(self):
1380 drops_average = {}
1381 for drop_set in self._last_drops:
1382 for drop in drop_set:
1383 key = drop.transmitter.GetId()
1384 drop_bytes, count = drops_average.get(key, (0, 0))
1385 drop_bytes += drop.bytes
1386 count += 1
1387 drops_average[key] = drop_bytes, count
1388
1389 old_arrows = self._drop_arrows
1390 for arrow, label in old_arrows:
1391 arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1392 label.set_property("visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1393 new_arrows = []
1394
1395 # get the coordinates for the edge of screen
1396 vadjustment = self._scrolled_window.get_vadjustment()
1397 bottom_y = vadjustment.get_value() + vadjustment.get_page_size()
1398 dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1399
1400 k = self.node_size_adjustment.get_value()/5
1401
1402 for transmitter_id, (drop_bytes, drop_count) in drops_average.items():
1403 transmitter = self.get_node(transmitter_id)
1404 try:
1405 arrow, label = old_arrows.pop()
1406 except IndexError:
1407 arrow = GooCanvas.CanvasPolyline(line_width=2.0, stroke_color_rgba=0xC00000C0, close_path=False, end_arrow=True, pointer_events=GooCanvas.CanvasPointerEvents.NONE)
1408 arrow.set_property("parent", self.canvas.get_root_item())
1409 arrow.raise_(None)
1410
1411 label = GooCanvas.CanvasText(pointer_events=GooCanvas.CanvasPointerEvents.NONE)#, fill_color_rgba=0x00C000C0)
1412 label.set_property("parent", self.canvas.get_root_item())
1413 label.raise_(None)
1414
1415 arrow.set_property("visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1416 arrow.set_property("line-width", max(0.1, math.log(float(drop_bytes)/drop_count/self.sample_period)*k))
1417 pos1_x, pos1_y = transmitter.get_position()
1418 pos2_x, pos2_y = pos1_x, edge_y
1419 points = GooCanvas.CanvasPoints.new(2)
1420 points.set_point(0, pos1_x, pos1_y)
1421 points.set_point(1, pos2_x, pos2_y)
1422 arrow.set_property("points", points)
1423
1424 label.set_properties(visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1425 visibility_threshold=0.5,
1426 font=("Sans Serif %i" % int(1+BITRATE_FONT_SIZE*k)),
1427 text=("%.2f kbit/s" % (float(drop_bytes*8)/1e3/drop_count/self.sample_period,)),
1428 alignment=Pango.Alignment.CENTER,
1429 x=(pos1_x + pos2_x)/2,
1430 y=(pos1_y + pos2_y)/2)
1431
1432 new_arrows.append((arrow, label))
1433
1434 self._drop_arrows = new_arrows + old_arrows
1435
1436
1437 def update_view_timeout(self):
1438 #print "view: update_view_timeout called at real time ", time.time()
1439
1440 # while the simulator is busy, run the gtk event loop
1441 while not self.simulation.lock.acquire(False):
1442 while Gtk.events_pending():
1443 Gtk.main_iteration()
1444 pause_messages = self.simulation.pause_messages
1445 self.simulation.pause_messages = []
1446 try:
1447 self.update_view()
1448 self.simulation.target_time = ns.Simulator.Now ().GetSeconds () + self.sample_period
1449 #print "view: target time set to %f" % self.simulation.target_time
1450 finally:
1451 self.simulation.lock.release()
1452
1453 if pause_messages:
1454 #print pause_messages
1455 dialog = Gtk.MessageDialog(parent=self.window, flags=0, type=Gtk.MessageType.WARNING, buttons=Gtk.ButtonsType.OK,
1456 message_format='\n'.join(pause_messages))
1457 dialog.connect("response", lambda d, r: d.destroy())
1458 dialog.show()
1459 self.play_button.set_active(False)
1460
1461 # if we're paused, stop the update timer
1462 if not self.play_button.get_active():
1463 self._update_timeout_id = None
1464 return False
1465
1466 #print "view: self.simulation.go.set()"
1467 self.simulation.go.set()
1468 #print "view: done."
1469 return True
1470
1471 def _start_update_timer(self):
1472 if self._update_timeout_id is not None:
1473 GLib.source_remove(self._update_timeout_id)
1474 #print "start_update_timer"
1475 self._update_timeout_id = GLib.timeout_add(int(SAMPLE_PERIOD/min(self.speed, 1)*1e3),
1476 self.update_view_timeout,
1477 priority=PRIORITY_UPDATE_VIEW)
1478
1479 def _on_play_button_toggled(self, button):
1480 if button.get_active():
1481 self._start_update_timer()
1482 else:
1483 if self._update_timeout_id is not None:
1484 GLib.source_remove(self._update_timeout_id)
1485
1486 def _quit(self, *dummy_args):
1487 if self._update_timeout_id is not None:
1488 GLib.source_remove(self._update_timeout_id)
1489 self._update_timeout_id = None
1490 self.simulation.quit = True
1491 self.simulation.go.set()
1492 self.simulation.join()
1493 Gtk.main_quit()
1494
1495 def _monkey_patch_ipython(self):
1496 # The user may want to access the NS 3 simulation state, but
1497 # NS 3 is not thread safe, so it could cause serious problems.
1498 # To work around this, monkey-patch IPython to automatically
1499 # acquire and release the simulation lock around each code
1500 # that is executed.
1501
1502 original_runcode = self.ipython.runcode
1503 def runcode(ip, *args):
1504 #print "lock"
1505 self.simulation.lock.acquire()
1506 try:
1507 return original_runcode(*args)
1508 finally:
1509 #print "unlock"
1510 self.simulation.lock.release()
1511 import types
1512 self.ipython.runcode = types.MethodType(runcode, self.ipython)
1513
1514 def autoscale_view(self):
1515 if not self.nodes:
1516 return
1517 self._update_node_positions()
1518 positions = [node.get_position() for node in self.nodes.values()]
1519 min_x, min_y = min(x for (x,y) in positions), min(y for (x,y) in positions)
1520 max_x, max_y = max(x for (x,y) in positions), max(y for (x,y) in positions)
1521 min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1522 max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1523 dx = max_x - min_x
1524 dy = max_y - min_y
1525 dx_px = max_x_px - min_x_px
1526 dy_px = max_y_px - min_y_px
1527 hadj = self._scrolled_window.get_hadjustment()
1528 vadj = self._scrolled_window.get_vadjustment()
1529 new_dx, new_dy = 1.5*dx_px, 1.5*dy_px
1530
1531 if new_dx == 0 or new_dy == 0:
1532 return
1533
1534 self.zoom.set_value(min(hadj.get_page_size()/new_dx, vadj.get_page_size()/new_dy))
1535
1536 x1, y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1537 x2, y2 = self.canvas.convert_from_pixels((hadj.get_value() +
1538 hadj.get_page_size()),
1539 (vadj.get_value() +
1540 vadj.get_page_size()))
1541 width = x2 - x1
1542 height = y2 - y1
1543 center_x = (min_x + max_x) / 2
1544 center_y = (min_y + max_y) / 2
1545
1546 self.canvas.scroll_to(center_x - width/2, center_y - height/2)
1547
1548 return False
1549
1550 def start(self):
1551 self.scan_topology()
1552 self.window.connect("delete-event", self._quit)
1553 #self._start_update_timer()
1554 GLib.timeout_add(200, self.autoscale_view)
1555 self.simulation.start()
1556
1557 try:
1558 __IPYTHON__
1559 except NameError:
1560 pass
1561 else:
1562 self._monkey_patch_ipython()
1563
1564 Gtk.main()
1565
1566
1567 def on_root_button_press_event(self, view, target, event):
1568 if event.button == 1:
1569 self.select_node(None)
1570 return True
1571
1572 def on_node_button_press_event(self, view, target, event, node):
1573 button = event.button
1574 if button == 1:
1575 self.select_node(node)
1576 return True
1577 elif button == 3:
1578 self.popup_node_menu(node, event)
1579 return True
1580 elif button == 2:
1581 self.begin_node_drag(node, event)
1582 return True
1583 return False
1584
1585 def on_node_button_release_event(self, view, target, event, node):
1586 if event.button == 2:
1587 self.end_node_drag(node)
1588 return True
1589 return False
1590
1591 class NodeDragState(object):
1592 def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1593 self.canvas_x0 = canvas_x0
1594 self.canvas_y0 = canvas_y0
1595 self.sim_x0 = sim_x0
1596 self.sim_y0 = sim_y0
1597 self.motion_signal = None
1598
1599 def begin_node_drag(self, node, event):
1600 self.simulation.lock.acquire()
1601 try:
1602 ns3_node = ns.NodeList.GetNode(node.node_index)
1603 mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1604 if not mob:
1605 return
1606 if self.node_drag_state is not None:
1607 return
1608 pos = ns.cppyy.gbl.getNodePosition(ns3_node)
1609 finally:
1610 self.simulation.lock.release()
1611 devpos = self.canvas.get_window().get_device_position(event.device)
1612 x0, y0 = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1613 self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1614 self.node_drag_state.motion_signal = node.canvas_item.connect("motion-notify-event", self.node_drag_motion, node)
1615
1616 def node_drag_motion(self, item, targe_item, event, node):
1617 self.simulation.lock.acquire()
1618 try:
1619 ns3_node = ns.NodeList.GetNode(node.node_index)
1620 mob = ns.cppyy.gbl.hasMobilityModel(ns3_node)
1621 if not mob:
1622 return False
1623 if self.node_drag_state is None:
1624 return False
1625 devpos = self.canvas.get_window().get_device_position(event.device)
1626 canvas_x, canvas_y = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1627 dx = (canvas_x - self.node_drag_state.canvas_x0)
1628 dy = (canvas_y - self.node_drag_state.canvas_y0)
1629 pos = mob.GetPosition()
1630 pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1631 pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1632 #print "SetPosition(%G, %G)" % (pos.x, pos.y)
1633 mob.SetPosition(pos)
1634 node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1635 finally:
1636 self.simulation.lock.release()
1637 return True
1638
1639 def end_node_drag(self, node):
1640 if self.node_drag_state is None:
1641 return
1642 node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1643 self.node_drag_state = None
1644
1645 def popup_node_menu(self, node, event):
1646 menu = Gtk.Menu()
1647 self.emit("populate-node-menu", node, menu)
1648 menu.popup_at_pointer(event)
1649
1650 def _update_ipython_selected_node(self):
1651 # If we are running under ipython -gthread, make this new
1652 # selected node available as a global 'selected_node'
1653 # variable.
1654 try:
1655 __IPYTHON__
1656 except NameError:
1657 pass
1658 else:
1659 if self.selected_node is None:
1660 ns3_node = None
1661 else:
1662 self.simulation.lock.acquire()
1663 try:
1664 ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1665 finally:
1666 self.simulation.lock.release()
1667 self.ipython.updateNamespace({'selected_node': ns3_node})
1668
1669
1670 def select_node(self, node):
1671 if isinstance(node, ns.Node):
1672 node = self.nodes[node.GetId()]
1673 elif isinstance(node, int):
1674 node = self.nodes[node]
1675 elif isinstance(node, Node):
1676 pass
1677 elif node is None:
1678 pass
1679 else:
1680 raise TypeError("expected None, int, viz.Node or ns.Node, not %r" % node)
1681
1682 if node is self.selected_node:
1683 return
1684
1685 if self.selected_node is not None:
1686 self.selected_node.selected = False
1687 self.selected_node = node
1688 if self.selected_node is not None:
1689 self.selected_node.selected = True
1690
1691 if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1692 if self.selected_node is None:
1693 self.simulation.set_nodes_of_interest([])
1694 else:
1695 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1696
1697 self._update_ipython_selected_node()
1698
1699
1700 def add_information_window(self, info_win):
1701 self.information_windows.append(info_win)
1702 self.simulation.lock.acquire()
1703 try:
1704 info_win.update()
1705 finally:
1706 self.simulation.lock.release()
1707
1708 def remove_information_window(self, info_win):
1709 self.information_windows.remove(info_win)
1710
1711 def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1712 #print "tooltip query: ", x, y
1713 hadj = self._scrolled_window.get_hadjustment()
1714 vadj = self._scrolled_window.get_vadjustment()
1715 x, y = self.canvas.convert_from_pixels(hadj.get_value() + x, vadj.get_value() + y)
1716 item = self.canvas.get_item_at(x, y, True)
1717 #print "items at (%f, %f): %r | keyboard_mode=%r" % (x, y, item, keyboard_mode)
1718 if not item:
1719 return False
1720 while item is not None:
1721 obj = getattr(item, "pyviz_object", None)
1722 if obj is not None:
1723 obj.tooltip_query(tooltip)
1724 return True
1725 item = item.props.parent
1726 return False
1727
1728 def _get_export_file_name(self):
1729 sel = Gtk.FileChooserNative.new("Save...", self.canvas.get_toplevel(),
1730 Gtk.FileChooserAction.SAVE,
1731 "_Save",
1732 "_Cancel")
1733 sel.set_local_only(True)
1734 sel.set_do_overwrite_confirmation(True)
1735 sel.set_current_name("Unnamed.pdf")
1736
1737 filter = Gtk.FileFilter()
1738 filter.set_name("Embedded PostScript")
1739 filter.add_mime_type("image/x-eps")
1740 sel.add_filter(filter)
1741
1742 filter = Gtk.FileFilter()
1743 filter.set_name("Portable Document Graphics")
1744 filter.add_mime_type("application/pdf")
1745 sel.add_filter(filter)
1746
1747 filter = Gtk.FileFilter()
1748 filter.set_name("Scalable Vector Graphics")
1749 filter.add_mime_type("image/svg+xml")
1750 sel.add_filter(filter)
1751
1752 resp = sel.run()
1753 if resp != Gtk.ResponseType.ACCEPT:
1754 sel.destroy()
1755 return None
1756
1757 file_name = sel.get_filename()
1758 sel.destroy()
1759 return file_name
1760
1761 def _take_screenshot(self, dummy_button):
1762 #print "Cheese!"
1763 file_name = self._get_export_file_name()
1764 if file_name is None:
1765 return
1766
1767 # figure out the correct bounding box for what is visible on screen
1768 x1 = self._scrolled_window.get_hadjustment().get_value()
1769 y1 = self._scrolled_window.get_vadjustment().get_value()
1770 x2 = x1 + self._scrolled_window.get_hadjustment().get_page_size()
1771 y2 = y1 + self._scrolled_window.get_vadjustment().get_page_size()
1772 bounds = GooCanvas.CanvasBounds()
1773 bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1774 bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1775 dest_width = bounds.x2 - bounds.x1
1776 dest_height = bounds.y2 - bounds.y1
1777 #print bounds.x1, bounds.y1, " -> ", bounds.x2, bounds.y2
1778
1779 dummy, extension = os.path.splitext(file_name)
1780 extension = extension.lower()
1781 if extension == '.eps':
1782 surface = cairo.PSSurface(file_name, dest_width, dest_height)
1783 elif extension == '.pdf':
1784 surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1785 elif extension == '.svg':
1786 surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1787 else:
1788 dialog = Gtk.MessageDialog(parent = self.canvas.get_toplevel(),
1789 flags = Gtk.DialogFlags.DESTROY_WITH_PARENT,
1790 type = Gtk.MessageType.ERROR,
1791 buttons = Gtk.ButtonsType.OK,
1792 message_format = "Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1793 % (extension,))
1794 dialog.run()
1795 dialog.destroy()
1796 return
1797
1798 # draw the canvas to a printing context
1799 cr = cairo.Context(surface)
1800 cr.translate(-bounds.x1, -bounds.y1)
1801 self.canvas.render(cr, bounds, self.zoom.get_value())
1802 cr.show_page()
1803 surface.finish()
1804
1805 def set_follow_node(self, node):
1806 if isinstance(node, ns.Node):
1807 node = self.nodes[node.GetId()]
1808 self.follow_node = node
1809
1810 def _start_shell(self, dummy_button):
1811 if self.shell_window is not None:
1812 self.shell_window.present()
1813 return
1814
1815 self.shell_window = Gtk.Window()
1816 self.shell_window.set_size_request(750,550)
1817 self.shell_window.set_resizable(True)
1818 scrolled_window = Gtk.ScrolledWindow()
1819 scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
1820 Gtk.PolicyType.AUTOMATIC)
1821 self.ipython = ipython_view.IPythonView()
1822 self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
1823 self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
1824 self.ipython.show()
1825 scrolled_window.add(self.ipython)
1826 scrolled_window.show()
1827 self.shell_window.add(scrolled_window)
1828 self.shell_window.show()
1829 self.shell_window.connect('destroy', self._on_shell_window_destroy)
1830
1831 self._update_ipython_selected_node()
1832 self.ipython.updateNamespace({'viz': self})
1833
1834
1835 def _on_shell_window_destroy(self, window):
1836 self.shell_window = None
1837
1838
1839initialization_hooks = []
1840
1842 """
1843 Adds a callback to be called after
1844 the visualizer is initialized, like this::
1845 initialization_hook(visualizer, *args)
1846 """
1847 global initialization_hooks
1848 initialization_hooks.append((hook, args))
1849
1850
1851def set_bounds(x1, y1, x2, y2):
1852 assert x2>x1
1853 assert y2>y1
1854 def hook(viz):
1855 cx1, cy1 = transform_point_simulation_to_canvas(x1, y1)
1856 cx2, cy2 = transform_point_simulation_to_canvas(x2, y2)
1857 viz.canvas.set_bounds(cx1, cy1, cx2, cy2)
1859
1860
1861def start():
1862 assert Visualizer.INSTANCE is None
1863 if _import_error is not None:
1864 import sys
1865 print("No visualization support (%s)." % (str(_import_error),),
1866 file=sys.stderr)
1867 ns.Simulator.Run()
1868 return
1869 load_plugins()
1870 viz = Visualizer()
1871 for hook, args in initialization_hooks:
1872 GLib.idle_add(hook, viz, *args)
1873 ns.Packet.EnablePrinting()
1874 viz.start()
#define min(a, b)
Definition: 80211b.c:42
#define max(a, b)
Definition: 80211b.c:43
static bool IsFinished()
Check if the simulation should finish.
Definition: simulator.cc:169
PyVizObject class.
Definition: base.py:8
def set_position(self, x, y)
Initializer function.
Definition: core.py:545
def __init__(self, channel)
Initializer function.
Definition: core.py:529
def get_position(self)
Initializer function.
Definition: core.py:560
links
list of links
Definition: core.py:543
Node class.
Definition: core.py:85
svg_align_y
svg align Y
Definition: core.py:151
def on_enter_notify_event(self, view, target, event)
On Enter event handle.
Definition: core.py:294
visualizer
visualier object
Definition: core.py:136
def set_label(self, label)
Set a label for the node.
Definition: core.py:203
def add_link(self, link)
Add link function.
Definition: core.py:484
def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5)
Set a background SVG icon for the node.
Definition: core.py:157
def get_position(self)
Get position function.
Definition: core.py:451
_highlighted
is highlighted
Definition: core.py:143
def _set_selected(self, value)
Set selected function.
Definition: core.py:317
_label_canvas_item
label canvas
Definition: core.py:153
highlighted
highlighted property
Definition: core.py:357
def on_leave_notify_event(self, view, target, event)
On Leave event handle.
Definition: core.py:305
def remove_link(self, link)
Remove link function.
Definition: core.py:495
svg_item
svg item
Definition: core.py:149
def _update_svg_position(self, x, y)
Update svg position.
Definition: core.py:216
def __init__(self, visualizer, node_index)
Initialize function.
Definition: core.py:128
_has_mobility
has mobility model
Definition: core.py:141
def _get_selected(self)
Get selected function.
Definition: core.py:327
def set_color(self, color)
Set color function.
Definition: core.py:470
def _get_highlighted(self)
Get highlighted function.
Definition: core.py:348
_selected
is selected
Definition: core.py:142
def _update_position(self)
Update position function.
Definition: core.py:460
def has_mobility(self)
Has mobility function.
Definition: core.py:507
svg_align_x
svg align X
Definition: core.py:150
def set_position(self, x, y)
Set position function.
Definition: core.py:411
def tooltip_query(self, tooltip)
Query tooltip.
Definition: core.py:231
def set_size(self, size)
Set size function.
Definition: core.py:359
canvas_item
canvas item
Definition: core.py:138
def _update_appearance(self)
Update the node aspect to reflect the selected/highlighted state.
Definition: core.py:370
def _set_highlighted(self, value)
Set highlighted function.
Definition: core.py:338
node_index
node index
Definition: core.py:137
ShowTransmissionsMode.
Definition: core.py:690
SimulationThread.
Definition: core.py:609
pause_messages
pause messages
Definition: core.py:640
def run(self)
Initializer function.
Definition: core.py:656
def set_nodes_of_interest(self, nodes)
Set nodes of interest function.
Definition: core.py:642
def __init__(self, viz)
Initializer function.
Definition: core.py:624
sim_helper
helper function
Definition: core.py:639
viz
Visualizer object.
Definition: core.py:633
Axes class.
Definition: hud.py:9
SvgItem class.
Definition: svgitem.py:8
string release
Definition: conf.py:53
def transform_distance_simulation_to_canvas(d)
Definition: base.py:75
def transform_distance_canvas_to_simulation(d)
Definition: base.py:81
def load_plugins()
Definition: base.py:106
def lookup_netdevice_traits(class_type)
Definition: base.py:63
def transform_point_simulation_to_canvas(x, y)
Definition: base.py:78
def add_initialization_hook(hook, *args)
Definition: core.py:1841
def set_bounds(x1, y1, x2, y2)
Definition: core.py:1851
def start()
Definition: core.py:1861
#define list