A Discrete-Event Network Simulator
API
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
core.py
1 # -*- Mode: python; coding: utf-8 -*-
2 from __future__ import division
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 = 3.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 platform
17 if platform.system() == "Windows":
18  SHELL_FONT = "Lucida Console 9"
19 else:
20  SHELL_FONT = "Luxi Mono 10"
21 
22 
23 import ns.core
24 import ns.network
25 import ns.visualizer
26 import ns.internet
27 import ns.mobility
28 
29 import math
30 import os
31 import sys
32 import gobject
33 import time
34 
35 try:
36  import pygraphviz
37  import gtk
38  import pango
39  import goocanvas
40  import cairo
41  import threading
42  import hud
43  #import time
44  import cairo
45  from higcontainer import HIGContainer
46  gobject.threads_init()
47  try:
48  import svgitem
49  except ImportError:
50  svgitem = None
51 except ImportError, _import_error:
52  import dummy_threading as threading
53 else:
54  _import_error = None
55 
56 try:
57  import ipython_view
58 except ImportError:
59  ipython_view = None
60 
61 from base import InformationWindow, PyVizObject, Link, lookup_netdevice_traits, PIXELS_PER_METER
62 from base import transform_distance_simulation_to_canvas, transform_point_simulation_to_canvas
63 from base import transform_distance_canvas_to_simulation, transform_point_canvas_to_simulation
64 from base import load_plugins, register_plugin, plugins
65 
66 PI_OVER_2 = math.pi/2
67 PI_TIMES_2 = math.pi*2
68 
69 class Node(PyVizObject):
70 
71  __gsignals__ = {
72 
73  # signal emitted whenever a tooltip is about to be shown for the node
74  # the first signal parameter is a python list of strings, to which information can be appended
75  'query-extra-tooltip-info': (gobject.SIGNAL_RUN_LAST, None, (object,)),
76 
77  }
78 
79  def __init__(self, visualizer, node_index):
80  super(Node, self).__init__()
81 
82  self.visualizer = visualizer
83  self.node_index = node_index
84  self.canvas_item = goocanvas.Ellipse()
85  self.canvas_item.set_data("pyviz-object", self)
86  self.links = []
87  self._has_mobility = None
88  self._selected = False
89  self._highlighted = False
90  self._color = 0x808080ff
91  self._size = DEFAULT_NODE_SIZE
92  self.canvas_item.connect("enter-notify-event", self.on_enter_notify_event)
93  self.canvas_item.connect("leave-notify-event", self.on_leave_notify_event)
94  self.menu = None
95  self.svg_item = None
96  self.svg_align_x = None
97  self.svg_align_y = None
98  self._label = None
99  self._label_canvas_item = None
100 
101  self._update_appearance() # call this last
102 
103  def set_svg_icon(self, file_base_name, width=None, height=None, align_x=0.5, align_y=0.5):
104  """
105  Set a background SVG icon for the node.
106 
107  @param file_base_name: base file name, including .svg
108  extension, of the svg file. Place the file in the folder
109  src/contrib/visualizer/resource.
110 
111  @param width: scale to the specified width, in meters
112  @param width: scale to the specified height, in meters
113 
114  @param align_x: horizontal alignment of the icon relative to
115  the node position, from 0 (icon fully to the left of the node)
116  to 1.0 (icon fully to the right of the node)
117 
118  @param align_y: vertical alignment of the icon relative to the
119  node position, from 0 (icon fully to the top of the node) to
120  1.0 (icon fully to the bottom of the node)
121 
122  """
123  if width is None and height is None:
124  raise ValueError("either width or height must be given")
125  rsvg_handle = svgitem.rsvg_handle_factory(file_base_name)
126  x = self.canvas_item.props.center_x
127  y = self.canvas_item.props.center_y
128  self.svg_item = svgitem.SvgItem(x, y, rsvg_handle)
129  self.svg_item.props.parent = self.visualizer.canvas.get_root_item()
130  self.svg_item.props.pointer_events = 0
131  self.svg_item.lower(None)
132  self.svg_item.props.visibility = goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD
133  if width is not None:
134  self.svg_item.props.width = transform_distance_simulation_to_canvas(width)
135  if height is not None:
136  self.svg_item.props.height = transform_distance_simulation_to_canvas(height)
137 
138  #threshold1 = 10.0/self.svg_item.props.height
139  #threshold2 = 10.0/self.svg_item.props.width
140  #self.svg_item.props.visibility_threshold = min(threshold1, threshold2)
141 
142  self.svg_align_x = align_x
143  self.svg_align_y = align_y
144  self._update_svg_position(x, y)
145  self._update_appearance()
146 
147  def set_label(self, label):
148  assert isinstance(label, basestring)
149  self._label = label
150  self._update_appearance()
151 
152  def _update_svg_position(self, x, y):
153  w = self.svg_item.width
154  h = self.svg_item.height
155  self.svg_item.set_properties(x=(x - (1-self.svg_align_x)*w),
156  y=(y - (1-self.svg_align_y)*h))
157 
158 
159  def tooltip_query(self, tooltip):
160  self.visualizer.simulation.lock.acquire()
161  try:
162  ns3_node = ns.network.NodeList.GetNode(self.node_index)
163  ipv4 = ns3_node.GetObject(ns.internet.Ipv4.GetTypeId())
164  ipv6 = ns3_node.GetObject(ns.internet.Ipv6.GetTypeId())
165 
166  name = '<b><u>Node %i</u></b>' % self.node_index
167  node_name = ns.core.Names.FindName (ns3_node)
168  if len(node_name)!=0:
169  name += ' <b>(' + node_name + ')</b>'
170 
171  lines = [name]
172  lines.append('')
173 
174  self.emit("query-extra-tooltip-info", lines)
175 
176  mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
177  if mob is not None:
178  lines.append(' <b>Mobility Model</b>: %s' % mob.GetInstanceTypeId().GetName())
179 
180  for devI in range(ns3_node.GetNDevices()):
181  lines.append('')
182  lines.append(' <u>NetDevice %i:</u>' % devI)
183  dev = ns3_node.GetDevice(devI)
184  name = ns.core.Names.FindName(dev)
185  if name:
186  lines.append(' <b>Name:</b> %s' % name)
187  devname = dev.GetInstanceTypeId().GetName()
188  lines.append(' <b>Type:</b> %s' % devname)
189 
190  if ipv4 is not None:
191  ipv4_idx = ipv4.GetInterfaceForDevice(dev)
192  if ipv4_idx != -1:
193  addresses = [
194  '%s/%s' % (ipv4.GetAddress(ipv4_idx, i).GetLocal(),
195  ipv4.GetAddress(ipv4_idx, i).GetMask())
196  for i in range(ipv4.GetNAddresses(ipv4_idx))]
197  lines.append(' <b>IPv4 Addresses:</b> %s' % '; '.join(addresses))
198 
199  if ipv6 is not None:
200  ipv6_idx = ipv6.GetInterfaceForDevice(dev)
201  if ipv6_idx != -1:
202  addresses = [
203  '%s/%s' % (ipv6.GetAddress(ipv6_idx, i).GetAddress(),
204  ipv6.GetAddress(ipv6_idx, i).GetPrefix())
205  for i in range(ipv6.GetNAddresses(ipv6_idx))]
206  lines.append(' <b>IPv6 Addresses:</b> %s' % '; '.join(addresses))
207 
208  lines.append(' <b>MAC Address:</b> %s' % (dev.GetAddress(),))
209 
210  tooltip.set_markup('\n'.join(lines))
211  finally:
212  self.visualizer.simulation.lock.release()
213 
214  def on_enter_notify_event(self, view, target, event):
215  self.highlighted = True
216  def on_leave_notify_event(self, view, target, event):
217  self.highlighted = False
218 
219  def _set_selected(self, value):
220  self._selected = value
221  self._update_appearance()
222  def _get_selected(self):
223  return self._selected
224  selected = property(_get_selected, _set_selected)
225 
226  def _set_highlighted(self, value):
227  self._highlighted = value
228  self._update_appearance()
229  def _get_highlighted(self):
230  return self._highlighted
231  highlighted = property(_get_highlighted, _set_highlighted)
232 
233  def set_size(self, size):
234  self._size = size
235  self._update_appearance()
236 
238  """Update the node aspect to reflect the selected/highlighted state"""
239 
240  size = transform_distance_simulation_to_canvas(self._size)
241  if self.svg_item is not None:
242  alpha = 0x80
243  else:
244  alpha = 0xff
245  fill_color_rgba = (self._color & 0xffffff00) | alpha
246  self.canvas_item.set_properties(radius_x=size, radius_y=size,
247  fill_color_rgba=fill_color_rgba)
248  if self._selected:
249  line_width = size*.3
250  else:
251  line_width = size*.15
252  if self.highlighted:
253  stroke_color = 'yellow'
254  else:
255  stroke_color = 'black'
256  self.canvas_item.set_properties(line_width=line_width, stroke_color=stroke_color)
257 
258  if self._label is not None:
259  if self._label_canvas_item is None:
260  self._label_canvas_item = goocanvas.Text(visibility_threshold=0.5,
261  font="Sans Serif 10",
262  fill_color_rgba=0x808080ff,
263  alignment=pango.ALIGN_CENTER,
264  anchor=gtk.ANCHOR_N,
265  parent=self.visualizer.canvas.get_root_item(),
266  pointer_events=0)
267  self._label_canvas_item.lower(None)
268 
269  self._label_canvas_item.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
270  text=self._label)
271  self._update_position()
272 
273  def set_position(self, x, y):
274  self.canvas_item.set_property("center_x", x)
275  self.canvas_item.set_property("center_y", y)
276  if self.svg_item is not None:
277  self._update_svg_position(x, y)
278 
279  for link in self.links:
280  link.update_points()
281 
282  if self._label_canvas_item is not None:
283  self._label_canvas_item.set_properties(x=x, y=(y+self._size*3))
284 
285  def get_position(self):
286  return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
287 
288  def _update_position(self):
289  x, y = self.get_position()
290  self.set_position(x, y)
291 
292  def set_color(self, color):
293  if isinstance(color, str):
294  color = gtk.gdk.color_parse(color)
295  color = ((color.red>>8) << 24) | ((color.green>>8) << 16) | ((color.blue>>8) << 8) | 0xff
296  self._color = color
297  self._update_appearance()
298 
299  def add_link(self, link):
300  assert isinstance(link, Link)
301  self.links.append(link)
302 
303  def remove_link(self, link):
304  assert isinstance(link, Link)
305  self.links.remove(link)
306 
307  @property
308  def has_mobility(self):
309  if self._has_mobility is None:
310  node = ns.network.NodeList.GetNode(self.node_index)
311  mobility = node.GetObject(ns.mobility.MobilityModel.GetTypeId())
312  self._has_mobility = (mobility is not None)
313  return self._has_mobility
314 
315 
316 class Channel(PyVizObject):
317  def __init__(self, channel):
318  self.channel = channel
319  self.canvas_item = goocanvas.Ellipse(radius_x=30, radius_y=30,
320  fill_color="white",
321  stroke_color="grey", line_width=2.0,
322  line_dash=goocanvas.LineDash([10.0, 10.0 ]),
323  visibility=goocanvas.ITEM_VISIBLE)
324  self.canvas_item.set_data("pyviz-object", self)
325  self.links = []
326 
327  def set_position(self, x, y):
328  self.canvas_item.set_property("center_x", x)
329  self.canvas_item.set_property("center_y", y)
330 
331  for link in self.links:
332  link.update_points()
333 
334  def get_position(self):
335  return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
336 
337 
338 class WiredLink(Link):
339  def __init__(self, node1, node2):
340  assert isinstance(node1, Node)
341  assert isinstance(node2, (Node, Channel))
342  self.node1 = node1
343  self.node2 = node2
344  self.canvas_item = goocanvas.Path(line_width=1.0, stroke_color="black")
345  self.canvas_item.set_data("pyviz-object", self)
346  self.node1.links.append(self)
347  self.node2.links.append(self)
348 
349  def update_points(self):
350  pos1_x, pos1_y = self.node1.get_position()
351  pos2_x, pos2_y = self.node2.get_position()
352  self.canvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y))
353 
354 
355 
356 class SimulationThread(threading.Thread):
357  def __init__(self, viz):
358  super(SimulationThread, self).__init__()
359  assert isinstance(viz, Visualizer)
360  self.viz = viz # Visualizer object
361  self.lock = threading.Lock()
362  self.go = threading.Event()
363  self.go.clear()
364  self.target_time = 0 # in seconds
365  self.quit = False
366  self.sim_helper = ns.visualizer.PyViz()
367  self.pause_messages = []
368 
369  def set_nodes_of_interest(self, nodes):
370  self.lock.acquire()
371  try:
372  self.sim_helper.SetNodesOfInterest(nodes)
373  finally:
374  self.lock.release()
375 
376  def run(self):
377  while not self.quit:
378  #print "sim: Wait for go"
379  self.go.wait() # wait until the main (view) thread gives us the go signal
380  self.go.clear()
381  if self.quit:
382  break
383  #self.go.clear()
384  #print "sim: Acquire lock"
385  self.lock.acquire()
386  try:
387  if 0:
388  if ns3.core.Simulator.IsFinished():
389  self.viz.play_button.set_sensitive(False)
390  break
391  #print "sim: Current time is %f; Run until: %f" % (ns3.Simulator.Now ().GetSeconds (), self.target_time)
392  #if ns3.Simulator.Now ().GetSeconds () > self.target_time:
393  # print "skipping, model is ahead of view!"
394  self.sim_helper.SimulatorRunUntil(ns.core.Seconds(self.target_time))
395  #print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds ()
396  self.pause_messages.extend(self.sim_helper.GetPauseMessages())
397  gobject.idle_add(self.viz.update_model, priority=PRIORITY_UPDATE_MODEL)
398  #print "sim: Run until: ", self.target_time, ": finished."
399  finally:
400  self.lock.release()
401  #print "sim: Release lock, loop."
402 
403 # enumeration
404 class ShowTransmissionsMode(object):
405  __slots__ = []
406 ShowTransmissionsMode.ALL = ShowTransmissionsMode()
407 ShowTransmissionsMode.NONE = ShowTransmissionsMode()
408 ShowTransmissionsMode.SELECTED = ShowTransmissionsMode()
409 
410 class Visualizer(gobject.GObject):
411  INSTANCE = None
412 
413  if _import_error is None:
414  __gsignals__ = {
415 
416  # signal emitted whenever a right-click-on-node popup menu is being constructed
417  'populate-node-menu': (gobject.SIGNAL_RUN_LAST, None, (object, gtk.Menu,)),
418 
419  # signal emitted after every simulation period (SAMPLE_PERIOD seconds of simulated time)
420  # the simulation lock is acquired while the signal is emitted
421  'simulation-periodic-update': (gobject.SIGNAL_RUN_LAST, None, ()),
422 
423  # signal emitted right after the topology is scanned
424  'topology-scanned': (gobject.SIGNAL_RUN_LAST, None, ()),
425 
426  # signal emitted when it's time to update the view objects
427  'update-view': (gobject.SIGNAL_RUN_LAST, None, ()),
428 
429  }
430 
431  def __init__(self):
432  assert Visualizer.INSTANCE is None
433  Visualizer.INSTANCE = self
434  super(Visualizer, self).__init__()
435  self.nodes = {} # node index -> Node
436  self.channels = {} # id(ns3.Channel) -> Channel
437  self.window = None # toplevel window
438  self.canvas = None # goocanvas.Canvas
439  self.time_label = None # gtk.Label
440  self.play_button = None # gtk.ToggleButton
441  self.zoom = None # gtk.Adjustment
442  self._scrolled_window = None # gtk.ScrolledWindow
443 
444  self.links_group = goocanvas.Group()
445  self.channels_group = goocanvas.Group()
446  self.nodes_group = goocanvas.Group()
447 
448  self._update_timeout_id = None
449  self.simulation = SimulationThread(self)
450  self.selected_node = None # node currently selected
451  self.speed = 1.0
452  self.information_windows = []
453  self._transmission_arrows = []
454  self._last_transmissions = []
455  self._drop_arrows = []
456  self._last_drops = []
457  self._show_transmissions_mode = None
458  self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
459  self._panning_state = None
460  self.node_size_adjustment = None
462  self.sample_period = SAMPLE_PERIOD
463  self.node_drag_state = None
464  self.follow_node = None
465  self.shell_window = None
466 
467  self.create_gui()
468 
469  for plugin in plugins:
470  plugin(self)
471 
472  def set_show_transmissions_mode(self, mode):
473  assert isinstance(mode, ShowTransmissionsMode)
474  self._show_transmissions_mode = mode
475  if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
476  self.simulation.set_nodes_of_interest(range(ns.network.NodeList.GetNNodes()))
477  elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
478  self.simulation.set_nodes_of_interest([])
479  elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
480  if self.selected_node is None:
481  self.simulation.set_nodes_of_interest([])
482  else:
483  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
484 
485  def _create_advanced_controls(self):
486  expander = gtk.Expander("Advanced")
487  expander.show()
488 
489  main_vbox = gobject.new(gtk.VBox, border_width=8, visible=True)
490  expander.add(main_vbox)
491 
492  main_hbox1 = gobject.new(gtk.HBox, border_width=8, visible=True)
493  main_vbox.pack_start(main_hbox1)
494 
495  show_transmissions_group = HIGContainer("Show transmissions")
496  show_transmissions_group.show()
497  main_hbox1.pack_start(show_transmissions_group, False, False, 8)
498 
499  vbox = gtk.VBox(True, 4)
500  vbox.show()
501  show_transmissions_group.add(vbox)
502 
503  all_nodes = gtk.RadioButton(None)
504  all_nodes.set_label("All nodes")
505  all_nodes.set_active(True)
506  all_nodes.show()
507  vbox.add(all_nodes)
508 
509  selected_node = gtk.RadioButton(all_nodes)
510  selected_node.show()
511  selected_node.set_label("Selected node")
512  selected_node.set_active(False)
513  vbox.add(selected_node)
514 
515  no_node = gtk.RadioButton(all_nodes)
516  no_node.show()
517  no_node.set_label("Disabled")
518  no_node.set_active(False)
519  vbox.add(no_node)
520 
521  def toggled(radio):
522  if radio.get_active():
523  self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
524  all_nodes.connect("toggled", toggled)
525 
526  def toggled(radio):
527  if radio.get_active():
528  self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
529  no_node.connect("toggled", toggled)
530 
531  def toggled(radio):
532  if radio.get_active():
533  self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
534  selected_node.connect("toggled", toggled)
535 
536 
537  # -- misc settings
538  misc_settings_group = HIGContainer("Misc Settings")
539  misc_settings_group.show()
540  main_hbox1.pack_start(misc_settings_group, False, False, 8)
541  settings_hbox = gobject.new(gtk.HBox, border_width=8, visible=True)
542  misc_settings_group.add(settings_hbox)
543 
544  # --> node size
545  vbox = gobject.new(gtk.VBox, border_width=0, visible=True)
546  scale = gobject.new(gtk.HScale, visible=True, digits=2)
547  vbox.pack_start(scale, True, True, 0)
548  vbox.pack_start(gobject.new(gtk.Label, label="Node Size", visible=True), True, True, 0)
549  settings_hbox.pack_start(vbox, False, False, 6)
550  self.node_size_adjustment = scale.get_adjustment()
551  def node_size_changed(adj):
552  for node in self.nodes.itervalues():
553  node.set_size(adj.value)
554  self.node_size_adjustment.connect("value-changed", node_size_changed)
555  self.node_size_adjustment.set_all(DEFAULT_NODE_SIZE, 0.01, 20, 0.1)
556 
557  # --> transmissions smooth factor
558  vbox = gobject.new(gtk.VBox, border_width=0, visible=True)
559  scale = gobject.new(gtk.HScale, visible=True, digits=1)
560  vbox.pack_start(scale, True, True, 0)
561  vbox.pack_start(gobject.new(gtk.Label, label="Tx. Smooth Factor (s)", visible=True), True, True, 0)
562  settings_hbox.pack_start(vbox, False, False, 6)
563  self.transmissions_smoothing_adjustment = scale.get_adjustment()
564  self.transmissions_smoothing_adjustment.set_all(DEFAULT_TRANSMISSIONS_MEMORY*0.1, 0.1, 10, 0.1)
565 
566  return expander
567 
568  class _PanningState(object):
569  __slots__ = ['initial_mouse_pos', 'initial_canvas_pos', 'motion_signal']
570 
571  def _begin_panning(self, widget, event):
572  self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
573  self._panning_state = self._PanningState()
574  x, y, dummy = widget.window.get_pointer()
575  self._panning_state.initial_mouse_pos = (x, y)
576  x = self._scrolled_window.get_hadjustment().value
577  y = self._scrolled_window.get_vadjustment().value
578  self._panning_state.initial_canvas_pos = (x, y)
579  self._panning_state.motion_signal = self.canvas.connect("motion-notify-event", self._panning_motion)
580 
581  def _end_panning(self, event):
582  if self._panning_state is None:
583  return
584  self.canvas.window.set_cursor(None)
585  self.canvas.disconnect(self._panning_state.motion_signal)
586  self._panning_state = None
587 
588  def _panning_motion(self, widget, event):
589  assert self._panning_state is not None
590  if event.is_hint:
591  x, y, dummy = widget.window.get_pointer()
592  else:
593  x, y = event.x, event.y
594 
595  hadj = self._scrolled_window.get_hadjustment()
596  vadj = self._scrolled_window.get_vadjustment()
597  mx0, my0 = self._panning_state.initial_mouse_pos
598  cx0, cy0 = self._panning_state.initial_canvas_pos
599 
600  dx = x - mx0
601  dy = y - my0
602  hadj.value = cx0 - dx
603  vadj.value = cy0 - dy
604  return True
605 
606  def _canvas_button_press(self, widget, event):
607  if event.button == 2:
608  self._begin_panning(widget, event)
609  return True
610  return False
611 
612  def _canvas_button_release(self, dummy_widget, event):
613  if event.button == 2:
614  self._end_panning(event)
615  return True
616  return False
617 
618  def _canvas_scroll_event(self, dummy_widget, event):
619  if event.direction == gtk.gdk.SCROLL_UP:
620  self.zoom.value *= 1.25
621  return True
622  elif event.direction == gtk.gdk.SCROLL_DOWN:
623  self.zoom.value /= 1.25
624  return True
625  return False
626 
627  def get_hadjustment(self):
628  return self._scrolled_window.get_hadjustment()
629  def get_vadjustment(self):
630  return self._scrolled_window.get_vadjustment()
631 
632  def create_gui(self):
633  self.window = gtk.Window()
634  vbox = gtk.VBox(); vbox.show()
635  self.window.add(vbox)
636 
637  # canvas
638  self.canvas = goocanvas.Canvas()
639  self.canvas.connect_after("button-press-event", self._canvas_button_press)
640  self.canvas.connect_after("button-release-event", self._canvas_button_release)
641  self.canvas.connect("scroll-event", self._canvas_scroll_event)
642  self.canvas.props.has_tooltip = True
643  self.canvas.connect("query-tooltip", self._canvas_tooltip_cb)
644  self.canvas.show()
645  sw = gtk.ScrolledWindow(); sw.show()
646  self._scrolled_window = sw
647  sw.add(self.canvas)
648  vbox.pack_start(sw, True, True, 4)
649  self.canvas.set_size_request(600, 450)
650  self.canvas.set_bounds(-10000, -10000, 10000, 10000)
651  self.canvas.scroll_to(0, 0)
652 
653 
654  self.canvas.get_root_item().add_child(self.links_group)
655  self.links_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
656 
657  self.canvas.get_root_item().add_child(self.channels_group)
658  self.channels_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
659  self.channels_group.raise_(self.links_group)
660 
661  self.canvas.get_root_item().add_child(self.nodes_group)
662  self.nodes_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
663  self.nodes_group.raise_(self.channels_group)
664 
665  self.hud = hud.Axes(self)
666 
667  hbox = gtk.HBox(); hbox.show()
668  vbox.pack_start(hbox, False, False, 4)
669 
670  # zoom
671  zoom_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0)
672  self.zoom = zoom_adj
673  def _zoom_changed(adj):
674  self.canvas.set_scale(adj.value)
675  zoom_adj.connect("value-changed", _zoom_changed)
676  zoom = gtk.SpinButton(zoom_adj)
677  zoom.set_digits(3)
678  zoom.show()
679  hbox.pack_start(gobject.new(gtk.Label, label=" Zoom:", visible=True), False, False, 4)
680  hbox.pack_start(zoom, False, False, 4)
681  _zoom_changed(zoom_adj)
682 
683  # speed
684  speed_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0)
685  def _speed_changed(adj):
686  self.speed = adj.value
687  self.sample_period = SAMPLE_PERIOD*adj.value
688  self._start_update_timer()
689  speed_adj.connect("value-changed", _speed_changed)
690  speed = gtk.SpinButton(speed_adj)
691  speed.set_digits(3)
692  speed.show()
693  hbox.pack_start(gobject.new(gtk.Label, label=" Speed:", visible=True), False, False, 4)
694  hbox.pack_start(speed, False, False, 4)
695  _speed_changed(speed_adj)
696 
697  # Current time
698  self.time_label = gobject.new(gtk.Label, label=" Speed:", visible=True)
699  self.time_label.set_width_chars(20)
700  hbox.pack_start(self.time_label, False, False, 4)
701 
702  # Screenshot button
703  screenshot_button = gobject.new(gtk.Button,
704  label="Snapshot",
705  relief=gtk.RELIEF_NONE, focus_on_click=False,
706  visible=True)
707  hbox.pack_start(screenshot_button, False, False, 4)
708 
709  def load_button_icon(button, icon_name):
710  try:
711  import gnomedesktop
712  except ImportError:
713  sys.stderr.write("Could not load icon %s due to missing gnomedesktop Python module\n" % icon_name)
714  else:
715  icon = gnomedesktop.find_icon(gtk.icon_theme_get_default(), icon_name, 16, 0)
716  if icon is not None:
717  button.props.image = gobject.new(gtk.Image, file=icon, visible=True)
718 
719  load_button_icon(screenshot_button, "applets-screenshooter")
720  screenshot_button.connect("clicked", self._take_screenshot)
721 
722  # Shell button
723  if ipython_view is not None:
724  shell_button = gobject.new(gtk.Button,
725  label="Shell",
726  relief=gtk.RELIEF_NONE, focus_on_click=False,
727  visible=True)
728  hbox.pack_start(shell_button, False, False, 4)
729  load_button_icon(shell_button, "gnome-terminal")
730  shell_button.connect("clicked", self._start_shell)
731 
732  # Play button
733  self.play_button = gobject.new(gtk.ToggleButton,
734  image=gobject.new(gtk.Image, stock=gtk.STOCK_MEDIA_PLAY, visible=True),
735  label="Simulate (F3)",
736  relief=gtk.RELIEF_NONE, focus_on_click=False,
737  use_stock=True, visible=True)
738  accel_group = gtk.AccelGroup()
739  self.window.add_accel_group(accel_group)
740  self.play_button.add_accelerator("clicked", accel_group,
741  gtk.keysyms.F3, 0, gtk.ACCEL_VISIBLE)
742  self.play_button.connect("toggled", self._on_play_button_toggled)
743  hbox.pack_start(self.play_button, False, False, 4)
744 
745  self.canvas.get_root_item().connect("button-press-event", self.on_root_button_press_event)
746 
747  vbox.pack_start(self._create_advanced_controls(), False, False, 4)
748 
749  self.window.show()
750 
751  def scan_topology(self):
752  print "scanning topology: %i nodes..." % (ns.network.NodeList.GetNNodes(),)
753  graph = pygraphviz.AGraph()
754  seen_nodes = 0
755  for nodeI in range(ns.network.NodeList.GetNNodes()):
756  seen_nodes += 1
757  if seen_nodes == 100:
758  print "scan topology... %i nodes visited (%.1f%%)" % (nodeI, 100*nodeI/ns.network.NodeList.GetNNodes())
759  seen_nodes = 0
760  node = ns.network.NodeList.GetNode(nodeI)
761  node_name = "Node %i" % nodeI
762  node_view = self.get_node(nodeI)
763 
764  mobility = node.GetObject(ns.mobility.MobilityModel.GetTypeId())
765  if mobility is not None:
766  node_view.set_color("red")
767  pos = mobility.GetPosition()
768  node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
769  #print "node has mobility position -> ", "%f,%f" % (pos.x, pos.y)
770  else:
771  graph.add_node(node_name)
772 
773  for devI in range(node.GetNDevices()):
774  device = node.GetDevice(devI)
775  device_traits = lookup_netdevice_traits(type(device))
776  if device_traits.is_wireless:
777  continue
778  if device_traits.is_virtual:
779  continue
780  channel = device.GetChannel()
781  if channel.GetNDevices() > 2:
782  if REPRESENT_CHANNELS_AS_NODES:
783  # represent channels as white nodes
784  if mobility is None:
785  channel_name = "Channel %s" % id(channel)
786  graph.add_edge(node_name, channel_name)
787  self.get_channel(channel)
788  self.create_link(self.get_node(nodeI), self.get_channel(channel))
789  else:
790  # don't represent channels, just add links between nodes in the same channel
791  for otherDevI in range(channel.GetNDevices()):
792  otherDev = channel.GetDevice(otherDevI)
793  otherNode = otherDev.GetNode()
794  otherNodeView = self.get_node(otherNode.GetId())
795  if otherNode is not node:
796  if mobility is None and not otherNodeView.has_mobility:
797  other_node_name = "Node %i" % otherNode.GetId()
798  graph.add_edge(node_name, other_node_name)
799  self.create_link(self.get_node(nodeI), otherNodeView)
800  else:
801  for otherDevI in range(channel.GetNDevices()):
802  otherDev = channel.GetDevice(otherDevI)
803  otherNode = otherDev.GetNode()
804  otherNodeView = self.get_node(otherNode.GetId())
805  if otherNode is not node:
806  if mobility is None and not otherNodeView.has_mobility:
807  other_node_name = "Node %i" % otherNode.GetId()
808  graph.add_edge(node_name, other_node_name)
809  self.create_link(self.get_node(nodeI), otherNodeView)
810 
811  print "scanning topology: calling graphviz layout"
812  graph.layout(LAYOUT_ALGORITHM)
813  for node in graph.iternodes():
814  #print node, "=>", node.attr['pos']
815  node_type, node_id = node.split(' ')
816  pos_x, pos_y = [float(s) for s in node.attr['pos'].split(',')]
817  if node_type == 'Node':
818  obj = self.nodes[int(node_id)]
819  elif node_type == 'Channel':
820  obj = self.channels[int(node_id)]
821  obj.set_position(pos_x, pos_y)
822 
823  print "scanning topology: all done."
824  self.emit("topology-scanned")
825 
826  def get_node(self, index):
827  try:
828  return self.nodes[index]
829  except KeyError:
830  node = Node(self, index)
831  self.nodes[index] = node
832  self.nodes_group.add_child(node.canvas_item)
833  node.canvas_item.connect("button-press-event", self.on_node_button_press_event, node)
834  node.canvas_item.connect("button-release-event", self.on_node_button_release_event, node)
835  return node
836 
837  def get_channel(self, ns3_channel):
838  try:
839  return self.channels[id(ns3_channel)]
840  except KeyError:
841  channel = Channel(ns3_channel)
842  self.channels[id(ns3_channel)] = channel
843  self.channels_group.add_child(channel.canvas_item)
844  return channel
845 
846  def create_link(self, node, node_or_channel):
847  link = WiredLink(node, node_or_channel)
848  self.links_group.add_child(link.canvas_item)
849  link.canvas_item.lower(None)
850 
851  def update_view(self):
852  #print "update_view"
853 
854  self.time_label.set_text("Time: %f s" % ns.core.Simulator.Now().GetSeconds())
855 
856  self._update_node_positions()
857 
858  # Update information
859  for info_win in self.information_windows:
860  info_win.update()
861 
862  self._update_transmissions_view()
863  self._update_drops_view()
864 
865  self.emit("update-view")
866 
867  def _update_node_positions(self):
868  for node in self.nodes.itervalues():
869  if node.has_mobility:
870  ns3_node = ns.network.NodeList.GetNode(node.node_index)
871  mobility = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
872  if mobility is not None:
873  pos = mobility.GetPosition()
874  x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
875  node.set_position(x, y)
876  if node is self.follow_node:
877  hadj = self._scrolled_window.get_hadjustment()
878  vadj = self._scrolled_window.get_vadjustment()
879  px, py = self.canvas.convert_to_pixels(x, y)
880  hadj.value = px - hadj.page_size/2
881  vadj.value = py - vadj.page_size/2
882 
883  def center_on_node(self, node):
884  if isinstance(node, ns.network.Node):
885  node = self.nodes[node.GetId()]
886  elif isinstance(node, (int, long)):
887  node = self.nodes[node]
888  elif isinstance(node, Node):
889  pass
890  else:
891  raise TypeError("expected int, viz.Node or ns.network.Node, not %r" % node)
892 
893  x, y = node.get_position()
894  hadj = self._scrolled_window.get_hadjustment()
895  vadj = self._scrolled_window.get_vadjustment()
896  px, py = self.canvas.convert_to_pixels(x, y)
897  hadj.value = px - hadj.page_size/2
898  vadj.value = py - vadj.page_size/2
899 
900 
901  def update_model(self):
902  self.simulation.lock.acquire()
903  try:
904  self.emit("simulation-periodic-update")
905  finally:
906  self.simulation.lock.release()
907 
908  def do_simulation_periodic_update(self):
909  smooth_factor = int(self.transmissions_smoothing_adjustment.value*10)
910 
911  transmissions = self.simulation.sim_helper.GetTransmissionSamples()
912  self._last_transmissions.append(transmissions)
913  while len(self._last_transmissions) > smooth_factor:
914  self._last_transmissions.pop(0)
915 
916  drops = self.simulation.sim_helper.GetPacketDropSamples()
917  self._last_drops.append(drops)
918  while len(self._last_drops) > smooth_factor:
919  self._last_drops.pop(0)
920 
921  def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
922  hadj = self._scrolled_window.get_hadjustment()
923  vadj = self._scrolled_window.get_vadjustment()
924  bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value)
925  bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(hadj.value + hadj.page_size,
926  vadj.value + vadj.page_size)
927  pos1_x, pos1_y, pos2_x, pos2_y = ns.visualizer.PyViz.LineClipping(bounds_x1, bounds_y1,
928  bounds_x2, bounds_y2,
929  pos1_x, pos1_y,
930  pos2_x, pos2_y)
931  return (pos1_x + pos2_x)/2, (pos1_y + pos2_y)/2
932 
933  def _update_transmissions_view(self):
934  transmissions_average = {}
935  for transmission_set in self._last_transmissions:
936  for transmission in transmission_set:
937  key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
938  rx_bytes, count = transmissions_average.get(key, (0, 0))
939  rx_bytes += transmission.bytes
940  count += 1
941  transmissions_average[key] = rx_bytes, count
942 
943  old_arrows = self._transmission_arrows
944  for arrow, label in old_arrows:
945  arrow.set_property("visibility", goocanvas.ITEM_HIDDEN)
946  label.set_property("visibility", goocanvas.ITEM_HIDDEN)
947  new_arrows = []
948 
949  k = self.node_size_adjustment.value/5
950 
951  for (transmitter_id, receiver_id), (rx_bytes, rx_count) in transmissions_average.iteritems():
952  transmitter = self.get_node(transmitter_id)
953  receiver = self.get_node(receiver_id)
954  try:
955  arrow, label = old_arrows.pop()
956  except IndexError:
957  arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0x00C000C0, close_path=False, end_arrow=True)
958  arrow.set_property("parent", self.canvas.get_root_item())
959  arrow.props.pointer_events = 0
960  arrow.raise_(None)
961 
962  label = goocanvas.Text(parent=self.canvas.get_root_item(), pointer_events=0)
963  label.raise_(None)
964 
965  arrow.set_property("visibility", goocanvas.ITEM_VISIBLE)
966  line_width = max(0.1, math.log(float(rx_bytes)/rx_count/self.sample_period)*k)
967  arrow.set_property("line-width", line_width)
968 
969  pos1_x, pos1_y = transmitter.get_position()
970  pos2_x, pos2_y = receiver.get_position()
971  points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)])
972  arrow.set_property("points", points)
973 
974  kbps = float(rx_bytes*8)/1e3/rx_count/self.sample_period
975  label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
976  visibility_threshold=0.5,
977  font=("Sans Serif %f" % int(1+BITRATE_FONT_SIZE*k)))
978  angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
979  if -PI_OVER_2 <= angle <= PI_OVER_2:
980  label.set_properties(text=("%.2f kbit/s →" % (kbps,)),
981  alignment=pango.ALIGN_CENTER,
982  anchor=gtk.ANCHOR_S,
983  x=0, y=-line_width/2)
984  M = cairo.Matrix()
985  M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y))
986  M.rotate(angle)
987  label.set_transform(M)
988  else:
989  label.set_properties(text=("← %.2f kbit/s" % (kbps,)),
990  alignment=pango.ALIGN_CENTER,
991  anchor=gtk.ANCHOR_N,
992  x=0, y=line_width/2)
993  M = cairo.Matrix()
994  M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y))
995  M.rotate(angle)
996  M.scale(-1, -1)
997  label.set_transform(M)
998 
999  new_arrows.append((arrow, label))
1000 
1001  self._transmission_arrows = new_arrows + old_arrows
1002 
1003 
1004  def _update_drops_view(self):
1005  drops_average = {}
1006  for drop_set in self._last_drops:
1007  for drop in drop_set:
1008  key = drop.transmitter.GetId()
1009  drop_bytes, count = drops_average.get(key, (0, 0))
1010  drop_bytes += drop.bytes
1011  count += 1
1012  drops_average[key] = drop_bytes, count
1013 
1014  old_arrows = self._drop_arrows
1015  for arrow, label in old_arrows:
1016  arrow.set_property("visibility", goocanvas.ITEM_HIDDEN)
1017  label.set_property("visibility", goocanvas.ITEM_HIDDEN)
1018  new_arrows = []
1019 
1020  # get the coordinates for the edge of screen
1021  vadjustment = self._scrolled_window.get_vadjustment()
1022  bottom_y = vadjustment.value + vadjustment.page_size
1023  dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1024 
1025  k = self.node_size_adjustment.value/5
1026 
1027  for transmitter_id, (drop_bytes, drop_count) in drops_average.iteritems():
1028  transmitter = self.get_node(transmitter_id)
1029  try:
1030  arrow, label = old_arrows.pop()
1031  except IndexError:
1032  arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0xC00000C0, close_path=False, end_arrow=True)
1033  arrow.props.pointer_events = 0
1034  arrow.set_property("parent", self.canvas.get_root_item())
1035  arrow.raise_(None)
1036 
1037  label = goocanvas.Text()#, fill_color_rgba=0x00C000C0)
1038  label.props.pointer_events = 0
1039  label.set_property("parent", self.canvas.get_root_item())
1040  label.raise_(None)
1041 
1042  arrow.set_property("visibility", goocanvas.ITEM_VISIBLE)
1043  arrow.set_property("line-width", max(0.1, math.log(float(drop_bytes)/drop_count/self.sample_period)*k))
1044  pos1_x, pos1_y = transmitter.get_position()
1045  pos2_x, pos2_y = pos1_x, edge_y
1046  points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)])
1047  arrow.set_property("points", points)
1048 
1049  label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
1050  visibility_threshold=0.5,
1051  font=("Sans Serif %i" % int(1+BITRATE_FONT_SIZE*k)),
1052  text=("%.2f kbit/s" % (float(drop_bytes*8)/1e3/drop_count/self.sample_period,)),
1053  alignment=pango.ALIGN_CENTER,
1054  x=(pos1_x + pos2_x)/2,
1055  y=(pos1_y + pos2_y)/2)
1056 
1057  new_arrows.append((arrow, label))
1058 
1059  self._drop_arrows = new_arrows + old_arrows
1060 
1061 
1062  def update_view_timeout(self):
1063  #print "view: update_view_timeout called at real time ", time.time()
1064 
1065  # while the simulator is busy, run the gtk event loop
1066  while not self.simulation.lock.acquire(False):
1067  while gtk.events_pending():
1068  gtk.main_iteration()
1069  pause_messages = self.simulation.pause_messages
1070  self.simulation.pause_messages = []
1071  try:
1072  self.update_view()
1073  self.simulation.target_time = ns.core.Simulator.Now ().GetSeconds () + self.sample_period
1074  #print "view: target time set to %f" % self.simulation.target_time
1075  finally:
1076  self.simulation.lock.release()
1077 
1078  if pause_messages:
1079  #print pause_messages
1080  dialog = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK,
1081  message_format='\n'.join(pause_messages))
1082  dialog.connect("response", lambda d, r: d.destroy())
1083  dialog.show()
1084  self.play_button.set_active(False)
1085 
1086  # if we're paused, stop the update timer
1087  if not self.play_button.get_active():
1088  self._update_timeout_id = None
1089  return False
1090 
1091  #print "view: self.simulation.go.set()"
1092  self.simulation.go.set()
1093  #print "view: done."
1094  return True
1095 
1096  def _start_update_timer(self):
1097  if self._update_timeout_id is not None:
1098  gobject.source_remove(self._update_timeout_id)
1099  #print "start_update_timer"
1100  self._update_timeout_id = gobject.timeout_add(int(SAMPLE_PERIOD/min(self.speed, 1)*1e3),
1101  self.update_view_timeout,
1102  priority=PRIORITY_UPDATE_VIEW)
1103 
1104  def _on_play_button_toggled(self, button):
1105  if button.get_active():
1106  self._start_update_timer()
1107  else:
1108  if self._update_timeout_id is not None:
1109  gobject.source_remove(self._update_timeout_id)
1110 
1111  def _quit(self, *dummy_args):
1112  if self._update_timeout_id is not None:
1113  gobject.source_remove(self._update_timeout_id)
1114  self._update_timeout_id = None
1115  self.simulation.quit = True
1116  self.simulation.go.set()
1117  self.simulation.join()
1118  gtk.main_quit()
1119 
1120  def _monkey_patch_ipython(self):
1121  # The user may want to access the NS 3 simulation state, but
1122  # NS 3 is not thread safe, so it could cause serious problems.
1123  # To work around this, monkey-patch IPython to automatically
1124  # acquire and release the simulation lock around each code
1125  # that is executed.
1126 
1127  original_runcode = __IPYTHON__.runcode
1128  def runcode(ip, *args):
1129  #print "lock"
1130  self.simulation.lock.acquire()
1131  try:
1132  return original_runcode(*args)
1133  finally:
1134  #print "unlock"
1135  self.simulation.lock.release()
1136  import types
1137  __IPYTHON__.runcode = types.MethodType(runcode, __IPYTHON__)
1138 
1139  def autoscale_view(self):
1140  if not self.nodes:
1141  return
1142  self._update_node_positions()
1143  positions = [node.get_position() for node in self.nodes.itervalues()]
1144  min_x, min_y = min(x for (x,y) in positions), min(y for (x,y) in positions)
1145  max_x, max_y = max(x for (x,y) in positions), max(y for (x,y) in positions)
1146  min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1147  max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1148  dx = max_x - min_x
1149  dy = max_y - min_y
1150  dx_px = max_x_px - min_x_px
1151  dy_px = max_y_px - min_y_px
1152  hadj = self._scrolled_window.get_hadjustment()
1153  vadj = self._scrolled_window.get_vadjustment()
1154  new_dx, new_dy = 1.5*dx_px, 1.5*dy_px
1155 
1156  if new_dx == 0 or new_dy == 0:
1157  return
1158 
1159  self.zoom.value = min(hadj.page_size/new_dx, vadj.page_size/new_dy)
1160 
1161  x1, y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value)
1162  x2, y2 = self.canvas.convert_from_pixels(hadj.value+hadj.page_size, vadj.value+vadj.page_size)
1163  width = x2 - x1
1164  height = y2 - y1
1165  center_x = (min_x + max_x) / 2
1166  center_y = (min_y + max_y) / 2
1167 
1168  self.canvas.scroll_to(center_x - width/2, center_y - height/2)
1169 
1170  return False
1171 
1172  def start(self):
1173  self.scan_topology()
1174  self.window.connect("delete-event", self._quit)
1175  #self._start_update_timer()
1176  gobject.timeout_add(200, self.autoscale_view)
1177  self.simulation.start()
1178 
1179  try:
1180  __IPYTHON__
1181  except NameError:
1182  pass
1183  else:
1184  self._monkey_patch_ipython()
1185 
1186  gtk.main()
1187 
1188 
1189  def on_root_button_press_event(self, view, target, event):
1190  if event.button == 1:
1191  self.select_node(None)
1192  return True
1193 
1194  def on_node_button_press_event(self, view, target, event, node):
1195  if event.button == 1:
1196  self.select_node(node)
1197  return True
1198  elif event.button == 3:
1199  self.popup_node_menu(node, event)
1200  return True
1201  elif event.button == 2:
1202  self.begin_node_drag(node)
1203  return True
1204  return False
1205 
1206  def on_node_button_release_event(self, view, target, event, node):
1207  if event.button == 2:
1208  self.end_node_drag(node)
1209  return True
1210  return False
1211 
1212  class NodeDragState(object):
1213  def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1214  self.canvas_x0 = canvas_x0
1215  self.canvas_y0 = canvas_y0
1216  self.sim_x0 = sim_x0
1217  self.sim_y0 = sim_y0
1218  self.motion_signal = None
1219 
1220  def begin_node_drag(self, node):
1221  self.simulation.lock.acquire()
1222  try:
1223  ns3_node = ns.network.NodeList.GetNode(node.node_index)
1224  mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
1225  if mob is None:
1226  return
1227  if self.node_drag_state is not None:
1228  return
1229  pos = mob.GetPosition()
1230  finally:
1231  self.simulation.lock.release()
1232  x, y, dummy = self.canvas.window.get_pointer()
1233  x0, y0 = self.canvas.convert_from_pixels(x, y)
1234  self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1235  self.node_drag_state.motion_signal = node.canvas_item.connect("motion-notify-event", self.node_drag_motion, node)
1236 
1237  def node_drag_motion(self, item, targe_item, event, node):
1238  self.simulation.lock.acquire()
1239  try:
1240  ns3_node = ns.network.NodeList.GetNode(node.node_index)
1241  mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
1242  if mob is None:
1243  return False
1244  if self.node_drag_state is None:
1245  return False
1246  x, y, dummy = self.canvas.window.get_pointer()
1247  canvas_x, canvas_y = self.canvas.convert_from_pixels(x, y)
1248  dx = (canvas_x - self.node_drag_state.canvas_x0)
1249  dy = (canvas_y - self.node_drag_state.canvas_y0)
1250  pos = mob.GetPosition()
1251  pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1252  pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1253  #print "SetPosition(%G, %G)" % (pos.x, pos.y)
1254  mob.SetPosition(pos)
1255  node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1256  finally:
1257  self.simulation.lock.release()
1258  return True
1259 
1260  def end_node_drag(self, node):
1261  if self.node_drag_state is None:
1262  return
1263  node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1264  self.node_drag_state = None
1265 
1266  def popup_node_menu(self, node, event):
1267  menu = gtk.Menu()
1268  self.emit("populate-node-menu", node, menu)
1269  menu.popup(None, None, None, event.button, event.time)
1270 
1271  def _update_ipython_selected_node(self):
1272  # If we are running under ipython -gthread, make this new
1273  # selected node available as a global 'selected_node'
1274  # variable.
1275  try:
1276  __IPYTHON__
1277  except NameError:
1278  pass
1279  else:
1280  if self.selected_node is None:
1281  ns3_node = None
1282  else:
1283  self.simulation.lock.acquire()
1284  try:
1285  ns3_node = ns.network.NodeList.GetNode(self.selected_node.node_index)
1286  finally:
1287  self.simulation.lock.release()
1288  __IPYTHON__.user_ns['selected_node'] = ns3_node
1289 
1290 
1291  def select_node(self, node):
1292  if isinstance(node, ns.network.Node):
1293  node = self.nodes[node.GetId()]
1294  elif isinstance(node, (int, long)):
1295  node = self.nodes[node]
1296  elif isinstance(node, Node):
1297  pass
1298  elif node is None:
1299  pass
1300  else:
1301  raise TypeError("expected None, int, viz.Node or ns.network.Node, not %r" % node)
1302 
1303  if node is self.selected_node:
1304  return
1305 
1306  if self.selected_node is not None:
1307  self.selected_node.selected = False
1308  self.selected_node = node
1309  if self.selected_node is not None:
1310  self.selected_node.selected = True
1311 
1312  if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1313  if self.selected_node is None:
1314  self.simulation.set_nodes_of_interest([])
1315  else:
1316  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1317 
1318  self._update_ipython_selected_node()
1319 
1320 
1321  def add_information_window(self, info_win):
1322  self.information_windows.append(info_win)
1323  self.simulation.lock.acquire()
1324  try:
1325  info_win.update()
1326  finally:
1327  self.simulation.lock.release()
1328 
1329  def remove_information_window(self, info_win):
1330  self.information_windows.remove(info_win)
1331 
1332  def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1333  #print "tooltip query: ", x, y
1334  hadj = self._scrolled_window.get_hadjustment()
1335  vadj = self._scrolled_window.get_vadjustment()
1336  x, y = self.canvas.convert_from_pixels(hadj.value + x, vadj.value + y)
1337  item = self.canvas.get_item_at(x, y, True)
1338  #print "items at (%f, %f): %r | keyboard_mode=%r" % (x, y, item, keyboard_mode)
1339  if not item:
1340  return False
1341  while item is not None:
1342  obj = item.get_data("pyviz-object")
1343  if obj is not None:
1344  obj.tooltip_query(tooltip)
1345  return True
1346  item = item.props.parent
1347  return False
1348 
1349  def _get_export_file_name(self):
1350  sel = gtk.FileChooserDialog("Save...", self.canvas.get_toplevel(),
1351  gtk.FILE_CHOOSER_ACTION_SAVE,
1352  (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1353  gtk.STOCK_SAVE, gtk.RESPONSE_OK))
1354  sel.set_default_response(gtk.RESPONSE_OK)
1355  sel.set_local_only(True)
1356  sel.set_do_overwrite_confirmation(True)
1357  sel.set_current_name("Unnamed.pdf")
1358 
1359  filter = gtk.FileFilter()
1360  filter.set_name("Embedded PostScript")
1361  filter.add_mime_type("image/x-eps")
1362  sel.add_filter(filter)
1363 
1364  filter = gtk.FileFilter()
1365  filter.set_name("Portable Document Graphics")
1366  filter.add_mime_type("application/pdf")
1367  sel.add_filter(filter)
1368 
1369  filter = gtk.FileFilter()
1370  filter.set_name("Scalable Vector Graphics")
1371  filter.add_mime_type("image/svg+xml")
1372  sel.add_filter(filter)
1373 
1374  resp = sel.run()
1375  if resp != gtk.RESPONSE_OK:
1376  sel.destroy()
1377  return None
1378 
1379  file_name = sel.get_filename()
1380  sel.destroy()
1381  return file_name
1382 
1383  def _take_screenshot(self, dummy_button):
1384  #print "Cheese!"
1385  file_name = self._get_export_file_name()
1386  if file_name is None:
1387  return
1388 
1389  # figure out the correct bounding box for what is visible on screen
1390  x1 = self._scrolled_window.get_hadjustment().value
1391  y1 = self._scrolled_window.get_vadjustment().value
1392  x2 = x1 + self._scrolled_window.get_hadjustment().page_size
1393  y2 = y1 + self._scrolled_window.get_vadjustment().page_size
1394  bounds = goocanvas.Bounds()
1395  bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1396  bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1397  dest_width = bounds.x2 - bounds.x1
1398  dest_height = bounds.y2 - bounds.y1
1399  #print bounds.x1, bounds.y1, " -> ", bounds.x2, bounds.y2
1400 
1401  dummy, extension = os.path.splitext(file_name)
1402  extension = extension.lower()
1403  if extension == '.eps':
1404  surface = cairo.PSSurface(file_name, dest_width, dest_height)
1405  elif extension == '.pdf':
1406  surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1407  elif extension == '.svg':
1408  surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1409  else:
1410  dialog = gtk.MessageDialog(parent = self.canvas.get_toplevel(),
1411  flags = gtk.DIALOG_DESTROY_WITH_PARENT,
1412  type = gtk.MESSAGE_ERROR,
1413  buttons = gtk.BUTTONS_OK,
1414  message_format = "Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1415  % (extension,))
1416  dialog.run()
1417  dialog.destroy()
1418  return
1419 
1420  # draw the canvas to a printing context
1421  cr = cairo.Context(surface)
1422  cr.translate(-bounds.x1, -bounds.y1)
1423  self.canvas.render(cr, bounds, self.zoom.value)
1424  cr.show_page()
1425  surface.finish()
1426 
1427  def set_follow_node(self, node):
1428  if isinstance(node, ns.network.Node):
1429  node = self.nodes[node.GetId()]
1430  self.follow_node = node
1431 
1432  def _start_shell(self, dummy_button):
1433  if self.shell_window is not None:
1434  self.shell_window.present()
1435  return
1436 
1437  self.shell_window = gtk.Window()
1438  self.shell_window.set_size_request(750,550)
1439  self.shell_window.set_resizable(True)
1440  scrolled_window = gtk.ScrolledWindow()
1441  scrolled_window.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
1442  ipython = ipython_view.IPythonView()
1443  ipython.modify_font(pango.FontDescription(SHELL_FONT))
1444  ipython.set_wrap_mode(gtk.WRAP_CHAR)
1445  ipython.show()
1446  scrolled_window.add(ipython)
1447  scrolled_window.show()
1448  self.shell_window.add(scrolled_window)
1449  self.shell_window.show()
1450  self.shell_window.connect('destroy', self._on_shell_window_destroy)
1451 
1452  self._update_ipython_selected_node()
1453  __IPYTHON__.user_ns['viz'] = self
1454 
1455 
1456  def _on_shell_window_destroy(self, window):
1457  self.shell_window = None
1458 
1459 
1460 initialization_hooks = []
1461 
1462 def add_initialization_hook(hook, *args):
1463  """
1464  Adds a callback to be called after
1465  the visualizer is initialized, like this::
1466  initialization_hook(visualizer, *args)
1467  """
1468  global initialization_hooks
1469  initialization_hooks.append((hook, args))
1470 
1471 
1472 def set_bounds(x1, y1, x2, y2):
1473  assert x2>x1
1474  assert y2>y1
1475  def hook(viz):
1476  cx1, cy1 = transform_point_simulation_to_canvas(x1, y1)
1477  cx2, cy2 = transform_point_simulation_to_canvas(x2, y2)
1478  viz.canvas.set_bounds(cx1, cy1, cx2, cy2)
1479  add_initialization_hook(hook)
1480 
1481 
1482 def start():
1483  assert Visualizer.INSTANCE is None
1484  if _import_error is not None:
1485  import sys
1486  print >> sys.stderr, "No visualization support (%s)." % (str(_import_error),)
1487  ns.core.Simulator.Run()
1488  return
1489  load_plugins()
1490  viz = Visualizer()
1491  for hook, args in initialization_hooks:
1492  gobject.idle_add(hook, viz, *args)
1493  ns.network.Packet.EnablePrinting()
1494  viz.start()
def _update_svg_position
Definition: core.py:152
def on_enter_notify_event
Definition: core.py:214
def on_leave_notify_event
Definition: core.py:216
def _update_position
Definition: core.py:288
def _update_appearance
Definition: core.py:237