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