A Discrete-Event Network Simulator
API
core.py
Go to the documentation of this file.
1 # -*- Mode: python; coding: utf-8 -*-
2 from ctypes import c_double
3 
4 LAYOUT_ALGORITHM = 'neato' # ['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
5 REPRESENT_CHANNELS_AS_NODES = 1
6 DEFAULT_NODE_SIZE = 1.0 # default node size in meters
7 DEFAULT_TRANSMISSIONS_MEMORY = 5 # default number of of past intervals whose transmissions are remembered
8 BITRATE_FONT_SIZE = 10
9 
10 # internal constants, normally not meant to be changed
11 SAMPLE_PERIOD = 0.1
12 PRIORITY_UPDATE_MODEL = -100
13 PRIORITY_UPDATE_VIEW = 200
14 
15 import warnings
16 import platform
17 if platform.system() == "Windows":
18  SHELL_FONT = "Lucida Console 9"
19 else:
20  SHELL_FONT = "Luxi Mono 10"
21 
22 import math
23 import os
24 import sys
25 
26 try:
27  import threading
28 except ImportError:
29  import dummy_threading as threading
30 
31 try:
32  import pygraphviz
33 except ImportError:
34  print("Pygraphviz is required by the visualizer module and could not be found")
35  exit(1)
36 
37 try:
38  import cairo
39 except ImportError:
40  print("Pycairo is required by the visualizer module and could not be found")
41  exit(1)
42 
43 try:
44  import gi
45 except ImportError:
46  print("PyGObject is required by the visualizer module and could not be found")
47  exit(1)
48 
49 try:
50  import svgitem
51 except ImportError:
52  svgitem = None
53 
54 try:
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
66 except ImportError as e:
67  _import_error = e
68 else:
69  _import_error = None
70 
71 try:
72  import ipython_viewxxxxxxxxxx
73 except ImportError:
74  ipython_view = None
75 
76 from .base import InformationWindow, PyVizObject, Link, lookup_netdevice_traits, PIXELS_PER_METER
77 from .base import transform_distance_simulation_to_canvas, transform_point_simulation_to_canvas
78 from .base import transform_distance_canvas_to_simulation, transform_point_canvas_to_simulation
79 from .base import load_plugins, register_plugin, plugins
80 
81 PI_OVER_2 = math.pi/2
82 PI_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.visualizervisualizer = visualizer
137  self.node_indexnode_index = node_index
138  self.canvas_itemcanvas_item = GooCanvas.CanvasEllipse()
139  self.canvas_itemcanvas_item.pyviz_object = self
140  self.linkslinks = []
141  self._has_mobility_has_mobility = None
142  self._selected_selected = False
143  self._highlighted_highlighted = False
144  self._color_color = 0x808080ff
145  self._size_size = DEFAULT_NODE_SIZE
146  self.canvas_itemcanvas_item.connect("enter-notify-event", self.on_enter_notify_eventon_enter_notify_event)
147  self.canvas_itemcanvas_item.connect("leave-notify-event", self.on_leave_notify_eventon_leave_notify_event)
148  self.menumenu = None
149  self.svg_itemsvg_item = None
150  self.svg_align_xsvg_align_x = None
151  self.svg_align_ysvg_align_y = None
152  self._label_label = None
153  self._label_canvas_item_label_canvas_item = None
154 
155  self._update_appearance_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_itemcanvas_item.props.center_x
183  y = self.canvas_itemcanvas_item.props.center_y
184  self.svg_itemsvg_item = svgitem.SvgItem(x, y, rsvg_handle)
185  self.svg_itemsvg_item.props.parent = self.visualizervisualizer.canvas.get_root_item()
186  self.svg_itemsvg_item.props.pointer_events = GooCanvas.CanvasPointerEvents.NONE
187  self.svg_itemsvg_item.lower(None)
188  self.svg_itemsvg_item.props.visibility = GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD
189  if width is not None:
190  self.svg_itemsvg_item.props.width = transform_distance_simulation_to_canvas(width)
191  if height is not None:
192  self.svg_itemsvg_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_xsvg_align_x = align_x
199  self.svg_align_ysvg_align_y = align_y
200  self._update_svg_position_update_svg_position(x, y)
201  self._update_appearance_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 = label
214  self._update_appearance_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_itemsvg_item.width
226  h = self.svg_itemsvg_item.height
227  self.svg_itemsvg_item.set_properties(x=(x - (1-self.svg_align_xsvg_align_x)*w),
228  y=(y - (1-self.svg_align_ysvg_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.visualizervisualizer.simulation.lock.acquire()
240  try:
241  ns3_node = ns.NodeList.GetNode(self.node_indexnode_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_indexnode_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.visualizervisualizer.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 
306  self.highlightedhighlighted = True
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_selected = value
329  self._update_appearance_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_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_highlighted = value
350  self._update_appearance_update_appearance()
351  def _get_highlighted(self):
352  """!
353  Get highlighted function.
354 
355  @param self: class object.
356  @return highlighted status
357  """
358  return self._highlighted_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 = size
371  self._update_appearance_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 
382  if self.svg_itemsvg_item is not None:
383  alpha = 0x80
384  else:
385  alpha = 0xff
386  fill_color_rgba = (self._color_color & 0xffffff00) | alpha
387  self.canvas_itemcanvas_item.set_properties(radius_x=size, radius_y=size,
388  fill_color_rgba=fill_color_rgba)
389  if self._selected_selected:
390  line_width = size*.3
391  else:
392  line_width = size*.15
393  if self.highlightedhighlighted:
394  stroke_color = 'yellow'
395  else:
396  stroke_color = 'black'
397  self.canvas_itemcanvas_item.set_properties(line_width=line_width, stroke_color=stroke_color)
398 
399  if self._label_label is not None:
400  if self._label_canvas_item_label_canvas_item is None:
401  self._label_canvas_item_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.visualizervisualizer.canvas.get_root_item(),
407  pointer_events=GooCanvas.CanvasPointerEvents.NONE)
408  self._label_canvas_item_label_canvas_item.lower(None)
409 
410  self._label_canvas_item_label_canvas_item.set_properties(visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
411  text=self._label_label)
412  self._update_position_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_itemcanvas_item.set_property("center_x", x)
424  self.canvas_itemcanvas_item.set_property("center_y", y)
425  if self.svg_itemsvg_item is not None:
426  self._update_svg_position_update_svg_position(x, y)
427 
428  for link in self.linkslinks:
429  link.update_points()
430 
431  if self._label_canvas_item_label_canvas_item is not None:
432  self._label_canvas_item_label_canvas_item.set_properties(x=x, y=(y+self._size_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.visualizervisualizer.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.visualizervisualizer.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_itemcanvas_item.get_property("center_x"), self.canvas_itemcanvas_item.get_property("center_y"))
462 
463  def _update_position(self):
464  """!
465  Update position function.
466 
467  @param self: class object.
468  @return none
469  """
470  x, y = self.get_positionget_position()
471  self.set_positionset_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 = color
485  self._update_appearance_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.linkslinks.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.linkslinks.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_has_mobility is None:
518  node = ns.NodeList.GetNode(self.node_indexnode_index)
519  self._has_mobility_has_mobility = ns.cppyy.gbl.hasMobilityModel(node)
520  return self._has_mobility_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.channelchannel = channel
540  self.canvas_itemcanvas_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_itemcanvas_item.pyviz_object = self
546  self.linkslinks = []
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_itemcanvas_item.set_property("center_x", x)
558  self.canvas_itemcanvas_item.set_property("center_y", y)
559 
560  for link in self.linkslinks:
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_itemcanvas_item.get_property("center_x"), self.canvas_itemcanvas_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.node1node1 = node1
593  self.node2node2 = node2
594  self.canvas_itemcanvas_item = GooCanvas.CanvasPath(line_width=1.0, stroke_color="black")
595  self.canvas_itemcanvas_item.pyviz_object = self
596  self.node1node1.links.append(self)
597  self.node2node2.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.node1node1.get_position()
607  pos2_x, pos2_y = self.node2node2.get_position()
608  self.canvas_itemcanvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y))
609 
610 
611 
612 class 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.vizviz = viz # Visualizer object
637  self.locklock = threading.Lock()
638  self.gogo = threading.Event()
639  self.gogo.clear()
640  self.target_timetarget_time = 0 # in seconds
641  self.quitquit = False
642  self.sim_helpersim_helper = ns.PyViz()
643  self.pause_messagespause_messages = []
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.locklock.acquire()
654  try:
655  self.sim_helpersim_helper.SetNodesOfInterest(nodes)
656  finally:
657  self.locklock.release()
658 
659  def run(self):
660  """!
661  Initializer function.
662 
663  @param self: class object.
664  @return none
665  """
666  while not self.quitquit:
667  #print "sim: Wait for go"
668  self.gogo.wait() # wait until the main (view) thread gives us the go signal
669  self.gogo.clear()
670  if self.quitquit:
671  break
672  #self.go.clear()
673  #print "sim: Acquire lock"
674  self.locklock.acquire()
675  try:
676  if 0:
678  self.vizviz.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_helpersim_helper.SimulatorRunUntil(ns.Seconds(self.target_timetarget_time))
684  #print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds ()
685  self.pause_messagespause_messages.extend(self.sim_helpersim_helper.GetPauseMessages())
686  GLib.idle_add(self.vizviz.update_model, priority=PRIORITY_UPDATE_MODEL)
687  #print "sim: Run until: ", self.target_time, ": finished."
688  finally:
689  self.locklock.release()
690  #print "sim: Release lock, loop."
691 
692 
694 
700 
701 
702  __slots__ = []
703 ShowTransmissionsMode.ALL = ShowTransmissionsMode()
704 ShowTransmissionsMode.NONE = ShowTransmissionsMode()
705 ShowTransmissionsMode.SELECTED = ShowTransmissionsMode()
706 
707 
708 class 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 
1842 initialization_hooks = []
1843 
1844 def 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 
1854 def 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)
1862 
1863 
1864 def 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: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: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
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
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:1844
def set_bounds(x1, y1, x2, y2)
Definition: core.py:1854
def start()
Definition: core.py:1864
#define list