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