Completed
Pull Request — master (#384)
by
unknown
02:57
created

BlockEditorTableModel   F

Complexity

Total Complexity 93

Size/Duplication

Total Lines 362
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 362
rs 1.5789
wmc 93

15 Methods

Rating   Name   Duplication   Size   Complexity  
B headerData() 0 19 6
A flags() 0 2 1
F setData() 0 62 22
F set_activation_config() 0 35 10
A columnCount() 0 2 1
F data() 0 50 17
A set_pulse_block() 0 16 3
A _get_column_widths() 0 12 3
A _create_header_data() 0 13 3
B removeRows() 0 25 6
B insertRows() 0 22 5
A __init__() 0 22 1
A rowCount() 0 2 1
F _get_column_width() 0 31 9
B _notify_column_width() 0 15 5

How to fix   Complexity   

Complex Class

Complex classes like BlockEditorTableModel often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
"""
4
Qudi is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
Qudi is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
16
17
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
18
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
19
"""
20
21
import numpy as np
22
import copy
23
24
from qtpy import QtCore, QtGui, QtWidgets
25
from gui.pulsed.pulsed_item_delegates import ScienDSpinBoxItemDelegate, ComboBoxItemDelegate
26
from gui.pulsed.pulsed_item_delegates import DigitalStatesItemDelegate, AnalogParametersItemDelegate
27
from gui.pulsed.pulsed_item_delegates import SpinBoxItemDelegate, CheckBoxItemDelegate
28
from logic.pulsed.pulse_objects import PulseBlockElement, PulseBlock, PulseBlockEnsemble
29
from logic.pulsed.pulse_objects import PulseSequence
30
import logic.pulsed.sampling_functions as sf
31
32
33
class BlockEditorTableModel(QtCore.QAbstractTableModel):
34
    """
35
36
    """
37
    # signals
38
    sigColumnWidthChanged = QtCore.Signal(int, int)
39
40
    # User defined roles for model data access
41
    lengthRole = QtCore.Qt.UserRole + 1
42
    incrementRole = QtCore.Qt.UserRole + 2
43
    digitalStateRole = QtCore.Qt.UserRole + 3
44
    analogFunctionRole = QtCore.Qt.UserRole + 4
45
    analogShapeRole = QtCore.Qt.UserRole + 5
46
    analogParameterRole = QtCore.Qt.UserRole + 6
47
    analogChannelSetRole = QtCore.Qt.UserRole + 7
48
    digitalChannelSetRole = QtCore.Qt.UserRole + 8
49
    channelSetRole = QtCore.Qt.UserRole + 9
50
    blockElementRole = QtCore.Qt.UserRole + 10
51
    pulseBlockRole = QtCore.Qt.UserRole + 11
52
53
    def __init__(self):
54
        super().__init__()
55
56
        self.digital_channels = list()
57
        self.analog_channels = list()
58
        self.activation_config = set()
59
60
        # The actual model data container.
61
        self._pulse_block = PulseBlock('EDITOR CONTAINER')
62
        # The default PulseBlockElement
63
        self.__default_element = PulseBlockElement()
64
65
        # Create header strings
66
        self._create_header_data()
67
68
        # The current column widths.
69
        # The fact that the widths are stored in the model saves a huge amount of computational
70
        # time when resizing columns due to item changes.
71
        self._col_widths = self._get_column_widths()
72
        # Notify the QTableView about a change in column widths
73
        self._notify_column_width()
74
        return
75
76
    def _create_header_data(self):
77
        """
78
79
        @return:
80
        """
81
        # The horizontal header data
82
        self._h_header_data = ['length\nin s', 'increment\nin s']
83
        if self.digital_channels:
84
            self._h_header_data.append('digital\nchannels')
85
        for chnl in self.analog_channels:
86
            self._h_header_data.append('{0}\nshape'.format(chnl))
87
            self._h_header_data.append('{0}\nparameters'.format(chnl))
88
        return
89
90
    def _notify_column_width(self, column=None):
91
        """
92
93
        @param column:
94
        @return:
95
        """
96
        if column is None:
97
            for column, width in enumerate(self._col_widths):
98
                self.sigColumnWidthChanged.emit(column, width)
99
            return
100
101
        if isinstance(column, int):
102
            if 0 <= column < len(self._col_widths):
103
                self.sigColumnWidthChanged.emit(column, self._col_widths[column])
104
        return
105
106
    def _get_column_widths(self):
107
        """
108
109
        @return:
110
        """
111
        widths = list()
112
        for column in range(self.columnCount()):
113
            width = self._get_column_width(column)
114
            if width < 0:
115
                return -1
116
            widths.append(width)
117
        return widths
118
119
    def _get_column_width(self, column):
120
        """
121
122
        @return:
123
        """
124
        if not isinstance(column, int):
125
            return -1
126
127
        if column < self.columnCount():
128
            has_digital = bool(len(self.digital_channels))
129
130
            if column < 2:
131
                width = 90
132
            elif column == 2 and has_digital:
133
                width = 30 * len(self.digital_channels)
134
            else:
135
                a_ch_offset = 2 + int(has_digital)
136
                if (column - a_ch_offset) % 2 == 0:
137
                    width = 80
138
                else:
139
                    channel = self.analog_channels[(column - a_ch_offset) // 2]
140
                    max_param_number = 0
141
                    for element in self._pulse_block.element_list:
142
                        tmp_size = len(element.pulse_function[channel].params)
143
                        if tmp_size > max_param_number:
144
                            max_param_number = tmp_size
145
                    width = 90 * max_param_number
146
147
            return width
148
        else:
149
            return -1
150
151
    def set_activation_config(self, activation_config):
152
        """
153
154
        @param activation_config:
155
        @return:
156
        """
157
        if isinstance(activation_config, list):
158
            activation_config = set(activation_config)
159
160
        # Do nothing if the activation config has not changed or is wrong data type
161
        if not isinstance(activation_config, set) or activation_config == self.activation_config:
162
            return
163
164
        self.beginResetModel()
165
166
        self.activation_config = activation_config
167
        self.digital_channels = sorted({chnl for chnl in activation_config if chnl.startswith('d')})
168
        self.analog_channels = sorted({chnl for chnl in activation_config if chnl.startswith('a')})
169
170
        analog_shape = {chnl: sf.SamplingFunctions.Idle() for chnl in self.analog_channels}
171
        digital_state = {chnl: False for chnl in self.digital_channels}
172
        self.__default_element = PulseBlockElement(pulse_function=analog_shape,
173
                                                   digital_high=digital_state)
174
175
        # The actual model data container with a single default element
176
        self._pulse_block = PulseBlock(name='EDITOR CONTAINER',
177
                                       element_list=[self.__default_element])
178
179
        self._col_widths = self._get_column_widths()
180
        self._create_header_data()
181
182
        self.endResetModel()
183
184
        self._notify_column_width()
185
        return
186
187
    def rowCount(self, parent=QtCore.QModelIndex()):
188
        return len(self._pulse_block.element_list)
189
190
    def columnCount(self, parent=QtCore.QModelIndex()):
191
        return 2 + int(len(self.digital_channels) > 0) + 2 * len(self.analog_channels)
192
193
    def data(self, index, role=QtCore.Qt.DisplayRole):
194
        if role == QtCore.Qt.DisplayRole:
195
            return None
196
197
        if role == self.pulseBlockRole:
198
            return self._pulse_block
199
        if role == self.analogChannelSetRole:
200
            return self._pulse_block.analog_channels
201
        if role == self.digitalChannelSetRole:
202
            return self._pulse_block.digital_channels
203
        if role == self.channelSetRole:
204
            return self._pulse_block.channel_set
205
206
        if not index.isValid():
207
            return None
208
209
        if role == self.lengthRole:
210
            return self._pulse_block.element_list[index.row()].init_length_s
211
        if role == self.incrementRole:
212
            return self._pulse_block.element_list[index.row()].increment_s
213
        if role == self.digitalStateRole:
214
            return self._pulse_block.element_list[index.row()].digital_high
215
        if role == self.analogFunctionRole:
216
            element = self._pulse_block.element_list[index.row()]
217
            if len(self.digital_channels) > 0:
218
                col_offset = 3
219
            else:
220
                col_offset = 2
221
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
222
            return element.pulse_function[analog_chnl]
223
        if role == self.analogShapeRole:
224
            element = self._pulse_block.element_list[index.row()]
225
            if len(self.digital_channels) > 0:
226
                col_offset = 3
227
            else:
228
                col_offset = 2
229
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
230
            return element.pulse_function[analog_chnl].__class__.__name__
231
        if role == self.analogParameterRole:
232
            element = self._pulse_block.element_list[index.row()]
233
            if len(self.digital_channels) > 0:
234
                col_offset = 3
235
            else:
236
                col_offset = 2
237
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
238
            return vars(element.pulse_function[analog_chnl])
239
        if role == self.blockElementRole:
240
            return self._pulse_block.element_list[index.row()]
241
242
        return None
243
244
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
245
        """
246
        """
247
        if isinstance(data, PulseBlockElement):
248
            self._pulse_block.element_list[index.row()] = copy.deepcopy(data)
249
            return
250
251
        if role == self.lengthRole and isinstance(data, (int, float)):
252
            old_elem = self._pulse_block.element_list[index.row()]
253
            if data != old_elem.init_length_s:
254
                new_elem = PulseBlockElement(init_length_s=max(0, data),
255
                                             increment_s=old_elem.increment_s,
256
                                             pulse_function=old_elem.pulse_function,
257
                                             digital_high=old_elem.digital_high)
258
                self._pulse_block.replace_element(position=index.row(), element=new_elem)
259
        elif role == self.incrementRole and isinstance(data, (int, float)):
260
            old_elem = self._pulse_block.element_list[index.row()]
261
            if data != old_elem.increment_s:
262
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
263
                                             increment_s=data,
264
                                             pulse_function=old_elem.pulse_function,
265
                                             digital_high=old_elem.digital_high)
266
                self._pulse_block.replace_element(position=index.row(), element=new_elem)
267
        elif role == self.digitalStateRole and isinstance(data, dict):
268
            old_elem = self._pulse_block.element_list[index.row()]
269
            if data != old_elem.digital_high:
270
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
271
                                             increment_s=old_elem.increment_s,
272
                                             pulse_function=old_elem.pulse_function,
273
                                             digital_high=data.copy())
