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 'Max time difference:': ({'value': nm.settings().max_timediff, 143 'settings': nm.settings(), 144 'attrname': 'max_timediff', 145 'value_default': nm.settings().MAX_TIMEDIFF, 146 'value_step': 0.1, 147 'tooltip': '<p>Shows a warning if the time difference to ' 148 'remote host is greater than this value</p>' 149 },), 150 'Autoupdate:': ({'value': nm.settings().autoupdate, 151 'settings': nm.settings(), 152 'attrname': 'autoupdate', 153 'value_default': nm.settings().AUTOUPDATE, 154 'tooltip': '<p>By default node manager updates the current ' 155 'state on changes. You can deactivate this behavior to ' 156 'reduce the network load. If autoupdate is deactivated ' 157 'you must refresh the state manually.</p>' 158 },), 159 'Start sync with discovery:': ({'value': nm.settings().start_sync_with_discovery, 160 'settings': nm.settings(), 161 'attrname': 'start_sync_with_discovery', 162 'value_default': nm.settings().START_SYNC_WITH_DISCOVERY, 163 'tooltip': "<p>Sets 'start sync' in 'Start' master discovery " 164 "dialog to True, if this option is set to true.</p>" 165 },), 166 'Confirm exit when closing:': ({'value': nm.settings().confirm_exit_when_closing, 167 'settings': nm.settings(), 168 'attrname': 'confirm_exit_when_closing', 169 'value_default': nm.settings().CONFIRM_EXIT_WHEN_CLOSING, 170 'tooltip': "<p>Shows on closing of node_manager a dialog to stop " 171 "all ROS nodes if this option is set to true.</p>" 172 },), 173 'Highlight xml blocks:': ({'value': nm.settings().highlight_xml_blocks, 174 'settings': nm.settings(), 175 'attrname': 'highlight_xml_blocks', 176 'value_default': nm.settings().HIGHLIGHT_XML_BLOCKS, 177 'tooltip': "<p>Highlights the current selected XML block, while " 178 "editing ROS launch file.</p>" 179 },), 180 'Colorize hosts:': ({'value': nm.settings().colorize_hosts, 181 'settings': nm.settings(), 182 'attrname': 'colorize_hosts', 183 'value_default': nm.settings().COLORIZE_HOSTS, 184 'tooltip': "<p>Determine automatic a default color for each host if True. " 185 "Manually setted color will be prefered. You can select the color by " 186 "double-click on hostname in description panel. To remove a setted color " 187 "delete it manually from $HOME/.ros/node_manager/settings.ini</p>" 188 },), 189 'Show domain suffix:': ({'value': nm.settings().show_domain_suffix, 190 'settings': nm.settings(), 191 'attrname': 'show_domain_suffix', 192 'value_default': nm.settings().SHOW_DOMAIN_SUFFIX, 193 'tooltip': "<p>Shows the domain suffix of the host in the host description" 194 " panel and node tree view.</p>" 195 },), 196 'Transpose pub/sub description:': ({'value': nm.settings().transpose_pub_sub_descr, 197 'settings': nm.settings(), 198 'attrname': 'transpose_pub_sub_descr', 199 'value_default': nm.settings().TRANSPOSE_PUB_SUB_DESCR, 200 'tooltip': "<p>Transpose publisher/subscriber in description dock.</p>" 201 },) 202 } 203 self.settings_model.init_settings(settings) 204 # self.settingsTreeView.setSortingEnabled(True) 205 self.settingsTreeView.sortByColumn(0, Qt.AscendingOrder) 206 self.settingsTreeView.expandAll()
207 208
209 -class ItemDelegate(QStyledItemDelegate):
210 ''' 211 This ItemDelegate provides editors for different setting types in settings view. 212 ''' 213 214 settings_path_changed_signal = Signal() 215 216 reload_settings = False 217
218 - def createEditor(self, parent, option, index):
219 ''' 220 Creates a editor in the TreeView depending on type of the settings data. 221 ''' 222 item = self._itemFromIndex(index) 223 if item.edit_type() == SettingsValueItem.EDIT_TYPE_AUTODETECT: 224 if isinstance(item.value(), bool): 225 box = QCheckBox(parent) 226 box.setFocusPolicy(Qt.StrongFocus) 227 box.setAutoFillBackground(True) 228 box.stateChanged.connect(self.edit_finished) 229 return box 230 elif isinstance(item.value(), int): 231 box = QSpinBox(parent) 232 box.setValue(item.value()) 233 if not item.value_min() is None: 234 box.setMinimum(item.value_min()) 235 if not item.value_max() is None: 236 box.setMaximum(item.value_max()) 237 if not item.value_step() is None: 238 box.setSingleStep(item.value_step()) 239 return box 240 elif isinstance(item.value(), float): 241 box = QDoubleSpinBox(parent) 242 box.setValue(item.value()) 243 if not item.value_min() is None: 244 box.setMinimum(item.value_min()) 245 if not item.value_max() is None: 246 box.setMaximum(item.value_max()) 247 if not item.value_step() is None: 248 box.setSingleStep(item.value_step()) 249 box.setDecimals(3) 250 return box 251 elif item.edit_type() == SettingsValueItem.EDIT_TYPE_FOLDER: 252 editor = PathEditor(item.value(), parent) 253 editor.editing_finished_signal.connect(self.edit_finished) 254 return editor 255 elif item.edit_type() == SettingsValueItem.EDIT_TYPE_LIST: 256 box = QComboBox(parent) 257 box.addItems(item.value_list()) 258 index = box.findText(item.value()) 259 if index >= 0: 260 box.setCurrentIndex(index) 261 box.setEditable(False) 262 return box 263 return QStyledItemDelegate.createEditor(self, parent, option, index)
264 265 # def setEditorData(self, editor, index): 266 # print "setEditorData" 267 # QStyledItemDelegate.setEditorData(self, editor, index) 268 269 # def updateEditorGeometry(self, editor, option, index): 270 # print "updateEditorGeometry", option.rect.width() 271 # editor.setMaximumSize(option.rect.width(), option.rect.height()) 272 # QStyledItemDelegate.updateEditorGeometry(self, editor, option, index) 273
274 - def setModelData(self, editor, model, index):
275 if isinstance(editor, PathEditor): 276 cfg_path = nm.settings().cfg_path 277 model.setData(index, editor.path) 278 self.reload_settings = (cfg_path != nm.settings().cfg_path) 279 else: 280 QStyledItemDelegate.setModelData(self, editor, model, index)
281
282 - def sizeHint(self, option, index):
283 ''' 284 Determines and returns the size of the text after the format. 285 @see: U{http://www.pyside.org/docs/pyside/PySide/QtGui/QAbstractItemDelegate.html#PySide.QtGui.QAbstractItemDelegate} 286 ''' 287 options = QStyleOptionViewItem(option) 288 self.initStyleOption(options, index) 289 return QSize(options.rect.width(), 25)
290
291 - def edit_finished(self, arg=None):
292 editor = self.sender() 293 # The commitData signal must be emitted when we've finished editing 294 # and need to write our changed back to the model. 295 self.commitData.emit(editor) 296 self.closeEditor.emit(editor, QAbstractItemDelegate.NoHint) 297 if self.reload_settings: 298 self.reload_settings = False 299 self.settings_path_changed_signal.emit()
300
301 - def _itemFromIndex(self, index):
302 if isinstance(index.model(), QSortFilterProxyModel): 303 sindex = index.model().mapToSource(index) 304 return index.model().sourceModel().itemFromIndex(sindex) 305 else: 306 return index.model().itemFromIndex(index)
307 308
309 -class PathEditor(QWidget):
310 ''' 311 This is a path editor used as ItemDeligate in settings view. This editor 312 provides an additional button for directory selection dialog. 313 ''' 314 315 editing_finished_signal = Signal() 316
317 - def __init__(self, path, parent=None):
318 QWidget.__init__(self, parent) 319 self.path = path 320 self._layout = QHBoxLayout(self) 321 self._layout.setContentsMargins(0, 0, 0, 0) 322 self._layout.setSpacing(0) 323 self._button = QPushButton('...') 324 self._button.setMaximumSize(QSize(24, 20)) 325 self._button.clicked.connect(self._on_path_select_clicked) 326 self._layout.addWidget(self._button) 327 self._lineedit = QLineEdit(path) 328 self._lineedit.returnPressed.connect(self._on_editing_finished) 329 self._layout.addWidget(self._lineedit) 330 self.setLayout(self._layout) 331 self.setFocusProxy(self._button) 332 self.setAutoFillBackground(True)
333
334 - def _on_path_select_clicked(self):
335 # Workaround for QFileDialog.getExistingDirectory because it do not 336 # select the configuration folder in the dialog 337 self.dialog = QFileDialog(self, caption='Select a new settings folder') 338 self.dialog.setOption(QFileDialog.HideNameFilterDetails, True) 339 self.dialog.setFileMode(QFileDialog.Directory) 340 self.dialog.setDirectory(self.path) 341 if self.dialog.exec_(): 342 fileNames = self.dialog.selectedFiles() 343 path = fileNames[0] 344 if os.path.isfile(path): 345 path = os.path.basename(path) 346 self._lineedit.setText(path) 347 self.path = dir 348 self.editing_finished_signal.emit()
349
350 - def _on_editing_finished(self):
351 if self._lineedit.text(): 352 self.path = self._lineedit.text() 353 self.editing_finished_signal.emit()
354