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