274
                self._pulse_block.replace_element(position=index.row(), element=new_elem)
275
        elif role == self.analogShapeRole and isinstance(data, str):
276
            if self.data(index=index, role=self.analogShapeRole) != data:
277
                old_elem = self._pulse_block.element_list[index.row()]
278
279
                sampling_func = getattr(sf.SamplingFunctions, data)
280
                col_offset = 3 if self.digital_channels else 2
281
                chnl = self.analog_channels[(index.column() - col_offset) // 2]
282
283
                pulse_function = old_elem.pulse_function.copy()
284
                pulse_function[chnl] = sampling_func()
285
286
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
287
                                             increment_s=old_elem.increment_s,
288
                                             pulse_function=pulse_function,
289
                                             digital_high=old_elem.digital_high)
290
                self._pulse_block.replace_element(position=index.row(), element=new_elem)
291
292
                new_column_width = self._get_column_width(index.column()+1)
293
                if new_column_width >= 0 and new_column_width != self._col_widths[index.column()+1]:
294
                    self._col_widths[index.column() + 1] = new_column_width
295
                    self._notify_column_width(index.column()+1)
296
297
        elif role == self.analogParameterRole and isinstance(data, dict):
298
            col_offset = 3 if self.digital_channels else 2
299
            chnl = self.analog_channels[(index.column() - col_offset) // 2]
300
            self._pulse_block.element_list[index.row()].pulse_function[chnl].__init__(**data)
301
        elif role == self.pulseBlockRole and isinstance(data, PulseBlock):
302
            self._pulse_block = copy.deepcopy(data)
303
            self._pulse_block.name = 'EDITOR CONTAINER'
304
            self._pulse_block.refresh_parameters()
305
        return
306
307
    def headerData(self, section, orientation, role):
308
        # Horizontal header
309
        if orientation == QtCore.Qt.Horizontal:
310
            # if role == QtCore.Qt.BackgroundRole:
311
            #     return QVariant(QBrush(QColor(Qt::green), Qt::SolidPattern))
312
            if role == QtCore.Qt.SizeHintRole:
313
                if section < len(self._col_widths):
314
                    return QtCore.QSize(self._col_widths[section], 40)
315
316
            if role == QtCore.Qt.DisplayRole:
317
                if section < len(self._h_header_data):
318
                    return self._h_header_data[section]
319
320
        # Vertical header
321
        # if orientation == QtCore.Qt.Vertical:
322
        #     if role == QtCore.Qt.BackgroundRole:
323
        #         return QtCore.Qt.QVariant(QtGui.Qt.QBrush(QtGui.Qt.QColor(QtCore.Qt.green),
324
        #                                                   QtCore.Qt.SolidPattern))
325
        return super().headerData(section, orientation, role)
326
327
    def flags(self, index):
328
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
329
330
    def insertRows(self, row, count, parent=None):
331
        """
332
333
        @param row:
334
        @param count:
335
        @param parent:
336
        @return:
337
        """
338
        # Sanity/range checking
339
        if row < 0 or row > self.rowCount():
340
            return False
341
342
        if parent is None:
343
            parent = QtCore.QModelIndex()
344
345
        self.beginInsertRows(parent, row, row + count - 1)
346
347
        for i in range(count):
348
            self._pulse_block.insert_element(position=row, element=self.__default_element)
349
350
        self.endInsertRows()
351
        return True
352
353
    def removeRows(self, row, count, parent=None):
354
        """
355
356
        @param row:
357
        @param count:
358
        @param parent:
359
        @return:
360
        """
361
        # Sanity/range checking
362
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
363
            return False
364
365
        if parent is None:
366
            parent = QtCore.QModelIndex()
367
368
        self.beginRemoveRows(parent, row, row + count - 1)
369
370
        for i in range(count):
371
            self._pulse_block.delete_element(position=row)
372
373
        self._col_widths = self._get_column_widths()
374
        self._notify_column_width()
375
376
        self.endRemoveRows()
377
        return True
378
379
    def set_pulse_block(self, pulse_block):
380
        """
381
382
        @param pulse_block:
383
        @return:
384
        """
385
        if not isinstance(pulse_block, PulseBlock):
386
            return False
387
        elif pulse_block.channel_set != self.activation_config:
388
            return False
389
        self.beginResetModel()
390
        self.setData(QtCore.QModelIndex(), pulse_block, self.pulseBlockRole)
391
        self._col_widths = self._get_column_widths()
392
        self._notify_column_width()
393
        self.endResetModel()
394
        return True
395
396
397
class BlockEditor(QtWidgets.QTableView):
398
    """
399
400
    """
401
    def __init__(self, parent):
402
        # Initialize inherited QTableView
403
        super().__init__(parent)
404
405
        # Create custom data model and hand it to the QTableView.
406
        # (essentially it's a PulseBlock instance with QAbstractTableModel interface)
407
        model = BlockEditorTableModel()
408
        self.setModel(model)
409
410
        # Connect the custom signal sigColumnWidthChanged from the model to the setColumnWidth
411
        # slot of QTableView in order to resize the columns upon resizing.
412
        self.model().sigColumnWidthChanged.connect(self.setColumnWidth)
413
414
        # Set header sizes
415
        self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
416
        # self.horizontalHeader().setStyleSheet('QHeaderView { font-weight: 400; }')
417
        self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
418
        self.verticalHeader().setDefaultSectionSize(50)
419
420
        # Set item selection and editing behaviour
421
        self.setEditTriggers(
422
            QtGui.QAbstractItemView.CurrentChanged | QtGui.QAbstractItemView.SelectedClicked)
423
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
424
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
425
426
        # Set item delegates for all table columns
427
        self._set_item_delegates()
428
        return
429
430
    def _set_item_delegates(self):
431
        """
432
433
        @return:
434
        """
435
        # Set item delegates (scientific SpinBoxes) for length and increment column
436
        length_item_dict = {'unit': 's',
437
                            'init_val': '10.0e-9',
438
                            'min': 0,
439
                            'max': np.inf,
440
                            'dec': 6}
441
        self.setItemDelegateForColumn(
442
            0, ScienDSpinBoxItemDelegate(self, length_item_dict, self.model().lengthRole))
443
        increment_item_dict = {'unit': 's',
444
                               'init_val': 0,
445
                               'min': -np.inf,
446
                               'max': np.inf,
447
                               'dec': 6}
448
        self.setItemDelegateForColumn(
449
            1, ScienDSpinBoxItemDelegate(self, increment_item_dict, self.model().incrementRole))
450
451
        # If any digital channels are present, set item delegate (custom multi-CheckBox widget)
452
        # for digital channels column.
453
        if len(self.model().digital_channels) > 0:
454
            self.setItemDelegateForColumn(
455
                2, DigitalStatesItemDelegate(self, self.model().digitalStateRole))
456
            offset_index = 3  # to indicate which column comes next.
457
        else:
458
            offset_index = 2  # to indicate which column comes next.
459
460
        # loop through all analog channels and set two item delegates for each channel.
461
        # First a ComboBox delegate for the analog shape column and second a custom
462
        # composite widget widget for the analog parameters column.
463
        for num, chnl in enumerate(self.model().analog_channels):
464
            self.setItemDelegateForColumn(
465
                offset_index + 2 * num, ComboBoxItemDelegate(
466
                    self, sorted(sf.SamplingFunctions.parameters), self.model().analogShapeRole))
467
            self.setItemDelegateForColumn(
468
                offset_index + 2 * num + 1,
469
                AnalogParametersItemDelegate(
470
                    self, [self.model().analogFunctionRole, self.model().analogParameterRole]))
471
        return
472
473
    def set_activation_config(self, activation_config):
474
        """
475
476
        @param activation_config:
477
        @return:
478
        """
479
        # Remove item delegates
480
        for column in range(self.model().columnCount()):
481
            self.setItemDelegateForColumn(column, None)
482
        # Set new activation config in model (perform model reset)
483
        self.model().set_activation_config(activation_config)
484
        # Set new item delegates
485
        self._set_item_delegates()
486
        return
487
488
    def setModel(self, model):
489
        """
490
491
        @param model:
492
        @return:
493
        """
494
        super().setModel(model)
495
        for column in range(model.columnCount()):
496
            width = model.headerData(column, QtCore.Qt.Horizontal, QtCore.Qt.SizeHintRole).width()
497
            self.setColumnWidth(column, width)
498
        return
499
500
    def rowCount(self):
501
        return self.model().rowCount()
502
503
    def columnCount(self):
504
        return self.model().columnCount()
505
506
    def currentRow(self):
507
        index = self.currentIndex()
508
        if index.isValid():
509
            return index.row()
510
        else:
511
            return 0
512
513
    def currentColumn(self):
514
        index = self.currentIndex()
515
        if index.isValid():
516
            return index.column()
517
        else:
518
            return 0
519
520
    def add_elements(self, count=1, at_position=None):
521
        """
522
523
        @param count:
524
        @param at_position:
525
        @return: bool, operation success
526
        """
527
        # Sanity checking
528
        if count < 1:
529
            return False
530
531
        if at_position is None:
532
            at_position = self.model().rowCount()
533
534
        # Insert new element(s) as row to the table model/view at the specified position.
535
        # Append new element(s) to the table model/view if no position was given.
536
        return self.model().insertRows(at_position, count)
537
538
    def remove_elements(self, count=1, at_position=None):
539
        """
540
541
        @param count:
542
        @param at_position:
543
        @return: bool, operation success
544
        """
545
        # Sanity checking
546
        if count < 1:
547
            return False
548
549
        if at_position is None:
550
            at_position = self.model().rowCount() - count
551
552
        # Remove rows/elements with index <at_position> to index <at_position + count - 1>
553
        # Remove last <count> number of elements if no at_position was given.
554
        return self.model().removeRows(at_position, count)
555
556
    def clear(self):
557
        """
558
        Removes all PulseBlockElements from the view/model and inserts a single afterwards.
559
560
        @return: bool, operation success
561
        """
562
        success = self.remove_elements(self.model().rowCount(), 0)
563
        if success:
564
            self.add_elements(1, 0)
565
        return success
566
567
    def get_block(self):
568
        """
569
        Returns a (deep)copy of the PulseBlock instance serving as model for this editor.
570
571
        @return: PulseBlock, an instance of PulseBlock
572
        """
573
        block_copy = copy.deepcopy(
574
            self.model().data(QtCore.QModelIndex(), self.model().pulseBlockRole))
575
        block_copy.name = ''
576
        block_copy.refresh_parameters()
577
        return block_copy
578
579
    def load_block(self, pulse_block):
580
        """
581
        Load an instance of PulseBlock into the model in order to view/edit it.
582
583
        @param pulse_block: PulseBlock, the PulseBlock instance to load into the model/view
584
        @return: bool, operation success
585
        """
586
        return self.model().set_pulse_block(pulse_block)
587
588
589
class EnsembleEditorTableModel(QtCore.QAbstractTableModel):
590
    """
591
592
    """
593
    # User defined roles for model data access
594
    repetitionsRole = QtCore.Qt.UserRole + 1
595
    blockNameRole = QtCore.Qt.UserRole + 2
596
    blockEnsembleRole = QtCore.Qt.UserRole + 3
597
598
    def __init__(self):
599
        super().__init__()
600
601
        # set containing available block names
602
        self.available_pulse_blocks = None
603
604
        # The actual model data container.
605
        self._block_ensemble = PulseBlockEnsemble('EDITOR CONTAINER')
606
        # The default block name
607
        self.__default_block = ''
608
        return
609
610
    def set_available_pulse_blocks(self, blocks):
611
        """
612
613
        @param blocks: list|dict|set, list/dict/set containing all available PulseBlock names
614
        @return: int, error code (>=0: OK, <0: ERR)
615
        """
616
        # Convert to set
617
        if isinstance(blocks, (list, dict)):
618
            blocks = set(blocks)
619
        elif not isinstance(blocks, set):
620
            return -1
621
622
        # Do nothing if available blocks are unchanged
623
        if self.available_pulse_blocks == blocks:
624
            return 0
625
626
        self.available_pulse_blocks = blocks
627
628
        # Set default block
629
        if len(self.available_pulse_blocks) > 0:
630
            self.__default_block = sorted(self.available_pulse_blocks)[0]
631
        else:
632
            self.__default_block = ''
633
634
        # Remove blocks from list that are not there anymore
635
        for row, (block_name, reps) in enumerate(self._block_ensemble.block_list):
636
            if block_name not in blocks:
637
                self.removeRows(row, 1)
638
639
        # Check if the PulseBlockEnsemble model instance is empty and set a single block if True.
640
        if self.rowCount() == 0:
641
            self.insertRows(0, 1)
642
643
        return 0
644
645
    def set_rotating_frame(self, rotating_frame=True):
646
        """
647
648
        @param rotating_frame:
649
        @return:
650
        """
651
        if isinstance(rotating_frame, bool):
652
            self._block_ensemble.rotating_frame = rotating_frame
653
        return
654
655
    def rowCount(self, parent=QtCore.QModelIndex()):
656
        return len(self._block_ensemble.block_list)
657
658
    def columnCount(self, parent=QtCore.QModelIndex()):
659
        return 2
660
661
    def data(self, index, role=QtCore.Qt.DisplayRole):
662
        if role == QtCore.Qt.DisplayRole:
663
            return None
664
665
        if role == self.blockEnsembleRole:
666
            return self._block_ensemble
667
668
        if not index.isValid():
669
            return None
670
671
        if role == self.repetitionsRole:
672
            return self._block_ensemble.block_list[index.row()][1]
673
        if role == self.blockNameRole:
674
            return self._block_ensemble.block_list[index.row()][0]
675
676
        return None
677
678
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
679
        """
680
        """
681
        if role == self.repetitionsRole and isinstance(data, int):
682
            # Delete potentially preexisting measurement_information dict upon edit
683
            self._block_ensemble.measurement_information = dict()
684
            block_name = self._block_ensemble.block_list[index.row()][0]
685
            self._block_ensemble.replace_block(position=index.row(),
686
                                               block_name=block_name,
687
                                               reps=data)
688
        elif role == self.blockNameRole and isinstance(data, str):
689
            # Delete potentially preexisting measurement_information dict upon edit
690
            self._block_ensemble.measurement_information = dict()
691
            reps = self._block_ensemble.block_list[index.row()][1]
692
            self._block_ensemble.replace_block(position=index.row(),
693
                                               block_name=data,
694
                                               reps=reps)
695
        elif role == self.blockEnsembleRole and isinstance(data, PulseBlockEnsemble):
696
            self._block_ensemble = copy.deepcopy(data)
697
            self._block_ensemble.name = 'EDITOR CONTAINER'
698
        return
699
700
    def headerData(self, section, orientation, role):
701
        # Horizontal header
702
        if orientation == QtCore.Qt.Horizontal:
703
            if role == QtCore.Qt.DisplayRole:
704
                if section == 0:
705
                    return 'PulseBlock'
706
                if section == 1:
707
                    return 'repetitions'
708
            # if role == QtCore.Qt.BackgroundRole:
709
            #     return QVariant(QBrush(QColor(Qt::green), Qt::SolidPattern))
710
            # if role == QtCore.Qt.SizeHintRole:
711
            #     if section < len(self._col_widths):
712
            #         return QtCore.QSize(self._col_widths[section], 40)
713
        return super().headerData(section, orientation, role)
714
715
    def flags(self, index):
716
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
717
718
    def insertRows(self, row, count, parent=None):
719
        """
720
721
        @param row:
722
        @param count:
723
        @param parent:
724
        @return:
725
        """
726
        # Do nothing if no blocks are available
727
        if len(self.available_pulse_blocks) == 0:
728
            return False
729
730
        # Sanity/range checking
731
        if row < 0 or row > self.rowCount() or not self.available_pulse_blocks:
732
            return False
733
734
        # Delete potentially preexisting measurement_information dict upon edit
735
        self._block_ensemble.measurement_information = dict()
736
737
        if parent is None:
738
            parent = QtCore.QModelIndex()
739
740
        self.beginInsertRows(parent, row, row + count - 1)
741
742
        for i in range(count):
743
            self._block_ensemble.insert_block(position=row, block_name=self.__default_block, reps=0)
744
745
        self.endInsertRows()
746
        return True
747
748 View Code Duplication
    def removeRows(self, row, count, parent=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
749
        """
750
751
        @param row:
752
        @param count:
753
        @param parent:
754
        @return:
755
        """
756
        # Sanity/range checking
757
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
758
            return False
759
760
        # Delete potentially preexisting measurement_information dict upon edit
761
        self._block_ensemble.measurement_information = dict()
762
763
        if parent is None:
764
            parent = QtCore.QModelIndex()
765
766
        self.beginRemoveRows(parent, row, row + count - 1)
767
768
        for i in range(count):
769
            self._block_ensemble.delete_block(position=row)
770
771
        self.endRemoveRows()
772
        return True
773
774
    def set_block_ensemble(self, block_ensemble):
775
        """
776
777
        @param block_ensemble:
778
        @return:
779
        """
780
        if not isinstance(block_ensemble, PulseBlockEnsemble):
781
            return False
782
        self.beginResetModel()
783
        self.setData(QtCore.QModelIndex(), block_ensemble, self.blockEnsembleRole)
784
        self.endResetModel()
785
        return True
786
787
788
class EnsembleEditor(QtWidgets.QTableView):
789
    """
790
791
    """
792
    def __init__(self, parent):
793
        # Initialize inherited QTableView
794
        super().__init__(parent)
795
796
        # Create custom data model and hand it to the QTableView.
797
        # (essentially it's a PulseBlockEnsemble instance with QAbstractTableModel interface)
798
        model = EnsembleEditorTableModel()
799
        self.setModel(model)
800
801
        # Set item selection and editing behaviour
802
        self.setEditTriggers(
803
            QtGui.QAbstractItemView.CurrentChanged | QtGui.QAbstractItemView.SelectedClicked)
804
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
805
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
806
807
        # Set item delegate (ComboBox) for PulseBlock column
808
        self.setItemDelegateForColumn(0, ComboBoxItemDelegate(self, list(),
809
                                                              self.model().blockNameRole,
810
                                                              QtCore.QSize(100, 50)))
811
        # Set item delegate (SpinBoxes) for repetition column
812
        repetition_item_dict = {'init_val': 0, 'min': 0, 'max': (2**31)-1}
813
        self.setItemDelegateForColumn(1, SpinBoxItemDelegate(self, repetition_item_dict,
814
                                                             self.model().repetitionsRole))
815
816
        # Set header sizes
817
        self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
818
        # self.horizontalHeader().setDefaultSectionSize(100)
819
        # self.horizontalHeader().setStyleSheet('QHeaderView { font-weight: 400; }')
820
        self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
821
        self.verticalHeader().setDefaultSectionSize(50)
822
        for col in range(self.columnCount()):
823
            width = self.itemDelegateForColumn(col).sizeHint().width()
824
            self.setColumnWidth(col, width)
825
        return
826
827
    def set_available_pulse_blocks(self, blocks):
828
        """
829
830
        @param list|set blocks:
831
        @return: int, error code (>=0: OK, <0: ERR)
832
        """
833
        if isinstance(blocks, (list, dict, set)):
834
            blocks = sorted(blocks)
835
        else:
836
            return -1
837
838
        err_code = self.model().set_available_pulse_blocks(blocks)
839
        self.setItemDelegateForColumn(
840
            0,
841
            ComboBoxItemDelegate(self, blocks, self.model().blockNameRole))
842
        return err_code
843
844
    def set_rotating_frame(self, rotating_frame=True):
845
        """
846
847
        @param rotating_frame:
848
        @return:
849
        """
850
        self.model().set_rotating_frame(rotating_frame)
851
        return
852
853
    def rowCount(self):
854
        return self.model().rowCount()
855
856
    def columnCount(self):
857
        return self.model().columnCount()
858
859
    def currentRow(self):
860
        index = self.currentIndex()
861
        return index.row() if index.isValid() else 0
862
863
    def currentColumn(self):
864
        index = self.currentIndex()
865
        return index.column() if index.isValid() else 0
866
867
    def add_blocks(self, count=1, at_position=None):
868
        """
869
870
        @param count:
871
        @param at_position:
872
        @return: bool, operation success
873
        """
874
        # Sanity checking
875
        if count < 1:
876
            return False
877
878
        # Insert new block(s) as row to the table model/view at the specified position.
879
        # Append new block(s) to the table model/view if no position was given.
880
        if at_position is None:
881
            at_position = self.model().rowCount()
882
        return self.model().insertRows(at_position, count)
883
884
    def remove_blocks(self, count=1, at_position=None):
885
        """
886
887
        @param count:
888
        @param at_position:
889
        @return: bool, operation success
890
        """
891
        # Sanity checking
892
        if count < 1:
893
            return False
894
895
        # Remove rows/blocks with index <at_position> to index <at_position + count - 1>
896
        # Remove last <count> number of blocks if no at_position was given.
897
        if at_position is None:
898
            at_position = self.model().rowCount() - count
899
        return self.model().removeRows(at_position, count)
900
901
    def clear(self):
902
        """
903
        Removes all PulseBlocks from the view/model and inserts a single one afterwards.
904
905
        @return: bool, operation success
906
        """
907
        success = self.remove_blocks(self.model().rowCount(), 0)
908
        if not success:
909
            return False
910
        self.add_blocks(1, 0)
911
        return True
912
913
    def get_ensemble(self):
914
        """
915
        Returns a (deep)copy of the PulseBlockEnsemble instance serving as model for this editor.
916
917
        @return: PulseBlockEnsemble, an instance of PulseBlockEnsemble
918
        """
919
        data_container = self.model().data(QtCore.QModelIndex(), self.model().blockEnsembleRole)
920
        ensemble_copy = copy.deepcopy(data_container)
921
        ensemble_copy.name = ''
922
        return ensemble_copy
923
924
    def load_ensemble(self, block_ensemble):
925
        """
926
        Load an instance of PulseBlockEnsemble into the model in order to view/edit it.
927
928
        @param block_ensemble: PulseBlockEnsemble, the PulseBlockEnsemble instance to load into the
929
                               model/view
930
        @return: bool, operation success
931
        """
932
        return self.model().set_block_ensemble(block_ensemble)
933
934
935
class SequenceEditorTableModel(QtCore.QAbstractTableModel):
936
    """
937
938
    """
939
    # User defined roles for model data access
940
    repetitionsRole = QtCore.Qt.UserRole + 1
941
    ensembleNameRole = QtCore.Qt.UserRole + 2
942
    goToRole = QtCore.Qt.UserRole + 4
943
    eventJumpToRole = QtCore.Qt.UserRole + 5
944
    eventTriggerRole = QtCore.Qt.UserRole + 6
945
    waitForRole = QtCore.Qt.UserRole + 7
946
    flagTriggerRole = QtCore.Qt.UserRole + 8
947
    flagHighRole = QtCore.Qt.UserRole + 9
948
    sequenceRole = QtCore.Qt.UserRole + 10
949
950
    def __init__(self):
951
        super().__init__()
952
953
        # list containing available ensemble names
954
        self.available_block_ensembles = None
955
956
        # The actual model data container.
957
        self._pulse_sequence = PulseSequence('EDITOR CONTAINER')
958
        # The default ensemble name for sequence steps
959
        self.__default_ensemble = ''
960
        # The headers for each column
961
        self.__horizontal_headers = ['BlockEnsemble',
962
                                     'Repetitions',
963
                                     'Go To',
964
                                     'Event Jump To',
965
                                     'Event Trigger',
966
                                     'Wait For',
967
                                     'Flag Trigger',
968
                                     'Flag High']
969
        return
970
971
    def set_available_block_ensembles(self, ensembles):
972
        """
973
974
        @param ensembles: list|set, list/set containing all available PulseBlockEnsemble names
975
        @return: int, error code (>=0: OK, <0: ERR)
976
        """
977
        # Convert to set
978
        if isinstance(ensembles, (list, dict)):
979
            ensembles = set(ensembles)
980
        elif not isinstance(ensembles, set):
981
            return -1
982
983
        # Do nothing if available ensembles have not changed
984
        if self.available_block_ensembles == ensembles:
985
            return 0
986
987
        self.available_block_ensembles = ensembles
988
989
        # Set default ensemble name
990
        if len(self.available_block_ensembles) > 0:
991
            self.__default_ensemble = sorted(self.available_block_ensembles)[0]
992
        else:
993
            self.__default_ensemble = ''
994
995
        # Remove ensembles from list that are not there anymore
996
        rows_to_remove = list()
997
        for row, (ensemble_name, params) in enumerate(self._pulse_sequence.ensemble_list):
998
            if ensemble_name not in ensembles:
999
                rows_to_remove.append(row)
1000
        for row in reversed(rows_to_remove):
1001
            self.removeRows(row, 1)
1002
1003
        # Check if the PulseSequence model instance is empty and set a single ensemble if True.
1004
        if len(self._pulse_sequence.ensemble_list) == 0:
1005
            self.insertRows(0, 1)
1006
1007
        return 0
1008
1009
    def set_rotating_frame(self, rotating_frame=True):
1010
        """
1011
1012
        @param rotating_frame:
1013
        @return:
1014
        """
1015
        if isinstance(rotating_frame, bool):
1016
            self._pulse_sequence.rotating_frame = rotating_frame
1017
        return
1018
1019
    def rowCount(self, parent=QtCore.QModelIndex()):
1020
        return len(self._pulse_sequence.ensemble_list)
1021
1022
    def columnCount(self, parent=QtCore.QModelIndex()):
1023
        return len(self.__horizontal_headers)
1024
1025
    def data(self, index, role=QtCore.Qt.DisplayRole):
1026
        if role == QtCore.Qt.DisplayRole:
1027
            return None
1028
1029
        if role == self.sequenceRole:
1030
            return self._pulse_sequence
1031
1032
        if not index.isValid():
1033
            return None
1034
1035
        if role == self.repetitionsRole:
1036
            return self._pulse_sequence.ensemble_list[index.row()][1].get('repetitions')
1037
        elif role == self.ensembleNameRole:
1038
            return self._pulse_sequence.ensemble_list[index.row()][0]
1039
        elif role == self.goToRole:
1040
            return self._pulse_sequence.ensemble_list[index.row()][1].get('go_to')
1041
        elif role == self.eventJumpToRole:
1042
            return self._pulse_sequence.ensemble_list[index.row()][1].get('event_jump_to')
1043
        elif role == self.eventTriggerRole:
1044
            return self._pulse_sequence.ensemble_list[index.row()][1].get('event_trigger')
1045
        elif role == self.waitForRole:
1046
            return self._pulse_sequence.ensemble_list[index.row()][1].get('wait_for')
1047
        elif role == self.flagTriggerRole:
1048
            return self._pulse_sequence.ensemble_list[index.row()][1].get('flag_trigger')
1049
        elif role == self.flagHighRole:
1050
            return self._pulse_sequence.ensemble_list[index.row()][1].get('flag_high')
1051
        else:
1052
            return None
1053
1054
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
1055
        """
1056
        """
1057
        # Delete potentially preexisting measurement_information dict upon edit
1058
        if role != self.sequenceRole:
1059
            self._pulse_sequence.measurement_information = dict()
1060
1061
        if role == self.ensembleNameRole and isinstance(data, str):
1062
            self._pulse_sequence.replace_ensemble(position=index.row(), ensemble_name=data)
1063
        elif role == self.repetitionsRole and isinstance(data, int):
1064
            self._pulse_sequence.ensemble_list[index.row()][1]['repetitions'] = data
1065
        elif role == self.goToRole and isinstance(data, int):
1066
            self._pulse_sequence.ensemble_list[index.row()][1]['go_to'] = data
1067
        elif role == self.eventJumpToRole and isinstance(data, int):
1068
            self._pulse_sequence.ensemble_list[index.row()][1]['event_jump_to'] = data
1069
        elif role == self.sequenceRole and isinstance(data, PulseSequence):
1070
            self._pulse_sequence = copy.deepcopy(data)
1071
            self._pulse_sequence.name = 'EDITOR CONTAINER'
1072
        elif role == self.eventTriggerRole and isinstance(data, str):
1073
            self._pulse_sequence.ensemble_list[index.row()][1]['event_trigger'] = data
1074
        elif role == self.waitForRole and isinstance(data, str):
1075
            self._pulse_sequence.ensemble_list[index.row()][1]['wait_for'] = data
1076
        elif role == self.flagTriggerRole and isinstance(data, str):
1077
            self._pulse_sequence.ensemble_list[index.row()][1]['flag_trigger'] = data
1078
        elif role == self.flagHighRole and isinstance(data, str):
1079
            self._pulse_sequence.ensemble_list[index.row()][1]['flag_high'] = data
1080
        return
1081
1082
    def headerData(self, section, orientation, role):
1083
        # Horizontal header
1084
        if orientation == QtCore.Qt.Horizontal:
1085
            if role == QtCore.Qt.DisplayRole and (0 < section < len(self.__horizontal_headers)):
1086
                return self.__horizontal_headers[section]
1087
        return super().headerData(section, orientation, role)
1088
1089
    def flags(self, index):
1090
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
1091
1092
    def insertRows(self, row, count, parent=None):
1093
        """
1094
1095
        @param row:
1096
        @param count:
1097
        @param parent:
1098
        @return:
1099
        """
1100
        # Do nothing if no ensembles are available
1101
        if len(self.available_block_ensembles) == 0:
1102
            return False
1103 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1104
        # Sanity/range checking
1105
        if row < 0 or row > self.rowCount() or not self.available_block_ensembles:
1106
            return False
1107
1108
        # Delete potentially preexisting measurement_information dict upon edit
1109
        self._pulse_sequence.measurement_information = dict()
1110
1111
        if parent is None:
1112
            parent = QtCore.QModelIndex()
1113
1114
        self.beginInsertRows(parent, row, row + count - 1)
1115
1116
        for i in range(count):
1117
            self._pulse_sequence.insert_ensemble(position=row,
1118
                                                 ensemble_name=self.__default_ensemble)
1119
1120
        self.endInsertRows()
1121
        return True
1122
1123
    def removeRows(self, row, count, parent=None):
1124
        """
1125
1126
        @param row:
1127
        @param count:
1128
        @param parent:
1129
        @return:
1130
        """
1131
        # Sanity/range checking
1132
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
1133
            return False
1134
1135
        # Delete potentially preexisting measurement_information dict upon edit
1136
        self._pulse_sequence.measurement_information = dict()
1137
1138
        if parent is None:
1139
            parent = QtCore.QModelIndex()
1140
1141
        self.beginRemoveRows(parent, row, row + count - 1)
1142
1143
        for i in range(count):
1144
            self._pulse_sequence.delete_ensemble(position=row)
1145
1146
        self.endRemoveRows()
1147
        return True
1148
1149
    def set_pulse_sequence(self, pulse_sequence):
1150
        """
1151
1152
        @param pulse_sequence:
1153
        @return:
1154
        """
1155
        if not isinstance(pulse_sequence, PulseSequence):
1156
            return False
1157
        self.beginResetModel()
1158
        self.setData(QtCore.QModelIndex(), pulse_sequence, self.sequenceRole)
1159
        self.endResetModel()
1160
        return True
1161
1162
1163
class SequenceEditor(QtWidgets.QTableView):
1164
    """
1165
1166
    """
1167
    def __init__(self, parent):
1168
        # Initialize inherited QTableView
1169
        super().__init__(parent)
1170
1171
        # Create custom data model and hand it to the QTableView.
1172
        # (essentially it's a PulseSequence instance with QAbstractTableModel interface)
1173
        model = SequenceEditorTableModel()
1174
        self.setModel(model)
1175
1176
        # Set item selection and editing behaviour
1177
        self.setEditTriggers(
1178
            QtGui.QAbstractItemView.CurrentChanged | QtGui.QAbstractItemView.SelectedClicked)
1179
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
1180
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
1181
1182
        # Set item delegate (ComboBox) for PulseBlockEnsemble column
1183
        self.setItemDelegateForColumn(0, ComboBoxItemDelegate(self, list(),
1184
                                                              self.model().ensembleNameRole,
1185
                                                              QtCore.QSize(100, 50)))
1186
        # Set item delegate (SpinBoxes) for repetition column
1187
        self.setItemDelegateForColumn(1, SpinBoxItemDelegate(self, {'init_val': 1, 'min': -1},
1188
                                                             self.model().repetitionsRole))
1189
        # Set item delegate (SpinBoxes) for go_to column
1190
        self.setItemDelegateForColumn(2, SpinBoxItemDelegate(self, {'init_val': -1, 'min': -1},
1191
                                                             self.model().goToRole))
1192
        # Set item delegate (SpinBoxes) for event_jump_to column
1193
        self.setItemDelegateForColumn(3, SpinBoxItemDelegate(self, {'init_val': -1, 'min': -1},
1194
                                                             self.model().eventJumpToRole))
1195
        # Set item delegate (ComboBox) for event_trigger column
1196
        self.setItemDelegateForColumn(4, ComboBoxItemDelegate(self, ['OFF'],
1197
                                                              self.model().eventTriggerRole))
1198
        # Set item delegate (ComboBox) for wait_for column
1199
        self.setItemDelegateForColumn(5, ComboBoxItemDelegate(self, ['OFF'],
1200
                                                              self.model().waitForRole))
1201
        # Set item delegate (ComboBox) for flag_trigger column
1202
        self.setItemDelegateForColumn(6, ComboBoxItemDelegate(self, ['OFF'],
1203
                                                              self.model().flagTriggerRole))
1204
        # Set item delegate (ComboBox) for flag_high column
1205
        self.setItemDelegateForColumn(7, ComboBoxItemDelegate(self, ['OFF'],
1206
                                                              self.model().flagHighRole))
1207
1208
        # Set header sizes
1209
        self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
1210
        # self.horizontalHeader().setDefaultSectionSize(100)
1211
        # self.horizontalHeader().setStyleSheet('QHeaderView { font-weight: 400; }')
1212
        self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
1213
        self.verticalHeader().setDefaultSectionSize(50)
1214
        for col in range(self.columnCount()):
1215
            width = self.itemDelegateForColumn(col).sizeHint().width()
1216
            self.setColumnWidth(col, width)
1217
        return
1218
1219
    def set_available_block_ensembles(self, ensembles):
1220
        """
1221
1222
        @param ensembles:
1223
        @return: int, error code (>=0: OK, <0: ERR)
1224
        """
1225
        err_code = self.model().set_available_block_ensembles(ensembles)
1226
        if err_code >= 0:
1227
            delegate = ComboBoxItemDelegate(
1228
                self, sorted(self.model().available_block_ensembles), self.model().ensembleNameRole)
1229
            self.setItemDelegateForColumn(0, delegate)
1230
        return err_code
1231
1232
    def set_rotating_frame(self, rotating_frame=True):
1233
        """
1234
1235
        @param rotating_frame:
1236
        @return:
1237
        """
1238
        self.model().set_rotating_frame(rotating_frame)
1239
        return
1240
1241
    def set_available_triggers(self, trigger_list):
1242
        """
1243
1244
        @param list trigger_list: List of strings describing the available pulse generator trigger
1245
                                  input channels.
1246
        """
1247
        if not isinstance(trigger_list, list):
1248
            return
1249
        trigger_list.insert(0, 'OFF')
1250
        # Set item delegate (ComboBox) for event_trigger column
1251
        self.setItemDelegateForColumn(4, ComboBoxItemDelegate(self, trigger_list,
1252
                                                              self.model().eventTriggerRole))
1253
        # Set item delegate (ComboBox) for wait_for column
1254
        self.setItemDelegateForColumn(5, ComboBoxItemDelegate(self, trigger_list,
1255
                                                              self.model().waitForRole))
1256
        return
1257
1258
    def set_available_flags(self, flag_list):
1259
        """
1260
1261
        @param list flag_list: List of strings describing the available pulse generator flag output
1262
                               channels.
1263
        """
1264
        if not isinstance(flag_list, list):
1265
            return
1266
        flag_list.insert(0, 'OFF')
1267
        # Set item delegate (ComboBox) for event_trigger column
1268
        self.setItemDelegateForColumn(6, ComboBoxItemDelegate(self, flag_list,
1269
                                                              self.model().flagTriggerRole))
1270
        # Set item delegate (ComboBox) for wait_for column
1271
        self.setItemDelegateForColumn(7, ComboBoxItemDelegate(self, flag_list,
1272
                                                              self.model().flagHighRole))
1273
        return
1274
1275
    def rowCount(self):
1276
        return self.model().rowCount()
1277
1278
    def columnCount(self):
1279
        return self.model().columnCount()
1280
1281
    def currentRow(self):
1282
        index = self.currentIndex()
1283
        return index.row() if index.isValid() else 0
1284
1285
    def currentColumn(self):
1286
        index = self.currentIndex()
1287
        return index.column() if index.isValid() else 0
1288
1289
    def add_steps(self, count=1, at_position=None):
1290
        """
1291
1292
        @param count:
1293
        @param at_position:
1294
        @return: bool, operation success
1295
        """
1296
        # Sanity checking
1297
        if count < 1:
1298
            return False
1299
1300
        # Insert new sequence step(s) as row to the table model/view at the specified position.
1301
        # Append new sequence step(s) to the table model/view if no position was given.
1302
        if at_position is None:
1303
            at_position = self.model().rowCount()
1304
        return self.model().insertRows(at_position, count)
1305
1306
    def remove_steps(self, count=1, at_position=None):
1307
        """
1308
1309
        @param count:
1310
        @param at_position:
1311
        @return: bool, operation success
1312
        """
1313
        # Sanity checking
1314
        if count < 1:
1315
            return False
1316
1317
        # Remove rows/sequence steps with index <at_position> to index <at_position + count - 1>
1318
        # Remove last <count> number of sequence steps if no at_position was given.
1319
        if at_position is None:
1320
            at_position = self.model().rowCount() - count
1321
        return self.model().removeRows(at_position, count)
1322
1323
    def clear(self):
1324
        """
1325
        Removes all sequence steps from the view/model and inserts a single one afterwards.
1326
1327
        @return: bool, operation success
1328
        """
1329
        success = self.remove_steps(self.model().rowCount(), 0)
1330
        if success:
1331
            self.add_steps(1, 0)
1332
        return success
1333
1334
    def get_sequence(self):
1335
        """
1336
        Returns a (deep)copy of the PulseSequence instance serving as model for this editor.
1337
1338
        @return: object, an instance of PulseSequence
1339
        """
1340
        data_container = self.model().data(QtCore.QModelIndex(), self.model().sequenceRole)
1341
        sequence_copy = copy.deepcopy(data_container)
1342
        sequence_copy.name = ''
1343
        sequence_copy.refresh_parameters()
1344
        return sequence_copy
1345
1346
    def load_sequence(self, pulse_sequence):
1347
        """
1348
        Load an instance of PulseSequence into the model in order to view/edit it.
1349
1350
        @param pulse_sequence: object, the PulseSequence instance to load into the model/view
1351
        @return: bool, operation success
1352
        """
1353
        return self.model().set_pulse_sequence(pulse_sequence)
1354