754 if _import_error
is None:
757 "populate-node-menu": (
758 GObject.SignalFlags.RUN_LAST,
767 "simulation-periodic-update": (GObject.SignalFlags.RUN_LAST,
None, ()),
769 "topology-scanned": (GObject.SignalFlags.RUN_LAST,
None, ()),
771 "update-view": (GObject.SignalFlags.RUN_LAST,
None, ()),
776 Initializer function.
778 @param self: class object.
781 assert Visualizer.INSTANCE
is None
782 Visualizer.INSTANCE = self
783 super(Visualizer, self).__init__()
788 self.time_label =
None
789 self.play_button =
None
791 self._scrolled_window =
None
793 self.links_group = GooCanvas.CanvasGroup()
794 self.channels_group = GooCanvas.CanvasGroup()
795 self.nodes_group = GooCanvas.CanvasGroup()
797 self._update_timeout_id =
None
799 self.selected_node =
None
801 self.information_windows = []
802 self._transmission_arrows = []
803 self._last_transmissions = []
804 self._drop_arrows = []
805 self._last_drops = []
806 self._show_transmissions_mode =
None
807 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
808 self._panning_state =
None
809 self.node_size_adjustment =
None
810 self.transmissions_smoothing_adjustment =
None
811 self.sample_period = SAMPLE_PERIOD
812 self.node_drag_state =
None
813 self.follow_node =
None
814 self.shell_window =
None
818 for plugin
in plugins:
821 def set_show_transmissions_mode(self, mode):
823 Set show transmission mode.
825 @param self: class object.
826 @param mode: mode to set.
829 assert isinstance(mode, ShowTransmissionsMode)
830 self._show_transmissions_mode = mode
831 if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
832 self.simulation.set_nodes_of_interest(list(range(ns.NodeList.GetNNodes())))
833 elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
834 self.simulation.set_nodes_of_interest([])
835 elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
836 if self.selected_node
is None:
837 self.simulation.set_nodes_of_interest([])
839 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
841 def _create_advanced_controls(self):
843 Create advanced controls.
845 @param self: class object.
848 expander = Gtk.Expander.new(
"Advanced")
851 main_vbox = GObject.new(Gtk.VBox, border_width=8, visible=
True)
852 expander.add(main_vbox)
854 main_hbox1 = GObject.new(Gtk.HBox, border_width=8, visible=
True)
855 main_vbox.pack_start(main_hbox1,
True,
True, 0)
857 show_transmissions_group = GObject.new(
858 Gtk.HeaderBar, title=
"Show transmissions", visible=
True
860 main_hbox1.pack_start(show_transmissions_group,
False,
False, 8)
862 vbox = Gtk.VBox(homogeneous=
True, spacing=4)
864 show_transmissions_group.add(vbox)
866 all_nodes = Gtk.RadioButton.new(
None)
867 all_nodes.set_label(
"All nodes")
868 all_nodes.set_active(
True)
872 selected_node = Gtk.RadioButton.new_from_widget(all_nodes)
874 selected_node.set_label(
"Selected node")
875 selected_node.set_active(
False)
876 vbox.add(selected_node)
878 no_node = Gtk.RadioButton.new_from_widget(all_nodes)
880 no_node.set_label(
"Disabled")
881 no_node.set_active(
False)
885 if radio.get_active():
886 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
888 all_nodes.connect(
"toggled", toggled)
891 if radio.get_active():
892 self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
894 no_node.connect(
"toggled", toggled)
897 if radio.get_active():
898 self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
900 selected_node.connect(
"toggled", toggled)
903 misc_settings_group = GObject.new(Gtk.HeaderBar, title=
"Misc Settings", visible=
True)
904 main_hbox1.pack_start(misc_settings_group,
False,
False, 8)
905 settings_hbox = GObject.new(Gtk.HBox, border_width=8, visible=
True)
906 misc_settings_group.add(settings_hbox)
909 vbox = GObject.new(Gtk.VBox, border_width=0, visible=
True)
910 scale = GObject.new(Gtk.HScale, visible=
True, digits=2)
911 vbox.pack_start(scale,
True,
True, 0)
912 vbox.pack_start(GObject.new(Gtk.Label, label=
"Node Size", visible=
True),
True,
True, 0)
913 settings_hbox.pack_start(vbox,
False,
False, 6)
914 self.node_size_adjustment = scale.get_adjustment()
916 def node_size_changed(adj):
917 for node
in self.nodes.values():
918 node.set_size(adj.get_value())
920 self.node_size_adjustment.connect(
"value-changed", node_size_changed)
921 self.node_size_adjustment.set_lower(0.01)
922 self.node_size_adjustment.set_upper(20)
923 self.node_size_adjustment.set_step_increment(0.1)
924 self.node_size_adjustment.set_value(DEFAULT_NODE_SIZE)
927 vbox = GObject.new(Gtk.VBox, border_width=0, visible=
True)
928 scale = GObject.new(Gtk.HScale, visible=
True, digits=1)
929 vbox.pack_start(scale,
True,
True, 0)
931 GObject.new(Gtk.Label, label=
"Tx. Smooth Factor (s)", visible=
True),
True,
True, 0
933 settings_hbox.pack_start(vbox,
False,
False, 6)
934 self.transmissions_smoothing_adjustment = scale.get_adjustment()
935 adj = self.transmissions_smoothing_adjustment
938 adj.set_step_increment(0.1)
939 adj.set_value(DEFAULT_TRANSMISSIONS_MEMORY * 0.1)
944 class _PanningState(
object):
947 __slots__ = [
"initial_mouse_pos",
"initial_canvas_pos",
"motion_signal"]
949 def _begin_panning(self, widget, event):
951 Set show trnamission mode.
953 @param self: class object.
954 @param mode: mode to set.
957 display = self.canvas.get_window().get_display()
958 cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.FLEUR)
959 self.canvas.get_window().set_cursor(cursor)
960 self._panning_state = self._PanningState()
961 pos = widget.get_window().get_device_position(event.device)
962 self._panning_state.initial_mouse_pos = (pos.x, pos.y)
963 x = self._scrolled_window.get_hadjustment().get_value()
964 y = self._scrolled_window.get_vadjustment().get_value()
965 self._panning_state.initial_canvas_pos = (x, y)
966 self._panning_state.motion_signal = self.canvas.connect(
967 "motion-notify-event", self._panning_motion
970 def _end_panning(self, event):
972 End panning function.
974 @param self: class object.
975 @param event: active event.
978 if self._panning_state
is None:
980 self.canvas.get_window().set_cursor(
None)
981 self.canvas.disconnect(self._panning_state.motion_signal)
982 self._panning_state =
None
984 def _panning_motion(self, widget, event):
986 Panning motion function.
988 @param self: class object.
989 @param widget: widget.
991 @return true if successful
993 assert self._panning_state
is not None
995 pos = widget.get_window().get_device_position(event.device)
998 x, y = event.x, event.y
1000 hadj = self._scrolled_window.get_hadjustment()
1001 vadj = self._scrolled_window.get_vadjustment()
1002 mx0, my0 = self._panning_state.initial_mouse_pos
1003 cx0, cy0 = self._panning_state.initial_canvas_pos
1007 hadj.set_value(cx0 - dx)
1008 vadj.set_value(cy0 - dy)
1011 def _canvas_button_press(self, widget, event):
1012 if event.button == 2:
1013 self._begin_panning(widget, event)
1017 def _canvas_button_release(self, dummy_widget, event):
1018 if event.button == 2:
1019 self._end_panning(event)
1023 def _canvas_scroll_event(self, dummy_widget, event):
1024 if event.direction == Gdk.ScrollDirection.UP:
1025 self.zoom.set_value(self.zoom.get_value() * 1.25)
1027 elif event.direction == Gdk.ScrollDirection.DOWN:
1028 self.zoom.set_value(self.zoom.get_value() / 1.25)
1032 def get_hadjustment(self):
1033 return self._scrolled_window.get_hadjustment()
1035 def get_vadjustment(self):
1036 return self._scrolled_window.get_vadjustment()
1038 def create_gui(self):
1039 self.window = Gtk.Window()
1042 self.window.add(vbox)
1045 self.canvas = GooCanvas.Canvas()
1046 self.canvas.connect_after(
"button-press-event", self._canvas_button_press)
1047 self.canvas.connect_after(
"button-release-event", self._canvas_button_release)
1048 self.canvas.connect(
"scroll-event", self._canvas_scroll_event)
1049 self.canvas.props.has_tooltip =
True
1050 self.canvas.connect(
"query-tooltip", self._canvas_tooltip_cb)
1052 sw = Gtk.ScrolledWindow()
1054 self._scrolled_window = sw
1056 vbox.pack_start(sw,
True,
True, 4)
1057 self.canvas.set_size_request(600, 450)
1058 self.canvas.
set_bounds(-10000, -10000, 10000, 10000)
1059 self.canvas.scroll_to(0, 0)
1061 self.canvas.get_root_item().add_child(self.links_group, -1)
1062 self.links_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1064 self.canvas.get_root_item().add_child(self.channels_group, -1)
1065 self.channels_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1066 self.channels_group.raise_(self.links_group)
1068 self.canvas.get_root_item().add_child(self.nodes_group, -1)
1069 self.nodes_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1070 self.nodes_group.raise_(self.channels_group)
1076 vbox.pack_start(hbox,
False,
False, 4)
1079 zoom_adj = Gtk.Adjustment(
1083 step_increment=0.02,
1087 self.zoom = zoom_adj
1089 def _zoom_changed(adj):
1090 self.canvas.set_scale(adj.get_value())
1092 zoom_adj.connect(
"value-changed", _zoom_changed)
1093 zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
1096 hbox.pack_start(GObject.new(Gtk.Label, label=
" Zoom:", visible=
True),
False,
False, 4)
1097 hbox.pack_start(zoom,
False,
False, 4)
1098 _zoom_changed(zoom_adj)
1101 speed_adj = Gtk.Adjustment(
1102 value=1.0, lower=0.01, upper=10.0, step_increment=0.02, page_increment=1.0, page_size=0
1105 def _speed_changed(adj):
1106 self.speed = adj.get_value()
1107 self.sample_period = SAMPLE_PERIOD * adj.get_value()
1108 self._start_update_timer()
1110 speed_adj.connect(
"value-changed", _speed_changed)
1111 speed = Gtk.SpinButton.new(speed_adj, 1, 0)
1114 hbox.pack_start(GObject.new(Gtk.Label, label=
" Speed:", visible=
True),
False,
False, 4)
1115 hbox.pack_start(speed,
False,
False, 4)
1116 _speed_changed(speed_adj)
1119 self.time_label = GObject.new(Gtk.Label, label=
" Speed:", visible=
True)
1120 self.time_label.set_width_chars(20)
1121 hbox.pack_start(self.time_label,
False,
False, 4)
1124 screenshot_button = GObject.new(
1127 relief=Gtk.ReliefStyle.NONE,
1128 focus_on_click=
False,
1131 hbox.pack_start(screenshot_button,
False,
False, 4)
1133 def load_button_icon(button, icon_name):
1134 if not Gtk.IconTheme.get_default().has_icon(icon_name):
1135 print(f
"Could not load icon {icon_name}", file=sys.stderr)
1137 image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
1138 button.set_image(image)
1139 button.props.always_show_image =
True
1141 load_button_icon(screenshot_button,
"applets-screenshooter")
1142 screenshot_button.connect(
"clicked", self._take_screenshot)
1145 if ipython_view
is not None:
1146 shell_button = GObject.new(
1149 relief=Gtk.ReliefStyle.NONE,
1150 focus_on_click=
False,
1153 hbox.pack_start(shell_button,
False,
False, 4)
1154 load_button_icon(shell_button,
"gnome-terminal")
1155 shell_button.connect(
"clicked", self._start_shell)
1158 self.play_button = GObject.new(
1160 label=
"Simulate (F3)",
1161 relief=Gtk.ReliefStyle.NONE,
1162 focus_on_click=
False,
1165 load_button_icon(self.play_button,
"media-playback-start")
1166 accel_group = Gtk.AccelGroup()
1167 self.window.add_accel_group(accel_group)
1168 self.play_button.add_accelerator(
1169 "clicked", accel_group, Gdk.KEY_F3, 0, Gtk.AccelFlags.VISIBLE
1171 self.play_button.connect(
"toggled", self._on_play_button_toggled)
1172 hbox.pack_start(self.play_button,
False,
False, 4)
1174 self.canvas.get_root_item().connect(
"button-press-event", self.on_root_button_press_event)
1176 vbox.pack_start(self._create_advanced_controls(),
False,
False, 4)
1178 display = Gdk.Display.get_default()
1180 monitor = display.get_primary_monitor()
1181 geometry = monitor.get_geometry()
1182 scale_factor = monitor.get_scale_factor()
1183 except AttributeError:
1184 screen = display.get_default_screen()
1185 monitor_id = screen.get_primary_monitor()
1186 geometry = screen.get_monitor_geometry(monitor_id)
1187 scale_factor = screen.get_monitor_scale_factor(monitor_id)
1188 width = scale_factor * geometry.width
1189 height = scale_factor * geometry.height
1190 self.window.set_default_size(width * 2 / 3, height * 2 / 3)
1193 def scan_topology(self):
1194 print(
"scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1195 graph = pygraphviz.AGraph()
1197 for nodeI
in range(ns.NodeList.GetNNodes()):
1199 if seen_nodes == 100:
1201 "scan topology... %i nodes visited (%.1f%%)"
1202 % (nodeI, 100 * nodeI / ns.NodeList.GetNNodes())
1205 node = ns.NodeList.GetNode(nodeI)
1206 node_name =
"Node %i" % nodeI
1207 node_view = self.get_node(nodeI)
1209 mobility = node.GetObject[ns.MobilityModel]()
1211 node_view.set_color(
"red")
1212 pos = node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1213 node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1216 graph.add_node(node_name)
1218 for devI
in range(node.GetNDevices()):
1219 device = node.GetDevice(devI)
1220 device_traits = lookup_netdevice_traits(type(device.__deref__()))
1221 if device_traits.is_wireless:
1223 if device_traits.is_virtual:
1225 channel = device.GetChannel()
1226 if channel.GetNDevices() > 2:
1227 if REPRESENT_CHANNELS_AS_NODES:
1229 if mobility
is None:
1230 channel_name =
"Channel %s" % id(channel)
1231 graph.add_edge(node_name, channel_name)
1232 self.get_channel(channel)
1233 self.create_link(self.get_node(nodeI), self.get_channel(channel))
1236 for otherDevI
in range(channel.GetNDevices()):
1237 otherDev = channel.GetDevice(otherDevI)
1238 otherNode = otherDev.GetNode()
1239 otherNodeView = self.get_node(otherNode.GetId())
1240 if otherNode
is not node:
1241 if mobility
is None and not otherNodeView.has_mobility:
1242 other_node_name =
"Node %i" % otherNode.GetId()
1243 graph.add_edge(node_name, other_node_name)
1244 self.create_link(self.get_node(nodeI), otherNodeView)
1246 for otherDevI
in range(channel.GetNDevices()):
1247 otherDev = channel.GetDevice(otherDevI)
1248 otherNode = otherDev.GetNode()
1249 otherNodeView = self.get_node(otherNode.GetId())
1250 if otherNode
is not node:
1251 if mobility
is None and not otherNodeView.has_mobility:
1252 other_node_name =
"Node %i" % otherNode.GetId()
1253 graph.add_edge(node_name, other_node_name)
1254 self.create_link(self.get_node(nodeI), otherNodeView)
1256 print(
"scanning topology: calling graphviz layout")
1257 graph.layout(LAYOUT_ALGORITHM)
1258 for node
in graph.iternodes():
1260 node_type, node_id = node.split(
" ")
1261 pos_x, pos_y = [float(s)
for s
in node.attr[
"pos"].split(
",")]
1262 if node_type ==
"Node":
1263 obj = self.nodes[int(node_id)]
1264 elif node_type ==
"Channel":
1265 obj = self.channels[int(node_id)]
1266 obj.set_position(pos_x, pos_y)
1268 print(
"scanning topology: all done.")
1269 self.emit(
"topology-scanned")
1271 def get_node(self, index):
1273 return self.nodes[index]
1275 node =
Node(self, index)
1276 self.nodes[index] = node
1277 self.nodes_group.add_child(node.canvas_item, -1)
1278 node.canvas_item.connect(
"button-press-event", self.on_node_button_press_event, node)
1279 node.canvas_item.connect(
1280 "button-release-event", self.on_node_button_release_event, node
1284 def get_channel(self, ns3_channel):
1286 return self.channels[id(ns3_channel)]
1288 channel =
Channel(ns3_channel)
1289 self.channels[id(ns3_channel)] = channel
1290 self.channels_group.add_child(channel.canvas_item, -1)
1293 def create_link(self, node, node_or_channel):
1295 self.links_group.add_child(link.canvas_item, -1)
1296 link.canvas_item.lower(
None)
1298 def update_view(self):
1301 self.time_label.set_text(
"Time: %f s" % ns.Simulator.Now().GetSeconds())
1303 self._update_node_positions()
1306 for info_win
in self.information_windows:
1309 self._update_transmissions_view()
1310 self._update_drops_view()
1312 self.emit(
"update-view")
1314 def _update_node_positions(self):
1315 for node
in self.nodes.values():
1316 if node.has_mobility:
1317 ns3_node = ns.NodeList.GetNode(node.node_index)
1318 mobility = ns3_node.GetObject[ns.MobilityModel]()
1320 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1321 x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
1322 node.set_position(x, y)
1323 if node
is self.follow_node:
1324 hadj = self._scrolled_window.get_hadjustment()
1325 vadj = self._scrolled_window.get_vadjustment()
1326 px, py = self.canvas.convert_to_pixels(x, y)
1327 hadj.set_value(px - hadj.get_page_size() / 2)
1328 vadj.set_value(py - vadj.get_page_size() / 2)
1330 def center_on_node(self, node):
1331 if isinstance(node, ns.Node):
1332 node = self.nodes[node.GetId()]
1333 elif isinstance(node, int):
1334 node = self.nodes[node]
1335 elif isinstance(node, Node):
1338 raise TypeError(
"expected int, viz.Node or ns.Node, not %r" % node)
1340 x, y = node.get_position()
1341 hadj = self._scrolled_window.get_hadjustment()
1342 vadj = self._scrolled_window.get_vadjustment()
1343 px, py = self.canvas.convert_to_pixels(x, y)
1344 hadj.set_value(px - hadj.get_page_size() / 2)
1345 vadj.set_value(py - vadj.get_page_size() / 2)
1347 def update_model(self):
1348 self.simulation.lock.acquire()
1350 self.emit(
"simulation-periodic-update")
1352 self.simulation.lock.release()
1354 def do_simulation_periodic_update(self):
1355 smooth_factor = int(self.transmissions_smoothing_adjustment.get_value() * 10)
1357 transmissions = self.simulation.sim_helper.GetTransmissionSamples()
1358 self._last_transmissions.append(transmissions)
1359 while len(self._last_transmissions) > smooth_factor:
1360 self._last_transmissions.pop(0)
1362 drops = self.simulation.sim_helper.GetPacketDropSamples()
1363 self._last_drops.append(drops)
1364 while len(self._last_drops) > smooth_factor:
1365 self._last_drops.pop(0)
1367 def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
1368 hadj = self._scrolled_window.get_hadjustment()
1369 vadj = self._scrolled_window.get_vadjustment()
1370 bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1371 bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(
1372 hadj.get_value() + hadj.get_page_size(), vadj.get_value() + vadj.get_page_size()
1374 ns.PyViz.LineClipping(
1375 bounds_x1, bounds_y1, bounds_x2, bounds_y2, pos1_x, pos1_y, pos2_x, pos2_y
1377 return (pos1_x.value + pos2_x.value) / 2, (pos1_y.value + pos2_y.value) / 2
1379 def _update_transmissions_view(self):
1380 transmissions_average = {}
1381 for transmission_set
in self._last_transmissions:
1382 for transmission
in transmission_set:
1383 key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
1384 rx_bytes, count = transmissions_average.get(key, (0, 0))
1385 rx_bytes += transmission.bytes
1387 transmissions_average[key] = rx_bytes, count
1389 old_arrows = self._transmission_arrows
1390 for arrow, label
in old_arrows:
1391 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1392 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1395 k = self.node_size_adjustment.get_value() / 5
1397 for (transmitter_id, receiver_id), (rx_bytes, rx_count)
in transmissions_average.items():
1398 transmitter = self.get_node(transmitter_id)
1399 receiver = self.get_node(receiver_id)
1401 arrow, label = old_arrows.pop()
1403 arrow = GooCanvas.CanvasPolyline(
1405 stroke_color_rgba=0x00C000C0,
1408 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1410 arrow.set_property(
"parent", self.canvas.get_root_item())
1413 label = GooCanvas.CanvasText(
1414 parent=self.canvas.get_root_item(),
1415 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1419 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1420 line_width = max(0.1, math.log(float(rx_bytes) / rx_count / self.sample_period) * k)
1421 arrow.set_property(
"line-width", line_width)
1423 pos1_x, pos1_y = transmitter.get_position()
1424 pos2_x, pos2_y = receiver.get_position()
1425 points = GooCanvas.CanvasPoints.new(2)
1426 points.set_point(0, pos1_x, pos1_y)
1427 points.set_point(1, pos2_x, pos2_y)
1428 arrow.set_property(
"points", points)
1430 kbps = float(rx_bytes * 8) / 1e3 / rx_count / self.sample_period
1431 label.set_properties(
1432 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1433 visibility_threshold=0.5,
1434 font=(
"Sans Serif %f" % int(1 + BITRATE_FONT_SIZE * k)),
1437 angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1438 if angle > math.pi / 2
or angle < -math.pi / 2:
1444 label.set_properties(
1445 text=(
"← %.2f kbit/s" % (kbps,)),
1446 alignment=Pango.Alignment.CENTER,
1447 anchor=GooCanvas.CanvasAnchorType.S,
1452 label.set_properties(
1453 text=(
"%.2f kbit/s →" % (kbps,)),
1454 alignment=Pango.Alignment.CENTER,
1455 anchor=GooCanvas.CanvasAnchorType.N,
1460 lx, ly = self._get_label_over_line_position(
1461 c_double(pos1_x), c_double(pos1_y), c_double(pos2_x), c_double(pos2_y)
1466 label.set_transform(M)
1470 "PyGobject bug causing label position error; "
1471 "should be fixed in PyGObject >= 3.29.1"
1473 label.set_properties(x=(lx + label.props.x), y=(ly + label.props.y))
1475 new_arrows.append((arrow, label))
1477 self._transmission_arrows = new_arrows + old_arrows
1479 def _update_drops_view(self):
1481 for drop_set
in self._last_drops:
1482 for drop
in drop_set:
1483 key = drop.transmitter.GetId()
1484 drop_bytes, count = drops_average.get(key, (0, 0))
1485 drop_bytes += drop.bytes
1487 drops_average[key] = drop_bytes, count
1489 old_arrows = self._drop_arrows
1490 for arrow, label
in old_arrows:
1491 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1492 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1496 vadjustment = self._scrolled_window.get_vadjustment()
1497 bottom_y = vadjustment.get_value() + vadjustment.get_page_size()
1498 dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1500 k = self.node_size_adjustment.get_value() / 5
1502 for transmitter_id, (drop_bytes, drop_count)
in drops_average.items():
1503 transmitter = self.get_node(transmitter_id)
1505 arrow, label = old_arrows.pop()
1507 arrow = GooCanvas.CanvasPolyline(
1509 stroke_color_rgba=0xC00000C0,
1512 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1514 arrow.set_property(
"parent", self.canvas.get_root_item())
1517 label = GooCanvas.CanvasText(
1518 pointer_events=GooCanvas.CanvasPointerEvents.NONE
1520 label.set_property(
"parent", self.canvas.get_root_item())
1523 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1526 max(0.1, math.log(float(drop_bytes) / drop_count / self.sample_period) * k),
1528 pos1_x, pos1_y = transmitter.get_position()
1529 pos2_x, pos2_y = pos1_x, edge_y
1530 points = GooCanvas.CanvasPoints.new(2)
1531 points.set_point(0, pos1_x, pos1_y)
1532 points.set_point(1, pos2_x, pos2_y)
1533 arrow.set_property(
"points", points)
1535 label.set_properties(
1536 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1537 visibility_threshold=0.5,
1538 font=(
"Sans Serif %i" % int(1 + BITRATE_FONT_SIZE * k)),
1540 "%.2f kbit/s" % (float(drop_bytes * 8) / 1e3 / drop_count / self.sample_period,)
1542 alignment=Pango.Alignment.CENTER,
1543 x=(pos1_x + pos2_x) / 2,
1544 y=(pos1_y + pos2_y) / 2,
1547 new_arrows.append((arrow, label))
1549 self._drop_arrows = new_arrows + old_arrows
1551 def _on_simulation_finished(self):
1552 print(
"Simulation finished.")
1553 if self._update_timeout_id
is not None:
1554 GLib.source_remove(self._update_timeout_id)
1555 self._update_timeout_id =
None
1556 self.play_button.set_active(
False)
1557 self.play_button.set_sensitive(
False)
1559 def update_view_timeout(self):
1563 while not self.simulation.lock.acquire(
False):
1564 while Gtk.events_pending():
1565 Gtk.main_iteration()
1566 pause_messages = self.simulation.pause_messages
1567 self.simulation.pause_messages = []
1570 self.simulation.target_time = ns.Simulator.Now().GetSeconds() + self.sample_period
1573 self.simulation.lock.release()
1577 dialog = Gtk.MessageDialog(
1580 type=Gtk.MessageType.WARNING,
1581 buttons=Gtk.ButtonsType.OK,
1582 message_format=
"\n".join(pause_messages),
1584 dialog.connect(
"response",
lambda d, r: d.destroy())
1586 self.play_button.set_active(
False)
1589 if not self.play_button.get_active():
1590 self._update_timeout_id =
None
1594 self.simulation.go.set()
1598 def _start_update_timer(self):
1599 if self._update_timeout_id
is not None:
1600 GLib.source_remove(self._update_timeout_id)
1602 self._update_timeout_id = GLib.timeout_add(
1603 int(SAMPLE_PERIOD / min(self.speed, 1) * 1e3),
1604 self.update_view_timeout,
1605 priority=PRIORITY_UPDATE_VIEW,
1608 def _on_play_button_toggled(self, button):
1609 if button.get_active():
1610 self._start_update_timer()
1612 if self._update_timeout_id
is not None:
1613 GLib.source_remove(self._update_timeout_id)
1615 def _quit(self, *dummy_args):
1616 if self._update_timeout_id
is not None:
1617 GLib.source_remove(self._update_timeout_id)
1618 self._update_timeout_id =
None
1619 self.simulation.quit =
True
1620 self.simulation.go.set()
1621 self.simulation.join()
1624 def _monkey_patch_ipython(self):
1631 original_runcode = self.ipython.runcode
1633 def runcode(ip, *args):
1635 self.simulation.lock.acquire()
1637 return original_runcode(*args)
1640 self.simulation.lock.release()
1644 self.ipython.runcode = types.MethodType(runcode, self.ipython)
1646 def autoscale_view(self):
1649 self._update_node_positions()
1650 positions = [node.get_position()
for node
in self.nodes.values()]
1651 min_x, min_y = min(x
for (x, y)
in positions), min(y
for (x, y)
in positions)
1652 max_x, max_y = max(x
for (x, y)
in positions), max(y
for (x, y)
in positions)
1653 min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1654 max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1657 dx_px = max_x_px - min_x_px
1658 dy_px = max_y_px - min_y_px
1659 hadj = self._scrolled_window.get_hadjustment()
1660 vadj = self._scrolled_window.get_vadjustment()
1661 new_dx, new_dy = 1.5 * dx_px, 1.5 * dy_px
1663 if new_dx == 0
or new_dy == 0:
1666 self.zoom.set_value(min(hadj.get_page_size() / new_dx, vadj.get_page_size() / new_dy))
1668 x1, y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1669 x2, y2 = self.canvas.convert_from_pixels(
1670 (hadj.get_value() + hadj.get_page_size()), (vadj.get_value() + vadj.get_page_size())
1674 center_x = (min_x + max_x) / 2
1675 center_y = (min_y + max_y) / 2
1677 self.canvas.scroll_to(center_x - width / 2, center_y - height / 2)
1682 self.scan_topology()
1683 self.window.connect(
"delete-event", self._quit)
1685 GLib.timeout_add(200, self.autoscale_view)
1686 self.simulation.
start()
1693 self._monkey_patch_ipython()
1697 def on_root_button_press_event(self, view, target, event):
1698 if event.button == 1:
1699 self.select_node(
None)
1702 def on_node_button_press_event(self, view, target, event, node):
1703 button = event.button
1705 self.select_node(node)
1708 self.popup_node_menu(node, event)
1711 self.begin_node_drag(node, event)
1715 def on_node_button_release_event(self, view, target, event, node):
1716 if event.button == 2:
1717 self.end_node_drag(node)
1721 class NodeDragState(
object):
1722 def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1723 self.canvas_x0 = canvas_x0
1724 self.canvas_y0 = canvas_y0
1725 self.sim_x0 = sim_x0
1726 self.sim_y0 = sim_y0
1727 self.motion_signal =
None
1729 def begin_node_drag(self, node, event):
1730 self.simulation.lock.acquire()
1732 ns3_node = ns.NodeList.GetNode(node.node_index)
1733 mob = ns3_node.GetObject[ns.MobilityModel]()
1736 if self.node_drag_state
is not None:
1738 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1740 self.simulation.lock.release()
1741 devpos = self.canvas.get_window().get_device_position(event.device)
1742 x0, y0 = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1743 self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1744 self.node_drag_state.motion_signal = node.canvas_item.connect(
1745 "motion-notify-event", self.node_drag_motion, node
1748 def node_drag_motion(self, item, targe_item, event, node):
1749 self.simulation.lock.acquire()
1751 ns3_node = ns.NodeList.GetNode(node.node_index)
1752 mob = ns3_node.GetObject[ns.MobilityModel]()
1755 if self.node_drag_state
is None:
1757 devpos = self.canvas.get_window().get_device_position(event.device)
1758 canvas_x, canvas_y = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1759 dx = canvas_x - self.node_drag_state.canvas_x0
1760 dy = canvas_y - self.node_drag_state.canvas_y0
1761 pos = mob.GetPosition()
1762 pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1763 pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1765 mob.SetPosition(pos)
1766 node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1768 self.simulation.lock.release()
1771 def end_node_drag(self, node):
1772 if self.node_drag_state
is None:
1774 node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1775 self.node_drag_state =
None
1777 def popup_node_menu(self, node, event):
1779 self.emit(
"populate-node-menu", node, menu)
1780 menu.popup_at_pointer(event)
1782 def _update_ipython_selected_node(self):
1791 if self.selected_node
is None:
1794 self.simulation.lock.acquire()
1796 ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1798 self.simulation.lock.release()
1799 self.ipython.updateNamespace({
"selected_node": ns3_node})
1801 def select_node(self, node):
1802 if isinstance(node, ns.Node):
1803 node = self.nodes[node.GetId()]
1804 elif isinstance(node, int):
1805 node = self.nodes[node]
1806 elif isinstance(node, Node):
1811 raise TypeError(
"expected None, int, viz.Node or ns.Node, not %r" % node)
1813 if node
is self.selected_node:
1816 if self.selected_node
is not None:
1817 self.selected_node.selected =
False
1818 self.selected_node = node
1819 if self.selected_node
is not None:
1820 self.selected_node.selected =
True
1822 if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1823 if self.selected_node
is None:
1824 self.simulation.set_nodes_of_interest([])
1826 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1828 self._update_ipython_selected_node()
1830 def add_information_window(self, info_win):
1831 self.information_windows.append(info_win)
1832 self.simulation.lock.acquire()
1836 self.simulation.lock.release()
1838 def remove_information_window(self, info_win):
1839 self.information_windows.remove(info_win)
1841 def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1843 hadj = self._scrolled_window.get_hadjustment()
1844 vadj = self._scrolled_window.get_vadjustment()
1845 x, y = self.canvas.convert_from_pixels(hadj.get_value() + x, vadj.get_value() + y)
1846 item = self.canvas.get_item_at(x, y,
True)
1850 while item
is not None:
1851 obj = getattr(item,
"pyviz_object",
None)
1853 obj.tooltip_query(tooltip)
1855 item = item.props.parent
1858 def _get_export_file_name(self):
1859 sel = Gtk.FileChooserNative.new(
1860 "Save...", self.canvas.get_toplevel(), Gtk.FileChooserAction.SAVE,
"_Save",
"_Cancel"
1862 sel.set_local_only(
True)
1863 sel.set_do_overwrite_confirmation(
True)
1864 sel.set_current_name(
"Unnamed.pdf")
1866 filter = Gtk.FileFilter()
1867 filter.set_name(
"Embedded PostScript")
1868 filter.add_mime_type(
"image/x-eps")
1869 sel.add_filter(filter)
1871 filter = Gtk.FileFilter()
1872 filter.set_name(
"Portable Document Graphics")
1873 filter.add_mime_type(
"application/pdf")
1874 sel.add_filter(filter)
1876 filter = Gtk.FileFilter()
1877 filter.set_name(
"Scalable Vector Graphics")
1878 filter.add_mime_type(
"image/svg+xml")
1879 sel.add_filter(filter)
1882 if resp != Gtk.ResponseType.ACCEPT:
1886 file_name = sel.get_filename()
1890 def _take_screenshot(self, dummy_button):
1892 file_name = self._get_export_file_name()
1893 if file_name
is None:
1897 x1 = self._scrolled_window.get_hadjustment().get_value()
1898 y1 = self._scrolled_window.get_vadjustment().get_value()
1899 x2 = x1 + self._scrolled_window.get_hadjustment().get_page_size()
1900 y2 = y1 + self._scrolled_window.get_vadjustment().get_page_size()
1901 bounds = GooCanvas.CanvasBounds()
1902 bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1903 bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1904 dest_width = bounds.x2 - bounds.x1
1905 dest_height = bounds.y2 - bounds.y1
1908 dummy, extension = os.path.splitext(file_name)
1909 extension = extension.lower()
1910 if extension ==
".eps":
1911 surface = cairo.PSSurface(file_name, dest_width, dest_height)
1912 elif extension ==
".pdf":
1913 surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1914 elif extension ==
".svg":
1915 surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1917 dialog = Gtk.MessageDialog(
1918 parent=self.canvas.get_toplevel(),
1919 flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
1920 type=Gtk.MessageType.ERROR,
1921 buttons=Gtk.ButtonsType.OK,
1922 message_format=
"Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1930 cr = cairo.Context(surface)
1931 cr.translate(-bounds.x1, -bounds.y1)
1932 self.canvas.render(cr, bounds, self.zoom.get_value())
1936 def set_follow_node(self, node):
1937 if isinstance(node, ns.Node):
1938 node = self.nodes[node.GetId()]
1939 self.follow_node = node
1941 def _start_shell(self, dummy_button):
1942 if self.shell_window
is not None:
1943 self.shell_window.present()
1946 self.shell_window = Gtk.Window()
1947 self.shell_window.set_size_request(750, 550)
1948 self.shell_window.set_resizable(
True)
1949 scrolled_window = Gtk.ScrolledWindow()
1950 scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
1952 self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
1953 self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
1955 scrolled_window.add(self.ipython)
1956 scrolled_window.show()
1957 self.shell_window.add(scrolled_window)
1958 self.shell_window.show()
1959 self.shell_window.connect(
"destroy", self._on_shell_window_destroy)
1961 self._update_ipython_selected_node()
1962 self.ipython.updateNamespace({
"viz": self})
1964 def _on_shell_window_destroy(self, window):
1965 self.shell_window =
None