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