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