Home | Trees | Indices | Help |
---|
|
1 # Software License Agreement (BSD License) 2 # 3 # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 4 # All rights reserved. 5 # 6 # Redistribution and use in source and binary forms, with or without 7 # modification, are permitted provided that the following conditions 8 # are met: 9 # 10 # * Redistributions of source code must retain the above copyright 11 # notice, this list of conditions and the following disclaimer. 12 # * Redistributions in binary form must reproduce the above 13 # copyright notice, this list of conditions and the following 14 # disclaimer in the documentation and/or other materials provided 15 # with the distribution. 16 # * Neither the name of Fraunhofer nor the names of its 17 # contributors may be used to endorse or promote products derived 18 # from this software without specific prior written permission. 19 # 20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 # POSSIBILITY OF SUCH DAMAGE. 32 from python_qt_binding import loadUi 33 from python_qt_binding.QtCore import QSize, Qt, Signal 34 try: 35 from python_qt_binding.QtGui import QSortFilterProxyModel 36 from python_qt_binding.QtGui import QAbstractItemDelegate, QStyledItemDelegate, QHBoxLayout 37 from python_qt_binding.QtGui import QCheckBox, QComboBox, QDockWidget, QFileDialog, QLineEdit, QSpinBox, QDoubleSpinBox, QPushButton, QWidget 38 from python_qt_binding.QtGui import QStyleOptionViewItemV4 as QStyleOptionViewItem 39 except: 40 from python_qt_binding.QtWidgets import QAbstractItemDelegate, QStyledItemDelegate, QHBoxLayout 41 from python_qt_binding.QtWidgets import QCheckBox, QComboBox, QDockWidget, QFileDialog, QLineEdit, QSpinBox, QDoubleSpinBox, QPushButton, QWidget 42 from python_qt_binding.QtWidgets import QStyleOptionViewItem 43 from python_qt_binding.QtCore import QSortFilterProxyModel 44 import os 45 46 import node_manager_fkie as nm 47 48 from .settings_model import SettingsModel, SettingsValueItem 49 5052 ''' 53 Settings widget to handle the settings changes. The changes will direct change 54 the settings of the GUI. 55 ''' 56243 24458 ''' 59 Creates the window, connects the signals and init the class. 60 ''' 61 QDockWidget.__init__(self, parent) 62 # load the UI file 63 settings_dock_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'SettingsDockWidget.ui') 64 loadUi(settings_dock_file, self) 65 # initialize the settings view model 66 self.settings_model = SettingsModel() 67 self.settings_proxyModel = QSortFilterProxyModel(self) 68 self.settings_proxyModel.setSourceModel(self.settings_model) 69 self.settingsTreeView.setModel(self.settings_proxyModel) 70 self.settingsTreeView.setAlternatingRowColors(True) 71 for i, (_, width) in enumerate(SettingsModel.header): 72 self.settingsTreeView.setColumnWidth(i, width) 73 self.item_delegate = ItemDelegate() 74 self.item_delegate.settings_path_changed_signal.connect(self.reload_settings) 75 self.settingsTreeView.setItemDelegateForColumn(1, self.item_delegate) 76 self.reload_settings()7779 ''' 80 Load the current settings data into the model. The settings itself will not 81 be loaded. 82 ''' 83 settings = {'Default user:': ({'value': nm.settings().default_user, 84 'settings': nm.settings(), 85 'attrname': 'default_user', 86 'value_default': nm.settings().USER_DEFAULT, 87 'tooltip': '<p>The user used for ssh connection to remote hosts</p>' 88 },), 89 'Launch history length:': ({'value': nm.settings().launch_history_length, 90 'settings': nm.settings(), 91 'attrname': 'launch_history_length', 92 'value_default': nm.settings().LAUNCH_HISTORY_LENGTH, 93 'value_min': 0, 94 'value_max': 25, 95 'tooltip': '<p>The count of recent ' 96 'loaded launch files displayed in the root ' 97 'of the <span style=" font-weight:600;">launch ' 98 'files</span> view.</p>' 99 },), 100 'Param history length:': ({'value': nm.settings().param_history_length, 101 'settings': nm.settings(), 102 'attrname': 'param_history_length', 103 'value_default': nm.settings().PARAM_HISTORY_LENGTH, 104 'value_min': 0, 105 'value_max': 25, 106 'tooltip': '<p>The count of parameters stored which ' 107 'are entered in a parameter dialog (Launch file arguments, ' 108 'paramter server, publishing to a topic, service call)</p>' 109 },), 110 111 'Settings path:': ({'value': nm.settings().cfg_path, 112 'settings': nm.settings(), 113 'attrname': 'cfg_path', 114 'edit_type': SettingsValueItem.EDIT_TYPE_FOLDER, 115 'value_default': nm.settings().CFG_PATH, 116 'tooltip': '' 117 },), 118 'Robot icon path:': ({'value': nm.settings().robots_path, 119 'settings': nm.settings(), 120 'attrname': 'robots_path', 121 'edit_type': SettingsValueItem.EDIT_TYPE_FOLDER, 122 'value_default': nm.settings().ROBOTS_DIR, 123 'tooltip': '<p>The path to the folder with robot images ' 124 '(<span style=" font-weight:600;">.png</span>).' 125 'The images with robot name will be displayed in the ' 126 'info bar.</p>' 127 },), 128 'Show files extensions:': ({'value': ', '.join(nm.settings().launch_view_file_ext), 129 'settings': nm.settings(), 130 'attrname': 'launch_view_file_ext', 131 'value_default': ', '.join(nm.settings().LAUNCH_VIEW_EXT), 132 'tooltip': '<p>Files that are displayed next to Launch ' 133 'files in the <span style="font-weight:600;">' 134 'launch files</span> view</p>' 135 },), 136 'Store window layout:': ({'value': nm.settings().store_geometry, 137 'settings': nm.settings(), 138 'attrname': 'store_geometry', 139 'value_default': nm.settings().STORE_GEOMETRY, 140 'tooltip': '' 141 },), 142 'Movable dock widgets:': ({'value': nm.settings().movable_dock_widgets, 143 'settings': nm.settings(), 144 'attrname': 'movable_dock_widgets', 145 'value_default': nm.settings().MOVABLE_DOCK_WIDGETS, 146 'tooltip': 'On false you can\'t reorganize docking widgets. Needs restart!', 147 'need_restart': True 148 },), 149 'Max time difference:': ({'value': nm.settings().max_timediff, 150 'settings': nm.settings(), 151 'attrname': 'max_timediff', 152 'value_default': nm.settings().MAX_TIMEDIFF, 153 'value_step': 0.1, 154 'tooltip': '<p>Shows a warning if the time difference to ' 155 'remote host is greater than this value</p>' 156 },), 157 'Autoupdate:': ({'value': nm.settings().autoupdate, 158 'settings': nm.settings(), 159 'attrname': 'autoupdate', 160 'value_default': nm.settings().AUTOUPDATE, 161 'tooltip': '<p>By default node manager updates the current ' 162 'state on changes. You can deactivate this behavior to ' 163 'reduce the network load. If autoupdate is deactivated ' 164 'you must refresh the state manually.</p>' 165 },), 166 'Start sync with discovery:': ({'value': nm.settings().start_sync_with_discovery, 167 'settings': nm.settings(), 168 'attrname': 'start_sync_with_discovery', 169 'value_default': nm.settings().START_SYNC_WITH_DISCOVERY, 170 'tooltip': "<p>Sets 'start sync' in 'Start' master discovery " 171 "dialog to True, if this option is set to true.</p>" 172 },), 173 'Confirm exit when closing:': ({'value': nm.settings().confirm_exit_when_closing, 174 'settings': nm.settings(), 175 'attrname': 'confirm_exit_when_closing', 176 'value_default': nm.settings().CONFIRM_EXIT_WHEN_CLOSING, 177 'tooltip': "<p>Shows on closing of node_manager a dialog to stop " 178 "all ROS nodes if this option is set to true.</p>" 179 },), 180 'Highlight xml blocks:': ({'value': nm.settings().highlight_xml_blocks, 181 'settings': nm.settings(), 182 'attrname': 'highlight_xml_blocks', 183 'value_default': nm.settings().HIGHLIGHT_XML_BLOCKS, 184 'tooltip': "<p>Highlights the current selected XML block, while " 185 "editing ROS launch file.</p>" 186 },), 187 'Colorize hosts:': ({'value': nm.settings().colorize_hosts, 188 'settings': nm.settings(), 189 'attrname': 'colorize_hosts', 190 'value_default': nm.settings().COLORIZE_HOSTS, 191 'tooltip': "<p>Determine automatic a default color for each host if True. " 192 "Manually setted color will be prefered. You can select the color by " 193 "double-click on hostname in description panel. To remove a setted color " 194 "delete it manually from $HOME/.ros/node_manager/settings.ini</p>" 195 },), 196 'Check for nodelets at start:': ({'value': nm.settings().check_for_nodelets_at_start, 197 'settings': nm.settings(), 198 'attrname': 'check_for_nodelets_at_start', 199 'value_default': nm.settings().CHECK_FOR_NODELETS_AT_START, 200 'tooltip': "Test the startlist for nodelet manager and all nodelets. " 201 "If one of the nodes is not in the list a dialog is displayed with " 202 "proposal to start other nodes, too.</p>" 203 },), 204 'Show noscreen error:': ({'value': nm.settings().show_noscreen_error, 205 'settings': nm.settings(), 206 'attrname': 'show_noscreen_error', 207 'value_default': nm.settings().SHOW_NOSCREEN_ERROR, 208 'tooltip': "Shows an error if requested screen for a node is not available.</p>" 209 },), 210 'Show domain suffix:': ({'value': nm.settings().show_domain_suffix, 211 'settings': nm.settings(), 212 'attrname': 'show_domain_suffix', 213 'value_default': nm.settings().SHOW_DOMAIN_SUFFIX, 214 'tooltip': "<p>Shows the domain suffix of the host in the host description" 215 " panel and node tree view.</p>" 216 },), 217 'Transpose pub/sub description:': ({'value': nm.settings().transpose_pub_sub_descr, 218 'settings': nm.settings(), 219 'attrname': 'transpose_pub_sub_descr', 220 'value_default': nm.settings().TRANSPOSE_PUB_SUB_DESCR, 221 'tooltip': "<p>Transpose publisher/subscriber in description dock.</p>" 222 },), 223 'Timeout close dialog:': ({'value': nm.settings().timeout_close_dialog, 224 'settings': nm.settings(), 225 'attrname': 'timeout_close_dialog', 226 'value_default': nm.settings().TIMEOUT_CLOSE_DIALOG, 227 'value_step': 1., 228 'tooltip': "<p>Timeout in seconds to close dialog while closing Node Manager." 229 " 0 disables autoclose functionality.</p>" 230 },), 231 'Group nodes by namespace:': ({'value': nm.settings().group_nodes_by_namespace, 232 'settings': nm.settings(), 233 'attrname': 'group_nodes_by_namespace', 234 'value_default': nm.settings().GROUP_BY_NAMESPACE, 235 'tooltip': "<p>Split namespace of the node by / and create groups for each name part.</p>", 236 'need_restart': True 237 },) 238 } 239 self.settings_model.init_settings(settings) 240 # self.settingsTreeView.setSortingEnabled(True) 241 self.settingsTreeView.sortByColumn(0, Qt.AscendingOrder) 242 self.settingsTreeView.expandAll()246 ''' 247 This ItemDelegate provides editors for different setting types in settings view. 248 ''' 249 250 settings_path_changed_signal = Signal() 251 252 reload_settings = False 253343 344255 ''' 256 Creates a editor in the TreeView depending on type of the settings data. 257 ''' 258 item = self._itemFromIndex(index) 259 if item.edit_type() == SettingsValueItem.EDIT_TYPE_AUTODETECT: 260 if isinstance(item.value(), bool): 261 box = QCheckBox(parent) 262 box.setFocusPolicy(Qt.StrongFocus) 263 box.setAutoFillBackground(True) 264 box.stateChanged.connect(self.edit_finished) 265 return box 266 elif isinstance(item.value(), int): 267 box = QSpinBox(parent) 268 box.setValue(item.value()) 269 if not item.value_min() is None: 270 box.setMinimum(item.value_min()) 271 if not item.value_max() is None: 272 box.setMaximum(item.value_max()) 273 if not item.value_step() is None: 274 box.setSingleStep(item.value_step()) 275 return box 276 elif isinstance(item.value(), float): 277 box = QDoubleSpinBox(parent) 278 box.setValue(item.value()) 279 if not item.value_min() is None: 280 box.setMinimum(item.value_min()) 281 if not item.value_max() is None: 282 box.setMaximum(item.value_max()) 283 if not item.value_step() is None: 284 box.setSingleStep(item.value_step()) 285 box.setDecimals(3) 286 return box 287 elif item.edit_type() == SettingsValueItem.EDIT_TYPE_FOLDER: 288 editor = PathEditor(item.value(), parent) 289 editor.editing_finished_signal.connect(self.edit_finished) 290 return editor 291 elif item.edit_type() == SettingsValueItem.EDIT_TYPE_LIST: 292 box = QComboBox(parent) 293 box.addItems(item.value_list()) 294 index = box.findText(item.value()) 295 if index >= 0: 296 box.setCurrentIndex(index) 297 box.setEditable(False) 298 return box 299 return QStyledItemDelegate.createEditor(self, parent, option, index)300 301 # def setEditorData(self, editor, index): 302 # print "setEditorData" 303 # QStyledItemDelegate.setEditorData(self, editor, index) 304 305 # def updateEditorGeometry(self, editor, option, index): 306 # print "updateEditorGeometry", option.rect.width() 307 # editor.setMaximumSize(option.rect.width(), option.rect.height()) 308 # QStyledItemDelegate.updateEditorGeometry(self, editor, option, index) 309311 if isinstance(editor, PathEditor): 312 cfg_path = nm.settings().cfg_path 313 model.setData(index, editor.path) 314 self.reload_settings = (cfg_path != nm.settings().cfg_path) 315 else: 316 QStyledItemDelegate.setModelData(self, editor, model, index)317319 ''' 320 Determines and returns the size of the text after the format. 321 @see: U{http://www.pyside.org/docs/pyside/PySide/QtGui/QAbstractItemDelegate.html#PySide.QtGui.QAbstractItemDelegate} 322 ''' 323 options = QStyleOptionViewItem(option) 324 self.initStyleOption(options, index) 325 return QSize(options.rect.width(), 25)326328 editor = self.sender() 329 # The commitData signal must be emitted when we've finished editing 330 # and need to write our changed back to the model. 331 self.commitData.emit(editor) 332 self.closeEditor.emit(editor, QAbstractItemDelegate.NoHint) 333 if self.reload_settings: 334 self.reload_settings = False 335 self.settings_path_changed_signal.emit()336346 ''' 347 This is a path editor used as ItemDeligate in settings view. This editor 348 provides an additional button for directory selection dialog. 349 ''' 350 351 editing_finished_signal = Signal() 352390354 QWidget.__init__(self, parent) 355 self.path = path 356 self._layout = QHBoxLayout(self) 357 self._layout.setContentsMargins(0, 0, 0, 0) 358 self._layout.setSpacing(0) 359 self._button = QPushButton('...') 360 self._button.setMaximumSize(QSize(24, 20)) 361 self._button.clicked.connect(self._on_path_select_clicked) 362 self._layout.addWidget(self._button) 363 self._lineedit = QLineEdit(path) 364 self._lineedit.returnPressed.connect(self._on_editing_finished) 365 self._layout.addWidget(self._lineedit) 366 self.setLayout(self._layout) 367 self.setFocusProxy(self._button) 368 self.setAutoFillBackground(True)369371 # Workaround for QFileDialog.getExistingDirectory because it do not 372 # select the configuration folder in the dialog 373 self.dialog = QFileDialog(self, caption='Select a new settings folder') 374 self.dialog.setOption(QFileDialog.HideNameFilterDetails, True) 375 self.dialog.setFileMode(QFileDialog.Directory) 376 self.dialog.setDirectory(self.path) 377 if self.dialog.exec_(): 378 fileNames = self.dialog.selectedFiles() 379 path = fileNames[0] 380 if os.path.isfile(path): 381 path = os.path.basename(path) 382 self._lineedit.setText(path) 383 self.path = dir 384 self.editing_finished_signal.emit()385387 if self._lineedit.text(): 388 self.path = self._lineedit.text() 389 self.editing_finished_signal.emit()
Home | Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Tue Mar 1 06:56:10 2022 | http://epydoc.sourceforge.net |