Package node_manager_fkie :: Module settings_widget
[frames] | no frames]

Source Code for Module node_manager_fkie.settings_widget

  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   
 50   
51 -class SettingsWidget(QDockWidget):
52 ''' 53 Settings widget to handle the settings changes. The changes will direct change 54 the settings of the GUI. 55 ''' 56
57 - def __init__(self, parent=None):
58 ''' 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()
77
78 - def reload_settings(self):
79 ''' 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()
243 244
245 -class ItemDelegate(QStyledItemDelegate):
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 253
254 - def createEditor(self, parent, option, index):
255 ''' 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) 309
310 - def setModelData(self, editor, model, index):
311 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)
317
318 - def sizeHint(self, option, index):
319 ''' 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)
326
327 - def edit_finished(self, arg=None):
328 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()
336
337 - def _itemFromIndex(self, index):
338 if isinstance(index.model(), QSortFilterProxyModel): 339 sindex = index.model().mapToSource(index) 340 return index.model().sourceModel().itemFromIndex(sindex) 341 else: 342 return index.model().itemFromIndex(index)
343 344
345 -class PathEditor(QWidget):
346 ''' 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() 352
353 - def __init__(self, path, parent=None):
354 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)
369
370 - def _on_path_select_clicked(self):
371 # 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()
385
386 - def _on_editing_finished(self):
387 if self._lineedit.text(): 388 self.path = self._lineedit.text() 389 self.editing_finished_signal.emit()
390