786 if _import_error
is None:
789 "populate-node-menu": (
790 GObject.SignalFlags.RUN_LAST,
799 "simulation-periodic-update": (GObject.SignalFlags.RUN_LAST,
None, ()),
801 "topology-scanned": (GObject.SignalFlags.RUN_LAST,
None, ()),
803 "update-view": (GObject.SignalFlags.RUN_LAST,
None, ()),
808 Initializer function.
810 @param self: class object.
813 assert Visualizer.INSTANCE
is None
814 Visualizer.INSTANCE = self
815 super(Visualizer, self).__init__()
820 self.time_label =
None
821 self.play_button =
None
823 self._scrolled_window =
None
825 self.links_group = GooCanvas.CanvasGroup()
826 self.channels_group = GooCanvas.CanvasGroup()
827 self.nodes_group = GooCanvas.CanvasGroup()
829 self._update_timeout_id =
None
831 self.selected_node =
None
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
850 for plugin
in plugins:
853 def set_show_transmissions_mode(self, mode):
855 Set show transmission mode.
857 @param self: class object.
858 @param mode: mode to set.
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([])
871 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
873 def _create_advanced_controls(self):
875 Create advanced controls.
877 @param self: class object.
880 expander = Gtk.Expander.new(
"Advanced")
883 main_vbox = GObject.new(Gtk.VBox, border_width=8, visible=
True)
884 expander.add(main_vbox)
886 main_hbox1 = GObject.new(Gtk.HBox, border_width=8, visible=
True)
887 main_vbox.pack_start(main_hbox1,
True,
True, 0)
889 show_transmissions_group = GObject.new(
890 Gtk.HeaderBar, title=
"Show transmissions", visible=
True
892 main_hbox1.pack_start(show_transmissions_group,
False,
False, 8)
894 vbox = Gtk.VBox(homogeneous=
True, spacing=4)
896 show_transmissions_group.add(vbox)
898 all_nodes = Gtk.RadioButton.new(
None)
899 all_nodes.set_label(
"All nodes")
900 all_nodes.set_active(
True)
904 selected_node = Gtk.RadioButton.new_from_widget(all_nodes)
906 selected_node.set_label(
"Selected node")
907 selected_node.set_active(
False)
908 vbox.add(selected_node)
910 no_node = Gtk.RadioButton.new_from_widget(all_nodes)
912 no_node.set_label(
"Disabled")
913 no_node.set_active(
False)
917 if radio.get_active():
918 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
920 all_nodes.connect(
"toggled", toggled)
923 if radio.get_active():
924 self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
926 no_node.connect(
"toggled", toggled)
929 if radio.get_active():
930 self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
932 selected_node.connect(
"toggled", toggled)
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)
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()
948 def node_size_changed(adj):
949 for node
in self.nodes.values():
950 node.set_size(adj.get_value())
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)
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)
963 GObject.new(Gtk.Label, label=
"Tx. Smooth Factor (s)", visible=
True),
True,
True, 0
965 settings_hbox.pack_start(vbox,
False,
False, 6)
966 self.transmissions_smoothing_adjustment = scale.get_adjustment()
967 adj = self.transmissions_smoothing_adjustment
970 adj.set_step_increment(0.1)
971 adj.set_value(DEFAULT_TRANSMISSIONS_MEMORY * 0.1)
976 class _PanningState(
object):
979 __slots__ = [
"initial_mouse_pos",
"initial_canvas_pos",
"motion_signal"]
981 def _begin_panning(self, widget, event):
983 Set show trnamission mode.
985 @param self: class object.
986 @param mode: mode to set.
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
1002 def _end_panning(self, event):
1004 End panning function.
1006 @param self: class object.
1007 @param event: active event.
1010 if self._panning_state
is None:
1012 self.canvas.get_window().set_cursor(
None)
1013 self.canvas.disconnect(self._panning_state.motion_signal)
1014 self._panning_state =
None
1016 def _panning_motion(self, widget, event):
1018 Panning motion function.
1020 @param self: class object.
1021 @param widget: widget.
1022 @param event: event.
1023 @return true if successful
1025 assert self._panning_state
is not None
1027 pos = widget.get_window().get_device_position(event.device)
1030 x, y = event.x, event.y
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
1039 hadj.set_value(cx0 - dx)
1040 vadj.set_value(cy0 - dy)
1043 def _canvas_button_press(self, widget, event):
1044 if event.button == 2:
1045 self._begin_panning(widget, event)
1049 def _canvas_button_release(self, dummy_widget, event):
1050 if event.button == 2:
1051 self._end_panning(event)
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)
1059 elif event.direction == Gdk.ScrollDirection.DOWN:
1060 self.zoom.set_value(self.zoom.get_value() / 1.25)
1064 def get_hadjustment(self):
1065 return self._scrolled_window.get_hadjustment()
1067 def get_vadjustment(self):
1068 return self._scrolled_window.get_vadjustment()
1070 def create_gui(self):
1071 self.window = Gtk.Window()
1072 self.window.set_title(sys.argv[0])
1075 self.window.add(vbox)
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)
1085 sw = Gtk.ScrolledWindow()
1087 self._scrolled_window = sw
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)
1094 self.canvas.get_root_item().add_child(self.links_group, -1)
1095 self.links_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
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)
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)
1109 vbox.pack_start(hbox,
False,
False, 4)
1112 zoom_adj = Gtk.Adjustment(
1116 step_increment=0.02,
1120 self.zoom = zoom_adj
1122 def _zoom_changed(adj):
1123 self.canvas.set_scale(adj.get_value())
1125 zoom_adj.connect(
"value-changed", _zoom_changed)
1126 zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
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)
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
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()
1143 speed_adj.connect(
"value-changed", _speed_changed)
1144 speed = Gtk.SpinButton.new(speed_adj, 1, 0)
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)
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)
1157 screenshot_button = GObject.new(
1160 relief=Gtk.ReliefStyle.NONE,
1161 focus_on_click=
False,
1164 hbox.pack_start(screenshot_button,
False,
False, 4)
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)
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
1174 load_button_icon(screenshot_button,
"applets-screenshooter")
1175 screenshot_button.connect(
"clicked", self._take_screenshot)
1178 if ipython_view
is not None:
1179 shell_button = GObject.new(
1182 relief=Gtk.ReliefStyle.NONE,
1183 focus_on_click=
False,
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)
1191 self.play_button = GObject.new(
1193 label=
"Simulate (F3)",
1194 relief=Gtk.ReliefStyle.NONE,
1195 focus_on_click=
False,
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
1204 self.play_button.connect(
"toggled", self._on_play_button_toggled)
1205 hbox.pack_start(self.play_button,
False,
False, 4)
1207 self.canvas.get_root_item().connect(
"button-press-event", self.on_root_button_press_event)
1209 vbox.pack_start(self._create_advanced_controls(),
False,
False, 4)
1211 display = Gdk.Display.get_default()
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)
1226 def scan_topology(self):
1227 print(
"scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1228 graph = pygraphviz.AGraph()
1230 for nodeI
in range(ns.NodeList.GetNNodes()):
1232 if seen_nodes == 100:
1234 "scan topology... %i nodes visited (%.1f%%)"
1235 % (nodeI, 100 * nodeI / ns.NodeList.GetNNodes())
1238 node = ns.NodeList.GetNode(nodeI)
1239 node_name =
"Node %i" % nodeI
1240 node_view = self.get_node(nodeI)
1242 mobility = node.GetObject[ns.MobilityModel]()
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))
1249 graph.add_node(node_name)
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:
1256 if device_traits.is_virtual:
1258 channel = device.GetChannel()
1259 if channel.GetNDevices() > 2:
1260 if REPRESENT_CHANNELS_AS_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))
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)
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)
1289 print(
"scanning topology: calling graphviz layout")
1290 graph.layout(LAYOUT_ALGORITHM)
1291 for node
in graph.iternodes():
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)
1301 print(
"scanning topology: all done.")
1302 self.emit(
"topology-scanned")
1304 def get_node(self, index):
1306 return self.nodes[index]
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
1317 def get_channel(self, ns3_channel):
1319 return self.channels[id(ns3_channel)]
1321 channel =
Channel(ns3_channel)
1322 self.channels[id(ns3_channel)] = channel
1323 self.channels_group.add_child(channel.canvas_item, -1)
1326 def create_link(self, node, node_or_channel):
1328 self.links_group.add_child(link.canvas_item, -1)
1329 link.canvas_item.lower(
None)
1331 def update_view(self):
1334 self.time_label.set_text(
"Time: %f s" % ns.Simulator.Now().GetSeconds())
1336 self._update_node_positions()
1339 for info_win
in self.information_windows:
1342 self._update_transmissions_view()
1343 self._update_drops_view()
1345 self.emit(
"update-view")
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]()
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)
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):
1371 raise TypeError(
"expected int, viz.Node or ns.Node, not %r" % node)
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)
1380 def update_model(self):
1381 self.simulation.lock.acquire()
1383 self.emit(
"simulation-periodic-update")
1385 self.simulation.lock.release()
1387 def do_simulation_periodic_update(self):
1388 smooth_factor = int(self.transmissions_smoothing_adjustment.get_value() * 10)
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)
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)
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()
1407 ns.PyViz.LineClipping(
1408 bounds_x1, bounds_y1, bounds_x2, bounds_y2, pos1_x, pos1_y, pos2_x, pos2_y
1410 return (pos1_x.value + pos2_x.value) / 2, (pos1_y.value + pos2_y.value) / 2
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
1420 transmissions_average[key] = rx_bytes, count
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)
1428 k = self.node_size_adjustment.get_value() / 5
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)
1434 arrow, label = old_arrows.pop()
1436 arrow = GooCanvas.CanvasPolyline(
1438 stroke_color_rgba=0x00C000C0,
1441 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1443 arrow.set_property(
"parent", self.canvas.get_root_item())
1446 label = GooCanvas.CanvasText(
1447 parent=self.canvas.get_root_item(),
1448 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
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)
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)
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)),
1470 angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1471 if angle > math.pi / 2
or angle < -math.pi / 2:
1477 label.set_properties(
1478 text=(
"← %.2f kbit/s" % (kbps,)),
1479 alignment=Pango.Alignment.CENTER,
1480 anchor=GooCanvas.CanvasAnchorType.S,
1485 label.set_properties(
1486 text=(
"%.2f kbit/s →" % (kbps,)),
1487 alignment=Pango.Alignment.CENTER,
1488 anchor=GooCanvas.CanvasAnchorType.N,
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)
1499 label.set_transform(M)
1503 "PyGobject bug causing label position error; "
1504 "should be fixed in PyGObject >= 3.29.1"
1506 label.set_properties(x=(lx + label.props.x), y=(ly + label.props.y))
1508 new_arrows.append((arrow, label))
1510 self._transmission_arrows = new_arrows + old_arrows
1512 def _update_drops_view(self):
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
1520 drops_average[key] = drop_bytes, count
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)
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)
1533 k = self.node_size_adjustment.get_value() / 5
1535 for transmitter_id, (drop_bytes, drop_count)
in drops_average.items():
1536 transmitter = self.get_node(transmitter_id)
1538 arrow, label = old_arrows.pop()
1540 arrow = GooCanvas.CanvasPolyline(
1542 stroke_color_rgba=0xC00000C0,
1545 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1547 arrow.set_property(
"parent", self.canvas.get_root_item())
1550 label = GooCanvas.CanvasText(
1551 pointer_events=GooCanvas.CanvasPointerEvents.NONE
1553 label.set_property(
"parent", self.canvas.get_root_item())
1556 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1559 max(0.1, math.log(float(drop_bytes) / drop_count / self.sample_period) * k),
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)
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)),
1573 "%.2f kbit/s" % (float(drop_bytes * 8) / 1e3 / drop_count / self.sample_period,)
1575 alignment=Pango.Alignment.CENTER,
1576 x=(pos1_x + pos2_x) / 2,
1577 y=(pos1_y + pos2_y) / 2,
1580 new_arrows.append((arrow, label))
1582 self._drop_arrows = new_arrows + old_arrows
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)
1590 GLib.Source.remove(self._update_timeout_id)
1594 self._update_timeout_id =
None
1595 self.play_button.set_active(
False)
1596 self.play_button.set_sensitive(
False)
1598 def update_view_timeout(self):
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 = []
1609 self.simulation.target_time = ns.Simulator.Now().GetSeconds() + self.sample_period
1612 self.simulation.lock.release()
1616 dialog = Gtk.MessageDialog(
1619 type=Gtk.MessageType.WARNING,
1620 buttons=Gtk.ButtonsType.OK,
1621 message_format=
"\n".join(pause_messages),
1623 dialog.connect(
"response",
lambda d, r: d.destroy())
1625 self.play_button.set_active(
False)
1628 if not self.play_button.get_active():
1629 self._update_timeout_id =
None
1633 self.simulation.go.set()
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)
1642 GLib.Source.remove(self._update_timeout_id)
1646 self._update_timeout_id =
None
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,
1654 def _on_play_button_toggled(self, button):
1655 if button.get_active():
1656 self._start_update_timer()
1658 if self._update_timeout_id
is not None:
1659 idExists = GLib.MainContext.default().find_source_by_id(self._update_timeout_id)
1662 GLib.Source.remove(self._update_timeout_id)
1666 self._update_timeout_id =
None
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)
1673 GLib.Source.remove(self._update_timeout_id)
1677 self._update_timeout_id =
None
1679 self.simulation.quit =
True
1680 self.simulation.go.set()
1681 self.simulation.join()
1684 def _monkey_patch_ipython(self):
1691 original_runcode = self.ipython.runcode
1693 def runcode(ip, *args):
1695 self.simulation.lock.acquire()
1697 return original_runcode(*args)
1700 self.simulation.lock.release()
1704 self.ipython.runcode = types.MethodType(runcode, self.ipython)
1706 def autoscale_view(self):
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)
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
1723 if new_dx == 0
or new_dy == 0:
1726 self.zoom.set_value(min(hadj.get_page_size() / new_dx, vadj.get_page_size() / new_dy))
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())
1734 center_x = (min_x + max_x) / 2
1735 center_y = (min_y + max_y) / 2
1737 self.canvas.scroll_to(center_x - width / 2, center_y - height / 2)
1742 self.scan_topology()
1743 self.window.connect(
"delete-event", self._quit)
1745 GLib.timeout_add(200, self.autoscale_view)
1746 self.simulation.
start()
1753 self._monkey_patch_ipython()
1757 def on_root_button_press_event(self, view, target, event):
1758 if event.button == 1:
1759 self.select_node(
None)
1762 def on_node_button_press_event(self, view, target, event, node):
1763 button = event.button
1765 self.select_node(node)
1768 self.popup_node_menu(node, event)
1771 self.begin_node_drag(node, event)
1775 def on_node_button_release_event(self, view, target, event, node):
1776 if event.button == 2:
1777 self.end_node_drag(node)
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
1789 def begin_node_drag(self, node, event):
1790 self.simulation.lock.acquire()
1792 ns3_node = ns.NodeList.GetNode(node.node_index)
1793 mobility = ns3_node.GetObject[ns.MobilityModel]()
1796 if self.node_drag_state
is not None:
1798 pos = mobility.__deref__().GetPosition()
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
1808 def node_drag_motion(self, item, targe_item, event, node):
1809 self.simulation.lock.acquire()
1811 ns3_node = ns.NodeList.GetNode(node.node_index)
1812 mobility = ns3_node.GetObject[ns.MobilityModel]()
1815 if self.node_drag_state
is None:
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)
1825 mobility.SetPosition(pos)
1826 node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1828 self.simulation.lock.release()
1831 def end_node_drag(self, node):
1832 if self.node_drag_state
is None:
1834 node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1835 self.node_drag_state =
None
1837 def popup_node_menu(self, node, event):
1839 self.emit(
"populate-node-menu", node, menu)
1840 menu.popup_at_pointer(event)
1842 def _update_ipython_selected_node(self):
1851 if self.selected_node
is None:
1854 self.simulation.lock.acquire()
1856 ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1858 self.simulation.lock.release()
1859 self.ipython.updateNamespace({
"selected_node": ns3_node})
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):
1871 raise TypeError(
"expected None, int, viz.Node or ns.Node, not %r" % node)
1873 if node
is self.selected_node:
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
1882 if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1883 if self.selected_node
is None:
1884 self.simulation.set_nodes_of_interest([])
1886 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1888 self._update_ipython_selected_node()
1890 def add_information_window(self, info_win):
1891 self.information_windows.append(info_win)
1892 self.simulation.lock.acquire()
1896 self.simulation.lock.release()
1898 def remove_information_window(self, info_win):
1899 self.information_windows.remove(info_win)
1901 def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
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)
1910 while item
is not None:
1911 obj = getattr(item,
"pyviz_object",
None)
1913 obj.tooltip_query(tooltip)
1915 item = item.props.parent
1918 def _get_export_file_name(self):
1919 sel = Gtk.FileChooserNative.new(
1920 "Save...", self.canvas.get_toplevel(), Gtk.FileChooserAction.SAVE,
"_Save",
"_Cancel"
1922 sel.set_local_only(
True)
1923 sel.set_do_overwrite_confirmation(
True)
1924 sel.set_current_name(
"Unnamed.pdf")
1926 filter = Gtk.FileFilter()
1927 filter.set_name(
"Embedded PostScript")
1928 filter.add_mime_type(
"image/x-eps")
1929 sel.add_filter(filter)
1931 filter = Gtk.FileFilter()
1932 filter.set_name(
"Portable Document Graphics")
1933 filter.add_mime_type(
"application/pdf")
1934 sel.add_filter(filter)
1936 filter = Gtk.FileFilter()
1937 filter.set_name(
"Scalable Vector Graphics")
1938 filter.add_mime_type(
"image/svg+xml")
1939 sel.add_filter(filter)
1942 if resp != Gtk.ResponseType.ACCEPT:
1946 file_name = sel.get_filename()
1950 def _take_screenshot(self, dummy_button):
1952 file_name = self._get_export_file_name()
1953 if file_name
is None:
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
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)
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')"
1990 cr = cairo.Context(surface)
1991 cr.translate(-bounds.x1, -bounds.y1)
1992 self.canvas.render(cr, bounds, self.zoom.get_value())
1996 def set_follow_node(self, node):
1997 if isinstance(node, ns.Node):
1998 node = self.nodes[node.GetId()]
1999 self.follow_node = node
2001 def _start_shell(self, dummy_button):
2002 if self.shell_window
is not None:
2003 self.shell_window.present()
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)
2012 self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
2013 self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
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)
2021 self._update_ipython_selected_node()
2022 self.ipython.updateNamespace({
"viz": self})
2024 def _on_shell_window_destroy(self, window):
2025 self.shell_window =
None