def setupTabs(self):
        """ Setup the various tabs in the AddressWidget. """
        groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]

        for group in groups:
            proxyModel = QSortFilterProxyModel(self)
            proxyModel.setSourceModel(self.tableModel)
            proxyModel.setDynamicSortFilter(True)

            tableView = QTableView()
            tableView.setModel(proxyModel)
            tableView.setSortingEnabled(True)
            tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            tableView.horizontalHeader().setStretchLastSection(True)
            tableView.verticalHeader().hide()
            tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
            tableView.setSelectionMode(QAbstractItemView.SingleSelection)

            # This here be the magic: we use the group name (e.g. "ABC") to
            # build the regex for the QSortFilterProxyModel for the group's
            # tab. The regex will end up looking like "^[ABC].*", only
            # allowing this tab to display items where the name starts with
            # "A", "B", or "C". Notice that we set it to be case-insensitive.
            reFilter = "^[%s].*" % group

            proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive))
            proxyModel.setFilterKeyColumn(0) # Filter on the "name" column
            proxyModel.sort(0, Qt.AscendingOrder)

            # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash)
            viewselectionmodel = tableView.selectionModel()
            tableView.selectionModel().selectionChanged.connect(self.selectionChanged)

            self.addTab(tableView, group)
    def setupTabs(self):
        """ Setup the various tabs in the AddressWidget. """
        groups = ["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VW", "XYZ"]

        for group in groups:
            proxyModel = QSortFilterProxyModel(self)
            proxyModel.setSourceModel(self.tableModel)
            proxyModel.setDynamicSortFilter(True)

            tableView = QTableView()
            tableView.setModel(proxyModel)
            tableView.setSortingEnabled(True)
            tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
            tableView.horizontalHeader().setStretchLastSection(True)
            tableView.verticalHeader().hide()
            tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
            tableView.setSelectionMode(QAbstractItemView.SingleSelection)

            # This here be the magic: we use the group name (e.g. "ABC") to
            # build the regex for the QSortFilterProxyModel for the group's
            # tab. The regex will end up looking like "^[ABC].*", only
            # allowing this tab to display items where the name starts with
            # "A", "B", or "C". Notice that we set it to be case-insensitive.
            reFilter = "^[%s].*" % group

            proxyModel.setFilterRegExp(QRegExp(reFilter, Qt.CaseInsensitive))
            proxyModel.setFilterKeyColumn(0)  # Filter on the "name" column
            proxyModel.sort(0, Qt.AscendingOrder)

            # This prevents an application crash (see: http://www.qtcentre.org/threads/58874-QListView-SelectionModel-selectionChanged-Crash)
            viewselectionmodel = tableView.selectionModel()
            tableView.selectionModel().selectionChanged.connect(
                self.selectionChanged)

            self.addTab(tableView, group)
class ArrayDatasetTableWidget(QWidget):
    """
    Wrapper over a QTableView with buttons to add and delete rows/columns
    """
    def __init__(self, type: np.dtype = np.byte, parent=None):
        super().__init__(parent)
        self.model = ArrayDatasetTableModel(dtype=type, parent=self)
        self.view = QTableView()
        self.view.setModel(self.model)
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.view.setItemDelegate(ValueDelegate(type, parent))

        self.setLayout(QGridLayout())
        self.toolbox = QToolBar()
        self.add_row_button = QAction(text="➕ Add Row")
        self.add_row_button.triggered.connect(self.model.add_row)
        self.remove_row_button = QAction(text="➖ Remove Row")
        self.remove_row_button.triggered.connect(
            partial(self.model.delete_index, True))
        self.add_column_button = QAction(text="➕ Add Column")
        self.add_column_button.triggered.connect(self.model.add_column)
        self.remove_column_button = QAction(text="➖ Remove Column")
        self.remove_column_button.triggered.connect(
            partial(self.model.delete_index, False))

        self.toolbox.addAction(self.add_row_button)
        self.toolbox.addAction(self.remove_row_button)
        self.toolbox.addAction(self.add_column_button)
        self.toolbox.addAction(self.remove_column_button)

        self.layout().addWidget(self.toolbox)
        self.layout().addWidget(self.view)
def get_view_for_submodel(submodel):
    main_widget = QWidget()

    hbox_layout = QHBoxLayout(main_widget)
    vbox_layout = QVBoxLayout()

    table_view = QTableView()
    table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
    table_view.setSelectionMode(QAbstractItemView.SingleSelection)
    table_view.setModel(submodel)

    add_button = QPushButton("+")
    add_button.clicked.connect(lambda: add_item_to_model(submodel))

    remove_button = QPushButton("-")
    remove_button.clicked.connect(
        lambda: remove_item_from_model(submodel, table_view))

    vbox_layout.addWidget(add_button)
    vbox_layout.addWidget(remove_button)
    vbox_layout.addItem(
        QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))

    hbox_layout.addWidget(table_view)
    hbox_layout.addLayout(vbox_layout)

    return main_widget
class AddressWidget(QDialog):

    selectionChanged = Signal(QItemSelection)

    def __init__(self, parent=None):
        super(AddressWidget, self).__init__(parent)

        self.tableModel = TableModel()
        self.tableView = QTableView()
        self.setupTable()

        statusLabel = QLabel("Tabular data view demo")

        layout = QVBoxLayout()
        layout.addWidget(self.tableView)
        layout.addWidget(statusLabel)
        self.setLayout(layout)
        self.setWindowTitle("Address Book")
        self.resize(800,500)

        # add test data
        self.populateTestData()

    def setupTable(self):
        proxyModel = QSortFilterProxyModel(self)
        proxyModel.setSourceModel(self.tableModel)
        proxyModel.setDynamicSortFilter(True)

        self.tableView.setModel(proxyModel)
        self.tableView.setSortingEnabled(True)
        self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.tableView.horizontalHeader().setStretchLastSection(True)
        self.tableView.verticalHeader().hide()
        self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tableView.setSelectionMode(QAbstractItemView.SingleSelection)

        proxyModel.setFilterKeyColumn(0)  # Filter on the "name" column
        proxyModel.sort(0, Qt.AscendingOrder)
        viewselectionmodel = self.tableView.selectionModel()
        self.tableView.selectionModel().selectionChanged.connect(self.selectionChanged)



    def populateTestData(self):
        addresses = [{"name": "John Doe", "address": "Alameda"},
                     {"name": "Alan Turing", "address": "San Deigo"},
                     {"name": "Bjarne Stroutsup", "address": "Columbia"},
                     {"name": "Herb Sutter", "address": "Seattle"},
                     {"name": "Micheal Konin", "address": "Colorado"}]

        for i in range(len(addresses)):
            self.tableModel.insertRows(0)
            ix = self.tableModel.index(0, 0, QModelIndex())
            self.tableModel.setData(ix, addresses[i]["name"], Qt.EditRole)

            ix = self.tableModel.index(0, 1, QModelIndex())
            self.tableModel.setData(ix, addresses[i]["address"], Qt.EditRole)

            self.tableView.resizeRowToContents(ix.row())
