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