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()
1040 self.window.set_title(sys.argv[0])
1043 self.window.add(vbox)
1046 self.canvas = GooCanvas.Canvas()
1047 self.canvas.connect_after(
"button-press-event", self._canvas_button_press)
1048 self.canvas.connect_after(
"button-release-event", self._canvas_button_release)
1049 self.canvas.connect(
"scroll-event", self._canvas_scroll_event)
1050 self.canvas.props.has_tooltip =
True
1051 self.canvas.connect(
"query-tooltip", self._canvas_tooltip_cb)
1053 sw = Gtk.ScrolledWindow()
1055 self._scrolled_window = sw
1057 vbox.pack_start(sw,
True,
True, 4)
1058 self.canvas.set_size_request(600, 450)
1059 self.canvas.
set_bounds(-10000, -10000, 10000, 10000)
1060 self.canvas.scroll_to(0, 0)
1062 self.canvas.get_root_item().add_child(self.links_group, -1)
1063 self.links_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1065 self.canvas.get_root_item().add_child(self.channels_group, -1)
1066 self.channels_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1067 self.channels_group.raise_(self.links_group)
1069 self.canvas.get_root_item().add_child(self.nodes_group, -1)
1070 self.nodes_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1071 self.nodes_group.raise_(self.channels_group)
1077 vbox.pack_start(hbox,
False,
False, 4)
1080 zoom_adj = Gtk.Adjustment(
1084 step_increment=0.02,
1088 self.zoom = zoom_adj
1090 def _zoom_changed(adj):
1091 self.canvas.set_scale(adj.get_value())
1093 zoom_adj.connect(
"value-changed", _zoom_changed)
1094 zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
1097 hbox.pack_start(GObject.new(Gtk.Label, label=
" Zoom:", visible=
True),
False,
False, 4)
1098 hbox.pack_start(zoom,
False,
False, 4)
1099 _zoom_changed(zoom_adj)
1102 speed_adj = Gtk.Adjustment(
1103 value=1.0, lower=0.01, upper=10.0, step_increment=0.02, page_increment=1.0, page_size=0
1106 def _speed_changed(adj):
1107 self.speed = adj.get_value()
1108 self.sample_period = SAMPLE_PERIOD * adj.get_value()
1109 self._start_update_timer()
1111 speed_adj.connect(
"value-changed", _speed_changed)
1112 speed = Gtk.SpinButton.new(speed_adj, 1, 0)
1115 hbox.pack_start(GObject.new(Gtk.Label, label=
" Speed:", visible=
True),
False,
False, 4)
1116 hbox.pack_start(speed,
False,
False, 4)
1117 _speed_changed(speed_adj)
1120 self.time_label = GObject.new(Gtk.Label, label=
" Speed:", visible=
True)
1121 self.time_label.set_width_chars(20)
1122 hbox.pack_start(self.time_label,
False,
False, 4)
1125 screenshot_button = GObject.new(
1128 relief=Gtk.ReliefStyle.NONE,
1129 focus_on_click=
False,
1132 hbox.pack_start(screenshot_button,
False,
False, 4)
1134 def load_button_icon(button, icon_name):
1135 if not Gtk.IconTheme.get_default().has_icon(icon_name):
1136 print(f
"Could not load icon {icon_name}", file=sys.stderr)
1138 image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
1139 button.set_image(image)
1140 button.props.always_show_image =
True
1142 load_button_icon(screenshot_button,
"applets-screenshooter")
1143 screenshot_button.connect(
"clicked", self._take_screenshot)
1146 if ipython_view
is not None:
1147 shell_button = GObject.new(
1150 relief=Gtk.ReliefStyle.NONE,
1151 focus_on_click=
False,
1154 hbox.pack_start(shell_button,
False,
False, 4)
1155 load_button_icon(shell_button,
"gnome-terminal")
1156 shell_button.connect(
"clicked", self._start_shell)
1159 self.play_button = GObject.new(
1161 label=
"Simulate (F3)",
1162 relief=Gtk.ReliefStyle.NONE,
1163 focus_on_click=
False,
1166 load_button_icon(self.play_button,
"media-playback-start")
1167 accel_group = Gtk.AccelGroup()
1168 self.window.add_accel_group(accel_group)
1169 self.play_button.add_accelerator(
1170 "clicked", accel_group, Gdk.KEY_F3, 0, Gtk.AccelFlags.VISIBLE
1172 self.play_button.connect(
"toggled", self._on_play_button_toggled)
1173 hbox.pack_start(self.play_button,
False,
False, 4)
1175 self.canvas.get_root_item().connect(
"button-press-event", self.on_root_button_press_event)
1177 vbox.pack_start(self._create_advanced_controls(),
False,
False, 4)
1179 display = Gdk.Display.get_default()
1181 monitor = display.get_primary_monitor()
1182 geometry = monitor.get_geometry()
1183 scale_factor = monitor.get_scale_factor()
1184 except AttributeError:
1185 screen = display.get_default_screen()
1186 monitor_id = screen.get_primary_monitor()
1187 geometry = screen.get_monitor_geometry(monitor_id)
1188 scale_factor = screen.get_monitor_scale_factor(monitor_id)
1189 width = scale_factor * geometry.width
1190 height = scale_factor * geometry.height
1191 self.window.set_default_size(width * 2 / 3, height * 2 / 3)
1194 def scan_topology(self):
1195 print(
"scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1196 graph = pygraphviz.AGraph()
1198 for nodeI
in range(ns.NodeList.GetNNodes()):
1200 if seen_nodes == 100:
1202 "scan topology... %i nodes visited (%.1f%%)"
1203 % (nodeI, 100 * nodeI / ns.NodeList.GetNNodes())
1206 node = ns.NodeList.GetNode(nodeI)
1207 node_name =
"Node %i" % nodeI
1208 node_view = self.get_node(nodeI)
1210 mobility = node.GetObject[ns.MobilityModel]()
1212 node_view.set_color(
"red")
1213 pos = node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1214 node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1217 graph.add_node(node_name)
1219 for devI
in range(node.GetNDevices()):
1220 device = node.GetDevice(devI)
1221 device_traits = lookup_netdevice_traits(type(device.__deref__()))
1222 if device_traits.is_wireless:
1224 if device_traits.is_virtual:
1226 channel = device.GetChannel()
1227 if channel.GetNDevices() > 2:
1228 if REPRESENT_CHANNELS_AS_NODES:
1230 if mobility
is None:
1231 channel_name =
"Channel %s" % id(channel)
1232 graph.add_edge(node_name, channel_name)
1233 self.get_channel(channel)
1234 self.create_link(self.get_node(nodeI), self.get_channel(channel))
1237 for otherDevI
in range(channel.GetNDevices()):
1238 otherDev = channel.GetDevice(otherDevI)
1239 otherNode = otherDev.GetNode()
1240 otherNodeView = self.get_node(otherNode.GetId())
1241 if otherNode
is not node:
1242 if mobility
is None and not otherNodeView.has_mobility:
1243 other_node_name =
"Node %i" % otherNode.GetId()
1244 graph.add_edge(node_name, other_node_name)
1245 self.create_link(self.get_node(nodeI), otherNodeView)
1247 for otherDevI
in range(channel.GetNDevices()):
1248 otherDev = channel.GetDevice(otherDevI)
1249 otherNode = otherDev.GetNode()
1250 otherNodeView = self.get_node(otherNode.GetId())
1251 if otherNode
is not node:
1252 if mobility
is None and not otherNodeView.has_mobility:
1253 other_node_name =
"Node %i" % otherNode.GetId()
1254 graph.add_edge(node_name, other_node_name)
1255 self.create_link(self.get_node(nodeI), otherNodeView)
1257 print(
"scanning topology: calling graphviz layout")
1258 graph.layout(LAYOUT_ALGORITHM)
1259 for node
in graph.iternodes():
1261 node_type, node_id = node.split(
" ")
1262 pos_x, pos_y = [float(s)
for s
in node.attr[
"pos"].split(
",")]
1263 if node_type ==
"Node":
1264 obj = self.nodes[int(node_id)]
1265 elif node_type ==
"Channel":
1266 obj = self.channels[int(node_id)]
1267 obj.set_position(pos_x, pos_y)
1269 print(
"scanning topology: all done.")
1270 self.emit(
"topology-scanned")
1272 def get_node(self, index):
1274 return self.nodes[index]
1276 node =
Node(self, index)
1277 self.nodes[index] = node
1278 self.nodes_group.add_child(node.canvas_item, -1)
1279 node.canvas_item.connect(
"button-press-event", self.on_node_button_press_event, node)
1280 node.canvas_item.connect(
1281 "button-release-event", self.on_node_button_release_event, node
1285 def get_channel(self, ns3_channel):
1287 return self.channels[id(ns3_channel)]
1289 channel =
Channel(ns3_channel)
1290 self.channels[id(ns3_channel)] = channel
1291 self.channels_group.add_child(channel.canvas_item, -1)
1294 def create_link(self, node, node_or_channel):
1296 self.links_group.add_child(link.canvas_item, -1)
1297 link.canvas_item.lower(
None)
1299 def update_view(self):
1302 self.time_label.set_text(
"Time: %f s" % ns.Simulator.Now().GetSeconds())
1304 self._update_node_positions()
1307 for info_win
in self.information_windows:
1310 self._update_transmissions_view()
1311 self._update_drops_view()
1313 self.emit(
"update-view")
1315 def _update_node_positions(self):
1316 for node
in self.nodes.values():
1317 if node.has_mobility:
1318 ns3_node = ns.NodeList.GetNode(node.node_index)
1319 mobility = ns3_node.GetObject[ns.MobilityModel]()
1321 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1322 x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
1323 node.set_position(x, y)
1324 if node
is self.follow_node:
1325 hadj = self._scrolled_window.get_hadjustment()
1326 vadj = self._scrolled_window.get_vadjustment()
1327 px, py = self.canvas.convert_to_pixels(x, y)
1328 hadj.set_value(px - hadj.get_page_size() / 2)
1329 vadj.set_value(py - vadj.get_page_size() / 2)
1331 def center_on_node(self, node):
1332 if isinstance(node, ns.Node):
1333 node = self.nodes[node.GetId()]
1334 elif isinstance(node, int):
1335 node = self.nodes[node]
1336 elif isinstance(node, Node):
1339 raise TypeError(
"expected int, viz.Node or ns.Node, not %r" % node)
1341 x, y = node.get_position()
1342 hadj = self._scrolled_window.get_hadjustment()
1343 vadj = self._scrolled_window.get_vadjustment()
1344 px, py = self.canvas.convert_to_pixels(x, y)
1345 hadj.set_value(px - hadj.get_page_size() / 2)
1346 vadj.set_value(py - vadj.get_page_size() / 2)
1348 def update_model(self):
1349 self.simulation.lock.acquire()
1351 self.emit(
"simulation-periodic-update")
1353 self.simulation.lock.release()
1355 def do_simulation_periodic_update(self):
1356 smooth_factor = int(self.transmissions_smoothing_adjustment.get_value() * 10)
1358 transmissions = self.simulation.sim_helper.GetTransmissionSamples()
1359 self._last_transmissions.append(transmissions)
1360 while len(self._last_transmissions) > smooth_factor:
1361 self._last_transmissions.pop(0)
1363 drops = self.simulation.sim_helper.GetPacketDropSamples()
1364 self._last_drops.append(drops)
1365 while len(self._last_drops) > smooth_factor:
1366 self._last_drops.pop(0)
1368 def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
1369 hadj = self._scrolled_window.get_hadjustment()
1370 vadj = self._scrolled_window.get_vadjustment()
1371 bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1372 bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(
1373 hadj.get_value() + hadj.get_page_size(), vadj.get_value() + vadj.get_page_size()
1375 ns.PyViz.LineClipping(
1376 bounds_x1, bounds_y1, bounds_x2, bounds_y2, pos1_x, pos1_y, pos2_x, pos2_y
1378 return (pos1_x.value + pos2_x.value) / 2, (pos1_y.value + pos2_y.value) / 2
1380 def _update_transmissions_view(self):
1381 transmissions_average = {}
1382 for transmission_set
in self._last_transmissions:
1383 for transmission
in transmission_set:
1384 key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
1385 rx_bytes, count = transmissions_average.get(key, (0, 0))
1386 rx_bytes += transmission.bytes
1388 transmissions_average[key] = rx_bytes, count
1390 old_arrows = self._transmission_arrows
1391 for arrow, label
in old_arrows:
1392 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1393 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1396 k = self.node_size_adjustment.get_value() / 5
1398 for (transmitter_id, receiver_id), (rx_bytes, rx_count)
in transmissions_average.items():
1399 transmitter = self.get_node(transmitter_id)
1400 receiver = self.get_node(receiver_id)
1402 arrow, label = old_arrows.pop()
1404 arrow = GooCanvas.CanvasPolyline(
1406 stroke_color_rgba=0x00C000C0,
1409 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1411 arrow.set_property(
"parent", self.canvas.get_root_item())
1414 label = GooCanvas.CanvasText(
1415 parent=self.canvas.get_root_item(),
1416 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1420 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1421 line_width = max(0.1, math.log(float(rx_bytes) / rx_count / self.sample_period) * k)
1422 arrow.set_property(
"line-width", line_width)
1424 pos1_x, pos1_y = transmitter.get_position()
1425 pos2_x, pos2_y = receiver.get_position()
1426 points = GooCanvas.CanvasPoints.new(2)
1427 points.set_point(0, pos1_x, pos1_y)
1428 points.set_point(1, pos2_x, pos2_y)
1429 arrow.set_property(
"points", points)
1431 kbps = float(rx_bytes * 8) / 1e3 / rx_count / self.sample_period
1432 label.set_properties(
1433 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1434 visibility_threshold=0.5,
1435 font=(
"Sans Serif %f" % int(1 + BITRATE_FONT_SIZE * k)),
1438 angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1439 if angle > math.pi / 2
or angle < -math.pi / 2:
1445 label.set_properties(
1446 text=(
"← %.2f kbit/s" % (kbps,)),
1447 alignment=Pango.Alignment.CENTER,
1448 anchor=GooCanvas.CanvasAnchorType.S,
1453 label.set_properties(
1454 text=(
"%.2f kbit/s →" % (kbps,)),
1455 alignment=Pango.Alignment.CENTER,
1456 anchor=GooCanvas.CanvasAnchorType.N,
1461 lx, ly = self._get_label_over_line_position(
1462 c_double(pos1_x), c_double(pos1_y), c_double(pos2_x), c_double(pos2_y)
1467 label.set_transform(M)
1471 "PyGobject bug causing label position error; "
1472 "should be fixed in PyGObject >= 3.29.1"
1474 label.set_properties(x=(lx + label.props.x), y=(ly + label.props.y))
1476 new_arrows.append((arrow, label))
1478 self._transmission_arrows = new_arrows + old_arrows
1480 def _update_drops_view(self):
1482 for drop_set
in self._last_drops:
1483 for drop
in drop_set:
1484 key = drop.transmitter.GetId()
1485 drop_bytes, count = drops_average.get(key, (0, 0))
1486 drop_bytes += drop.bytes
1488 drops_average[key] = drop_bytes, count
1490 old_arrows = self._drop_arrows
1491 for arrow, label
in old_arrows:
1492 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1493 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1497 vadjustment = self._scrolled_window.get_vadjustment()
1498 bottom_y = vadjustment.get_value() + vadjustment.get_page_size()
1499 dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1501 k = self.node_size_adjustment.get_value() / 5
1503 for transmitter_id, (drop_bytes, drop_count)
in drops_average.items():
1504 transmitter = self.get_node(transmitter_id)
1506 arrow, label = old_arrows.pop()
1508 arrow = GooCanvas.CanvasPolyline(
1510 stroke_color_rgba=0xC00000C0,
1513 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1515 arrow.set_property(
"parent", self.canvas.get_root_item())
1518 label = GooCanvas.CanvasText(
1519 pointer_events=GooCanvas.CanvasPointerEvents.NONE
1521 label.set_property(
"parent", self.canvas.get_root_item())
1524 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1527 max(0.1, math.log(float(drop_bytes) / drop_count / self.sample_period) * k),
1529 pos1_x, pos1_y = transmitter.get_position()
1530 pos2_x, pos2_y = pos1_x, edge_y
1531 points = GooCanvas.CanvasPoints.new(2)
1532 points.set_point(0, pos1_x, pos1_y)
1533 points.set_point(1, pos2_x, pos2_y)
1534 arrow.set_property(
"points", points)
1536 label.set_properties(
1537 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1538 visibility_threshold=0.5,
1539 font=(
"Sans Serif %i" % int(1 + BITRATE_FONT_SIZE * k)),
1541 "%.2f kbit/s" % (float(drop_bytes * 8) / 1e3 / drop_count / self.sample_period,)
1543 alignment=Pango.Alignment.CENTER,
1544 x=(pos1_x + pos2_x) / 2,
1545 y=(pos1_y + pos2_y) / 2,
1548 new_arrows.append((arrow, label))
1550 self._drop_arrows = new_arrows + old_arrows
1552 def _on_simulation_finished(self):
1553 print(
"Simulation finished.")
1554 if self._update_timeout_id
is not None:
1555 GLib.source_remove(self._update_timeout_id)
1556 self._update_timeout_id =
None
1557 self.play_button.set_active(
False)
1558 self.play_button.set_sensitive(
False)
1560 def update_view_timeout(self):
1564 while not self.simulation.lock.acquire(
False):
1565 while Gtk.events_pending():
1566 Gtk.main_iteration()
1567 pause_messages = self.simulation.pause_messages
1568 self.simulation.pause_messages = []
1571 self.simulation.target_time = ns.Simulator.Now().GetSeconds() + self.sample_period
1574 self.simulation.lock.release()
1578 dialog = Gtk.MessageDialog(
1581 type=Gtk.MessageType.WARNING,
1582 buttons=Gtk.ButtonsType.OK,
1583 message_format=
"\n".join(pause_messages),
1585 dialog.connect(
"response",
lambda d, r: d.destroy())
1587 self.play_button.set_active(
False)
1590 if not self.play_button.get_active():
1591 self._update_timeout_id =
None
1595 self.simulation.go.set()
1599 def _start_update_timer(self):
1600 if self._update_timeout_id
is not None:
1601 GLib.source_remove(self._update_timeout_id)
1603 self._update_timeout_id = GLib.timeout_add(
1604 int(SAMPLE_PERIOD / min(self.speed, 1) * 1e3),
1605 self.update_view_timeout,
1606 priority=PRIORITY_UPDATE_VIEW,
1609 def _on_play_button_toggled(self, button):
1610 if button.get_active():
1611 self._start_update_timer()
1613 if self._update_timeout_id
is not None:
1614 GLib.source_remove(self._update_timeout_id)
1616 def _quit(self, *dummy_args):
1617 if self._update_timeout_id
is not None:
1618 GLib.source_remove(self._update_timeout_id)
1619 self._update_timeout_id =
None
1620 self.simulation.quit =
True
1621 self.simulation.go.set()
1622 self.simulation.join()
1625 def _monkey_patch_ipython(self):
1632 original_runcode = self.ipython.runcode
1634 def runcode(ip, *args):
1636 self.simulation.lock.acquire()
1638 return original_runcode(*args)
1641 self.simulation.lock.release()
1645 self.ipython.runcode = types.MethodType(runcode, self.ipython)
1647 def autoscale_view(self):
1650 self._update_node_positions()
1651 positions = [node.get_position()
for node
in self.nodes.values()]
1652 min_x, min_y = min(x
for (x, y)
in positions), min(y
for (x, y)
in positions)
1653 max_x, max_y = max(x
for (x, y)
in positions), max(y
for (x, y)
in positions)
1654 min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1655 max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1658 dx_px = max_x_px - min_x_px
1659 dy_px = max_y_px - min_y_px
1660 hadj = self._scrolled_window.get_hadjustment()
1661 vadj = self._scrolled_window.get_vadjustment()
1662 new_dx, new_dy = 1.5 * dx_px, 1.5 * dy_px
1664 if new_dx == 0
or new_dy == 0:
1667 self.zoom.set_value(min(hadj.get_page_size() / new_dx, vadj.get_page_size() / new_dy))
1669 x1, y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1670 x2, y2 = self.canvas.convert_from_pixels(
1671 (hadj.get_value() + hadj.get_page_size()), (vadj.get_value() + vadj.get_page_size())
1675 center_x = (min_x + max_x) / 2
1676 center_y = (min_y + max_y) / 2
1678 self.canvas.scroll_to(center_x - width / 2, center_y - height / 2)
1683 self.scan_topology()
1684 self.window.connect(
"delete-event", self._quit)
1686 GLib.timeout_add(200, self.autoscale_view)
1687 self.simulation.
start()
1694 self._monkey_patch_ipython()
1698 def on_root_button_press_event(self, view, target, event):
1699 if event.button == 1:
1700 self.select_node(
None)
1703 def on_node_button_press_event(self, view, target, event, node):
1704 button = event.button
1706 self.select_node(node)
1709 self.popup_node_menu(node, event)
1712 self.begin_node_drag(node, event)
1716 def on_node_button_release_event(self, view, target, event, node):
1717 if event.button == 2:
1718 self.end_node_drag(node)
1722 class NodeDragState(
object):
1723 def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1724 self.canvas_x0 = canvas_x0
1725 self.canvas_y0 = canvas_y0
1726 self.sim_x0 = sim_x0
1727 self.sim_y0 = sim_y0
1728 self.motion_signal =
None
1730 def begin_node_drag(self, node, event):
1731 self.simulation.lock.acquire()
1733 ns3_node = ns.NodeList.GetNode(node.node_index)
1734 mob = ns3_node.GetObject[ns.MobilityModel]()
1737 if self.node_drag_state
is not None:
1739 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1741 self.simulation.lock.release()
1742 devpos = self.canvas.get_window().get_device_position(event.device)
1743 x0, y0 = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1744 self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1745 self.node_drag_state.motion_signal = node.canvas_item.connect(
1746 "motion-notify-event", self.node_drag_motion, node
1749 def node_drag_motion(self, item, targe_item, event, node):
1750 self.simulation.lock.acquire()
1752 ns3_node = ns.NodeList.GetNode(node.node_index)
1753 mob = ns3_node.GetObject[ns.MobilityModel]()
1756 if self.node_drag_state
is None:
1758 devpos = self.canvas.get_window().get_device_position(event.device)
1759 canvas_x, canvas_y = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1760 dx = canvas_x - self.node_drag_state.canvas_x0
1761 dy = canvas_y - self.node_drag_state.canvas_y0
1762 pos = mob.GetPosition()
1763 pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1764 pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1766 mob.SetPosition(pos)
1767 node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1769 self.simulation.lock.release()
1772 def end_node_drag(self, node):
1773 if self.node_drag_state
is None:
1775 node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1776 self.node_drag_state =
None
1778 def popup_node_menu(self, node, event):
1780 self.emit(
"populate-node-menu", node, menu)
1781 menu.popup_at_pointer(event)
1783 def _update_ipython_selected_node(self):
1792 if self.selected_node
is None:
1795 self.simulation.lock.acquire()
1797 ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1799 self.simulation.lock.release()
1800 self.ipython.updateNamespace({
"selected_node": ns3_node})
1802 def select_node(self, node):
1803 if isinstance(node, ns.Node):
1804 node = self.nodes[node.GetId()]
1805 elif isinstance(node, int):
1806 node = self.nodes[node]
1807 elif isinstance(node, Node):
1812 raise TypeError(
"expected None, int, viz.Node or ns.Node, not %r" % node)
1814 if node
is self.selected_node:
1817 if self.selected_node
is not None:
1818 self.selected_node.selected =
False
1819 self.selected_node = node
1820 if self.selected_node
is not None:
1821 self.selected_node.selected =
True
1823 if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1824 if self.selected_node
is None:
1825 self.simulation.set_nodes_of_interest([])
1827 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1829 self._update_ipython_selected_node()
1831 def add_information_window(self, info_win):
1832 self.information_windows.append(info_win)
1833 self.simulation.lock.acquire()
1837 self.simulation.lock.release()
1839 def remove_information_window(self, info_win):
1840 self.information_windows.remove(info_win)
1842 def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1844 hadj = self._scrolled_window.get_hadjustment()
1845 vadj = self._scrolled_window.get_vadjustment()
1846 x, y = self.canvas.convert_from_pixels(hadj.get_value() + x, vadj.get_value() + y)
1847 item = self.canvas.get_item_at(x, y,
True)
1851 while item
is not None:
1852 obj = getattr(item,
"pyviz_object",
None)
1854 obj.tooltip_query(tooltip)
1856 item = item.props.parent
1859 def _get_export_file_name(self):
1860 sel = Gtk.FileChooserNative.new(
1861 "Save...", self.canvas.get_toplevel(), Gtk.FileChooserAction.SAVE,
"_Save",
"_Cancel"
1863 sel.set_local_only(
True)
1864 sel.set_do_overwrite_confirmation(
True)
1865 sel.set_current_name(
"Unnamed.pdf")
1867 filter = Gtk.FileFilter()
1868 filter.set_name(
"Embedded PostScript")
1869 filter.add_mime_type(
"image/x-eps")
1870 sel.add_filter(filter)
1872 filter = Gtk.FileFilter()
1873 filter.set_name(
"Portable Document Graphics")
1874 filter.add_mime_type(
"application/pdf")
1875 sel.add_filter(filter)
1877 filter = Gtk.FileFilter()
1878 filter.set_name(
"Scalable Vector Graphics")
1879 filter.add_mime_type(
"image/svg+xml")
1880 sel.add_filter(filter)
1883 if resp != Gtk.ResponseType.ACCEPT:
1887 file_name = sel.get_filename()
1891 def _take_screenshot(self, dummy_button):
1893 file_name = self._get_export_file_name()
1894 if file_name
is None:
1898 x1 = self._scrolled_window.get_hadjustment().get_value()
1899 y1 = self._scrolled_window.get_vadjustment().get_value()
1900 x2 = x1 + self._scrolled_window.get_hadjustment().get_page_size()
1901 y2 = y1 + self._scrolled_window.get_vadjustment().get_page_size()
1902 bounds = GooCanvas.CanvasBounds()
1903 bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1904 bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1905 dest_width = bounds.x2 - bounds.x1
1906 dest_height = bounds.y2 - bounds.y1
1909 dummy, extension = os.path.splitext(file_name)
1910 extension = extension.lower()
1911 if extension ==
".eps":
1912 surface = cairo.PSSurface(file_name, dest_width, dest_height)
1913 elif extension ==
".pdf":
1914 surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1915 elif extension ==
".svg":
1916 surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1918 dialog = Gtk.MessageDialog(
1919 parent=self.canvas.get_toplevel(),
1920 flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
1921 type=Gtk.MessageType.ERROR,
1922 buttons=Gtk.ButtonsType.OK,
1923 message_format=
"Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1931 cr = cairo.Context(surface)
1932 cr.translate(-bounds.x1, -bounds.y1)
1933 self.canvas.render(cr, bounds, self.zoom.get_value())
1937 def set_follow_node(self, node):
1938 if isinstance(node, ns.Node):
1939 node = self.nodes[node.GetId()]
1940 self.follow_node = node
1942 def _start_shell(self, dummy_button):
1943 if self.shell_window
is not None:
1944 self.shell_window.present()
1947 self.shell_window = Gtk.Window()
1948 self.shell_window.set_size_request(750, 550)
1949 self.shell_window.set_resizable(
True)
1950 scrolled_window = Gtk.ScrolledWindow()
1951 scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
1953 self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
1954 self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
1956 scrolled_window.add(self.ipython)
1957 scrolled_window.show()
1958 self.shell_window.add(scrolled_window)
1959 self.shell_window.show()
1960 self.shell_window.connect(
"destroy", self._on_shell_window_destroy)
1962 self._update_ipython_selected_node()
1963 self.ipython.updateNamespace({
"viz": self})
1965 def _on_shell_window_destroy(self, window):
1966 self.shell_window =
None