Exemple #6
0
class DebugBreakpointsWidget(QWidget, DockContextHandler):
	def __init__(self, parent, name, data):
		if not type(data) == binaryninja.binaryview.BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data
		
		QWidget.__init__(self, parent)
		DockContextHandler.__init__(self, self, name)
		self.actionHandler = UIActionHandler()
		self.actionHandler.setupActionHandler(self)

		self.table = QTableView(self)
		self.model = DebugBreakpointsListModel(self.table, data)
		self.table.setModel(self.model)

		self.item_delegate = DebugBreakpointsItemDelegate(self)
		self.table.setItemDelegate(self.item_delegate)

		# self.table.setSortingEnabled(True)
		self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
		self.table.setSelectionMode(QAbstractItemView.ExtendedSelection)

		self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
		self.table.verticalHeader().setVisible(False)

		self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
		self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)

		self.table.resizeColumnsToContents()
		self.table.resizeRowsToContents()

		for i in range(len(self.model.columns)):
			self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width())
		self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.table)
		self.setLayout(layout)

	def notifyOffsetChanged(self, offset):
		pass

	def notifyBreakpointsChanged(self, new_rows):
		self.model.update_rows(new_rows)

	def contextMenuEvent(self, event):
		self.m_contextMenuManager.show(self.m_menu, self.actionHandler)

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True
Exemple #7
0
    def __init__(self, data_list, header, *args):
        QWidget.__init__(self, *args)

        # setGeometry(x_pos, y_pos, width, height)
        self.setGeometry(300, 200, 570, 450)
        self.setWindowTitle('Click on column title to sort')

        # Setup the model and view
        '''tmodel = MyTableModel(self, data_list, header)
        tview = QTableView()
        tview.setModel(tmodel)
        delegate = MyDelegate()
        tview.setItemDelegate(delegate)'''

        # Setup the proxy model for sorting and filtering
        tmodel = MyTableModel(self, data_list, header)
        pmodel = QSortFilterProxyModel()
        pmodel.setSourceModel(tmodel)

        tview = QTableView()
        tview.setModel(pmodel)
        delegate = MyDelegate()
        tview.setItemDelegate(delegate)

        # TableView properties
        tview.resizeColumnsToContents()  # set column width to fit contents
        tview.setShowGrid(False)  # hide gridlines
        #tview.verticalHeader().hide() # row labels
        #tview.horizontalHeader().hide() # column labels

        # Select a single row at a time
        tview.setSelectionBehavior(QTableView.SelectRows)
        tview.setSelectionMode(QTableView.SingleSelection)

        # Enable sorting
        tview.setSortingEnabled(True)

        # Drag and drop reordering using header labels
        '''tview.verticalHeader().setSectionsMovable(True)
        tview.verticalHeader().setDragEnabled(True)
        tview.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove)

        tview.horizontalHeader().setSectionsMovable(True)
        tview.horizontalHeader().setDragEnabled(True)
        tview.horizontalHeader().setDragDropMode(QAbstractItemView.InternalMove)'''

        # Drag and drop reordering using rows
        tview.setDragEnabled(True)
        tview.setAcceptDrops(True)
        tview.setDragDropMode(QTableView.InternalMove)
        tview.setDragDropOverwriteMode(False)

        layout = QVBoxLayout(self)
        layout.addWidget(tview)
        self.setLayout(layout)
Exemple #8
0
    def _create_device_table(self) -> QTableView:
        device_table = QTableView()
        device_table.setModel(self._device_model)
        device_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        device_table.setSelectionMode(QAbstractItemView.SingleSelection)

        device_table.selectionModel().selectionChanged.connect(
            self._on_device_table_selection_changed)

        horizontal_header = device_table.horizontalHeader()
        vertical_header = device_table.verticalHeader()
        horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents)
        vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents)
        horizontal_header.setStretchLastSection(True)

        return device_table
Exemple #9
0
class DebugModulesWidget(QWidget, DockContextHandler):
    def __init__(self, parent, name, data):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data

        QWidget.__init__(self, parent)
        DockContextHandler.__init__(self, self, name)
        self.actionHandler = UIActionHandler()
        self.actionHandler.setupActionHandler(self)

        self.table = QTableView(self)
        self.model = DebugModulesListModel(self.table, data)
        self.table.setModel(self.model)

        self.item_delegate = DebugModulesItemDelegate(self)
        self.table.setItemDelegate(self.item_delegate)

        # self.table.setSortingEnabled(True)
        self.table.setSelectionBehavior(
            QAbstractItemView.SelectionBehavior.SelectRows)
        self.table.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self.table.verticalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        self.table.verticalHeader().setVisible(False)

        self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)

        self.table.resizeColumnsToContents()
        self.table.resizeRowsToContents()

        for i in range(len(self.model.columns)):
            self.table.setColumnWidth(
                i,
                self.item_delegate.sizeHint(
                    self.table.viewOptions(),
                    self.model.index(-1, i, QModelIndex())).width())

        update_layout = QHBoxLayout()
        update_layout.setContentsMargins(0, 0, 0, 0)

        update_label = QLabel("Data is Stale")
        update_button = QPushButton("Refresh")
        update_button.clicked.connect(lambda: self.refresh())

        update_layout.addWidget(update_label)
        update_layout.addStretch(1)
        update_layout.addWidget(update_button)

        self.update_box = QWidget()
        self.update_box.setLayout(update_layout)

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.layout.addWidget(self.table)
        self.setLayout(self.layout)

    def notifyOffsetChanged(self, offset):
        pass

    def refresh(self):
        debug_state = binjaplug.get_state(self.bv)
        debug_state.ui.update_modules()

    def notifyModulesChanged(self, new_modules):
        self.model.update_rows(new_modules)
        self.table.resizeColumnsToContents()
        self.layout.removeWidget(self.update_box)
        self.update_box.setVisible(False)

    def mark_dirty(self):
        self.layout.addWidget(self.update_box)
        self.update_box.setVisible(True)

    def contextMenuEvent(self, event):
        self.m_contextMenuManager.show(self.m_menu, self.actionHandler)

    def shouldBeVisible(self, view_frame):
        if view_frame is None:
            return False
        else:
            return True
