def add_config(self, title, choices, single_choice=True): ''' create a UI element for selecting options for one variation and put it in a scrollArea ''' scroll = QScrollArea() group_box = QGroupBox(title) group_box.setFlat(True) layout = QVBoxLayout() if len(choices) > 5 and single_choice: combo_box = QComboBox(group_box) for obj in choices: combo_box.addItem(obj) layout.addWidget(combo_box) else: for obj in choices: if single_choice: layout.addWidget(QRadioButton(obj, group_box)) else: layout.addWidget(QCheckBox(obj, group_box)) layout.addStretch(1) group_box.setLayout(layout) scroll.setWidget(group_box) scroll.setWidgetResizable(True) return group_box, scroll
class JointStatePublisherGui(QWidget): sliderUpdateTrigger = Signal() def __init__(self, title, jsp, num_rows=0): super(JointStatePublisherGui, self).__init__() self.jsp = jsp self.joint_map = {} self.vlayout = QVBoxLayout(self) self.scrollable = QWidget() self.gridlayout = QGridLayout() self.scroll = QScrollArea() self.scroll.setWidgetResizable(True) font = QFont("Helvetica", 9, QFont.Bold) ### Generate sliders ### sliders = [] for name in self.jsp.joint_list: if name not in self.jsp.free_joints: continue joint = self.jsp.free_joints[name] if joint['min'] == joint['max']: continue joint_layout = QVBoxLayout() row_layout = QHBoxLayout() label = QLabel(name) label.setFont(font) row_layout.addWidget(label) display = QLineEdit("0.00") display.setAlignment(Qt.AlignRight) display.setFont(font) display.setReadOnly(True) row_layout.addWidget(display) joint_layout.addLayout(row_layout) slider = QSlider(Qt.Horizontal) slider.setFont(font) slider.setRange(0, RANGE) slider.setValue(RANGE / 2) joint_layout.addWidget(slider) self.joint_map[name] = { 'slidervalue': 0, 'display': display, 'slider': slider, 'joint': joint } # Connect to the signal provided by QSignal slider.valueChanged.connect( lambda event, name=name: self.onValueChangedOne(name)) sliders.append(joint_layout) # Determine number of rows to be used in grid self.num_rows = num_rows # if desired num of rows wasn't set, default behaviour is a vertical layout if self.num_rows == 0: self.num_rows = len(sliders) # equals VBoxLayout # Generate positions in grid and place sliders there self.positions = self.generate_grid_positions(len(sliders), self.num_rows) for item, pos in zip(sliders, self.positions): self.gridlayout.addLayout(item, *pos) # Set zero positions read from parameters self.center() # Synchronize slider and displayed value self.sliderUpdate(None) # Set up a signal for updating the sliders based on external joint info self.sliderUpdateTrigger.connect(self.updateSliders) self.scrollable.setLayout(self.gridlayout) self.scroll.setWidget(self.scrollable) self.vlayout.addWidget(self.scroll) # Buttons for randomizing and centering sliders and # Spinbox for on-the-fly selecting number of rows self.randbutton = QPushButton('Randomize', self) self.randbutton.clicked.connect(self.randomize_event) self.vlayout.addWidget(self.randbutton) self.ctrbutton = QPushButton('Center', self) self.ctrbutton.clicked.connect(self.center_event) self.vlayout.addWidget(self.ctrbutton) self.maxrowsupdown = QSpinBox() self.maxrowsupdown.setMinimum(1) self.maxrowsupdown.setMaximum(len(sliders)) self.maxrowsupdown.setValue(self.num_rows) self.maxrowsupdown.valueChanged.connect(self.reorggrid_event) self.vlayout.addWidget(self.maxrowsupdown) self.setLayout(self.vlayout) def onValueChangedOne(self, name): # A slider value was changed, but we need to change the joint_info metadata. joint_info = self.joint_map[name] joint_info['slidervalue'] = joint_info['slider'].value() joint = joint_info['joint'] joint['position'] = self.sliderToValue(joint_info['slidervalue'], joint) joint_info['display'].setText("%.2f" % joint['position']) @pyqtSlot() def updateSliders(self): self.update_sliders() def update_sliders(self): for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slidervalue'] = self.valueToSlider( joint['position'], joint) joint_info['slider'].setValue(joint_info['slidervalue']) def center_event(self, event): self.center() def center(self): rospy.loginfo("Centering") for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slider'].setValue( self.valueToSlider(joint['zero'], joint)) def reorggrid_event(self, event): self.reorganize_grid(event) def reorganize_grid(self, number_of_rows): self.num_rows = number_of_rows # Remove items from layout (won't destroy them!) items = [] for pos in self.positions: item = self.gridlayout.itemAtPosition(*pos) items.append(item) self.gridlayout.removeItem(item) # Generate new positions for sliders and place them in their new spots self.positions = self.generate_grid_positions(len(items), self.num_rows) for item, pos in zip(items, self.positions): self.gridlayout.addLayout(item, *pos) def generate_grid_positions(self, num_items, num_rows): if num_rows == 0: return [] positions = [ (y, x) for x in range(int((math.ceil(float(num_items) / num_rows)))) for y in range(num_rows) ] positions = positions[:num_items] return positions def randomize_event(self, event): self.randomize() def randomize(self): rospy.loginfo("Randomizing") for name, joint_info in self.joint_map.items(): joint = joint_info['joint'] joint_info['slider'].setValue( self.valueToSlider(random.uniform(joint['min'], joint['max']), joint)) def sliderUpdate(self, event): for name, joint_info in self.joint_map.items(): joint_info['slidervalue'] = joint_info['slider'].value() self.update_sliders() def valueToSlider(self, value, joint): return (value - joint['min']) * float(RANGE) / (joint['max'] - joint['min']) def sliderToValue(self, slider, joint): pctvalue = slider / float(RANGE) return joint['min'] + (joint['max'] - joint['min']) * pctvalue
class SystemWidget(QWidget): """ main class inherits from the ui window class. You can specify the topics that the topic pane. SystemWidget.start must be called in order to update topic pane. """ _column_names = ['topic', 'type', 'bandwidth', 'rate', 'value'] def __init__(self, plugin=None): """ """ super(SystemWidget, self).__init__() rp = rospkg.RosPack() ui_file = os.path.join(rp.get_path('rqt_agent'), 'resource', 'SystemWidget.ui') loadUi(ui_file, self) self._plugin = plugin self._subsystems = {} self.all_subsystems = {} self._widgets = {} self.prev_subsystems = [] self.levels_layouts = [] self.structure_root = None self.structure_graph = None self.structure_changed = False self.scrollArea = QScrollArea() self.scrollArea.setWidgetResizable(True) self.horizontalLayout.addWidget(self.scrollArea) self.mainWidget = QWidget() self.scrollArea.setWidget(self.mainWidget) self.verticalLayout = QVBoxLayout() self.mainWidget.setLayout(self.verticalLayout) # self._tree_items = {} # self._column_index = {} # for column_name in self._column_names: # self._column_index[column_name] = len(self._column_index) # self.refresh_topics() # init and start update timer self._timer_refresh_topics = QTimer(self) self._timer_refresh_topics.timeout.connect(self.refresh_topics) def checkStructureChange(self): result = False for subsystem_name in self.prev_subsystems: if not subsystem_name in self._widgets: result = True break if result == False: for subsystem_name in self._widgets: if not subsystem_name in self.prev_subsystems: result = True break self.prev_subsystems = [] for subsystem_name in self._widgets: self.prev_subsystems.append(subsystem_name) return result def start(self): """ This method needs to be called to start updating topic pane. """ self._timer_refresh_topics.start(100) def generateStructure(self): names = [] for w1_name in self._widgets: names.append(w1_name) self._widgets[w1_name].resetBuffersLayout() for w1_name in self._widgets: w1 = self._widgets[w1_name] if not w1.isInitialized(): continue for w2_name in self._widgets: w2 = self._widgets[w2_name] if not w2.isInitialized(): continue common_buffers = w1.getCommonBuffers(w2) if common_buffers != None: w1.groupBuffers(common_buffers, w2.subsystem_name) w2.groupBuffers(common_buffers, w1.subsystem_name) parents_dict = {} for w1_name in self._widgets: w1 = self._widgets[w1_name] if not w1.isInitialized(): continue for w2_name in self._widgets: w2 = self._widgets[w2_name] if not w2.isInitialized(): continue rel_pose = w1.getLowerSubsystemPosition(w2.subsystem_name) if rel_pose != -1: parents_dict[w2_name] = w1_name # print parents_dict # get top-most subsystem (root) root = None if parents_dict: root = list(parents_dict.keys())[0] while root in parents_dict: root = parents_dict[root] levels = [] if root == None: # there are no levels one_level = [] for w_name in self._widgets: one_level.append(w_name) if len(one_level) > 0: levels.append(one_level) else: levels.append([root]) while True: # expand all subsystems in the lowest level current_lowest = levels[-1] next_lower_level = [] for s in current_lowest: lower_list = self._widgets[s].getLowerSubsystems() next_lower_level = next_lower_level + lower_list if len(next_lower_level) == 0: break else: levels.append(next_lower_level) # TODO: manage disjoint trees added_subsystems = [] for l in levels: for s in l: added_subsystems.append(s) for w_name in self._widgets: if not w_name in added_subsystems: print "WARNING: subsystem %s is not in the main tree. This is not implemented." % ( w_name) print "levels:", levels return levels def layout_widgets(self, layout): return (layout.itemAt(i) for i in range(layout.count())) @Slot() def refresh_topics(self): """ refresh tree view items """ # # update the list of subsystems # topic_list = rospy.get_published_topics() if topic_list is None: rospy.logerr( 'Not even a single published topic found. Check network configuration' ) return # start new topic dict new_subsystems = {} for topic_name, topic_type in topic_list: name_split = topic_name.split('/') # print name_split if (len(name_split) == 3) and (name_split[0] == '') and ( name_split[2] == 'diag') and (topic_type == "diagnostic_msgs/DiagnosticArray"): subsystem_name = name_split[1] # if topic is new if subsystem_name not in self._subsystems: # create new TopicInfo topic_info = TopicInfo(topic_name, topic_type) new_subsystems[subsystem_name] = topic_info topic_info.start_monitoring() else: # if topic has been seen before, copy it to new dict and # remove it from the old one new_subsystems[subsystem_name] = self._subsystems[ subsystem_name] del self._subsystems[subsystem_name] # remove unused subsystems while True: repeat = False for s in self._subsystems: if not s in new_subsystems: del self._subsystems[s] repeat = True break if not repeat: break # switch to new topic dict self._subsystems = new_subsystems # print self._subsystems.keys() # # update each subsystem # new_widgets = {} for subsystem_name in self._subsystems: msg = self._subsystems[subsystem_name].last_message if (msg != None) and (len(msg.status) == 2) and \ msg.status[0].name == 'components' and msg.status[1].name == 'diagnostics': name_split = subsystem_name.split('/') if not subsystem_name in self.all_subsystems: self.all_subsystems[subsystem_name] = SubsystemWidget( self._plugin, subsystem_name) if not subsystem_name in self._widgets: new_widgets[subsystem_name] = self.all_subsystems[ subsystem_name] # self.verticalLayout.addWidget(new_widgets[subsystem_name]) else: new_widgets[subsystem_name] = self._widgets[subsystem_name] # del self._widgets[subsystem_name] # for value in msg.status[1].values: # if value.key == 'master_component': # new_widgets[subsystem_name].setStateName(value.value, '') # break # remove unused subsystems # while True: # repeat = False # for s in self._widgets: # if not s in new_widgets: # del self._widgets[s] # repeat = True # break # if not repeat: # break self._widgets = new_widgets # print self._widgets.keys() structure_changed = self.checkStructureChange() if structure_changed: self.structure_changed = True if self.structure_changed: allInitialized = True for subsystem_name in self._widgets: if not self._widgets[subsystem_name].isInitialized(): allInitialized = False break if allInitialized: # remove all widgets from layouts # and remove all layouts for i in reversed(range(len(self.levels_layouts))): layout = self.levels_layouts[i] for i in reversed(range(layout.count())): # The new widget is deleted when its parent is deleted. layout.itemAt(i).widget().setParent(None) self.verticalLayout.removeItem(layout) del layout self.levels_layouts = [] levels = self.generateStructure() for l in levels: hbox = QHBoxLayout() for w in l: hbox.addWidget(self._widgets[w]) self._widgets[w].show() self.levels_layouts.append(hbox) self.verticalLayout.addLayout(hbox) # for # TODO self.structure_changed = False while True: repeat = False for s in self.all_subsystems: if not s in self._widgets: del self.all_subsystems[s] repeat = True break if not repeat: break for subsystem_name in self._widgets: self._widgets[subsystem_name].update_subsystem( self._subsystems[subsystem_name].last_message) def shutdown_plugin(self): for topic in self._topics.values(): topic['info'].stop_monitoring() self._timer_refresh_topics.stop() def set_selected_topics(self, selected_topics): """ @param selected_topics: list of tuple. [(topic_name, topic_type)] @type selected_topics: [] """ rospy.logdebug('set_selected_topics topics={}'.format( len(selected_topics))) self._selected_topics = selected_topics # TODO(Enhancement) Save/Restore tree expansion state def save_settings(self, plugin_settings, instance_settings): header_state = self.topics_tree_widget.header().saveState() instance_settings.set_value('tree_widget_header_state', header_state) def restore_settings(self, pluggin_settings, instance_settings): if instance_settings.contains('tree_widget_header_state'): header_state = instance_settings.value('tree_widget_header_state') if not self.topics_tree_widget.header().restoreState(header_state): rospy.logwarn("rqt_topic: Failed to restore header state.")