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 self.resize(settings["size"]) self.move(settings["pos"]) # remove None entries from self.recent self.recent = [recent for recent in self.recent if recent is not None] # trigger theme setting QIcon.setThemeSearchPaths([str(Path(__file__).parent / "icons")]) self.event(QEvent(QEvent.PaletteChange)) self.actions = {} # contains all actions # initialize menus file_menu = self.menuBar().addMenu("&File") icon = QIcon.fromTheme("open-file") 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.fromTheme("meta-info") 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 ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") self.actions[action] = self.export_menu.addAction( f"{ext[1:].upper()} ({description[1]})...", 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["xdf_chunks"] = file_menu.addAction( "Show XDF chunks...", self.xdf_chunks) 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.fromTheme("chan-props") 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.fromTheme("plot-data") self.actions["plot_data"] = plot_menu.addAction( icon, "&Data", self.plot_data) icon = QIcon.fromTheme("plot-psd") self.actions["plot_psd"] = plot_menu.addAction( icon, "&Power spectral density", self.plot_psd) icon = QIcon.fromTheme("plot-locations") self.actions["plot_locations"] = plot_menu.addAction( icon, "&Channel locations", self.plot_locations) self.actions["plot_erds"] = plot_menu.addAction( "&ERDS maps...", self.plot_erds) 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.fromTheme("filter-data") self.actions["filter"] = tools_menu.addAction(icon, "&Filter data...", self.filter_data) icon = QIcon.fromTheme("find-events") 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) self.actions["annotations_from_events"] = tools_menu.addAction( "Create annotations from events", self.annotations_from_events) tools_menu.addSeparator() nirs_menu = tools_menu.addMenu("NIRS") self.actions["convert_od"] = nirs_menu.addAction( "Convert to &optical density", self.convert_od) self.actions["convert_bl"] = nirs_menu.addAction( "Convert to &haemoglobin", self.convert_bl) tools_menu.addSeparator() icon = QIcon.fromTheme("run-ica") 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.fromTheme("epoch-data") 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", "xdf_chunks", "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_locations"]) 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.toolbar.setMovable(False) 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) locations = has_locations(self.model.current["data"].info) self.actions["plot_locations"].setEnabled(enabled and locations) 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_erds"].setEnabled( enabled and self.model.current["dtype"] == "epochs") self.actions["plot_ica_components"].setEnabled(enabled and ica and locations) self.actions["plot_ica_sources"].setEnabled(enabled and ica) self.actions["interpolate_bads"].setEnabled(enabled and locations and bads) self.actions["events"].setEnabled(enabled and events) self.actions["events_from_annotations"].setEnabled(enabled and annot) self.actions["annotations_from_events"].setEnabled(enabled and events) 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"] in ["XDF", "XDFZ", "XDF.GZ"]) self.actions["convert_od"].setEnabled( len( mne.pick_types(self.model.current["data"].info, fnirs="fnirs_cw_amplitude"))) self.actions["convert_bl"].setEnabled( len( mne.pick_types(self.model.current["data"].info, fnirs="fnirs_od"))) # disable unsupported exporters for epochs (all must support raw) if self.model.current["dtype"] == "epochs": for ext, description in writers.items(): action = "export_data" + ext.replace(".", "_") if "epoch" in description[2]: self.actions[action].setEnabled(True) else: self.actions[action].setEnabled(False) # 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")[0] if fname: if not (Path(fname).is_file() or Path(fname).is_dir()): self._remove_recent(fname) QMessageBox.critical(self, "File does not exist", f"File {fname} does not exist anymore.") return ext = "".join(Path(fname).suffixes) if any([ext.endswith(e) for e in (".xdf", ".xdfz", ".xdf.gz")]): rows, disabled = [], [] for idx, s in enumerate(resolve_streams(fname)): 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)) srate = "effective" if dialog.effective_srate else "nominal" prefix_markers = dialog.prefix_markers kwargs = {} if srate == "nominal": kwargs["srate"] = srate if prefix_markers: kwargs["prefix_markers"] = prefix_markers self.model.load(fname, stream_id=stream_id, **kwargs) else: # all other file formats try: self.model.load(fname) except FileNotFoundError as e: QMessageBox.critical(self, "File not found", str(e)) except ValueError 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 xdf_chunks(self): """Show XDF chunks.""" fname = QFileDialog.getOpenFileName(self, "Select XDF file", filter="*.xdf *.xdfz *.xdf.gz")[0] if fname: chunks = list_chunks(fname) dialog = XDFChunksDialog(self, chunks, fname) dialog.exec_() def export_file(self, f, text, ffilter="*"): """Export to file.""" fname = QFileDialog.getSaveFileName(self, text, filter=ffilter)[0] if fname: if ffilter != "*": exts = [ext.replace("*", "") for ext in ffilter.split()] maxsuffixes = max([ext.count(".") for ext in exts]) suffixes = Path(fname).suffixes for i in range(-maxsuffixes, 0): ext = "".join(suffixes[i:]) if ext in exts: return f(fname) fname = fname + exts[0] return f(fname) 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): """Show XDF meta info.""" 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 = list(set(channels) - set(picks)) if drops: self.auto_duplicate() self.model.drop_channels(drops) self.model.history.append(f"data.drop_channels({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) 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.statusBar().hide() # not necessary since matplotlib 3.3 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_locations(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.statusBar().hide() # not necessary since matplotlib 3.3 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 plot_erds(self): """Plot ERDS maps.""" data = self.model.current["data"] t_range = [data.tmin, data.tmax] f_range = [1, data.info["sfreq"] / 2] dialog = ERDSDialog(self, t_range, f_range) if dialog.exec(): freqs = np.arange(dialog.f1, dialog.f2, dialog.step) baseline = [dialog.b1, dialog.b2] times = [dialog.t1, dialog.t2] figs = plot_erds(data, freqs, freqs, baseline, times) for fig in figs: fig.show() 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 = mp.Pool(processes=1) def callback(x): QMetaObject.invokeMethod(calc, "accept", Qt.QueuedConnection) res = pool.apply_async( func=ica.fit, args=(self.model.current["data"], ), kwds={"reject_by_annotation": exclude_bad_segments}, callback=callback) pool.close() if not calc.exec(): pool.terminate() print("ICA calculation aborted...") else: self.model.current["ica"] = res.get(timeout=1) self.model.history.append( f"ica.fit(inst=raw, reject_by_annotation=" f"{exclude_bad_segments})") self.data_changed() 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 annotations_from_events(self): self.model.annotations_from_events() 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 convert_od(self): """Convert to optical density.""" self.auto_duplicate() self.model.convert_od() def convert_bl(self): """Convert to haemoglobin.""" self.auto_duplicate() self.model.convert_beer_lambert() 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.""" from . import __version__ msg_box = QMessageBox(self) text = ( f"<img src='{image_path('mnelab_logo.png')}'><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)") version = ".".join(str(k) for k in version_info[:3]) text = ( f"<nobr><p>This program uses Python {version} and the following packages:" f"</p></nobr><p>{', '.join(pkgs)}</p>" f"<nobr><p>MNELAB repository: <a href=https://{mnelab_url}>{mnelab_url}</a>" f"</p></nobr><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–2021 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(size=self.size(), pos=self.pos()) if self.model.history: print("\n# Command History\n") 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) def event(self, ev): """Catch system events.""" if ev.type() == QEvent.PaletteChange: # detect theme switches style = interface_style() # light or dark if style is not None: QIcon.setThemeName(style) else: QIcon.setThemeName("light") # fallback return super().event(ev)
class Window(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.images = [] self.index = -1 self.ratio = 1 # ratio for QLabel image self.mouse_position = None self.settings = None # Extensions self.extensions = [] for format in QImageReader.supportedImageFormats(): self.extensions.append(format.data().decode('utf-8')) # Filters self.filters = [] for extension in self.extensions: self.filters.append('*.{0}'.format(str(extension))) # UI self.set_up_ui() # settings self.load_settings() def on_message_received(self, msg): """ on message received from single application Args: msg (string): file path """ self.create_images(msg) self.display_image() def set_up_ui(self): # Status Bar self.status_bar = self.statusBar() self.label_name = QLabel() self.label_numero = QLabel() self.status_bar.addPermanentWidget(self.label_name, 1) self.status_bar.addPermanentWidget(self.label_numero, 0) # Main Window self.setWindowTitle('BaloViewer') self.setWindowIcon(QIcon('baloviewer.ico')) # Label image self.image = QLabel() self.image.setScaledContents(True) # Scroll area self.scroll_area = QScrollArea() self.scroll_area.setWidget(self.image) self.scroll_area.showMaximized() self.scroll_area.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.scroll_area.setAlignment(Qt.AlignmentFlag.AlignCenter) self.scroll_area.viewport().installEventFilter(self) # image list self.image_gallery = ImageGallery() self.image_gallery.itemClicked.connect(self.image_gallery_clicked) self.image_gallery.viewport().installEventFilter(self) self.dock_widget = QDockWidget('Image Gallery', self) self.dock_widget.setWidget(self.image_gallery) self.dock_widget.setFloating(False) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_widget) # central widget self.setCentralWidget(self.scroll_area) # Action bar self.create_actions() self.create_menubar() self.create_toolbar() # option parser parser = OptionParser() parser.add_option("-f", "--file", dest="filename", help="open a file") (options, args) = parser.parse_args() parser_file = options.filename if parser_file is not None and os.path.isfile(parser_file): self.create_images(parser_file) self.display_image() def create_actions(self): # Action Open self.action_open = QAction(QIcon.fromTheme('document-open'), 'Open', self) self.action_open.setShortcut('Ctrl+O') self.action_open.setStatusTip('Open file') self.action_open.triggered.connect(self.open) # Action Save self.action_save = QAction(QIcon.fromTheme('document-save'), 'Save', self) self.action_save.setShortcut('Ctrl+S') self.action_save.setStatusTip('Save file') self.action_save.triggered.connect(self.save) # Action Copy self.action_copy = QAction(QIcon.fromTheme('edit-copy'), 'Copy', self) self.action_copy.setStatusTip('Copy') self.action_copy.triggered.connect(self.copy) # Action move self.action_move = QAction(QIcon.fromTheme('edit-cut'), 'Move', self) self.action_move.setStatusTip('Move') self.action_move.triggered.connect(self.move) # Action Delete self.action_delete = QAction(QIcon.fromTheme('edit-delete'), 'Delete', self) self.action_delete.setStatusTip('Delete') self.action_delete.triggered.connect(self.delete) # Action Quit self.action_quit = QAction(QIcon.fromTheme('application-exit'), 'Quit', self) self.action_quit.setShortcut('Ctrl+Q') self.action_quit.setStatusTip('Quit') self.action_quit.triggered.connect(self.close) # Action Rotate left self.action_rotate_left = QAction( QIcon.fromTheme('object-rotate-left'), 'Rotate left', self) self.action_rotate_left.setStatusTip('Rotate left') self.action_rotate_left.triggered.connect(self.rotate_left) # Action Rotate right self.action_rotate_right = QAction( QIcon.fromTheme('object-rotate-right'), 'Rotate right', self) self.action_rotate_right.setStatusTip('Rotate right') self.action_rotate_right.triggered.connect(self.rotate_right) # Action Mirror self.action_flip_horizontal = QAction( QIcon.fromTheme('object-flip-horizontal'), 'Flip horizontally', self) self.action_flip_horizontal.setStatusTip('Flip horizontally') self.action_flip_horizontal.triggered.connect(self.flip_horizontal) # Action Flip vertical self.action_flip_vertical = QAction( QIcon.fromTheme('object-flip-vertical'), 'Flip vertically', self) self.action_flip_vertical.setStatusTip('Flip vertically') self.action_flip_vertical.triggered.connect(self.flip_vertical) # Action Previous image self.action_previous_image = QAction(QIcon.fromTheme('go-previous'), 'Previous image', self) self.action_previous_image.setStatusTip('Previous image') self.action_previous_image.triggered.connect(self.previous_image) # Action Full screen self.action_fullscreen = QAction(QIcon.fromTheme('view-fullscreen'), 'Full screen', self) self.action_fullscreen.setStatusTip('Full screen') self.action_fullscreen.triggered.connect(self.fullscreen) # Action Normal size self.action_normal_size = QAction(QIcon.fromTheme('zoom-original'), 'Normal size', self) self.action_normal_size.setStatusTip('Normal Size') self.action_normal_size.triggered.connect(self.normal_size) # Action Fit Screen self.action_fit_screen = QAction(QIcon.fromTheme('zoom-fit-best'), 'Fit to screen', self) self.action_fit_screen.setStatusTip('Fit to screen') self.action_fit_screen.setCheckable(True) self.action_fit_screen.triggered.connect(self.fit_screen) # Action Zoom in self.action_zoom_in = QAction(QIcon.fromTheme('zoom-in'), 'Zoom in', self) self.action_zoom_in.setStatusTip('Zoom in') self.action_zoom_in.triggered.connect(self.zoom_in) # Action Zoom out self.action_zoom_out = QAction(QIcon.fromTheme('zoom-out'), 'Zoom out', self) self.action_zoom_out.setStatusTip('Zoom out') self.action_zoom_out.triggered.connect(self.zoom_out) # Action Fit height self.action_fit_vertical = QAction('Fit vertically', self) self.action_fit_vertical.setStatusTip('Fit vertically') self.action_fit_vertical.setCheckable(True) self.action_fit_vertical.triggered.connect(self.fit_height) # Action Fit width self.action_fit_horizontal = QAction('Fit horizontally', self) self.action_fit_horizontal.setStatusTip('Fit horizontally') self.action_fit_horizontal.setCheckable(True) self.action_fit_horizontal.triggered.connect(self.fit_width) # Action Fit width self.action_fit_horizontal = QAction('Fit horizontally', self) self.action_fit_horizontal.setStatusTip('Fit horizontally') self.action_fit_horizontal.setCheckable(True) self.action_fit_horizontal.triggered.connect(self.fit_width) # Action Image list self.action_image_gallery = QAction('Image gallery', self) self.action_image_gallery.setStatusTip('Image gallery') self.action_image_gallery.setCheckable(True) self.action_image_gallery.triggered.connect( self.image_gallery_triggered) # Action Next_image self.action_next_image = QAction(QIcon.fromTheme('go-next'), 'Next image', self) self.action_next_image.setStatusTip('Next image') self.action_next_image.triggered.connect(self.next_image) # Action First image self.action_first_image = QAction(QIcon.fromTheme('go-first'), 'First image', self) self.action_first_image.setStatusTip('First image') self.action_first_image.triggered.connect(self.first_image) # Action Last image self.action_last_image = QAction(QIcon.fromTheme('go-last'), 'Last image', self) self.action_last_image.setStatusTip('Last image') self.action_last_image.triggered.connect(self.last_image) # Action About self.action_about = QAction(QIcon.fromTheme('help-about'), 'About', self) self.action_about.setStatusTip('About') self.action_about.triggered.connect(self.about) def create_menubar(self): self.menubar = self.menuBar() # File self.menu_file = self.menubar.addMenu('File') self.menu_file.addAction(self.action_open) self.menu_file.addAction(self.action_save) self.menu_file.addSeparator() self.menu_file.addAction(self.action_copy) self.menu_file.addAction(self.action_move) self.menu_file.addAction(self.action_delete) self.menu_file.addSeparator() self.menu_file.addAction(self.action_quit) # Edit self.menu_edit = self.menubar.addMenu('Edit') self.menu_edit.addAction(self.action_rotate_left) self.menu_edit.addAction(self.action_rotate_right) self.menu_edit.addSeparator() self.menu_edit.addAction(self.action_flip_horizontal) self.menu_edit.addAction(self.action_flip_vertical) # View self.menu_view = self.menubar.addMenu('View') self.menu_view.addAction(self.action_fullscreen) self.menu_view.addAction(self.action_normal_size) self.menu_view.addAction(self.action_fit_screen) self.menu_view.addSeparator() self.menu_view.addAction(self.action_zoom_in) self.menu_view.addAction(self.action_zoom_out) self.menu_view.addSeparator() self.menu_view.addAction(self.action_fit_vertical) self.menu_view.addAction(self.action_fit_horizontal) self.menu_view.addSeparator() self.menu_view.addAction(self.action_image_gallery) # Go self.menu_go = self.menubar.addMenu('Go') self.menu_go.addAction(self.action_previous_image) self.menu_go.addAction(self.action_next_image) self.menu_go.addSeparator() self.menu_go.addAction(self.action_first_image) self.menu_go.addAction(self.action_last_image) # About self.menu_about = self.menubar.addMenu('About') self.menu_about.addAction(self.action_about) def create_toolbar(self): self.toolbar = self.addToolBar('Tool bar') self.toolbar.addAction(self.action_open) self.toolbar.addAction(self.action_save) self.toolbar.addSeparator() self.toolbar.addAction(self.action_fullscreen) self.toolbar.addAction(self.action_normal_size) self.toolbar.addAction(self.action_fit_screen) self.toolbar.addSeparator() self.toolbar.addAction(self.action_zoom_in) self.toolbar.addAction(self.action_zoom_out) self.toolbar.addSeparator() self.toolbar.addAction(self.action_rotate_left) self.toolbar.addAction(self.action_rotate_right) self.toolbar.addSeparator() self.toolbar.addAction(self.action_first_image) self.toolbar.addAction(self.action_previous_image) self.toolbar.addAction(self.action_next_image) self.toolbar.addAction(self.action_last_image) self.toolbar.addSeparator() self.toolbar.addAction(self.action_copy) self.toolbar.addAction(self.action_move) def load_settings(self): self.settings = QSettings() check_state = self.settings.value('view/image_gallery', True, type=bool) self.action_image_gallery.setChecked(check_state) self.image_gallery_triggered() def contextMenuEvent(self, QContextMenuEvent): menu = QMenu() menu.addAction(self.action_fullscreen) menu.addSeparator() menu.addAction(self.action_image_gallery) menu.addSeparator() menu.addAction(self.action_previous_image) menu.addAction(self.action_next_image) menu.addSeparator() menu.addAction(self.action_normal_size) menu.addAction(self.action_fit_screen) menu.addAction(self.action_fit_vertical) menu.addAction(self.action_fit_horizontal) menu.addSeparator() menu.addAction(self.action_zoom_in) menu.addAction(self.action_zoom_out) menu.addSeparator() menu.addAction(self.action_copy) menu.addAction(self.action_move) menu.addSeparator() menu.addAction(self.action_delete) menu.exec_(QContextMenuEvent.globalPos()) def eventFilter(self, obj, event): """ filter events for wheel events Args: obj (QWidget): scroll_area event (QEvent): event """ # try: if event.type() == QEvent.Wheel: if event.angleDelta().y() < 0: self.next_image() else: self.previous_image() return True elif event.type() == QEvent.MouseButtonPress and event.button( ) == Qt.RightButton: index = self.image_gallery.select_row_pos() if index > -1: self.index = index self.display_image() return True # pass the event on to the parent class return super(QMainWindow, self).eventFilter(obj, event) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Delete: self.delete() elif key == Qt.Key_Left: self.previous_image() elif key == Qt.Key_Right: self.next_image() elif key == Qt.Key_PageUp: self.first_image() elif key == Qt.Key_PageDown: self.last_image() elif key == Qt.Key_Escape and self.isFullScreen(): self.fullscreen() else: QWidget.keyPressEvent(self, event) def mouseDoubleClickEvent(self, QMouseEvent): self.fullscreen() def mousePressEvent(self, QMouseEvent): self.mouse_position = QMouseEvent.pos() def mouseMoveEvent(self, QMouseEvent): diff = QPoint(QMouseEvent.pos() - self.mouse_position) self.mouse_position = QMouseEvent.pos() self.scroll_area.verticalScrollBar().setValue( self.scroll_area.verticalScrollBar().value() - diff.y()) self.scroll_area.horizontalScrollBar().setValue( self.scroll_area.horizontalScrollBar().value() - diff.x()) def resizeEvent(self, event): if not self.index == -1: self.display_image() def create_images(self, filename): """Create image list Args: filename (string): file from which to retrieve the list of images in the folder """ self.images.clear() # get images only with an allowed extension for ext in self.extensions: self.images += glob.glob( os.path.join( glob.escape(os.path.dirname(filename)), '*.' + ''.join('[%s%s]' % (e.lower(), e.upper()) for e in ext))) self.images.sort() if filename in self.images: self.index = self.images.index(filename) else: self.index = -1 # iamge list self.image_gallery.add_images(self.images) def remove_index(self): """ remove file from list images and display next or previous image """ del self.images[self.index] self.image_gallery.remove_row(self.index) if len(self.images) == 0: self.images.clear() self.index = -1 self.image.clear() self.image.resize(self.image.minimumSizeHint()) elif self.index < len(self.images) - 1: self.display_image() else: self.index = len(self.images) - 1 self.display_image() def display_image(self): if not self.index == -1: self.image.clear() self.image.resize(self.image.minimumSizeHint()) file = self.images[self.index] if os.path.isfile(file): self.label_name.setText(file) self.label_numero.setText( str(self.index + 1) + ' / ' + str(len(self.images))) # image list self.image_gallery.select_row(self.index) image_reader = QImageReader(file) if image_reader.imageCount() > 1: # Animated image movie = QMovie(file) movie.setCacheMode(QMovie.CacheAll) movie.jumpToFrame(0) movie_size = movie.currentPixmap().size() self.image.setMovie(movie) self.image.resize(movie_size) movie.start() else: self.image.setPixmap(QPixmap(file)) self.image.resize(self.image.pixmap().size()) # fit image if self.action_fit_screen.isChecked(): self.fit_screen() elif self.action_fit_horizontal.isChecked(): self.fit_width() elif self.action_fit_vertical.isChecked(): self.fit_height() else: self.ratio = 1.0 self.action_zoom_in.setEnabled(True) self.action_zoom_out.setEnabled(True) # scrollbar position self.scroll_area.verticalScrollBar().setSliderPosition(0) self.scroll_area.horizontalScrollBar().setSliderPosition(0) def resize_image(self): if self.action_fit_screen.isChecked(): self.fit_screen() elif self.action_fit_horizontal.isChecked(): self.fit_width() elif self.action_fit_vertical.isChecked(): self.fit_height() elif self.image.pixmap(): self.image.resize(self.ratio * self.image.pixmap().size()) elif movie := self.image.movie(): movie.jumpToFrame(0) movie_size = movie.currentPixmap().size() self.image.resize(self.ratio * movie_size)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) #self.dbTableConnection = SqLite() self.mainWidget = QWidget() self.setWindowTitle("Video Game Scanner Inventory") self.left = 2500 self.top = 100 self.width = 1200 self.height = 900 self.setGeometry(self.left, self.top, self.width, self.height) self.mainWindowGrid = QGridLayout() self.barcode = Barcode() self.barcodeNumber = None # Stores info on a single game self.videoGameInfo = None # A List of video games self.videoGameInfoList = [] self.gameVariantDict = dict([]) self.variant = 'Standard' self.dbHandler = SqlHandler() # Barcode Number and Game Variant Buttons self.barcodeWindow = QWidget() self.setBackgroundColor(self.barcodeWindow, 'orange') self.barcodeListLayout = QGridLayout() self.barcodeScannerInput = BarscodeScannerInput() self.barcodeScannerInput.textChanged.connect(self.updateGamePriceTable) self.variantButtonList = [] self.variantButtons = VideoGameVariantButtons() self.standardBarcode = '' self.barcodeListLayout.addWidget(self.barcodeScannerInput, 0, 0) self.barcodeListLayout.addWidget(self.variantButtons, 1, 0) self.barcodeWindow.setLayout(self.barcodeListLayout) self.mainWindowGrid.addWidget(self.barcodeWindow, 0, 1, 2, 2) # Console Label - Unused (potentially for game logos) self.barcodeWindow2 = QWidget() self.setBackgroundColor(self.barcodeWindow2, 'blue') self.flashyBoxLayout = QGridLayout() self.barcodeWindow2.setLayout(self.flashyBoxLayout) self.mainWindowGrid.addWidget(self.barcodeWindow2, 0, 3, 2, 2) # Side Buttons self.barcodeWindow3 = QWidget() self.setBackgroundColor(self.barcodeWindow3, 'grey') self.mainWindowGrid.addWidget(self.barcodeWindow3, 0, 0, 4, 1) #------ Button List self.layout = QVBoxLayout() self.b1 = QPushButton("Process Table") self.layout.addWidget(self.b1) self.b1.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.b1.clicked.connect(self.processTableAndInfo) self.b2 = QPushButton("Button2") self.layout.addWidget(self.b2) self.b2.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.b3 = QPushButton("Button3") self.layout.addWidget(self.b3) self.b3.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.b4 = QPushButton("Button4") self.layout.addWidget(self.b4) self.b4.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.barcodeWindow3.setLayout(self.layout) self.barcodeWindow4 = QWidget() self.setBackgroundColor(self.barcodeWindow4, 'pink') self.mainWindowGrid.addWidget(self.barcodeWindow4, 2, 1, 2, 4) ################ Pricing and Table Widget #################################### self.gridLayout = QGridLayout() self.barcodeWindow4.setLayout(self.gridLayout) #------- unprocessed table values ----------------- self.gamePriceWidget = QWidget() self.setBackgroundColor(self.gamePriceWidget, 'blue') self.gamePriceTableLayout = QVBoxLayout() self.gamePriceTable = TableView() self.gamePriceTable.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.gamePriceTableLayout.addWidget(self.gamePriceTable) self.gamePriceWidget.setLayout(self.gamePriceTableLayout) self.gridLayout.addWidget(self.gamePriceWidget, 0, 0, 2, 6) #------- verify game info labels ------------------ self.gameInfoWidget = QWidget() self.setBackgroundColor(self.gameInfoWidget, 'indigo') self.gameInfoLayout = QGridLayout() self.gameInfoWidget.setLayout(self.gameInfoLayout) self.gridLayout.addWidget(self.gameInfoWidget, 3, 0, 1, 6) self.gameNameBox = QLineEdit() self.gameSystemBox = QLineEdit() self.gameUsedPrice = QLineEdit() self.gameCompletePrice = QLineEdit() self.gameNewPrice = QLineEdit() self.totalPriceLabel = QLabel() self.totalPriceLabel.setFont(QFont('Arial', 15)) self.setBackgroundColor(self.totalPriceLabel, 'beige') self.gameNameBox.setAlignment(QtCore.Qt.AlignCenter) self.gameSystemBox.setAlignment(QtCore.Qt.AlignCenter) self.gameUsedPrice.setAlignment(QtCore.Qt.AlignCenter) self.gameCompletePrice.setAlignment(QtCore.Qt.AlignCenter) self.gameNewPrice.setAlignment(QtCore.Qt.AlignCenter) self.gameInfoLayout.addWidget(self.gameNameBox, 0, 0, 1, 3) self.gameInfoLayout.addWidget(self.gameSystemBox, 1, 0, 1, 3) self.gameInfoLayout.addWidget(self.totalPriceLabel, 0, 4, 2, 2) self.gameInfoLayout.addWidget(self.gameUsedPrice, 2, 0, 1, 2) self.gameInfoLayout.addWidget(self.gameCompletePrice, 2, 2, 1, 2) self.gameInfoLayout.addWidget(self.gameNewPrice, 2, 4, 1, 2) #------------Button Box -------------------------------- self.horizontalGroupBox = QGroupBox("Which Price To Use?") self.buttonLayout = QGridLayout() self.horizontalGroupBox.setLayout(self.buttonLayout) self.gridLayout.addWidget(self.horizontalGroupBox, 6, 0, 1, 6) self.buttonLoose = QPushButton('Loose Price', self) self.buttonLoose.clicked.connect(self.on_click) self.buttonLayout.addWidget(self.buttonLoose, 0, 0, 1, 1) self.buttonComplete = QPushButton('Complete Price', self) self.buttonComplete.clicked.connect(self.on_click) self.buttonLayout.addWidget(self.buttonComplete, 0, 1, 1, 1) self.buttonNew = QPushButton('New Price', self) self.buttonNew.clicked.connect(self.on_click) self.buttonLayout.addWidget(self.buttonNew, 0, 2, 1, 1) self.label1 = QLabel('Alternative Bulk Price') self.label1.setAlignment(QtCore.Qt.AlignCenter) self.buttonLayout.addWidget(self.label1, 1, 1, 1, 1) self.label2 = QLabel('Alternative Single Price') self.label2.setAlignment(QtCore.Qt.AlignCenter) self.buttonLayout.addWidget(self.label2, 1, 2, 1, 1) self.buttonBadinfo = QPushButton('Bad Info', self) self.buttonLayout.addWidget(self.buttonBadinfo, 2, 0, 1, 1) self.bulkPrice = QLineEdit() self.buttonLayout.addWidget(self.bulkPrice, 2, 1, 1, 1) self.bulkPrice.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.bulkPrice.setAlignment(QtCore.Qt.AlignCenter) self.singleAltPrice = QLineEdit('') self.buttonLayout.addWidget(self.singleAltPrice, 2, 2, 1, 1) self.singleAltPrice.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) self.singleAltPrice.setAlignment(QtCore.Qt.AlignCenter) self.bulkPriceCheckbox = QCheckBox('Bulk Price Enabled') self.buttonLayout.addWidget(self.bulkPriceCheckbox, 1, 0) #------------------------------------------------------------- ################################################## self.mainWidget.setLayout(self.mainWindowGrid) self.setCentralWidget(self.mainWidget) self.show() def setBackgroundColor(self, widget, color): widget.setAutoFillBackground(True) palette = widget.palette() palette.setColor(QPalette.Window, QColor(color)) widget.setPalette(palette) def closeEvent(self, event): print('I Quit') self.dbHandler.conn.close() QApplication.quit() #When you click 'Loose Price','Complete Price','New Price' #This slot puts that info on the table @Slot() def on_click(self): mp.putGameInfoInTable(self, self.sender().text(), self.gamePriceTable, self.barcodeNumber) self.barcodeScannerInput.setFocus() #Once a barcod has star, this slot processes it @Slot(str) def updateGamePriceTable(self, barcode): if ('*' in barcode): self.barcodeNumber = barcode.strip('*') self.standardBarcode = self.barcodeNumber self.barcodeScannerInput.clear() print(barcode) mp.readBarcodesFromMain(self, self.barcodeNumber, self.barcodeNumber) # Clicking Process Table button - clears the table and enters is into the database @Slot() def processTableAndInfo(self): if (len(self.videoGameInfoList) < 1): return self.dbHandler.processMainWindowTable(self.videoGameInfoList, self.gamePriceTable) self.bulkPrice.clear() self.totalPriceLabel.clear() # Some games variants with different prices. When clicking on a button, this slot handles swapping prices @Slot() def variantClicked(self): self.variant = self.sender().text() variantURL = self.gameVariantDict[self.sender().text()] items = (self.variantButtons.gameVariantLayout.itemAt(i) for i in range(self.variantButtons.gameVariantLayout.count())) for btn in items: btn.widget().deleteLater() mp.readBarcodesFromMain(self, variantURL, self.standardBarcode) pass
class MainWidget(QWidget): data = None current_question = None current_correct_answer = None buttons_choices = None question_mode = None answer_mode = None correct_questions = 0 total_questions = 0 def __init__(self): super().__init__() # Create Widgets self.question = QLabel(Text="Press Start", Alignment=QtCore.Qt.AlignCenter, Font=QtGui.QFont("", 30)) self.label_correct = QLabel(Alignment=QtCore.Qt.AlignCenter) self.button_quit = QPushButton("Quit", Visible=False) self.button_start = QPushButton("Start") lcd_height = 40 self.lcd_total = QLCDNumber(SegmentStyle=QLCDNumber.Flat, FixedHeight=lcd_height) self.lcd_score = QLCDNumber(SegmentStyle=QLCDNumber.Flat, FixedHeight=lcd_height) self.stack = QStackedWidget() self.page1 = Page1Widget() self.page2 = Page2Widget() self.page3 = Page3Widget() self.stack.addWidget(self.page1) self.stack.addWidget(self.page2) self.stack.addWidget(self.page3) # Make Layout self.layout = QGridLayout(self) self.layout.addWidget(self.question, 2, 0, 1, 2) self.layout.addWidget(self.label_correct, 1, 0, 1, 2) self.layout.addWidget(self.lcd_total, 0, 0) self.layout.addWidget(self.lcd_score, 0, 1) self.layout.addWidget(self.stack, 3, 0, 1, 2) self.layout.addWidget(self.button_quit, 4, 0) self.layout.addWidget(self.button_start, 4, 1) # Connect Callbacks self.button_quit.clicked.connect(self.on_quit_pressed) self.button_start.clicked.connect(self.on_start_pressed) self.page2.ans_buttons[0].clicked.connect(self.on_ans_button0_clicked) self.page2.ans_buttons[1].clicked.connect(self.on_ans_button1_clicked) self.page2.ans_buttons[2].clicked.connect(self.on_ans_button2_clicked) self.page3.line_edit.returnPressed.connect(self.on_answer_given) def update_questions_from_selection(self): selected_groups = [] for i in range(self.page1.list_groups.count()): item = self.page1.list_groups.item(i) if item.checkState(): selected_groups.append(item.text()) if len(selected_groups) == 0: self.data = data else: self.data = data.loc[data['Group'].isin(selected_groups), :] def on_quit_pressed(self): self.button_start.setVisible(True) self.button_quit.setVisible(False) self.question.clear() self.stack.setCurrentIndex(0) def on_start_pressed(self): self.update_questions_from_selection() self.button_start.setVisible(False) self.button_quit.setVisible(True) self.total_questions = -1 self.correct_questions = 0 self.stack.setCurrentIndex(int(self.page1.spinbox_level.text())) self.question_mode = MODES[self.page1.dropdown_quest.currentIndex()] self.answer_mode = MODES[self.page1.dropdown_ans.currentIndex()] self.update_question() def update_score(self): self.lcd_total.display(self.total_questions) self.lcd_score.display(self.correct_questions) def get_also_string(self): also_mode = self.page1.dropdown_also.currentText() if also_mode == "----": return "" return self.current_question[also_mode] def update_question(self): self.total_questions += 1 self.update_score() picked_questions = self.data.sample(frac=1).iloc[:3] self.current_question = picked_questions.iloc[0] self.current_correct_answer = self.current_question[self.answer_mode] also_string = " " + self.get_also_string() self.question.setText(self.current_question[self.question_mode] + also_string) self.buttons_choices = list(picked_questions[self.answer_mode]) random.shuffle(self.buttons_choices) for button, answer in zip(self.page2.ans_buttons, self.buttons_choices): button.setText(answer) def correct_answer(self): self.correct_questions += 1 self.label_correct.setText("Correct!") self.label_correct.setStyleSheet("QLabel { background-color : green;}") def wrong_answer(self): wrong_string = (f"Wrong: {self.current_question[self.question_mode]} " f"= {self.current_correct_answer}") self.label_correct.setText(wrong_string) self.label_correct.setStyleSheet("QLabel { background-color : red;}") def on_answer_given(self): given_answer = self.page3.line_edit.text() if given_answer == self.current_correct_answer: self.correct_answer() else: self.wrong_answer() self.question.setText(self.page3.line_edit.text()) self.page3.line_edit.clear() self.update_question() def after_button_clicked(self, ans_id): if self.buttons_choices[ans_id] == self.current_correct_answer: self.correct_answer() else: self.wrong_answer() self.update_question() def on_ans_button0_clicked(self): self.after_button_clicked(0) def on_ans_button1_clicked(self): self.after_button_clicked(1) def on_ans_button2_clicked(self): self.after_button_clicked(2)