Exemple #10
0
class MetricsDialog(PluginDialog):

    ENABLE = True

    def __init__(self, conn=None, parent=None):
        super().__init__(parent)
        self.conn = conn

        self.view = QTableView()
        self.model = MetricModel()
        self.buttons = QDialogButtonBox(QDialogButtonBox.Ok)

        self.view.setModel(self.model)
        self.view.setAlternatingRowColors(True)
        self.view.horizontalHeader().hide()
        self.view.verticalHeader().hide()
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.buttons.accepted.connect(self.accept)

        self.setWindowTitle(self.tr("Project metrics"))

        v_layout = QVBoxLayout()
        v_layout.addWidget(self.view)
        v_layout.addWidget(self.buttons)
        self.setLayout(v_layout)

        # Async stuff
        self.metrics_runnable = None
        self.populate()

    def populate(self):
        """Async implementation to populate the view

        Notes:
            When closing the dialog window, the thread is not stopped.
        """
        def compute_metrics(conn):
            """Async function"""
            return {
                "variants": get_variant_count(conn),
                "snps": get_snp_count(conn),
                "transitions": get_variant_transition(conn),
                "transversions": get_variant_transversion(conn),
                "samples": get_sample_count(conn),
            }

        self.model.add_metrics(self.tr("Loading..."), self.tr("data..."))

        self.metrics_runnable = SqlRunnable(self.conn, compute_metrics)
        self.metrics_runnable.finished.connect(self.loaded)
        QThreadPool.globalInstance().start(self.metrics_runnable)

    def loaded(self):
        """Called at the end of the thread and populate data"""
        results = self.metrics_runnable.results

        self.model.clear()
        ratio = results["transitions"] / results["transversions"]

        self.model.add_metrics("Variant count", results["variants"])
        self.model.add_metrics("Snp count", results["snps"])
        self.model.add_metrics("Transition count", results["transitions"])
        self.model.add_metrics("Transversion count", results["transversions"])
        self.model.add_metrics("Tr/tv ratio", ratio)
        self.model.add_metrics("Sample count", results["variants"])

        self.view.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        self.view.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.Stretch)
class DebugThreadsWidget(QWidget, DockContextHandler):
	def __init__(self, parent, name, data):
		if not type(data) == BinaryView:
			raise Exception('expected widget data to be a BinaryView')

		self.bv = data

		QWidget.__init__(self, parent)
		DockContextHandler.__init__(self, self, name)
		self.actionHandler = UIActionHandler()
		self.actionHandler.setupActionHandler(self)

		self.table = QTableView(self)
		self.model = DebugThreadsListModel(self.table)
		self.table.setModel(self.model)
		self.table.clicked.connect(self.threadRowClicked)

		self.item_delegate = DebugThreadsItemDelegate(self)
		self.table.setItemDelegate(self.item_delegate)

		# self.table.setSortingEnabled(True)
		self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
		self.table.setSelectionMode(QAbstractItemView.ExtendedSelection)

		self.table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
		self.table.verticalHeader().setVisible(False)

		self.table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
		self.table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)

		self.table.resizeColumnsToContents()
		self.table.resizeRowsToContents()

		for i in range(len(self.model.columns)):
			self.table.setColumnWidth(i, self.item_delegate.sizeHint(self.table.viewOptions(), self.model.index(-1, i, QModelIndex())).width())
		self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)

		layout = QVBoxLayout()
		layout.setContentsMargins(0, 0, 0, 0)
		layout.setSpacing(0)
		layout.addWidget(self.table)
		self.setLayout(layout)

	def notifyOffsetChanged(self, offset):
		pass

	# called from QTableView's clicked signal
	# index: QModelIndex
	def threadRowClicked(self, index):
		index = self.model.createIndex(index.row(), 0)
		tid_str = self.model.data(index, Qt.DisplayRole)
		#print('clicked to change to thread %s' % tid_str)
		stateObj = binjaplug.get_state(self.bv)
		if stateObj.connected and not stateObj.running:
			tid = int(tid_str, 16)
			stateObj.threads.current = tid
			stateObj.ui.context_display()
			stateObj.ui.on_step()
		else:
			print('cannot set thread in current state')

	# called from plugin's context_display() function
	def notifyThreadsChanged(self, new_threads):
		idx_selected = self.model.update_rows(new_threads)
		if idx_selected:
			self.table.setCurrentIndex(idx_selected)

	def contextMenuEvent(self, event):
		self.m_contextMenuManager.show(self.m_menu, self.actionHandler)

	def shouldBeVisible(self, view_frame):
		if view_frame is None:
			return False
		else:
			return True
