1# -*- coding: utf-8 -*- 2# 3# Copyright © Spyder Project Contributors 4# Licensed under the terms of the MIT License 5# (see spyder/__init__.py for details) 6 7"""Layout dialogs""" 8 9# Standard library imports 10import sys 11 12# Third party imports 13from qtpy.QtCore import QAbstractTableModel, QModelIndex, QSize, Qt 14from qtpy.compat import from_qvariant, to_qvariant 15from qtpy.QtWidgets import (QAbstractItemView, QComboBox, QDialog, 16 QDialogButtonBox, QGroupBox, QHBoxLayout, 17 QPushButton, QTableView, QVBoxLayout) 18 19# Local imports 20from spyder.config.base import _ 21from spyder.py3compat import to_text_string 22 23 24class LayoutModel(QAbstractTableModel): 25 """ """ 26 def __init__(self, parent, order, active): 27 super(LayoutModel, self).__init__(parent) 28 29 # variables 30 self._parent = parent 31 self.order = order 32 self.active = active 33 self._rows = [] 34 self.set_data(order, active) 35 36 def set_data(self, order, active): 37 """ """ 38 self._rows = [] 39 self.order = order 40 self.active = active 41 for name in order: 42 if name in active: 43 row = [name, True] 44 else: 45 row = [name, False] 46 self._rows.append(row) 47 48 def flags(self, index): 49 """Override Qt method""" 50 if not index.isValid(): 51 return Qt.ItemIsEnabled 52 column = index.column() 53 if column in [0]: 54 return Qt.ItemFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | 55 Qt.ItemIsUserCheckable | Qt.ItemIsEditable) 56 else: 57 return Qt.ItemFlags(Qt.ItemIsEnabled) 58 59 def data(self, index, role=Qt.DisplayRole): 60 """Override Qt method""" 61 if not index.isValid() or not 0 <= index.row() < len(self._rows): 62 return to_qvariant() 63 row = index.row() 64 column = index.column() 65 66 name, state = self.row(row) 67 68 if role == Qt.DisplayRole or role == Qt.EditRole: 69 if column == 0: 70 return to_qvariant(name) 71 elif role == Qt.CheckStateRole: 72 if column == 0: 73 if state: 74 return Qt.Checked 75 else: 76 return Qt.Unchecked 77 if column == 1: 78 return to_qvariant(state) 79 return to_qvariant() 80 81 def setData(self, index, value, role): 82 """Override Qt method""" 83 row = index.row() 84 name, state = self.row(row) 85 86 if role == Qt.CheckStateRole: 87 self.set_row(row, [name, not state]) 88 self._parent.setCurrentIndex(index) 89 self._parent.setFocus() 90 self.dataChanged.emit(index, index) 91 return True 92 elif role == Qt.EditRole: 93 self.set_row(row, [from_qvariant(value, to_text_string), state]) 94 self.dataChanged.emit(index, index) 95 return True 96 return True 97 98 def rowCount(self, index=QModelIndex()): 99 """Override Qt method""" 100 return len(self._rows) 101 102 def columnCount(self, index=QModelIndex()): 103 """Override Qt method""" 104 return 2 105 106 def row(self, rownum): 107 """ """ 108 if self._rows == []: 109 return [None, None] 110 else: 111 return self._rows[rownum] 112 113 def set_row(self, rownum, value): 114 """ """ 115 self._rows[rownum] = value 116 117 118class LayoutSaveDialog(QDialog): 119 """ """ 120 def __init__(self, parent, order): 121 super(LayoutSaveDialog, self).__init__(parent) 122 123 # variables 124 self._parent = parent 125 126 # widgets 127 self.combo_box = QComboBox(self) 128 self.combo_box.addItems(order) 129 self.combo_box.setEditable(True) 130 self.combo_box.clearEditText() 131 self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | 132 QDialogButtonBox.Cancel, 133 Qt.Horizontal, self) 134 self.button_ok = self.button_box.button(QDialogButtonBox.Ok) 135 self.button_cancel = self.button_box.button(QDialogButtonBox.Cancel) 136 137 # widget setup 138 self.button_ok.setEnabled(False) 139 self.dialog_size = QSize(300, 100) 140 self.setWindowTitle('Save layout as') 141 self.setModal(True) 142 self.setMinimumSize(self.dialog_size) 143 self.setFixedSize(self.dialog_size) 144 145 # layouts 146 self.layout = QVBoxLayout() 147 self.layout.addWidget(self.combo_box) 148 self.layout.addWidget(self.button_box) 149 self.setLayout(self.layout) 150 151 # signals and slots 152 self.button_box.accepted.connect(self.accept) 153 self.button_box.rejected.connect(self.close) 154 self.combo_box.editTextChanged.connect(self.check_text) 155 156 def check_text(self, text): 157 """Disable empty layout name possibility""" 158 if to_text_string(text) == u'': 159 self.button_ok.setEnabled(False) 160 else: 161 self.button_ok.setEnabled(True) 162 163 164class LayoutSettingsDialog(QDialog): 165 """Layout settings dialog""" 166 def __init__(self, parent, names, order, active): 167 super(LayoutSettingsDialog, self).__init__(parent) 168 169 # variables 170 self._parent = parent 171 self._selection_model = None 172 self.names = names 173 self.order = order 174 self.active = active 175 176 # widgets 177 self.button_move_up = QPushButton(_('Move Up')) 178 self.button_move_down = QPushButton(_('Move Down')) 179 self.button_delete = QPushButton(_('Delete Layout')) 180 self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | 181 QDialogButtonBox.Cancel, 182 Qt.Horizontal, self) 183 self.group_box = QGroupBox(_("Layout Display and Order")) 184 self.table = QTableView(self) 185 self.ok_button = self.button_box.button(QDialogButtonBox.Ok) 186 self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) 187 self.cancel_button.setDefault(True) 188 self.cancel_button.setAutoDefault(True) 189 190 # widget setup 191 self.dialog_size = QSize(300, 200) 192 self.setMinimumSize(self.dialog_size) 193 self.setFixedSize(self.dialog_size) 194 self.setWindowTitle('Layout Settings') 195 196 self.table.setModel(LayoutModel(self.table, order, active)) 197 self.table.setSelectionBehavior(QAbstractItemView.SelectRows) 198 self.table.setSelectionMode(QAbstractItemView.SingleSelection) 199 self.table.verticalHeader().hide() 200 self.table.horizontalHeader().hide() 201 self.table.setAlternatingRowColors(True) 202 self.table.setShowGrid(False) 203 self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 204 self.table.horizontalHeader().setStretchLastSection(True) 205 self.table.setColumnHidden(1, True) 206 207 # need to keep a reference for pyside not to segfault! 208 self._selection_model = self.table.selectionModel() 209 210 # layout 211 buttons_layout = QVBoxLayout() 212 buttons_layout.addWidget(self.button_move_up) 213 buttons_layout.addWidget(self.button_move_down) 214 buttons_layout.addStretch() 215 buttons_layout.addWidget(self.button_delete) 216 217 group_layout = QHBoxLayout() 218 group_layout.addWidget(self.table) 219 group_layout.addLayout(buttons_layout) 220 self.group_box.setLayout(group_layout) 221 222 layout = QVBoxLayout() 223 layout.addWidget(self.group_box) 224 layout.addWidget(self.button_box) 225 226 self.setLayout(layout) 227 228 # signals and slots 229 self.button_box.accepted.connect(self.accept) 230 self.button_box.rejected.connect(self.close) 231 self.button_delete.clicked.connect(self.delete_layout) 232 self.button_move_up.clicked.connect(lambda: self.move_layout(True)) 233 self.button_move_down.clicked.connect(lambda: self.move_layout(False)) 234 self.table.model().dataChanged.connect( 235 lambda: self.selection_changed(None, None)) 236 self._selection_model.selectionChanged.connect( 237 lambda: self.selection_changed(None, None)) 238 239 # focus table 240 index = self.table.model().index(0, 0) 241 self.table.setCurrentIndex(index) 242 self.table.setFocus() 243 244 def delete_layout(self): 245 """ """ 246 names, order, active = self.names, self.order, self.order 247 name = from_qvariant(self.table.selectionModel().currentIndex().data(), 248 to_text_string) 249 250 if name in names: 251 index = names.index(name) 252 # In case nothing has focus in the table 253 if index != -1: 254 order.remove(name) 255 names[index] = None 256 if name in active: 257 active.remove(name) 258 self.names, self.order, self.active = names, order, active 259 self.table.model().set_data(order, active) 260 index = self.table.model().index(0, 0) 261 self.table.setCurrentIndex(index) 262 self.table.setFocus() 263 self.selection_changed(None, None) 264 if len(order) == 0: 265 self.button_move_up.setDisabled(True) 266 self.button_move_down.setDisabled(True) 267 self.button_delete.setDisabled(True) 268 269 def move_layout(self, up=True): 270 """ """ 271 names, order, active = self.names, self.order, self.active 272 row = self.table.selectionModel().currentIndex().row() 273 row_new = row 274 275 if up: 276 row_new -= 1 277 else: 278 row_new += 1 279 280 order[row], order[row_new] = order[row_new], order[row] 281 282 self.order = order 283 self.table.model().set_data(order, active) 284 index = self.table.model().index(row_new, 0) 285 self.table.setCurrentIndex(index) 286 self.table.setFocus() 287 self.selection_changed(None, None) 288 289 def selection_changed(self, selection, deselection): 290 """ """ 291 model = self.table.model() 292 index = self.table.currentIndex() 293 row = index.row() 294 order, names, active = self.order, self.names, self.active 295 296 state = model.row(row)[1] 297 name = model.row(row)[0] 298 299 # Check if name changed 300 if name not in names: # Did changed 301 if row != -1: # row == -1, means no items left to delete 302 old_name = order[row] 303 order[row] = name 304 names[names.index(old_name)] = name 305 if old_name in active: 306 active[active.index(old_name)] = name 307 308 # Check if checbox clicked 309 if state: 310 if name not in active: 311 active.append(name) 312 else: 313 if name in active: 314 active.remove(name) 315 316 self.active = active 317 self.button_move_up.setDisabled(False) 318 self.button_move_down.setDisabled(False) 319 320 if row == 0: 321 self.button_move_up.setDisabled(True) 322 if row == len(names) - 1: 323 self.button_move_down.setDisabled(True) 324 if len(names) == 0: 325 self.button_move_up.setDisabled(True) 326 self.button_move_down.setDisabled(True) 327 328 329def test(): 330 """Run layout test widget test""" 331 from spyder.utils.qthelpers import qapplication 332 333 app = qapplication() 334 names = ['test', 'tester', '20', '30', '40'] 335 order = ['test', 'tester', '20', '30', '40'] 336 active = ['test', 'tester'] 337 widget_1 = LayoutSettingsDialog(None, names, order, active) 338 widget_2 = LayoutSaveDialog(None, order) 339 widget_1.show() 340 widget_2.show() 341 sys.exit(app.exec_()) 342 343if __name__ == '__main__': 344 test() 345