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