Exemple #12
0
class RemoverDialog(QDialog):
    def __init__(self, parent=None):
        super(RemoverDialog, self).__init__(parent)

        self.setupUi()

    def setupUi(self):
        self.menuBar = QMenuBar()
        self.menuBar.show()

        self.pathInputBox = QLineEdit(self)
        self.pathInputBox.setEnabled(False)
        self.pathInputBox.setToolTip(
            'Input a path or drag a directory here...')

        self.openPathButton = QPushButton('Open...', self)
        self.openPathButton.clicked.connect(self.openPath)

        inputLayout = QHBoxLayout()
        inputLayout.addWidget(QLabel('Path:', self))
        inputLayout.addWidget(self.pathInputBox)
        inputLayout.addWidget(self.openPathButton)

        self.filterFolderCheckBox = QCheckBox('Folders', self)
        self.filterFolderCheckBox.setChecked(True)
        self.filterFolderCheckBox.toggled.connect(self.filter)
        self.filterFileCheckBox = QCheckBox('Files', self)
        self.filterFileCheckBox.setChecked(True)
        self.filterFileCheckBox.toggled.connect(self.filter)
        self.filterSuffixCheckBox = QCheckBox('Suffixes', self)
        self.filterSuffixCheckBox.setChecked(True)
        self.filterSuffixCheckBox.toggled.connect(self.filter)

        filterLayout = QHBoxLayout()
        filterLayout.addWidget(self.filterFolderCheckBox)
        filterLayout.addWidget(self.filterFileCheckBox)
        filterLayout.addWidget(self.filterSuffixCheckBox)
        filterLayout.addStretch()

        self.trashButton = QPushButton('Trash', self)
        self.trashButton.clicked.connect(self.trash)
        self.deleteButton = QPushButton('Delete', self)
        self.deleteButton.clicked.connect(self.delete)

        confirmLayout = QHBoxLayout()
        confirmLayout.addStretch()
        confirmLayout.addWidget(self.trashButton)
        confirmLayout.addWidget(self.deleteButton)

        layout = QVBoxLayout()
        layout.addLayout(inputLayout)
        layout.addLayout(filterLayout)
        layout.addWidget(self.createResultView())
        layout.addLayout(confirmLayout)

        self.setAcceptDrops(True)
        self.setLayout(layout)
        self.setMinimumWidth(600)
        self.setWindowTitle('Remover')
        self.setWindowIcon(QApplication.style().standardIcon(
            QStyle.SP_DirIcon))

    def createResultView(self):
        self.resultModel = ResultModel(self)
        self.resultView = QTableView(self)
        self.resultView.setSortingEnabled(True)
        self.resultView.setShowGrid(False)
        self.resultView.setAlternatingRowColors(True)
        self.resultView.verticalHeader().hide()
        self.resultView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.resultView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.sortFilterModel = SortFilterResultModel(self.resultModel, self)
        self.resultView.setModel(self.sortFilterModel)
        return self.resultView

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls() and len(event.mimeData().urls()) == 1:
            event.acceptProposedAction()

    def dropEvent(self, event):
        if event.mimeData().hasUrls() and len(event.mimeData().urls()) == 1:
            self.pathInputBox.setText(event.mimeData().urls()[0].toLocalFile())
            self.reloadPath(self.pathInputBox.text())

    def openPath(self):
        path = QFileDialog.getExistingDirectory(
            parent=self,
            caption='Open',
            options=QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        if path:
            self.pathInputBox.setText(path)
            self.reloadPath(path)

    def filter(self):
        filters = []
        if self.filterFolderCheckBox.isChecked():
            filters.append('folder')
        if self.filterFileCheckBox.isChecked():
            filters.append('file')
        if self.filterSuffixCheckBox.isChecked():
            filters.append('suffix')
        self.sortFilterModel.setFilterRegularExpression('|'.join(filters))
        self.sortFilterModel.filterRegularExpression()
        self.resultView.resizeRowsToContents()

    def trash(self):
        folders, files = self.collectSelectedFolderFiles()

        try:
            for file in files:
                send2trash(file)
            for folder in folders:
                if QFile(folder).exists():
                    send2trash(folder)
        except:
            QMessageBox.warning(self, 'Failed',
                                'Failed to trash selected files/folders')
            return

        self.reloadPath(self.pathInputBox.text())

    def delete(self):
        folders, files = self.collectSelectedFolderFiles()

        try:
            for file in files:
                os.remove(file)
            for folder in folders:
                shutil.rmtree(folder)
        except:
            QMessageBox.warning(self, 'Failed',
                                'Failed to delete selected files/folders')
            return

        self.reloadPath(self.pathInputBox.text())

    def collectSelectedFolderFiles(self):
        folders, files, suffixes = [], [], []
        for index in self.resultView.selectedIndexes():
            if index.column() != 0:
                # 忽略第二列的selection
                continue
            item = self.sortFilterModel.data(index, Qt.UserRole)
            if 'folder' == item.type:
                folders.append(item.name)
            elif 'file' == item.type:
                files.append(item.name)
            elif 'suffix' == item.type:
                suffixes.append(item.name[2:])

        # 将后缀符合选中条件的文件添加到files中
        path = self.pathInputBox.text()
        iterator = QDirIterator(path,
                                filter=QDir.Files | QDir.Dirs | QDir.Hidden
                                | QDir.NoDotAndDotDot,
                                flags=QDirIterator.Subdirectories)
        folderPaths, filePaths = set(), set()
        while iterator.hasNext():
            file = iterator.next()
            if '.' == file[-1] or '..' == file[-2]:
                continue
            fileInfo = QFileInfo(file)
            if fileInfo.isDir():
                if fileInfo.fileName() in folders:
                    folderPaths.add(fileInfo.absoluteFilePath())
            if fileInfo.isFile():
                if fileInfo.fileName() in files:
                    filePaths.add(fileInfo.absoluteFilePath())
                if fileInfo.suffix() in suffixes:
                    filePaths.add(fileInfo.absoluteFilePath())
        return sorted(folderPaths), filePaths

    def reloadPath(self, path):
        self.resultModel.reload(path)
        self.resultView.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.resultView.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.filter()
        self.resultView.sortByColumn(1, Qt.DescendingOrder)
class AttributesTab(QWidget):

    def __init__(self):
        """
        Widget containing the table summary of all attributes
        """
        QWidget.__init__(self)

        # Data
        self.attributes = {}  # Attributes names to ids -> {attr_name: attr_id, ...}
        self.attr_idx = {}  # Attributes ids to indexes -> {attr_id: attr_idx, ...}
        self.students = {}  # Students names to ids -> {std_name: std_id, ...}
        self.std_idx = {}  # Students ids to indexes -> {std_id: std_idx, ...}
        self.data = {}  # Attributes and students ids to cell data -> {(attr_id, std_id): cell_data, ...}

        self.dm: AttributesTableModel = None

        # Widget
        self.table_attributes = QTableView()
        self.table_attributes.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_attributes.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_attributes.verticalHeader().setSectionResizeMode(QHeaderView.Fixed)
        self.table_attributes.verticalHeader().setFixedWidth(150)

        # Signals
        self.table_attributes.clicked.connect(self.on_cell_clicked)
        self.sig_cell_clicked: Signal = None

        # Layout
        self.__set_layout()

        self.set_data([], [], {})

    def __set_layout(self) -> None:
        layout = QHBoxLayout()
        layout.setSpacing(0)

        layout.addWidget(self.table_attributes)

        self.setLayout(layout)

    def set_data(self, attributes_data: list, students_data: list, data: dict) -> None:
        """
        Sets the data inside the table

        :param attributes_data: Attributes list (needs to be ordered) = [(attr_id, attr_name), ...]
        :param students_data: Students list (needs to be ordered) = [(std_id, std_name), ...]
        :param data: Table's data {(attr_id, std_id}: cell_data, ...}
        """
        self.attributes = {}
        self.attr_idx = {}
        i = 0
        for attr_id, attr_name in attributes_data:
            self.attributes[attr_name] = attr_id
            self.attr_idx[attr_id] = i
            i += 1

        self.students = {}
        self.std_idx = {}
        i = 0
        for std_id, std_name in students_data:
            self.students[std_name] = std_id
            self.std_idx[std_id] = i
            i += 1

        self.data = data
        attr_header = [a[1] for a in attributes_data]
        std_header = [s[1] for s in students_data]

        data_for_model = []
        for _ in range(len(std_header)):
            data_for_model.append([None] * len(attr_header))

        for attr_id, std_id in data:
            data_for_model[self.std_idx[std_id]][self.attr_idx[attr_id]] = data[(attr_id, std_id)]

        self.dm = AttributesTableModel(self.table_attributes, data_for_model, attr_header, std_header)

    def on_cell_clicked(self, item) -> None:
        """
        Triggered when a cell is clicked. Emits a signal with the attribute and student ids
        """
        attr_id = None
        for a in self.attr_idx:
            if self.attr_idx[a] == item.column():
                attr_id = a

        std_id = None
        for s in self.std_idx:
            if self.std_idx[s] == item.row():
                std_id = s

        self.sig_cell_clicked.emit(attr_id, std_id)
class WTableParameterEdit(QWidget):

    dataChanged = Signal()

    def __init__(self, obj):
        super(WTableParameterEdit, self).__init__()
        self._obj = obj

        self._model = WTableParameterModel(self._obj)

        self._view = QTableView()
        self._view.setModel(self._model)

        self.setupUi()
        self.setNotEditableHidden(True)
        self.resizeToContent()

        self._model.dataChanged.connect(self.dataChanged.emit)

    def resizeToContent(self):
        """
        Resize all the columns (but the last) to fit the content or the minimum size.
        """
        rootIndex = self._view.rootIndex()
        for colId in range(self._model.columnCount() - 1):
            self._view.resizeColumnToContents(colId)

    def setNotEditableHidden(self, hidden):
        """Set the hide state of all rows that are not editable."""
        rootIndex = self._view.rootIndex()
        for rowId in range(self._model.rowCount()):
            isEditableRow = False
            for colId in range(self._model.columnCount()):
                flag = self._model.flags(
                    self._model.index(rowId, colId, rootIndex))
                if flag & Qt.ItemIsEditable:
                    isEditableRow = True

            self._view.setRowHidden(rowId, not isEditableRow and hidden)

    def setupUi(self):
        header = QLabel(f"{type(self._obj).__name__}")
        font = header.font()
        font.setBold(True)
        header.setFont(font)

        self.mainLayout = QVBoxLayout()
        self.mainLayout.addWidget(header)

        if isinstance(self._obj, (list, dict)):
            label = f"The {type(self._obj).__name__} has {len(self._obj)} element"
            if len(self._obj) != 1:
                label += "s"
            self.mainLayout.addWidget(QLabel(label + "."))
            self.mainLayout.addStretch()
        elif self._obj is None:
            self.mainLayout.addWidget(QLabel("Not implemented yet."))
            self.mainLayout.addStretch()
        else:
            self.mainLayout.addWidget(self._view)

        self.setLayout(self.mainLayout)

        # === table settings ===
        # self._view.resizeColumnsToContents()
        self._view.verticalHeader().setVisible(False)
        self._view.setFocusPolicy(Qt.NoFocus)
        self._view.setSelectionMode(QAbstractItemView.SingleSelection)
        self._view.setEditTriggers(QAbstractItemView.AllEditTriggers)
        self._view.horizontalHeader().setStretchLastSection(True)
        self._view.horizontalHeader().setSectionsClickable(False)
        # self._view.setMinimumWidth(650)

        self.setMinimumWidth(600)
class AddressWidget(QDialog):

    selectionChanged = Signal(QItemSelection)

    def __init__(self, parent=None):
        super(AddressWidget, self).__init__(parent)

        self.tableModel = TableModel()
        self.tableView = QTableView()
        self.setupTable()

        statusLabel = QLabel("Tabular data view demo")

        layout = QVBoxLayout()
        layout.addWidget(self.tableView)
        layout.addWidget(statusLabel)
        self.setLayout(layout)
        self.setWindowTitle("Address Book")
        self.resize(800, 500)

        # add test data
        self.populateTestData()

    def setupTable(self):
        proxyModel = QSortFilterProxyModel(self)
        proxyModel.setSourceModel(self.tableModel)
        proxyModel.setDynamicSortFilter(True)

        self.tableView.setModel(proxyModel)
        self.tableView.setSortingEnabled(True)
        self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.tableView.horizontalHeader().setStretchLastSection(True)
        self.tableView.verticalHeader().hide()
        self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.tableView.setSelectionMode(QAbstractItemView.SingleSelection)

        proxyModel.setFilterKeyColumn(0)  # Filter on the "name" column
        proxyModel.sort(0, Qt.AscendingOrder)
        viewselectionmodel = self.tableView.selectionModel()
        self.tableView.selectionModel().selectionChanged.connect(
            self.selectionChanged)

    def populateTestData(self):
        addresses = [{
            "name": "John Doe",
            "address": "Alameda"
        }, {
            "name": "Alan Turing",
            "address": "San Deigo"
        }, {
            "name": "Bjarne Stroutsup",
            "address": "Columbia"
        }, {
            "name": "Herb Sutter",
            "address": "Seattle"
        }, {
            "name": "Micheal Konin",
            "address": "Colorado"
        }]

        for i in range(len(addresses)):
            self.tableModel.insertRows(0)
            ix = self.tableModel.index(0, 0, QModelIndex())
            self.tableModel.setData(ix, addresses[i]["name"], Qt.EditRole)

            ix = self.tableModel.index(0, 1, QModelIndex())
            self.tableModel.setData(ix, addresses[i]["address"], Qt.EditRole)

            self.tableView.resizeRowToContents(ix.row())
Exemple #16
0
class WorkSpaceWidget(QWidget):
    def __init__(self, parent, file_clicked):
        super().__init__(parent)
        self.main_layout = QVBoxLayout()
        # Tool Bar
        self.toolbar = QToolBar(self)
        # delete action on toolbar
        self.delete_action_tb = QAction("DELETE TABLE ROW", self)
        self.delete_action_tb.setStatusTip("Obrisi Red U Tabeli")
        self.delete_action_tb.triggered.connect(self.delete_table_row_tb)
        self.toolbar.addAction(self.delete_action_tb)

        # ADD ONE TOOLBAR BUTTON
        self.add_one_action_tb = QAction("ADD TABLE ROW", self)
        self.add_one_action_tb.setStatusTip("ADD SINGLE ROW TO TABLE")
        self.add_one_action_tb.triggered.connect(self.add_table_row_handler)
        self.toolbar.addAction(self.add_one_action_tb)

        self.setLayout(self.main_layout)
        self.file_clicked = file_clicked
        self.abstract_table_model = AbstractTableModel(self.file_clicked)
        self.database_type = self.abstract_table_model.database_type
        self.create_tab_widget()
        self.check_database_type_and_run()
        self.tab_widget.addTab(self.main_table, QIcon("img/iconXLNK.png"),
                               self.file_clicked)

        self.main_layout.addWidget(self.toolbar)
        self.main_layout.addWidget(self.tab_widget)

    # TODO Srediti da funkcija bise element iz tabele klikom na dugme delete u ToolBar-u.
    def delete_table_row_tb(self):
        print("Ugraditi funkciju za brisanje reda iz tabele.")

    # TODO srediti dodavnje u tabelu.
    def add_table_row_handler(self):
        self.addWindow = InsertOneForm(
            self.abstract_table_model.file_handler.metadata[0]["columns"],
            self.check_data)
        self.main_layout.addWidget(self.addWindow)

        # chekiranje validnosti podataka

    def check_data(self):
        self.return_data = self.addWindow.final_data
        self.return_metadata = self.addWindow.metadata_columns
        not_valid = False
        i = 0
        for data in self.return_data:
            if data.get(self.return_metadata[i]) == "" or data.get(
                    self.return_metadata[i]) == " " or data.get(
                        self.return_metadata[i]) == None:
                print("Nije uredu")
                not_valid = True
            else:
                print("sve ok")
            i += 1

        if not_valid != True:
            self.new_instanc = SmartPhone(self.return_data[0]["brand"],
                                          self.return_data[1]["model"],
                                          self.return_data[2]["price"],
                                          self.return_data[3]["made_in"],
                                          self.return_data[4]["dealer"],
                                          self.return_data[5]["imei_code"],
                                          self.return_data[6]["stores"])
            self.abstract_table_model.file_handler.insert(self.new_instanc)

    def check_database_type_and_run(self):
        if self.database_type == "serial" or self.database_type == "sequential":
            self.create_table()
        else:
            # TODO Obraditi izuzetak.
            pass

    def create_tab_widget(self):
        self.tab_widget = QTabWidget(self)
        self.tab_widget.setTabsClosable(True)

    def create_table(self):
        self.main_table = QTableView(self.tab_widget)
        self.main_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.main_table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.main_table.setModel(self.abstract_table_model)
        # makes responsive tabel sizes
        max_width = 1620 // self.abstract_table_model.column_number()
        for width in range(self.abstract_table_model.column_number()):
            self.main_table.setColumnWidth(width, max_width)
Exemple #17
0
class DictWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__()

        self.view = QTableView()
        self.model = DictModel()
        self.proxy_model = QSortFilterProxyModel()
        self.search_bar = QLineEdit()
        self._show_loading = False

        self.proxy_model.setSourceModel(self.model)

        self.view.setModel(self.proxy_model)
        self.view.setAlternatingRowColors(True)
        self.view.horizontalHeader().setStretchLastSection(True)
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)
        self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.view.setSortingEnabled(True)
        self.view.verticalHeader().hide()

        self.search_bar.textChanged.connect(
            self.proxy_model.setFilterRegularExpression)
        self.search_bar.setVisible(False)

        self._show_search_action = QAction("show search bar")
        self._show_search_action.setCheckable(True)
        self._show_search_action.setShortcutContext(Qt.WidgetShortcut)
        self._show_search_action.setShortcut(QKeySequence.Find)
        self._show_search_action.triggered.connect(self._on_show_search)

        self._close_search_action = QAction()
        self._close_search_action.setShortcut(QKeySequence(Qt.Key_Escape))
        self._close_search_action.setShortcutContext(Qt.WidgetShortcut)
        self._close_search_action.triggered.connect(self._on_close_search)

        self.view.addAction(self._show_search_action)
        self.search_bar.addAction(self._close_search_action)

        _layout = QVBoxLayout()
        _layout.addWidget(self.view)
        _layout.addWidget(self.search_bar)
        _layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(_layout)
        print("init")

    def set_dict(self, data: dict):

        self.model.set_dict(data)

    def _on_show_search(self):
        self.search_bar.setVisible(True)
        self.search_bar.setFocus(Qt.ShortcutFocusReason)

    def _on_close_search(self):
        self.search_bar.hide()
        self.search_bar.clear()
        self.view.setFocus(Qt.ShortcutFocusReason)

    def set_header_visible(self, visible=True):
        self.view.horizontalHeader().setVisible(visible)

    def clear(self):
        self.model.clear()

    def paintEvent(self, event: QPaintEvent):

        if self._show_loading:
            painter = QPainter(self)
            painter.drawText(self.rect(), Qt.AlignCenter,
                             self.tr("Loading ..."))
        else:
            super().paintEvent(event)

    def set_loading(self, show=True):
        self._show_loading = True
        self.view.setVisible(not show)
        self.update()
