def __init__(self): super(ConfigDialog, self).__init__() self.needs_reload = True # Set size and position self.setGeometry(0, 0, 900, 550) frameGm = self.frameGeometry() screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() frameGm.moveCenter(centerPoint) self.move(frameGm.topLeft()) self.contentsWidget = QListView() self.contentsWidget.setViewMode(QListView.IconMode) # self.contentsWidget.setIconSize(QSize(96, 84)) self.contentsWidget.setMovement(QListView.Static) self.contentsWidget.setMaximumWidth(174) self.contentsWidget.setSpacing(12) self.contentsWidget.setSelectionMode(QAbstractItemView.SingleSelection) self.contentsModel = QStandardItemModel() self.contentsWidget.setModel(self.contentsModel) self.contentsWidget.selectionModel().currentChanged.connect( self.changePage) self.buttonboxWidget = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Apply # | QDialogButtonBox.Help ) self.buttonboxWidget.button(QDialogButtonBox.Ok).clicked.connect( self.ok) self.buttonboxWidget.button(QDialogButtonBox.Apply).clicked.connect( self.apply) self.buttonboxWidget.button(QDialogButtonBox.Cancel).clicked.connect( self.close) self.pagesWidget = QStackedWidget() horizontalLayout = QHBoxLayout() horizontalLayout.addWidget(self.contentsWidget) horizontalLayout.addWidget(self.pagesWidget, 1) mainLayout = QVBoxLayout() mainLayout.addLayout(horizontalLayout) # mainLayout.addStretch(1) mainLayout.addSpacing(12) mainLayout.addWidget(self.buttonboxWidget) self.setLayout(mainLayout) self.setWindowTitle("Config Dialog") # Set modality self.setModal(True) self.lastwidget = None # Restore Settings pluginmanager.attach(self.request_reload, Filters.COMPLETE)
def commentaryListView(self): # https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtCore/QStringListModel.html # https://gist.github.com/minoue/9f384cd36339429eb0bf # https://www.pythoncentral.io/pyside-pyqt-tutorial-qlistview-and-qstandarditemmodel/ list = QListView() list.setEditTriggers(QAbstractItemView.NoEditTriggers) model = QStandardItemModel(list) for index, commentary in enumerate(self.parent.commentaryFullNameList): item = QStandardItem(commentary) item.setToolTip(self.parent.commentaryList[index]) #item.setCheckable(True) #item.setCheckState(Qt.CheckState.Checked) #item.setCheckState(Qt.CheckState.Unchecked) #print(item.checkState() is Qt.CheckState.Checked) model.appendRow(item) #model = QStringListModel(self.parent.commentaryList) #model = QStringListModel(self.parent.commentaryFullNameList) list.setModel(model) if config.commentaryText in self.parent.commentaryList: list.setCurrentIndex( model.index( self.parent.commentaryList.index(config.commentaryText), 0)) list.selectionModel().selectionChanged.connect(self.commentarySelected) return list
def setupUI(self): layout = QVBoxLayout() # Add a label layout.addWidget(QLabel(config.thisTranslation["downloadOptions"])) # Add a list view list = QListView() list.setEditTriggers(QAbstractItemView.NoEditTriggers) model = QStringListModel(self.options) list.setModel(model) list.selectionModel().selectionChanged.connect(self.optionSelected) list.setCurrentIndex(model.index(len(self.options) - 1, 0)) layout.addWidget(list) subLayout = QHBoxLayout() # Add a cancel button button = QPushButton(config.thisTranslation["message_cancel"]) button.clicked.connect(self.close) subLayout.addWidget(button) # Add a download button button = QPushButton(config.thisTranslation["download"]) button.clicked.connect( lambda: self.parent.downloadSelectedOption(self.selectedOption)) subLayout.addWidget(button) layout.addLayout(subLayout) # Set layout self.setLayout(layout)
def __init__(self, parent): super(InfoFrame, self).__init__(parent) self.widget_layout = QVBoxLayout(self) self.setLayout(self.widget_layout) self.section_label = QLabel(self) self.section_label.setText("Informations") self.widget_layout.addWidget(self.section_label) self.label = QLabel(self) self.label.setText("Select information to collect after successfull connection. Keep in mind that the more " "data you collect the more suspicious you are for antivirus software. You can " "change these settings later.") self.label.setWordWrap(True) self.widget_layout.addWidget(self.label) self.list = QListView(self) self.model = QStandardItemModel(self.list) self.widget_layout.addWidget(self.list) self.list.setModel(self.model) self.item_string = {} infos = ConfigManager.get_infos() for info in infos: self.item_string[info] = {"name": " ".join(info.capitalize().split("_"))} for string in self.item_string: item = QStandardItem(self.item_string.get(string).get("name")) item.setFlags(Qt.ItemIsEnabled) item.setData(QVariant(Qt.Checked), Qt.CheckStateRole) self.model.appendRow(item)
def __init__(self, key_defs): QWidget.__init__(self) self.__filter_popup = FilterPopup(self, key_defs) self.__filter_popup.filterSettingsChanged.connect(self.onItemChanged) layout = QVBoxLayout() self.model = DataTypeKeysListModel(key_defs) self.filter_model = DataTypeProxyModel(self, self.model) filter_layout = QHBoxLayout() self.search_box = SearchBox() self.search_box.filterChanged.connect(self.setSearchString) filter_layout.addWidget(self.search_box) filter_popup_button = QToolButton() filter_popup_button.setIcon(resourceIcon("ide/cog_edit.png")) filter_popup_button.clicked.connect(self.showFilterPopup) filter_layout.addWidget(filter_popup_button) layout.addLayout(filter_layout) self.data_type_keys_widget = QListView() self.data_type_keys_widget.setModel(self.filter_model) self.data_type_keys_widget.selectionModel().selectionChanged.connect(self.itemSelected) layout.addSpacing(15) layout.addWidget(self.data_type_keys_widget, 2) layout.addStretch() # layout.addWidget(Legend("Default types", DataTypeKeysListModel.DEFAULT_DATA_TYPE)) layout.addWidget(Legend("Observations available", DataTypeKeysListModel.HAS_OBSERVATIONS)) self.setLayout(layout)
def __init__(self, iter: int, parent=None) -> None: super().__init__(parent) self._iter = iter self._delegateWidth = 70 self._delegateHeight = 70 self._real_view = QListView(self) self._real_view.setViewMode(QListView.IconMode) self._real_view.setGridSize( QSize(self._delegateWidth, self._delegateHeight)) self._real_view.setItemDelegate( RealizationDelegate(self._delegateWidth, self._delegateHeight, self)) self._real_view.setSelectionMode(QAbstractItemView.SingleSelection) self._real_view.setFlow(QListView.LeftToRight) self._real_view.setWrapping(True) self._real_view.setResizeMode(QListView.Adjust) self._real_view.setUniformItemSizes(True) self._real_view.currentChanged = lambda current, _: self.currentChanged.emit( current) layout = QVBoxLayout() layout.addWidget(self._real_view) self.setLayout(layout)
def createMainListView(self): # Main and study history records are editable, so users can slightly modify a command and execute a new one. self.mainListView = QListView() self.mainModel = QStringListModel() self.mainListView.setModel(self.mainModel) self.mainListView.selectionModel().selectionChanged.connect( lambda selection: self.historyAction(selection, "main")) return self.mainListView
def commentaryListView(self): # https://doc.qt.io/archives/qtforpython-5.12/PySide2/QtCore/QStringListModel.html # https://gist.github.com/minoue/9f384cd36339429eb0bf # https://www.pythoncentral.io/pyside-pyqt-tutorial-qlistview-and-qstandarditemmodel/ self.commentaryListView = QListView() self.commentaryListView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.reloadCommentariesListModel() return self.commentaryListView
def chapterListView(self): self.chapterlist = QListView() self.chapterlist.setEditTriggers(QAbstractItemView.NoEditTriggers) topicList = self.getBookTopicList() self.chapterModel = QStringListModel(topicList) self.chapterlist.setModel(self.chapterModel) self.scrollChapterList(topicList) self.chapterlist.selectionModel().selectionChanged.connect(self.chapterSelected) return self.chapterlist
def createExternalListView(self): self.externalListView = QListView() # Only external file history record is not editable self.externalListView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.externalModel = QStringListModel() self.externalListView.setModel(self.externalModel) self.externalListView.selectionModel().selectionChanged.connect( lambda selection: self.historyAction(selection, "external")) return self.externalListView
def createStudyListView(self): #studyItems = list(reversed(config.history["study"])) # Main and study history records are editable, so users can slightly modify a command and execute a new one. self.studyListView = QListView() self.studyModel = QStringListModel() self.studyListView.setModel(self.studyModel) self.studyListView.selectionModel().selectionChanged.connect( lambda selection: self.historyAction(selection, "study")) return self.studyListView
def __init__(self, parent, batch_manager): super().__init__(parent) self.task_count = 0 self.calculation_manager = batch_manager self.whole_progress = QProgressBar(self) self.whole_progress.setMinimum(0) self.whole_progress.setMaximum(1) self.whole_progress.setFormat("%v of %m") self.whole_progress.setTextVisible(True) self.part_progress = QProgressBar(self) self.part_progress.setMinimum(0) self.part_progress.setMaximum(1) self.part_progress.setFormat("%v of %m") self.whole_label = QLabel("All batch progress:", self) self.part_label = QLabel("Single batch progress:", self) self.cancel_remove_btn = QPushButton("Remove task") self.cancel_remove_btn.setDisabled(True) self.logs = ExceptionList(self) self.logs.setToolTip("Logs") self.task_view = QListView() self.task_que = QStandardItemModel(self) self.task_view.setModel(self.task_que) self.process_num_timer = QTimer() self.process_num_timer.setInterval(1000) self.process_num_timer.setSingleShot(True) self.process_num_timer.timeout.connect(self.change_number_of_workers) self.number_of_process = QSpinBox(self) self.number_of_process.setRange(1, multiprocessing.cpu_count()) self.number_of_process.setValue(1) self.number_of_process.setToolTip( "Number of process used in batch calculation") self.number_of_process.valueChanged.connect( self.process_num_timer_start) self.progress_item_dict = {} layout = QGridLayout() layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight) layout.addWidget(self.whole_progress, 0, 1, 1, 2) layout.addWidget(self.part_label, 1, 0, Qt.AlignRight) layout.addWidget(self.part_progress, 1, 1, 1, 2) lab = QLabel("Number of process:") lab.setToolTip("Number of process used in batch calculation") layout.addWidget(lab, 2, 0) layout.addWidget(self.number_of_process, 2, 1) layout.addWidget(self.logs, 3, 0, 2, 3) layout.addWidget(self.task_view, 0, 4, 4, 1) layout.addWidget(self.cancel_remove_btn, 4, 4, 1, 1) layout.setColumnMinimumWidth(2, 10) layout.setColumnStretch(2, 1) self.setLayout(layout) self.preview_timer = QTimer() self.preview_timer.setInterval(1000) self.preview_timer.timeout.connect(self.update_info) self.task_view.selectionModel().currentChanged.connect( self.task_selection_change) self.cancel_remove_btn.clicked.connect(self.task_cancel_remove)
def __init__(self, parent, help_text=None, item_styles=ITEM_STYLES, item_separator_styles=ITEM_SEPARATOR_STYLES): """Multi purpose switcher.""" super(Switcher, self).__init__(parent) self._visible_rows = 0 self._modes = {} self._mode_on = '' self._item_styles = item_styles self._item_separator_styles = item_separator_styles # Widgets self.edit = QLineEdit(self) self.list = QListView(self) self.model = QStandardItemModel(self.list) self.proxy = SwitcherProxyModel(self.list) self.filter = KeyPressFilter() # Widgets setup self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) # self.setMinimumHeight(self._MIN_HEIGHT) self.setMaximumHeight(self._MAX_HEIGHT) self.edit.installEventFilter(self.filter) self.edit.setPlaceholderText(help_text if help_text else '') self.list.setMinimumWidth(self._MIN_WIDTH) self.list.setSpacing(-2) self.list.setItemDelegate(HTMLDelegate(self)) self.list.setFocusPolicy(Qt.NoFocus) self.list.setSelectionBehavior(self.list.SelectItems) self.list.setSelectionMode(self.list.SingleSelection) self.list.setVerticalScrollMode(QAbstractItemView.ScrollPerItem) self.proxy.setSourceModel(self.model) self.list.setModel(self.proxy) # Layout layout = QVBoxLayout() layout.addWidget(self.edit) layout.addWidget(self.list) self.setLayout(layout) # Signals self.filter.sig_up_key_pressed.connect(self.previous_row) self.filter.sig_down_key_pressed.connect(self.next_row) self.filter.sig_enter_key_pressed.connect(self.enter) self.edit.textChanged.connect(self.setup) self.edit.textChanged.connect(self.sig_text_changed) self.edit.returnPressed.connect(self.enter) self.list.clicked.connect(self.enter) self.list.clicked.connect(self.edit.setFocus) self.list.selectionModel().currentChanged.connect( self.current_item_changed) self.edit.setFocus()
def setupUI(self): from qtpy.QtGui import QStandardItemModel from qtpy.QtWidgets import (QPushButton, QLabel, QListView, QAbstractItemView, QHBoxLayout, QVBoxLayout, QLineEdit) mainLayout = QVBoxLayout() readingListLayout = QVBoxLayout() readingListLayout.addWidget(QLabel(self.translation[0])) readingListLayout.addWidget( QLabel("{0}{1}".format(self.translation[1], self.today))) filterLayout = QHBoxLayout() filterLayout.addWidget(QLabel(self.translation[2])) self.filterEntry = QLineEdit() self.filterEntry.textChanged.connect(self.resetItems) filterLayout.addWidget(self.filterEntry) readingListLayout.addLayout(filterLayout) self.readingList = QListView() self.readingList.setEditTriggers(QAbstractItemView.NoEditTriggers) self.readingListModel = QStandardItemModel(self.readingList) self.readingList.setModel(self.readingListModel) self.resetItems() self.readingListModel.itemChanged.connect(self.itemChanged) #print(self.readingList.currentIndex().row()) #self.readingList.selectionModel().selectionChanged.connect(self.function) readingListLayout.addWidget(self.readingList) buttonsLayout = QHBoxLayout() button = QPushButton(self.translation[3]) button.clicked.connect(self.openInTabs) buttonsLayout.addWidget(button) self.hideShowButton = QPushButton(self.translation[4]) self.hideShowButton.clicked.connect(self.hideShowCheckedItems) buttonsLayout.addWidget(self.hideShowButton) button = QPushButton(self.translation[6]) button.clicked.connect(self.resetAllItems) buttonsLayout.addWidget(button) button = QPushButton(self.translation[7]) button.clicked.connect(self.saveProgress) buttonsLayout.addWidget(button) mainLayout.addLayout(readingListLayout) mainLayout.addLayout(buttonsLayout) self.setLayout(mainLayout)
def bookListView(self): self.bookList = QListView() self.bookList.setEditTriggers(QAbstractItemView.NoEditTriggers) self.bookModel = QStringListModel(self.parent.referenceBookList) self.bookList.setModel(self.bookModel) if config.book in self.parent.referenceBookList: self.bookList.setCurrentIndex( self.bookModel.index( self.parent.referenceBookList.index(config.book), 0)) self.bookList.selectionModel().selectionChanged.connect( self.bookSelected) return self.bookList
def pdfListView(self): list = QListView() list.setEditTriggers(QAbstractItemView.NoEditTriggers) model = QStandardItemModel(list) for pdf in self.pdfList: item = QStandardItem(pdf) model.appendRow(item) list.setModel(model) if config.pdfText in self.parent.pdfList: list.setCurrentIndex( model.index(self.parent.pdfList.index(config.pdfText), 0)) list.selectionModel().selectionChanged.connect(self.pdfSelected) return list
def __init__(self, folder, parent=None): """Init.""" super().__init__(parent) font = get_font() self.folder = folder self.path_selected = "" self.path_list = [] self.results_old = {} self.setWindowTitle("Path Finder") self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) self.setFixedHeight(self._MAX_HEIGHT) self.setFont(font) # Set List widget self.list_viewer = QListView(self) self.list_viewer.setAttribute(Qt.WA_TransparentForMouseEvents) self.list_viewer.setFocusPolicy(Qt.NoFocus) self.list_viewer.setFixedWidth(self._MIN_WIDTH) self.list_viewer.setUniformItemSizes(True) self.list_model = QStringListModel() self.list_viewer.setModel(self.list_model) self.list_viewer.setFont(font) # Set edit self.edit = PathFinderEdit(self, textChanged=self.update_list) self.edit.setFont(font) self.edit.sig_esc_key_pressed.connect(self.close) self.edit.sig_enter_key_pressed.connect(self.enter) self.edit.sig_up_key_pressed.connect(lambda: self.prev_row(1)) self.edit.sig_pg_up_key_pressed.connect(self.pg_up) self.edit.sig_pg_half_up_key_pressed.connect(self.pg_half_up) self.edit.sig_down_key_pressed.connect(lambda: self.next_row(1)) self.edit.sig_pg_down_key_pressed.connect(self.pg_down) self.edit.sig_pg_half_down_key_pressed.connect(self.pg_half_down) layout = QVBoxLayout() layout.addWidget(self.edit) layout.addWidget(self.list_viewer) self.setLayout(layout) self.get_path_list() self.update_list() self.edit.setFocus()
def __init__(self, parent=None): super().__init__(parent) self.resize(200, 300) self.progress_log_list_view = QListView(self) self.progress_log_item_model = BufferedItemModel( self.progress_log_list_view) self.progress_log_list_view.setUniformItemSizes(True) self.progress_log_list_view.setModel(self.progress_log_item_model) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(self.progress_log_list_view) self.__bind_auto_scroll_handlers()
def devotionsListView(self): list = QListView() list.setEditTriggers(QAbstractItemView.NoEditTriggers) model = QStandardItemModel(list) for devotional in self.devotionals: item = QStandardItem(devotional) model.appendRow(item) list.setModel(model) list.selectionModel().selectionChanged.connect(self.devotionalSelected) return list
def videoListView(self): list = QListView() list.setEditTriggers(QAbstractItemView.NoEditTriggers) model = QStandardItemModel(list) for file in self.videoList: item = QStandardItem(file) model.appendRow(item) list.setModel(model) list.selectionModel().selectionChanged.connect(self.playSelectedVideo) return list
def _make_plot_list(self): """ Make a list showing the names of the plots :return: A QListView object which will contain plot names """ list_view = QListView(self) list_view.setSelectionMode(QAbstractItemView.ExtendedSelection) plot_list = QStandardItemModel(list_view) list_view.setModel(plot_list) list_view.installEventFilter(self) return list_view
def showEvent(self, event): if not self.model(): return row_count = self.model().rowCount(QModelIndex()) if row_count == 0: return # how to get individual item height? #test = self.model().createIndex(0, 0, None) #print(self.visualRect(test)) self.setMinimumHeight(100) return QListView.showEvent(self, event)
class ExampleProgressLogDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.resize(200, 300) self.progress_log_list_view = QListView(self) self.progress_log_item_model = BufferedItemModel( self.progress_log_list_view) self.progress_log_list_view.setUniformItemSizes(True) self.progress_log_list_view.setModel(self.progress_log_item_model) self.setLayout(QVBoxLayout(self)) self.layout().addWidget(self.progress_log_list_view) self.__bind_auto_scroll_handlers() def __bind_auto_scroll_handlers(self): progress_log_scroll_bar = self.progress_log_list_view.verticalScrollBar( ) self.__scroll_bar_was_at_bottom = False @Slot() @helpers.connect_slot( self.progress_log_item_model.rowsAboutToBeInserted) def __on_progress_log_model_rows_about_to_be_inserted(): self.__scroll_bar_was_at_bottom = progress_log_scroll_bar.value( ) == progress_log_scroll_bar.maximum() @Slot(int, int) @helpers.connect_slot(progress_log_scroll_bar.rangeChanged) def __on_progress_log_scroll_bar_range_changed(_, max_value): if self.__scroll_bar_was_at_bottom: progress_log_scroll_bar.setValue(max_value) def log(self, text): self.progress_log_item_model.appendRow(QStandardItem(text))
def __init__(self, headermodel, selectionmodel): """ Parameters ---------- headermodel The model to use in the file list view selectionmodel The selection model to use in the file list view """ super(FileSelectionView, self).__init__() # self.parameters = ParameterTree() self.fileListView = QListView() self.correlationName = QLineEdit() self.correlationName.setPlaceholderText('Name of result') layout = QVBoxLayout() layout.addWidget(self.fileListView) layout.addWidget(self.correlationName) self.setLayout(layout) self.headerModel = headermodel self.selectionModel = selectionmodel self.fileListView.setModel(headermodel) self.fileListView.setSelectionModel(selectionmodel) self.fileListView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.fileListView.setSelectionMode(QAbstractItemView.ExtendedSelection) # Make sure when the tabview selection model changes, the file list # current item updates self.selectionModel.currentChanged.connect( lambda current, _: self.fileListView.setCurrentIndex(current)) self.selectionModel.currentChanged.connect( lambda current, _: self.correlationName.setPlaceholderText(current. data()))
def __init__( self, id_num: int, colors: typing.List[str], color_dict: ColorMapDict, colormap: str = "", base_height=50, lock=False, blur=NoiseFilterType.No, gamma=1, ): super().__init__() self.id = id_num self.check_box = QCheckBox() # ColorCheckBox(parent=self) self.check_box.setChecked(True) self.lock = LockedInfoWidget(base_height - 10) self.lock.setVisible(lock) self.blur = BlurInfoWidget(base_height - 10) self.blur.setVisible(blur != NoiseFilterType.No) self.gamma = GammaInfoWidget() self.gamma.setVisible(gamma != 1) self.color_dict = color_dict self.colors = colors self.addItems(self.colors) if colormap: self.color = colormap else: self.color = self.itemText(0) self.setCurrentText(self.color) self.currentTextChanged.connect(self._update_image) self.base_height = base_height self.show_arrow = False self.show_frame = False view = QListView() view.setMinimumWidth(200) view.setItemDelegate(ColorStyledDelegate(self.base_height, color_dict)) self.setView(view) self.image = None # only for moment, to reduce code repetition layout = QHBoxLayout() layout.setContentsMargins(7, 0, 0, 0) layout.addWidget(self.check_box) layout.addWidget(self.lock) layout.addWidget(self.blur) layout.addWidget(self.gamma) layout.addStretch(1) self.setLayout(layout) self.check_box.stateChanged.connect( partial(self.channel_visible_changed.emit, self.id)) self.currentTextChanged.connect( partial(self.channel_colormap_changed.emit, self.id)) self._update_image()
def __init__(self, parent): super().__init__(parent) view = QListView() view.setMinimumWidth(COLORMAP_WIDTH + TEXT_WIDTH) view.setItemDelegate(ColorStyledDelegate(ENTRY_HEIGHT)) self.setView(view)
def __init__(self, model): """Initialize MNELAB main window. Parameters ---------- model : mnelab.model.Model instance The main window needs to connect to a model containing all data sets. This decouples the GUI from the data (model/view). """ super().__init__() self.model = model # data model self.setWindowTitle("MNELAB") # restore settings settings = read_settings() self.recent = settings["recent"] # list of recent files if settings["geometry"]: self.restoreGeometry(settings["geometry"]) else: self.setGeometry(300, 300, 1000, 750) # default window size self.move(QApplication.screens()[0].geometry().center() - self.rect().center()) if settings["state"]: self.restoreState(settings["state"]) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon(image_path("open_file.svg")) self.actions["open_file"] = file_menu.addAction( icon, "&Open...", self.open_data, QKeySequence.Open) self.recent_menu = file_menu.addMenu("Open recent") self.recent_menu.aboutToShow.connect(self._update_recent_menu) self.recent_menu.triggered.connect(self._load_recent) if not self.recent: self.recent_menu.setEnabled(False) self.actions["close_file"] = file_menu.addAction( "&Close", self.model.remove_data, QKeySequence.Close) self.actions["close_all"] = file_menu.addAction( "Close all", self.close_all) file_menu.addSeparator() icon = QIcon(image_path("meta_info.svg")) self.actions["meta_info"] = file_menu.addAction(icon, "Show information...", self.meta_info) file_menu.addSeparator() self.actions["import_bads"] = file_menu.addAction( "Import bad channels...", lambda: self.import_file(model.import_bads, "Import bad channels", "*.csv")) self.actions["import_events"] = file_menu.addAction( "Import events...", lambda: self.import_file(model.import_events, "Import events", "*.csv")) self.actions["import_annotations"] = file_menu.addAction( "Import annotations...", lambda: self.import_file(model.import_annotations, "Import annotations", "*.csv")) self.actions["import_ica"] = file_menu.addAction( "Import &ICA...", lambda: self.open_file(model.import_ica, "Import ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.export_menu = file_menu.addMenu("Export data") for name, ext in EXPORT_FORMATS.items(): self.actions["export_data_" + ext] = self.export_menu.addAction( f"{name} ({ext[1:].upper()})...", partial(self.export_file, model.export_data, "Export data", ext)) self.actions["export_bads"] = file_menu.addAction( "Export &bad channels...", lambda: self.export_file(model.export_bads, "Export bad channels", "*.csv")) self.actions["export_events"] = file_menu.addAction( "Export &events...", lambda: self.export_file(model.export_events, "Export events", "*.csv")) self.actions["export_annotations"] = file_menu.addAction( "Export &annotations...", lambda: self.export_file(model.export_annotations, "Export annotations", "*.csv")) self.actions["export_ica"] = file_menu.addAction( "Export ICA...", lambda: self.export_file(model.export_ica, "Export ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.actions["quit"] = file_menu.addAction("&Quit", self.close, QKeySequence.Quit) edit_menu = self.menuBar().addMenu("&Edit") self.actions["pick_chans"] = edit_menu.addAction( "P&ick channels...", self.pick_channels) icon = QIcon(image_path("chan_props.svg")) self.actions["chan_props"] = edit_menu.addAction( icon, "Channel &properties...", self.channel_properties) self.actions["set_montage"] = edit_menu.addAction("Set &montage...", self.set_montage) edit_menu.addSeparator() self.actions["set_ref"] = edit_menu.addAction("Set &reference...", self.set_reference) edit_menu.addSeparator() self.actions["annotations"] = edit_menu.addAction( "&Annotations...", self.edit_annotations) self.actions["events"] = edit_menu.addAction("&Events...", self.edit_events) edit_menu.addSeparator() self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop) self.actions["append_data"] = edit_menu.addAction( "Appen&d data...", self.append_data) plot_menu = self.menuBar().addMenu("&Plot") icon = QIcon(image_path("plot_data.svg")) self.actions["plot_data"] = plot_menu.addAction(icon, "&Data...", self.plot_data) icon = QIcon(image_path("plot_psd.svg")) self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density...", self.plot_psd) icon = QIcon(image_path("plot_montage.svg")) self.actions["plot_montage"] = plot_menu.addAction(icon, "&Montage...", self.plot_montage) plot_menu.addSeparator() self.actions["plot_ica_components"] = plot_menu.addAction( "ICA &components...", self.plot_ica_components) self.actions["plot_ica_sources"] = plot_menu.addAction( "ICA &sources...", self.plot_ica_sources) tools_menu = self.menuBar().addMenu("&Tools") icon = QIcon(image_path("filter.svg")) self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon(image_path("find_events.svg")) self.actions["find_events"] = tools_menu.addAction(icon, "Find &events...", self.find_events) self.actions["events_from_annotations"] = tools_menu.addAction( "Create events from annotations", self.events_from_annotations ) tools_menu.addSeparator() icon = QIcon(image_path("run_ica.svg")) self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...", self.run_ica) self.actions["apply_ica"] = tools_menu.addAction("Apply &ICA", self.apply_ica) tools_menu.addSeparator() self.actions["interpolate_bads"] = tools_menu.addAction( "Interpolate bad channels...", self.interpolate_bads) tools_menu.addSeparator() icon = QIcon(image_path("epoch_data.svg")) self.actions["epoch_data"] = tools_menu.addAction( icon, "Create Epochs...", self.epoch_data) view_menu = self.menuBar().addMenu("&View") self.actions["history"] = view_menu.addAction("&History...", self.show_history) self.actions["toolbar"] = view_menu.addAction("&Toolbar", self._toggle_toolbar) self.actions["toolbar"].setCheckable(True) self.actions["statusbar"] = view_menu.addAction("&Statusbar", self._toggle_statusbar) self.actions["statusbar"].setCheckable(True) help_menu = self.menuBar().addMenu("&Help") self.actions["about"] = help_menu.addAction("&About", self.show_about) self.actions["about_qt"] = help_menu.addAction("About &Qt", self.show_about_qt) # actions that are always enabled self.always_enabled = ["open_file", "about", "about_qt", "quit", "toolbar", "statusbar"] # set up toolbar self.toolbar = self.addToolBar("toolbar") self.toolbar.setObjectName("toolbar") self.toolbar.addAction(self.actions["open_file"]) self.toolbar.addAction(self.actions["meta_info"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["chan_props"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["plot_data"]) self.toolbar.addAction(self.actions["plot_psd"]) self.toolbar.addAction(self.actions["plot_montage"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["filter"]) self.toolbar.addAction(self.actions["find_events"]) self.toolbar.addAction(self.actions["epoch_data"]) self.toolbar.addAction(self.actions["run_ica"]) self.setUnifiedTitleAndToolBarOnMac(True) if settings["toolbar"]: self.toolbar.show() self.actions["toolbar"].setChecked(True) else: self.toolbar.hide() self.actions["toolbar"].setChecked(False) # set up data model for sidebar (list of open files) self.names = QStringListModel() self.names.dataChanged.connect(self._update_names) splitter = QSplitter() self.sidebar = QListView() self.sidebar.setFrameStyle(QFrame.NoFrame) self.sidebar.setFocusPolicy(Qt.NoFocus) self.sidebar.setModel(self.names) self.sidebar.clicked.connect(self._update_data) splitter.addWidget(self.sidebar) self.infowidget = InfoWidget() splitter.addWidget(self.infowidget) width = splitter.size().width() splitter.setSizes((int(width * 0.3), int(width * 0.7))) self.setCentralWidget(splitter) self.status_label = QLabel() self.statusBar().addPermanentWidget(self.status_label) if settings["statusbar"]: self.statusBar().show() self.actions["statusbar"].setChecked(True) else: self.statusBar().hide() self.actions["statusbar"].setChecked(False) self.setAcceptDrops(True) self.data_changed()
class Switcher(QDialog): """ A multi purpose switcher. Example ------- SwitcherItem: [title description <shortcut> section] SwitcherItem: [title description <shortcut> section] SwitcherSeparator: [---------------------------------------] SwitcherItem: [title description <shortcut> section] SwitcherItem: [title description <shortcut> section] """ # Dismissed switcher sig_rejected = Signal() # Search/Filter text changes sig_text_changed = Signal(TEXT_TYPES[-1]) # Current item changed sig_item_changed = Signal(object) # List item selected, mode and cleaned search text sig_item_selected = Signal(object, TEXT_TYPES[-1], TEXT_TYPES[-1], ) sig_mode_selected = Signal(TEXT_TYPES[-1]) _MAX_NUM_ITEMS = 15 _MIN_WIDTH = 580 _MIN_HEIGHT = 200 _MAX_HEIGHT = 390 _ITEM_WIDTH = _MIN_WIDTH - 20 def __init__(self, parent, help_text=None, item_styles=ITEM_STYLES, item_separator_styles=ITEM_SEPARATOR_STYLES): """Multi purpose switcher.""" super(Switcher, self).__init__(parent) self._modes = {} self._mode_on = '' self._item_styles = item_styles self._item_separator_styles = item_separator_styles # Widgets self.edit = QLineEdit(self) self.list = QListView(self) self.model = QStandardItemModel(self.list) self.proxy = SwitcherProxyModel(self.list) self.filter = KeyPressFilter() # Widgets setup self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) self.setWindowOpacity(0.95) # self.setMinimumHeight(self._MIN_HEIGHT) self.setMaximumHeight(self._MAX_HEIGHT) self.edit.installEventFilter(self.filter) self.edit.setPlaceholderText(help_text if help_text else '') self.list.setMinimumWidth(self._MIN_WIDTH) self.list.setItemDelegate(SwitcherDelegate(self)) self.list.setFocusPolicy(Qt.NoFocus) self.list.setSelectionBehavior(self.list.SelectItems) self.list.setSelectionMode(self.list.SingleSelection) self.list.setVerticalScrollMode(QAbstractItemView.ScrollPerItem) self.proxy.setSourceModel(self.model) self.list.setModel(self.proxy) # Layout layout = QVBoxLayout() layout.addWidget(self.edit) layout.addWidget(self.list) self.setLayout(layout) # Signals self.filter.sig_up_key_pressed.connect(self.previous_row) self.filter.sig_down_key_pressed.connect(self.next_row) self.filter.sig_enter_key_pressed.connect(self.enter) self.edit.textChanged.connect(self.setup) self.edit.textChanged.connect(self.sig_text_changed) self.edit.returnPressed.connect(self.enter) self.list.clicked.connect(self.enter) self.list.clicked.connect(self.edit.setFocus) self.list.selectionModel().currentChanged.connect( self.current_item_changed) self.edit.setFocus() # --- Helper methods def _add_item(self, item, last_item=True): """Perform common actions when adding items.""" item.set_width(self._ITEM_WIDTH) self.model.appendRow(item) if last_item: # Only set the current row to the first item when the added item is # the last one in order to prevent performance issues when # adding multiple items self.set_current_row(0) self.set_height() self.setup_sections() # --- API def clear(self): """Remove all items from the list and clear the search text.""" self.set_placeholder_text('') self.model.beginResetModel() self.model.clear() self.model.endResetModel() self.setMinimumHeight(self._MIN_HEIGHT) def set_placeholder_text(self, text): """Set the text appearing on the empty line edit.""" self.edit.setPlaceholderText(text) def add_mode(self, token, description): """Add mode by token key and description.""" if len(token) == 1: self._modes[token] = description else: raise Exception('Token must be of length 1!') def get_mode(self): """Get the current mode the switcher is in.""" return self._mode_on def remove_mode(self, token): """Remove mode by token key.""" if token in self._modes: self._modes.pop(token) def clear_modes(self): """Delete all modes spreviously defined.""" del self._modes self._modes = {} def add_item(self, icon=None, title=None, description=None, shortcut=None, section=None, data=None, tool_tip=None, action_item=False, last_item=True): """Add switcher list item.""" item = SwitcherItem( parent=self.list, icon=icon, title=title, description=description, data=data, shortcut=shortcut, section=section, action_item=action_item, tool_tip=tool_tip, styles=self._item_styles ) self._add_item(item, last_item=last_item) def add_separator(self): """Add separator item.""" item = SwitcherSeparatorItem(parent=self.list, styles=self._item_separator_styles) self._add_item(item) def setup(self): """Set-up list widget content based on the filtering.""" # Check exited mode mode = self._mode_on if mode: search_text = self.search_text()[len(mode):] else: search_text = self.search_text() # Check exited mode if self.search_text() == '': self._mode_on = '' self.clear() self.proxy.set_filter_by_score(False) self.sig_mode_selected.emit(self._mode_on) return # Check entered mode for key in self._modes: if self.search_text().startswith(key) and not mode: self._mode_on = key self.sig_mode_selected.emit(key) return # Filter by text titles = [] for row in range(self.model.rowCount()): item = self.model.item(row) if isinstance(item, SwitcherItem): title = item.get_title() else: title = '' titles.append(title) search_text = clean_string(search_text) scores = get_search_scores(to_text_string(search_text), titles, template=u"<b>{0}</b>") for idx, (title, rich_title, score_value) in enumerate(scores): item = self.model.item(idx) if not self._is_separator(item) and not item.is_action_item(): rich_title = rich_title.replace(" ", " ") item.set_rich_title(rich_title) item.set_score(score_value) self.proxy.set_filter_by_score(True) self.setup_sections() if self.count(): self.set_current_row(0) else: self.set_current_row(-1) self.set_height() def setup_sections(self): """Set-up which sections appear on the item list.""" mode = self._mode_on if mode: search_text = self.search_text()[len(mode):] else: search_text = self.search_text() if search_text: for row in range(self.model.rowCount()): item = self.model.item(row) if isinstance(item, SwitcherItem): item.set_section_visible(False) else: sections = [] for row in range(self.model.rowCount()): item = self.model.item(row) if isinstance(item, SwitcherItem): sections.append(item.get_section()) item.set_section_visible(bool(search_text)) else: sections.append('') if row != 0: visible = sections[row] != sections[row - 1] if not self._is_separator(item): item.set_section_visible(visible) else: item.set_section_visible(True) self.proxy.sortBy('_score') self.sig_item_changed.emit(self.current_item()) def set_height(self): """Set height taking into account the number of items.""" if self.count() >= self._MAX_NUM_ITEMS: switcher_height = self._MAX_HEIGHT elif self.count() != 0 and self.current_item(): current_item = self.current_item() item_height = current_item.get_height() list_height = item_height * (self.count() + 3) edit_height = self.edit.height() spacing_height = self.layout().spacing() * 4 switcher_height = list_height + edit_height + spacing_height switcher_height = max(switcher_height, self._MIN_HEIGHT) else: switcher_height = self._MIN_HEIGHT self.setFixedHeight(int(switcher_height)) def set_position(self, top): """Set the position of the dialog.""" parent = self.parent() if parent is not None: geo = parent.geometry() width = self.list.width() # This has been set in setup left = parent.geometry().width()/2 - width/2 while parent: geo = parent.geometry() top += geo.top() left += geo.left() parent = parent.parent() self.move(round(left), top) @Slot(QModelIndex, QModelIndex) def current_item_changed(self, current, previous): """Handle item selection.""" self.sig_item_changed.emit(self.current_item()) # --- Qt overrides # ------------------------------------------------------------------------ @Slot() @Slot(QListWidgetItem) def enter(self, itemClicked=None): """Override Qt method.""" row = self.current_row() model_index = self.proxy.mapToSource(self.proxy.index(row, 0)) item = self.model.item(model_index.row()) if item: mode = self._mode_on self.sig_item_selected.emit(item, mode, self.search_text()[len(mode):]) def accept(self): """Override Qt method.""" super(Switcher, self).accept() def reject(self): """Override Qt method.""" self.set_search_text('') self.sig_rejected.emit() super(Switcher, self).reject() def resizeEvent(self, event): """Override Qt method.""" super(Switcher, self).resizeEvent(event) # --- Helper methods: Lineedit widget def search_text(self): """Get the normalized (lowecase) content of the search text.""" return to_text_string(self.edit.text()).lower() def set_search_text(self, string): """Set the content of the search text.""" self.edit.setText(string) # --- Helper methods: List widget def _is_separator(self, item): """Check if item is an separator item (SwitcherSeparatorItem).""" return isinstance(item, SwitcherSeparatorItem) def _select_row(self, steps): """Select row in list widget based on a number of steps with direction. Steps can be positive (next rows) or negative (previous rows). """ row = self.current_row() + steps if 0 <= row < self.count(): self.set_current_row(row) def count(self): """Get the item count in the list widget.""" return self.proxy.rowCount() def current_row(self): """Return the current selected row in the list widget.""" return self.list.currentIndex().row() def current_item(self): """Return the current selected item in the list widget.""" row = self.current_row() model_index = self.proxy.mapToSource(self.proxy.index(row, 0)) item = self.model.item(model_index.row()) return item def set_current_row(self, row): """Set the current selected row in the list widget.""" proxy_index = self.proxy.index(row, 0) selection_model = self.list.selectionModel() # https://doc.qt.io/qt-5/qitemselectionmodel.html#SelectionFlag-enum selection_model.setCurrentIndex( proxy_index, selection_model.ClearAndSelect) # Ensure that the selected item is visible self.list.scrollTo(proxy_index, QAbstractItemView.EnsureVisible) def previous_row(self): """Select previous row in list widget.""" steps = 1 prev_row = self.current_row() - steps if prev_row == -1: self.set_current_row(self.count() - 1) else: if prev_row >= 0: # Need to map the filtered list to the actual model items list_index = self.proxy.index(prev_row, 0) model_index = self.proxy.mapToSource(list_index) item = self.model.item(model_index.row(), 0) if self._is_separator(item): steps += 1 self._select_row(-steps) def next_row(self): """Select next row in list widget.""" steps = 1 next_row = self.current_row() + steps # Need to map the filtered list to the actual model items list_index = self.proxy.index(next_row, 0) model_index = self.proxy.mapToSource(list_index) item = self.model.item(model_index.row(), 0) if next_row >= self.count(): self.set_current_row(0) else: if item: if self._is_separator(item): steps += 1 self._select_row(steps)
class MainWindow(QMainWindow): """MNELAB main window.""" def __init__(self, model): """Initialize MNELAB main window. Parameters ---------- model : mnelab.model.Model instance The main window needs to connect to a model containing all data sets. This decouples the GUI from the data (model/view). """ super().__init__() self.model = model # data model self.setWindowTitle("MNELAB") # restore settings settings = read_settings() self.recent = settings["recent"] # list of recent files if settings["geometry"]: self.restoreGeometry(settings["geometry"]) else: self.setGeometry(300, 300, 1000, 750) # default window size self.move(QApplication.screens()[0].geometry().center() - self.rect().center()) if settings["state"]: self.restoreState(settings["state"]) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon(image_path("open_file.svg")) self.actions["open_file"] = file_menu.addAction( icon, "&Open...", self.open_data, QKeySequence.Open) self.recent_menu = file_menu.addMenu("Open recent") self.recent_menu.aboutToShow.connect(self._update_recent_menu) self.recent_menu.triggered.connect(self._load_recent) if not self.recent: self.recent_menu.setEnabled(False) self.actions["close_file"] = file_menu.addAction( "&Close", self.model.remove_data, QKeySequence.Close) self.actions["close_all"] = file_menu.addAction( "Close all", self.close_all) file_menu.addSeparator() icon = QIcon(image_path("meta_info.svg")) self.actions["meta_info"] = file_menu.addAction(icon, "Show information...", self.meta_info) file_menu.addSeparator() self.actions["import_bads"] = file_menu.addAction( "Import bad channels...", lambda: self.import_file(model.import_bads, "Import bad channels", "*.csv")) self.actions["import_events"] = file_menu.addAction( "Import events...", lambda: self.import_file(model.import_events, "Import events", "*.csv")) self.actions["import_annotations"] = file_menu.addAction( "Import annotations...", lambda: self.import_file(model.import_annotations, "Import annotations", "*.csv")) self.actions["import_ica"] = file_menu.addAction( "Import &ICA...", lambda: self.open_file(model.import_ica, "Import ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.export_menu = file_menu.addMenu("Export data") for name, ext in EXPORT_FORMATS.items(): self.actions["export_data_" + ext] = self.export_menu.addAction( f"{name} ({ext[1:].upper()})...", partial(self.export_file, model.export_data, "Export data", ext)) self.actions["export_bads"] = file_menu.addAction( "Export &bad channels...", lambda: self.export_file(model.export_bads, "Export bad channels", "*.csv")) self.actions["export_events"] = file_menu.addAction( "Export &events...", lambda: self.export_file(model.export_events, "Export events", "*.csv")) self.actions["export_annotations"] = file_menu.addAction( "Export &annotations...", lambda: self.export_file(model.export_annotations, "Export annotations", "*.csv")) self.actions["export_ica"] = file_menu.addAction( "Export ICA...", lambda: self.export_file(model.export_ica, "Export ICA", "*.fif *.fif.gz")) file_menu.addSeparator() self.actions["quit"] = file_menu.addAction("&Quit", self.close, QKeySequence.Quit) edit_menu = self.menuBar().addMenu("&Edit") self.actions["pick_chans"] = edit_menu.addAction( "P&ick channels...", self.pick_channels) icon = QIcon(image_path("chan_props.svg")) self.actions["chan_props"] = edit_menu.addAction( icon, "Channel &properties...", self.channel_properties) self.actions["set_montage"] = edit_menu.addAction("Set &montage...", self.set_montage) edit_menu.addSeparator() self.actions["set_ref"] = edit_menu.addAction("Set &reference...", self.set_reference) edit_menu.addSeparator() self.actions["annotations"] = edit_menu.addAction( "&Annotations...", self.edit_annotations) self.actions["events"] = edit_menu.addAction("&Events...", self.edit_events) edit_menu.addSeparator() self.actions["crop"] = edit_menu.addAction("&Crop data...", self.crop) self.actions["append_data"] = edit_menu.addAction( "Appen&d data...", self.append_data) plot_menu = self.menuBar().addMenu("&Plot") icon = QIcon(image_path("plot_data.svg")) self.actions["plot_data"] = plot_menu.addAction(icon, "&Data...", self.plot_data) icon = QIcon(image_path("plot_psd.svg")) self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density...", self.plot_psd) icon = QIcon(image_path("plot_montage.svg")) self.actions["plot_montage"] = plot_menu.addAction(icon, "&Montage...", self.plot_montage) plot_menu.addSeparator() self.actions["plot_ica_components"] = plot_menu.addAction( "ICA &components...", self.plot_ica_components) self.actions["plot_ica_sources"] = plot_menu.addAction( "ICA &sources...", self.plot_ica_sources) tools_menu = self.menuBar().addMenu("&Tools") icon = QIcon(image_path("filter.svg")) self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon(image_path("find_events.svg")) self.actions["find_events"] = tools_menu.addAction(icon, "Find &events...", self.find_events) self.actions["events_from_annotations"] = tools_menu.addAction( "Create events from annotations", self.events_from_annotations ) tools_menu.addSeparator() icon = QIcon(image_path("run_ica.svg")) self.actions["run_ica"] = tools_menu.addAction(icon, "Run &ICA...", self.run_ica) self.actions["apply_ica"] = tools_menu.addAction("Apply &ICA", self.apply_ica) tools_menu.addSeparator() self.actions["interpolate_bads"] = tools_menu.addAction( "Interpolate bad channels...", self.interpolate_bads) tools_menu.addSeparator() icon = QIcon(image_path("epoch_data.svg")) self.actions["epoch_data"] = tools_menu.addAction( icon, "Create Epochs...", self.epoch_data) view_menu = self.menuBar().addMenu("&View") self.actions["history"] = view_menu.addAction("&History...", self.show_history) self.actions["toolbar"] = view_menu.addAction("&Toolbar", self._toggle_toolbar) self.actions["toolbar"].setCheckable(True) self.actions["statusbar"] = view_menu.addAction("&Statusbar", self._toggle_statusbar) self.actions["statusbar"].setCheckable(True) help_menu = self.menuBar().addMenu("&Help") self.actions["about"] = help_menu.addAction("&About", self.show_about) self.actions["about_qt"] = help_menu.addAction("About &Qt", self.show_about_qt) # actions that are always enabled self.always_enabled = ["open_file", "about", "about_qt", "quit", "toolbar", "statusbar"] # set up toolbar self.toolbar = self.addToolBar("toolbar") self.toolbar.setObjectName("toolbar") self.toolbar.addAction(self.actions["open_file"]) self.toolbar.addAction(self.actions["meta_info"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["chan_props"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["plot_data"]) self.toolbar.addAction(self.actions["plot_psd"]) self.toolbar.addAction(self.actions["plot_montage"]) self.toolbar.addSeparator() self.toolbar.addAction(self.actions["filter"]) self.toolbar.addAction(self.actions["find_events"]) self.toolbar.addAction(self.actions["epoch_data"]) self.toolbar.addAction(self.actions["run_ica"]) self.setUnifiedTitleAndToolBarOnMac(True) if settings["toolbar"]: self.toolbar.show() self.actions["toolbar"].setChecked(True) else: self.toolbar.hide() self.actions["toolbar"].setChecked(False) # set up data model for sidebar (list of open files) self.names = QStringListModel() self.names.dataChanged.connect(self._update_names) splitter = QSplitter() self.sidebar = QListView() self.sidebar.setFrameStyle(QFrame.NoFrame) self.sidebar.setFocusPolicy(Qt.NoFocus) self.sidebar.setModel(self.names) self.sidebar.clicked.connect(self._update_data) splitter.addWidget(self.sidebar) self.infowidget = InfoWidget() splitter.addWidget(self.infowidget) width = splitter.size().width() splitter.setSizes((int(width * 0.3), int(width * 0.7))) self.setCentralWidget(splitter) self.status_label = QLabel() self.statusBar().addPermanentWidget(self.status_label) if settings["statusbar"]: self.statusBar().show() self.actions["statusbar"].setChecked(True) else: self.statusBar().hide() self.actions["statusbar"].setChecked(False) self.setAcceptDrops(True) self.data_changed() def data_changed(self): # update sidebar self.names.setStringList(self.model.names) self.sidebar.setCurrentIndex(self.names.index(self.model.index)) # update info widget if self.model.data: self.infowidget.set_values(self.model.get_info()) else: self.infowidget.clear() # update status bar if self.model.data: mb = self.model.nbytes / 1024 ** 2 self.status_label.setText(f"Total Memory: {mb:.2f} MB") else: self.status_label.clear() # toggle actions if len(self.model) == 0: # disable if no data sets are currently open enabled = False else: enabled = True for name, action in self.actions.items(): # toggle if name not in self.always_enabled: action.setEnabled(enabled) if self.model.data: # toggle if specific conditions are met bads = bool(self.model.current["data"].info["bads"]) self.actions["export_bads"].setEnabled(enabled and bads) events = self.model.current["events"] is not None self.actions["export_events"].setEnabled(enabled and events) if self.model.current["dtype"] == "raw": annot = bool(self.model.current["data"].annotations) else: annot = False self.actions["export_annotations"].setEnabled(enabled and annot) self.actions["annotations"].setEnabled(enabled and annot) montage = bool(self.model.current["montage"]) self.actions["plot_montage"].setEnabled(enabled and montage) ica = bool(self.model.current["ica"]) self.actions["apply_ica"].setEnabled(enabled and ica) self.actions["export_ica"].setEnabled(enabled and ica) self.actions["plot_ica_components"].setEnabled(enabled and ica and montage) self.actions["plot_ica_sources"].setEnabled(enabled and ica) self.actions["interpolate_bads"].setEnabled(enabled and montage and bads) self.actions["events"].setEnabled(enabled and events) self.actions["events_from_annotations"].setEnabled(enabled and annot) self.actions["find_events"].setEnabled( enabled and self.model.current["dtype"] == "raw") self.actions["epoch_data"].setEnabled( enabled and events and self.model.current["dtype"] == "raw") self.actions["crop"].setEnabled( enabled and self.model.current["dtype"] == "raw") append = bool(self.model.get_compatibles()) self.actions["append_data"].setEnabled(enabled and append and (self.model.current["dtype"] in ("raw", "epochs"))) self.actions["meta_info"].setEnabled( enabled and self.model.current["ftype"] == "Extensible Data Format") # add to recent files if len(self.model) > 0: self._add_recent(self.model.current["fname"]) def open_data(self, fname=None): """Open raw file.""" if fname is None: fname = QFileDialog.getOpenFileName(self, "Open raw", filter="*")[0] if fname: if not (isfile(fname) or isdir(fname)): self._remove_recent(fname) QMessageBox.critical(self, "File does not exist", f"File {fname} does not exist anymore.") return name, ext, ftype = split_fname(fname, IMPORT_FORMATS) if ext in [".xdf", ".xdfz", ".xdf.gz"]: streams = parse_chunks(parse_xdf(fname)) rows, disabled = [], [] for idx, s in enumerate(streams): rows.append([s["stream_id"], s["name"], s["type"], s["channel_count"], s["channel_format"], s["nominal_srate"]]) is_marker = (s["nominal_srate"] == 0 or s["channel_format"] == "string") if is_marker: # disable marker streams disabled.append(idx) enabled = list(set(range(len(rows))) - set(disabled)) if enabled: selected = enabled[0] else: selected = None dialog = XDFStreamsDialog(self, rows, selected=selected, disabled=disabled) if dialog.exec_(): row = dialog.view.selectionModel().selectedRows()[0].row() stream_id = dialog.model.data(dialog.model.index(row, 0)) self.model.load(fname, stream_id=stream_id) else: # all other file formats try: self.model.load(fname) except FileNotFoundError as e: QMessageBox.critical(self, "File not found", str(e)) except UnknownFileTypeError as e: QMessageBox.critical(self, "Unknown file type", str(e)) def open_file(self, f, text, ffilter="*"): """Open file.""" fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0] if fname: f(fname) def export_file(self, f, text, ffilter="*"): """Export to file.""" fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0] if fname: f(fname, ffilter) def import_file(self, f, text, ffilter="*"): """Import file.""" fname = QFileDialog.getOpenFileName(self, text, filter=ffilter)[0] if fname: try: f(fname) except LabelsNotFoundError as e: QMessageBox.critical(self, "Channel labels not found", str(e)) except InvalidAnnotationsError as e: QMessageBox.critical(self, "Invalid annotations", str(e)) def close_all(self): """Close all currently open data sets.""" msg = QMessageBox.question(self, "Close all data sets", "Close all data sets?") if msg == QMessageBox.Yes: while len(self.model) > 0: self.model.remove_data() def meta_info(self): xml = get_xml(self.model.current["fname"]) dialog = MetaInfoDialog(self, xml) dialog.exec_() def pick_channels(self): """Pick channels in current data set.""" channels = self.model.current["data"].info["ch_names"] dialog = PickChannelsDialog(self, channels, selected=channels) if dialog.exec_(): picks = [item.data(0) for item in dialog.channels.selectedItems()] drops = set(channels) - set(picks) if drops: self.auto_duplicate() self.model.drop_channels(drops) self.model.history.append(f"raw.drop({drops})") def channel_properties(self): """Show channel properties dialog.""" info = self.model.current["data"].info dialog = ChannelPropertiesDialog(self, info) if dialog.exec_(): dialog.model.sort(0) bads = [] renamed = {} types = {} for i in range(dialog.model.rowCount()): new_label = dialog.model.item(i, 1).data(Qt.DisplayRole) old_label = info["ch_names"][i] if new_label != old_label: renamed[old_label] = new_label new_type = dialog.model.item(i, 2).data(Qt.DisplayRole).lower() old_type = channel_type(info, i).lower() if new_type != old_type: types[new_label] = new_type if dialog.model.item(i, 3).checkState() == Qt.Checked: bads.append(info["ch_names"][i]) self.model.set_channel_properties(bads, renamed, types) def set_montage(self): """Set montage.""" montages = mne.channels.get_builtin_montages() # TODO: currently it is not possible to remove an existing montage dialog = MontageDialog(self, montages, selected=self.model.current["montage"]) if dialog.exec_(): name = dialog.montages.selectedItems()[0].data(0) montage = mne.channels.make_standard_montage(name) ch_names = self.model.current["data"].info["ch_names"] # check if at least one channel name matches a name in the montage if set(ch_names) & set(montage.ch_names): self.model.set_montage(name) else: QMessageBox.critical(self, "No matching channel names", "Channel names defined in the montage do " "not match any channel name in the data.") def edit_annotations(self): fs = self.model.current["data"].info["sfreq"] pos = self.model.current["data"].annotations.onset pos = (pos * fs).astype(int).tolist() dur = self.model.current["data"].annotations.duration dur = (dur * fs).astype(int).tolist() desc = self.model.current["data"].annotations.description.tolist() dialog = AnnotationsDialog(self, pos, dur, desc) if dialog.exec_(): rows = dialog.table.rowCount() onset, duration, description = [], [], [] for i in range(rows): data = dialog.table.item(i, 0).data(Qt.DisplayRole) onset.append(float(data) / fs) data = dialog.table.item(i, 1).data(Qt.DisplayRole) duration.append(float(data) / fs) data = dialog.table.item(i, 2).data(Qt.DisplayRole) description.append(data) self.model.set_annotations(onset, duration, description) def edit_events(self): pos = self.model.current["events"][:, 0].tolist() desc = self.model.current["events"][:, 2].tolist() dialog = EventsDialog(self, pos, desc) if dialog.exec_(): rows = dialog.table.rowCount() events = np.zeros((rows, 3), dtype=int) for i in range(rows): pos = int(dialog.table.item(i, 0).data(Qt.DisplayRole)) desc = int(dialog.table.item(i, 1).data(Qt.DisplayRole)) events[i] = pos, 0, desc self.model.set_events(events) def crop(self): """Crop data.""" fs = self.model.current["data"].info["sfreq"] length = self.model.current["data"].n_times / fs dialog = CropDialog(self, 0, length) if dialog.exec_(): self.auto_duplicate() self.model.crop(dialog.start or 0, dialog.stop) def append_data(self): """Concatenate raw data objects to current one.""" compatibles = self.model.get_compatibles() dialog = AppendDialog(self, compatibles) if dialog.exec_(): self.auto_duplicate() self.model.append_data(dialog.names) def plot_data(self): """Plot data.""" # self.bad is needed to update history if bad channels are selected in # the interactive plot window (see also self.eventFilter) self.bads = self.model.current["data"].info["bads"] events = self.model.current["events"] nchan = self.model.current["data"].info["nchan"] fig = self.model.current["data"].plot(events=events, n_channels=nchan, title=self.model.current["name"], scalings="auto", show=False) if events is not None: hist = f"data.plot(events=events, n_channels={nchan})" else: hist = f"data.plot(n_channels={nchan})" self.model.history.append(hist) win = fig.canvas.manager.window win.setWindowTitle(self.model.current["name"]) win.findChild(QStatusBar).hide() win.installEventFilter(self) # detect if the figure is closed # prevent closing the window with the escape key try: fig._mne_params["close_key"] = None except AttributeError: # does not exist in older MNE versions pass fig.show() def plot_psd(self): """Plot power spectral density (PSD).""" kwds = {} if self.model.current["dtype"] == "raw": kwds.update({"average": False, "spatial_colors": False}) fig = self.model.current["data"].plot_psd(show=False, **kwds) if kwds: tmp = ", ".join(f"{key}={value}" for key, value in kwds.items()) hist = f"data.plot_psd({tmp})" else: hist = "data.plot_psd()" self.model.history.append(hist) win = fig.canvas.manager.window win.setWindowTitle("Power spectral density") fig.show() def plot_montage(self): """Plot current montage.""" fig = self.model.current["data"].plot_sensors(show_names=True, show=False) win = fig.canvas.manager.window win.setWindowTitle("Montage") win.findChild(QStatusBar).hide() win.findChild(QToolBar).hide() fig.show() def plot_ica_components(self): self.model.current["ica"].plot_components( inst=self.model.current["data"]) def plot_ica_sources(self): self.model.current["ica"].plot_sources(inst=self.model.current["data"]) def run_ica(self): """Run ICA calculation.""" methods = ["Infomax"] if have["picard"]: methods.insert(0, "Picard") if have["sklearn"]: methods.append("FastICA") dialog = RunICADialog(self, self.model.current["data"].info["nchan"], methods) if dialog.exec_(): calc = CalcDialog(self, "Calculating ICA", "Calculating ICA.") method = dialog.method.currentText().lower() exclude_bad_segments = dialog.exclude_bad_segments.isChecked() fit_params = {} if dialog.extended.isEnabled(): fit_params["extended"] = dialog.extended.isChecked() if dialog.ortho.isEnabled(): fit_params["ortho"] = dialog.ortho.isChecked() ica = mne.preprocessing.ICA(method=method, fit_params=fit_params) history = f"ica = mne.preprocessing.ICA(method='{method}'" if fit_params: history += f", fit_params={fit_params})" else: history += ")" self.model.history.append(history) pool = pebble.ProcessPool(max_workers=1) process = pool.schedule(function=ica.fit, args=(self.model.current["data"],), kwargs={"reject_by_annotation": exclude_bad_segments}) process.add_done_callback(lambda x: calc.accept()) pool.close() if not calc.exec_(): pool.stop() pool.join() else: self.model.current["ica"] = process.result() self.model.history.append(f"ica.fit(inst=raw, " f"reject_by_annotation=" f"{exclude_bad_segments})") self.data_changed() pool.join() def apply_ica(self): """Apply current fitted ICA.""" self.auto_duplicate() self.model.apply_ica() def interpolate_bads(self): """Interpolate bad channels""" dialog = InterpolateBadsDialog(self) if dialog.exec_(): duplicated = self.auto_duplicate() try: self.model.interpolate_bads(dialog.reset_bads, dialog.mode, dialog.origin) except ValueError as e: if duplicated: # undo self.model.remove_data() self.model.index -= 1 self.data_changed() msgbox = ErrorMessageBox(self, "Could not interpolate bad channels", str(e), traceback.format_exc()) msgbox.show() def filter_data(self): """Filter data.""" dialog = FilterDialog(self) if dialog.exec_(): self.auto_duplicate() self.model.filter(dialog.low, dialog.high) def find_events(self): info = self.model.current["data"].info # use first stim channel as default in dialog default_stim = 0 for i in range(info["nchan"]): if mne.io.pick.channel_type(info, i) == "stim": default_stim = i break dialog = FindEventsDialog(self, info["ch_names"], default_stim) if dialog.exec_(): stim_channel = dialog.stimchan.currentText() consecutive = dialog.consecutive.isChecked() initial_event = dialog.initial_event.isChecked() uint_cast = dialog.uint_cast.isChecked() min_dur = dialog.minduredit.value() shortest_event = dialog.shortesteventedit.value() self.model.find_events(stim_channel=stim_channel, consecutive=consecutive, initial_event=initial_event, uint_cast=uint_cast, min_duration=min_dur, shortest_event=shortest_event) def events_from_annotations(self): self.model.events_from_annotations() def epoch_data(self): """Epoch raw data.""" dialog = EpochDialog(self, self.model.current["events"]) if dialog.exec_(): events = [int(item.text()) for item in dialog.events.selectedItems()] tmin = dialog.tmin.value() tmax = dialog.tmax.value() if dialog.baseline.isChecked(): baseline = dialog.a.value(), dialog.b.value() else: baseline = None duplicated = self.auto_duplicate() try: self.model.epoch_data(events, tmin, tmax, baseline) except ValueError as e: if duplicated: # undo self.model.remove_data() self.model.index -= 1 self.data_changed() msgbox = ErrorMessageBox(self, "Could not create epochs", str(e), traceback.format_exc()) msgbox.show() def set_reference(self): """Set reference.""" dialog = ReferenceDialog(self) if dialog.exec_(): self.auto_duplicate() if dialog.average.isChecked(): self.model.set_reference("average") else: ref = [c.strip() for c in dialog.channellist.text().split(",")] self.model.set_reference(ref) def show_history(self): """Show history.""" dialog = HistoryDialog(self, "\n".join(self.model.history)) dialog.exec_() def show_about(self): """Show About dialog.""" msg_box = QMessageBox(self) text = (f"<img src='{image_path('mnelab_logo.png')}'>" f"<p>MNELAB {__version__}</p>") msg_box.setText(text) mnelab_url = "github.com/cbrnr/mnelab" mne_url = "github.com/mne-tools/mne-python" pkgs = [] for key, value in have.items(): if value: pkgs.append(f"{key} ({value})") else: pkgs.append(f"{key} (not installed)") text = (f'<nobr><p>This program uses Python ' f'{".".join(str(k) for k in version_info[:3])} and the ' f'following packages:</p></nobr>' f'<p>{", ".join(pkgs)}</p>' f'<nobr><p>MNELAB repository: ' f'<a href=https://{mnelab_url}>{mnelab_url}</a></p></nobr>' f'<nobr><p>MNE repository: ' f'<a href=https://{mne_url}>{mne_url}</a></p></nobr>' f'<p>Licensed under the BSD 3-clause license.</p>' f'<p>Copyright 2017-2020 by Clemens Brunner.</p>') msg_box.setInformativeText(text) msg_box.exec_() def show_about_qt(self): """Show About Qt dialog.""" QMessageBox.aboutQt(self, "About Qt") def auto_duplicate(self): """Automatically duplicate current data set. If the current data set is stored in a file (i.e. was loaded directly from a file), a new data set is automatically created. If the current data set is not stored in a file (i.e. was created by operations in MNELAB), a dialog box asks the user if the current data set should be overwritten or duplicated. Returns ------- duplicated : bool True if the current data set was automatically duplicated, False if the current data set was overwritten. """ # if current data is stored in a file create a new data set if self.model.current["fname"]: self.model.duplicate_data() return True # otherwise ask the user else: msg = QMessageBox.question(self, "Overwrite existing data set", "Overwrite existing data set?") if msg == QMessageBox.No: # create new data set self.model.duplicate_data() return True return False def _add_recent(self, fname): """Add a file to recent file list. Parameters ---------- fname : str File name. """ if fname in self.recent: # avoid duplicates self.recent.remove(fname) self.recent.insert(0, fname) while len(self.recent) > MAX_RECENT: # prune list self.recent.pop() write_settings(recent=self.recent) if not self.recent_menu.isEnabled(): self.recent_menu.setEnabled(True) def _remove_recent(self, fname): """Remove file from recent file list. Parameters ---------- fname : str File name. """ if fname in self.recent: self.recent.remove(fname) write_settings(recent=self.recent) if not self.recent: self.recent_menu.setEnabled(False) @Slot(QModelIndex) def _update_data(self, selected): """Update index and information based on the state of the sidebar. Parameters ---------- selected : QModelIndex Index of the selected row. """ if selected.row() != self.model.index: self.model.index = selected.row() self.data_changed() self.model.history.append(f"data = datasets[{self.model.index}]") @Slot(QModelIndex, QModelIndex) def _update_names(self, start, stop): """Update names in DataSets after changes in sidebar.""" for index in range(start.row(), stop.row() + 1): self.model.data[index]["name"] = self.names.stringList()[index] @Slot() def _update_recent_menu(self): self.recent_menu.clear() for recent in self.recent: self.recent_menu.addAction(recent) @Slot(QAction) def _load_recent(self, action): self.open_data(fname=action.text()) @Slot() def _toggle_toolbar(self): if self.toolbar.isHidden(): self.toolbar.show() else: self.toolbar.hide() write_settings(toolbar=not self.toolbar.isHidden()) @Slot() def _toggle_statusbar(self): if self.statusBar().isHidden(): self.statusBar().show() else: self.statusBar().hide() write_settings(statusbar=not self.statusBar().isHidden()) @Slot(QDropEvent) def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() @Slot(QDropEvent) def dropEvent(self, event): mime = event.mimeData() if mime.hasUrls(): urls = mime.urls() for url in urls: try: self.open_data(url.toLocalFile()) except FileNotFoundError as e: QMessageBox.critical(self, "File not found", str(e)) @Slot(QEvent) def closeEvent(self, event): """Close application. Parameters ---------- event : QEvent Close event. """ write_settings(geometry=self.saveGeometry(), state=self.saveState()) if self.model.history: print("\nCommand History") print("===============") print("\n".join(self.model.history)) QApplication.quit() def eventFilter(self, source, event): # currently the only source is the raw plot window if event.type() == QEvent.Close: self.data_changed() bads = self.model.current["data"].info["bads"] if self.bads != bads: self.model.history.append(f"data.info['bads'] = {bads}") return QObject.eventFilter(self, source, event)
class DataTypeKeysWidget(QWidget): dataTypeKeySelected = Signal() def __init__(self, model): QWidget.__init__(self) self.__filter_popup = FilterPopup(self) self.__filter_popup.filterSettingsChanged.connect(self.onItemChanged) layout = QVBoxLayout() self.model = model self.filter_model = DataTypeProxyModel(self.model) filter_layout = QHBoxLayout() self.search_box = SearchBox() self.search_box.filterChanged.connect(self.setSearchString) filter_layout.addWidget(self.search_box) filter_popup_button = QToolButton() filter_popup_button.setIcon(resourceIcon("ide/cog_edit.png")) filter_popup_button.clicked.connect(self.showFilterPopup) filter_layout.addWidget(filter_popup_button) layout.addLayout(filter_layout) self.data_type_keys_widget = QListView() self.data_type_keys_widget.setModel(self.filter_model) self.data_type_keys_widget.selectionModel().selectionChanged.connect( self.itemSelected) layout.addSpacing(15) layout.addWidget(self.data_type_keys_widget, 2) layout.addStretch() # layout.addWidget(Legend("Default types", DataTypeKeysListModel.DEFAULT_DATA_TYPE)) layout.addWidget( Legend("Observations available", DataTypeKeysListModel.HAS_OBSERVATIONS)) self.setLayout(layout) def onItemChanged(self, item): # self.filter_model.setShowBlockKeys(item["block"]) self.filter_model.setShowSummaryKeys(item["summary"]) self.filter_model.setShowGenKWKeys(item["gen_kw"]) self.filter_model.setShowGenDataKeys(item["gen_data"]) self.filter_model.setShowCustomKwKeys(item["custom_kw"]) # self.filter_model.setShowCustomPcaKeys(item["custom_pca"]) def itemSelected(self): selected_item = self.getSelectedItem() if selected_item is not None: self.dataTypeKeySelected.emit() def getSelectedItem(self): """ @rtype: str """ index = self.data_type_keys_widget.currentIndex() source_index = self.filter_model.mapToSource(index) item = self.model.itemAt(source_index) return item def selectDefault(self): self.data_type_keys_widget.setCurrentIndex( self.filter_model.index(0, 0)) def setSearchString(self, filter): self.filter_model.setFilterFixedString(filter) def showFilterPopup(self): self.__filter_popup.show()