def restore_settings(self, settings): serial_number = settings.value('parent', None) #print 'DockWidget.restore_settings()', 'parent', serial_number, 'settings group', settings._group if serial_number is not None: serial_number = int(serial_number) if self._parent_container_serial_number() != serial_number and self._container_manager is not None: floating = self.isFloating() pos = self.pos() new_container = self._container_manager.get_container(serial_number) if new_container is not None: new_parent = new_container.main_window else: new_parent = self._container_manager.get_root_main_window() area = self.parent().dockWidgetArea(self) new_parent.addDockWidget(area, self) if floating: self.setFloating(floating) self.move(pos) title_bar = self.titleBarWidget() title_bar.restore_settings(settings) if title_bar.hide_title_bar and not self.features() & DockWidget.DockWidgetFloatable and \ not self.features() & DockWidget.DockWidgetMovable: self._title_bar_backup = title_bar title_widget = QWidget(self) layout = QHBoxLayout(title_widget) layout.setContentsMargins(0, 0, 0, 0) title_widget.setLayout(layout) self.setTitleBarWidget(title_widget)
def __init__(self, host, masteruri=None, parent=None): PackageDialog.__init__(self, parent) self.host = host self.setWindowTitle('Run') ns_name_label = QLabel("NS/Name:", self.content) self.ns_field = QComboBox(self.content) self.ns_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.ns_field.setEditable(True) ns_history = nm.history().cachedParamValues('run_dialog/NS') ns_history.insert(0, '/') self.ns_field.addItems(ns_history) self.name_field = QLineEdit(self.content) self.name_field.setEnabled(False) horizontalLayout = QHBoxLayout() horizontalLayout.addWidget(self.ns_field) horizontalLayout.addWidget(self.name_field) self.contentLayout.addRow(ns_name_label, horizontalLayout) args_label = QLabel("Args:", self.content) self.args_field = QComboBox(self.content) self.args_field.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength) self.args_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.args_field.setEditable(True) self.contentLayout.addRow(args_label, self.args_field) args_history = nm.history().cachedParamValues('run_dialog/Args') args_history.insert(0, '') self.args_field.addItems(args_history) host_label = QLabel("Host:", self.content) self.host_field = QComboBox(self.content) # self.host_field.setSizeAdjustPolicy(QComboBox.AdjustToContents) self.host_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.host_field.setEditable(True) host_label.setBuddy(self.host_field) self.contentLayout.addRow(host_label, self.host_field) self.host_history = host_history = nm.history().cachedParamValues('/Host') if self.host in host_history: host_history.remove(self.host) host_history.insert(0, self.host) self.host_field.addItems(host_history) master_label = QLabel("ROS Master URI:", self.content) self.master_field = QComboBox(self.content) self.master_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.master_field.setEditable(True) master_label.setBuddy(self.host_field) self.contentLayout.addRow(master_label, self.master_field) self.master_history = master_history = nm.history().cachedParamValues('/Optional Parameter/ROS Master URI') self.masteruri = "ROS_MASTER_URI" if masteruri is None else masteruri if self.masteruri in master_history: master_history.remove(self.masteruri) master_history.insert(0, self.masteruri) self.master_field.addItems(master_history) # self.package_field.setFocus(QtCore.Qt.TabFocusReason) if hasattr(self.package_field, "textChanged"): # qt compatibility self.package_field.textChanged.connect(self.on_package_selected) else: self.package_field.editTextChanged.connect(self.on_package_selected) self.binary_field.activated[str].connect(self.on_binary_selected)
def __init__(self, parent=None, current_values=None): super(BlacklistDialog, self).__init__(parent) self.setWindowTitle("Blacklist") vbox = QVBoxLayout() self.setLayout(vbox) self._blacklist = Blacklist() if isinstance(current_values, list): for val in current_values: self._blacklist.append(val) vbox.addWidget(self._blacklist) controls_layout = QHBoxLayout() add_button = QPushButton(icon=QIcon.fromTheme('list-add')) rem_button = QPushButton(icon=QIcon.fromTheme('list-remove')) ok_button = QPushButton("Ok") cancel_button = QPushButton("Cancel") add_button.clicked.connect(self._add_item) rem_button.clicked.connect(self._remove_item) ok_button.clicked.connect(self.accept) cancel_button.clicked.connect(self.reject) controls_layout.addWidget(add_button) controls_layout.addWidget(rem_button) controls_layout.addStretch(0) controls_layout.addWidget(ok_button) controls_layout.addWidget(cancel_button) vbox.addLayout(controls_layout)
def __init__(self, parent = None, logger = Logger()): QWidgetWithLogger.__init__(self, parent, logger) # start widget hbox = QHBoxLayout() hbox.setMargin(0) hbox.setContentsMargins(0, 0, 0, 0) # get system icon icon = QIcon.fromTheme("view-refresh") size = icon.actualSize(QSize(32, 32)) # add combo box self.parameter_set_names_combo_box = QComboBox() self.parameter_set_names_combo_box.currentIndexChanged[str].connect(self.param_changed) hbox.addWidget(self.parameter_set_names_combo_box) # add refresh button self.get_all_parameter_set_names_button = QPushButton() self.get_all_parameter_set_names_button.clicked.connect(self._get_all_parameter_set_names) self.get_all_parameter_set_names_button.setIcon(icon) self.get_all_parameter_set_names_button.setFixedSize(size.width()+2, size.height()+2) hbox.addWidget(self.get_all_parameter_set_names_button) # end widget self.setLayout(hbox) # init widget self.reset_parameter_set_selection()
def add_widget_with_frame(parent, widget, text = ""): box_layout = QHBoxLayout() box_layout.addWidget(widget) group_box = QGroupBox() group_box.setStyleSheet("QGroupBox { border: 1px solid gray; border-radius: 4px; margin-top: 0.5em; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; }") group_box.setTitle(text) group_box.setLayout(box_layout) parent.addWidget(group_box)
def __init__(self, parent = None, logger = Logger(), step_plan_topic = str()): QWidgetWithLogger.__init__(self, parent, logger) # start widget vbox = QVBoxLayout() vbox.setMargin(0) vbox.setContentsMargins(0, 0, 0, 0) # step plan input topic selection if len(step_plan_topic) == 0: step_plan_topic_widget = QTopicWidget(topic_type = 'vigir_footstep_planning_msgs/StepPlan') step_plan_topic_widget.topic_changed_signal.connect(self._init_step_plan_subscriber) vbox.addWidget(step_plan_topic_widget) else: self._init_step_plan_subscriber(step_plan_topic) # execute action server topic selection execute_topic_widget = QTopicWidget(topic_type = 'vigir_footstep_planning_msgs/ExecuteStepPlanAction', is_action_topic = True) execute_topic_widget.topic_changed_signal.connect(self._init_execute_action_client) vbox.addWidget(execute_topic_widget) # start button part buttons_hbox = QHBoxLayout() buttons_hbox.setMargin(0) # execute self.execute_command = QPushButton("Execute (Steps: 0)") self.execute_command.clicked.connect(self.execute_command_callback) self.execute_command.setEnabled(False) buttons_hbox.addWidget(self.execute_command) # repeat self.repeat_command = QPushButton("Repeat") self.repeat_command.clicked.connect(self.execute_command_callback) self.repeat_command.setEnabled(False) buttons_hbox.addWidget(self.repeat_command) # stop self.stop_command = QPushButton("Stop") self.stop_command.clicked.connect(self.stop_command_callback) self.stop_command.setEnabled(False) buttons_hbox.addWidget(self.stop_command) # end button part vbox.addLayout(buttons_hbox) # end widget self.setLayout(vbox) # init widget if len(step_plan_topic) == 0: step_plan_topic_widget.emit_topic_name() execute_topic_widget.emit_topic_name()
def __init__(self, context): super(MultiRobotPasserGUIPlugin, self).__init__(context) # Give QObjects reasonable names self.setObjectName('MultiRobotPasserGUIPlugin') # Create QWidget self.widget = QWidget() self.master_layout = QVBoxLayout(self.widget) self.buttons = {} self.button_layout = QHBoxLayout() self.master_layout.addLayout(self.button_layout) for button_text in ["Enable", "Disable"]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect(self.handle_button) button.setCheckable(True) self.button_layout.addWidget(button) self.buttons[button_text] = button self.widget.setObjectName('MultiRobotPasserGUIPluginUI') if context.serial_number() > 1: self.widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.widget) self.status_subscriber = rospy.Subscriber("status", Bool, self.status_callback) self.enable = rospy.ServiceProxy("enable", Empty) self.disable = rospy.ServiceProxy("disable", Empty) self.enabled = False self.connect(self.widget, SIGNAL("update"), self.update)
def __init__(self, map_topic='/map', paths=['/move_base/NavFn/plan', '/move_base/TrajectoryPlannerROS/local_plan'], polygons=['/move_base/local_costmap/robot_footprint']): super(NavViewWidget, self).__init__() self._layout = QVBoxLayout() self._button_layout = QHBoxLayout() self.setAcceptDrops(True) self.setWindowTitle('Navigation Viewer') self.paths = paths self.polygons = polygons self.map = map_topic self._tf = tf.TransformListener() self._nav_view = NavView(map_topic, paths, polygons, tf = self._tf, parent = self) self._set_pose = QPushButton('Set Pose') self._set_pose.clicked.connect(self._nav_view.pose_mode) self._set_goal = QPushButton('Set Goal') self._set_goal.clicked.connect(self._nav_view.goal_mode) self._button_layout.addWidget(self._set_pose) self._button_layout.addWidget(self._set_goal) self._layout.addLayout(self._button_layout) self._layout.addWidget(self._nav_view) self.setLayout(self._layout)
def __init__(self, parent): super(TimelineWidget, self).__init__() self.parent = parent self._layout = QHBoxLayout() #self._view = QGraphicsView() self._view = TimelineWidget.TimelineView(self) self._scene = QGraphicsScene() self._colors = [QColor('green'), QColor('yellow'), QColor('red')] self._messages = [None for x in range(20)] self._mq = [1 for x in range(20)] self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setScene(self._scene) self._layout.addWidget(self._view, 1) self.pause_button = QPushButton('Pause') self.pause_button.setCheckable(True) self.pause_button.clicked.connect(self.pause) self._layout.addWidget(self.pause_button) self.setLayout(self._layout) self.update.connect(self.redraw)
def __init__(self, parent=None): """Create a new, empty DataPlot This will raise a RuntimeError if none of the supported plotting backends can be found """ super(DataPlot, self).__init__(parent) self._plot_index = 0 self._color_index = 0 self._markers_on = False self._autoscroll = True self._autoscale_x = True self._autoscale_y = DataPlot.SCALE_ALL # the backend widget that we're trying to hide/abstract self._data_plot_widget = None self._curves = {} self._vline = None self._redraw.connect(self._do_redraw) self._layout = QHBoxLayout() self.setLayout(self._layout) enabled_plot_types = [pt for pt in self.plot_types if pt['enabled']] if not enabled_plot_types: version_info = ' and PySide > 1.1.0' if QT_BINDING == 'pyside' else '' raise RuntimeError('No usable plot type found. Install at least one of: PyQtGraph, MatPlotLib (at least 1.1.0%s) or Python-Qwt5.' % version_info) self._switch_data_plot_widget(self._plot_index) self.show()
def __init__(self, context): super(QuestionDialogPlugin, self).__init__(context) # Give QObjects reasonable names self.setObjectName('QuestionDialogPlugin') # Create QWidget self._widget = QWidget() self._widget.setFont(QFont("Times", 14, QFont.Bold)) self._layout = QVBoxLayout(self._widget) self._text_browser = QTextBrowser(self._widget) self._layout.addWidget(self._text_browser) self._button_layout = QHBoxLayout() self._layout.addLayout(self._button_layout) # layout = QVBoxLayout(self._widget) # layout.addWidget(self.button) self._widget.setObjectName('QuestionDialogPluginUI') if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self._widget) # Setup service provider self.service = rospy.Service('question_dialog', QuestionDialog, self.service_callback) self.response_ready = False self.response = None self.buttons = [] self.text_label = None self.text_input = None self.connect(self._widget, SIGNAL("update"), self.update) self.connect(self._widget, SIGNAL("timeout"), self.timeout)
def addOnceOrRepeat_And_VoiceRadioButtons(self, layout): ''' Creates radio buttons for selecting whether a sound is to play once, or repeatedly until stopped. Also adds radio buttons for selecting voices. Places all in a horizontal box layout. Adds that hbox layout to the passed-in layout. Sets instance variables: 1. C{self.onceOrRepeatDict} 2. C{self.voicesRadioButtonsDict} @param layout: Layout object to which the label/txt-field C{hbox} is to be added. @type layout: QLayout ''' hbox = QHBoxLayout(); (self.onceOrRepeatGroup, onceOrRepeatButtonLayout, self.onceOrRepeatDict) =\ self.buildRadioButtons([SpeakEasyGUI.interactionWidgets['PLAY_ONCE'], SpeakEasyGUI.interactionWidgets['PLAY_REPEATEDLY'] ], Orientation.HORIZONTAL, Alignment.LEFT, activeButtons=[SpeakEasyGUI.interactionWidgets['PLAY_ONCE']], behavior=CheckboxGroupBehavior.RADIO_BUTTONS); self.replayPeriodSpinBox = QDoubleSpinBox(self); self.replayPeriodSpinBox.setRange(0.0, 99.9); # seconds self.replayPeriodSpinBox.setSingleStep(0.5); self.replayPeriodSpinBox.setDecimals(1); onceOrRepeatButtonLayout.addWidget(self.replayPeriodSpinBox); secondsLabel = QLabel("secs delay"); onceOrRepeatButtonLayout.addWidget(secondsLabel); # Create an array of voice radio button labels: voiceRadioButtonLabels = []; for voiceKey in SpeakEasyGUI.voices.keys(): voiceRadioButtonLabels.append(SpeakEasyGUI.interactionWidgets[voiceKey]); (self.voicesGroup, voicesButtonLayout, self.voicesRadioButtonsDict) =\ self.buildRadioButtons(voiceRadioButtonLabels, Orientation.HORIZONTAL, Alignment.RIGHT, activeButtons=[SpeakEasyGUI.interactionWidgets['VOICE_1']], behavior=CheckboxGroupBehavior.RADIO_BUTTONS); # Style all the radio buttons: for playFreqButton in self.onceOrRepeatDict.values(): playFreqButton.setStyleSheet(SpeakEasyGUI.playOnceRepeatButtonStylesheet); for playFreqButton in self.voicesRadioButtonsDict.values(): playFreqButton.setStyleSheet(SpeakEasyGUI.voiceButtonStylesheet); #...and the replay delay spinbox: self.replayPeriodSpinBox.setStyleSheet(SpeakEasyGUI.playRepeatSpinboxStylesheet); #****** replayPeriodSpinBox styling hbox.addLayout(onceOrRepeatButtonLayout); hbox.addStretch(1); hbox.addLayout(voicesButtonLayout); layout.addLayout(hbox);
def __init__(self, parent, fileName, top_widget_layout): self.controllers = [] self.parent = parent self.loadFile(fileName) print "Initialize controllers..." for controller in self.controllers: frame = QFrame() frame.setFrameShape(QFrame.StyledPanel); frame.setFrameShadow(QFrame.Raised); vbox = QVBoxLayout() label = QLabel() label.setText(controller.label) vbox.addWidget(label); print controller.name for joint in controller.joints: label = QLabel() label.setText(joint.name) vbox.addWidget(label); #Add input for setting the biases widget = QWidget() hbox = QHBoxLayout() hbox.addWidget(joint.sensor_bias_spinbox) hbox.addWidget(joint.control_bias_spinbox) hbox.addWidget(joint.gearing_bias_spinbox) widget.setLayout(hbox) vbox.addWidget(widget) label = QLabel() label.setText(" Sensor Control Gearing") vbox.addWidget(label); vbox.addStretch() frame.setLayout(vbox) top_widget_layout.addWidget(frame) print "Done loading controllers"
def addTxtInputFld(self, layout): ''' Creates text input field label and text field in a horizontal box layout. Adds that hbox layout to the passed-in layout. Sets instance variables: 1. C{self.speechInputFld} @param layout: Layout object to which the label/txt-field C{hbox} is to be added. @type layout: QLayout ''' speechControlsLayout = QHBoxLayout(); speechControlsLayout.addStretch(1); speechInputFldLabel = QLabel("<b>What to say:</b>") speechControlsLayout.addWidget(speechInputFldLabel); self.speechInputFld = TextPanel(numLines=5); self.speechInputFld.setStyleSheet(SpeakEasyGUI.inputFldStylesheet); self.speechInputFld.setFontPointSize(SpeakEasyGUI.EDIT_FIELD_TEXT_SIZE); speechControlsLayout.addWidget(self.speechInputFld); layout.addLayout(speechControlsLayout); # Create and hide the dialog for adding Cepstral voice modulation # markup to text in the text input field: self.speechControls = MarkupManagementUI(textPanel=self.speechInputFld, parent=self);
def _create_buttons(self): # create the buttons line self.buttons = QWidget(self) self.horizontalLayout = QHBoxLayout(self.buttons) self.horizontalLayout.setContentsMargins(4, 0, 4, 0) self.horizontalLayout.setObjectName("horizontalLayout") # add the search button self.searchButton = QPushButton(self) self.searchButton.setObjectName("searchButton") # self.searchButton.clicked.connect(self.on_shortcut_find) self.searchButton.toggled.connect(self.on_toggled_find) self.searchButton.setText(self._translate("&Find")) self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') self.searchButton.setFlat(True) self.searchButton.setCheckable(True) self.horizontalLayout.addWidget(self.searchButton) # add the replace button self.replaceButton = QPushButton(self) self.replaceButton.setObjectName("replaceButton") # self.replaceButton.clicked.connect(self.on_shortcut_replace) self.replaceButton.toggled.connect(self.on_toggled_replace) self.replaceButton.setText(self._translate("&Replace")) self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') self.replaceButton.setFlat(True) self.replaceButton.setCheckable(True) self.horizontalLayout.addWidget(self.replaceButton) # add the goto button self.gotoButton = QPushButton(self) self.gotoButton.setObjectName("gotoButton") self.gotoButton.clicked.connect(self.on_shortcut_goto) self.gotoButton.setText(self._translate("&Goto line")) self.gotoButton.setShortcut("Ctrl+G") self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') self.gotoButton.setFlat(True) self.horizontalLayout.addWidget(self.gotoButton) # add a tag button self.tagButton = self._create_tag_button(self) self.horizontalLayout.addWidget(self.tagButton) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add line number label self.pos_label = QLabel() self.horizontalLayout.addWidget(self.pos_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add save button self.saveButton = QPushButton(self) self.saveButton.setObjectName("saveButton") self.saveButton.clicked.connect(self.on_saveButton_clicked) self.saveButton.setText(self._translate("&Save")) self.saveButton.setShortcut("Ctrl+S") self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') self.saveButton.setFlat(True) self.horizontalLayout.addWidget(self.saveButton) return self.buttons
def addTitle(self,layout): title = QLabel("<H1>SpeakEasy</H1>"); hbox = QHBoxLayout(); hbox.addStretch(1); hbox.addWidget(title); hbox.addStretch(1); layout.addLayout(hbox);
def buildOptionsRadioButtons(self, layout): hbox = QHBoxLayout(); (self.playLocalityGroup, playLocalityButtonLayout, self.playLocalityRadioButtonsDict) =\ self.buildRadioButtons([SpeakEasyGUI.interactionWidgets['PLAY_LOCALLY'], SpeakEasyGUI.interactionWidgets['PLAY_AT_ROBOT'] ], Orientation.HORIZONTAL, Alignment.LEFT, activeButtons=[SpeakEasyGUI.interactionWidgets[DEFAULT_PLAY_LOCATION]], behavior=CheckboxGroupBehavior.RADIO_BUTTONS); #behavior=CheckboxGroupBehavior.CHECKBOXES); # Style all the radio buttons: for playLocalityButton in self.playLocalityRadioButtonsDict.values(): playLocalityButton.setStyleSheet(SpeakEasyGUI.voiceButtonStylesheet); hbox.addLayout(playLocalityButtonLayout); hbox.addStretch(1); self.buildConvenienceButtons(hbox); layout.addLayout(hbox);
def joint_widget(self, main_vbox, index): joint = self.chain[index] frame = QFrame() frame.setFrameShape(QFrame.StyledPanel) frame.setFrameShadow(QFrame.Raised) hbox = QHBoxLayout() # hbox.addWidget(frame) self.prior_time = 0.0 robot_joint = self.robot.joints[self.joint_list[joint.name]] joint.lower_limit = robot_joint.limit.lower joint.upper_limit = robot_joint.limit.upper ramp_range = robot_joint.limit.upper - robot_joint.limit.lower print " ", joint.name, " limits(", joint.lower_limit, ", ", joint.upper_limit, ") range=", ramp_range self.cmd_spinbox.append(QDoubleSpinBox()) self.cmd_spinbox[index].setDecimals(5) self.cmd_spinbox[index].setRange(joint.lower_limit, joint.upper_limit) self.cmd_spinbox[index].setSingleStep((robot_joint.limit.upper - robot_joint.limit.lower) / 50.0) self.cmd_spinbox[index].valueChanged.connect(joint.on_cmd_value) self.ramp_up_spinbox.append(QDoubleSpinBox()) self.ramp_up_spinbox[index].setDecimals(5) self.ramp_up_spinbox[index].setRange(-ramp_range, ramp_range) self.ramp_up_spinbox[index].setSingleStep(ramp_range / 50.0) self.ramp_up_spinbox[index].valueChanged.connect(joint.on_ramp_up_value) self.ramp_down_spinbox.append(QDoubleSpinBox()) self.ramp_down_spinbox[index].setDecimals(5) self.ramp_down_spinbox[index].setRange(-ramp_range, ramp_range) self.ramp_down_spinbox[index].setSingleStep(ramp_range / 50.0) self.ramp_down_spinbox[index].valueChanged.connect(joint.on_ramp_down_value) hbox.addWidget(QLabel(joint.name)) hbox.addWidget(self.cmd_spinbox[index]) hbox.addWidget(self.ramp_up_spinbox[index]) hbox.addWidget(self.ramp_down_spinbox[index]) # Add horizontal layout to frame for this joint group frame.setLayout(hbox) # Add frame to main vertical layout main_vbox.addWidget(frame)
def __init__(self, editor, *args): QFrame.__init__(self, *args) self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.edit = editor self.number_bar = self.NumberBar() self.number_bar.set_text_edit(self.edit) hbox = QHBoxLayout(self) hbox.setSpacing(0) hbox.setMargin(0) hbox.addWidget(self.number_bar) hbox.addWidget(self.edit) self.edit.installEventFilter(self) self.edit.viewport().installEventFilter(self)
def addChoiceButtons(self, layout): ''' Appends the existing Next/Previous/Cancel/OK buttons into the passed-in layout. @param layout: ''' choiceButtonRowLayout = QHBoxLayout(); choiceButtonRowLayout.addWidget(self.nextButton); choiceButtonRowLayout.addWidget(self.prevButton); choiceButtonRowLayout.addWidget(self.cancelButton); choiceButtonRowLayout.addWidget(self.OKButton); layout.addLayout(choiceButtonRowLayout);
class PathEditor(QWidget): ''' This is a path editor used as ItemDeligate in settings view. This editor provides an additional button for directory selection dialog. ''' editing_finished_signal = Signal() def __init__(self, path, parent=None): QWidget.__init__(self, parent) self.path = path self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._button = QPushButton('...') self._button.setMaximumSize(QSize(24, 20)) self._button.clicked.connect(self._on_path_select_clicked) self._layout.addWidget(self._button) self._lineedit = QLineEdit(path) self._lineedit.returnPressed.connect(self._on_editing_finished) self._layout.addWidget(self._lineedit) self.setLayout(self._layout) self.setFocusProxy(self._button) self.setAutoFillBackground(True) def _on_path_select_clicked(self): # Workaround for QFileDialog.getExistingDirectory because it do not # select the configuration folder in the dialog self.dialog = QFileDialog(self, caption='Select a new settings folder') self.dialog.setOption(QFileDialog.HideNameFilterDetails, True) self.dialog.setFileMode(QFileDialog.Directory) self.dialog.setDirectory(self.path) if self.dialog.exec_(): fileNames = self.dialog.selectedFiles() path = fileNames[0] if os.path.isfile(path): path = os.path.basename(path) self._lineedit.setText(path) self.path = dir self.editing_finished_signal.emit() def _on_editing_finished(self): if self._lineedit.text(): self.path = self._lineedit.text() self.editing_finished_signal.emit()
def __init__(self, path, parent=None): QWidget.__init__(self, parent) self.path = path self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._button = QPushButton('...') self._button.setMaximumSize(QSize(24, 20)) self._button.clicked.connect(self._on_path_select_clicked) self._layout.addWidget(self._button) self._lineedit = QLineEdit(path) self._lineedit.returnPressed.connect(self._on_editing_finished) self._layout.addWidget(self._lineedit) self.setLayout(self._layout) self.setFocusProxy(self._button) self.setAutoFillBackground(True)
def __init__(self, parent = None, subscribe = False): QWidget.__init__(self, parent) # start widget vbox = QVBoxLayout() vbox.setMargin(0) vbox.setContentsMargins(0, 0, 0, 0) # add error status text edit self.error_status_text_box = QErrorStatusTextBox() self.error_status_text_box_layout = QHBoxLayout() self.error_status_text_box_layout.addWidget(self.error_status_text_box) vbox.addLayout(self.error_status_text_box_layout) # add panel hbox = QHBoxLayout() # clear push button self.execute_command = QPushButton("Clear") self.execute_command.clicked.connect(self.error_status_text_box.clear) hbox.addWidget(self.execute_command) hbox.addStretch() # hide window checkbox hide_window_check_box = QCheckBox("Hide") hide_window_check_box.stateChanged.connect(self.state_changed) hbox.addWidget(hide_window_check_box) # end panel vbox.addLayout(hbox) # end widget self.setLayout(vbox) #self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) # subscriber if (subscribe): self.error_status_sub = rospy.Subscriber("error_status", ErrorStatus, self.error_status_callback) self.subscribed = subscribe # connect signal slot internally to prevent crash by subscriber self.error_status_signal.connect(self.append_error_status)
def __init__(self, parent = None, topic_type = str(), is_action_topic = False): QWidget.__init__(self, parent) if is_action_topic: self.topic_type = topic_type + "Goal" else: self.topic_type = topic_type self.is_action_topic = is_action_topic # start widget hbox = QHBoxLayout() hbox.setMargin(0) hbox.setContentsMargins(0, 0, 0, 0) # topic combo box self.topic_combo_box = QComboBox() self.topic_combo_box.setEnabled(False) self.topic_combo_box.blockSignals(True) self.topic_combo_box.setValidator(QRegExpValidator(QRegExp('((\d|\w|/)(?!//))*'), self)) self.topic_combo_box.currentIndexChanged[str].connect(self.topic_changed) hbox.addWidget(self.topic_combo_box) # get system icon icon = QIcon.fromTheme("view-refresh") size = icon.actualSize(QSize(32, 32)) # add refresh button refresh_topics_button = QPushButton() refresh_topics_button.clicked.connect(self.update_topic_list) refresh_topics_button.setIcon(icon) refresh_topics_button.setFixedSize(size.width()+2, size.height()+2) hbox.addWidget(refresh_topics_button) # end widget self.setLayout(hbox) # init widget self.update_topic_list()
def __init__(self, context): super(MultiRobotPatrollerPlugin, self).__init__(context) # Give QObjects reasonable names self.setObjectName('MultiRobotPatrollerPlugin') # Create QWidget self.widget = QWidget() self.master_layout = QVBoxLayout(self.widget) self.buttons = [] self.button_layout = QHBoxLayout() self.master_layout.addLayout(self.button_layout) for button_text in ["Play", "Pause"]: button = QPushButton(button_text, self.widget) button.clicked[bool].connect(self.handle_button) button.setCheckable(True) self.button_layout.addWidget(button) if button_text == "Pause": button.setChecked(True) self.buttons.append(button) self.text_labels = {} self.widget.setObjectName('MultiRobotPatrollerPluginUI') if context.serial_number() > 1: self.widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.widget) self.points = rospy.get_param("points") self.flip_direction = rospy.get_param("flip_direction") self.available_robots = [] self.global_start_counter = 0 self.global_forward = True self.available_robot_subscriber = rospy.Subscriber("/available_robots", AvailableRobotArray, self.available_robot_callback) self.robot_goals = {} self.paused = True self.connect(self.widget, SIGNAL("update"), self.update)
def joint_widget( self, main_vbox, index): joint = self.chain[index] frame = QFrame() frame.setFrameShape(QFrame.StyledPanel); frame.setFrameShadow(QFrame.Raised); hbox = QHBoxLayout() #hbox.addWidget(frame) self.prior_time = 0.0 robot_joint = self.robot.joints[self.joint_list[joint.name]] joint.lower_limit = robot_joint.limit.lower joint.upper_limit = robot_joint.limit.upper amplitude_range = (robot_joint.limit.upper-robot_joint.limit.lower) frequency_range = self.frequency_limit iterations_range = 10000 print " ",joint.name, " limits(", joint.lower_limit,", ",joint.upper_limit,") range=",amplitude_range self.cur_position_spinbox.append(QDoubleSpinBox()) self.cur_position_spinbox[index].setDecimals(5) self.cur_position_spinbox[index].setRange(joint.lower_limit, joint.upper_limit) self.cur_position_spinbox[index].setSingleStep((robot_joint.limit.upper-robot_joint.limit.lower)/50.0) self.cur_position_spinbox[index].valueChanged.connect(joint.on_position_value) self.amplitude_spinbox.append(QDoubleSpinBox()) self.amplitude_spinbox[index].setDecimals(5) self.amplitude_spinbox[index].setRange(-amplitude_range, amplitude_range) self.amplitude_spinbox[index].setSingleStep(amplitude_range/50.0) self.amplitude_spinbox[index].valueChanged.connect(joint.on_amplitude_value) self.amplitude_spinbox[index].setValue(joint.amplitude) hbox.addWidget(QLabel(joint.name)) hbox.addWidget(self.cur_position_spinbox[index]) hbox.addWidget(self.amplitude_spinbox[index]) # Add horizontal layout to frame for this joint group frame.setLayout(hbox) # Add frame to main vertical layout main_vbox.addWidget(frame)
class QuestionDialogPlugin(Plugin): def __init__(self, context): super(QuestionDialogPlugin, self).__init__(context) # Give QObjects reasonable names self.setObjectName('QuestionDialogPlugin') # Create QWidget self._widget = QWidget() self._widget.setFont(QFont("Times", 14, QFont.Bold)) self._layout = QVBoxLayout(self._widget) self._text_browser = QTextBrowser(self._widget) self._layout.addWidget(self._text_browser) self._button_layout = QHBoxLayout() self._layout.addLayout(self._button_layout) # layout = QVBoxLayout(self._widget) # layout.addWidget(self.button) self._widget.setObjectName('QuestionDialogPluginUI') if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self._widget) # Setup service provider self.service = rospy.Service('question_dialog', QuestionDialog, self.service_callback) self.response_ready = False self.response = None self.buttons = [] self.text_label = None self.text_input = None self.connect(self._widget, SIGNAL("update"), self.update) self.connect(self._widget, SIGNAL("timeout"), self.timeout) def shutdown_plugin(self): self.service.shutdown() def service_callback(self, req): self.response_ready = False self.request = req self._widget.emit(SIGNAL("update")) # Start timer against wall clock here instead of the ros clock. start_time = time.time() while not self.response_ready: if req.timeout != QuestionDialogRequest.NO_TIMEOUT: current_time = time.time() if current_time - start_time > req.timeout: self._widget.emit(SIGNAL("timeout")) return QuestionDialogResponse( QuestionDialogRequest.TIMED_OUT, "") time.sleep(0.2) return self.response def update(self): self.clean() req = self.request self._text_browser.setText(req.message) if req.type == QuestionDialogRequest.DISPLAY: # All done, nothing more too see here. self.response = QuestionDialogResponse( QuestionDialogRequest.NO_RESPONSE, "") self.response_ready = True elif req.type == QuestionDialogRequest.CHOICE_QUESTION: for index, options in enumerate(req.options): button = QPushButton(options, self._widget) button.clicked.connect(partial(self.handle_button, index)) self._button_layout.addWidget(button) self.buttons.append(button) elif req.type == QuestionDialogRequest.TEXT_QUESTION: self.text_label = QLabel("Enter here: ", self._widget) self._button_layout.addWidget(self.text_label) self.text_input = QLineEdit(self._widget) self.text_input.editingFinished.connect(self.handle_text) self._button_layout.addWidget(self.text_input) def timeout(self): self._text_browser.setText("Oh no! The request timed out.") self.clean() def clean(self): while self._button_layout.count(): item = self._button_layout.takeAt(0) item.widget().deleteLater() self.buttons = [] self.text_input = None self.text_label = None def handle_button(self, index): self.response = QuestionDialogResponse(index, "") self.clean() self.response_ready = True def handle_text(self): self.response = QuestionDialogResponse( QuestionDialogRequest.TEXT_RESPONSE, self.text_input.text()) self.clean() self.response_ready = True def save_settings(self, plugin_settings, instance_settings): # TODO save intrinsic configuration, usually using: # instance_settings.set_value(k, v) pass def restore_settings(self, plugin_settings, instance_settings): # TODO restore intrinsic configuration, usually using: # v = instance_settings.value(k) pass
class LogicalMarkerPlugin(Plugin): def __init__(self, context): super(LogicalMarkerPlugin, self).__init__(context) # Create an image for the original map. This will never change. try: self.map_yaml_file_str = rospy.get_param("~map_file") self.data_directory = rospy.get_param("~data_directory") except KeyError: rospy.logfatal("~map_file and ~data_directory need to be set to use the logical marker") return map_image_location = getImageFileLocation(self.map_yaml_file_str) map = loadMapFromFile(self.map_yaml_file_str) locations_file = getLocationsFileLocationFromDataDirectory(self.data_directory) doors_file = getDoorsFileLocationFromDataDirectory(self.data_directory) objects_file = getObjectsFileLocationFromDataDirectory(self.data_directory) # Give QObjects reasonable names self.setObjectName('LogicalMarkerPlugin') # Create QWidget self.master_widget = QWidget() self.master_layout = QVBoxLayout(self.master_widget) # Main Functions - Doors, Locations, Objects self.function_layout = QHBoxLayout() self.master_layout.addLayout(self.function_layout) self.function_buttons = [] self.current_function = None for button_text in ['Locations', 'Doors', 'Objects']: button = QPushButton(button_text, self.master_widget) button.clicked[bool].connect(self.handle_function_button) button.setCheckable(True) self.function_layout.addWidget(button) self.function_buttons.append(button) self.function_layout.addStretch(1) self.master_layout.addWidget(self.get_horizontal_line()) # Subfunction toolbar self.subfunction_layout = QHBoxLayout() clearLayoutAndFixHeight(self.subfunction_layout) self.master_layout.addLayout(self.subfunction_layout) self.current_subfunction = None self.master_layout.addWidget(self.get_horizontal_line()) self.image = MapImage(map_image_location, self.master_widget) self.master_layout.addWidget(self.image) self.master_layout.addWidget(self.get_horizontal_line()) # Configuration toolbar self.configuration_layout = QHBoxLayout() clearLayoutAndFixHeight(self.configuration_layout) self.master_layout.addLayout(self.configuration_layout) # Add a stretch at the bottom. self.master_layout.addStretch(1) self.master_widget.setObjectName('LogicalMarkerPluginUI') if context.serial_number() > 1: self.master_widget.setWindowTitle(self.master_widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.master_widget) # Activate the functions self.functions = {} self.functions['Locations'] = LocationFunction(locations_file, map, self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Doors'] = DoorFunction(doors_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Objects'] = ObjectFunction(objects_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) def construct_layout(self): pass def get_horizontal_line(self): """ http://stackoverflow.com/questions/5671354/how-to-programmatically-make-a-horizontal-line-in-qt """ hline = QFrame() hline.setFrameShape(QFrame.HLine) hline.setFrameShadow(QFrame.Sunken) return hline def handle_function_button(self): source = self.sender() if source.text() == self.current_function: source.setChecked(True) return # Depress all other buttons. for button in self.function_buttons: if button != source: button.setChecked(False) if self.current_function is not None: self.functions[self.current_function].deactivateFunction() self.current_function = source.text() # Clear all subfunction buttons. clearLayoutAndFixHeight(self.subfunction_layout) if self.current_function is not None: self.functions[self.current_function].activateFunction() def shutdown_plugin(self): modified = False for function in self.functions: if self.functions[function].isModified(): modified = True if modified: ret = QMessageBox.warning(self.master_widget, "Save", "The logical map has been modified.\n" "Do you want to save your changes?", QMessageBox.Save | QMessageBox.Discard) if ret == QMessageBox.Save: for function in self.functions: self.functions[function].saveConfiguration() def save_settings(self, plugin_settings, instance_settings): pass def restore_settings(self, plugin_settings, instance_settings): pass
def __init__(self, parent=None): super(VisualizerWidget, self).__init__(parent) self.setWindowTitle('Graph Profiler Visualizer') vbox = QVBoxLayout() self.setLayout(vbox) toolbar_layout = QHBoxLayout() refresh_button = QPushButton() refresh_button.setIcon(QIcon.fromTheme('view-refresh')) auto_refresh_checkbox = QCheckBox("Auto Refresh") hide_disconnected_topics = QCheckBox("Hide Disconnected Topics") topic_blacklist_button = QPushButton("Topic Blacklist") node_blacklist_button = QPushButton("Node Blacklist") refresh_button.clicked.connect(self._refresh) topic_blacklist_button.clicked.connect(self._edit_topic_blacklist) node_blacklist_button.clicked.connect(self._edit_node_blacklist) auto_refresh_checkbox.setCheckState(2) auto_refresh_checkbox.stateChanged.connect(self._autorefresh_changed) hide_disconnected_topics.setCheckState(2) hide_disconnected_topics.stateChanged.connect(self._hidedisconnectedtopics_changed) toolbar_layout.addWidget(refresh_button) toolbar_layout.addWidget(auto_refresh_checkbox) toolbar_layout.addStretch(0) toolbar_layout.addWidget(hide_disconnected_topics) toolbar_layout.addWidget(topic_blacklist_button) toolbar_layout.addWidget(node_blacklist_button) vbox.addLayout(toolbar_layout) # Initialize the Visualizer self._view = qt_view.QtView() self._adapter = rosprofiler_adapter.ROSProfileAdapter(self._view) self._adapter.set_topic_quiet_list(TOPIC_BLACKLIST) self._adapter.set_node_quiet_list(NODE_BLACKLIST) vbox.addWidget(self._view)
def __init__(self, masteruri, cfg, ns, nodes, parent=None): QFrame.__init__(self, parent) self._masteruri = masteruri self._nodes = {cfg: {ns: nodes}} frame_layout = QVBoxLayout(self) frame_layout.setContentsMargins(0, 0, 0, 0) # create frame for warning label self.warning_frame = warning_frame = QFrame(self) warning_layout = QHBoxLayout(warning_frame) warning_layout.setContentsMargins(0, 0, 0, 0) warning_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) self.warning_label = QLabel() icon = nm.settings().icon('crystal_clear_warning.png') self.warning_label.setPixmap(icon.pixmap(QSize(40, 40))) self.warning_label.setToolTip( 'Multiple configuration for same node found!\nA first one will be selected for the start a node!' ) warning_layout.addWidget(self.warning_label) warning_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) frame_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) frame_layout.addWidget(warning_frame) # create frame for start/stop buttons buttons_frame = QFrame() buttons_layout = QHBoxLayout(buttons_frame) buttons_layout.setContentsMargins(0, 0, 0, 0) buttons_layout.addItem(QSpacerItem(20, 20)) self.on_button = QPushButton() self.on_button.setFlat(False) self.on_button.setText("On") self.on_button.clicked.connect(self.on_on_clicked) buttons_layout.addWidget(self.on_button) self.off_button = QPushButton() self.off_button.setFlat(True) self.off_button.setText("Off") self.off_button.clicked.connect(self.on_off_clicked) buttons_layout.addWidget(self.off_button) buttons_layout.addItem(QSpacerItem(20, 20)) frame_layout.addWidget(buttons_frame) frame_layout.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) self.warning_frame.setVisible(False)
def __init__(self, updater, config, nodename): ''' :param config: :type config: Dictionary? defined in dynamic_reconfigure.client.Client :type nodename: str ''' #TODO figure out what data type 'config' is. It is afterall returned # from dynamic_reconfigure.client.get_parameter_descriptions() # ros.org/doc/api/dynamic_reconfigure/html/dynamic_reconfigure.client-pysrc.html#Client super(GroupWidget, self).__init__() self.state = config['state'] self.name = config['name'] self._toplevel_treenode_name = nodename # TODO: .ui file needs to be back into usage in later phase. # ui_file = os.path.join(rp.get_path('rqt_reconfigure'), # 'resource', 'singlenode_parameditor.ui') # loadUi(ui_file, self) verticalLayout = QVBoxLayout(self) verticalLayout.setContentsMargins(QMargins(0, 0, 0, 0)) _widget_nodeheader = QWidget() _h_layout_nodeheader = QHBoxLayout(_widget_nodeheader) _h_layout_nodeheader.setContentsMargins(QMargins(0, 0, 0, 0)) self.nodename_qlabel = QLabel(self) font = QFont('Trebuchet MS, Bold') font.setUnderline(True) font.setBold(True) # Button to close a node. _icon_disable_node = QIcon.fromTheme('window-close') _bt_disable_node = QPushButton(_icon_disable_node, '', self) _bt_disable_node.setToolTip('Hide this node') _bt_disable_node_size = QSize(36, 24) _bt_disable_node.setFixedSize(_bt_disable_node_size) _bt_disable_node.pressed.connect(self._node_disable_bt_clicked) _h_layout_nodeheader.addWidget(self.nodename_qlabel) _h_layout_nodeheader.addWidget(_bt_disable_node) self.nodename_qlabel.setAlignment(Qt.AlignCenter) font.setPointSize(10) self.nodename_qlabel.setFont(font) grid_widget = QWidget(self) self.grid = QFormLayout(grid_widget) verticalLayout.addWidget(_widget_nodeheader) verticalLayout.addWidget(grid_widget, 1) # Again, these UI operation above needs to happen in .ui file. self.tab_bar = None # Every group can have one tab bar self.tab_bar_shown = False self.updater = updater self.editor_widgets = [] self._param_names = [] self._create_node_widgets(config) rospy.logdebug('Groups node name={}'.format(nodename)) self.nodename_qlabel.setText(nodename)
def __init__(self, context, node=None): """ This class is intended to be called by rqt plugin framework class. Currently (12/12/2012) the whole widget is splitted into 2 panes: one on left allows you to choose the node(s) you work on. Right side pane lets you work with the parameters associated with the node(s) you select on the left. (12/27/2012) Despite the pkg name is changed to rqt_reconfigure to reflect the available functionality, file & class names remain 'param', expecting all the parameters will become handle-able. """ super(ParamWidget, self).__init__() self.setObjectName(self._TITLE_PLUGIN) self.setWindowTitle(self._TITLE_PLUGIN) rp = rospkg.RosPack() #TODO: .ui file needs to replace the GUI components declaration # below. For unknown reason, referring to another .ui files # from a .ui that is used in this class failed. So for now, # I decided not use .ui in this class. # If someone can tackle this I'd appreciate. _hlayout_top = QHBoxLayout(self) self._splitter = QSplitter(self) _hlayout_top.addWidget(self._splitter) _vlayout_nodesel_widget = QWidget() _vlayout_nodesel_side = QVBoxLayout() _hlayout_filter_widget = QWidget(self) _hlayout_filter = QHBoxLayout() self._text_filter = TextFilter() self.filter_lineedit = TextFilterWidget(self._text_filter, rp) self.filterkey_label = QLabel("&Filter key:") self.filterkey_label.setBuddy(self.filter_lineedit) _hlayout_filter.addWidget(self.filterkey_label) _hlayout_filter.addWidget(self.filter_lineedit) _hlayout_filter_widget.setLayout(_hlayout_filter) self._nodesel_widget = NodeSelectorWidget(self, rp, self.sig_sysmsg) _vlayout_nodesel_side.addWidget(_hlayout_filter_widget) _vlayout_nodesel_side.addWidget(self._nodesel_widget) _vlayout_nodesel_side.setSpacing(1) _vlayout_nodesel_widget.setLayout(_vlayout_nodesel_side) reconf_widget = ParameditWidget(rp) self._splitter.insertWidget(0, _vlayout_nodesel_widget) self._splitter.insertWidget(1, reconf_widget) # 1st column, _vlayout_nodesel_widget, to minimize width. # 2nd col to keep the possible max width. self._splitter.setStretchFactor(0, 0) self._splitter.setStretchFactor(1, 1) # Signal from paramedit widget to node selector widget. reconf_widget.sig_node_disabled_selected.connect( self._nodesel_widget.node_deselected) # Pass name of node to editor widget self._nodesel_widget.sig_node_selected.connect( reconf_widget.show_reconf) if not node: title = self._TITLE_PLUGIN else: title = self._TITLE_PLUGIN + ' %s' % node self.setObjectName(title) #Connect filter signal-slots. self._text_filter.filter_changed_signal.connect( self._filter_key_changed)
def _create_replace_frame(self): # create frame with replace row self.rplc_frame = rplc_frame = QFrame(self) rplc_hbox_layout = QHBoxLayout(rplc_frame) rplc_hbox_layout.setContentsMargins(0, 0, 0, 0) rplc_hbox_layout.setSpacing(1) self.replace_field = EnhancedLineEdit(rplc_frame) self.replace_field.setPlaceholderText('replace text') self.replace_field.returnPressed.connect(self.on_replace) rplc_hbox_layout.addWidget(self.replace_field) self.replace_result_label = QLabel(rplc_frame) self.replace_result_label.setText(' ') rplc_hbox_layout.addWidget(self.replace_result_label) self.replace_button = replace_button = QPushButton("> &Replace >") replace_button.setFixedWidth(90) replace_button.clicked.connect(self.on_replace_click) rplc_hbox_layout.addWidget(replace_button) rplc_frame.setVisible(False) return rplc_frame
def __init__(self, nodename, masteruri, loggername, level='INFO', parent=None): ''' Creates a new item. ''' QFrame.__init__(self, parent) self.setObjectName("LoggerItem") self.nodename = nodename self.masteruri = masteruri self.loggername = loggername self.current_level = None layout = QHBoxLayout(self) layout.setContentsMargins(1, 1, 1, 1) self.debug = QRadioButton() self.debug.setStyleSheet( "QRadioButton{ background-color: #39B54A;}") # QColor(57, 181, 74) self.debug.toggled.connect(self.toggled_debug) layout.addWidget(self.debug) self.info = QRadioButton() self.info.setStyleSheet("QRadioButton{ background-color: #FFFAFA;}") self.info.toggled.connect(self.toggled_info) layout.addWidget(self.info) self.warn = QRadioButton() self.warn.setStyleSheet( "QRadioButton{ background-color: #FFC706;}") # QColor(255, 199, 6) self.warn.toggled.connect(self.toggled_warn) layout.addWidget(self.warn) self.error = QRadioButton() self.error.setStyleSheet( "QRadioButton{ background-color: #DE382B;}") # QColor(222, 56, 43) self.error.toggled.connect(self.toggled_error) layout.addWidget(self.error) self.fatal = QRadioButton() self.fatal.setStyleSheet("QRadioButton{ background-color: #FF0000;}") self.fatal.toggled.connect(self.toggled_fatal) layout.addWidget(self.fatal) self.label = QLabel(loggername) layout.addWidget(self.label) layout.addStretch() self._callback = None # used to set all logger self.success_signal.connect(self.on_succes_update) self.error_signal.connect(self.on_error_update) self.set_level(level)
def __init__(self, items=list(), buttons=QDialogButtonBox.Cancel | QDialogButtonBox.Ok, exclusive=False, preselect_all=False, title='', description='', icon='', parent=None, select_if_single=True, checkitem1='', checkitem2='', closein=0, store_geometry=''): ''' Creates an input dialog. @param items: a list with strings @type items: C{list()} ''' QDialog.__init__(self, parent=parent) self.setObjectName(' - '.join(['SelectDialog', utf8(items)])) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(3, 3, 3, 3) # add filter row self.filter_field = EnhancedLineEdit(self) self.filter_field.setPlaceholderText("filter") self.filter_field.textChanged.connect(self._on_filter_changed) self.verticalLayout.addWidget(self.filter_field) if description: self.description_frame = QFrame(self) descriptionLayout = QHBoxLayout(self.description_frame) # descriptionLayout.setContentsMargins(1, 1, 1, 1) if icon: self.icon_label = QLabel(self.description_frame) self.icon_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.icon_label.setPixmap( QPixmap(icon).scaled(30, 30, Qt.KeepAspectRatio)) descriptionLayout.addWidget(self.icon_label) self.description_label = QLabel(self.description_frame) self.description_label.setWordWrap(True) self.description_label.setText(description) descriptionLayout.addWidget(self.description_label) self.verticalLayout.addWidget(self.description_frame) # create area for the parameter self.content = MainBox(self) if items: self.scroll_area = QScrollArea(self) self.scroll_area.setFocusPolicy(Qt.NoFocus) self.scroll_area.setObjectName("scroll_area") self.scroll_area.setWidgetResizable(True) self.scroll_area.setWidget(self.content) self.verticalLayout.addWidget(self.scroll_area) self.checkitem1 = checkitem1 self.checkitem1_result = False self.checkitem2 = checkitem2 self.checkitem2_result = False # add select all option if not exclusive and items: self._ignore_next_toggle = False self.select_all_checkbox = QCheckBox('all entries') self.select_all_checkbox.setTristate(True) self.select_all_checkbox.stateChanged.connect( self._on_select_all_checkbox_stateChanged) self.verticalLayout.addWidget(self.select_all_checkbox) self.content.toggled.connect(self._on_main_toggle) if self.checkitem1: self.checkitem1_checkbox = QCheckBox(self.checkitem1) self.checkitem1_checkbox.stateChanged.connect( self._on_select_checkitem1_checkbox_stateChanged) self.verticalLayout.addWidget(self.checkitem1_checkbox) if self.checkitem2: self.checkitem2_checkbox = QCheckBox(self.checkitem2) self.checkitem2_checkbox.stateChanged.connect( self._on_select_checkitem2_checkbox_stateChanged) self.verticalLayout.addWidget(self.checkitem2_checkbox) if not items: spacerItem = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) self._close_timer = None self._closein = closein - 1 if closein > 0: self.closein_label = QLabel("OK in %d sec..." % closein) self.closein_label.setAlignment(Qt.AlignRight) self.verticalLayout.addWidget(self.closein_label) self._close_timer = threading.Timer(1.0, self._on_close_timer) self._close_timer.start() # create buttons self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName("buttonBox") self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(buttons) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.verticalLayout.addWidget(self.buttonBox) # set the input fields if items: self.content.createFieldsFromValues(items, exclusive) if (select_if_single and len(items) == 1) or preselect_all: self.select_all_checkbox.setCheckState(Qt.Checked) if not items or len(items) < 7: self.filter_field.setVisible(False) # restore from configuration file self._geometry_name = store_geometry if store_geometry and nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup(store_geometry) self.resize(settings.value("size", QSize(480, 320))) pos = settings.value("pos", QPoint(0, 0)) if pos.x() != 0 and pos.y() != 0: self.move(pos) settings.endGroup()
def _create_add_rocon_master_dialog(self): # dialog connect_dlg = QDialog(self._widget_main) connect_dlg.setWindowTitle("Add Ros Master") connect_dlg.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) connect_dlg.setMinimumSize(350, 0) # dlg_rect = self._connect_dlg.geometry() # dialog layout ver_layout = QVBoxLayout(connect_dlg) ver_layout.setContentsMargins(9, 9, 9, 9) # param layout text_grid_sub_widget = QWidget() text_grid_layout = QGridLayout(text_grid_sub_widget) text_grid_layout.setColumnStretch(1, 0) text_grid_layout.setRowStretch(2, 0) # param 1 title_widget1 = QLabel("MASTER_URI: ") context_widget1 = QTextEdit() context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget1.setMinimumSize(0, 30) context_widget1.append(self.master_uri) # param 2 title_widget2 = QLabel("HOST_NAME: ") context_widget2 = QTextEdit() context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget2.setMinimumSize(0, 30) context_widget2.append(self.host_name) # add param text_grid_layout.addWidget(title_widget1) text_grid_layout.addWidget(context_widget1) text_grid_layout.addWidget(title_widget2) text_grid_layout.addWidget(context_widget2) # add param layout ver_layout.addWidget(text_grid_sub_widget) # button layout button_hor_sub_widget = QWidget() button_hor_layout = QHBoxLayout(button_hor_sub_widget) uri_text_widget = context_widget1 host_name_text_widget = context_widget2 # button btn_call = QPushButton("Add") btn_cancel = QPushButton("Cancel") btn_call.clicked.connect(lambda: connect_dlg.done(0)) btn_call.clicked.connect(lambda: self._add_rocon_master( uri_text_widget, host_name_text_widget)) btn_cancel.clicked.connect(lambda: connect_dlg.done(0)) # add button button_hor_layout.addWidget(btn_call) button_hor_layout.addWidget(btn_cancel) # add button layout ver_layout.addWidget(button_hor_sub_widget) return connect_dlg
class NavViewWidget(QWidget): def __init__(self, map_topic='/map', paths=['/move_base/NavFn/plan', '/move_base/TrajectoryPlannerROS/local_plan'], polygons=['/move_base/local_costmap/robot_footprint']): super(NavViewWidget, self).__init__() self._layout = QVBoxLayout() self._button_layout = QHBoxLayout() self.setAcceptDrops(True) self.setWindowTitle('Navigation Viewer') self.paths = paths self.polygons = polygons self.map = map_topic self._tf = tf.TransformListener() self._nav_view = NavView(map_topic, paths, polygons, tf = self._tf, parent = self) self._set_pose = QPushButton('Set Pose') self._set_pose.clicked.connect(self._nav_view.pose_mode) self._set_goal = QPushButton('Set Goal') self._set_goal.clicked.connect(self._nav_view.goal_mode) self._button_layout.addWidget(self._set_pose) self._button_layout.addWidget(self._set_goal) self._layout.addLayout(self._button_layout) self._layout.addWidget(self._nav_view) self.setLayout(self._layout) def dragEnterEvent(self, e): if not e.mimeData().hasText(): if not hasattr(e.source(), 'selectedItems') or len(e.source().selectedItems()) == 0: qWarning('NavView.dragEnterEvent(): not hasattr(event.source(), selectedItems) or len(event.source().selectedItems()) == 0') return item = e.source().selectedItems()[0] topic_name = item.data(0, Qt.UserRole) if topic_name == None: qWarning('NavView.dragEnterEvent(): not hasattr(item, ros_topic_name_)') return else: topic_name = str(e.mimeData().text()) if accepted_topic(topic_name): e.accept() e.acceptProposedAction() def dropEvent(self, e): if e.mimeData().hasText(): topic_name = str(e.mimeData().text()) else: droped_item = e.source().selectedItems()[0] topic_name = str(droped_item.data(0, Qt.UserRole)) topic_type, array = get_field_type(topic_name) if not array: if topic_type is OccupancyGrid: self.map = topic_name # Swap out the nav view for one with the new topics self._nav_view.close() self._nav_view = NavView(self.map, self.paths, self.polygons, self._tf, self) self._layout.addWidget(self._nav_view) elif topic_type is Path: self.paths.append(topic_name) self._nav_view.add_path(topic_name) elif topic_type is PolygonStamped: self.polygons.append(topic_name) self._nav_view.add_polygon(topic_name) def save_settings(self, plugin_settings, instance_settings): self._nav_view.save_settings(plugin_settings, instance_settings) def restore_settings(self, plugin_settings, instance_settings): self._nav_view.restore_settings(plugin_settings, instance_settings)
def __init__(self, items=list(), buttons=QDialogButtonBox.Cancel | QDialogButtonBox.Ok, exclusive=False, preselect_all=False, title='', description='', icon='', parent=None, select_if_single=True, checkitem1='', checkitem2=''): ''' Creates an input dialog. @param items: a list with strings @type items: C{list()} ''' QDialog.__init__(self, parent=parent) self.setObjectName(' - '.join(['SelectDialog', str(items)])) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) # add filter row self.filter_frame = QFrame(self) filterLayout = QHBoxLayout(self.filter_frame) filterLayout.setContentsMargins(1, 1, 1, 1) label = QLabel("Filter:", self.filter_frame) self.filter_field = EnchancedLineEdit(self.filter_frame) filterLayout.addWidget(label) filterLayout.addWidget(self.filter_field) self.filter_field.textChanged.connect(self._on_filter_changed) self.verticalLayout.addWidget(self.filter_frame) if description: self.description_frame = QFrame(self) descriptionLayout = QHBoxLayout(self.description_frame) # descriptionLayout.setContentsMargins(1, 1, 1, 1) if icon: self.icon_label = QLabel(self.description_frame) self.icon_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.icon_label.setPixmap(QPixmap(icon).scaled(30, 30, Qt.KeepAspectRatio)) descriptionLayout.addWidget(self.icon_label) self.description_label = QLabel(self.description_frame) self.description_label.setWordWrap(True) self.description_label.setText(description) descriptionLayout.addWidget(self.description_label) self.verticalLayout.addWidget(self.description_frame) # create area for the parameter self.content = MainBox(self) if items: self.scroll_area = QScrollArea(self) self.scroll_area.setFocusPolicy(Qt.NoFocus) self.scroll_area.setObjectName("scroll_area") self.scroll_area.setWidgetResizable(True) self.scroll_area.setWidget(self.content) self.verticalLayout.addWidget(self.scroll_area) self.checkitem1 = checkitem1 self.checkitem1_result = False self.checkitem2 = checkitem2 self.checkitem2_result = False # add select all option if not exclusive and items: self._ignore_next_toggle = False self.select_all_checkbox = QCheckBox('all entries') self.select_all_checkbox.setTristate(True) self.select_all_checkbox.stateChanged.connect(self._on_select_all_checkbox_stateChanged) self.verticalLayout.addWidget(self.select_all_checkbox) self.content.toggled.connect(self._on_main_toggle) if self.checkitem1: self.checkitem1_checkbox = QCheckBox(self.checkitem1) self.checkitem1_checkbox.stateChanged.connect(self._on_select_checkitem1_checkbox_stateChanged) self.verticalLayout.addWidget(self.checkitem1_checkbox) if self.checkitem2: self.checkitem2_checkbox = QCheckBox(self.checkitem2) self.checkitem2_checkbox.stateChanged.connect(self._on_select_checkitem2_checkbox_stateChanged) self.verticalLayout.addWidget(self.checkitem2_checkbox) if not items: spacerItem = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Expanding) self.verticalLayout.addItem(spacerItem) # create buttons self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName("buttonBox") self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setStandardButtons(buttons) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.verticalLayout.addWidget(self.buttonBox) # set the input fields if items: self.content.createFieldsFromValues(items, exclusive) if (select_if_single and len(items) == 1) or preselect_all: self.select_all_checkbox.setCheckState(Qt.Checked) if not items or len(items) < 7: self.filter_frame.setVisible(False)
def __init__(self, icon, title, text, detailed_text="", buttons=Cancel | Ok, parent=None): QDialog.__init__(self, parent=parent) self.setWindowFlags(self.windowFlags() & ~Qt.WindowTitleHint) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint & ~Qt.WindowMinimizeButtonHint) self.setObjectName('MessageBox') self._use_checkbox = True self.text = text self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setContentsMargins(1, 1, 1, 1) # create icon pixmap = None style_option = QStyleOption() if icon == self.NoIcon: pass elif icon == self.Question: pixmap = QApplication.style().standardPixmap( QStyle.SP_MessageBoxQuestion, style_option) elif icon == self.Information: pixmap = QApplication.style().standardPixmap( QStyle.SP_MessageBoxInformation, style_option) elif icon == self.Warning: pixmap = QPixmap(":icons/crystal_clear_warning_56.png") elif icon == self.Critical: pixmap = QApplication.style().standardPixmap( QStyle.SP_MessageBoxCritical, style_option) spacerItem = QSpacerItem(10, 60, QSizePolicy.Minimum, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.icon_label = QLabel() if pixmap is not None: self.icon_label.setPixmap(pixmap) self.icon_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.horizontalLayout.addWidget(self.icon_label) spacerItem = QSpacerItem(10, 60, QSizePolicy.Minimum, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add message self.message_label = QLabel(text) self.message_label.setWordWrap(True) self.message_label.setScaledContents(True) self.message_label.setOpenExternalLinks(True) self.horizontalLayout.addWidget(self.message_label) self.verticalLayout.addLayout(self.horizontalLayout) # create buttons self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName("buttonBox") self.buttonBox.setOrientation(Qt.Horizontal) self._accept_button = None self._reject_button = None self._buttons = buttons self._create_buttons(buttons) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.verticalLayout.addWidget(self.buttonBox) if detailed_text: self.btn_show_details = QPushButton(self.tr('Details...')) self.btn_show_details.setCheckable(True) self.btn_show_details.setChecked(True) self.btn_show_details.toggled.connect(self.on_toggled_details) self.buttonBox.addButton(self.btn_show_details, QDialogButtonBox.ActionRole) # create area for detailed text self.textEdit = textEdit = QTextEdit(self) textEdit.setObjectName("textEdit") textEdit.setReadOnly(True) textEdit.setText(detailed_text) # textEdit.setVisible(False) self.verticalLayout.addWidget(self.textEdit) self.resize(480, self.verticalLayout.totalSizeHint().height()) buttons_in_box = self.buttonBox.buttons() if buttons_in_box: self.buttonBox.buttons()[0].setFocus()
class CalibrationMovementsGUI(QWidget): NOT_INITED_YET = 0 BAD_PLAN = 1 GOOD_PLAN = 2 MOVED_TO_POSE = 3 BAD_STARTING_POSITION = 4 GOOD_STARTING_POSITION = 5 def __init__(self): super(CalibrationMovementsGUI, self).__init__() move_group_name = rospy.get_param('~move_group', 'manipulator') self.angle_delta = math.radians( rospy.get_param('~rotation_delta_degrees', 25)) self.translation_delta = rospy.get_param('~translation_delta_meters', 0.1) max_velocity_scaling = rospy.get_param('~max_velocity_scaling', 0.5) max_acceleration_scaling = rospy.get_param('~max_acceleration_scaling', 0.5) self.local_mover = CalibrationMovements(move_group_name, max_velocity_scaling, max_acceleration_scaling) self.current_pose = -1 self.current_plan = None self.initUI() self.state = CalibrationMovementsGUI.NOT_INITED_YET def initUI(self): self.layout = QVBoxLayout() self.labels_layout = QHBoxLayout() self.buttons_layout = QHBoxLayout() self.progress_bar = QProgressBar() self.pose_number_lbl = QLabel('0/8') self.bad_plan_lbl = QLabel('No plan yet') self.guide_lbl = QLabel('Hello') self.check_start_pose_btn = QPushButton('Check starting pose') self.check_start_pose_btn.clicked.connect( self.handle_check_current_state) self.next_pose_btn = QPushButton('Next Pose') self.next_pose_btn.clicked.connect(self.handle_next_pose) self.plan_btn = QPushButton('Plan') self.plan_btn.clicked.connect(self.handle_plan) self.execute_btn = QPushButton('Execute') self.execute_btn.clicked.connect(self.handle_execute) self.labels_layout.addWidget(self.pose_number_lbl) self.labels_layout.addWidget(self.bad_plan_lbl) self.buttons_layout.addWidget(self.check_start_pose_btn) self.buttons_layout.addWidget(self.next_pose_btn) self.buttons_layout.addWidget(self.plan_btn) self.buttons_layout.addWidget(self.execute_btn) self.layout.addWidget(self.progress_bar) self.layout.addLayout(self.labels_layout) self.layout.addWidget(self.guide_lbl) self.layout.addLayout(self.buttons_layout) self.setLayout(self.layout) self.plan_btn.setEnabled(False) self.execute_btn.setEnabled(False) self.setWindowTitle('Local Mover') self.show() def updateUI(self): self.progress_bar.setMaximum(len(self.local_mover.poses)) self.progress_bar.setValue(self.current_pose + 1) self.pose_number_lbl.setText('{}/{}'.format( self.current_pose + 1, len(self.local_mover.poses))) if self.state == CalibrationMovementsGUI.BAD_PLAN: self.bad_plan_lbl.setText('BAD plan!! Don\'t do it!!!!') self.bad_plan_lbl.setStyleSheet('QLabel { background-color : red}') elif self.state == CalibrationMovementsGUI.GOOD_PLAN: self.bad_plan_lbl.setText('Good plan') self.bad_plan_lbl.setStyleSheet( 'QLabel { background-color : green}') else: self.bad_plan_lbl.setText('No plan yet') self.bad_plan_lbl.setStyleSheet('') if self.state == CalibrationMovementsGUI.NOT_INITED_YET: self.guide_lbl.setText( 'Bring the robot to a plausible position and check if it is a suitable starting pose' ) elif self.state == CalibrationMovementsGUI.BAD_STARTING_POSITION: self.guide_lbl.setText('Cannot calibrate from current position') elif self.state == CalibrationMovementsGUI.GOOD_STARTING_POSITION: self.guide_lbl.setText('Ready to start: click on next pose') elif self.state == CalibrationMovementsGUI.GOOD_PLAN: self.guide_lbl.setText( 'The plan seems good: press execute to move the robot') elif self.state == CalibrationMovementsGUI.BAD_PLAN: self.guide_lbl.setText('Planning failed: try again') elif self.state == CalibrationMovementsGUI.MOVED_TO_POSE: self.guide_lbl.setText( 'Pose reached: take a sample and go on to next pose') can_plan = self.state == CalibrationMovementsGUI.GOOD_STARTING_POSITION self.plan_btn.setEnabled(can_plan) can_move = self.state == CalibrationMovementsGUI.GOOD_PLAN self.execute_btn.setEnabled(can_move) def handle_check_current_state(self): self.local_mover.compute_poses_around_current_state( self.angle_delta, self.translation_delta) joint_limits = [math.radians(90)] * 5 + [math.radians(180)] + [ math.radians(350) ] # TODO: make param if self.local_mover.check_poses(joint_limits): self.state = CalibrationMovementsGUI.GOOD_STARTING_POSITION else: self.state = CalibrationMovementsGUI.BAD_STARTING_POSITION self.current_pose = -1 self.updateUI() def handle_next_pose(self): self.guide_lbl.setText('Going to center position') if self.current_pose != -1: plan = self.local_mover.plan_to_start_pose() if plan is None: self.guide_lbl.setText( 'Failed planning to center position: try again') else: self.local_mover.execute_plan(plan) if self.current_pose < len(self.local_mover.poses) - 1: self.current_pose += 1 self.state = CalibrationMovementsGUI.GOOD_STARTING_POSITION self.updateUI() def handle_plan(self): self.guide_lbl.setText( 'Planning to the next position. Click on execute when a good one was found' ) if self.current_pose >= 0: self.current_plan = self.local_mover.plan_to_pose( self.local_mover.poses[self.current_pose]) if CalibrationMovements.is_crazy_plan( self.current_plan, self.local_mover.fallback_joint_limits ): #TODO: sort out this limits story self.state = CalibrationMovementsGUI.BAD_PLAN else: self.state = CalibrationMovementsGUI.GOOD_PLAN self.updateUI() def handle_execute(self): if self.current_plan is not None: self.guide_lbl.setText('Going to the selected pose') self.local_mover.execute_plan(self.current_plan) self.state = CalibrationMovementsGUI.MOVED_TO_POSE self.updateUI()
class Editor(QMainWindow): ''' Creates a dialog to edit a launch file. ''' finished_signal = Signal(list) ''' finished_signal has as parameter the filenames of the initialization and is emitted, if this dialog was closed. ''' def __init__(self, filenames, search_text='', master_name='', parent=None): ''' :param filenames: a list with filenames. The last one will be activated. :type filenames: [str] :param str search_text: if not empty, searches in new document for first occurrence of the given text ''' QMainWindow.__init__(self, parent) self.setObjectName('Editor - %s' % utf8(filenames)) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.mIcon = nm.settings().icon('crystal_clear_edit_launch.png') self._error_icon = nm.settings().icon('warning.png') self._info_icon = nm.settings().icon('info.png') self._empty_icon = QIcon() self.setWindowIcon(self.mIcon) window_title = "ROSLaunch Editor" if filenames: window_title = self.__getTabName(filenames[0]) self.setWindowTitle('%s @%s' % (window_title, master_name)) self.init_filenames = filenames self._search_node_count = 0 self._search_thread = None self._last_search_request = None # list with all open files self.files = [] # create tabs for files self.main_widget = QWidget(self) self.main_widget.setObjectName("editorMain") self.verticalLayout = QVBoxLayout(self.main_widget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(1) self.verticalLayout.setObjectName("verticalLayout") self.tabWidget = EditorTabWidget(self) self.tabWidget.setTabPosition(QTabWidget.North) self.tabWidget.setDocumentMode(True) self.tabWidget.setTabsClosable(True) self.tabWidget.setMovable(False) self.tabWidget.setObjectName("tabWidget") self.tabWidget.tabCloseRequested.connect(self.on_close_tab) self.tabWidget.currentChanged.connect(self.on_tab_changed) self.verticalLayout.addWidget(self.tabWidget) self.log_dock = self._create_log_bar() self.addDockWidget(Qt.BottomDockWidgetArea, self.log_dock) # self.verticalLayout.addWidget(self.log_bar) self.buttons = self._create_buttons() self.verticalLayout.addWidget(self.buttons) self.setCentralWidget(self.main_widget) self.find_dialog = TextSearchFrame(self.tabWidget, self) self.find_dialog.found_signal.connect(self.on_search_result) self.find_dialog.replace_signal.connect(self.on_replace) self.addDockWidget(Qt.RightDockWidgetArea, self.find_dialog) self.graph_view = GraphViewWidget(self.tabWidget, self) self.graph_view.load_signal.connect(self.on_graph_load_file) self.graph_view.goto_signal.connect(self.on_graph_goto) self.graph_view.search_signal.connect(self.on_load_request) self.graph_view.finished_signal.connect(self.on_graph_finished) self.graph_view.info_signal.connect(self.on_graph_info) self.addDockWidget(Qt.RightDockWidgetArea, self.graph_view) self.readSettings() self.find_dialog.setVisible(False) self.graph_view.setVisible(False) nm.nmd().file.changed_file.connect(self.on_changed_file) nm.nmd().file.packages_available.connect(self._on_new_packages) # open the files for f in filenames: if f: self.on_load_request(f, search_text, only_launch=True) self.log_dock.setVisible(False) try: pal = self.tabWidget.palette() self._default_color = pal.color(QPalette.Window) color = QColor.fromRgb(nm.settings().host_color(master_name, self._default_color.rgb())) bg_style_launch_dock = "QWidget#editorMain { background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 %s, stop: 0.7 %s);}" % (color.name(), self._default_color.name()) self.setStyleSheet('%s' % (bg_style_launch_dock)) except Exception as _: pass # import traceback # print(traceback.format_exc()) # def __del__(self): # print "******** destroy", self.objectName() def _create_buttons(self): # create the buttons line self.buttons = QWidget(self) self.horizontalLayout = QHBoxLayout(self.buttons) self.horizontalLayout.setContentsMargins(3, 0, 3, 0) self.horizontalLayout.setObjectName("horizontalLayout") # add open upper launchfile button self.upperButton = QPushButton(self) self.upperButton.setObjectName("upperButton") self.upperButton.clicked.connect(self.on_upperButton_clicked) self.upperButton.setIcon(nm.settings().icon('up.png')) self.upperButton.setShortcut("Ctrl+U") self.upperButton.setToolTip('Open the file which include the current file (Ctrl+U)') self.upperButton.setFlat(True) self.horizontalLayout.addWidget(self.upperButton) # add the goto button self.gotoButton = QPushButton(self) self.gotoButton.setObjectName("gotoButton") self.gotoButton.clicked.connect(self.on_shortcut_goto) self.gotoButton.setText(self._translate("&Goto line")) self.gotoButton.setShortcut("Ctrl+G") self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') self.gotoButton.setFlat(True) self.horizontalLayout.addWidget(self.gotoButton) # add a tag button self.tagButton = self._create_tag_button(self) self.horizontalLayout.addWidget(self.tagButton) # add save button self.saveButton = QPushButton(self) self.saveButton.setObjectName("saveButton") self.saveButton.setIcon(QIcon.fromTheme("document-save")) self.saveButton.clicked.connect(self.on_saveButton_clicked) self.saveButton.setText(self._translate("&Save")) self.saveButton.setShortcut("Ctrl+S") self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') self.saveButton.setFlat(True) self.horizontalLayout.addWidget(self.saveButton) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add line number label self.pos_label = QLabel() self.horizontalLayout.addWidget(self.pos_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add show log button self.show_log_button = QPushButton("Log>>", self) self.show_log_button.setObjectName("show_log_button") self.show_log_button.clicked.connect(self.on_toggled_log) self.show_log_button.setFlat(True) self.show_log_button.setCheckable(True) self.horizontalLayout.addWidget(self.show_log_button) # add graph button self.graphButton = QPushButton(self) self.graphButton.setObjectName("graphButton") self.graphButton.toggled.connect(self.on_toggled_graph) self.graphButton.setText("Includ&e Graph >>") self.graphButton.setCheckable(True) self.graphButton.setShortcut("Ctrl+E") self.graphButton.setToolTip('Shows include and include from files (Ctrl+E)') self.graphButton.setFlat(True) self.horizontalLayout.addWidget(self.graphButton) # add the search button self.searchButton = QPushButton(self) self.searchButton.setObjectName("searchButton") # self.searchButton.clicked.connect(self.on_shortcut_find) self.searchButton.toggled.connect(self.on_toggled_find) self.searchButton.setText(self._translate("&Find >>")) self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') self.searchButton.setFlat(True) self.searchButton.setCheckable(True) self.horizontalLayout.addWidget(self.searchButton) # add the replace button self.replaceButton = QPushButton(self) self.replaceButton.setObjectName("replaceButton") # self.replaceButton.clicked.connect(self.on_shortcut_replace) self.replaceButton.toggled.connect(self.on_toggled_replace) self.replaceButton.setText(self._translate("&Replace >>")) self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') self.replaceButton.setFlat(True) self.replaceButton.setCheckable(True) self.horizontalLayout.addWidget(self.replaceButton) return self.buttons def _create_log_bar(self): self.log_dock = QDockWidget(self) self.log_dock.setObjectName('LogFrame') self.log_dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.log_bar = QWidget(self) self.horizontal_layout_log_bar = QHBoxLayout(self.log_bar) self.horizontal_layout_log_bar.setContentsMargins(2, 0, 2, 0) self.horizontal_layout_log_bar.setObjectName("horizontal_layout_log_bar") # add info label self._log_warning_count = 0 self.log_browser = QTextEdit() self.log_browser.setObjectName("log_browser") self.log_browser.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.log_browser.setLineWrapMode(QTextEdit.NoWrap) # self.log_browser.setMaximumHeight(120) color = QColor(255, 255, 235) bg_style = "QTextEdit#log_browser { background-color: %s;}" % color.name() self.log_bar.setStyleSheet("%s" % (bg_style)) self.horizontal_layout_log_bar.addWidget(self.log_browser) # add hide button self.clear_log_button = QPushButton("clear", self) self.clear_log_button.setObjectName("clear_log_button") self.clear_log_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.clear_log_button.clicked.connect(self.on_clear_log_button_clicked) self.clear_log_button.setFlat(True) self.horizontal_layout_log_bar.addWidget(self.clear_log_button) self.log_dock.setWidget(self.log_bar) return self.log_dock def keyPressEvent(self, event): ''' Enable the shortcats for search and replace ''' if event.key() == Qt.Key_Escape: self.reject() elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_F: if self.tabWidget.currentWidget().hasFocus(): if not self.searchButton.isChecked(): self.searchButton.setChecked(True) else: self.on_toggled_find(True) else: self.searchButton.setChecked(not self.searchButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R: if self.tabWidget.currentWidget().hasFocus(): if not self.replaceButton.isChecked(): self.replaceButton.setChecked(True) else: self.on_toggled_replace(True) else: self.replaceButton.setChecked(not self.replaceButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_E: if self.tabWidget.currentWidget().hasFocus(): if not self.graphButton.isChecked(): self.graphButton.setChecked(True) else: self.on_toggled_graph(True) else: self.graphButton.setChecked(not self.graphButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_W: self.on_close_tab(self.tabWidget.currentIndex()) elif event.modifiers() in [Qt.ControlModifier, Qt.AltModifier] and event.key() == Qt.Key_Up: self.on_upperButton_clicked() elif event.modifiers() in [Qt.ControlModifier, Qt.AltModifier] and event.key() == Qt.Key_Down: self.on_downButton_clicked() else: event.accept() QMainWindow.keyPressEvent(self, event) def _translate(self, text): if hasattr(QApplication, "UnicodeUTF8"): return QApplication.translate("Editor", text, None, QApplication.UnicodeUTF8) else: return QApplication.translate("Editor", text, None) def readSettings(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") maximized = settings.value("maximized", 'false') == 'true' if maximized: self.showMaximized() else: self.resize(settings.value("size", QSize(800, 640))) self.move(settings.value("pos", QPoint(0, 0))) try: self.restoreState(settings.value("window_state")) except Exception: import traceback print(traceback.format_exc()) settings.endGroup() def storeSetting(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.setValue("maximized", self.isMaximized()) settings.setValue("window_state", self.saveState()) settings.endGroup() def on_load_request(self, filename, search_text='', insert_index=-1, goto_line=-1, only_launch=False, count_results=0): ''' Loads a file in a new tab or focus the tab, if the file is already open. :param str filename: the path to file :param str search_text: if not empty, searches in new document for first occurrence of the given text ''' if not filename: return self.tabWidget.setUpdatesEnabled(False) try: if filename not in self.files: tab_name = self.__getTabName(filename) editor = TextEdit(filename, parent=self) linenumber_editor = LineNumberWidget(editor) tab_index = 0 if insert_index > -1: tab_index = self.tabWidget.insertTab(insert_index, linenumber_editor, tab_name) else: tab_index = self.tabWidget.addTab(linenumber_editor, tab_name) self.files.append(filename) editor.setCurrentPath(os.path.basename(filename)) editor.load_request_signal.connect(self.on_load_request) editor.document().modificationChanged.connect(self.on_editor_modificationChanged) editor.cursorPositionChanged.connect(self.on_editor_positionChanged) editor.setFocus(Qt.OtherFocusReason) # editor.textChanged.connect(self.on_text_changed) editor.undoAvailable.connect(self.on_text_changed) self.tabWidget.setCurrentIndex(tab_index) # self.find_dialog.set_search_path(filename) else: for i in range(self.tabWidget.count()): if self.tabWidget.widget(i).filename == filename: self.tabWidget.setCurrentIndex(i) break self.tabWidget.setUpdatesEnabled(True) if search_text: if only_launch: self.find_dialog.found_files_list.clear() try: self._search_thread.stop() self._search_thread = None except Exception: pass # TODO: put all text of all tabs into path_text rospy.logdebug("serach for '%s'" % search_text) self._search_node_count = 0 self._search_thread = TextSearchThread(search_text, filename, recursive=True, only_launch=only_launch, count_results=count_results) self._search_thread.search_result_signal.connect(self.on_search_result_on_open) self._search_thread.warning_signal.connect(self.on_search_result_warning) self._last_search_request = (filename, search_text, insert_index, goto_line, only_launch) if not self.graph_view.is_loading(): self.on_graph_info("search thread: start search for '%s'" % self._search_thread._search_text) self._search_thread.start() if goto_line != -1: self._goto(goto_line, True) self.upperButton.setEnabled(self.tabWidget.count() > 1) except Exception as err: self.tabWidget.setUpdatesEnabled(True) import traceback msg = "Error while open %s: %s" % (filename, traceback.format_exc()) rospy.logwarn(msg) MessageBox.critical(self, "Error", utf8(err), msg) if self.tabWidget.count() == 0: self.close() def on_graph_load_file(self, path, insert_after=True): insert_index = self.tabWidget.currentIndex() + 1 if not insert_after and insert_index > 1: insert_index = self.tabWidget.currentIndex() self.on_load_request(path, insert_index=insert_index) def on_graph_goto(self, path, linenr): if path == self.tabWidget.currentWidget().filename: if linenr != -1: self._goto(linenr, True) def on_graph_finished(self): self.on_graph_info("build tree: finished", False) if self.graph_view.has_warnings: self.graphButton.setIcon(self._info_icon) else: self.graphButton.setIcon(self._empty_icon) if self._search_thread: try: self._search_thread.find_args_not_set = True self._search_thread.start() self.on_graph_info("search thread: start search for '%s'" % self._search_thread._search_text) except Exception: pass def on_graph_info(self, msg, warning=False): text_color = "#000000" if warning: self._log_warning_count += 1 if self._log_warning_count == 1: self.show_log_button.setIcon(self._error_icon) text_color = "#FE9A2E" text = ('<pre style="padding:10px;"><dt><font color="%s">' '%s</font></dt></pre>' % (text_color, msg)) self.log_browser.append(text) def on_text_changed(self, value=""): if self.tabWidget.currentWidget().hasFocus(): self.find_dialog.file_changed(self.tabWidget.currentWidget().filename) self._last_search_request = None def on_tab_changed(self, index): if index > -1: self.graph_view.set_file(self.tabWidget.widget(index).filename, self.tabWidget.widget(0).filename) self._last_search_request = None def on_close_tab(self, tab_index): ''' Signal handling to close single tabs. :param int tab_index: tab index to close ''' try: doremove = True w = self.tabWidget.widget(tab_index) if w.document().isModified(): name = self.__getTabName(w.filename) result = MessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", name])) if result == MessageBox.Yes: self.tabWidget.currentWidget().save() elif result == MessageBox.No: pass elif rospy.is_shutdown(): doremove = False if doremove: # remove the indexed files if w.filename in self.files: self.files.remove(w.filename) # close tab self.tabWidget.removeTab(tab_index) # close editor, if no tabs are open if not self.tabWidget.count(): self.close() self._last_search_request = None except Exception: import traceback rospy.logwarn("Error while close tab %s: %s", str(tab_index), traceback.format_exc(1)) self.upperButton.setEnabled(self.tabWidget.count() > 1) def reject(self): if self.find_dialog.isVisible(): self.searchButton.setChecked(not self.searchButton.isChecked()) else: self.close() def on_changed_file(self, grpc_path, mtime): if grpc_path in self.files: for i in range(self.tabWidget.count()): if self.tabWidget.widget(i).filename == grpc_path: self.tabWidget.widget(i).file_changed(mtime) break if self._last_search_request is not None: self.on_load_request(*self._last_search_request) def closeEvent(self, event): ''' Test the open files for changes and save this if needed. ''' changed = [] # get the names of all changed files for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i) if w.document().isModified(): changed.append(self.__getTabName(w.filename)) if changed: # ask the user for save changes if self.isHidden(): buttons = MessageBox.Yes | MessageBox.No else: buttons = MessageBox.Yes | MessageBox.No | MessageBox.Cancel result = MessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", '\n'.join(changed)]), buttons=buttons) if result == MessageBox.Yes: for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i).save() self.graph_view.clear_cache() event.accept() elif result == MessageBox.No: event.accept() elif rospy.is_shutdown(): event.ignore() else: event.accept() if event.isAccepted(): self.storeSetting() nm.nmd().file.changed_file.disconnect(self.on_changed_file) nm.nmd().file.packages_available.connect(self._on_new_packages) self.finished_signal.emit(self.init_filenames) def on_editor_modificationChanged(self, value=None): ''' If the content was changed, a '*' will be shown in the tab name. ''' tab_name = self.__getTabName(self.tabWidget.currentWidget().filename) if (self.tabWidget.currentWidget().document().isModified()): tab_name = '*%s' % tab_name self.tabWidget.setTabText(self.tabWidget.currentIndex(), tab_name) def on_editor_positionChanged(self): ''' Shows the number of the line and column in a label. ''' cursor = self.tabWidget.currentWidget().textCursor() self.pos_label.setText(':%s:%s #%s' % (cursor.blockNumber() + 1, cursor.columnNumber(), cursor.position())) def __getTabName(self, lfile): base = os.path.basename(lfile).replace('.launch', '') (package, _) = package_name(os.path.dirname(lfile)) return '%s [%s]' % (base, package) def _on_new_packages(self, url): try: if nmdurl.nmduri_from_path(url) == nmdurl.nmduri_from_path(self.tabWidget.currentWidget().filename): rospy.logdebug("packages updated, rebuild graph") if self.graph_view.has_none_packages: self.graph_view.clear_cache() except Exception: import traceback print(traceback.format_exc()) ############################################################################## # HANDLER for buttons ############################################################################## def on_clear_log_button_clicked(self): self._log_warning_count = 0 self.show_log_button.setIcon(self._empty_icon) self.log_browser.clear() self.log_dock.setVisible(False) self.show_log_button.setChecked(False) self.tabWidget.currentWidget().setFocus() def on_upperButton_clicked(self): ''' Opens the file which include the current open file ''' if self.tabWidget.currentIndex() != 0: self.graph_view.find_parent_file() def on_downButton_clicked(self): ''' Select editor right from current. ''' if self.tabWidget.currentIndex() < self.tabWidget.count(): self.tabWidget.setCurrentIndex(self.tabWidget.currentIndex() + 1) def on_saveButton_clicked(self): ''' Saves the current document. This method is called if the C{save button} was clicked. ''' saved, errors, msg = self.tabWidget.currentWidget().save() if errors: if msg: rospy.logwarn(msg) MessageBox.critical(self, "Error", "Error while save file: %s" % os.path.basename(self.tabWidget.currentWidget().filename), detailed_text=msg) self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._error_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), msg) self.on_graph_info("saved failed %s: %s" % (self.tabWidget.currentWidget().filename, msg), True) elif saved: self.on_graph_info("saved %s" % self.tabWidget.currentWidget().filename) self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._empty_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), '') self.graph_view.clear_cache() def on_shortcut_find(self): pass def on_toggled_log(self, value): ''' Shows the log bar ''' if value: self.log_dock.setVisible(True) else: self.log_dock.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_graph(self, value): ''' Shows the search frame ''' if value: self.graph_view.enable() else: # self.replaceButton.setChecked(False) self.graph_view.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_find(self, value, only_results=False): ''' Shows the search frame ''' if value: self.find_dialog.enable() self.find_dialog.find_frame.setVisible(not only_results) self.find_dialog.recursive_search_box.setVisible(not only_results) if not only_results: # clear results if not search text exists and we show not only search results if not self.find_dialog.search_field.text(): self.find_dialog.found_files_list.clear() else: self.replaceButton.setChecked(False) self.find_dialog.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_replace(self, value): ''' Shows the replace lineedit in the search frame ''' if value: self.searchButton.setChecked(True) self.find_dialog.set_replace_visible(value) def on_shortcut_goto(self): ''' Opens a C{goto} dialog. ''' value = 1 ok = False try: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, minValue=1, step=1) except Exception: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, min=1, step=1) if ok: self._goto(value) self.tabWidget.currentWidget().setFocus(Qt.ActiveWindowFocusReason) def _goto(self, linenr, select_line=True): if linenr > self.tabWidget.currentWidget().document().blockCount(): linenr = self.tabWidget.currentWidget().document().blockCount() curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 while curpos != linenr: mov = QTextCursor.NextBlock if curpos < linenr else QTextCursor.PreviousBlock self.tabWidget.currentWidget().moveCursor(mov) curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 self.tabWidget.currentWidget().moveCursor(QTextCursor.EndOfBlock) self.tabWidget.currentWidget().moveCursor(QTextCursor.StartOfBlock, QTextCursor.KeepAnchor) ############################################################################## # SLOTS for search dialog ############################################################################## def on_search_result(self, search_text, found, path, startpos, endpos, linenr=-1, line_text=''): ''' A slot to handle a found text. It goes to the position in the text and select the searched text. On new file it will be open. :param search_text: the searched text :type search_text: str :param found: the text was found or not :type found: bool :param path: the path of the file the text was found :type path: str :param startpos: the position in the text :type startpos: int :param endpos: the end position in the text :type endpos: int ''' if found: if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) focus_widget.setFocus() self.tabWidget.currentWidget().select(startpos, endpos, False) def on_search_result_on_open(self, search_text, found, path, startpos, endpos, linenr, line_text): ''' Like on_search_result, but skips the text in comments. ''' if found: self._search_node_count += 1 if self._search_node_count > 1: self.on_toggled_find(True, only_results=True) self.find_dialog.current_search_text = search_text self.find_dialog.on_search_result(search_text, found, path, startpos, endpos, linenr, line_text) self.on_graph_info("search thread: found %s in '%s:%d'" % (search_text, path, linenr)) if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) if focus_widget is not None: focus_widget.setFocus() self.tabWidget.currentWidget().select(startpos, endpos, True) # self.on_search_result(search_text, found, path, startpos, endpos, linenr, line_text) def on_search_result_warning(self, msg): self.on_graph_info("search thread: %s" % (msg), True) def on_replace(self, search_text, path, index, replaced_text): ''' A slot to handle a text replacement of the TextSearchFrame. :param search_text: the searched text :type search_text: str :param path: the path of the file the text was found :type path: str :param index: the position in the text :type index: int :param replaced_text: the new text :type replaced_text: str ''' cursor = self.tabWidget.currentWidget().textCursor() if cursor.selectedText() == search_text: cursor.insertText(replaced_text) ############################################################################## # LAUNCH TAG insertion ############################################################################## def _show_custom_parameter_dialog(self): methods = {'nm/associations': self._on_add_cp_associations, 'capability_group': self._on_add_cp_capability_group, 'nm/kill_on_stop': self._on_add_cp_kill_on_stop, 'autostart/delay': self._on_add_cp_as_delay, 'autostart/exclude': self._on_add_cp_as_exclude, 'autostart/required_publisher': self._on_add_cp_as_req_publisher, 'respawn/max': self._on_add_cp_r_max, 'respawn/min_runtime': self._on_add_cp_r_min_runtime, 'respawn/delay': self._on_add_cp_r_delay, } res = SelectDialog.getValue('Insert custom parameter', "Select parameter to insert:", sorted(methods.keys()), exclusive=True, parent=self, select_if_single=False, store_geometry='insert_param') tags2insert = res[0] for tag in tags2insert: methods[tag]() def _create_tag_button(self, parent=None): btn = QPushButton(parent) btn.setObjectName("tagButton") btn.setText(self._translate("Add &tag")) # btn.setShortcut("Ctrl+T") btn.setToolTip('Adds a ROS launch tag to launch file') btn.setMenu(self._create_tag_menu(btn)) btn.setFlat(True) return btn def _create_tag_menu(self, parent=None): # creates a tag menu tag_menu = QMenu("ROS Tags", parent) # group tag add_group_tag_action = QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) add_group_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+g")) tag_menu.addAction(add_group_tag_action) # node tag add_node_tag_action = QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) add_node_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+n")) tag_menu.addAction(add_node_tag_action) # node tag with all attributes add_node_tag_all_action = QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) tag_menu.addAction(add_node_tag_all_action) # include tag with all attributes add_include_tag_all_action = QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) add_include_tag_all_action.setShortcuts(QKeySequence("Ctrl+Shift+i")) tag_menu.addAction(add_include_tag_all_action) # remap add_remap_tag_action = QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) add_remap_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+r")) tag_menu.addAction(add_remap_tag_action) # env tag add_env_tag_action = QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) tag_menu.addAction(add_env_tag_action) # param tag add_param_clipboard_tag_action = QAction("<param value>", self, statusTip="add value from clipboard", triggered=self._on_add_param_clipboard_tag) add_param_clipboard_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+p")) tag_menu.addAction(add_param_clipboard_tag_action) add_param_tag_action = QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) add_param_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+Alt+p")) tag_menu.addAction(add_param_tag_action) # param tag with all attributes add_param_tag_all_action = QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) tag_menu.addAction(add_param_tag_all_action) # rosparam tag with all attributes add_rosparam_tag_all_action = QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) tag_menu.addAction(add_rosparam_tag_all_action) # arg tag with default definition add_arg_tag_default_action = QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) add_arg_tag_default_action.setShortcuts(QKeySequence("Ctrl+Shift+a")) tag_menu.addAction(add_arg_tag_default_action) # arg tag with value definition add_arg_tag_value_action = QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) add_arg_tag_value_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) tag_menu.addAction(add_arg_tag_value_action) # test tag add_test_tag_action = QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) add_test_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+t")) tag_menu.addAction(add_test_tag_action) # test tag with all attributes add_test_tag_all_action = QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) tag_menu.addAction(add_test_tag_all_action) sub_cp_menu = QMenu("Custom parameters", parent) show_cp_dialog_action = QAction("Show Dialog", self, statusTip="", triggered=self._show_custom_parameter_dialog) show_cp_dialog_action.setShortcuts(QKeySequence("Ctrl+Shift+d")) sub_cp_menu.addAction(show_cp_dialog_action) add_cp_associations_action = QAction("nm/associations", self, statusTip="", triggered=self._on_add_cp_associations) add_cp_associations_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) sub_cp_menu.addAction(add_cp_associations_action) sub_cp_as_menu = QMenu("Autostart", parent) add_cp_as_delay_action = QAction("delay", self, statusTip="", triggered=self._on_add_cp_as_delay) sub_cp_as_menu.addAction(add_cp_as_delay_action) add_cp_as_exclude_action = QAction("exclude", self, statusTip="", triggered=self._on_add_cp_as_exclude) sub_cp_as_menu.addAction(add_cp_as_exclude_action) add_cp_as_req_publisher_action = QAction("required publisher", self, statusTip="", triggered=self._on_add_cp_as_req_publisher) sub_cp_as_menu.addAction(add_cp_as_req_publisher_action) sub_cp_menu.addMenu(sub_cp_as_menu) sub_cp_r_menu = QMenu("Respawn", parent) add_cp_r_max_action = QAction("max", self, statusTip="", triggered=self._on_add_cp_r_max) sub_cp_r_menu.addAction(add_cp_r_max_action) add_cp_r_min_runtime_action = QAction("min_runtime", self, statusTip="", triggered=self._on_add_cp_r_min_runtime) sub_cp_r_menu.addAction(add_cp_r_min_runtime_action) add_cp_r_delay_action = QAction("delay", self, statusTip="", triggered=self._on_add_cp_r_delay) sub_cp_r_menu.addAction(add_cp_r_delay_action) sub_cp_menu.addMenu(sub_cp_r_menu) add_cp_capability_group_action = QAction("capability_group", self, statusTip="", triggered=self._on_add_cp_capability_group) add_cp_capability_group_action.setShortcuts(QKeySequence("Ctrl+Alt+p")) sub_cp_menu.addAction(add_cp_capability_group_action) add_cp_kill_on_stop_action = QAction("nm/kill_on_stop", self, statusTip="True or time to wait in ms", triggered=self._on_add_cp_kill_on_stop) add_cp_kill_on_stop_action.setShortcuts(QKeySequence("Ctrl+Shift+k")) sub_cp_menu.addAction(add_cp_kill_on_stop_action) tag_menu.addMenu(sub_cp_menu) return tag_menu def _insert_text(self, text, cursor_pose=None, selection_len=None): if self.tabWidget.currentWidget().isReadOnly(): return cursor = self.tabWidget.currentWidget().textCursor() if not cursor.isNull(): cursor.beginEditBlock() col = cursor.columnNumber() spaces = ''.join([' ' for _ in range(col)]) curr_cursor_pos = cursor.position() cursor.insertText(text.replace('\n', '\n%s' % spaces)) if cursor_pose is not None: cursor.setPosition(curr_cursor_pos + cursor_pose, QTextCursor.MoveAnchor) if selection_len is not None: cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, selection_len) cursor.endEditBlock() self.tabWidget.currentWidget().setTextCursor(cursor) self.tabWidget.currentWidget().setFocus(Qt.OtherFocusReason) def _on_add_group_tag(self): self._insert_text('<group ns="namespace" clear_params="true|false">\n' '</group>', 11, 9) def _get_package_dialog(self): muri = masteruri_from_ros() if self.init_filenames: muri = nmdurl.masteruri(self.init_filenames[0]) return PackageDialog(muri) def _on_add_node_tag(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_node_tag_all(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s"\n' ' args="arg1" machine="machine_name"\n' ' respawn="true" required="true"\n' ' ns="foo" clear_params="true|false"\n' ' output="log|screen" cwd="ROS_HOME|node"\n' ' launch-prefix="prefix arguments">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_include_tag_all(self): self._insert_text('<include file="$(find pkg-name)/path/filename.xml"\n' ' ns="foo" clear_params="true|false">\n' '</include>', 22, 27) def _on_add_remap_tag(self): self._insert_text('<remap from="original" to="new"/>', 13, 8) def _on_add_env_tag(self): self._insert_text('<env name="variable" value="value"/>', 11, 8) def _on_add_param_clipboard_tag(self): lines = QApplication.clipboard().mimeData().text().splitlines() name = "" if len(lines) == 1: name = lines[0] self._insert_text('<param name="%s" value="value" />' % name, 22 + len(name), 5) def _on_add_param_tag(self): self._insert_text('<param name="name" value="value" />', 13, 4) def _on_add_param_tag_all(self): self._insert_text('<param name="name" value="value"\n' ' type="str|int|double|bool"\n' ' textfile="$(find pkg-name)/path/file.txt"\n' ' binfile="$(find pkg-name)/path/file"\n' ' command="$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'">\n' '</param>', 13, 4) def _on_add_rosparam_tag_all(self): self._insert_text('<rosparam param="name"\n' ' file="$(find pkg-name)/path/foo.yaml"\n' ' command="load|dump|delete"\n' ' ns="namespace"\n' ' subst_value="true|false">\n' '</rosparam>', 17, 4) def _on_add_arg_tag_default(self): self._insert_text('<arg name="foo" default="1" />', 11, 3) def _on_add_arg_tag_value(self): self._insert_text('<arg name="foo" value="bar" />', 11, 3) def _on_add_test_tag(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary)) def _on_add_test_tag_all(self): dia = self._get_package_dialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' ' args="arg1" time-limit="60.0"\n' ' ns="foo" clear_params="true|false"\n' ' cwd="ROS_HOME|node" retry="0"\n' ' launch-prefix="prefix arguments">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary)) def _on_add_cp_capability_group(self): self._insert_text('<param name="capability_group" value="demo" />', 38, 4) def _on_add_cp_kill_on_stop(self): self._insert_text('<param name="nm/kill_on_stop" value="100" hint="[ms]" />', 34, 3) def _on_add_cp_associations(self): self._insert_text('<param name="nm/associations" value="node1,node2" hint="list of nodes" />', 34, 11) def _on_add_cp_as_delay(self): self._insert_text('<param name="autostart/delay" value="1" hint="[seconds]" />', 37, 1) def _on_add_cp_as_exclude(self): self._insert_text('<param name="autostart/exclude" value="True" />', 39, 4) def _on_add_cp_as_req_publisher(self): self._insert_text('<param name="autostart/required/publisher" value="topic" />', 50, 5) def _on_add_cp_r_max(self): self._insert_text('<param name="respawn/max" value="10" />', 33, 2) def _on_add_cp_r_min_runtime(self): self._insert_text('<param name="respawn/min_runtime" value="10" hint="[seconds]" />', 41, 2) def _on_add_cp_r_delay(self): self._insert_text('<param name="respawn/delay" value="5" hint="[seconds]" />', 31, 2)
class LogicalMarkerPlugin(Plugin): def __init__(self, context): super(LogicalMarkerPlugin, self).__init__(context) # Create an image for the original map. This will never change. try: self.map_yaml_file_str = rospy.get_param("~map_file") self.data_directory = rospy.get_param("~data_directory") except KeyError: rospy.logfatal("~map_file and ~data_directory need to be set to use the logical marker") return map_image_location = getImageFileLocation(self.map_yaml_file_str) map = loadMapFromFile(self.map_yaml_file_str) locations_file = getLocationsFileLocationFromDataDirectory(self.data_directory) connectivity_file = getConnectivityFileLocationFromDataDirectory(self.data_directory) doors_file = getDoorsFileLocationFromDataDirectory(self.data_directory) objects_file = getObjectsFileLocationFromDataDirectory(self.data_directory) # Give QObjects reasonable names self.setObjectName('LogicalMarkerPlugin') # Create QWidget self.master_widget = QWidget() self.master_layout = QVBoxLayout(self.master_widget) # Main Functions - Doors, Locations, Objects self.function_layout = QHBoxLayout() self.master_layout.addLayout(self.function_layout) self.function_buttons = [] self.current_function = None for button_text in ['Locations', 'Doors', 'Objects']: button = QPushButton(button_text, self.master_widget) button.clicked[bool].connect(self.handle_function_button) button.setCheckable(True) self.function_layout.addWidget(button) self.function_buttons.append(button) self.function_layout.addStretch(1) self.master_layout.addWidget(self.get_horizontal_line()) # Subfunction toolbar self.subfunction_layout = QHBoxLayout() clearLayoutAndFixHeight(self.subfunction_layout) self.master_layout.addLayout(self.subfunction_layout) self.current_subfunction = None self.master_layout.addWidget(self.get_horizontal_line()) self.image = MapImage(map_image_location, self.master_widget) self.master_layout.addWidget(self.image) self.master_layout.addWidget(self.get_horizontal_line()) # Configuration toolbar self.configuration_layout = QHBoxLayout() clearLayoutAndFixHeight(self.configuration_layout) self.master_layout.addLayout(self.configuration_layout) # Add a stretch at the bottom. self.master_layout.addStretch(1) self.master_widget.setObjectName('LogicalMarkerPluginUI') if context.serial_number() > 1: self.master_widget.setWindowTitle(self.master_widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.master_widget) # Activate the functions self.functions = {} self.functions['Locations'] = LocationFunction(locations_file, connectivity_file, map, self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Doors'] = DoorFunction(doors_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Objects'] = ObjectFunction(objects_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) def construct_layout(self): pass def get_horizontal_line(self): """ http://stackoverflow.com/questions/5671354/how-to-programmatically-make-a-horizontal-line-in-qt """ hline = QFrame() hline.setFrameShape(QFrame.HLine) hline.setFrameShadow(QFrame.Sunken) return hline def handle_function_button(self): source = self.sender() if source.text() == self.current_function: source.setChecked(True) return # Depress all other buttons. for button in self.function_buttons: if button != source: button.setChecked(False) if self.current_function is not None: self.functions[self.current_function].deactivateFunction() self.current_function = source.text() # Clear all subfunction buttons. clearLayoutAndFixHeight(self.subfunction_layout) if self.current_function is not None: self.functions[self.current_function].activateFunction() def shutdown_plugin(self): modified = False for function in self.functions: if self.functions[function].isModified(): modified = True if modified: ret = QMessageBox.warning(self.master_widget, "Save", "The logical map has been modified.\n" "Do you want to save your changes?", QMessageBox.Save | QMessageBox.Discard) if ret == QMessageBox.Save: for function in self.functions: self.functions[function].saveConfiguration() def save_settings(self, plugin_settings, instance_settings): pass def restore_settings(self, plugin_settings, instance_settings): pass
class RoomDialogPlugin(Plugin): def __init__(self, context): super(RoomDialogPlugin, self).__init__(context) # Give QObjects reasonable names self.setObjectName('RoomDialogPlugin') font_size = rospy.get_param("~font_size", 30) # Create QWidget self._widget = QWidget() self._widget.setFont(QFont("Times", font_size, QFont.Bold)) self._layout = QVBoxLayout(self._widget) self._text_browser = QTextBrowser(self._widget) self._layout.addWidget(self._text_browser) self._button_layout = QGridLayout() self._layout.addLayout(self._button_layout) # rospy.loginfo("Hello world") # Add combobox self._cb_layout = QHBoxLayout() self._cb = QComboBox() self._layout.addLayout(self._cb_layout) #layout = QVBoxLayout(self._widget) #layout.addWidget(self._button) self._widget.setObjectName('RoomDialogPluginUI') if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self._widget) # Setup service provider self.service = rospy.Service('room_dialog', RoomDialog, self.service_callback) self.response_ready = False self.response = None self.buttons = [] self.text_label = None self.text_input = None # Add combo options self.connect(self._widget, SIGNAL("update"), self.update) self.connect(self._widget, SIGNAL("timeout"), self.timeout) def shutdown_plugin(self): self.service.shutdown() def service_callback(self, req): self.response_ready = False self.request = req self._widget.emit(SIGNAL("update")) # Start timer against wall clock here instead of the ros clock. start_time = time.time() while not self.response_ready: if self.request != req: # The request got preempted by a new request. return RoomDialogResponse(RoomDialogRequest.PREEMPTED, "") if req.timeout != RoomDialogRequest.NO_TIMEOUT: current_time = time.time() if current_time - start_time > req.timeout: self._widget.emit(SIGNAL("timeout")) return RoomDialogResponse(RoomDialogRequest.TIMED_OUT, "") time.sleep(0.2) return self.response def update(self): self.clean() req = self.request self._text_browser.setText(req.message) if req.type == RoomDialogRequest.DISPLAY: # All done, nothing more too see here. self.response = RoomDialogResponse(RoomDialogRequest.NO_RESPONSE, "") self.response_ready = True elif req.type == RoomDialogRequest.CHOICE_QUESTION: for index, options in enumerate(req.options): button = QPushButton(options, self._widget) button.clicked.connect(partial(self.handle_button, index)) row = index / 3 col = index % 3 self._button_layout.addWidget(button, row, col) self.buttons.append(button) elif req.type == RoomDialogRequest.TEXT_QUESTION: self.text_label = QLabel("Enter here: ", self._widget) self._button_layout.addWidget(self.text_label, 0, 0) self.text_input = QLineEdit(self._widget) self.text_input.editingFinished.connect(self.handle_text) self._button_layout.addWidget(self.text_input, 0, 1) # add handling of combobox elif req.type == RoomDialogRequest.COMBOBOX_QUESTION: # self.clean() rospy.loginfo("Combobox selected") #self._cb.duplicatesEnabled = False if self._cb.count() == 0: for index, options in enumerate(req.options): self._cb.addItem(options) rospy.loginfo(options) #self.buttons.append(options) # NOTE COULD INTRODUCE BUG self._cb.currentIndexChanged.connect(self.handle_cb) self._cb_layout.addWidget(self._cb) def timeout(self): self._text_browser.setText("Oh no! The request timed out.") self.clean() def clean(self): while self._button_layout.count(): item = self._button_layout.takeAt(0) item.widget().deleteLater() # while self._cb_layout.count(): # item = self._cb_layout.takeAt(0) # item.widget().deleteLater() self.buttons = [] self.text_input = None self.text_label = None def handle_button(self, index): self.response = RoomDialogResponse(index, "") self.clean() self.response_ready = True def handle_text(self): self.response = RoomDialogResponse(RoomDialogRequest.TEXT_RESPONSE, self.text_input.text()) self.clean() self.response_ready = True def handle_cb(self, index): # This will be the sign format seen around building ex: 3.404 rospy.loginfo("handling cb") roomHuman = self._cb.currentText() # modify string into robot format ex: d3_404 splitHuman = roomHuman.split('.', 1) roomRobot = 'd' + splitHuman[0] + '_' + splitHuman[1] roomRobot = str(roomRobot) self.response = RoomDialogResponse(RoomDialogRequest.CB_RESPONSE, roomRobot) self.clean() self.response_ready = True def save_settings(self, plugin_settings, instance_settings): # TODO save intrinsic configuration, usually using: # instance_settings.set_value(k, v) pass def restore_settings(self, plugin_settings, instance_settings): # TODO restore intrinsic configuration, usually using: # v = instance_settings.value(k) pass
def _create_find_frame(self): find_frame = QFrame(self) find_hbox_layout = QHBoxLayout(find_frame) find_hbox_layout.setContentsMargins(0, 0, 0, 0) find_hbox_layout.setSpacing(1) self.search_field = EnhancedLineEdit(find_frame) self.search_field.setPlaceholderText('search text') self.search_field.textChanged.connect(self.on_search_text_changed) self.search_field.returnPressed.connect(self.on_search) find_hbox_layout.addWidget(self.search_field) self.search_result_label = QLabel(find_frame) self.search_result_label.setText(' ') find_hbox_layout.addWidget(self.search_result_label) self.find_button_back = QPushButton("<") self.find_button_back.setFixedWidth(44) self.find_button_back.clicked.connect(self.on_search_back) find_hbox_layout.addWidget(self.find_button_back) self.find_button = QPushButton(">") self.find_button.setDefault(True) # self.find_button.setFlat(True) self.find_button.setFixedWidth(44) self.find_button.clicked.connect(self.on_search) find_hbox_layout.addWidget(self.find_button) return find_frame
def _create_buttons(self): # create the buttons line self.buttons = QWidget(self) self.horizontalLayout = QHBoxLayout(self.buttons) self.horizontalLayout.setContentsMargins(4, 0, 4, 0) self.horizontalLayout.setObjectName("horizontalLayout") # add open upper launchfile button self.upperButton = QPushButton(self) self.upperButton.setObjectName("upperButton") self.upperButton.clicked.connect(self.on_upperButton_clicked) self.upperButton.setIcon(QIcon(":/icons/up.png")) self.upperButton.setShortcut("Ctrl+U") self.upperButton.setToolTip( 'Open the file which include the current file (Ctrl+U)') self.upperButton.setFlat(True) self.horizontalLayout.addWidget(self.upperButton) # add the goto button self.gotoButton = QPushButton(self) self.gotoButton.setObjectName("gotoButton") self.gotoButton.clicked.connect(self.on_shortcut_goto) self.gotoButton.setText(self._translate("&Goto line")) self.gotoButton.setShortcut("Ctrl+G") self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') self.gotoButton.setFlat(True) self.horizontalLayout.addWidget(self.gotoButton) # add a tag button self.tagButton = self._create_tag_button(self) self.horizontalLayout.addWidget(self.tagButton) # add save button self.saveButton = QPushButton(self) self.saveButton.setObjectName("saveButton") self.saveButton.setIcon(QIcon.fromTheme("document-save")) self.saveButton.clicked.connect(self.on_saveButton_clicked) self.saveButton.setText(self._translate("&Save")) self.saveButton.setShortcut("Ctrl+S") self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') self.saveButton.setFlat(True) self.horizontalLayout.addWidget(self.saveButton) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add line number label self.pos_label = QLabel() self.horizontalLayout.addWidget(self.pos_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add graph button self.graphButton = QPushButton(self) self.graphButton.setObjectName("graphButton") self.graphButton.toggled.connect(self.on_toggled_graph) self.graphButton.setText("Includ&e Graph >>") self.graphButton.setCheckable(True) # self.graphButton.setIcon(QIcon(":/icons/button_graph.png")) self.graphButton.setShortcut("Ctrl+E") self.graphButton.setToolTip( 'Shows include and include from files (Ctrl+E)') self.graphButton.setFlat(True) self.horizontalLayout.addWidget(self.graphButton) # add the search button self.searchButton = QPushButton(self) self.searchButton.setObjectName("searchButton") # self.searchButton.clicked.connect(self.on_shortcut_find) self.searchButton.toggled.connect(self.on_toggled_find) self.searchButton.setText(self._translate("&Find >>")) self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') self.searchButton.setFlat(True) self.searchButton.setCheckable(True) self.horizontalLayout.addWidget(self.searchButton) # add the replace button self.replaceButton = QPushButton(self) self.replaceButton.setObjectName("replaceButton") # self.replaceButton.clicked.connect(self.on_shortcut_replace) self.replaceButton.toggled.connect(self.on_toggled_replace) self.replaceButton.setText(self._translate("&Replace >>")) self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') self.replaceButton.setFlat(True) self.replaceButton.setCheckable(True) self.horizontalLayout.addWidget(self.replaceButton) return self.buttons
def _set_add_rocon_master(self): if self._connect_dlg_isValid: console.logdebug("Dialog is live!!") self._connect_dlg.done(0) #dialog self._connect_dlg = QDialog(self._widget_main) self._connect_dlg.setWindowTitle("Add Ros Master") self._connect_dlg.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self._connect_dlg.setMinimumSize(350, 0) # dlg_rect = self._connect_dlg.geometry() #dialog layout ver_layout = QVBoxLayout(self._connect_dlg) ver_layout.setContentsMargins(9, 9, 9, 9) #param layout text_grid_sub_widget = QWidget() text_grid_layout = QGridLayout(text_grid_sub_widget) text_grid_layout.setColumnStretch(1, 0) text_grid_layout.setRowStretch(2, 0) #param 1 title_widget1 = QLabel("MASTER_URI: ") context_widget1 = QTextEdit() context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget1.setMinimumSize(0, 30) context_widget1.append(self.master_uri) #param 2 title_widget2 = QLabel("HOST_NAME: ") context_widget2 = QTextEdit() context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget2.setMinimumSize(0, 30) context_widget2.append(self.host_name) #add param text_grid_layout.addWidget(title_widget1) text_grid_layout.addWidget(context_widget1) text_grid_layout.addWidget(title_widget2) text_grid_layout.addWidget(context_widget2) #add param layout ver_layout.addWidget(text_grid_sub_widget) #button layout button_hor_sub_widget = QWidget() button_hor_layout = QHBoxLayout(button_hor_sub_widget) uri_text_widget = context_widget1 host_name_text_widget = context_widget2 #check box use_env_var_check = QCheckBox("Use environment variables") use_env_var_check.setCheckState(Qt.Unchecked) def set_use_env_var(data, text_widget1, text_widget2): if data == Qt.Unchecked: text_widget1.setText(self.master_uri) text_widget2.setText(self.host_name) elif data == Qt.Checked: self.master_uri = str(text_widget1.toPlainText()) self.host_name = str(text_widget2.toPlainText()) text_widget1.setText(self.env_master_uri) text_widget2.setText(self.env_host_name) def check_event(data): set_use_env_var(data, context_widget1, context_widget2) use_env_var_check.stateChanged.connect(check_event) ver_layout.addWidget(use_env_var_check) #button btn_call = QPushButton("Add") btn_cancel = QPushButton("Cancel") btn_call.clicked.connect(lambda: self._connect_dlg.done(0)) btn_call.clicked.connect(lambda: self._add_rocon_master( uri_text_widget, host_name_text_widget)) btn_cancel.clicked.connect(lambda: self._connect_dlg.done(0)) #add button button_hor_layout.addWidget(btn_call) button_hor_layout.addWidget(btn_cancel) #add button layout ver_layout.addWidget(button_hor_sub_widget) self._connect_dlg.setVisible(True) self._connect_dlg.finished.connect(self._destroy_connect_dlg) self._connect_dlg_isValid = True
def _setting_service(self): if self.is_setting_dlg_live: print "Dialog is live!!" self._setting_dlg.done(0) #dialog self._setting_dlg = QDialog(self._widget) self._setting_dlg.setWindowTitle("Seting Configuration") self._setting_dlg.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self._setting_dlg.setMinimumSize(500, 0) dlg_rect = self._setting_dlg.geometry() #dialog layout ver_layout = QVBoxLayout(self._setting_dlg) ver_layout.setContentsMargins(9, 9, 9, 9) #param layout text_grid_sub_widget = QWidget() text_grid_layout = QGridLayout(text_grid_sub_widget) text_grid_layout.setColumnStretch(1, 0) text_grid_layout.setRowStretch(2, 0) #param 1 name = u"" title_widget1 = QLabel("Param1: ") context_widget1 = QTextEdit() context_widget1.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget1.setMinimumSize(0, 30) context_widget1.append("") #param 2 cancel = False title_widget2 = QLabel("Param2: ") context_widget2 = QTextEdit() context_widget2.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget2.setMinimumSize(0, 30) context_widget2.append("") #param 3 cancel = False title_widget3 = QLabel("Param2: ") context_widget3 = QTextEdit() context_widget3.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) context_widget3.setMinimumSize(0, 30) context_widget3.append("") #add param text_grid_layout.addWidget(title_widget1) text_grid_layout.addWidget(context_widget1) text_grid_layout.addWidget(title_widget2) text_grid_layout.addWidget(context_widget2) text_grid_layout.addWidget(title_widget3) text_grid_layout.addWidget(context_widget3) #add param layout ver_layout.addWidget(text_grid_sub_widget) #button layout button_hor_sub_widget = QWidget() button_hor_layout = QHBoxLayout(button_hor_sub_widget) params = {} params['param1'] = context_widget1 params['param2'] = context_widget2 params['param3'] = context_widget3 #button btn_call = QPushButton("Set") btn_cancel = QPushButton("Cancel") btn_call.clicked.connect(lambda: self._setting_dlg.done(0)) btn_call.clicked.connect(lambda: self._set_configuration(params)) btn_cancel.clicked.connect(lambda: self._setting_dlg.done(0)) #add button button_hor_layout.addWidget(btn_call) button_hor_layout.addWidget(btn_cancel) #add button layout ver_layout.addWidget(button_hor_sub_widget) self._setting_dlg.setVisible(True) self._setting_dlg.finished.connect(self._destroy_setting_dlg) self.is_setting_dlg_live = True pass
def __init__(self, context): super(PatternGeneratorWidget, self).__init__() # publisher self.pattern_generator_params_pub = rospy.Publisher( 'pattern_generator/set_params', PatternGeneratorParameters, queue_size=1) # start widget widget = context # start upper part hbox = QHBoxLayout() # start left column left_vbox = QVBoxLayout() # start button start_command = QPushButton("Start") left_vbox.addWidget(start_command) # simulation checkbox self.simulation_mode_checkbox = QCheckBox() self.simulation_mode_checkbox.setText("Simulation Mode") self.simulation_mode_checkbox.setChecked(False) left_vbox.addWidget(self.simulation_mode_checkbox) # realtime checkbox self.realtime_mode_checkbox = QCheckBox() self.realtime_mode_checkbox.setText("Realtime Mode") self.realtime_mode_checkbox.setChecked(False) left_vbox.addWidget(self.realtime_mode_checkbox) # joystick checkbox self.joystick_mode_checkbox = QCheckBox() self.joystick_mode_checkbox.setText("Joystick Mode") self.joystick_mode_checkbox.setChecked(False) left_vbox.addWidget(self.joystick_mode_checkbox) # ignore invalid steps checkbox self.ignore_invalid_steps_checkbox = QCheckBox() self.ignore_invalid_steps_checkbox.setText("Ignore Invalid Steps") self.ignore_invalid_steps_checkbox.setChecked(False) left_vbox.addWidget(self.ignore_invalid_steps_checkbox) # foot seperation self.foot_seperation = generate_q_double_spin_box( 0.2, 0.15, 0.3, 2, 0.01) self.foot_seperation.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.foot_seperation, "Foot Seperation (m):") # delta x self.delta_x = generate_q_double_spin_box(0.0, -0.4, 0.4, 2, 0.01) self.delta_x.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.delta_x, "dX (m):") # delta y self.delta_y = generate_q_double_spin_box(0.0, -2.2, 2.2, 2, 0.01) self.delta_y.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.delta_y, "dY (m):") # delta yaw self.delta_yaw = generate_q_double_spin_box(0.0, -30.0, 30.0, 0, 1.0) self.delta_yaw.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.delta_yaw, "dYaw (deg):") # roll self.roll = generate_q_double_spin_box(0.0, -30.0, 30.0, 0, 1.0) self.roll.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.roll, "Roll (deg):") # pitch self.pitch = generate_q_double_spin_box(0.0, -30.0, 30.0, 0, 1.0) self.pitch.valueChanged.connect(self.callback_spin_box) add_widget_with_frame(left_vbox, self.pitch, "Pitch (deg):") # end left column left_vbox.addStretch() hbox.addLayout(left_vbox, 1) # start right column right_vbox = QVBoxLayout() # stop button stop_command = QPushButton("Stop") right_vbox.addWidget(stop_command) # ignore collision self.collision_checkbox = QCheckBox() self.collision_checkbox.setText("Ignore Collision") self.collision_checkbox.setChecked(True) right_vbox.addWidget(self.collision_checkbox) # override 3D self.override_checkbox = QCheckBox() self.override_checkbox.setText("Override 3D") self.override_checkbox.setChecked(False) right_vbox.addWidget(self.override_checkbox) # end right coloumn right_vbox.addStretch() hbox.addLayout(right_vbox, 1) # add upper part hbox.setMargin(0) vbox = QVBoxLayout() vbox.addLayout(hbox) # parameter set selection self.parameter_set_widget = QParameterSetWidget() add_widget_with_frame(vbox, self.parameter_set_widget, "Parameter Set:") # end widget widget.setLayout(vbox) #context.add_widget(widget) # signal connections start_command.clicked.connect(self.start_command_callback) stop_command.clicked.connect(self.stop_command_callback) self.joystick_mode_checkbox.clicked.connect( self.joystick_mode_check_callback) self.ignore_invalid_steps_checkbox.clicked.connect( self._publish_parameters)
class Editor(QMainWindow): ''' Creates a dialog to edit a launch file. ''' finished_signal = Signal(list) ''' finished_signal has as parameter the filenames of the initialization and is emitted, if this dialog was closed. ''' def __init__(self, filenames, search_text='', parent=None): ''' @param filenames: a list with filenames. The last one will be activated. @type filenames: C{[str, ...]} @param search_text: if not empty, searches in new document for first occurrence of the given text @type search_text: C{str} (Default: C{Empty String}) ''' QMainWindow.__init__(self, parent) self.setObjectName(' - '.join(['Editor', str(filenames)])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.mIcon = QIcon(":/icons/crystal_clear_edit_launch.png") self._error_icon = QIcon(":/icons/crystal_clear_warning.png") self._empty_icon = QIcon() self.setWindowIcon(self.mIcon) window_title = "ROSLaunch Editor" if filenames: window_title = self.__getTabName(filenames[0]) self.setWindowTitle(window_title) self.init_filenames = list(filenames) self._search_thread = None # list with all open files self.files = [] # create tabs for files self.main_widget = QWidget(self) self.verticalLayout = QVBoxLayout(self.main_widget) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(1) self.verticalLayout.setObjectName("verticalLayout") self.tabWidget = EditorTabWidget(self) self.tabWidget.setTabPosition(QTabWidget.North) self.tabWidget.setDocumentMode(True) self.tabWidget.setTabsClosable(True) self.tabWidget.setMovable(False) self.tabWidget.setObjectName("tabWidget") self.tabWidget.tabCloseRequested.connect(self.on_close_tab) self.verticalLayout.addWidget(self.tabWidget) self.buttons = self._create_buttons() self.verticalLayout.addWidget(self.buttons) self.setCentralWidget(self.main_widget) self.find_dialog = TextSearchFrame(self.tabWidget, self) self.find_dialog.search_result_signal.connect(self.on_search_result) self.find_dialog.replace_signal.connect(self.on_replace) self.addDockWidget(Qt.RightDockWidgetArea, self.find_dialog) # open the files for f in filenames: if f: self.on_load_request(os.path.normpath(f), search_text) self.readSettings() self.find_dialog.setVisible(False) # def __del__(self): # print "******** destroy", self.objectName() def _create_buttons(self): # create the buttons line self.buttons = QWidget(self) self.horizontalLayout = QHBoxLayout(self.buttons) self.horizontalLayout.setContentsMargins(4, 0, 4, 0) self.horizontalLayout.setObjectName("horizontalLayout") # add the search button self.searchButton = QPushButton(self) self.searchButton.setObjectName("searchButton") # self.searchButton.clicked.connect(self.on_shortcut_find) self.searchButton.toggled.connect(self.on_toggled_find) self.searchButton.setText(self._translate("&Find")) self.searchButton.setToolTip('Open a search dialog (Ctrl+F)') self.searchButton.setFlat(True) self.searchButton.setCheckable(True) self.horizontalLayout.addWidget(self.searchButton) # add the replace button self.replaceButton = QPushButton(self) self.replaceButton.setObjectName("replaceButton") # self.replaceButton.clicked.connect(self.on_shortcut_replace) self.replaceButton.toggled.connect(self.on_toggled_replace) self.replaceButton.setText(self._translate("&Replace")) self.replaceButton.setToolTip('Open a search&replace dialog (Ctrl+R)') self.replaceButton.setFlat(True) self.replaceButton.setCheckable(True) self.horizontalLayout.addWidget(self.replaceButton) # add the goto button self.gotoButton = QPushButton(self) self.gotoButton.setObjectName("gotoButton") self.gotoButton.clicked.connect(self.on_shortcut_goto) self.gotoButton.setText(self._translate("&Goto line")) self.gotoButton.setShortcut("Ctrl+G") self.gotoButton.setToolTip('Open a goto dialog (Ctrl+G)') self.gotoButton.setFlat(True) self.horizontalLayout.addWidget(self.gotoButton) # add a tag button self.tagButton = self._create_tag_button(self) self.horizontalLayout.addWidget(self.tagButton) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add line number label self.pos_label = QLabel() self.horizontalLayout.addWidget(self.pos_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add save button self.saveButton = QPushButton(self) self.saveButton.setObjectName("saveButton") self.saveButton.clicked.connect(self.on_saveButton_clicked) self.saveButton.setText(self._translate("&Save")) self.saveButton.setShortcut("Ctrl+S") self.saveButton.setToolTip('Save the changes to the file (Ctrl+S)') self.saveButton.setFlat(True) self.horizontalLayout.addWidget(self.saveButton) return self.buttons def keyPressEvent(self, event): ''' Enable the shortcats for search and replace ''' if event.key() == Qt.Key_Escape: self.reject() elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_F: if self.tabWidget.currentWidget().hasFocus(): if not self.searchButton.isChecked(): self.searchButton.setChecked(True) else: self.on_toggled_find(True) else: self.searchButton.setChecked(not self.searchButton.isChecked()) elif event.modifiers() == Qt.ControlModifier and event.key() == Qt.Key_R: if self.tabWidget.currentWidget().hasFocus(): if not self.replaceButton.isChecked(): self.replaceButton.setChecked(True) else: self.on_toggled_replace(True) else: self.replaceButton.setChecked(not self.replaceButton.isChecked()) else: event.accept() QMainWindow.keyPressEvent(self, event) def _translate(self, text): if hasattr(QApplication, "UnicodeUTF8"): return QApplication.translate("Editor", text, None, QApplication.UnicodeUTF8) else: return QApplication.translate("Editor", text, None) def readSettings(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") maximized = settings.value("maximized", 'false') == 'true' if maximized: self.showMaximized() else: self.resize(settings.value("size", QSize(800, 640))) self.move(settings.value("pos", QPoint(0, 0))) try: self.restoreState(settings.value("window_state")) except: import traceback print traceback.format_exc() settings.endGroup() def storeSetting(self): if nm.settings().store_geometry: settings = nm.settings().qsettings(nm.settings().CFG_GUI_FILE) settings.beginGroup("editor") settings.setValue("size", self.size()) settings.setValue("pos", self.pos()) settings.setValue("maximized", self.isMaximized()) settings.setValue("window_state", self.saveState()) settings.endGroup() def on_load_request(self, filename, search_text=''): ''' Loads a file in a new tab or focus the tab, if the file is already open. @param filename: the path to file @type filename: C{str} @param search_text: if not empty, searches in new document for first occurrence of the given text @type search_text: C{str} (Default: C{Empty String}) ''' if not filename: return self.tabWidget.setUpdatesEnabled(False) try: if filename not in self.files: tab_name = self.__getTabName(filename) editor = TextEdit(filename, self.tabWidget) linenumber_editor = LineNumberWidget(editor) tab_index = self.tabWidget.addTab(linenumber_editor, tab_name) self.files.append(filename) editor.setCurrentPath(os.path.basename(filename)) editor.load_request_signal.connect(self.on_load_request) editor.document().modificationChanged.connect(self.on_editor_modificationChanged) editor.cursorPositionChanged.connect(self.on_editor_positionChanged) editor.setFocus(Qt.OtherFocusReason) editor.textChanged.connect(self.on_text_changed) self.tabWidget.setCurrentIndex(tab_index) # self.find_dialog.set_search_path(filename) else: for i in range(self.tabWidget.count()): if self.tabWidget.widget(i).filename == filename: self.tabWidget.setCurrentIndex(i) break except: import traceback rospy.logwarn("Error while open %s: %s", filename, traceback.format_exc(1)) self.tabWidget.setUpdatesEnabled(True) if search_text: try: self._search_thread.stop() self._search_thread = None except: pass self._search_thread = TextSearchThread(search_text, filename, path_text=self.tabWidget.widget(0).document().toPlainText(), recursive=True) self._search_thread.search_result_signal.connect(self.on_search_result_on_open) self._search_thread.start() def on_text_changed(self): if self.tabWidget.currentWidget().hasFocus(): self.find_dialog.file_changed(self.tabWidget.currentWidget().filename) def on_close_tab(self, tab_index): ''' Signal handling to close single tabs. @param tab_index: tab index to close @type tab_index: C{int} ''' try: doremove = True w = self.tabWidget.widget(tab_index) if w.document().isModified(): name = self.__getTabName(w.filename) result = QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", name]), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if result == QMessageBox.Yes: self.tabWidget.currentWidget().save() elif result == QMessageBox.No: pass else: doremove = False if doremove: # remove the indexed files if w.filename in self.files: self.files.remove(w.filename) # close tab self.tabWidget.removeTab(tab_index) # close editor, if no tabs are open if not self.tabWidget.count(): self.close() except: import traceback rospy.logwarn("Error while close tab %s: %s", str(tab_index), traceback.format_exc(1)) def reject(self): if self.find_dialog.isVisible(): self.searchButton.setChecked(not self.searchButton.isChecked()) else: self.close() def closeEvent(self, event): ''' Test the open files for changes and save this if needed. ''' changed = [] # get the names of all changed files for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i) if w.document().isModified(): changed.append(self.__getTabName(w.filename)) if changed: # ask the user for save changes if self.isHidden(): buttons = QMessageBox.Yes | QMessageBox.No else: buttons = QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel result = QMessageBox.question(self, "Unsaved Changes", '\n\n'.join(["Save the file before closing?", '\n'.join(changed)]), buttons) if result == QMessageBox.Yes: for i in range(self.tabWidget.count()): w = self.tabWidget.widget(i).save() event.accept() elif result == QMessageBox.No: event.accept() else: event.ignore() else: event.accept() if event.isAccepted(): self.storeSetting() self.finished_signal.emit(self.init_filenames) def on_editor_modificationChanged(self, value=None): ''' If the content was changed, a '*' will be shown in the tab name. ''' tab_name = self.__getTabName(self.tabWidget.currentWidget().filename) if (self.tabWidget.currentWidget().document().isModified()) or not QFileInfo(self.tabWidget.currentWidget().filename).exists(): tab_name = ''.join(['*', tab_name]) self.tabWidget.setTabText(self.tabWidget.currentIndex(), tab_name) def on_editor_positionChanged(self): ''' Shows the number of the line and column in a label. ''' cursor = self.tabWidget.currentWidget().textCursor() self.pos_label.setText(':%s:%s' % (cursor.blockNumber() + 1, cursor.columnNumber())) def __getTabName(self, lfile): base = os.path.basename(lfile).replace('.launch', '') (package, _) = package_name(os.path.dirname(lfile)) return '%s [%s]' % (base, package) ############################################################################## # HANDLER for buttons ############################################################################## def on_saveButton_clicked(self): ''' Saves the current document. This method is called if the C{save button} was clicked. ''' saved, errors, msg = self.tabWidget.currentWidget().save(True) if errors: QMessageBox.critical(self, "Error", msg) self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._error_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), msg) elif saved: self.tabWidget.setTabIcon(self.tabWidget.currentIndex(), self._empty_icon) self.tabWidget.setTabToolTip(self.tabWidget.currentIndex(), '') def on_shortcut_find(self): pass def on_toggled_find(self, value): ''' Shows the search frame ''' if value: self.find_dialog.enable() else: self.replaceButton.setChecked(False) self.find_dialog.setVisible(False) self.tabWidget.currentWidget().setFocus() def on_toggled_replace(self, value): ''' Shows the replace lineedit in the search frame ''' if value: self.searchButton.setChecked(True) self.find_dialog.set_replace_visible(value) def on_shortcut_goto(self): ''' Opens a C{goto} dialog. ''' value = 1 ok = False try: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, minValue=1, step=1) except: value, ok = QInputDialog.getInt(self, "Goto", self.tr("Line number:"), QLineEdit.Normal, min=1, step=1) if ok: if value > self.tabWidget.currentWidget().document().blockCount(): value = self.tabWidget.currentWidget().document().blockCount() curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 while curpos != value: mov = QTextCursor.NextBlock if curpos < value else QTextCursor.PreviousBlock self.tabWidget.currentWidget().moveCursor(mov) curpos = self.tabWidget.currentWidget().textCursor().blockNumber() + 1 self.tabWidget.currentWidget().setFocus(Qt.ActiveWindowFocusReason) ############################################################################## # SLOTS for search dialog ############################################################################## def on_search_result(self, search_text, found, path, index): ''' A slot to handle a found text. It goes to the position in the text and select the searched text. On new file it will be open. :param search_text: the searched text :type search_text: str :param found: the text was found or not :type found: bool :param path: the path of the file the text was found :type path: str :param index: the position in the text :type index: int ''' if found: if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) focus_widget.setFocus() cursor = self.tabWidget.currentWidget().textCursor() cursor.setPosition(index, QTextCursor.MoveAnchor) cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor, len(search_text)) self.tabWidget.currentWidget().setTextCursor(cursor) def on_search_result_on_open(self, search_text, found, path, index): ''' Like on_search_result, but skips the text in comments. ''' if found: if self.tabWidget.currentWidget().filename != path: focus_widget = QApplication.focusWidget() self.on_load_request(path) focus_widget.setFocus() comment_start = self.tabWidget.currentWidget().document().find('<!--', index, QTextDocument.FindBackward) if not comment_start.isNull(): comment_end = self.tabWidget.currentWidget().document().find('-->', comment_start) if not comment_end.isNull() and comment_end.position() > index + len(search_text): # commented -> retrun return self.on_search_result(search_text, found, path, index) def on_replace(self, search_text, path, index, replaced_text): ''' A slot to handle a text replacement of the TextSearchFrame. :param search_text: the searched text :type search_text: str :param path: the path of the file the text was found :type path: str :param index: the position in the text :type index: int :param replaced_text: the new text :type replaced_text: str ''' cursor = self.tabWidget.currentWidget().textCursor() if cursor.selectedText() == search_text: cursor.insertText(replaced_text) ############################################################################## # LAUNCH TAG insertion ############################################################################## def _create_tag_button(self, parent=None): btn = QPushButton(parent) btn.setObjectName("tagButton") btn.setText(self._translate("Add &tag")) btn.setShortcut("Ctrl+T") btn.setToolTip('Adds a ROS launch tag to launch file (Ctrl+T)') btn.setMenu(self._create_tag_menu(btn)) btn.setFlat(True) return btn def _create_tag_menu(self, parent=None): # creates a tag menu tag_menu = QMenu("ROS Tags", parent) # group tag add_group_tag_action = QAction("<group>", self, statusTip="", triggered=self._on_add_group_tag) add_group_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+g")) tag_menu.addAction(add_group_tag_action) # node tag add_node_tag_action = QAction("<node>", self, statusTip="", triggered=self._on_add_node_tag) add_node_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+n")) tag_menu.addAction(add_node_tag_action) # node tag with all attributes add_node_tag_all_action = QAction("<node all>", self, statusTip="", triggered=self._on_add_node_tag_all) tag_menu.addAction(add_node_tag_all_action) # include tag with all attributes add_include_tag_all_action = QAction("<include>", self, statusTip="", triggered=self._on_add_include_tag_all) add_include_tag_all_action.setShortcuts(QKeySequence("Ctrl+Shift+i")) tag_menu.addAction(add_include_tag_all_action) # remap add_remap_tag_action = QAction("<remap>", self, statusTip="", triggered=self._on_add_remap_tag) add_remap_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+r")) tag_menu.addAction(add_remap_tag_action) # env tag add_env_tag_action = QAction("<env>", self, statusTip="", triggered=self._on_add_env_tag) tag_menu.addAction(add_env_tag_action) # param tag add_param_tag_action = QAction("<param>", self, statusTip="", triggered=self._on_add_param_tag) add_param_tag_action.setShortcuts(QKeySequence("Ctrl+Shift+p")) tag_menu.addAction(add_param_tag_action) # param capability group tag add_param_cap_group_tag_action = QAction("<param capability group>", self, statusTip="", triggered=self._on_add_param_cap_group_tag) add_param_cap_group_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+p")) tag_menu.addAction(add_param_cap_group_tag_action) # param tag with all attributes add_param_tag_all_action = QAction("<param all>", self, statusTip="", triggered=self._on_add_param_tag_all) tag_menu.addAction(add_param_tag_all_action) # rosparam tag with all attributes add_rosparam_tag_all_action = QAction("<rosparam>", self, statusTip="", triggered=self._on_add_rosparam_tag_all) tag_menu.addAction(add_rosparam_tag_all_action) # arg tag with default definition add_arg_tag_default_action = QAction("<arg default>", self, statusTip="", triggered=self._on_add_arg_tag_default) add_arg_tag_default_action.setShortcuts(QKeySequence("Ctrl+Shift+a")) tag_menu.addAction(add_arg_tag_default_action) # arg tag with value definition add_arg_tag_value_action = QAction("<arg value>", self, statusTip="", triggered=self._on_add_arg_tag_value) add_arg_tag_value_action.setShortcuts(QKeySequence("Ctrl+Alt+a")) tag_menu.addAction(add_arg_tag_value_action) # test tag add_test_tag_action = QAction("<test>", self, statusTip="", triggered=self._on_add_test_tag) add_test_tag_action.setShortcuts(QKeySequence("Ctrl+Alt+t")) tag_menu.addAction(add_test_tag_action) # test tag with all attributes add_test_tag_all_action = QAction("<test all>", self, statusTip="", triggered=self._on_add_test_tag_all) tag_menu.addAction(add_test_tag_all_action) return tag_menu def _insert_text(self, text): cursor = self.tabWidget.currentWidget().textCursor() if not cursor.isNull(): col = cursor.columnNumber() spaces = ''.join([' ' for _ in range(col)]) cursor.insertText(text.replace('\n', '\n%s' % spaces)) self.tabWidget.currentWidget().setFocus(Qt.OtherFocusReason) def _on_add_group_tag(self): self._insert_text('<group ns="namespace" clear_params="true|false">\n' '</group>') def _on_add_node_tag(self): dia = PackageDialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_node_tag_all(self): dia = PackageDialog() if dia.exec_(): self._insert_text('<node name="%s" pkg="%s" type="%s"\n' ' args="arg1" machine="machine-name"\n' ' respawn="true" required="true"\n' ' ns="foo" clear_params="true|false"\n' ' output="log|screen" cwd="ROS_HOME|node"\n' ' launch-prefix="prefix arguments">\n' '</node>' % (dia.binary, dia.package, dia.binary)) def _on_add_include_tag_all(self): self._insert_text('<include file="$(find pkg-name)/path/filename.xml"\n' ' ns="foo" clear_params="true|false">\n' '</include>') def _on_add_remap_tag(self): self._insert_text('<remap from="original" to="new"/>') def _on_add_env_tag(self): self._insert_text('<env name="variable" value="value"/>') def _on_add_param_tag(self): self._insert_text('<param name="ns_name" value="value" />') def _on_add_param_cap_group_tag(self): self._insert_text('<param name="capability_group" value="demo" />') def _on_add_param_tag_all(self): self._insert_text('<param name="ns_name" value="value"\n' ' type="str|int|double|bool"\n' ' textfile="$(find pkg-name)/path/file.txt"\n' ' binfile="$(find pkg-name)/path/file"\n' ' command="$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'">\n' '</param>') def _on_add_rosparam_tag_all(self): self._insert_text('<rosparam param="param-name"\n' ' file="$(find pkg-name)/path/foo.yaml"\n' ' command="load|dump|delete"\n' ' ns="namespace">\n' '</rosparam>') def _on_add_arg_tag_default(self): self._insert_text('<arg name="foo" default="1" />') def _on_add_arg_tag_value(self): self._insert_text('<arg name="foo" value="bar" />') def _on_add_test_tag(self): dia = PackageDialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary)) def _on_add_test_tag_all(self): dia = PackageDialog() if dia.exec_(): self._insert_text('<test name="%s" pkg="%s" type="%s" test-name="test_%s">\n' ' args="arg1" time-limit="60.0"\n' ' ns="foo" clear_params="true|false"\n' ' cwd="ROS_HOME|node" retry="0"\n' ' launch-prefix="prefix arguments">\n' '</test>' % (dia.binary, dia.package, dia.binary, dia.binary))
class MessageBox(QDialog): NoIcon = 0 Information = 1 Warning = 2 Critical = 3 Question = 4 NoButton = 0 Ok = 1 # An "OK" button defined with the AcceptRole . Open = 2 # A "Open" button defined with the AcceptRole . Save = 4 # A "Save" button defined with the AcceptRole . Cancel = 8 # A "Cancel" button defined with the RejectRole . Close = 16 # A "Close" button defined with the RejectRole . Discard = 32 # A "Discard" or "Don't Save" button, depending on the platform, defined with the DestructiveRole . Apply = 64 # An "Apply" button defined with the ApplyRole . Reset = 128 # A "Reset" button defined with the ResetRole . RestoreDefaults = 256 # A "Restore Defaults" button defined with the ResetRole . Help = 512 # A "Help" button defined with the HelpRole . SaveAll = 1024 # A "Save All" button defined with the AcceptRole . Yes = 2048 # A "Yes" button defined with the YesRole . YesToAll = 4096 # A "Yes to All" button defined with the YesRole . No = 8192 # A "No" button defined with the NoRole . NoToAll = 16384 # A "No to All" button defined with the NoRole . Abort = 32768 # An "Abort" button defined with the RejectRole . Retry = 65536 # A "Retry" button defined with the AcceptRole . Ignore = 131072 # An "Ignore" button defined with the AcceptRole . Avoid = 262144 # An "'Don't show again'" button defined with the HelpRole, returns a default AcceptButton . def __init__(self, icon, title, text, detailed_text="", buttons=Cancel | Ok, parent=None): QDialog.__init__(self, parent=parent) self.setWindowFlags(self.windowFlags() & ~Qt.WindowTitleHint) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint & ~Qt.WindowMinimizeButtonHint) self.setObjectName('MessageBox') self._use_checkbox = True self.text = text self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setContentsMargins(1, 1, 1, 1) # create icon pixmap = None if icon == self.NoIcon: pass elif icon == self.Question: pixmap = nm.settings().pixmap('question.png').scaled( 56, 56, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) elif icon == self.Information: pixmap = nm.settings().pixmap('info.png').scaled( 56, 56, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) elif icon == self.Warning: pixmap = nm.settings().pixmap('warning.png').scaled( 56, 56, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) elif icon == self.Critical: pixmap = nm.settings().pixmap('critical.png').scaled( 56, 56, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) spacerItem = QSpacerItem(10, 60, QSizePolicy.Minimum, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.icon_label = QLabel() if pixmap is not None: self.icon_label.setPixmap(pixmap) self.icon_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.horizontalLayout.addWidget(self.icon_label) spacerItem = QSpacerItem(10, 60, QSizePolicy.Minimum, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) # add message self.message_label = QLabel(text) self.message_label.setWordWrap(True) self.message_label.setScaledContents(True) self.message_label.setOpenExternalLinks(True) self.horizontalLayout.addWidget(self.message_label) self.verticalLayout.addLayout(self.horizontalLayout) # create buttons self.buttonBox = QDialogButtonBox(self) self.buttonBox.setObjectName("buttonBox") self.buttonBox.setOrientation(Qt.Horizontal) self._accept_button = None self._reject_button = None self._buttons = buttons self._create_buttons(buttons) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.verticalLayout.addWidget(self.buttonBox) if detailed_text: self.btn_show_details = QPushButton(self.tr('Details...')) self.btn_show_details.setCheckable(True) self.btn_show_details.setChecked(True) self.btn_show_details.toggled.connect(self.on_toggled_details) self.buttonBox.addButton(self.btn_show_details, QDialogButtonBox.ActionRole) # create area for detailed text self.textEdit = textEdit = QTextEdit(self) textEdit.setObjectName("textEdit") textEdit.setReadOnly(True) textEdit.setText(detailed_text) # textEdit.setVisible(False) self.verticalLayout.addWidget(self.textEdit) self.resize(480, self.verticalLayout.totalSizeHint().height()) buttons_in_box = self.buttonBox.buttons() if buttons_in_box: self.buttonBox.buttons()[0].setFocus() def setAcceptButton(self, button): ''' Sets the button with given ID to accept button if more then one button with AcceptRole was added to this dialog. Adds the buttton to the box if is not already in. :param int button: button id ''' if not button & self._buttons: self._create_buttons(button) self._accept_button = button def setRejectButton(self, button): ''' Sets the button with given ID to reject button if more then one button with RejectRole was added to this dialog. Adds the buttton to the box if is not already in. :param int button: button id ''' if not button & self._buttons: self._create_buttons(button) self._reject_button = button def on_toggled_details(self, checked): if checked: self.verticalLayout.addWidget(self.textEdit) else: self.verticalLayout.removeWidget(self.textEdit) self.textEdit.setVisible(checked) if not self.isMaximized(): self.setMinimumSize(self.verticalLayout.totalMinimumSize()) self.resize(self._current_size.width(), self.verticalLayout.totalSizeHint().height()) @staticmethod def about(parent, title, text, detailed_text='', buttons=Close): box = MessageBox(MessageBox.Information, title, text, detailed_text=detailed_text, buttons=buttons, parent=parent) if MessageBox.Yes & buttons: box.setAcceptButton(MessageBox.Yes) if MessageBox.Cancel & buttons: box.setRejectButton(MessageBox.Cancel) elif MessageBox.No & buttons: box.setRejectButton(MessageBox.No) return box.exec_() @staticmethod def information(parent, title, text, detailed_text='', buttons=Close): box = MessageBox(MessageBox.Information, title, text, detailed_text=detailed_text, buttons=buttons, parent=parent) if MessageBox.Yes & buttons: box.setAcceptButton(MessageBox.Yes) if MessageBox.Cancel & buttons: box.setRejectButton(MessageBox.Cancel) elif MessageBox.No & buttons: box.setRejectButton(MessageBox.No) return box.exec_() @staticmethod def question(parent, title, text, detailed_text='', buttons=Yes | No | Cancel): box = MessageBox(MessageBox.Question, title, text, detailed_text=detailed_text, buttons=buttons, parent=parent) if MessageBox.Yes & buttons: box.setAcceptButton(MessageBox.Yes) if MessageBox.Cancel & buttons: box.setRejectButton(MessageBox.Cancel) elif MessageBox.No & buttons: box.setRejectButton(MessageBox.No) return box.exec_() @staticmethod def warning(parent, title, text, detailed_text='', buttons=Ok): box = MessageBox(MessageBox.Warning, title, text, detailed_text=detailed_text, buttons=buttons, parent=parent) if MessageBox.Yes & buttons: box.setAcceptButton(MessageBox.Yes) if MessageBox.Cancel & buttons: box.setRejectButton(MessageBox.Cancel) elif MessageBox.No & buttons: box.setRejectButton(MessageBox.No) return box.exec_() @staticmethod def critical(parent, title, text, detailed_text='', buttons=Ok): box = MessageBox(MessageBox.Critical, title, text, detailed_text=detailed_text, buttons=buttons, parent=parent) if MessageBox.Yes & buttons: box.setAcceptButton(MessageBox.Yes) if MessageBox.Cancel & buttons: box.setRejectButton(MessageBox.Cancel) elif MessageBox.No & buttons: box.setRejectButton(MessageBox.No) return box.exec_() def resizeEvent(self, event): if not self.isMaximized(): self._current_size = event.size() # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%%% close handling %%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% def exec_(self): if self.text in IGNORED_ERRORS: self.accept() return self.result() return QDialog.exec_(self) def accept(self): if self.result() == 0: if self._accept_button is not None: self.setResult(self._accept_button) else: self.setResult(1) self.accepted.emit() if self.isModal(): self.hide() def reject(self): if self.result() == 0: if self._reject_button is not None: self.setResult(self._reject_button) self.rejected.emit() self.hide() def hideEvent(self, event): # event.ignore() self.close() def closeEvent(self, event): self.setAttribute(Qt.WA_DeleteOnClose, True) QDialog.closeEvent(self, event) # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%%% create buttons %%%%%%%%%%%%%%%%%%%%% # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% def _create_buttons(self, buttons): if MessageBox.Ok & buttons: self._accept_button = MessageBox.Ok bt = QPushButton(self.tr("&ok")) bt.clicked.connect(self._on_ok_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Open & buttons: self._accept_button = MessageBox.Open bt = QPushButton(self.tr("&Open")) bt.clicked.connect(self._on_open_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Save & buttons: self._accept_button = MessageBox.Save bt = QPushButton(self.tr("&Save")) bt.clicked.connect(self._on_save_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Cancel & buttons: self._reject_button = MessageBox.Cancel bt = QPushButton(self.tr("&Cancel")) bt.clicked.connect(self._on_cancel_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.RejectRole) if MessageBox.Close & buttons: self._reject_button = MessageBox.Close bt = QPushButton(self.tr("&Close")) bt.clicked.connect(self._on_close_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.RejectRole) if MessageBox.Discard & buttons: bt = QPushButton(self.tr("&Discard")) bt.clicked.connect(self._on_discard_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.DestructiveRole) if MessageBox.Apply & buttons: bt = QPushButton(self.tr("&Apply")) bt.clicked.connect(self._on_apply_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.ApplyRole) if MessageBox.Reset & buttons: bt = QPushButton(self.tr("&Reset")) bt.clicked.connect(self._on_reset_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.ResetRole) if MessageBox.RestoreDefaults & buttons: bt = QPushButton(self.tr("&RestoreDefaults")) bt.clicked.connect(self._on_restore_defaults_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.ResetRole) if MessageBox.Help & buttons: bt = QPushButton(self.tr("&Help")) bt.clicked.connect(self._on_help_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.HelpRole) if MessageBox.SaveAll & buttons: self._accept_button = MessageBox.SaveAll bt = QPushButton(self.tr("&SaveAll")) bt.clicked.connect(self._on_saveall_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Yes & buttons: bt = QPushButton(self.tr("&Yes")) bt.clicked.connect(self._on_yes_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.YesRole) if MessageBox.YesToAll & buttons: bt = QPushButton(self.tr("&YesToAll")) bt.clicked.connect(self._on_yestoall_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.YesRole) if MessageBox.No & buttons: bt = QPushButton(self.tr("&No")) bt.clicked.connect(self._on_no_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.NoRole) if MessageBox.NoToAll & buttons: bt = QPushButton(self.tr("&NoToAll")) bt.clicked.connect(self._on_notoall_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.NoRole) if MessageBox.Abort & buttons: self._reject_button = MessageBox.Abort bt = QPushButton(self.tr("&Abort")) bt.clicked.connect(self._on_abort_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.RejectRole) if MessageBox.Retry & buttons: self._accept_button = MessageBox.Retry bt = QPushButton(self.tr("&Retry")) bt.clicked.connect(self._on_retry_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Ignore & buttons: self._accept_button = MessageBox.Ignore bt = QPushButton(self.tr("&Ignore")) bt.clicked.connect(self._on_ignore_clicked) self.buttonBox.addButton(bt, QDialogButtonBox.AcceptRole) if MessageBox.Avoid & buttons: if self._use_checkbox: checkbox = QCheckBox("&Don't show again", self) checkbox.stateChanged.connect(self._check_ignore) self.buttonBox.addButton(checkbox, QDialogButtonBox.HelpRole) else: bt = QPushButton(self.tr("&Don't show again")) bt.setMaximumHeight(24) bt.clicked.connect(self._add_to_ignore) self.buttonBox.addButton(bt, QDialogButtonBox.HelpRole) def _on_ok_clicked(self): self.done(MessageBox.Ok) def _on_open_clicked(self): self.done(MessageBox.Open) def _on_save_clicked(self): self.done(MessageBox.Save) def _on_cancel_clicked(self): self.done(MessageBox.Cancel) def _on_close_clicked(self): self.done(MessageBox.Close) def _on_discard_clicked(self): self.done(MessageBox.Discard) def _on_apply_clicked(self): self.done(MessageBox.Apply) def _on_reset_clicked(self): self.done(MessageBox.Reset) def _on_restore_defaults_clicked(self): self.done(MessageBox.RestoreDefaults) def _on_help_clicked(self): self.done(MessageBox.Help) def _on_saveall_clicked(self): self.done(MessageBox.SaveAll) def _on_yes_clicked(self): self.done(MessageBox.Yes) def _on_yestoall_clicked(self): self.done(MessageBox.YesToAll) def _on_no_clicked(self): self.done(MessageBox.No) def _on_notoall_clicked(self): self.done(MessageBox.NoToAll) def _on_abort_clicked(self): self.done(MessageBox.Abort) def _on_retry_clicked(self): self.done(MessageBox.Retry) def _on_ignore_clicked(self): self.done(MessageBox.Ignore) def _add_to_ignore(self): IGNORED_ERRORS.append(self.text) self.accept() def _check_ignore(self, state): if state: IGNORED_ERRORS.append(self.text) else: try: IGNORED_ERRORS.remove(self.text) except Exception: pass
def __init__(self, context): super(LogicalMarkerPlugin, self).__init__(context) # Create an image for the original map. This will never change. try: self.map_yaml_file_str = rospy.get_param("~map_file") self.data_directory = rospy.get_param("~data_directory") except KeyError: rospy.logfatal("~map_file and ~data_directory need to be set to use the logical marker") return map_image_location = getImageFileLocation(self.map_yaml_file_str) map = loadMapFromFile(self.map_yaml_file_str) locations_file = getLocationsFileLocationFromDataDirectory(self.data_directory) connectivity_file = getConnectivityFileLocationFromDataDirectory(self.data_directory) doors_file = getDoorsFileLocationFromDataDirectory(self.data_directory) objects_file = getObjectsFileLocationFromDataDirectory(self.data_directory) # Give QObjects reasonable names self.setObjectName('LogicalMarkerPlugin') # Create QWidget self.master_widget = QWidget() self.master_layout = QVBoxLayout(self.master_widget) # Main Functions - Doors, Locations, Objects self.function_layout = QHBoxLayout() self.master_layout.addLayout(self.function_layout) self.function_buttons = [] self.current_function = None for button_text in ['Locations', 'Doors', 'Objects']: button = QPushButton(button_text, self.master_widget) button.clicked[bool].connect(self.handle_function_button) button.setCheckable(True) self.function_layout.addWidget(button) self.function_buttons.append(button) self.function_layout.addStretch(1) self.master_layout.addWidget(self.get_horizontal_line()) # Subfunction toolbar self.subfunction_layout = QHBoxLayout() clearLayoutAndFixHeight(self.subfunction_layout) self.master_layout.addLayout(self.subfunction_layout) self.current_subfunction = None self.master_layout.addWidget(self.get_horizontal_line()) self.image = MapImage(map_image_location, self.master_widget) self.master_layout.addWidget(self.image) self.master_layout.addWidget(self.get_horizontal_line()) # Configuration toolbar self.configuration_layout = QHBoxLayout() clearLayoutAndFixHeight(self.configuration_layout) self.master_layout.addLayout(self.configuration_layout) # Add a stretch at the bottom. self.master_layout.addStretch(1) self.master_widget.setObjectName('LogicalMarkerPluginUI') if context.serial_number() > 1: self.master_widget.setWindowTitle(self.master_widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.master_widget) # Activate the functions self.functions = {} self.functions['Locations'] = LocationFunction(locations_file, connectivity_file, map, self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Doors'] = DoorFunction(doors_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Objects'] = ObjectFunction(objects_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image)
class DataPlot(QWidget): """A widget for displaying a plot of data The DataPlot widget displays a plot, on one of several plotting backends, depending on which backend(s) are available at runtime. It currently supports PyQtGraph, MatPlot and QwtPlot backends. The DataPlot widget manages the plot backend internally, and can save and restore the internal state using `save_settings` and `restore_settings` functions. Currently, the user MUST call `restore_settings` before using the widget, to cause the creation of the enclosed plotting widget. """ # plot types in order of priority plot_types = [ { 'title': 'PyQtGraph', 'widget_class': PyQtGraphDataPlot, 'description': 'Based on PyQtGraph\n- installer: http://luke.campagnola.me/code/pyqtgraph\n', 'enabled': PyQtGraphDataPlot is not None, }, { 'title': 'MatPlot', 'widget_class': MatDataPlot, 'description': 'Based on MatPlotLib\n- needs most CPU\n- needs matplotlib >= 1.1.0\n- if using PySide: PySide > 1.1.0\n', 'enabled': MatDataPlot is not None, }, { 'title': 'QwtPlot', 'widget_class': QwtDataPlot, 'description': 'Based on QwtPlot\n- does not use timestamps\n- uses least CPU\n- needs Python Qwt bindings\n', 'enabled': QwtDataPlot is not None, }, ] # pre-defined colors: RED=(255, 0, 0) GREEN=(0, 255, 0) BLUE=(0, 0, 255) SCALE_ALL=1 SCALE_VISIBLE=2 SCALE_EXTEND=4 _colors = [Qt.blue, Qt.red, Qt.cyan, Qt.magenta, Qt.green, Qt.darkYellow, Qt.black, Qt.darkCyan, Qt.darkRed, Qt.gray] limits_changed = Signal() _redraw = Signal() _add_curve = Signal(str, str, 'QColor', bool) def __init__(self, parent=None): """Create a new, empty DataPlot This will raise a RuntimeError if none of the supported plotting backends can be found """ super(DataPlot, self).__init__(parent) self._plot_index = 0 self._color_index = 0 self._markers_on = False self._autoscroll = True self._autoscale_x = True self._autoscale_y = DataPlot.SCALE_ALL # the backend widget that we're trying to hide/abstract self._data_plot_widget = None self._curves = {} self._vline = None self._redraw.connect(self._do_redraw) self._layout = QHBoxLayout() self.setLayout(self._layout) enabled_plot_types = [pt for pt in self.plot_types if pt['enabled']] if not enabled_plot_types: version_info = ' and PySide > 1.1.0' if QT_BINDING == 'pyside' else '' raise RuntimeError('No usable plot type found. Install at least one of: PyQtGraph, MatPlotLib (at least 1.1.0%s) or Python-Qwt5.' % version_info) self._switch_data_plot_widget(self._plot_index) self.show() def _switch_data_plot_widget(self, plot_index, markers_on=False): """Internal method for activating a plotting backend by index""" # check if selected plot type is available if not self.plot_types[plot_index]['enabled']: # find other available plot type for index, plot_type in enumerate(self.plot_types): if plot_type['enabled']: plot_index = index break self._plot_index = plot_index self._markers_on = markers_on selected_plot = self.plot_types[plot_index] if self._data_plot_widget: x_limits = self.get_xlim() y_limits = self.get_ylim() self._layout.removeWidget(self._data_plot_widget) self._data_plot_widget.close() self._data_plot_widget = None else: x_limits = [0.0, 10.0] y_limits = [-0.001, 0.001] self._data_plot_widget = selected_plot['widget_class'](self) self._data_plot_widget.limits_changed.connect(self.limits_changed) self._add_curve.connect(self._data_plot_widget.add_curve) self._layout.addWidget(self._data_plot_widget) # restore old data for curve_id in self._curves: curve = self._curves[curve_id] self._data_plot_widget.add_curve(curve_id, curve['name'], curve['color'], markers_on) if self._vline: self.vline(*self._vline) self.set_xlim(x_limits) self.set_ylim(y_limits) self.redraw() def _switch_plot_markers(self, markers_on): self._markers_on = markers_on self._data_plot_widget._color_index = 0 for curve_id in self._curves: self._data_plot_widget.remove_curve(curve_id) curve = self._curves[curve_id] self._data_plot_widget.add_curve(curve_id, curve['name'], curve['color'], markers_on) self.redraw() # interface out to the managing GUI component: get title, save, restore, # etc def getTitle(self): """get the title of the current plotting backend""" return self.plot_types[self._plot_index]['title'] def save_settings(self, plugin_settings, instance_settings): """Save the settings associated with this widget Currently, this is just the plot type, but may include more useful data in the future""" instance_settings.set_value('plot_type', self._plot_index) xlim = self.get_xlim() ylim = self.get_ylim() # convert limits to normal arrays of floats; some backends return numpy # arrays xlim = [float(x) for x in xlim] ylim = [float(y) for y in ylim] instance_settings.set_value('x_limits', pack(xlim)) instance_settings.set_value('y_limits', pack(ylim)) def restore_settings(self, plugin_settings, instance_settings): """Restore the settings for this widget Currently, this just restores the plot type.""" self._switch_data_plot_widget(int(instance_settings.value('plot_type', 0))) xlim = unpack(instance_settings.value('x_limits', [])) ylim = unpack(instance_settings.value('y_limits', [])) if xlim: # convert limits to an array of floats; they're often lists of # strings try: xlim = [float(x) for x in xlim] self.set_xlim(xlim) except: qWarning("Failed to restore X limits") if ylim: try: ylim = [float(y) for y in ylim] self.set_ylim(ylim) except: qWarning("Failed to restore Y limits") def doSettingsDialog(self): """Present the user with a dialog for choosing the plot backend This displays a SimpleSettingsDialog asking the user to choose a plot type, gets the result, and updates the plot type as necessary This method is blocking""" marker_settings = [ { 'title': 'Show Plot Markers', 'description': 'Warning: Displaying markers in rqt_plot may cause\n \t high cpu load, especially using PyQtGraph\n', 'enabled': True, }] if self._markers_on: selected_checkboxes = [0] else: selected_checkboxes = [] dialog = SimpleSettingsDialog(title='Plot Options') dialog.add_exclusive_option_group(title='Plot Type', options=self.plot_types, selected_index=self._plot_index) dialog.add_checkbox_group(title='Plot Markers', options=marker_settings, selected_indexes=selected_checkboxes) [plot_type, checkboxes] = dialog.get_settings() if plot_type is not None and plot_type['selected_index'] is not None and self._plot_index != plot_type['selected_index']: self._switch_data_plot_widget(plot_type['selected_index'], 0 in checkboxes['selected_indexes']) else: if checkboxes is not None and self._markers_on != (0 in checkboxes['selected_indexes']): self._switch_plot_markers(0 in checkboxes['selected_indexes']) # interface out to the managing DATA component: load data, update data, # etc def autoscroll(self, enabled=True): """Enable or disable autoscrolling of the plot""" self._autoscroll = enabled def redraw(self): self._redraw.emit() def _do_redraw(self): """Redraw the underlying plot This causes the underlying plot to be redrawn. This is usually used after adding or updating the plot data""" if self._data_plot_widget: self._merged_autoscale() for curve_id in self._curves: curve = self._curves[curve_id] self._data_plot_widget.set_values(curve_id, curve['x'], curve['y']) self._data_plot_widget.redraw() def _get_curve(self, curve_id): if curve_id in self._curves: return self._curves[curve_id] else: raise DataPlotException("No curve named %s in this DataPlot" % ( curve_id) ) def add_curve(self, curve_id, curve_name, data_x, data_y): """Add a new, named curve to this plot Add a curve named `curve_name` to the plot, with initial data series `data_x` and `data_y`. Future references to this curve should use the provided `curve_id` Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ curve_color = QColor(self._colors[self._color_index % len(self._colors)]) self._color_index += 1 self._curves[curve_id] = { 'x': numpy.array(data_x), 'y': numpy.array(data_y), 'name': curve_name, 'color': curve_color} if self._data_plot_widget: self._add_curve.emit(curve_id, curve_name, curve_color, self._markers_on) def remove_curve(self, curve_id): """Remove the specified curve from this plot""" # TODO: do on UI thread with signals if curve_id in self._curves: del self._curves[curve_id] if self._data_plot_widget: self._data_plot_widget.remove_curve(curve_id) def update_values(self, curve_id, values_x, values_y): """Append new data to an existing curve `values_x` and `values_y` will be appended to the existing data for `curve_id` Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ curve = self._get_curve(curve_id) curve['x'] = numpy.append(curve['x'], values_x) curve['y'] = numpy.append(curve['y'], values_y) # sort resulting data, so we can slice it later sort_order = curve['x'].argsort() curve['x'] = curve['x'][sort_order] curve['y'] = curve['y'][sort_order] def clear_values(self, curve_id=None): """Clear the values for the specified curve, or all curves This will erase the data series associaed with `curve_id`, or all curves if `curve_id` is not present or is None Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ # clear internal curve representation if curve_id: curve = self._get_curve(curve_id) curve['x'] = numpy.array([]) curve['y'] = numpy.array([]) else: for curve_id in self._curves: self._curves[curve_id]['x'] = numpy.array([]) self._curves[curve_id]['y'] = numpy.array([]) def vline(self, x, color=RED): """Draw a vertical line on the plot Draw a line a position X, with the given color @param x: position of the vertical line to draw @param color: optional parameter specifying the color, as tuple of RGB values from 0 to 255 """ self._vline = (x, color) if self._data_plot_widget: self._data_plot_widget.vline(x, color) # autoscaling methods def set_autoscale(self, x=None, y=None): """Change autoscaling of plot axes if a parameter is not passed, the autoscaling setting for that axis is not changed @param x: enable or disable autoscaling for X @param y: set autoscaling mode for Y """ if x is not None: self._autoscale_x = x if y is not None: self._autoscale_y = y # autoscaling: adjusting the plot bounds fit the data # autoscrollig: move the plot X window to show the most recent data # # what order do we do these adjustments in? # * assuming the various stages are enabled: # * autoscale X to bring all data into view # * else, autoscale X to determine which data we're looking at # * autoscale Y to fit the data we're viewing # # * autoscaling of Y might have several modes: # * scale Y to fit the entire dataset # * scale Y to fit the current view # * increase the Y scale to fit the current view # # TODO: incrmenetal autoscaling: only update the autoscaling bounds # when new data is added def _merged_autoscale(self): x_limit = [numpy.inf, -numpy.inf] if self._autoscale_x: for curve_id in self._curves: curve = self._curves[curve_id] if len(curve['x']) > 0: x_limit[0] = min(x_limit[0], curve['x'].min()) x_limit[1] = max(x_limit[1], curve['x'].max()) elif self._autoscroll: # get current width of plot x_limit = self.get_xlim() x_width = x_limit[1] - x_limit[0] # reset the upper x_limit so that we ignore the previous position x_limit[1] = -numpy.inf # get largest X value for curve_id in self._curves: curve = self._curves[curve_id] if len(curve['x']) > 0: x_limit[1] = max(x_limit[1], curve['x'].max()) # set lower limit based on width x_limit[0] = x_limit[1] - x_width else: # don't modify limit, or get it from plot x_limit = self.get_xlim() # set sane limits if our limits are infinite if numpy.isinf(x_limit[0]): x_limit[0] = 0.0 if numpy.isinf(x_limit[1]): x_limit[1] = 1.0 y_limit = [numpy.inf, -numpy.inf] if self._autoscale_y: # if we're extending the y limits, initialize them with the # current limits if self._autoscale_y & DataPlot.SCALE_EXTEND: y_limit = self.get_ylim() for curve_id in self._curves: curve = self._curves[curve_id] start_index = 0 end_index = len(curve['x']) # if we're scaling based on the visible window, find the # start and end indicies of our window if self._autoscale_y & DataPlot.SCALE_VISIBLE: # indexof x_limit[0] in curves['x'] start_index = curve['x'].searchsorted(x_limit[0]) # indexof x_limit[1] in curves['x'] end_index = curve['x'].searchsorted(x_limit[1]) # region here is cheap because it is a numpy view and not a # copy of the underlying data region = curve['y'][start_index:end_index] if len(region) > 0: y_limit[0] = min(y_limit[0], region.min()) y_limit[1] = max(y_limit[1], region.max()) # TODO: compute padding around new min and max values # ONLY consider data for new values; not # existing limits, or we'll add padding on top of old # padding in SCALE_EXTEND mode # # pad the min/max # TODO: invert this padding in get_ylim #ymin = limits[0] #ymax = limits[1] #delta = ymax - ymin if ymax != ymin else 0.1 #ymin -= .05 * delta #ymax += .05 * delta else: y_limit = self.get_ylim() # set sane limits if our limits are infinite if numpy.isinf(y_limit[0]): y_limit[0] = 0.0 if numpy.isinf(y_limit[1]): y_limit[1] = 1.0 self.set_xlim(x_limit) self.set_ylim(y_limit) def get_xlim(self): """get X limits""" if self._data_plot_widget: return self._data_plot_widget.get_xlim() else: qWarning("No plot widget; returning default X limits") return [0.0, 1.0] def set_xlim(self, limits): """set X limits""" if self._data_plot_widget: self._data_plot_widget.set_xlim(limits) else: qWarning("No plot widget; can't set X limits") def get_ylim(self): """get Y limits""" if self._data_plot_widget: return self._data_plot_widget.get_ylim() else: qWarning("No plot widget; returning default Y limits") return [0.0, 10.0] def set_ylim(self, limits): """set Y limits""" if self._data_plot_widget: self._data_plot_widget.set_ylim(limits) else: qWarning("No plot widget; can't set Y limits")
def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None): ''' Creates an input dialog. @param topic: the name of the topic @type topic: C{str} @param msg_type: the type of the topic @type msg_type: C{str} @raise Exception: if no topic class was found for the given type ''' QDialog.__init__(self, parent=parent) self._masteruri = masteruri masteruri_str = '' if masteruri is None else '[%s]' % masteruri self.setObjectName(' - '.join(['EchoDialog', topic, masteruri_str])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.setWindowTitle('%s %s %s' % ('Echo --- ' if not show_only_rate else 'Hz --- ', topic, masteruri_str)) self.resize(728, 512) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png") self.setWindowIcon(self.mIcon) self.topic = topic self.show_only_rate = show_only_rate self.lock = threading.RLock() self.last_printed_count = 0 self.msg_t0 = -1. self.msg_tn = 0 self.times = [] self.bytes = [] self.message_count = 0 self._state_message = '' self._scrapped_msgs = 0 self._scrapped_msgs_sl = 0 self._last_received_ts = 0 self.chars_limit = self.MESSAGE_CHARS_LIMIT self.receiving_hz = self.MESSAGE_HZ_LIMIT self.line_limit = self.MESSAGE_LINE_LIMIT self.field_filter_fn = None self._latched = False self._msgs = [] options = QWidget(self) if not show_only_rate: hLayout = QHBoxLayout(options) hLayout.setContentsMargins(1, 1, 1, 1) filter_string_label = QLabel('Strings:', self) hLayout.addWidget(filter_string_label) self.no_str_checkbox = no_str_checkbox = QCheckBox('hide') no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled) hLayout.addWidget(no_str_checkbox) self.combobox_reduce_ch = QComboBox(self) self.combobox_reduce_ch.addItems( [str(self.MESSAGE_LINE_LIMIT), '0', '80', '256', '1024']) self.combobox_reduce_ch.activated[str].connect( self.combobox_reduce_ch_activated) self.combobox_reduce_ch.setEditable(True) self.combobox_reduce_ch.setToolTip( "Set maximum line width. 0 disables the limit.") hLayout.addWidget(self.combobox_reduce_ch) filter_array_label = QLabel(' Arrays:', self) hLayout.addWidget(filter_array_label) self.no_arr_checkbox = no_arr_checkbox = QCheckBox('hide') no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled) hLayout.addWidget(no_arr_checkbox) # reduce_ch_label = QLabel('ch', self) # hLayout.addWidget(reduce_ch_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) filter_msg_label = QLabel('Msg:', self) hLayout.addWidget(filter_msg_label) # add combobox for displaying frequency of messages self.combobox_chars_hz = QComboBox(self) self.combobox_chars_hz.addItems( [str(self.MESSAGE_CHARS_LIMIT), '0', '500', '1000', '5000']) self.combobox_chars_hz.activated[str].connect( self.on_combobox_chars_activated) self.combobox_chars_hz.setEditable(True) self.combobox_chars_hz.setToolTip( "Set maximum displayed chars of a message. 0 disables the limit." ) hLayout.addWidget(self.combobox_chars_hz) displ_chars_label = QLabel('chars', self) hLayout.addWidget(displ_chars_label) # add combobox for displaying frequency of messages self.combobox_displ_hz = QComboBox(self) self.combobox_displ_hz.addItems([ str(self.MESSAGE_HZ_LIMIT), '0', '0.1', '1', '50', '100', '1000' ]) self.combobox_displ_hz.activated[str].connect( self.on_combobox_hz_activated) self.combobox_displ_hz.setEditable(True) self.combobox_displ_hz.setToolTip( "Set maximum displayed message rate in Hz. 0 disables the limit." ) hLayout.addWidget(self.combobox_displ_hz) displ_hz_label = QLabel('Hz', self) hLayout.addWidget(displ_hz_label) # add combobox for count of displayed messages self.combobox_msgs_count = QComboBox(self) self.combobox_msgs_count.addItems( [str(self.MAX_DISPLAY_MSGS), '0', '50', '100']) self.combobox_msgs_count.activated[str].connect( self.on_combobox_count_activated) self.combobox_msgs_count.setEditable(True) self.combobox_msgs_count.setToolTip( "Set maximum displayed message count. 0 disables the limit.") hLayout.addWidget(self.combobox_msgs_count) displ_count_label = QLabel('#', self) hLayout.addWidget(displ_count_label) # add topic control button for unsubscribe and subscribe self.topic_control_button = QToolButton(self) self.topic_control_button.setText('stop') self.topic_control_button.setIcon( QIcon(':/icons/deleket_deviantart_stop.png')) self.topic_control_button.clicked.connect( self.on_topic_control_btn_clicked) hLayout.addWidget(self.topic_control_button) # add clear button clearButton = QToolButton(self) clearButton.setText('clear') clearButton.clicked.connect(self.on_clear_btn_clicked) hLayout.addWidget(clearButton) self.verticalLayout.addWidget(options) self.display = QTextBrowser(self) self.display.setReadOnly(True) self.verticalLayout.addWidget(self.display) self.display.document().setMaximumBlockCount(500) self.max_displayed_msgs = self.MAX_DISPLAY_MSGS self._blocks_in_msg = None self.display.setOpenLinks(False) self.display.anchorClicked.connect(self._on_display_anchorClicked) self.status_label = QLabel('0 messages', self) self.verticalLayout.addWidget(self.status_label) # subscribe to the topic errmsg = '' try: self.__msg_class = message.get_message_class(msg_type) if not self.__msg_class: errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type # raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type) except Exception as e: self.__msg_class = None errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % ( msg_type, e) # raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e)) # variables for Subscriber self.msg_signal.connect(self._append_message) self.sub = None # vairables for SSH connection self.ssh_output_file = None self.ssh_error_file = None self.ssh_input_file = None self.text_signal.connect(self._append_text) self.text_hz_signal.connect(self._append_text_hz) self._current_msg = '' self._current_errmsg = '' self.text_error_signal.connect(self._append_error_text) # decide, which connection to open if use_ssh: self.__msg_class = None self._on_display_anchorClicked(QUrl(self._masteruri)) elif self.__msg_class is None: errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % ( errmsg) self.display.setText('<a href="%s">open using SSH</a>' % (masteruri)) self.display.append(errtxt) else: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self.print_hz_timer = QTimer() self.print_hz_timer.timeout.connect(self._on_calc_hz) self.print_hz_timer.start(1000) self._start_time = time.time()
def __init__(self, context): super(LogicalMarkerPlugin, self).__init__(context) # Create an image for the original map. This will never change. try: self.map_yaml_file_str = rospy.get_param("~map_file") self.data_directory = rospy.get_param("~data_directory") except KeyError: rospy.logfatal("~map_file and ~data_directory need to be set to use the logical marker") return map_image_location = getImageFileLocation(self.map_yaml_file_str) map = loadMapFromFile(self.map_yaml_file_str) locations_file = getLocationsFileLocationFromDataDirectory(self.data_directory) doors_file = getDoorsFileLocationFromDataDirectory(self.data_directory) objects_file = getObjectsFileLocationFromDataDirectory(self.data_directory) # Give QObjects reasonable names self.setObjectName('LogicalMarkerPlugin') # Create QWidget self.master_widget = QWidget() self.master_layout = QVBoxLayout(self.master_widget) # Main Functions - Doors, Locations, Objects self.function_layout = QHBoxLayout() self.master_layout.addLayout(self.function_layout) self.function_buttons = [] self.current_function = None for button_text in ['Locations', 'Doors', 'Objects']: button = QPushButton(button_text, self.master_widget) button.clicked[bool].connect(self.handle_function_button) button.setCheckable(True) self.function_layout.addWidget(button) self.function_buttons.append(button) self.function_layout.addStretch(1) self.master_layout.addWidget(self.get_horizontal_line()) # Subfunction toolbar self.subfunction_layout = QHBoxLayout() clearLayoutAndFixHeight(self.subfunction_layout) self.master_layout.addLayout(self.subfunction_layout) self.current_subfunction = None self.master_layout.addWidget(self.get_horizontal_line()) self.image = MapImage(map_image_location, self.master_widget) self.master_layout.addWidget(self.image) self.master_layout.addWidget(self.get_horizontal_line()) # Configuration toolbar self.configuration_layout = QHBoxLayout() clearLayoutAndFixHeight(self.configuration_layout) self.master_layout.addLayout(self.configuration_layout) # Add a stretch at the bottom. self.master_layout.addStretch(1) self.master_widget.setObjectName('LogicalMarkerPluginUI') if context.serial_number() > 1: self.master_widget.setWindowTitle(self.master_widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.master_widget) # Activate the functions self.functions = {} self.functions['Locations'] = LocationFunction(locations_file, map, self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Doors'] = DoorFunction(doors_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image) self.functions['Objects'] = ObjectFunction(objects_file, map, self.functions['Locations'], self.master_widget, self.subfunction_layout, self.configuration_layout, self.image)
class QErrorStatusWidget(QWidget): error_status_signal = Signal(ErrorStatus) def __init__(self, parent=None, subscribe=False): QWidget.__init__(self, parent) # start widget vbox = QVBoxLayout() vbox.setMargin(0) vbox.setContentsMargins(0, 0, 0, 0) # add error status text edit self.error_status_text_box = QErrorStatusTextBox() self.error_status_text_box_layout = QHBoxLayout() self.error_status_text_box_layout.addWidget(self.error_status_text_box) vbox.addLayout(self.error_status_text_box_layout) # add panel hbox = QHBoxLayout() # clear push button self.execute_command = QPushButton("Clear") self.execute_command.clicked.connect(self.error_status_text_box.clear) hbox.addWidget(self.execute_command) hbox.addStretch() # hide window checkbox hide_window_check_box = QCheckBox("Hide") hide_window_check_box.stateChanged.connect(self.state_changed) hbox.addWidget(hide_window_check_box) # end panel vbox.addLayout(hbox) # end widget self.setLayout(vbox) #self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) # subscriber if subscribe: self.error_status_sub = rospy.Subscriber("error_status", ErrorStatus, self.error_status_callback) self.subscribed = subscribe # connect signal slot internally to prevent crash by subscriber self.error_status_signal.connect(self.append_error_status) def __del__(self): if self.subscribed: self.error_status_sub.unregister() def error_status_callback(self, error_status): self.error_status_signal.emit(error_status) def get_text_box(self): return self.error_status_text_box @Slot(str, QColor) def set_text(self, msg, color): self.error_status_text_box.clear() self.append(msg, color) @Slot(str, QColor) def append(self, msg, color): self.error_status_text_box.out_log(msg, color) @Slot(ErrorStatus) def set_error_status(self, error_status): self.error_status_text_box.set_error_status(error_status) @Slot(ErrorStatus) def append_error_status(self, error_status): self.error_status_text_box.append_error_status(error_status) @Slot(int) def state_changed(self, state): if state == Qt.Unchecked: self.error_status_text_box_layout.addWidget(self.error_status_text_box) self.error_status_text_box.setVisible(True) elif state == Qt.Checked: self.error_status_text_box_layout.removeWidget(self.error_status_text_box) self.error_status_text_box.setVisible(False)
class CalibrationMovementsGUI(QWidget): NOT_INITED_YET = 0 BAD_PLAN = 1 GOOD_PLAN = 2 MOVED_TO_POSE = 3 BAD_STARTING_POSITION = 4 GOOD_STARTING_POSITION = 5 CHECKING_STARTING_POSITION = 6 MOVEMENT_FAILED = 7 def __init__(self): super(CalibrationMovementsGUI, self).__init__() self.handeye_client = HandeyeClient() self.current_target_pose = -1 # -1 is home self.target_poses = None self.plan_was_successful = None self.state = CalibrationMovementsGUI.NOT_INITED_YET self.layout = QVBoxLayout() self.labels_layout = QHBoxLayout() self.buttons_layout = QHBoxLayout() self.progress_bar = QProgressBar() self.pose_number_lbl = QLabel('0/0') self.bad_plan_lbl = QLabel('No plan yet') self.bad_plan_lbl.setAlignment(Qt.AlignCenter) self.guide_lbl = QLabel('Hello') self.guide_lbl.setWordWrap(True) self.check_start_pose_btn = QPushButton('Check starting pose') self.check_start_pose_btn.clicked.connect( self.handle_check_current_state) self.next_pose_btn = QPushButton('Next Pose') self.next_pose_btn.clicked.connect(self.handle_next_pose) self.plan_btn = QPushButton('Plan') self.plan_btn.clicked.connect(self.handle_plan) self.execute_btn = QPushButton('Execute') self.execute_btn.clicked.connect(self.handle_execute) self.labels_layout.addWidget(self.pose_number_lbl) self.labels_layout.addWidget(self.bad_plan_lbl) self.buttons_layout.addWidget(self.check_start_pose_btn) self.buttons_layout.addWidget(self.next_pose_btn) self.buttons_layout.addWidget(self.plan_btn) self.buttons_layout.addWidget(self.execute_btn) self.layout.addWidget(self.progress_bar) self.layout.addLayout(self.labels_layout) self.layout.addWidget(self.guide_lbl) self.layout.addLayout(self.buttons_layout) self.setLayout(self.layout) self.plan_btn.setEnabled(False) self.execute_btn.setEnabled(False) self.setWindowTitle('Local Mover') self.show() def update_ui(self): if self.target_poses: count_target_poses = len(self.target_poses) else: count_target_poses = 1 self.progress_bar.setMaximum(count_target_poses) self.progress_bar.setValue(self.current_target_pose + 1) self.pose_number_lbl.setText('{}/{}'.format( self.current_target_pose + 1, count_target_poses)) if self.state == CalibrationMovementsGUI.BAD_PLAN: self.bad_plan_lbl.setText('BAD plan!! Don\'t do it!!!!') self.bad_plan_lbl.setStyleSheet('QLabel { background-color : red}') elif self.state == CalibrationMovementsGUI.GOOD_PLAN: self.bad_plan_lbl.setText('Good plan') self.bad_plan_lbl.setStyleSheet( 'QLabel { background-color : green}') else: self.bad_plan_lbl.setText('No plan yet') self.bad_plan_lbl.setStyleSheet('') if self.state == CalibrationMovementsGUI.NOT_INITED_YET: self.guide_lbl.setText( 'Bring the robot to a plausible position and check if it is a suitable starting pose' ) elif self.state == CalibrationMovementsGUI.CHECKING_STARTING_POSITION: self.guide_lbl.setText( 'Checking if the robot can translate and rotate in all directions from the current pose' ) elif self.state == CalibrationMovementsGUI.BAD_STARTING_POSITION: self.guide_lbl.setText('Cannot calibrate from current position') elif self.state == CalibrationMovementsGUI.GOOD_STARTING_POSITION: self.guide_lbl.setText('Ready to start: click on next pose') elif self.state == CalibrationMovementsGUI.GOOD_PLAN: self.guide_lbl.setText( 'The plan seems good: press execute to move the robot') elif self.state == CalibrationMovementsGUI.BAD_PLAN: self.guide_lbl.setText('Planning failed: try again') elif self.state == CalibrationMovementsGUI.MOVED_TO_POSE: self.guide_lbl.setText( 'Pose reached: take a sample and go on to next pose') can_plan = self.state == CalibrationMovementsGUI.GOOD_STARTING_POSITION self.plan_btn.setEnabled(can_plan) can_move = self.state == CalibrationMovementsGUI.GOOD_PLAN self.execute_btn.setEnabled(can_move) QCoreApplication.processEvents() def handle_check_current_state(self): self.state = CalibrationMovementsGUI.CHECKING_STARTING_POSITION self.update_ui() res = self.handeye_client.check_starting_pose() if res.can_calibrate: self.state = CalibrationMovementsGUI.GOOD_STARTING_POSITION else: self.state = CalibrationMovementsGUI.BAD_STARTING_POSITION self.current_target_pose = res.target_poses.current_target_pose_index self.target_poses = res.target_poses.target_poses self.plan_was_successful = None self.update_ui() def handle_next_pose(self): res = self.handeye_client.select_target_pose(self.current_target_pose + 1) self.current_target_pose = res.target_poses.current_target_pose_index self.target_poses = res.target_poses.target_poses self.plan_was_successful = None self.state = CalibrationMovementsGUI.GOOD_STARTING_POSITION self.update_ui() def handle_plan(self): self.guide_lbl.setText( 'Planning to the next position. Click on execute when a good one was found' ) res = self.handeye_client.plan_to_selected_target_pose() self.plan_was_successful = res.success if self.plan_was_successful: self.state = CalibrationMovementsGUI.GOOD_PLAN else: self.state = CalibrationMovementsGUI.BAD_PLAN self.update_ui() def handle_execute(self): if self.plan_was_successful: self.guide_lbl.setText('Going to the selected pose') res = self.handeye_client.execute_plan() if res.success: self.state = CalibrationMovementsGUI.MOVED_TO_POSE else: self.state = CalibrationMovementsGUI.MOVEMENT_FAILED self.update_ui()
class DataPlot(QWidget): """A widget for displaying a plot of data The DataPlot widget displays a plot, on one of several plotting backends, depending on which backend(s) are available at runtime. It currently supports PyQtGraph, MatPlot and QwtPlot backends. The DataPlot widget manages the plot backend internally, and can save and restore the internal state using `save_settings` and `restore_settings` functions. Currently, the user MUST call `restore_settings` before using the widget, to cause the creation of the enclosed plotting widget. """ # plot types in order of priority plot_types = [ { 'title': 'PyQtGraph', 'widget_class': PyQtGraphDataPlot, 'description': 'Based on PyQtGraph\n- installer: http://luke.campagnola.me/code/pyqtgraph\n', 'enabled': PyQtGraphDataPlot is not None, }, { 'title': 'MatPlot', 'widget_class': MatDataPlot, 'description': 'Based on MatPlotLib\n- needs most CPU\n- needs matplotlib >= 1.1.0\n- if using PySide: PySide > 1.1.0\n', 'enabled': MatDataPlot is not None, }, { 'title': 'QwtPlot', 'widget_class': QwtDataPlot, 'description': 'Based on QwtPlot\n- does not use timestamps\n- uses least CPU\n- needs Python Qwt bindings\n', 'enabled': QwtDataPlot is not None, }, ] # pre-defined colors: RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) SCALE_ALL = 1 SCALE_VISIBLE = 2 SCALE_EXTEND = 4 _colors = [ Qt.blue, Qt.red, Qt.cyan, Qt.magenta, Qt.green, Qt.darkYellow, Qt.black, Qt.darkCyan, Qt.darkRed, Qt.gray ] limits_changed = Signal() _redraw = Signal() _add_curve = Signal(str, str, 'QColor', bool) def __init__(self, parent=None): """Create a new, empty DataPlot This will raise a RuntimeError if none of the supported plotting backends can be found """ super(DataPlot, self).__init__(parent) self._plot_index = 0 self._color_index = 0 self._markers_on = False self._autoscroll = True self._autoscale_x = True self._autoscale_y = DataPlot.SCALE_ALL # the backend widget that we're trying to hide/abstract self._data_plot_widget = None self._curves = {} self._vline = None self._redraw.connect(self._do_redraw) self._layout = QHBoxLayout() self.setLayout(self._layout) enabled_plot_types = [pt for pt in self.plot_types if pt['enabled']] if not enabled_plot_types: version_info = ' and PySide > 1.1.0' if QT_BINDING == 'pyside' else '' raise RuntimeError( 'No usable plot type found. Install at least one of: PyQtGraph, MatPlotLib (at least 1.1.0%s) or Python-Qwt5.' % version_info) self._switch_data_plot_widget(self._plot_index) self.show() def _switch_data_plot_widget(self, plot_index, markers_on=False): """Internal method for activating a plotting backend by index""" # check if selected plot type is available if not self.plot_types[plot_index]['enabled']: # find other available plot type for index, plot_type in enumerate(self.plot_types): if plot_type['enabled']: plot_index = index break self._plot_index = plot_index self._markers_on = markers_on selected_plot = self.plot_types[plot_index] if self._data_plot_widget: x_limits = self.get_xlim() y_limits = self.get_ylim() self._layout.removeWidget(self._data_plot_widget) self._data_plot_widget.close() self._data_plot_widget = None else: x_limits = [0.0, 10.0] y_limits = [-0.001, 0.001] self._data_plot_widget = selected_plot['widget_class'](self) self._data_plot_widget.limits_changed.connect(self.limits_changed) self._add_curve.connect(self._data_plot_widget.add_curve) self._layout.addWidget(self._data_plot_widget) # restore old data for curve_id in self._curves: curve = self._curves[curve_id] self._data_plot_widget.add_curve(curve_id, curve['name'], curve['color'], markers_on) if self._vline: self.vline(*self._vline) self.set_xlim(x_limits) self.set_ylim(y_limits) self.redraw() def _switch_plot_markers(self, markers_on): self._markers_on = markers_on self._data_plot_widget._color_index = 0 for curve_id in self._curves: self._data_plot_widget.remove_curve(curve_id) curve = self._curves[curve_id] self._data_plot_widget.add_curve(curve_id, curve['name'], curve['color'], markers_on) self.redraw() # interface out to the managing GUI component: get title, save, restore, # etc def getTitle(self): """get the title of the current plotting backend""" return self.plot_types[self._plot_index]['title'] def save_settings(self, plugin_settings, instance_settings): """Save the settings associated with this widget Currently, this is just the plot type, but may include more useful data in the future""" instance_settings.set_value('plot_type', self._plot_index) xlim = self.get_xlim() ylim = self.get_ylim() # convert limits to normal arrays of floats; some backends return numpy # arrays xlim = [float(x) for x in xlim] ylim = [float(y) for y in ylim] instance_settings.set_value('x_limits', pack(xlim)) instance_settings.set_value('y_limits', pack(ylim)) def restore_settings(self, plugin_settings, instance_settings): """Restore the settings for this widget Currently, this just restores the plot type.""" self._switch_data_plot_widget( int(instance_settings.value('plot_type', 0))) xlim = unpack(instance_settings.value('x_limits', [])) ylim = unpack(instance_settings.value('y_limits', [])) if xlim: # convert limits to an array of floats; they're often lists of # strings try: xlim = [float(x) for x in xlim] self.set_xlim(xlim) except: qWarning("Failed to restore X limits") if ylim: try: ylim = [float(y) for y in ylim] self.set_ylim(ylim) except: qWarning("Failed to restore Y limits") def doSettingsDialog(self): """Present the user with a dialog for choosing the plot backend This displays a SimpleSettingsDialog asking the user to choose a plot type, gets the result, and updates the plot type as necessary This method is blocking""" marker_settings = [{ 'title': 'Show Plot Markers', 'description': 'Warning: Displaying markers in rqt_plot may cause\n \t high cpu load, especially using PyQtGraph\n', 'enabled': True, }] if self._markers_on: selected_checkboxes = [0] else: selected_checkboxes = [] dialog = SimpleSettingsDialog(title='Plot Options') dialog.add_exclusive_option_group(title='Plot Type', options=self.plot_types, selected_index=self._plot_index) dialog.add_checkbox_group(title='Plot Markers', options=marker_settings, selected_indexes=selected_checkboxes) [plot_type, checkboxes] = dialog.get_settings() if plot_type is not None and plot_type[ 'selected_index'] is not None and self._plot_index != plot_type[ 'selected_index']: self._switch_data_plot_widget(plot_type['selected_index'], 0 in checkboxes['selected_indexes']) else: if checkboxes is not None and self._markers_on != ( 0 in checkboxes['selected_indexes']): self._switch_plot_markers(0 in checkboxes['selected_indexes']) # interface out to the managing DATA component: load data, update data, # etc def autoscroll(self, enabled=True): """Enable or disable autoscrolling of the plot""" self._autoscroll = enabled def redraw(self): self._redraw.emit() def _do_redraw(self): """Redraw the underlying plot This causes the underlying plot to be redrawn. This is usually used after adding or updating the plot data""" if self._data_plot_widget: self._merged_autoscale() for curve_id in self._curves: curve = self._curves[curve_id] self._data_plot_widget.set_values(curve_id, curve['x'], curve['y']) self._data_plot_widget.redraw() def _get_curve(self, curve_id): if curve_id in self._curves: return self._curves[curve_id] else: raise DataPlotException("No curve named %s in this DataPlot" % (curve_id)) def add_curve(self, curve_id, curve_name, data_x, data_y): """Add a new, named curve to this plot Add a curve named `curve_name` to the plot, with initial data series `data_x` and `data_y`. Future references to this curve should use the provided `curve_id` Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ curve_color = QColor(self._colors[self._color_index % len(self._colors)]) self._color_index += 1 self._curves[curve_id] = { 'x': numpy.array(data_x), 'y': numpy.array(data_y), 'name': curve_name, 'color': curve_color } if self._data_plot_widget: self._add_curve.emit(curve_id, curve_name, curve_color, self._markers_on) def remove_curve(self, curve_id): """Remove the specified curve from this plot""" # TODO: do on UI thread with signals if curve_id in self._curves: del self._curves[curve_id] if self._data_plot_widget: self._data_plot_widget.remove_curve(curve_id) def update_values(self, curve_id, values_x, values_y): """Append new data to an existing curve `values_x` and `values_y` will be appended to the existing data for `curve_id` Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ curve = self._get_curve(curve_id) curve['x'] = numpy.append(curve['x'], values_x) curve['y'] = numpy.append(curve['y'], values_y) # sort resulting data, so we can slice it later sort_order = curve['x'].argsort() curve['x'] = curve['x'][sort_order] curve['y'] = curve['y'][sort_order] def clear_values(self, curve_id=None): """Clear the values for the specified curve, or all curves This will erase the data series associaed with `curve_id`, or all curves if `curve_id` is not present or is None Note that the plot is not redraw automatically; call `redraw()` to make any changes visible to the user. """ # clear internal curve representation if curve_id: curve = self._get_curve(curve_id) curve['x'] = numpy.array([]) curve['y'] = numpy.array([]) else: for curve_id in self._curves: self._curves[curve_id]['x'] = numpy.array([]) self._curves[curve_id]['y'] = numpy.array([]) def vline(self, x, color=RED): """Draw a vertical line on the plot Draw a line a position X, with the given color @param x: position of the vertical line to draw @param color: optional parameter specifying the color, as tuple of RGB values from 0 to 255 """ self._vline = (x, color) if self._data_plot_widget: self._data_plot_widget.vline(x, color) # autoscaling methods def set_autoscale(self, x=None, y=None): """Change autoscaling of plot axes if a parameter is not passed, the autoscaling setting for that axis is not changed @param x: enable or disable autoscaling for X @param y: set autoscaling mode for Y """ if x is not None: self._autoscale_x = x if y is not None: self._autoscale_y = y # autoscaling: adjusting the plot bounds fit the data # autoscrollig: move the plot X window to show the most recent data # # what order do we do these adjustments in? # * assuming the various stages are enabled: # * autoscale X to bring all data into view # * else, autoscale X to determine which data we're looking at # * autoscale Y to fit the data we're viewing # # * autoscaling of Y might have several modes: # * scale Y to fit the entire dataset # * scale Y to fit the current view # * increase the Y scale to fit the current view # # TODO: incrmenetal autoscaling: only update the autoscaling bounds # when new data is added def _merged_autoscale(self): x_limit = [numpy.inf, -numpy.inf] if self._autoscale_x: for curve_id in self._curves: curve = self._curves[curve_id] if len(curve['x']) > 0: x_limit[0] = min(x_limit[0], curve['x'].min()) x_limit[1] = max(x_limit[1], curve['x'].max()) elif self._autoscroll: # get current width of plot x_limit = self.get_xlim() x_width = x_limit[1] - x_limit[0] # reset the upper x_limit so that we ignore the previous position x_limit[1] = -numpy.inf # get largest X value for curve_id in self._curves: curve = self._curves[curve_id] if len(curve['x']) > 0: x_limit[1] = max(x_limit[1], curve['x'].max()) # set lower limit based on width x_limit[0] = x_limit[1] - x_width else: # don't modify limit, or get it from plot x_limit = self.get_xlim() # set sane limits if our limits are infinite if numpy.isinf(x_limit[0]): x_limit[0] = 0.0 if numpy.isinf(x_limit[1]): x_limit[1] = 1.0 y_limit = [numpy.inf, -numpy.inf] if self._autoscale_y: # if we're extending the y limits, initialize them with the # current limits if self._autoscale_y & DataPlot.SCALE_EXTEND: y_limit = self.get_ylim() for curve_id in self._curves: curve = self._curves[curve_id] start_index = 0 end_index = len(curve['x']) # if we're scaling based on the visible window, find the # start and end indicies of our window if self._autoscale_y & DataPlot.SCALE_VISIBLE: # indexof x_limit[0] in curves['x'] start_index = curve['x'].searchsorted(x_limit[0]) # indexof x_limit[1] in curves['x'] end_index = curve['x'].searchsorted(x_limit[1]) # region here is cheap because it is a numpy view and not a # copy of the underlying data region = curve['y'][start_index:end_index] if len(region) > 0: y_limit[0] = min(y_limit[0], region.min()) y_limit[1] = max(y_limit[1], region.max()) # TODO: compute padding around new min and max values # ONLY consider data for new values; not # existing limits, or we'll add padding on top of old # padding in SCALE_EXTEND mode # # pad the min/max # TODO: invert this padding in get_ylim #ymin = limits[0] #ymax = limits[1] #delta = ymax - ymin if ymax != ymin else 0.1 #ymin -= .05 * delta #ymax += .05 * delta else: y_limit = self.get_ylim() # set sane limits if our limits are infinite if numpy.isinf(y_limit[0]): y_limit[0] = 0.0 if numpy.isinf(y_limit[1]): y_limit[1] = 1.0 self.set_xlim(x_limit) self.set_ylim(y_limit) def get_xlim(self): """get X limits""" if self._data_plot_widget: return self._data_plot_widget.get_xlim() else: qWarning("No plot widget; returning default X limits") return [0.0, 1.0] def set_xlim(self, limits): """set X limits""" if self._data_plot_widget: self._data_plot_widget.set_xlim(limits) else: qWarning("No plot widget; can't set X limits") def get_ylim(self): """get Y limits""" if self._data_plot_widget: return self._data_plot_widget.get_ylim() else: qWarning("No plot widget; returning default Y limits") return [0.0, 10.0] def set_ylim(self, limits): """set Y limits""" if self._data_plot_widget: self._data_plot_widget.set_ylim(limits) else: qWarning("No plot widget; can't set Y limits")