Exemple #18
0
class _ExtractSeriesWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # {series name: {frame name: [ (Attribute, QPersistentModelIndex) ]
        self.seriesOptions: Dict[str, Dict[str, List[Tuple[
            int, QPersistentModelIndex]]]] = dict()
        # {frame name: attribute model}
        self.models: Dict[str, CustomProxyAttributeModel] = dict()
        self.seriesView = CustomSignalView(parent=self)
        self.seriesModel = CustomStringListModel(self)
        self.seriesModel.setHeaderLabel('Series name')
        self.addSeriesButton = QPushButton('Add', self)
        self.removeSeriesButton = QPushButton('Remove', self)
        self.addSeriesButton.clicked.connect(self.addSeries)
        self.removeSeriesButton.clicked.connect(self.removeSeries)
        self.seriesView.setModel(self.seriesModel)
        self.seriesView.setDragDropMode(QTableView.InternalMove)
        self.seriesView.setDragDropOverwriteMode(False)
        self.seriesView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.seriesView.verticalHeader().hide()

        # Connect selection to change
        self.seriesView.selectedRowChanged[str, str].connect(
            self.onSeriesSelectionChanged)
        # When a series is added it should be immediately edited
        self.seriesModel.rowAppended.connect(self.editSeriesName)
        self.seriesModel.rowsInserted.connect(self.checkNoSeries)
        self.seriesModel.rowsRemoved.connect(self.checkNoSeries)

        self.workbenchView = WorkbenchView(self, editable=False)
        self.workbench: WorkbenchModel = None
        self.workbenchView.selectedRowChanged[str, str].connect(
            self.onFrameSelectionChanged)

        self.attributesView = SearchableAttributeTableWidget(
            self, True, False, False, [Types.Numeric, Types.Ordinal])

        firstRowLayout = QHBoxLayout()
        firstRowLayout.setSpacing(5)

        selectionGroup = QGroupBox(
            title=
            'Select a time series. Then select the columns to add from the current '
            'datasets',
            parent=self)
        firstRowLayout.addWidget(self.seriesView)
        buttonLayout = QVBoxLayout()
        buttonLayout.addWidget(self.addSeriesButton)
        buttonLayout.addWidget(self.removeSeriesButton)
        firstRowLayout.addLayout(buttonLayout)
        firstRowLayout.addSpacing(30)
        firstRowLayout.addWidget(self.workbenchView)
        firstRowLayout.addSpacing(30)
        firstRowLayout.addWidget(self.attributesView)
        selectionGroup.setLayout(firstRowLayout)

        # Time axis labels model with add/remove buttons
        self.timeAxisModel = CustomStringListModel(self)
        self.timeAxisModel.setHeaderLabel('Time labels')
        self.timeAxisView = CustomSignalView(self)
        self.timeAxisView.setModel(self.timeAxisModel)
        self.addTimeButton = QPushButton('Add', self)
        self.removeTimeButton = QPushButton('Remove', self)
        self.addTimeButton.clicked.connect(self.addTimeLabel)
        self.removeTimeButton.clicked.connect(self.removeTimeLabel)
        self.timeAxisView.setDragDropMode(QTableView.InternalMove)
        self.timeAxisView.setDragDropOverwriteMode(False)
        self.timeAxisView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.timeAxisView.verticalHeader().hide()
        self.timeAxisModel.rowAppended.connect(self.editTimeLabelName)

        # Concatenation model
        self.timeSeriesDataModel = ConcatenatedModel(self)
        self.timeSeriesDataView = QTableView(self)
        self.timeSeriesDataView.setSelectionMode(QTableView.NoSelection)
        self.timeSeriesDataView.setItemDelegateForColumn(
            1, ComboBoxDelegate(self.timeAxisModel, self.timeSeriesDataView))
        self.timeSeriesDataView.setEditTriggers(QTableView.CurrentChanged
                                                | QTableView.DoubleClicked)
        self.timeSeriesDataView.verticalHeader().hide()
        # Update the label column when some label changes in the label table
        self.timeAxisModel.dataChanged.connect(
            self.timeSeriesDataModel.timeAxisLabelChanged)

        groupTime = QGroupBox(
            title=
            'Add the time points (ordered) and set the correspondence to every selected column',
            parent=self)
        secondRowLayout = QHBoxLayout()
        secondRowLayout.setSpacing(5)
        # labelLayout = QVBoxLayout()
        # lab = QLabel('Here you should define every time point, in the correct order. After adding '
        #              'double-click a row to edit the point name and drag rows to reorder them', self)
        # lab.setWordWrap(True)
        # labelLayout.addWidget(lab)
        # labelLayout.addWidget(self.timeAxisView)
        secondRowLayout.addWidget(self.timeAxisView)
        timeButtonLayout = QVBoxLayout()
        timeButtonLayout.addWidget(self.addTimeButton)
        timeButtonLayout.addWidget(self.removeTimeButton)
        secondRowLayout.addLayout(timeButtonLayout)
        secondRowLayout.addSpacing(30)
        # labelLayout = QVBoxLayout()
        # lab = QLabel('Every selected column for the current series will be listed here. Click the right '
        #              'column of the table to set the time label associated with every original column',
        #              self)
        # lab.setWordWrap(True)
        # labelLayout.addWidget(lab)
        secondRowLayout.addWidget(self.timeSeriesDataView)
        # secondRowLayout.addLayout(labelLayout)
        groupTime.setLayout(secondRowLayout)

        self.outputName = QLineEdit(self)
        self.warningLabel = MessageLabel(text='',
                                         color='orange',
                                         icon=QMessageBox.Warning,
                                         parent=self)
        lastRowLayout = QFormLayout()
        lastRowLayout.addRow('Output variable name:', self.outputName)
        self.outputName.setPlaceholderText('Output name')
        lastRowLayout.setVerticalSpacing(0)
        lastRowLayout.addRow('', self.warningLabel)
        lastRowLayout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        self.warningLabel.hide()
        self.outputName.textChanged.connect(self.checkOutputName)

        layout = QVBoxLayout(self)
        layout.addWidget(selectionGroup)
        layout.addWidget(groupTime)
        layout.addLayout(lastRowLayout)
        self.checkNoSeries()

    def setWorkbench(self, w: WorkbenchModel) -> None:
        """
        Sets the workbench and initialises every attribute model (one for each frame)
        """
        self.workbench = w
        self.workbenchView.setModel(w)
        # Set a default name for output
        if self.workbench:
            name = 'time_series_{:d}'
            n = 1
            name_n = name.format(n)
            while name_n in self.workbench.names:
                n += 1
                name_n = name.format(n)
            self.outputName.setText(name_n)

    def addSourceFrameModel(self, frameName: str) -> None:
        if self.workbench:
            dfModel = self.workbench.getDataframeModelByName(frameName)
            # Create an attribute model with checkboxes
            standardModel = AttributeTableModel(self,
                                                checkable=True,
                                                editable=False,
                                                showTypes=True)
            standardModel.setFrameModel(dfModel)
            # Create a proxy to filter data in the concatenation
            customProxy = CustomProxyAttributeModel(self)
            customProxy.setSourceModel(standardModel)
            # Add proxy to the list of models
            self.models[frameName] = customProxy
            # Add proxy as source model
            self.timeSeriesDataModel.addSourceModel(customProxy)

    @Slot()
    def checkNoSeries(self) -> None:
        if not self.seriesModel.rowCount():
            self.workbenchView.setEnabled(False)
            self.attributesView.setEnabled(False)
            self.timeAxisView.setEnabled(False)
            self.addTimeButton.setEnabled(False)
            self.removeTimeButton.setEnabled(False)
            self.timeSeriesDataView.setEnabled(False)
        else:
            self.workbenchView.setEnabled(True)
            self.attributesView.setEnabled(True)
            self.timeAxisView.setEnabled(True)
            self.addTimeButton.setEnabled(True)
            self.removeTimeButton.setEnabled(True)
            self.timeSeriesDataView.setEnabled(True)

    def persistOptionsSetForSeries(self, seriesName: str) -> None:
        if seriesName:
            seriesValues: Dict[str,
                               List[Tuple[int,
                                          QPersistentModelIndex]]] = dict()
            for r in range(self.timeSeriesDataModel.rowCount()):
                column0Index: QModelIndex = self.timeSeriesDataModel.index(
                    r, 0, QModelIndex())
                column1Index: QModelIndex = self.timeSeriesDataModel.index(
                    r, 1, QModelIndex())
                sourceIndex: QModelIndex = self.timeSeriesDataModel.mapToSource(
                    column0Index)
                proxy: CustomProxyAttributeModel = sourceIndex.model()
                frameName: str = proxy.sourceModel().frameModel().name
                attrIndexInFrame: int = proxy.mapToSource(sourceIndex).row()
                timeLabelIndex: QPersistentModelIndex = column1Index.data(
                    Qt.DisplayRole)
                if seriesValues.get(frameName, None):
                    seriesValues[frameName].append(
                        (attrIndexInFrame, timeLabelIndex))
                else:
                    seriesValues[frameName] = [(attrIndexInFrame,
                                                timeLabelIndex)]
            self.seriesOptions[seriesName] = seriesValues

    @Slot(str, str)
    def onSeriesSelectionChanged(self, new: str, old: str) -> None:
        # Save current set options
        self.persistOptionsSetForSeries(old)
        if new:
            # Get options of new selection
            newOptions: Dict[str, List[Tuple[int, QPersistentModelIndex]]] = \
                self.seriesOptions.get(new, dict())
            for frameName, proxyModel in self.models.items():
                frameOptions = newOptions.get(frameName, None)
                self.setOptionsForFrame(frameName, frameOptions)
                # Update proxy view on the time label columns
                proxyModel.dataChanged.emit(
                    proxyModel.index(0, 1, QModelIndex()),
                    proxyModel.index(proxyModel.rowCount() - 1, 1,
                                     QModelIndex()),
                    [Qt.DisplayRole, Qt.EditRole])
        # Every time series change clear frame selection in workbench
        self.workbenchView.clearSelection()

    @Slot(str, str)
    def onFrameSelectionChanged(self, newFrame: str, _: str) -> None:
        if not newFrame:
            # Nothing is selected
            return self.attributesView.setAttributeModel(
                AttributeTableModel(self))
        # Check if frame is already in the source models
        if newFrame not in self.models.keys():
            # Create a new proxy and add it to source models
            self.addSourceFrameModel(newFrame)
            if len(self.models) == 1:
                # If it is the first model added then set up the view
                self.timeSeriesDataView.setModel(self.timeSeriesDataModel)
                self.timeSeriesDataView.horizontalHeader(
                ).setSectionResizeMode(0, QHeaderView.Stretch)
                self.timeSeriesDataView.horizontalHeader(
                ).setSectionResizeMode(1, QHeaderView.Stretch)
        # Update the attribute table
        self.attributesView.setAttributeModel(
            self.models[newFrame].sourceModel())

    def setOptionsForFrame(
            self, frameName: str,
            options: Optional[List[Tuple[int,
                                         QPersistentModelIndex]]]) -> None:
        customProxyModel = self.models[frameName]
        attributeTableModel = customProxyModel.sourceModel()
        attributeTableModel.setAllChecked(False)
        if options:
            proxySelection: Dict[int, QPersistentModelIndex] = {
                i: pmi
                for i, pmi in options
            }
            customProxyModel.attributes = proxySelection
            attributeTableModel.setChecked(list(proxySelection.keys()),
                                           value=True)
        else:
            customProxyModel.attributes = dict()

    @Slot()
    def addSeries(self) -> None:
        # Append new row
        self.seriesModel.appendEmptyRow()
        # In oder to avoid copying previous options
        for frameName, proxyModel in self.models.items():
            self.setOptionsForFrame(frameName, None)

    @Slot()
    def removeSeries(self) -> None:
        selected: List[QModelIndex] = self.seriesView.selectedIndexes()
        if selected:
            seriesName: str = selected[0].data(Qt.DisplayRole)
            # Remove row
            self.seriesModel.removeRow(selected[0].row())
            # Remove options for series if they exists
            self.seriesOptions.pop(seriesName, None)

    @Slot()
    def addTimeLabel(self) -> None:
        self.timeAxisModel.appendEmptyRow()

    @Slot()
    def removeTimeLabel(self) -> None:
        selected: List[QModelIndex] = self.timeAxisView.selectedIndexes()
        if selected:
            self.timeAxisModel.removeRow(selected[0].row())
            # Update model
            self.timeSeriesDataModel.dataChanged.emit(
                self.timeSeriesDataModel.index(0, 1, QModelIndex()),
                self.timeSeriesDataModel.index(
                    self.timeSeriesDataModel.rowCount() - 1, 1, QModelIndex()),
                [Qt.DisplayRole, Qt.EditRole])

    @Slot()
    def editSeriesName(self) -> None:
        index = self.seriesModel.index(self.seriesModel.rowCount() - 1, 0,
                                       QModelIndex())
        self.seriesView.setCurrentIndex(index)
        self.seriesView.edit(index)

    @Slot()
    def editTimeLabelName(self) -> None:
        index = self.timeAxisModel.index(self.timeAxisModel.rowCount() - 1, 0,
                                         QModelIndex())
        self.timeAxisView.setCurrentIndex(index)
        self.timeAxisView.edit(index)

    @Slot(str)
    def checkOutputName(self, text: str) -> None:
        if self.workbench and text in self.workbench.names:
            self.warningLabel.setText(
                'Variable {:s} will be overwritten'.format(text))
            self.warningLabel.show()
        else:
            self.warningLabel.hide()
