756 if _import_error
is None:
759 "populate-node-menu": (
760 GObject.SignalFlags.RUN_LAST,
769 "simulation-periodic-update": (GObject.SignalFlags.RUN_LAST,
None, ()),
771 "topology-scanned": (GObject.SignalFlags.RUN_LAST,
None, ()),
773 "update-view": (GObject.SignalFlags.RUN_LAST,
None, ()),
778 Initializer function.
780 @param self: class object.
783 assert Visualizer.INSTANCE
is None
784 Visualizer.INSTANCE = self
785 super(Visualizer, self).__init__()
790 self.time_label =
None
791 self.play_button =
None
793 self._scrolled_window =
None
795 self.links_group = GooCanvas.CanvasGroup()
796 self.channels_group = GooCanvas.CanvasGroup()
797 self.nodes_group = GooCanvas.CanvasGroup()
799 self._update_timeout_id =
None
801 self.selected_node =
None
803 self.information_windows = []
804 self._transmission_arrows = []
805 self._last_transmissions = []
806 self._drop_arrows = []
807 self._last_drops = []
808 self._show_transmissions_mode =
None
809 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
810 self._panning_state =
None
811 self.node_size_adjustment =
None
812 self.transmissions_smoothing_adjustment =
None
813 self.sample_period = SAMPLE_PERIOD
814 self.node_drag_state =
None
815 self.follow_node =
None
816 self.shell_window =
None
820 for plugin
in plugins:
823 def set_show_transmissions_mode(self, mode):
825 Set show transmission mode.
827 @param self: class object.
828 @param mode: mode to set.
831 assert isinstance(mode, ShowTransmissionsMode)
832 self._show_transmissions_mode = mode
833 if self._show_transmissions_mode == ShowTransmissionsMode.ALL:
834 self.simulation.set_nodes_of_interest(list(range(ns.NodeList.GetNNodes())))
835 elif self._show_transmissions_mode == ShowTransmissionsMode.NONE:
836 self.simulation.set_nodes_of_interest([])
837 elif self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
838 if self.selected_node
is None:
839 self.simulation.set_nodes_of_interest([])
841 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
843 def _create_advanced_controls(self):
845 Create advanced controls.
847 @param self: class object.
850 expander = Gtk.Expander.new(
"Advanced")
853 main_vbox = GObject.new(Gtk.VBox, border_width=8, visible=
True)
854 expander.add(main_vbox)
856 main_hbox1 = GObject.new(Gtk.HBox, border_width=8, visible=
True)
857 main_vbox.pack_start(main_hbox1,
True,
True, 0)
859 show_transmissions_group = GObject.new(
860 Gtk.HeaderBar, title=
"Show transmissions", visible=
True
862 main_hbox1.pack_start(show_transmissions_group,
False,
False, 8)
864 vbox = Gtk.VBox(homogeneous=
True, spacing=4)
866 show_transmissions_group.add(vbox)
868 all_nodes = Gtk.RadioButton.new(
None)
869 all_nodes.set_label(
"All nodes")
870 all_nodes.set_active(
True)
874 selected_node = Gtk.RadioButton.new_from_widget(all_nodes)
876 selected_node.set_label(
"Selected node")
877 selected_node.set_active(
False)
878 vbox.add(selected_node)
880 no_node = Gtk.RadioButton.new_from_widget(all_nodes)
882 no_node.set_label(
"Disabled")
883 no_node.set_active(
False)
887 if radio.get_active():
888 self.set_show_transmissions_mode(ShowTransmissionsMode.ALL)
890 all_nodes.connect(
"toggled", toggled)
893 if radio.get_active():
894 self.set_show_transmissions_mode(ShowTransmissionsMode.NONE)
896 no_node.connect(
"toggled", toggled)
899 if radio.get_active():
900 self.set_show_transmissions_mode(ShowTransmissionsMode.SELECTED)
902 selected_node.connect(
"toggled", toggled)
905 misc_settings_group = GObject.new(Gtk.HeaderBar, title=
"Misc Settings", visible=
True)
906 main_hbox1.pack_start(misc_settings_group,
False,
False, 8)
907 settings_hbox = GObject.new(Gtk.HBox, border_width=8, visible=
True)
908 misc_settings_group.add(settings_hbox)
911 vbox = GObject.new(Gtk.VBox, border_width=0, visible=
True)
912 scale = GObject.new(Gtk.HScale, visible=
True, digits=2)
913 vbox.pack_start(scale,
True,
True, 0)
914 vbox.pack_start(GObject.new(Gtk.Label, label=
"Node Size", visible=
True),
True,
True, 0)
915 settings_hbox.pack_start(vbox,
False,
False, 6)
916 self.node_size_adjustment = scale.get_adjustment()
918 def node_size_changed(adj):
919 for node
in self.nodes.values():
920 node.set_size(adj.get_value())
922 self.node_size_adjustment.connect(
"value-changed", node_size_changed)
923 self.node_size_adjustment.set_lower(0.01)
924 self.node_size_adjustment.set_upper(20)
925 self.node_size_adjustment.set_step_increment(0.1)
926 self.node_size_adjustment.set_value(DEFAULT_NODE_SIZE)
929 vbox = GObject.new(Gtk.VBox, border_width=0, visible=
True)
930 scale = GObject.new(Gtk.HScale, visible=
True, digits=1)
931 vbox.pack_start(scale,
True,
True, 0)
933 GObject.new(Gtk.Label, label=
"Tx. Smooth Factor (s)", visible=
True),
True,
True, 0
935 settings_hbox.pack_start(vbox,
False,
False, 6)
936 self.transmissions_smoothing_adjustment = scale.get_adjustment()
937 adj = self.transmissions_smoothing_adjustment
940 adj.set_step_increment(0.1)
941 adj.set_value(DEFAULT_TRANSMISSIONS_MEMORY * 0.1)
946 class _PanningState(
object):
949 __slots__ = [
"initial_mouse_pos",
"initial_canvas_pos",
"motion_signal"]
951 def _begin_panning(self, widget, event):
953 Set show trnamission mode.
955 @param self: class object.
956 @param mode: mode to set.
959 display = self.canvas.get_window().get_display()
960 cursor = Gdk.Cursor.new_for_display(display, Gdk.CursorType.FLEUR)
961 self.canvas.get_window().set_cursor(cursor)
962 self._panning_state = self._PanningState()
963 pos = widget.get_window().get_device_position(event.device)
964 self._panning_state.initial_mouse_pos = (pos.x, pos.y)
965 x = self._scrolled_window.get_hadjustment().get_value()
966 y = self._scrolled_window.get_vadjustment().get_value()
967 self._panning_state.initial_canvas_pos = (x, y)
968 self._panning_state.motion_signal = self.canvas.connect(
969 "motion-notify-event", self._panning_motion
972 def _end_panning(self, event):
974 End panning function.
976 @param self: class object.
977 @param event: active event.
980 if self._panning_state
is None:
982 self.canvas.get_window().set_cursor(
None)
983 self.canvas.disconnect(self._panning_state.motion_signal)
984 self._panning_state =
None
986 def _panning_motion(self, widget, event):
988 Panning motion function.
990 @param self: class object.
991 @param widget: widget.
993 @return true if successful
995 assert self._panning_state
is not None
997 pos = widget.get_window().get_device_position(event.device)
1000 x, y = event.x, event.y
1002 hadj = self._scrolled_window.get_hadjustment()
1003 vadj = self._scrolled_window.get_vadjustment()
1004 mx0, my0 = self._panning_state.initial_mouse_pos
1005 cx0, cy0 = self._panning_state.initial_canvas_pos
1009 hadj.set_value(cx0 - dx)
1010 vadj.set_value(cy0 - dy)
1013 def _canvas_button_press(self, widget, event):
1014 if event.button == 2:
1015 self._begin_panning(widget, event)
1019 def _canvas_button_release(self, dummy_widget, event):
1020 if event.button == 2:
1021 self._end_panning(event)
1025 def _canvas_scroll_event(self, dummy_widget, event):
1026 if event.direction == Gdk.ScrollDirection.UP:
1027 self.zoom.set_value(self.zoom.get_value() * 1.25)
1029 elif event.direction == Gdk.ScrollDirection.DOWN:
1030 self.zoom.set_value(self.zoom.get_value() / 1.25)
1034 def get_hadjustment(self):
1035 return self._scrolled_window.get_hadjustment()
1037 def get_vadjustment(self):
1038 return self._scrolled_window.get_vadjustment()
1040 def create_gui(self):
1041 self.window = Gtk.Window()
1044 self.window.add(vbox)
1047 self.canvas = GooCanvas.Canvas()
1048 self.canvas.connect_after(
"button-press-event", self._canvas_button_press)
1049 self.canvas.connect_after(
"button-release-event", self._canvas_button_release)
1050 self.canvas.connect(
"scroll-event", self._canvas_scroll_event)
1051 self.canvas.props.has_tooltip =
True
1052 self.canvas.connect(
"query-tooltip", self._canvas_tooltip_cb)
1054 sw = Gtk.ScrolledWindow()
1056 self._scrolled_window = sw
1058 vbox.pack_start(sw,
True,
True, 4)
1059 self.canvas.set_size_request(600, 450)
1060 self.canvas.set_bounds(-10000, -10000, 10000, 10000)
1061 self.canvas.scroll_to(0, 0)
1063 self.canvas.get_root_item().add_child(self.links_group, -1)
1064 self.links_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1066 self.canvas.get_root_item().add_child(self.channels_group, -1)
1067 self.channels_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1068 self.channels_group.raise_(self.links_group)
1070 self.canvas.get_root_item().add_child(self.nodes_group, -1)
1071 self.nodes_group.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1072 self.nodes_group.raise_(self.channels_group)
1078 vbox.pack_start(hbox,
False,
False, 4)
1081 zoom_adj = Gtk.Adjustment(
1085 step_increment=0.02,
1089 self.zoom = zoom_adj
1091 def _zoom_changed(adj):
1092 self.canvas.set_scale(adj.get_value())
1094 zoom_adj.connect(
"value-changed", _zoom_changed)
1095 zoom = Gtk.SpinButton.new(zoom_adj, 0.1, 1)
1098 hbox.pack_start(GObject.new(Gtk.Label, label=
" Zoom:", visible=
True),
False,
False, 4)
1099 hbox.pack_start(zoom,
False,
False, 4)
1100 _zoom_changed(zoom_adj)
1103 speed_adj = Gtk.Adjustment(
1104 value=1.0, lower=0.01, upper=10.0, step_increment=0.02, page_increment=1.0, page_size=0
1107 def _speed_changed(adj):
1108 self.speed = adj.get_value()
1109 self.sample_period = SAMPLE_PERIOD * adj.get_value()
1110 self._start_update_timer()
1112 speed_adj.connect(
"value-changed", _speed_changed)
1113 speed = Gtk.SpinButton.new(speed_adj, 1, 0)
1116 hbox.pack_start(GObject.new(Gtk.Label, label=
" Speed:", visible=
True),
False,
False, 4)
1117 hbox.pack_start(speed,
False,
False, 4)
1118 _speed_changed(speed_adj)
1121 self.time_label = GObject.new(Gtk.Label, label=
" Speed:", visible=
True)
1122 self.time_label.set_width_chars(20)
1123 hbox.pack_start(self.time_label,
False,
False, 4)
1126 screenshot_button = GObject.new(
1129 relief=Gtk.ReliefStyle.NONE,
1130 focus_on_click=
False,
1133 hbox.pack_start(screenshot_button,
False,
False, 4)
1135 def load_button_icon(button, icon_name):
1136 if not Gtk.IconTheme.get_default().has_icon(icon_name):
1137 print(f
"Could not load icon {icon_name}", file=sys.stderr)
1139 image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.BUTTON)
1140 button.set_image(image)
1141 button.props.always_show_image =
True
1143 load_button_icon(screenshot_button,
"applets-screenshooter")
1144 screenshot_button.connect(
"clicked", self._take_screenshot)
1147 if ipython_view
is not None:
1148 shell_button = GObject.new(
1151 relief=Gtk.ReliefStyle.NONE,
1152 focus_on_click=
False,
1155 hbox.pack_start(shell_button,
False,
False, 4)
1156 load_button_icon(shell_button,
"gnome-terminal")
1157 shell_button.connect(
"clicked", self._start_shell)
1160 self.play_button = GObject.new(
1162 label=
"Simulate (F3)",
1163 relief=Gtk.ReliefStyle.NONE,
1164 focus_on_click=
False,
1167 load_button_icon(self.play_button,
"media-playback-start")
1168 accel_group = Gtk.AccelGroup()
1169 self.window.add_accel_group(accel_group)
1170 self.play_button.add_accelerator(
1171 "clicked", accel_group, Gdk.KEY_F3, 0, Gtk.AccelFlags.VISIBLE
1173 self.play_button.connect(
"toggled", self._on_play_button_toggled)
1174 hbox.pack_start(self.play_button,
False,
False, 4)
1176 self.canvas.get_root_item().connect(
"button-press-event", self.on_root_button_press_event)
1178 vbox.pack_start(self._create_advanced_controls(),
False,
False, 4)
1180 display = Gdk.Display.get_default()
1182 monitor = display.get_primary_monitor()
1183 geometry = monitor.get_geometry()
1184 scale_factor = monitor.get_scale_factor()
1185 except AttributeError:
1186 screen = display.get_default_screen()
1187 monitor_id = screen.get_primary_monitor()
1188 geometry = screen.get_monitor_geometry(monitor_id)
1189 scale_factor = screen.get_monitor_scale_factor(monitor_id)
1190 width = scale_factor * geometry.width
1191 height = scale_factor * geometry.height
1192 self.window.set_default_size(width * 2 / 3, height * 2 / 3)
1195 def scan_topology(self):
1196 print(
"scanning topology: %i nodes..." % (ns.NodeList.GetNNodes(),))
1197 graph = pygraphviz.AGraph()
1199 for nodeI
in range(ns.NodeList.GetNNodes()):
1201 if seen_nodes == 100:
1203 "scan topology... %i nodes visited (%.1f%%)"
1204 % (nodeI, 100 * nodeI / ns.NodeList.GetNNodes())
1207 node = ns.NodeList.GetNode(nodeI)
1208 node_name =
"Node %i" % nodeI
1209 node_view = self.get_node(nodeI)
1211 mobility = node.GetObject[ns.MobilityModel]()
1213 node_view.set_color(
"red")
1214 pos = node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1215 node_view.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1218 graph.add_node(node_name)
1220 for devI
in range(node.GetNDevices()):
1221 device = node.GetDevice(devI)
1222 device_traits = lookup_netdevice_traits(type(device.__deref__()))
1223 if device_traits.is_wireless:
1225 if device_traits.is_virtual:
1227 channel = device.GetChannel()
1228 if channel.GetNDevices() > 2:
1229 if REPRESENT_CHANNELS_AS_NODES:
1231 if mobility
is None:
1232 channel_name =
"Channel %s" % id(channel)
1233 graph.add_edge(node_name, channel_name)
1234 self.get_channel(channel)
1235 self.create_link(self.get_node(nodeI), self.get_channel(channel))
1238 for otherDevI
in range(channel.GetNDevices()):
1239 otherDev = channel.GetDevice(otherDevI)
1240 otherNode = otherDev.GetNode()
1241 otherNodeView = self.get_node(otherNode.GetId())
1242 if otherNode
is not node:
1243 if mobility
is None and not otherNodeView.has_mobility:
1244 other_node_name =
"Node %i" % otherNode.GetId()
1245 graph.add_edge(node_name, other_node_name)
1246 self.create_link(self.get_node(nodeI), otherNodeView)
1248 for otherDevI
in range(channel.GetNDevices()):
1249 otherDev = channel.GetDevice(otherDevI)
1250 otherNode = otherDev.GetNode()
1251 otherNodeView = self.get_node(otherNode.GetId())
1252 if otherNode
is not node:
1253 if mobility
is None and not otherNodeView.has_mobility:
1254 other_node_name =
"Node %i" % otherNode.GetId()
1255 graph.add_edge(node_name, other_node_name)
1256 self.create_link(self.get_node(nodeI), otherNodeView)
1258 print(
"scanning topology: calling graphviz layout")
1259 graph.layout(LAYOUT_ALGORITHM)
1260 for node
in graph.iternodes():
1262 node_type, node_id = node.split(
" ")
1263 pos_x, pos_y = [float(s)
for s
in node.attr[
"pos"].split(
",")]
1264 if node_type ==
"Node":
1265 obj = self.nodes[int(node_id)]
1266 elif node_type ==
"Channel":
1267 obj = self.channels[int(node_id)]
1268 obj.set_position(pos_x, pos_y)
1270 print(
"scanning topology: all done.")
1271 self.emit(
"topology-scanned")
1273 def get_node(self, index):
1275 return self.nodes[index]
1277 node =
Node(self, index)
1278 self.nodes[index] = node
1279 self.nodes_group.add_child(node.canvas_item, -1)
1280 node.canvas_item.connect(
"button-press-event", self.on_node_button_press_event, node)
1281 node.canvas_item.connect(
1282 "button-release-event", self.on_node_button_release_event, node
1286 def get_channel(self, ns3_channel):
1288 return self.channels[id(ns3_channel)]
1290 channel =
Channel(ns3_channel)
1291 self.channels[id(ns3_channel)] = channel
1292 self.channels_group.add_child(channel.canvas_item, -1)
1295 def create_link(self, node, node_or_channel):
1297 self.links_group.add_child(link.canvas_item, -1)
1298 link.canvas_item.lower(
None)
1300 def update_view(self):
1303 self.time_label.set_text(
"Time: %f s" % ns.Simulator.Now().GetSeconds())
1305 self._update_node_positions()
1308 for info_win
in self.information_windows:
1311 self._update_transmissions_view()
1312 self._update_drops_view()
1314 self.emit(
"update-view")
1316 def _update_node_positions(self):
1317 for node
in self.nodes.values():
1318 if node.has_mobility:
1319 ns3_node = ns.NodeList.GetNode(node.node_index)
1320 mobility = ns3_node.GetObject[ns.MobilityModel]()
1322 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1323 x, y = transform_point_simulation_to_canvas(pos.x, pos.y)
1324 node.set_position(x, y)
1325 if node
is self.follow_node:
1326 hadj = self._scrolled_window.get_hadjustment()
1327 vadj = self._scrolled_window.get_vadjustment()
1328 px, py = self.canvas.convert_to_pixels(x, y)
1329 hadj.set_value(px - hadj.get_page_size() / 2)
1330 vadj.set_value(py - vadj.get_page_size() / 2)
1332 def center_on_node(self, node):
1333 if isinstance(node, ns.Node):
1334 node = self.nodes[node.GetId()]
1335 elif isinstance(node, int):
1336 node = self.nodes[node]
1337 elif isinstance(node, Node):
1340 raise TypeError(
"expected int, viz.Node or ns.Node, not %r" % node)
1342 x, y = node.get_position()
1343 hadj = self._scrolled_window.get_hadjustment()
1344 vadj = self._scrolled_window.get_vadjustment()
1345 px, py = self.canvas.convert_to_pixels(x, y)
1346 hadj.set_value(px - hadj.get_page_size() / 2)
1347 vadj.set_value(py - vadj.get_page_size() / 2)
1349 def update_model(self):
1350 self.simulation.lock.acquire()
1352 self.emit(
"simulation-periodic-update")
1354 self.simulation.lock.release()
1356 def do_simulation_periodic_update(self):
1357 smooth_factor = int(self.transmissions_smoothing_adjustment.get_value() * 10)
1359 transmissions = self.simulation.sim_helper.GetTransmissionSamples()
1360 self._last_transmissions.append(transmissions)
1361 while len(self._last_transmissions) > smooth_factor:
1362 self._last_transmissions.pop(0)
1364 drops = self.simulation.sim_helper.GetPacketDropSamples()
1365 self._last_drops.append(drops)
1366 while len(self._last_drops) > smooth_factor:
1367 self._last_drops.pop(0)
1369 def _get_label_over_line_position(self, pos1_x, pos1_y, pos2_x, pos2_y):
1370 hadj = self._scrolled_window.get_hadjustment()
1371 vadj = self._scrolled_window.get_vadjustment()
1372 bounds_x1, bounds_y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1373 bounds_x2, bounds_y2 = self.canvas.convert_from_pixels(
1374 hadj.get_value() + hadj.get_page_size(), vadj.get_value() + vadj.get_page_size()
1376 ns.PyViz.LineClipping(
1377 bounds_x1, bounds_y1, bounds_x2, bounds_y2, pos1_x, pos1_y, pos2_x, pos2_y
1379 return (pos1_x.value + pos2_x.value) / 2, (pos1_y.value + pos2_y.value) / 2
1381 def _update_transmissions_view(self):
1382 transmissions_average = {}
1383 for transmission_set
in self._last_transmissions:
1384 for transmission
in transmission_set:
1385 key = (transmission.transmitter.GetId(), transmission.receiver.GetId())
1386 rx_bytes, count = transmissions_average.get(key, (0, 0))
1387 rx_bytes += transmission.bytes
1389 transmissions_average[key] = rx_bytes, count
1391 old_arrows = self._transmission_arrows
1392 for arrow, label
in old_arrows:
1393 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1394 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1397 k = self.node_size_adjustment.get_value() / 5
1399 for (transmitter_id, receiver_id), (rx_bytes, rx_count)
in transmissions_average.items():
1400 transmitter = self.get_node(transmitter_id)
1401 receiver = self.get_node(receiver_id)
1403 arrow, label = old_arrows.pop()
1405 arrow = GooCanvas.CanvasPolyline(
1407 stroke_color_rgba=0x00C000C0,
1410 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1412 arrow.set_property(
"parent", self.canvas.get_root_item())
1415 label = GooCanvas.CanvasText(
1416 parent=self.canvas.get_root_item(),
1417 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1421 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1422 line_width = max(0.1, math.log(float(rx_bytes) / rx_count / self.sample_period) * k)
1423 arrow.set_property(
"line-width", line_width)
1425 pos1_x, pos1_y = transmitter.get_position()
1426 pos2_x, pos2_y = receiver.get_position()
1427 points = GooCanvas.CanvasPoints.new(2)
1428 points.set_point(0, pos1_x, pos1_y)
1429 points.set_point(1, pos2_x, pos2_y)
1430 arrow.set_property(
"points", points)
1432 kbps = float(rx_bytes * 8) / 1e3 / rx_count / self.sample_period
1433 label.set_properties(
1434 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1435 visibility_threshold=0.5,
1436 font=(
"Sans Serif %f" % int(1 + BITRATE_FONT_SIZE * k)),
1438 angle = math.atan2((pos2_y - pos1_y), (pos2_x - pos1_x))
1439 if -PI_OVER_2 <= angle <= PI_OVER_2:
1440 label.set_properties(
1441 text=(
"%.2f kbit/s →" % (kbps,)),
1442 alignment=Pango.Alignment.CENTER,
1443 anchor=GooCanvas.CanvasAnchorType.S,
1448 label.set_properties(
1449 text=(
"← %.2f kbit/s" % (kbps,)),
1450 alignment=Pango.Alignment.CENTER,
1451 anchor=GooCanvas.CanvasAnchorType.N,
1456 lx, ly = self._get_label_over_line_position(
1457 c_double(pos1_x), c_double(pos1_y), c_double(pos2_x), c_double(pos2_y)
1462 label.set_transform(M)
1466 "PyGobject bug causing label position error; "
1467 "should be fixed in PyGObject >= 3.29.1"
1469 label.set_properties(x=(lx + label.props.x), y=(ly + label.props.y))
1471 new_arrows.append((arrow, label))
1473 self._transmission_arrows = new_arrows + old_arrows
1475 def _update_drops_view(self):
1477 for drop_set
in self._last_drops:
1478 for drop
in drop_set:
1479 key = drop.transmitter.GetId()
1480 drop_bytes, count = drops_average.get(key, (0, 0))
1481 drop_bytes += drop.bytes
1483 drops_average[key] = drop_bytes, count
1485 old_arrows = self._drop_arrows
1486 for arrow, label
in old_arrows:
1487 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1488 label.set_property(
"visibility", GooCanvas.CanvasItemVisibility.HIDDEN)
1492 vadjustment = self._scrolled_window.get_vadjustment()
1493 bottom_y = vadjustment.get_value() + vadjustment.get_page_size()
1494 dummy, edge_y = self.canvas.convert_from_pixels(0, bottom_y)
1496 k = self.node_size_adjustment.get_value() / 5
1498 for transmitter_id, (drop_bytes, drop_count)
in drops_average.items():
1499 transmitter = self.get_node(transmitter_id)
1501 arrow, label = old_arrows.pop()
1503 arrow = GooCanvas.CanvasPolyline(
1505 stroke_color_rgba=0xC00000C0,
1508 pointer_events=GooCanvas.CanvasPointerEvents.NONE,
1510 arrow.set_property(
"parent", self.canvas.get_root_item())
1513 label = GooCanvas.CanvasText(
1514 pointer_events=GooCanvas.CanvasPointerEvents.NONE
1516 label.set_property(
"parent", self.canvas.get_root_item())
1519 arrow.set_property(
"visibility", GooCanvas.CanvasItemVisibility.VISIBLE)
1522 max(0.1, math.log(float(drop_bytes) / drop_count / self.sample_period) * k),
1524 pos1_x, pos1_y = transmitter.get_position()
1525 pos2_x, pos2_y = pos1_x, edge_y
1526 points = GooCanvas.CanvasPoints.new(2)
1527 points.set_point(0, pos1_x, pos1_y)
1528 points.set_point(1, pos2_x, pos2_y)
1529 arrow.set_property(
"points", points)
1531 label.set_properties(
1532 visibility=GooCanvas.CanvasItemVisibility.VISIBLE_ABOVE_THRESHOLD,
1533 visibility_threshold=0.5,
1534 font=(
"Sans Serif %i" % int(1 + BITRATE_FONT_SIZE * k)),
1536 "%.2f kbit/s" % (float(drop_bytes * 8) / 1e3 / drop_count / self.sample_period,)
1538 alignment=Pango.Alignment.CENTER,
1539 x=(pos1_x + pos2_x) / 2,
1540 y=(pos1_y + pos2_y) / 2,
1543 new_arrows.append((arrow, label))
1545 self._drop_arrows = new_arrows + old_arrows
1547 def update_view_timeout(self):
1551 while not self.simulation.lock.acquire(
False):
1552 while Gtk.events_pending():
1553 Gtk.main_iteration()
1554 pause_messages = self.simulation.pause_messages
1555 self.simulation.pause_messages = []
1558 self.simulation.target_time = ns.Simulator.Now().GetSeconds() + self.sample_period
1561 self.simulation.lock.release()
1565 dialog = Gtk.MessageDialog(
1568 type=Gtk.MessageType.WARNING,
1569 buttons=Gtk.ButtonsType.OK,
1570 message_format=
"\n".join(pause_messages),
1572 dialog.connect(
"response",
lambda d, r: d.destroy())
1574 self.play_button.set_active(
False)
1577 if not self.play_button.get_active():
1578 self._update_timeout_id =
None
1582 self.simulation.go.set()
1586 def _start_update_timer(self):
1587 if self._update_timeout_id
is not None:
1588 GLib.source_remove(self._update_timeout_id)
1590 self._update_timeout_id = GLib.timeout_add(
1591 int(SAMPLE_PERIOD / min(self.speed, 1) * 1e3),
1592 self.update_view_timeout,
1593 priority=PRIORITY_UPDATE_VIEW,
1596 def _on_play_button_toggled(self, button):
1597 if button.get_active():
1598 self._start_update_timer()
1600 if self._update_timeout_id
is not None:
1601 GLib.source_remove(self._update_timeout_id)
1603 def _quit(self, *dummy_args):
1604 if self._update_timeout_id
is not None:
1605 GLib.source_remove(self._update_timeout_id)
1606 self._update_timeout_id =
None
1607 self.simulation.quit =
True
1608 self.simulation.go.set()
1609 self.simulation.join()
1612 def _monkey_patch_ipython(self):
1619 original_runcode = self.ipython.runcode
1621 def runcode(ip, *args):
1623 self.simulation.lock.acquire()
1625 return original_runcode(*args)
1628 self.simulation.lock.release()
1632 self.ipython.runcode = types.MethodType(runcode, self.ipython)
1634 def autoscale_view(self):
1637 self._update_node_positions()
1638 positions = [node.get_position()
for node
in self.nodes.values()]
1639 min_x, min_y = min(x
for (x, y)
in positions), min(y
for (x, y)
in positions)
1640 max_x, max_y = max(x
for (x, y)
in positions), max(y
for (x, y)
in positions)
1641 min_x_px, min_y_px = self.canvas.convert_to_pixels(min_x, min_y)
1642 max_x_px, max_y_px = self.canvas.convert_to_pixels(max_x, max_y)
1645 dx_px = max_x_px - min_x_px
1646 dy_px = max_y_px - min_y_px
1647 hadj = self._scrolled_window.get_hadjustment()
1648 vadj = self._scrolled_window.get_vadjustment()
1649 new_dx, new_dy = 1.5 * dx_px, 1.5 * dy_px
1651 if new_dx == 0
or new_dy == 0:
1654 self.zoom.set_value(min(hadj.get_page_size() / new_dx, vadj.get_page_size() / new_dy))
1656 x1, y1 = self.canvas.convert_from_pixels(hadj.get_value(), vadj.get_value())
1657 x2, y2 = self.canvas.convert_from_pixels(
1658 (hadj.get_value() + hadj.get_page_size()), (vadj.get_value() + vadj.get_page_size())
1662 center_x = (min_x + max_x) / 2
1663 center_y = (min_y + max_y) / 2
1665 self.canvas.scroll_to(center_x - width / 2, center_y - height / 2)
1670 self.scan_topology()
1671 self.window.connect(
"delete-event", self._quit)
1673 GLib.timeout_add(200, self.autoscale_view)
1674 self.simulation.start()
1681 self._monkey_patch_ipython()
1685 def on_root_button_press_event(self, view, target, event):
1686 if event.button == 1:
1687 self.select_node(
None)
1690 def on_node_button_press_event(self, view, target, event, node):
1691 button = event.button
1693 self.select_node(node)
1696 self.popup_node_menu(node, event)
1699 self.begin_node_drag(node, event)
1703 def on_node_button_release_event(self, view, target, event, node):
1704 if event.button == 2:
1705 self.end_node_drag(node)
1709 class NodeDragState(
object):
1710 def __init__(self, canvas_x0, canvas_y0, sim_x0, sim_y0):
1711 self.canvas_x0 = canvas_x0
1712 self.canvas_y0 = canvas_y0
1713 self.sim_x0 = sim_x0
1714 self.sim_y0 = sim_y0
1715 self.motion_signal =
None
1717 def begin_node_drag(self, node, event):
1718 self.simulation.lock.acquire()
1720 ns3_node = ns.NodeList.GetNode(node.node_index)
1721 mob = ns3_node.GetObject[ns.MobilityModel]()
1724 if self.node_drag_state
is not None:
1726 pos = ns3_node.GetObject[ns.MobilityModel]().__deref__().GetPosition()
1728 self.simulation.lock.release()
1729 devpos = self.canvas.get_window().get_device_position(event.device)
1730 x0, y0 = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1731 self.node_drag_state = self.NodeDragState(x0, y0, pos.x, pos.y)
1732 self.node_drag_state.motion_signal = node.canvas_item.connect(
1733 "motion-notify-event", self.node_drag_motion, node
1736 def node_drag_motion(self, item, targe_item, event, node):
1737 self.simulation.lock.acquire()
1739 ns3_node = ns.NodeList.GetNode(node.node_index)
1740 mob = ns3_node.GetObject[ns.MobilityModel]()
1743 if self.node_drag_state
is None:
1745 devpos = self.canvas.get_window().get_device_position(event.device)
1746 canvas_x, canvas_y = self.canvas.convert_from_pixels(devpos.x, devpos.y)
1747 dx = canvas_x - self.node_drag_state.canvas_x0
1748 dy = canvas_y - self.node_drag_state.canvas_y0
1749 pos = mob.GetPosition()
1750 pos.x = self.node_drag_state.sim_x0 + transform_distance_canvas_to_simulation(dx)
1751 pos.y = self.node_drag_state.sim_y0 + transform_distance_canvas_to_simulation(dy)
1753 mob.SetPosition(pos)
1754 node.set_position(*transform_point_simulation_to_canvas(pos.x, pos.y))
1756 self.simulation.lock.release()
1759 def end_node_drag(self, node):
1760 if self.node_drag_state
is None:
1762 node.canvas_item.disconnect(self.node_drag_state.motion_signal)
1763 self.node_drag_state =
None
1765 def popup_node_menu(self, node, event):
1767 self.emit(
"populate-node-menu", node, menu)
1768 menu.popup_at_pointer(event)
1770 def _update_ipython_selected_node(self):
1779 if self.selected_node
is None:
1782 self.simulation.lock.acquire()
1784 ns3_node = ns.NodeList.GetNode(self.selected_node.node_index)
1786 self.simulation.lock.release()
1787 self.ipython.updateNamespace({
"selected_node": ns3_node})
1789 def select_node(self, node):
1790 if isinstance(node, ns.Node):
1791 node = self.nodes[node.GetId()]
1792 elif isinstance(node, int):
1793 node = self.nodes[node]
1794 elif isinstance(node, Node):
1799 raise TypeError(
"expected None, int, viz.Node or ns.Node, not %r" % node)
1801 if node
is self.selected_node:
1804 if self.selected_node
is not None:
1805 self.selected_node.selected =
False
1806 self.selected_node = node
1807 if self.selected_node
is not None:
1808 self.selected_node.selected =
True
1810 if self._show_transmissions_mode == ShowTransmissionsMode.SELECTED:
1811 if self.selected_node
is None:
1812 self.simulation.set_nodes_of_interest([])
1814 self.simulation.set_nodes_of_interest([self.selected_node.node_index])
1816 self._update_ipython_selected_node()
1818 def add_information_window(self, info_win):
1819 self.information_windows.append(info_win)
1820 self.simulation.lock.acquire()
1824 self.simulation.lock.release()
1826 def remove_information_window(self, info_win):
1827 self.information_windows.remove(info_win)
1829 def _canvas_tooltip_cb(self, canvas, x, y, keyboard_mode, tooltip):
1831 hadj = self._scrolled_window.get_hadjustment()
1832 vadj = self._scrolled_window.get_vadjustment()
1833 x, y = self.canvas.convert_from_pixels(hadj.get_value() + x, vadj.get_value() + y)
1834 item = self.canvas.get_item_at(x, y,
True)
1838 while item
is not None:
1839 obj = getattr(item,
"pyviz_object",
None)
1841 obj.tooltip_query(tooltip)
1843 item = item.props.parent
1846 def _get_export_file_name(self):
1847 sel = Gtk.FileChooserNative.new(
1848 "Save...", self.canvas.get_toplevel(), Gtk.FileChooserAction.SAVE,
"_Save",
"_Cancel"
1850 sel.set_local_only(
True)
1851 sel.set_do_overwrite_confirmation(
True)
1852 sel.set_current_name(
"Unnamed.pdf")
1854 filter = Gtk.FileFilter()
1855 filter.set_name(
"Embedded PostScript")
1856 filter.add_mime_type(
"image/x-eps")
1857 sel.add_filter(filter)
1859 filter = Gtk.FileFilter()
1860 filter.set_name(
"Portable Document Graphics")
1861 filter.add_mime_type(
"application/pdf")
1862 sel.add_filter(filter)
1864 filter = Gtk.FileFilter()
1865 filter.set_name(
"Scalable Vector Graphics")
1866 filter.add_mime_type(
"image/svg+xml")
1867 sel.add_filter(filter)
1870 if resp != Gtk.ResponseType.ACCEPT:
1874 file_name = sel.get_filename()
1878 def _take_screenshot(self, dummy_button):
1880 file_name = self._get_export_file_name()
1881 if file_name
is None:
1885 x1 = self._scrolled_window.get_hadjustment().get_value()
1886 y1 = self._scrolled_window.get_vadjustment().get_value()
1887 x2 = x1 + self._scrolled_window.get_hadjustment().get_page_size()
1888 y2 = y1 + self._scrolled_window.get_vadjustment().get_page_size()
1889 bounds = GooCanvas.CanvasBounds()
1890 bounds.x1, bounds.y1 = self.canvas.convert_from_pixels(x1, y1)
1891 bounds.x2, bounds.y2 = self.canvas.convert_from_pixels(x2, y2)
1892 dest_width = bounds.x2 - bounds.x1
1893 dest_height = bounds.y2 - bounds.y1
1896 dummy, extension = os.path.splitext(file_name)
1897 extension = extension.lower()
1898 if extension ==
".eps":
1899 surface = cairo.PSSurface(file_name, dest_width, dest_height)
1900 elif extension ==
".pdf":
1901 surface = cairo.PDFSurface(file_name, dest_width, dest_height)
1902 elif extension ==
".svg":
1903 surface = cairo.SVGSurface(file_name, dest_width, dest_height)
1905 dialog = Gtk.MessageDialog(
1906 parent=self.canvas.get_toplevel(),
1907 flags=Gtk.DialogFlags.DESTROY_WITH_PARENT,
1908 type=Gtk.MessageType.ERROR,
1909 buttons=Gtk.ButtonsType.OK,
1910 message_format=
"Unknown extension '%s' (valid extensions are '.eps', '.svg', and '.pdf')"
1918 cr = cairo.Context(surface)
1919 cr.translate(-bounds.x1, -bounds.y1)
1920 self.canvas.render(cr, bounds, self.zoom.get_value())
1924 def set_follow_node(self, node):
1925 if isinstance(node, ns.Node):
1926 node = self.nodes[node.GetId()]
1927 self.follow_node = node
1929 def _start_shell(self, dummy_button):
1930 if self.shell_window
is not None:
1931 self.shell_window.present()
1934 self.shell_window = Gtk.Window()
1935 self.shell_window.set_size_request(750, 550)
1936 self.shell_window.set_resizable(
True)
1937 scrolled_window = Gtk.ScrolledWindow()
1938 scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
1940 self.ipython.modify_font(Pango.FontDescription(SHELL_FONT))
1941 self.ipython.set_wrap_mode(Gtk.WrapMode.CHAR)
1943 scrolled_window.add(self.ipython)
1944 scrolled_window.show()
1945 self.shell_window.add(scrolled_window)
1946 self.shell_window.show()
1947 self.shell_window.connect(
"destroy", self._on_shell_window_destroy)
1949 self._update_ipython_selected_node()
1950 self.ipython.updateNamespace({
"viz": self})
1952 def _on_shell_window_destroy(self, window):
1953 self.shell_window =
None