Completed
Pull Request — master (#384)
by
unknown
01:25
created

BlockEditorTableModel.setData()   F

Complexity

Conditions 22

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 22
dl 0
loc 62
rs 0
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like BlockEditorTableModel.setData() 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
from logic.pulsed.sampling_functions import SamplingFunctions
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:
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: 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)
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
            self._pulse_block.refresh_parameters()
199
            return self._pulse_block
200
        if role == self.analogChannelSetRole:
201
            return self._pulse_block.analog_channels
202
        if role == self.digitalChannelSetRole:
203
            return self._pulse_block.digital_channels
204
        if role == self.channelSetRole:
205
            return self._pulse_block.channel_set
206
207
        if not index.isValid():
208
            return None
209
210
        if role == self.lengthRole:
211
            return self._pulse_block[index.row()].init_length_s
212
        if role == self.incrementRole:
213
            return self._pulse_block[index.row()].increment_s
214
        if role == self.digitalStateRole:
215
            return self._pulse_block[index.row()].digital_high
216
        if role == self.analogFunctionRole:
217
            element = self._pulse_block[index.row()]
218
            if len(self.digital_channels) > 0:
219
                col_offset = 3
220
            else:
221
                col_offset = 2
222
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
223
            return element.pulse_function[analog_chnl]
224
        if role == self.analogShapeRole:
225
            element = self._pulse_block[index.row()]
226
            if len(self.digital_channels) > 0:
227
                col_offset = 3
228
            else:
229
                col_offset = 2
230
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
231
            return element.pulse_function[analog_chnl].__class__.__name__
232
        if role == self.analogParameterRole:
233
            element = self._pulse_block[index.row()]
234
            if len(self.digital_channels) > 0:
235
                col_offset = 3
236
            else:
237
                col_offset = 2
238
            analog_chnl = self.analog_channels[(index.column() - col_offset) // 2]
239
            return vars(element.pulse_function[analog_chnl])
240
        if role == self.blockElementRole:
241
            return self._pulse_block[index.row()]
242
243
        return None
244
245
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
246
        """
247
        """
248
        if isinstance(data, PulseBlockElement):
249
            self._pulse_block[index.row()] = copy.deepcopy(data)
250
            return
251
252
        if role == self.lengthRole and isinstance(data, (int, float)):
253
            old_elem = self._pulse_block[index.row()]
254
            if data != old_elem.init_length_s:
255
                new_elem = PulseBlockElement(init_length_s=max(0, data),
256
                                             increment_s=old_elem.increment_s,
257
                                             pulse_function=old_elem.pulse_function,
258
                                             digital_high=old_elem.digital_high)
259
                self._pulse_block[index.row()] = new_elem
260
        elif role == self.incrementRole and isinstance(data, (int, float)):
261
            old_elem = self._pulse_block[index.row()]
262
            if data != old_elem.increment_s:
263
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
264
                                             increment_s=data,
265
                                             pulse_function=old_elem.pulse_function,
266
                                             digital_high=old_elem.digital_high)
267
                self._pulse_block[index.row()] = new_elem
268
        elif role == self.digitalStateRole and isinstance(data, dict):
269
            old_elem = self._pulse_block[index.row()]
270
            if data != old_elem.digital_high:
271
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
272
                                             increment_s=old_elem.increment_s,
273
                                             pulse_function=old_elem.pulse_function,
274
                                             digital_high=data.copy())
275
                self._pulse_block[index.row()] = new_elem
276
        elif role == self.analogShapeRole and isinstance(data, str):
277
            if self.data(index=index, role=self.analogShapeRole) != data:
278
                old_elem = self._pulse_block[index.row()]
279
280
                sampling_func = getattr(SamplingFunctions, data)
281
                col_offset = 3 if self.digital_channels else 2
