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