class VStdAttributesDialog(QDialog):
    def __init__(self, parent, sig_attribute_edition: Signal, student: Student,
                 attributes: list):
        """
        Confirm dialog for dangerous actions

        :param parent: gui's main window
        :param sig_attribute_edition: Signal to emit in order to edit the selected attribute
        :param student: Current student
        :param attributes: List of attributes
        """
        QDialog.__init__(self, parent)

        self.setFixedSize(QSize(350, 350))
        self.setWindowFlag(Qt.WindowStaysOnTopHint)

        self.student = student
        self.attributes = attributes

        # Widgets
        self.std_info = QLabel(f"{student.firstname} {student.lastname}")

        self.table_attributes = QTableView()
        self.table_attributes.setFixedWidth(300)
        self.table_attributes.horizontalHeader().setStretchLastSection(True)
        self.table_attributes.horizontalHeader().hide()
        self.table_attributes.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_attributes.setSelectionMode(
            QAbstractItemView.SingleSelection)
        self.table_attributes.verticalHeader().setSectionResizeMode(
            QHeaderView.Fixed)
        self.table_attributes.verticalHeader().setFixedWidth(150)

        self.dm: AttributesTableModel = None
        self.attr_idx = {
        }  # Attributes ids to indexes -> {attr_id: attr_idx, ...}

        # Close button
        self.ok_btn = QPushButton("Ok")
        self.ok_btn.clicked.connect(self.accept)
        self.ok_btn.setFixedSize(QSize(60, 33))

        # Signals
        self.sig_attribute_edition = sig_attribute_edition
        self.table_attributes.clicked.connect(self.on_attr_clicked)

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.std_info)
        layout.setAlignment(self.std_info, Qt.AlignCenter)
        layout.addWidget(self.table_attributes)
        layout.addWidget(self.ok_btn)
        layout.setAlignment(self.ok_btn, Qt.AlignCenter)
        self.setLayout(layout)

        self.setStyleSheet(get_stylesheet("dialog"))

        # Initialization with the given attributes
        self.attributes_updated(self.attributes)

    def attributes_updated(self, attributes: list) -> None:
        """
        Called when an attribute changed and needs to be updated in the tableview

        :param attributes: List of attributes
        """
        self.attributes = attributes

        header = [a[1] for a in attributes]  # Table Header
        data_for_model = [a[2] for a in attributes]  # Table data

        # Save indexes
        self.attr_idx = {}
        i = 0
        for attr_id, _, _ in attributes:
            self.attr_idx[attr_id] = i
            i += 1

        # Create the table model
        self.dm = AttributesTableModel(self.table_attributes, data_for_model,
                                       header)

    def on_attr_clicked(self, item) -> None:
        """
        Triggered when a cell is clicked. Emits a signal with the attribute and student ids
        """
        attr_id = None
        for a in self.attr_idx:
            if self.attr_idx[a] == item.row():
                attr_id = a

        self.sig_attribute_edition.emit(attr_id, self.student.id)