282
                chnl = self.analog_channels[(index.column() - col_offset) // 2]
283
284
                pulse_function = old_elem.pulse_function.copy()
285
                pulse_function[chnl] = sampling_func()
286
287
                new_elem = PulseBlockElement(init_length_s=old_elem.init_length_s,
288
                                             increment_s=old_elem.increment_s,
289
                                             pulse_function=pulse_function,
290
                                             digital_high=old_elem.digital_high)
291
                self._pulse_block[index.row()] = new_elem
292
293
                new_column_width = self._get_column_width(index.column()+1)
294
                if new_column_width >= 0 and new_column_width != self._col_widths[index.column()+1]:
295
                    self._col_widths[index.column() + 1] = new_column_width
296
                    self._notify_column_width(index.column()+1)
297
298
        elif role == self.analogParameterRole and isinstance(data, dict):
299
            col_offset = 3 if self.digital_channels else 2
300
            chnl = self.analog_channels[(index.column() - col_offset) // 2]
301
            self._pulse_block[index.row()].pulse_function[chnl].__init__(**data)
302
        elif role == self.pulseBlockRole and isinstance(data, PulseBlock):
303
            self._pulse_block = copy.deepcopy(data)
304
            self._pulse_block.name = 'EDITOR CONTAINER'
305
            self._pulse_block.refresh_parameters()
306
        return
307
308
    def headerData(self, section, orientation, role):
309
        # Horizontal header
310
        if orientation == QtCore.Qt.Horizontal:
311
            # if role == QtCore.Qt.BackgroundRole:
312
            #     return QVariant(QBrush(QColor(Qt::green), Qt::SolidPattern))
313
            if role == QtCore.Qt.SizeHintRole:
314
                if section < len(self._col_widths):
315
                    return QtCore.QSize(self._col_widths[section], 40)
316
317
            if role == QtCore.Qt.DisplayRole:
318
                if section < len(self._h_header_data):
319
                    return self._h_header_data[section]
320
321
        # Vertical header
322
        # if orientation == QtCore.Qt.Vertical:
323
        #     if role == QtCore.Qt.BackgroundRole:
324
        #         return QtCore.Qt.QVariant(QtGui.Qt.QBrush(QtGui.Qt.QColor(QtCore.Qt.green),
325
        #                                                   QtCore.Qt.SolidPattern))
326
        return super().headerData(section, orientation, role)
327
328
    def flags(self, index):
329
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
330
331
    def insertRows(self, row, count, parent=None):
332
        """
333
334
        @param row:
335
        @param count:
336
        @param parent:
337
        @return:
338
        """
339
        # Sanity/range checking
340
        if row < 0 or row > self.rowCount():
341
            return False
342
343
        if parent is None:
344
            parent = QtCore.QModelIndex()
345
346
        self.beginInsertRows(parent, row, row + count - 1)
347
348
        for i in range(count):
349
            self._pulse_block.insert(position=row, element=self.__default_element)
350
351
        self.endInsertRows()
352
        return True
353
354
    def removeRows(self, row, count, parent=None):
355
        """
356
357
        @param row:
358
        @param count:
359
        @param parent:
360
        @return:
361
        """
362
        # Sanity/range checking
363
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
364
            return False
365
366
        if parent is None:
367
            parent = QtCore.QModelIndex()
368
369
        self.beginRemoveRows(parent, row, row + count - 1)
370
371
        del self._pulse_block[row:row + count]
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(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
    blockElementRole = QtCore.Qt.UserRole + 4
598
599
    def __init__(self):
600
        super().__init__()
601
602
        # set containing available block names
603
        self.available_pulse_blocks = None
604
605
        # The actual model data container.
606
        self._block_ensemble = PulseBlockEnsemble('EDITOR CONTAINER')
607
        # The default block name
608
        self.__default_block = ''
609
        return
610
611 View Code Duplication
    def set_available_pulse_blocks(self, blocks):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
612
        """
613
614
        @param blocks: list|dict|set, list/dict/set containing all available PulseBlock names
615
        @return: int, error code (>=0: OK, <0: ERR)
616
        """
617
        # Convert to set
618
        if isinstance(blocks, (list, dict)):
619
            blocks = set(blocks)
620
        elif not isinstance(blocks, set):
621
            return -1
622
623
        # Do nothing if available blocks are unchanged
624
        if self.available_pulse_blocks == blocks:
625
            return 0
626
627
        self.available_pulse_blocks = blocks
628
629
        # Set default block
630
        if len(self.available_pulse_blocks) > 0:
631
            self.__default_block = sorted(self.available_pulse_blocks)[0]
632
        else:
633
            self.__default_block = ''
634
635
        # Remove blocks from list that are not there anymore
636
        for row, (block_name, reps) in enumerate(self._block_ensemble):
637
            if block_name not in blocks:
638
                self.removeRows(row, 1)
639
640
        # Check if the PulseBlockEnsemble model instance is empty and set a single block if True.
641
        if self.rowCount() == 0:
642
            self.insertRows(0, 1)
643
644
        return 0
645
646
    def set_rotating_frame(self, rotating_frame=True):
647
        """
648
649
        @param rotating_frame:
650
        @return:
651
        """
652
        if isinstance(rotating_frame, bool):
653
            self._block_ensemble.rotating_frame = rotating_frame
654
        return
655
656
    def rowCount(self, parent=QtCore.QModelIndex()):
657
        return len(self._block_ensemble)
658
659
    def columnCount(self, parent=QtCore.QModelIndex()):
660
        return 2
661
662
    def data(self, index, role=QtCore.Qt.DisplayRole):
663
        if role == QtCore.Qt.DisplayRole:
664
            return None
665
666
        if role == self.blockEnsembleRole:
667
            return self._block_ensemble
668
669
        if not index.isValid():
670
            return None
671
672
        if role == self.repetitionsRole:
673
            return self._block_ensemble[index.row()][1]
674
        if role == self.blockNameRole:
675
            return self._block_ensemble[index.row()][0]
676
        if role == self.blockElementRole:
677
            return self._block_ensemble[index.row()]
678
        return None
679
680
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
681
        """
682
        """
683
        if role == self.repetitionsRole and isinstance(data, int):
684
            block_name = self._block_ensemble[index.row()][0]
685
            self._block_ensemble[index.row()] = (block_name, data)
686
        elif role == self.blockNameRole and isinstance(data, str):
687
            reps = self._block_ensemble[index.row()][1]
688
            self._block_ensemble[index.row()] = (data, reps)
689
        elif role == self.blockElementRole and isinstance(data, tuple):
690
            self._block_ensemble[index.row()] = data
691
        elif role == self.blockEnsembleRole and isinstance(data, PulseBlockEnsemble):
692
            self._block_ensemble = copy.deepcopy(data)
693
            self._block_ensemble.name = 'EDITOR CONTAINER'
694
        return
695
696
    def headerData(self, section, orientation, role):
697
        # Horizontal header
698
        if orientation == QtCore.Qt.Horizontal:
699
            if role == QtCore.Qt.DisplayRole:
700
                if section == 0:
701
                    return 'PulseBlock'
702
                if section == 1:
703
                    return 'repetitions'
704
            # if role == QtCore.Qt.BackgroundRole:
705
            #     return QVariant(QBrush(QColor(Qt::green), Qt::SolidPattern))
706
            # if role == QtCore.Qt.SizeHintRole:
707
            #     if section < len(self._col_widths):
708
            #         return QtCore.QSize(self._col_widths[section], 40)
709
        return super().headerData(section, orientation, role)
710
711
    def flags(self, index):
712
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
713
714 View Code Duplication
    def insertRows(self, row, count, parent=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
715
        """
716
717
        @param row:
718
        @param count:
719
        @param parent:
720
        @return:
721
        """
722
        # Do nothing if no blocks are available
723
        if len(self.available_pulse_blocks) == 0:
724
            return False
725
726
        # Sanity/range checking
727
        if row < 0 or row > self.rowCount() or not self.available_pulse_blocks:
728
            return False
729
730
        if parent is None:
731
            parent = QtCore.QModelIndex()
732
733
        self.beginInsertRows(parent, row, row + count - 1)
734
735
        for i in range(count):
736
            self._block_ensemble.insert(position=row, element=(self.__default_block, 0))
737
738
        self.endInsertRows()
739
        return True
740
741
    def removeRows(self, row, count, parent=None):
742
        """
743
744
        @param row:
745
        @param count:
746
        @param parent:
747
        @return:
748
        """
749
        # Sanity/range checking
750
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
751
            return False
752
753
        if parent is None:
754
            parent = QtCore.QModelIndex()
755
756
        self.beginRemoveRows(parent, row, row + count - 1)
757
758
        del self._block_ensemble[row:row + count]
759
760
        self.endRemoveRows()
761
        return True
762
763
    def set_block_ensemble(self, block_ensemble):
764
        """
765
766
        @param block_ensemble:
767
        @return:
768
        """
769
        if not isinstance(block_ensemble, PulseBlockEnsemble):
770
            return False
771
        self.beginResetModel()
772
        self.setData(QtCore.QModelIndex(), block_ensemble, self.blockEnsembleRole)
773
        self.endResetModel()
774
        return True
775
776
777
class EnsembleEditor(QtWidgets.QTableView):
778
    """
779
780
    """
781
    def __init__(self, parent):
782
        # Initialize inherited QTableView
783
        super().__init__(parent)
784
785
        # Create custom data model and hand it to the QTableView.
786
        # (essentially it's a PulseBlockEnsemble instance with QAbstractTableModel interface)
787
        model = EnsembleEditorTableModel()
788
        self.setModel(model)
789
790
        # Set item selection and editing behaviour
791
        self.setEditTriggers(
792
            QtGui.QAbstractItemView.CurrentChanged | QtGui.QAbstractItemView.SelectedClicked)
793
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
794
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
795
796
        # Set item delegate (ComboBox) for PulseBlock column
797
        self.setItemDelegateForColumn(0, ComboBoxItemDelegate(self, list(),
798
                                                              self.model().blockNameRole,
799
                                                              QtCore.QSize(100, 50)))
800
        # Set item delegate (SpinBoxes) for repetition column
801
        repetition_item_dict = {'init_val': 0, 'min': 0, 'max': (2**31)-1}
802
        self.setItemDelegateForColumn(1, SpinBoxItemDelegate(self, repetition_item_dict,
803
                                                             self.model().repetitionsRole))
804
805
        # Set header sizes
806
        self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
807
        # self.horizontalHeader().setDefaultSectionSize(100)
808
        # self.horizontalHeader().setStyleSheet('QHeaderView { font-weight: 400; }')
809
        self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
810
        self.verticalHeader().setDefaultSectionSize(50)
811
        for col in range(self.columnCount()):
812
            width = self.itemDelegateForColumn(col).sizeHint().width()
813
            self.setColumnWidth(col, width)
814
        return
815
816
    def set_available_pulse_blocks(self, blocks):
817
        """
818
819
        @param list|set blocks:
820
        @return: int, error code (>=0: OK, <0: ERR)
821
        """
822
        if isinstance(blocks, (list, dict, set)):
823
            blocks = sorted(blocks)
824
        else:
825
            return -1
826
827
        err_code = self.model().set_available_pulse_blocks(blocks)
828
        self.setItemDelegateForColumn(
829
            0,
830
            ComboBoxItemDelegate(self, blocks, self.model().blockNameRole))
831
        return err_code
832
833
    def set_rotating_frame(self, rotating_frame=True):
834
        """
835
836
        @param rotating_frame:
837
        @return:
838
        """
839
        self.model().set_rotating_frame(rotating_frame)
840
        return
841
842
    def rowCount(self):
843
        return self.model().rowCount()
844
845
    def columnCount(self):
846
        return self.model().columnCount()
847
848
    def currentRow(self):
849
        index = self.currentIndex()
850
        return index.row() if index.isValid() else 0
851
852
    def currentColumn(self):
853
        index = self.currentIndex()
854
        return index.column() if index.isValid() else 0
855
856
    def add_blocks(self, count=1, at_position=None):
857
        """
858
859
        @param count:
860
        @param at_position:
861
        @return: bool, operation success
862
        """
863
        # Sanity checking
864
        if count < 1:
865
            return False
866
867
        # Insert new block(s) as row to the table model/view at the specified position.
868
        # Append new block(s) to the table model/view if no position was given.
869
        if at_position is None:
870
            at_position = self.model().rowCount()
871
        return self.model().insertRows(at_position, count)
872
873
    def remove_blocks(self, count=1, at_position=None):
874
        """
875
876
        @param count:
877
        @param at_position:
878
        @return: bool, operation success
879
        """
880
        # Sanity checking
881
        if count < 1:
882
            return False
883
884
        # Remove rows/blocks with index <at_position> to index <at_position + count - 1>
885
        # Remove last <count> number of blocks if no at_position was given.
886
        if at_position is None:
887
            at_position = self.model().rowCount() - count
888
        return self.model().removeRows(at_position, count)
889
890
    def clear(self):
891
        """
892
        Removes all PulseBlocks from the view/model and inserts a single one afterwards.
893
894
        @return: bool, operation success
895
        """
896
        success = self.remove_blocks(self.model().rowCount(), 0)
897
        if not success:
898
            return False
899
        self.add_blocks(1, 0)
900
        return True
901
902
    def get_ensemble(self):
903
        """
904
        Returns a (deep)copy of the PulseBlockEnsemble instance serving as model for this editor.
905
906
        @return: PulseBlockEnsemble, an instance of PulseBlockEnsemble
907
        """
908
        data_container = self.model().data(QtCore.QModelIndex(), self.model().blockEnsembleRole)
909
        ensemble_copy = copy.deepcopy(data_container)
910
        ensemble_copy.name = ''
911
        return ensemble_copy
912
913
    def load_ensemble(self, block_ensemble):
914
        """
915
        Load an instance of PulseBlockEnsemble into the model in order to view/edit it.
916
917
        @param block_ensemble: PulseBlockEnsemble, the PulseBlockEnsemble instance to load into the
918
                               model/view
919
        @return: bool, operation success
920
        """
921
        return self.model().set_block_ensemble(block_ensemble)
922
923
924
class SequenceEditorTableModel(QtCore.QAbstractTableModel):
925
    """
926
927
    """
928
    # User defined roles for model data access
929
    repetitionsRole = QtCore.Qt.UserRole + 1
930
    ensembleNameRole = QtCore.Qt.UserRole + 2
931
    goToRole = QtCore.Qt.UserRole + 4
932
    eventJumpToRole = QtCore.Qt.UserRole + 5
933
    eventTriggerRole = QtCore.Qt.UserRole + 6
934
    waitForRole = QtCore.Qt.UserRole + 7
935
    flagTriggerRole = QtCore.Qt.UserRole + 8
936
    flagHighRole = QtCore.Qt.UserRole + 9
937
    sequenceRole = QtCore.Qt.UserRole + 10
938
939
    def __init__(self):
940
        super().__init__()
941
942
        # list containing available ensemble names
943
        self.available_block_ensembles = None
944
945
        # The actual model data container.
946
        self._pulse_sequence = PulseSequence('EDITOR CONTAINER')
947
        # The default ensemble name for sequence steps
948
        self.__default_ensemble = ''
949
        # The headers for each column
950
        self.__horizontal_headers = ['BlockEnsemble',
951
                                     'Repetitions',
952
                                     'Go To',
953
                                     'Event Jump To',
954
                                     'Event Trigger',
955
                                     'Wait For',
956
                                     'Flag Trigger',
957
                                     'Flag High']
958
        return
959
960 View Code Duplication
    def set_available_block_ensembles(self, ensembles):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
961
        """
962
963
        @param ensembles: list|set, list/set containing all available PulseBlockEnsemble names
964
        @return: int, error code (>=0: OK, <0: ERR)
965
        """
966
        # Convert to set
967
        if isinstance(ensembles, (list, dict)):
968
            ensembles = set(ensembles)
969
        elif not isinstance(ensembles, set):
970
            return -1
971
972
        # Do nothing if available ensembles have not changed
973
        if self.available_block_ensembles == ensembles:
974
            return 0
975
976
        self.available_block_ensembles = ensembles
977
978
        # Set default ensemble name
979
        if len(self.available_block_ensembles) > 0:
980
            self.__default_ensemble = sorted(self.available_block_ensembles)[0]
981
        else:
982
            self.__default_ensemble = ''
983
984
        # Remove ensembles from list that are not there anymore
985
        rows_to_remove = list()
986
        for row, (ensemble_name, params) in enumerate(self._pulse_sequence):
987
            if ensemble_name not in ensembles:
988
                rows_to_remove.append(row)
989
        for row in reversed(rows_to_remove):
990
            self.removeRows(row, 1)
991
992
        # Check if the PulseSequence model instance is empty and set a single ensemble if True.
993
        if len(self._pulse_sequence) == 0:
994
            self.insertRows(0, 1)
995
996
        return 0
997
998
    def set_rotating_frame(self, rotating_frame=True):
999
        """
1000
1001
        @param rotating_frame:
1002
        @return:
1003
        """
1004
        if isinstance(rotating_frame, bool):
1005
            self._pulse_sequence.rotating_frame = rotating_frame
1006
        return
1007
1008
    def rowCount(self, parent=QtCore.QModelIndex()):
1009
        return len(self._pulse_sequence)
1010
1011
    def columnCount(self, parent=QtCore.QModelIndex()):
1012
        return len(self.__horizontal_headers)
1013
1014
    def data(self, index, role=QtCore.Qt.DisplayRole):
1015
        if role == QtCore.Qt.DisplayRole:
1016
            return None
1017
1018
        if role == self.sequenceRole:
1019
            return self._pulse_sequence
1020
1021
        if not index.isValid():
1022
            return None
1023
1024
        if role == self.repetitionsRole:
1025
            return self._pulse_sequence[index.row()][1].get('repetitions')
1026
        elif role == self.ensembleNameRole:
1027
            return self._pulse_sequence[index.row()][0]
1028
        elif role == self.goToRole:
1029
            return self._pulse_sequence[index.row()][1].get('go_to')
1030
        elif role == self.eventJumpToRole:
1031
            return self._pulse_sequence[index.row()][1].get('event_jump_to')
1032
        elif role == self.eventTriggerRole:
1033
            return self._pulse_sequence[index.row()][1].get('event_trigger')
1034
        elif role == self.waitForRole:
1035
            return self._pulse_sequence[index.row()][1].get('wait_for')
1036
        elif role == self.flagTriggerRole:
1037
            return self._pulse_sequence[index.row()][1].get('flag_trigger')
1038
        elif role == self.flagHighRole:
1039
            return self._pulse_sequence[index.row()][1].get('flag_high')
1040
        else:
1041
            return None
1042
1043
    def setData(self, index, data, role=QtCore.Qt.DisplayRole):
1044
        """
1045
        """
1046
        if role == self.ensembleNameRole and isinstance(data, str):
1047
            params = self._pulse_sequence[index.row()][1]
1048
            self._pulse_sequence[index.row()] = (data, params)
1049
        elif role == self.repetitionsRole and isinstance(data, int):
1050
            self._pulse_sequence[index.row()][1]['repetitions'] = data
1051
        elif role == self.goToRole and isinstance(data, int):
1052
            self._pulse_sequence[index.row()][1]['go_to'] = data
1053
        elif role == self.eventJumpToRole and isinstance(data, int):
1054
            self._pulse_sequence[index.row()][1]['event_jump_to'] = data
1055
        elif role == self.eventTriggerRole and isinstance(data, str):
1056
            self._pulse_sequence[index.row()][1]['event_trigger'] = data
1057
        elif role == self.waitForRole and isinstance(data, str):
1058
            self._pulse_sequence[index.row()][1]['wait_for'] = data
1059
        elif role == self.flagTriggerRole and isinstance(data, str):
1060
            self._pulse_sequence[index.row()][1]['flag_trigger'] = data
1061
        elif role == self.flagHighRole and isinstance(data, str):
1062
            self._pulse_sequence[index.row()][1]['flag_high'] = data
1063
        elif role == self.sequenceRole and isinstance(data, PulseSequence):
1064
            self._pulse_sequence = copy.deepcopy(data)
1065
            self._pulse_sequence.name = 'EDITOR CONTAINER'
1066
        return
1067
1068
    def headerData(self, section, orientation, role):
1069
        # Horizontal header
1070
        if orientation == QtCore.Qt.Horizontal:
1071
            if role == QtCore.Qt.DisplayRole and (0 < section < len(self.__horizontal_headers)):
1072
                return self.__horizontal_headers[section]
1073
        return super().headerData(section, orientation, role)
1074
1075
    def flags(self, index):
1076
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
1077
1078 View Code Duplication
    def insertRows(self, row, count, parent=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1079
        """
1080
1081
        @param row:
1082
        @param count:
1083
        @param parent:
1084
        @return:
1085
        """
1086
        # Do nothing if no ensembles are available
1087
        if len(self.available_block_ensembles) == 0:
1088
            return False
1089
1090
        # Sanity/range checking
1091
        if row < 0 or row > self.rowCount() or not self.available_block_ensembles:
1092
            return False
1093
1094
        if parent is None:
1095
            parent = QtCore.QModelIndex()
1096
1097
        self.beginInsertRows(parent, row, row + count - 1)
1098
1099
        for i in range(count):
1100
            self._pulse_sequence.insert(position=row, element=self.__default_ensemble)
1101
1102
        self.endInsertRows()
1103
        return True
1104
1105
    def removeRows(self, row, count, parent=None):
1106
        """
1107
1108
        @param row:
1109
        @param count:
1110
        @param parent:
1111
        @return:
1112
        """
1113
        # Sanity/range checking
1114
        if row < 0 or row >= self.rowCount() or (row + count) > self.rowCount():
1115
            return False
1116
1117
        if parent is None:
1118
            parent = QtCore.QModelIndex()
1119
1120
        self.beginRemoveRows(parent, row, row + count - 1)
1121
1122
        del self._pulse_sequence[row:row + count]
1123
1124
        self.endRemoveRows()
1125
        return True
1126
1127
    def set_pulse_sequence(self, pulse_sequence):
1128
        """
1129
1130
        @param pulse_sequence:
1131
        @return:
1132
        """
1133
        if not isinstance(pulse_sequence, PulseSequence):
1134
            return False
1135
        self.beginResetModel()
1136
        self.setData(QtCore.QModelIndex(), pulse_sequence, self.sequenceRole)
1137
        self.endResetModel()
1138
        return True
1139
1140
1141
class SequenceEditor(QtWidgets.QTableView):
1142
    """
1143
1144
    """
1145
    def __init__(self, parent):
1146
        # Initialize inherited QTableView
1147
        super().__init__(parent)
1148
1149
        # Create custom data model and hand it to the QTableView.
1150
        # (essentially it's a PulseSequence instance with QAbstractTableModel interface)
1151
        model = SequenceEditorTableModel()
1152
        self.setModel(model)
1153
1154
        # Set item selection and editing behaviour
1155
        self.setEditTriggers(
1156
            QtGui.QAbstractItemView.CurrentChanged | QtGui.QAbstractItemView.SelectedClicked)
1157
        self.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
1158
        self.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
1159
1160
        # Set item delegate (ComboBox) for PulseBlockEnsemble column
1161
        self.setItemDelegateForColumn(0, ComboBoxItemDelegate(self, list(),
1162
                                                              self.model().ensembleNameRole,
1163
                                                              QtCore.QSize(100, 50)))
1164
        # Set item delegate (SpinBoxes) for repetition column
1165
        self.setItemDelegateForColumn(1, SpinBoxItemDelegate(self, {'init_val': 1, 'min': -1},
1166
                                                             self.model().repetitionsRole))
1167
        # Set item delegate (SpinBoxes) for go_to column
1168
        self.setItemDelegateForColumn(2, SpinBoxItemDelegate(self, {'init_val': -1, 'min': -1},
1169
                                                             self.model().goToRole))
1170
        # Set item delegate (SpinBoxes) for event_jump_to column
1171
        self.setItemDelegateForColumn(3, SpinBoxItemDelegate(self, {'init_val': -1, 'min': -1},
1172
                                                             self.model().eventJumpToRole))
1173
        # Set item delegate (ComboBox) for event_trigger column
1174
        self.setItemDelegateForColumn(4, ComboBoxItemDelegate(self, ['OFF'],
1175
                                                              self.model().eventTriggerRole))
1176
        # Set item delegate (ComboBox) for wait_for column
1177
        self.setItemDelegateForColumn(5, ComboBoxItemDelegate(self, ['OFF'],
1178
                                                              self.model().waitForRole))
1179
        # Set item delegate (ComboBox) for flag_trigger column
1180
        self.setItemDelegateForColumn(6, ComboBoxItemDelegate(self, ['OFF'],
1181
                                                              self.model().flagTriggerRole))
1182
        # Set item delegate (ComboBox) for flag_high column
1183
        self.setItemDelegateForColumn(7, ComboBoxItemDelegate(self, ['OFF'],
1184
                                                              self.model().flagHighRole))
1185
1186
        # Set header sizes
1187
        self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
1188
        # self.horizontalHeader().setDefaultSectionSize(100)
1189
        # self.horizontalHeader().setStyleSheet('QHeaderView { font-weight: 400; }')
1190
        self.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Fixed)
1191
        self.verticalHeader().setDefaultSectionSize(50)
1192
        for col in range(self.columnCount()):
1193
            width = self.itemDelegateForColumn(col).sizeHint().width()
1194
            self.setColumnWidth(col, width)
1195
        return
1196
1197
    def set_available_block_ensembles(self, ensembles):
1198
        """
1199
1200
        @param ensembles:
1201
        @return: int, error code (>=0: OK, <0: ERR)
1202
        """
1203
        err_code = self.model().set_available_block_ensembles(ensembles)
1204
        if err_code >= 0:
1205
            delegate = ComboBoxItemDelegate(
1206
                self, sorted(self.model().available_block_ensembles), self.model().ensembleNameRole)
1207
            self.setItemDelegateForColumn(0, delegate)
1208
        return err_code
1209
1210
    def set_rotating_frame(self, rotating_frame=True):
1211
        """
1212
1213
        @param rotating_frame:
1214
        @return:
1215
        """
1216
        self.model().set_rotating_frame(rotating_frame)
1217
        return
1218
1219
    def set_available_triggers(self, trigger_list):
1220
        """
1221
1222
        @param list trigger_list: List of strings describing the available pulse generator trigger
1223
                                  input channels.
1224
        """
1225
        if not isinstance(trigger_list, list):
1226
            return
1227
        trigger_list.insert(0, 'OFF')
1228
        # Set item delegate (ComboBox) for event_trigger column
1229
        self.setItemDelegateForColumn(4, ComboBoxItemDelegate(self, trigger_list,
1230
                                                              self.model().eventTriggerRole))
1231
        # Set item delegate (ComboBox) for wait_for column
1232
        self.setItemDelegateForColumn(5, ComboBoxItemDelegate(self, trigger_list,
1233
                                                              self.model().waitForRole))
1234
        return
1235
1236
    def set_available_flags(self, flag_list):
1237
        """
1238
1239
        @param list flag_list: List of strings describing the available pulse generator flag output
1240
                               channels.
1241
        """
1242
        if not isinstance(flag_list, list):
1243
            return
1244
        flag_list.insert(0, 'OFF')
1245
        # Set item delegate (ComboBox) for event_trigger column
1246
        self.setItemDelegateForColumn(6, ComboBoxItemDelegate(self, flag_list,
1247
                                                              self.model().flagTriggerRole))
1248
        # Set item delegate (ComboBox) for wait_for column
1249
        self.setItemDelegateForColumn(7, ComboBoxItemDelegate(self, flag_list,
1250
                                                              self.model().flagHighRole))
1251
        return
1252
1253
    def rowCount(self):
1254
        return self.model().rowCount()
1255
1256
    def columnCount(self):
1257
        return self.model().columnCount()
1258
1259
    def currentRow(self):
1260
        index = self.currentIndex()
1261
        return index.row() if index.isValid() else 0
1262
1263
    def currentColumn(self):
1264
        index = self.currentIndex()
1265
        return index.column() if index.isValid() else 0
1266
1267
    def add_steps(self, count=1, at_position=None):
1268
        """
1269
1270
        @param count:
1271
        @param at_position:
1272
        @return: bool, operation success
1273
        """
1274
        # Sanity checking
1275
        if count < 1:
1276
            return False
1277
1278
        # Insert new sequence step(s) as row to the table model/view at the specified position.
1279
        # Append new sequence step(s) to the table model/view if no position was given.
1280
        if at_position is None:
1281
            at_position = self.model().rowCount()
1282
        return self.model().insertRows(at_position, count)
1283
1284
    def remove_steps(self, count=1, at_position=None):
1285
        """
1286
1287
        @param count:
1288
        @param at_position:
1289
        @return: bool, operation success
1290
        """
1291
        # Sanity checking
1292
        if count < 1:
1293
            return False
1294
1295
        # Remove rows/sequence steps with index <at_position> to index <at_position + count - 1>
1296
        # Remove last <count> number of sequence steps if no at_position was given.
1297
        if at_position is None:
1298
            at_position = self.model().rowCount() - count
1299
        return self.model().removeRows(at_position, count)
1300
1301
    def clear(self):
1302
        """
1303
        Removes all sequence steps from the view/model and inserts a single one afterwards.
1304
1305
        @return: bool, operation success
1306
        """
1307
        success = self.remove_steps(self.model().rowCount(), 0)
1308
        if success:
1309
            self.add_steps(1, 0)
1310
        return success
1311
1312
    def get_sequence(self):
1313
        """
1314
        Returns a (deep)copy of the PulseSequence instance serving as model for this editor.
1315
1316
        @return: object, an instance of PulseSequence
1317
        """
1318
        data_container = self.model().data(QtCore.QModelIndex(), self.model().sequenceRole)
1319
        sequence_copy = copy.deepcopy(data_container)
1320
        sequence_copy.name = ''
1321
        sequence_copy.refresh_parameters()
1322
        return sequence_copy
1323
1324
    def load_sequence(self, pulse_sequence):
1325
        """
1326
        Load an instance of PulseSequence into the model in order to view/edit it.
1327
1328
        @param pulse_sequence: object, the PulseSequence instance to load into the model/view
1329
        @return: bool, operation success
1330
        """
1331
        return self.model().set_pulse_sequence(pulse_sequence)
1332