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