text: # -*- Mode: python; coding: utf-8 -*-
  from __future__ import division
  #from __future__ import with_statement
  
  LAYOUT_ALGORITHM = 'neato' # ['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
  REPRESENT_CHANNELS_AS_NODES = 1
  DEFAULT_NODE_SIZE = 3.0 # default node size in meters
  DEFAULT_TRANSMISSIONS_MEMORY = 5 # default number of of past intervals whose transmissions are remembered
  BITRATE_FONT_SIZE = 10
  
  # internal constants, normally not meant to be changed
  SAMPLE_PERIOD = 0.1
  PRIORITY_UPDATE_MODEL = -100
  PRIORITY_UPDATE_VIEW = 200
  
  import platform
  if platform.system() == "Windows":
      SHELL_FONT = "Lucida Console 9"
  else:
      SHELL_FONT = "Luxi Mono 10"
  
  
  import ns.core
  import ns.network
  import ns.visualizer
  import ns.internet
  import ns.mobility
  
  import math
  import os
  import sys
  import gobject
  import time
  
  try:
      import pygraphviz
      import gtk
      import pango
      import goocanvas
      import cairo
      import threading
      import hud
      #import time
      import cairo
      from higcontainer import HIGContainer
      gobject.threads_init()
      try:
          import svgitem
      except ImportError:
          svgitem = None
  except ImportError, _import_error:
      import dummy_threading as threading
  else:
      _import_error = None
  
  try:
      import ipython_view
  except ImportError:
      ipython_view = None
  
  from base import InformationWindow, PyVizObject, Link, lookup_netdevice_traits, PIXELS_PER_METER
  from base import transform_distance_simulation_to_canvas, transform_point_simulation_to_canvas
  from base import transform_distance_canvas_to_simulation, transform_point_canvas_to_simulation
  from base import load_plugins, register_plugin, plugins
  
  PI_OVER_2 = math.pi/2
  PI_TIMES_2 = math.pi*2
  
  class Node(PyVizObject):
      
      __gsignals__ = {
  
          # signal emitted whenever a tooltip is about to be shown for the node
          # the first signal parameter is a python list of strings, to which information can be appended
          'query-extra-tooltip-info': (gobject.SIGNAL_RUN_LAST, None, (object,)),
          
          }
  
      def __init__(self, visualizer, node_index):
          super(Node, self).__init__()
  
          self.visualizer = visualizer
          self.node_index = node_index
          self.canvas_item = goocanvas.Ellipse()
          self.canvas_item.set_data("pyviz-object", self)
          self.links = []
          self._has_mobility = None
          self._selected = False
          self._highlighted = False
          self._color = 0x808080ff
          self._size = DEFAULT_NODE_SIZE
          self.canvas_item.connect("enter-notify-event", self.on_enter_notify_event)
          self.canvas_item.connect("leave-notify-event", self.on_leave_notify_event)
          self.menu = None
          self.svg_item = None
          self.svg_align_x = None
          self.svg_align_y = None
          self._label = None
          self._label_canvas_item = None
  
          self._update_appearance() # call this last
   
      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.
  
          @param file_base_name: base file name, including .svg
          extension, of the svg file.  Place the file in the folder
          src/contrib/visualizer/resource.
          
          @param width: scale to the specified width, in meters
          @param width: scale to the specified height, in meters
  
          @param align_x: horizontal alignment of the icon relative to
          the node position, from 0 (icon fully to the left of the node)
          to 1.0 (icon fully to the right of the node)
  
          @param align_y: vertical alignment of the icon relative to the
          node position, from 0 (icon fully to the top of the node) to
          1.0 (icon fully to the bottom of the node)
  
          """
          if width is None and height is None:
              raise ValueError("either width or height must be given")
          rsvg_handle = svgitem.rsvg_handle_factory(file_base_name)
          x = self.canvas_item.props.center_x
          y = self.canvas_item.props.center_y
          self.svg_item = svgitem.SvgItem(x, y, rsvg_handle)
          self.svg_item.props.parent = self.visualizer.canvas.get_root_item()
          self.svg_item.props.pointer_events = 0
          self.svg_item.lower(None)
          self.svg_item.props.visibility = goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD
          if width is not None:
              self.svg_item.props.width = transform_distance_simulation_to_canvas(width)
          if height is not None:
              self.svg_item.props.height = transform_distance_simulation_to_canvas(height)
  
          #threshold1 = 10.0/self.svg_item.props.height
          #threshold2 = 10.0/self.svg_item.props.width
          #self.svg_item.props.visibility_threshold = min(threshold1, threshold2)
  
          self.svg_align_x = align_x
          self.svg_align_y = align_y
          self._update_svg_position(x, y)
          self._update_appearance()
  
      def set_label(self, label):
          assert isinstance(label, basestring)
          self._label = label
          self._update_appearance()
  
      def _update_svg_position(self, x, y):
          w = self.svg_item.width
          h = self.svg_item.height
          self.svg_item.set_properties(x=(x - (1-self.svg_align_x)*w),
                                       y=(y - (1-self.svg_align_y)*h))
          
  
      def tooltip_query(self, tooltip):
          self.visualizer.simulation.lock.acquire()
          try:
              ns3_node = ns.network.NodeList.GetNode(self.node_index)
              ipv4 = ns3_node.GetObject(ns.internet.Ipv4.GetTypeId())
              ipv6 = ns3_node.GetObject(ns.internet.Ipv6.GetTypeId())
              lines = ['<b><u>Node %i</u></b>' % self.node_index]
              lines.append('')
  
              self.emit("query-extra-tooltip-info", lines)
  
              mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
              if mob is not None:
                  lines.append('  <b>Mobility Model</b>: %s' % mob.GetInstanceTypeId().GetName())
  
              for devI in range(ns3_node.GetNDevices()):
                  lines.append('')
                  lines.append('  <u>NetDevice %i:</u>' % devI)
                  dev = ns3_node.GetDevice(devI)
                  name = ns.core.Names.FindName(dev)
                  if name:
                      lines.append('    <b>Name:</b> %s' % name)
                  devname = dev.GetInstanceTypeId().GetName()
                  lines.append('    <b>Type:</b> %s' % devname)
  
                  if ipv4 is not None:
                      ipv4_idx = ipv4.GetInterfaceForDevice(dev)
                      if ipv4_idx != -1:
                          addresses = [
                              '%s/%s' % (ipv4.GetAddress(ipv4_idx, i).GetLocal(),
                                         ipv4.GetAddress(ipv4_idx, i).GetMask())
                              for i in range(ipv4.GetNAddresses(ipv4_idx))]
                          lines.append('    <b>IPv4 Addresses:</b> %s' % '; '.join(addresses))
  
                  if ipv6 is not None:
                      ipv6_idx = ipv6.GetInterfaceForDevice(dev)
                      if ipv6_idx != -1:
                          addresses = [
                              '%s/%s' % (ipv6.GetAddress(ipv6_idx, i).GetAddress(),
                                         ipv6.GetAddress(ipv6_idx, i).GetPrefix())
                              for i in range(ipv6.GetNAddresses(ipv4_idx))]
                          lines.append('    <b>IPv6 Addresses:</b> %s' % '; '.join(addresses))
                              
                  lines.append('    <b>MAC Address:</b> %s' % (dev.GetAddress(),))
  
              tooltip.set_markup('\n'.join(lines))
          finally:
              self.visualizer.simulation.lock.release()
  
      def on_enter_notify_event(self, view, target, event):
          self.highlighted = True
      def on_leave_notify_event(self, view, target, event):
          self.highlighted = False
  
      def _set_selected(self, value):
          self._selected = value
          self._update_appearance()
      def _get_selected(self):
          return self._selected
      selected = property(_get_selected, _set_selected)
  
      def _set_highlighted(self, value):
          self._highlighted = value
          self._update_appearance()
      def _get_highlighted(self):
          return self._highlighted
      highlighted = property(_get_highlighted, _set_highlighted)
      
      def set_size(self, size):
          self._size = size
          self._update_appearance()
  
      def _update_appearance(self):
          """Update the node aspect to reflect the selected/highlighted state"""
  
          size = transform_distance_simulation_to_canvas(self._size)
          if self.svg_item is not None:
              alpha = 0x80
          else:
              alpha = 0xff
          fill_color_rgba = (self._color & 0xffffff00) | alpha
          self.canvas_item.set_properties(radius_x=size, radius_y=size,
                                          fill_color_rgba=fill_color_rgba)
          if self._selected:
              line_width = size*.3
          else:
              line_width = size*.15
          if self.highlighted:
              stroke_color = 'yellow'
          else:
              stroke_color = 'black'
          self.canvas_item.set_properties(line_width=line_width, stroke_color=stroke_color)
  
          if self._label is not None:
              if self._label_canvas_item is None:
                  self._label_canvas_item = goocanvas.Text(visibility_threshold=0.5,
                                                           font="Sans Serif 10",
                                                           fill_color_rgba=0x808080ff,
                                                           alignment=pango.ALIGN_CENTER,
                                                           anchor=gtk.ANCHOR_N,
                                                           parent=self.visualizer.canvas.get_root_item(),
                                                           pointer_events=0)
                  self._label_canvas_item.lower(None)
  
              self._label_canvas_item.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
                                                     text=self._label)
              self._update_position()
      
      def set_position(self, x, y):
          self.canvas_item.set_property("center_x", x)
          self.canvas_item.set_property("center_y", y)
          if self.svg_item is not None:
              self._update_svg_position(x, y)
  
          for link in self.links:
              link.update_points()
  
          if self._label_canvas_item is not None:
              self._label_canvas_item.set_properties(x=x, y=(y+self._size*3))
  
      def get_position(self):
          return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
  
      def _update_position(self):
          x, y = self.get_position()
          self.set_position(x, y)
  
      def set_color(self, color):
          if isinstance(color, str):
              color = gtk.gdk.color_parse(color)
              color = ((color.red>>8) << 24) | ((color.green>>8) << 16) | ((color.blue>>8) << 8) | 0xff
          self._color = color
          self._update_appearance()
  
      def add_link(self, link):
          assert isinstance(link, Link)
          self.links.append(link)
  
      def remove_link(self, link):
          assert isinstance(link, Link)
          self.links.remove(link)
  
      @property
      def has_mobility(self):
          if self._has_mobility is None:
              node = ns.network.NodeList.GetNode(self.node_index)
              mobility = node.GetObject(ns.mobility.MobilityModel.GetTypeId())
              self._has_mobility = (mobility is not None)
          return self._has_mobility
  
  
  class Channel(PyVizObject):
      def __init__(self, channel):
          self.channel = channel
          self.canvas_item = goocanvas.Ellipse(radius_x=30, radius_y=30,
                                               fill_color="white",
                                               stroke_color="grey", line_width=2.0,
                                               line_dash=goocanvas.LineDash([10.0, 10.0 ]),
                                               visibility=goocanvas.ITEM_VISIBLE)
          self.canvas_item.set_data("pyviz-object", self)
          self.links = []
      
      def set_position(self, x, y):
          self.canvas_item.set_property("center_x", x)
          self.canvas_item.set_property("center_y", y)
  
          for link in self.links:
              link.update_points()
  
      def get_position(self):
          return (self.canvas_item.get_property("center_x"), self.canvas_item.get_property("center_y"))
  
  
  class WiredLink(Link):
      def __init__(self, node1, node2):
          assert isinstance(node1, Node)
          assert isinstance(node2, (Node, Channel))
          self.node1 = node1
          self.node2 = node2
          self.canvas_item = goocanvas.Path(line_width=1.0, stroke_color="black")
          self.canvas_item.set_data("pyviz-object", self)
          self.node1.links.append(self)
          self.node2.links.append(self)
  
      def update_points(self):
          pos1_x, pos1_y = self.node1.get_position()
          pos2_x, pos2_y = self.node2.get_position()
          self.canvas_item.set_property("data", "M %r %r L %r %r" % (pos1_x, pos1_y, pos2_x, pos2_y))
  
  
  
  class SimulationThread(threading.Thread):
      def __init__(self, viz):
          super(SimulationThread, self).__init__()
          assert isinstance(viz, Visualizer)
          self.viz = viz # Visualizer object
          self.lock = threading.Lock()
          self.go = threading.Event()
          self.go.clear()
          self.target_time = 0 # in seconds
          self.quit = False
          self.sim_helper = ns.visualizer.PyViz()
          self.pause_messages = []
  
      def set_nodes_of_interest(self, nodes):
          self.lock.acquire()
          try:
              self.sim_helper.SetNodesOfInterest(nodes)
          finally:
              self.lock.release()
          
      def run(self):
          while not self.quit:
              #print "sim: Wait for go"
              self.go.wait() # wait until the main (view) thread gives us the go signal
              self.go.clear()
              if self.quit:
                  break
              #self.go.clear()
              #print "sim: Acquire lock"
              self.lock.acquire()
              try:
                  if 0:
                      if ns3.core.Simulator.IsFinished():
                          self.viz.play_button.set_sensitive(False)
                          break
                  #print "sim: Current time is %f; Run until: %f" % (ns3.Simulator.Now ().GetSeconds (), self.target_time)
                  #if ns3.Simulator.Now ().GetSeconds () > self.target_time:
                  #    print "skipping, model is ahead of view!"
                  self.sim_helper.SimulatorRunUntil(ns.core.Seconds(self.target_time))
                  #print "sim: Run until ended at current time: ", ns3.Simulator.Now ().GetSeconds ()
                  self.pause_messages.extend(self.sim_helper.GetPauseMessages())
                  gobject.idle_add(self.viz.update_model, priority=PRIORITY_UPDATE_MODEL)
                  #print "sim: Run until: ", self.target_time, ": finished."
              finally:
                  self.lock.release()
              #print "sim: Release lock, loop."
  
  # enumeration
  class ShowTransmissionsMode(object):
      __slots__ = []
  ShowTransmissionsMode.ALL = ShowTransmissionsMode()
  ShowTransmissionsMode.NONE = ShowTransmissionsMode()
  ShowTransmissionsMode.SELECTED = ShowTransmissionsMode()
  
  class Visualizer(gobject.GObject):
      INSTANCE = None
  
      if _import_error is None:
          __gsignals__ = {
  
              # signal emitted whenever a right-click-on-node popup menu is being constructed
              'populate-node-menu': (gobject.SIGNAL_RUN_LAST, None, (object, gtk.Menu,)),
  
              # signal emitted after every simulation period (SAMPLE_PERIOD seconds of simulated time)
              # the simulation lock is acquired while the signal is emitted
              'simulation-periodic-update': (gobject.SIGNAL_RUN_LAST, None, ()),
  
              # signal emitted right after the topology is scanned
              'topology-scanned': (gobject.SIGNAL_RUN_LAST, None, ()),
  
              # signal emitted when it's time to update the view objects
              'update-view': (gobject.SIGNAL_RUN_LAST, None, ()),
  
              }
  
      def __init__(self):
          assert Visualizer.INSTANCE is None
          Visualizer.INSTANCE = self
          super(Visualizer, self).__init__()
          self.nodes =  {} # node index -> Node
          self.channels = {} # id(ns3.Channel) -> Channel
          self.window = None # toplevel window
          self.canvas = None # goocanvas.Canvas
          self.time_label = None # gtk.Label
          self.play_button = None # gtk.ToggleButton
          self.zoom = None # gtk.Adjustment
          self._scrolled_window = None # gtk.ScrolledWindow
  
          self.links_group = goocanvas.Group()
          self.channels_group = goocanvas.Group()
          self.nodes_group = goocanvas.Group()
  
          self._update_timeout_id = None
          self.simulation = SimulationThread(self)
          self.selected_node = None # node currently selected
          self.speed = 1.0
          self.information_windows = []
          self._transmission_arrows = []
          self._last_transmissions = []
          self._drop_arrows = []
          self._last_drops = []
          self._show_transmissions_mode = None
          self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
          self._panning_state = None
          self.node_size_adjustment = None
          self.transmissions_smoothing_adjustment = None
          self.sample_period = SAMPLE_PERIOD
          self.node_drag_state = None
          self.follow_node = None
          self.shell_window = None
  
          self.create_gui()
  
          for plugin in plugins:
              plugin(self)
  
      def set_show_transmissions_mode(self, mode):
          assert isinstance(mode, ShowTransmissionsMode)
          self._show_transmissions_mode = mode
          if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
              self.simulation.set_nodes_of_interest(range(ns.network.NodeList.GetNNodes()))
          elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
              self.simulation.set_nodes_of_interest([])
          elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
              if self.selected_node is None:
                  self.simulation.set_nodes_of_interest([])
              else:
                  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
  
      def _create_advanced_controls(self):
          expander = gtk.Expander("Advanced")
          expander.show()
  
          main_vbox = gobject.new(gtk.VBox, border_width=8, visible=True)
          expander.add(main_vbox)
  
          main_hbox1 = gobject.new(gtk.HBox, border_width=8, visible=True)
          main_vbox.pack_start(main_hbox1)
  
          show_transmissions_group = HIGContainer("Show transmissions")
          show_transmissions_group.show()
          main_hbox1.pack_start(show_transmissions_group, False, False, 8)
  
          vbox = gtk.VBox(True, 4)
          vbox.show()
          show_transmissions_group.add(vbox)
  
          all_nodes = gtk.RadioButton(None)
          all_nodes.set_label("All nodes")
          all_nodes.set_active(True)
          all_nodes.show()
          vbox.add(all_nodes)
  
          selected_node = gtk.RadioButton(all_nodes)
          selected_node.show()
          selected_node.set_label("Selected node")
          selected_node.set_active(False)
          vbox.add(selected_node)
          
          no_node = gtk.RadioButton(all_nodes)
          no_node.show()
          no_node.set_label("Disabled")
          no_node.set_active(False)
          vbox.add(no_node)
  
          def toggled(radio):
              if radio.get_active():
                  self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
          all_nodes.connect("toggled", toggled)
  
          def toggled(radio):
              if radio.get_active():
                  self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
          no_node.connect("toggled", toggled)
  
          def toggled(radio):
              if radio.get_active():
                  self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
          selected_node.connect("toggled", toggled)
  
          
          # -- misc settings
          misc_settings_group = HIGContainer("Misc Settings")
          misc_settings_group.show()
          main_hbox1.pack_start(misc_settings_group, False, False, 8)
          settings_hbox = gobject.new(gtk.HBox, border_width=8, visible=True)
          misc_settings_group.add(settings_hbox)
  
          # --> node size
          vbox = gobject.new(gtk.VBox, border_width=0, visible=True)
          scale = gobject.new(gtk.HScale, visible=True, digits=2)
          vbox.pack_start(scale, True, True, 0)
          vbox.pack_start(gobject.new(gtk.Label, label="Node Size", visible=True), True, True, 0)
          settings_hbox.pack_start(vbox, False, False, 6)
          self.node_size_adjustment = scale.get_adjustment()
          def node_size_changed(adj):
              for node in self.nodes.itervalues():
                  node.set_size(adj.value)
          self.node_size_adjustment.connect("value-changed", node_size_changed)
          self.node_size_adjustment.set_all(DEFAULT_NODE_SIZE, 0.01, 20, 0.1)
  
          # --> transmissions smooth factor
          vbox = gobject.new(gtk.VBox, border_width=0, visible=True)
          scale = gobject.new(gtk.HScale, visible=True, digits=1)
          vbox.pack_start(scale, True, True, 0)
          vbox.pack_start(gobject.new(gtk.Label, label="Tx. Smooth Factor (s)", visible=True), True, True, 0)
          settings_hbox.pack_start(vbox, False, False, 6)
          self.transmissions_smoothing_adjustment = scale.get_adjustment()
          self.transmissions_smoothing_adjustment.set_all(DEFAULT_TRANSMISSIONS_MEMORY*0.1, 0.1, 10, 0.1)
  
          return expander
  
      class _PanningState(object):
          __slots__ = ['initial_mouse_pos', 'initial_canvas_pos', 'motion_signal']
  
      def _begin_panning(self, widget, event):
          self.canvas.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
          self._panning_state = self._PanningState()
          x, y, dummy = widget.window.get_pointer()
          self._panning_state.initial_mouse_pos = (x, y)
          x = self._scrolled_window.get_hadjustment().value
          y = self._scrolled_window.get_vadjustment().value
          self._panning_state.initial_canvas_pos = (x, y)
          self._panning_state.motion_signal = self.canvas.connect("motion-notify-event", self._panning_motion)
  
      def _end_panning(self, event):
          if self._panning_state is None:
              return
          self.canvas.window.set_cursor(None)
          self.canvas.disconnect(self._panning_state.motion_signal)
          self._panning_state = None
          
      def _panning_motion(self, widget, event):
          assert self._panning_state is not None
          if event.is_hint:
              x, y, dummy = widget.window.get_pointer()
          else:
              x, y = event.x, event.y
  
          hadj = self._scrolled_window.get_hadjustment()
          vadj = self._scrolled_window.get_vadjustment()
          mx0, my0 = self._panning_state.initial_mouse_pos
          cx0, cy0 = self._panning_state.initial_canvas_pos
          
          dx = x - mx0
          dy = y - my0
          hadj.value = cx0 - dx
          vadj.value = cy0 - dy
          return True
  
      def _canvas_button_press(self, widget, event):
          if event.button == 2:
              self._begin_panning(widget, event)
              return True
          return False
  
      def _canvas_button_release(self, dummy_widget, event):
          if event.button == 2:
              self._end_panning(event)
              return True
          return False
      
      def _canvas_scroll_event(self, dummy_widget, event):
          if event.direction == gtk.gdk.SCROLL_UP:
              self.zoom.value *= 1.25
              return True
          elif event.direction == gtk.gdk.SCROLL_DOWN:
              self.zoom.value /= 1.25
              return True
          return False
  
      def get_hadjustment(self):
          return self._scrolled_window.get_hadjustment()
      def get_vadjustment(self):
          return self._scrolled_window.get_vadjustment()
  
      def create_gui(self):
          self.window = gtk.Window()
          vbox = gtk.VBox(); vbox.show()
          self.window.add(vbox)
  
          # canvas
          self.canvas = goocanvas.Canvas()
          self.canvas.connect_after("button-press-event", self._canvas_button_press)
          self.canvas.connect_after("button-release-event", self._canvas_button_release)
          self.canvas.connect("scroll-event", self._canvas_scroll_event)
          self.canvas.props.has_tooltip = True
          self.canvas.connect("query-tooltip", self._canvas_tooltip_cb)
          self.canvas.show()
          sw = gtk.ScrolledWindow(); sw.show()
          self._scrolled_window = sw
          sw.add(self.canvas)
          vbox.pack_start(sw, True, True, 4)
          self.canvas.set_size_request(600, 450)
          self.canvas.set_bounds(-10000, -10000, 10000, 10000)
          self.canvas.scroll_to(0, 0)
  
  
          self.canvas.get_root_item().add_child(self.links_group)
          self.links_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
          
          self.canvas.get_root_item().add_child(self.channels_group)
          self.channels_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
          self.channels_group.raise_(self.links_group)
  
          self.canvas.get_root_item().add_child(self.nodes_group)
          self.nodes_group.set_property("visibility", goocanvas.ITEM_VISIBLE)
          self.nodes_group.raise_(self.channels_group)
  
          self.hud = hud.Axes(self)
  
          hbox = gtk.HBox(); hbox.show()
          vbox.pack_start(hbox, False, False, 4)
  
          # zoom
          zoom_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0)
          self.zoom = zoom_adj
          def _zoom_changed(adj):
              self.canvas.set_scale(adj.value)
          zoom_adj.connect("value-changed", _zoom_changed)
          zoom = gtk.SpinButton(zoom_adj)
          zoom.set_digits(3)
          zoom.show()
          hbox.pack_start(gobject.new(gtk.Label, label=" Zoom:", visible=True), False, False, 4)
          hbox.pack_start(zoom, False, False, 4)
          _zoom_changed(zoom_adj)
  
          # speed
          speed_adj = gtk.Adjustment(1.0, 0.01, 10.0, 0.02, 1.0, 0)
          def _speed_changed(adj):
              self.speed = adj.value
              self.sample_period = SAMPLE_PERIOD*adj.value
              self._start_update_timer()
          speed_adj.connect("value-changed", _speed_changed)
          speed = gtk.SpinButton(speed_adj)
          speed.set_digits(3)
          speed.show()
          hbox.pack_start(gobject.new(gtk.Label, label="  Speed:", visible=True), False, False, 4)
          hbox.pack_start(speed, False, False, 4)
          _speed_changed(speed_adj)
  
          # Current time
          self.time_label = gobject.new(gtk.Label, label="  Speed:", visible=True)
          self.time_label.set_width_chars(20)
          hbox.pack_start(self.time_label, False, False, 4)
  
          # Screenshot button
          screenshot_button = gobject.new(gtk.Button,
                                         label="Snapshot",
                                         relief=gtk.RELIEF_NONE, focus_on_click=False,
                                         visible=True)
          hbox.pack_start(screenshot_button, False, False, 4)
  
          def load_button_icon(button, icon_name):
              try:
                  import gnomedesktop
              except ImportError:
                  sys.stderr.write("Could not load icon %s due to missing gnomedesktop Python module\n" % icon_name)
              else:
                  icon = gnomedesktop.find_icon(gtk.icon_theme_get_default(), icon_name, 16, 0)
                  if icon is not None:
                      button.props.image = gobject.new(gtk.Image, file=icon, visible=True)
  
          load_button_icon(screenshot_button, "applets-screenshooter")
          screenshot_button.connect("clicked", self._take_screenshot)
  
          # Shell button
          if ipython_view is not None:
              shell_button = gobject.new(gtk.Button,
                                             label="Shell",
                                             relief=gtk.RELIEF_NONE, focus_on_click=False,
                                             visible=True)
              hbox.pack_start(shell_button, False, False, 4)
              load_button_icon(shell_button, "gnome-terminal")
              shell_button.connect("clicked", self._start_shell)
  
          # Play button
          self.play_button = gobject.new(gtk.ToggleButton,
                                         image=gobject.new(gtk.Image, stock=gtk.STOCK_MEDIA_PLAY, visible=True),
                                         label="Simulate (F3)",
                                         relief=gtk.RELIEF_NONE, focus_on_click=False,
                                         use_stock=True, visible=True)
          accel_group = gtk.AccelGroup()
          self.window.add_accel_group(accel_group)
          self.play_button.add_accelerator("clicked", accel_group,
                                           gtk.keysyms.F3, 0, gtk.ACCEL_VISIBLE)
          self.play_button.connect("toggled", self._on_play_button_toggled)
          hbox.pack_start(self.play_button, False, False, 4)
  
          self.canvas.get_root_item().connect("button-press-event", self.on_root_button_press_event)
  
          vbox.pack_start(self._create_advanced_controls(), False, False, 4)
          
          self.window.show()
  
      def scan_topology(self):
          print "scanning topology: %i nodes..." % (ns.network.NodeList.GetNNodes(),)
          graph = pygraphviz.AGraph()
          seen_nodes = 0
          for nodeI in range(ns.network.NodeList.GetNNodes()):
              seen_nodes += 1
              if seen_nodes == 100:
                  print "scan topology... %i nodes visited (%.1f%%)" % (nodeI, 100*nodeI/ns.network.NodeList.GetNNodes())
                  seen_nodes = 0
              node = ns.network.NodeList.GetNode(nodeI)
              node_name = "Node %i" % nodeI
              node_view = self.get_node(nodeI)
  
              mobility = node.GetObject(ns.mobility.MobilityModel.GetTypeId())
              if mobility is not None:
                  node_view.set_color("red")
                  pos = mobility.GetPosition()
                  node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
                  #print "node has mobility position -> ", "%f,%f" % (pos.x, pos.y)
              else:
                  graph.add_node(node_name)
  
              for devI in range(node.GetNDevices()):
                  device = node.GetDevice(devI)
                  device_traits = lookup_netdevice_traits(type(device))
                  if device_traits.is_wireless:
                      continue
                  if device_traits.is_virtual:
                      continue
                  channel = device.GetChannel()
                  if channel.GetNDevices() > 2:
                      if REPRESENT_CHANNELS_AS_NODES:
                          # represent channels as white nodes
                          if mobility is None:
                              channel_name = "Channel %s" % id(channel)
                              graph.add_edge(node_name, channel_name)
                          self.get_channel(channel)
                          self.create_link(self.get_node(nodeI), self.get_channel(channel))
                      else:
                          # don't represent channels, just add links between nodes in the same channel
                          for otherDevI in range(channel.GetNDevices()):
                              otherDev = channel.GetDevice(otherDevI)
                              otherNode = otherDev.GetNode()
                              otherNodeView = self.get_node(otherNode.GetId())
                              if otherNode is not node:
                                  if mobility is None and not otherNodeView.has_mobility:
                                      other_node_name = "Node %i" % otherNode.GetId()
                                      graph.add_edge(node_name, other_node_name)
                                  self.create_link(self.get_node(nodeI), otherNodeView)
                  else:
                      for otherDevI in range(channel.GetNDevices()):
                          otherDev = channel.GetDevice(otherDevI)
                          otherNode = otherDev.GetNode()
                          otherNodeView = self.get_node(otherNode.GetId())
                          if otherNode is not node:
                              if mobility is None and not otherNodeView.has_mobility:
                                  other_node_name = "Node %i" % otherNode.GetId()
                                  graph.add_edge(node_name, other_node_name)
                              self.create_link(self.get_node(nodeI), otherNodeView)
  
          print "scanning topology: calling graphviz layout"
          graph.layout(LAYOUT_ALGORITHM)
          for node in graph.iternodes():
              #print node, "=>", node.attr['pos']
              node_type, node_id = node.split(' ')
              pos_x, pos_y = [float(s) for s in node.attr['pos'].split(',')]
              if node_type == 'Node':
                  obj = self.nodes[int(node_id)]
              elif node_type == 'Channel':
                  obj = self.channels[int(node_id)]
              obj.set_position(pos_x, pos_y)
  
          print "scanning topology: all done."
          self.emit("topology-scanned")
  
      def get_node(self, index):
          try:
              return self.nodes[index]
          except KeyError:
              node = Node(self, index)
              self.nodes[index] = node
              self.nodes_group.add_child(node.canvas_item)
              node.canvas_item.connect("button-press-event", self.on_node_button_press_event, node)
              node.canvas_item.connect("button-release-event", self.on_node_button_release_event, node)
              return node
  
      def get_channel(self, ns3_channel):
          try:
              return self.channels[id(ns3_channel)]
          except KeyError:
              channel = Channel(ns3_channel)
              self.channels[id(ns3_channel)] = channel
              self.channels_group.add_child(channel.canvas_item)
              return channel
  
      def create_link(self, node, node_or_channel):
          link = WiredLink(node, node_or_channel)
          self.links_group.add_child(link.canvas_item)
          link.canvas_item.lower(None)
  
      def update_view(self):
          #print "update_view"
  
          self.time_label.set_text("Time: %f s" % ns.core.Simulator.Now().GetSeconds())
          
          self._update_node_positions()
  
          # Update information 
          for info_win in self.information_windows:
              info_win.update()
  
          self._update_transmissions_view()
          self._update_drops_view()
  
          self.emit("update-view")
  
      def _update_node_positions(self):
          for node in self.nodes.itervalues():
              if node.has_mobility:
                  ns3_node = ns.network.NodeList.GetNode(node.node_index)
                  mobility = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
                  if mobility is not None:
                      pos = mobility.GetPosition()
                      x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
                      node.set_position(x, y)
                      if node is self.follow_node:
                          hadj = self._scrolled_window.get_hadjustment()
                          vadj = self._scrolled_window.get_vadjustment()
                          px, py = self.canvas.convert_to_pixels(x, y)
                          hadj.value = px - hadj.page_size/2
                          vadj.value = py - vadj.page_size/2
  
      def center_on_node(self, node):
          if isinstance(node, ns.network.Node):
              node = self.nodes[node.GetId()]
          elif isinstance(node, (int, long)):
              node = self.nodes[node]
          elif isinstance(node, Node):
              pass
          else:
              raise TypeError("expected int, viz.Node or ns.network.Node, not %r" % node)
          
          x, y = node.get_position()
          hadj = self._scrolled_window.get_hadjustment()
          vadj = self._scrolled_window.get_vadjustment()
          px, py = self.canvas.convert_to_pixels(x, y)
          hadj.value = px - hadj.page_size/2
          vadj.value = py - vadj.page_size/2
          
  
      def update_model(self):
          self.simulation.lock.acquire()
          try:
              self.emit("simulation-periodic-update")
          finally:
              self.simulation.lock.release()
  
      def do_simulation_periodic_update(self):
          smooth_factor = int(self.transmissions_smoothing_adjustment.value*10)
  
          transmissions = self.simulation.sim_helper.GetTransmissionSamples()
          self._last_transmissions.append(transmissions)
          while len(self._last_transmissions) > smooth_factor:
              self._last_transmissions.pop(0)            
  
          drops = self.simulation.sim_helper.GetPacketDropSamples()
          self._last_drops.append(drops)
          while len(self._last_drops) > smooth_factor:
              self._last_drops.pop(0)
  
      def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
          hadj = self._scrolled_window.get_hadjustment()
          vadj = self._scrolled_window.get_vadjustment()
          bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value)
          bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(hadj.value + hadj.page_size,
                                                                 vadj.value + vadj.page_size)
          pos1_x, pos1_y, pos2_x, pos2_y = ns.visualizer.PyViz.LineClipping(bounds_x1, bounds_y1,
                                                                  bounds_x2, bounds_y2,
                                                                  pos1_x, pos1_y,
                                                                  pos2_x, pos2_y)
          return (pos1_x + pos2_x)/2, (pos1_y + pos2_y)/2
  
      def _update_transmissions_view(self):
          transmissions_average = {}
          for transmission_set in self._last_transmissions:
              for transmission in transmission_set:
                  key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
                  rx_bytes, count = transmissions_average.get(key, (0, 0))
                  rx_bytes += transmission.bytes
                  count += 1
                  transmissions_average[key] = rx_bytes, count
  
          old_arrows = self._transmission_arrows
          for arrow, label in old_arrows:
              arrow.set_property("visibility", goocanvas.ITEM_HIDDEN)
              label.set_property("visibility", goocanvas.ITEM_HIDDEN)
          new_arrows = []
  
          k = self.node_size_adjustment.value/5
  
          for (transmitter_id, receiver_id), (rx_bytes, rx_count) in transmissions_average.iteritems():
              transmitter = self.get_node(transmitter_id)
              receiver = self.get_node(receiver_id)
              try:
                  arrow, label = old_arrows.pop()
              except IndexError:
                  arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0x00C000C0, close_path=False, end_arrow=True)
                  arrow.set_property("parent", self.canvas.get_root_item())
                  arrow.props.pointer_events = 0
                  arrow.raise_(None)
                  
                  label = goocanvas.Text(parent=self.canvas.get_root_item(), pointer_events=0)
                  label.raise_(None)
  
              arrow.set_property("visibility", goocanvas.ITEM_VISIBLE)
              line_width = max(0.1, math.log(float(rx_bytes)/rx_count/self.sample_period)*k)
              arrow.set_property("line-width", line_width)
  
              pos1_x, pos1_y = transmitter.get_position()
              pos2_x, pos2_y = receiver.get_position()
              points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)])
              arrow.set_property("points", points)
  
              kbps = float(rx_bytes*8)/1e3/rx_count/self.sample_period
              label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
                                   visibility_threshold=0.5,
                                   font=("Sans Serif %f" % int(1+BITRATE_FONT_SIZE*k)))
              angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
              if -PI_OVER_2 <= angle <= PI_OVER_2:
                  label.set_properties(text=("%.2f kbit/s →" % (kbps,)),
                                       alignment=pango.ALIGN_CENTER,
                                       anchor=gtk.ANCHOR_S,
                                       x=0, y=-line_width/2)
                  M = cairo.Matrix()
                  M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y))
                  M.rotate(angle)
                  label.set_transform(M)
              else:
                  label.set_properties(text=("← %.2f kbit/s" % (kbps,)),
                                       alignment=pango.ALIGN_CENTER,
                                       anchor=gtk.ANCHOR_N,
                                       x=0, y=line_width/2)
                  M = cairo.Matrix()
                  M.translate(*self._get_label_over_line_position(pos1_x, pos1_y, pos2_x, pos2_y))
                  M.rotate(angle)
                  M.scale(-1, -1)
                  label.set_transform(M)
  
              new_arrows.append((arrow, label))
              
          self._transmission_arrows = new_arrows + old_arrows
  
  
      def _update_drops_view(self):
          drops_average = {}
          for drop_set in self._last_drops:
              for drop in drop_set:
                  key = drop.transmitter.GetId()
                  drop_bytes, count = drops_average.get(key, (0, 0))
                  drop_bytes += drop.bytes
                  count += 1
                  drops_average[key] = drop_bytes, count
  
          old_arrows = self._drop_arrows
          for arrow, label in old_arrows:
              arrow.set_property("visibility", goocanvas.ITEM_HIDDEN)
              label.set_property("visibility", goocanvas.ITEM_HIDDEN)
          new_arrows = []
  
          # get the coordinates for the edge of screen
          vadjustment = self._scrolled_window.get_vadjustment()
          bottom_y = vadjustment.value + vadjustment.page_size
          dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
  
          k = self.node_size_adjustment.value/5
  
          for transmitter_id, (drop_bytes, drop_count) in drops_average.iteritems():
              transmitter = self.get_node(transmitter_id)
              try:
                  arrow, label = old_arrows.pop()
              except IndexError:
                  arrow = goocanvas.Polyline(line_width=2.0, stroke_color_rgba=0xC00000C0, close_path=False, end_arrow=True)
                  arrow.props.pointer_events = 0
                  arrow.set_property("parent", self.canvas.get_root_item())
                  arrow.raise_(None)
                  
                  label = goocanvas.Text()#, fill_color_rgba=0x00C000C0)
                  label.props.pointer_events = 0
                  label.set_property("parent", self.canvas.get_root_item())
                  label.raise_(None)
  
              arrow.set_property("visibility", goocanvas.ITEM_VISIBLE)
              arrow.set_property("line-width", max(0.1, math.log(float(drop_bytes)/drop_count/self.sample_period)*k))
              pos1_x, pos1_y = transmitter.get_position()
              pos2_x, pos2_y = pos1_x, edge_y
              points = goocanvas.Points([(pos1_x, pos1_y), (pos2_x, pos2_y)])
              arrow.set_property("points", points)
  
              label.set_properties(visibility=goocanvas.ITEM_VISIBLE_ABOVE_THRESHOLD,
                                   visibility_threshold=0.5,
                                   font=("Sans Serif %i" % int(1+BITRATE_FONT_SIZE*k)),
                                   text=("%.2f kbit/s" % (float(drop_bytes*8)/1e3/drop_count/self.sample_period,)),
                                   alignment=pango.ALIGN_CENTER,
                                   x=(pos1_x + pos2_x)/2,
                                   y=(pos1_y + pos2_y)/2)
  
              new_arrows.append((arrow, label))
              
          self._drop_arrows = new_arrows + old_arrows
              
                  
      def update_view_timeout(self):
          #print "view: update_view_timeout called at real time ", time.time()
  
          # while the simulator is busy, run the gtk event loop
          while not self.simulation.lock.acquire(False):
              while gtk.events_pending():
                  gtk.main_iteration()
          pause_messages = self.simulation.pause_messages
          self.simulation.pause_messages = []
          try:
              self.update_view()
              self.simulation.target_time = ns.core.Simulator.Now ().GetSeconds () + self.sample_period
              #print "view: target time set to %f" % self.simulation.target_time
          finally:
              self.simulation.lock.release()
  
          if pause_messages:
              #print pause_messages
              dialog = gtk.MessageDialog(parent=self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_OK,
                                         message_format='\n'.join(pause_messages))
              dialog.connect("response", lambda d, r: d.destroy())
              dialog.show()
              self.play_button.set_active(False)
  
          # if we're paused, stop the update timer
          if not self.play_button.get_active():
              self._update_timeout_id = None
              return False
  
          #print "view: self.simulation.go.set()"
          self.simulation.go.set()
          #print "view: done."
          return True
  
      def _start_update_timer(self):
          if self._update_timeout_id is not None:
              gobject.source_remove(self._update_timeout_id)
          #print "start_update_timer"
          self._update_timeout_id = gobject.timeout_add(int(SAMPLE_PERIOD/min(self.speed, 1)*1e3),
                                                        self.update_view_timeout,
                                                        priority=PRIORITY_UPDATE_VIEW)
  
      def _on_play_button_toggled(self, button):
          if button.get_active():
              self._start_update_timer()
          else:
              if self._update_timeout_id is not None:
                  gobject.source_remove(self._update_timeout_id)
  
      def _quit(self, *dummy_args):
          if self._update_timeout_id is not None:
              gobject.source_remove(self._update_timeout_id)
              self._update_timeout_id = None
          self.simulation.quit = True
          self.simulation.go.set()
          self.simulation.join()
          gtk.main_quit()
  
      def _monkey_patch_ipython(self):
          # The user may want to access the NS 3 simulation state, but
          # NS 3 is not thread safe, so it could cause serious problems.
          # To work around this, monkey-patch IPython to automatically
          # acquire and release the simulation lock around each code
          # that is executed.
  
          original_runcode = __IPYTHON__.runcode
          def runcode(ip, *args):
              #print "lock"
              self.simulation.lock.acquire()
              try:
                  return original_runcode(*args)
              finally:
                  #print "unlock"
                  self.simulation.lock.release()
          import types
          __IPYTHON__.runcode = types.MethodType(runcode, __IPYTHON__)                
  
      def autoscale_view(self):
          if not self.nodes:
              return
          self._update_node_positions()
          positions = [node.get_position() for node in self.nodes.itervalues()]
          min_x, min_y = min(x for (x,y) in positions), min(y for (x,y) in positions)
          max_x, max_y = max(x for (x,y) in positions), max(y for (x,y) in positions)
          min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
          max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
          dx = max_x - min_x
          dy = max_y - min_y
          dx_px = max_x_px - min_x_px
          dy_px = max_y_px - min_y_px
          hadj = self._scrolled_window.get_hadjustment()
          vadj = self._scrolled_window.get_vadjustment()
          new_dx, new_dy = 1.5*dx_px, 1.5*dy_px
  
          if new_dx == 0 or new_dy == 0:
              return
  
          self.zoom.value = min(hadj.page_size/new_dx, vadj.page_size/new_dy)
  
          x1, y1 = self.canvas.convert_from_pixels(hadj.value, vadj.value)
          x2, y2 = self.canvas.convert_from_pixels(hadj.value+hadj.page_size, vadj.value+vadj.page_size)
          width = x2 - x1
          height = y2 - y1
          center_x = (min_x + max_x) / 2
          center_y = (min_y + max_y) / 2
          
          self.canvas.scroll_to(center_x - width/2, center_y - height/2)
  
          return False
  
      def start(self):
          self.scan_topology()
          self.window.connect("delete-event", self._quit)
          #self._start_update_timer()
          gobject.timeout_add(200, self.autoscale_view)
          self.simulation.start()
  
          try:
              __IPYTHON__
          except NameError:
              pass
          else:
              self._monkey_patch_ipython()
  
          gtk.main()
  
  
      def on_root_button_press_event(self, view, target, event):
          if event.button == 1:
              self.select_node(None)
              return True
  
      def on_node_button_press_event(self, view, target, event, node):
          if event.button == 1:
              self.select_node(node)
              return True
          elif event.button == 3:
              self.popup_node_menu(node, event)
              return True
          elif event.button == 2:
              self.begin_node_drag(node)
              return True
          return False
  
      def on_node_button_release_event(self, view, target, event, node):
          if event.button == 2:
              self.end_node_drag(node)
              return True
          return False
  
      class NodeDragState(object):
          def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
              self.canvas_x0 = canvas_x0
              self.canvas_y0 = canvas_y0
              self.sim_x0 = sim_x0
              self.sim_y0 = sim_y0
              self.motion_signal = None
  
      def begin_node_drag(self, node):
          self.simulation.lock.acquire()
          try:
              ns3_node = ns.network.NodeList.GetNode(node.node_index)
              mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
              if mob is None:
                  return
              if self.node_drag_state is not None:
                  return
              pos = mob.GetPosition()
          finally:
              self.simulation.lock.release()            
          x, y, dummy = self.canvas.window.get_pointer()
          x0, y0 = self.canvas.convert_from_pixels(x, y)
          self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
          self.node_drag_state.motion_signal = node.canvas_item.connect("motion-notify-event", self.node_drag_motion, node)
  
      def node_drag_motion(self, item, targe_item, event, node):
          self.simulation.lock.acquire()
          try:
              ns3_node = ns.network.NodeList.GetNode(node.node_index)
              mob = ns3_node.GetObject(ns.mobility.MobilityModel.GetTypeId())
              if mob is None:
                  return False
              if self.node_drag_state is None:
                  return False
              x, y, dummy = self.canvas.window.get_pointer()
              canvas_x, canvas_y = self.canvas.convert_from_pixels(x, y)
              dx = (canvas_x - self.node_drag_state.canvas_x0)
              dy = (canvas_y - self.node_drag_state.canvas_y0)
              pos = mob.GetPosition()
              pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
              pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
              #print "SetPosition(%G, %G)" % (pos.x, pos.y)
              mob.SetPosition(pos)
              node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
          finally:
              self.simulation.lock.release()            
          return True
  
      def end_node_drag(self, node):
          if self.node_drag_state is None:
              return
          node.canvas_item.disconnect(self.node_drag_state.motion_signal)
          self.node_drag_state = None
  
      def popup_node_menu(self, node, event):
          menu = gtk.Menu()
          self.emit("populate-node-menu", node, menu)
          menu.popup(None, None, None, event.button, event.time)
  
      def _update_ipython_selected_node(self):
          # If we are running under ipython -gthread, make this new
          # selected node available as a global 'selected_node'
          # variable.
          try:
              __IPYTHON__
          except NameError:
              pass
          else:
              if self.selected_node is None:
                  ns3_node = None
              else:
                  self.simulation.lock.acquire()
                  try:
                      ns3_node = ns.network.NodeList.GetNode(self.selected_node.node_index)
                  finally:
                      self.simulation.lock.release()
              __IPYTHON__.user_ns['selected_node'] = ns3_node
  
  
      def select_node(self, node):
          if isinstance(node, ns.network.Node):
              node = self.nodes[node.GetId()]
          elif isinstance(node, (int, long)):
              node = self.nodes[node]
          elif isinstance(node, Node):
              pass
          elif node is None:
              pass
          else:
              raise TypeError("expected None, int, viz.Node or ns.network.Node, not %r" % node)
  
          if node is self.selected_node:
              return
  
          if self.selected_node is not None:
              self.selected_node.selected = False
          self.selected_node = node
          if self.selected_node is not None:
              self.selected_node.selected = True
  
          if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
              if self.selected_node is None:
                  self.simulation.set_nodes_of_interest([])
              else:
                  self.simulation.set_nodes_of_interest([self.selected_node.node_index])
  
          self._update_ipython_selected_node()
  
  
      def add_information_window(self, info_win):
          self.information_windows.append(info_win)
          self.simulation.lock.acquire()
          try:
              info_win.update()
          finally:
              self.simulation.lock.release()
  
      def remove_information_window(self, info_win):
          self.information_windows.remove(info_win)
          
      def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
          #print "tooltip query: ", x, y
          hadj = self._scrolled_window.get_hadjustment()
          vadj = self._scrolled_window.get_vadjustment()
          x, y = self.canvas.convert_from_pixels(hadj.value + x, vadj.value + y)
          item = self.canvas.get_item_at(x, y, True)
          #print "items at (%f, %f): %r | keyboard_mode=%r" % (x, y, item, keyboard_mode)
          if not item:
              return False
          while item is not None:
              obj = item.get_data("pyviz-object")
              if obj is not None:
                  obj.tooltip_query(tooltip)
                  return True
              item = item.props.parent
          return False
  
      def _get_export_file_name(self):
          sel = gtk.FileChooserDialog("Save...", self.canvas.get_toplevel(),
                                      gtk.FILE_CHOOSER_ACTION_SAVE,
                                      (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                       gtk.STOCK_SAVE, gtk.RESPONSE_OK))
          sel.set_default_response(gtk.RESPONSE_OK)
          sel.set_local_only(True)
          sel.set_do_overwrite_confirmation(True)
          sel.set_current_name("Unnamed.pdf")
  
          filter = gtk.FileFilter()
          filter.set_name("Embedded PostScript")
          filter.add_mime_type("image/x-eps")
          sel.add_filter(filter)
  
          filter = gtk.FileFilter()
          filter.set_name("Portable Document Graphics")
          filter.add_mime_type("application/pdf")
          sel.add_filter(filter)
  
          filter = gtk.FileFilter()
          filter.set_name("Scalable Vector Graphics")
          filter.add_mime_type("image/svg+xml")
          sel.add_filter(filter)
  
          resp = sel.run()
          if resp != gtk.RESPONSE_OK:
              sel.destroy()
              return None
          
          file_name = sel.get_filename()
          sel.destroy()
          return file_name
  
      def _take_screenshot(self, dummy_button):
          #print "Cheese!"
          file_name = self._get_export_file_name()
          if file_name is None:
              return
  
          # figure out the correct bounding box for what is visible on screen
          x1 = self._scrolled_window.get_hadjustment().value
          y1 = self._scrolled_window.get_vadjustment().value
          x2 = x1 + self._scrolled_window.get_hadjustment().page_size
          y2 = y1 + self._scrolled_window.get_vadjustment().page_size
          bounds = goocanvas.Bounds()
          bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
          bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
          dest_width = bounds.x2 - bounds.x1
          dest_height = bounds.y2 - bounds.y1
          #print bounds.x1, bounds.y1, " -> ", bounds.x2, bounds.y2
  
          dummy, extension = os.path.splitext(file_name)
          extension = extension.lower()
          if extension == '.eps':
              surface = cairo.PSSurface(file_name, dest_width, dest_height)
          elif extension == '.pdf':
              surface = cairo.PDFSurface(file_name, dest_width, dest_height)
          elif extension == '.svg':
              surface = cairo.SVGSurface(file_name, dest_width, dest_height)
          else:
              dialog = gtk.MessageDialog(parent  = self.canvas.get_toplevel(),
                  		       flags   = gtk.DIALOG_DESTROY_WITH_PARENT,
                  		       type    = gtk.MESSAGE_ERROR,
                  		       buttons = gtk.BUTTONS_OK,
                  		       message_format = "Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
                                                            % (extension,))
              dialog.run()
              dialog.destroy()
              return
  
          # draw the canvas to a printing context
          cr = cairo.Context(surface)
          cr.translate(-bounds.x1, -bounds.y1)
          self.canvas.render(cr, bounds, self.zoom.value)
          cr.show_page()
          surface.finish()
  
      def set_follow_node(self, node):
          if isinstance(node, ns.network.Node):
              node = self.nodes[node.GetId()]
          self.follow_node = node
  
      def _start_shell(self, dummy_button):
          if self.shell_window is not None:
              self.shell_window.present()
              return
          
          self.shell_window = gtk.Window()
          self.shell_window.set_size_request(750,550)
          self.shell_window.set_resizable(True)
          scrolled_window = gtk.ScrolledWindow()
          scrolled_window.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
          ipython = ipython_view.IPythonView()
          ipython.modify_font(pango.FontDescription(SHELL_FONT))
          ipython.set_wrap_mode(gtk.WRAP_CHAR)
          ipython.show()
          scrolled_window.add(ipython)
          scrolled_window.show()
          self.shell_window.add(scrolled_window)
          self.shell_window.show()
          self.shell_window.connect('destroy', self._on_shell_window_destroy)
  
          self._update_ipython_selected_node()
          __IPYTHON__.user_ns['viz'] = self
  
  
      def _on_shell_window_destroy(self, window):
          self.shell_window = None
  
  
  initialization_hooks = []
  
  def add_initialization_hook(hook, *args):
      """
      Adds a callback to be called after
      the visualizer is initialized, like this::
         initialization_hook(visualizer, *args)
      """
      global initialization_hooks
      initialization_hooks.append((hook, args))
  
  
  def set_bounds(x1, y1, x2, y2):
      assert x2>x1
      assert y2>y1
      def hook(viz):
          cx1, cy1 = transform_point_simulation_to_canvas(x1, y1)
          cx2, cy2 = transform_point_simulation_to_canvas(x2, y2)
          viz.canvas.set_bounds(cx1, cy1, cx2, cy2)
      add_initialization_hook(hook)
  
  
  def start():
      assert Visualizer.INSTANCE is None
      if _import_error is not None:
          import sys
          print >> sys.stderr, "No visualization support (%s)." % (str(_import_error),)
          ns.core.Simulator.Run()
          return
      load_plugins()
      viz = Visualizer()
      for hook, args in initialization_hooks:
          gobject.idle_add(hook, viz, *args)
      ns.network.Packet.EnablePrinting()
      viz.start()
  