diff --git a/corrscope/cli.py b/corrscope/cli.py index 1428801..e188094 100644 --- a/corrscope/cli.py +++ b/corrscope/cli.py @@ -115,9 +115,8 @@ def main( show_gui = not any([write, play, render]) - # Create cfg: Config object. - cfg: Optional[Config] = None - cfg_path: Optional[Path] = None + # Gather data for cfg: Config object. + cfg_or_path: Union[Config, Path, None] = None cfg_dir: Optional[str] = None wav_list: List[Path] = [] @@ -144,8 +143,7 @@ def main( if len(files) > 1: raise click.ClickException( f'Cannot supply multiple arguments when providing config {path}') - cfg = yaml.load(path) - cfg_path = path + cfg_or_path = path cfg_dir = str(path.parent) break @@ -159,11 +157,11 @@ def main( f'Supplied nonexistent file or wildcard: {path}') wav_list += matches - if not cfg: + if not cfg_or_path: # cfg and cfg_dir are always initialized together. channels = [ChannelConfig(str(wav_path)) for wav_path in wav_list] - cfg = default_config( + cfg_or_path = default_config( master_audio=audio, # fps=default, channels=channels, @@ -172,10 +170,11 @@ def main( ) cfg_dir = '.' + assert cfg_or_path is not None if show_gui: def command(): from corrscope import gui - return gui.gui_main(cfg, cfg_path) + return gui.gui_main(cfg_or_path) if profile: import cProfile @@ -189,6 +188,16 @@ def main( else: if not files: raise click.UsageError('Must specify files or folders to play') + + if isinstance(cfg_or_path, Config): + cfg = cfg_or_path + cfg_path = None + elif isinstance(cfg_or_path, Path): + cfg = yaml.load(cfg_or_path) + cfg_path = cfg_or_path + else: + assert False, cfg_or_path + if write: write_path = get_path(audio, YAML_NAME) yaml.dump(cfg, write_path) diff --git a/corrscope/gui/__init__.py b/corrscope/gui/__init__.py index fb30232..477f189 100644 --- a/corrscope/gui/__init__.py +++ b/corrscope/gui/__init__.py @@ -48,8 +48,7 @@ def res(file: str) -> str: return str(APP_DIR / file) -def gui_main(cfg: Config, cfg_path: Optional[Path]): - # TODO read config within MainWindow, and show popup if loading fails. +def gui_main(cfg_or_path: Union[Config, Path]): # qw.QApplication.setStyle('fusion') QApp = qw.QApplication QApp.setAttribute(qc.Qt.AA_EnableHighDpiScaling) @@ -63,7 +62,7 @@ def gui_main(cfg: Config, cfg_path: Optional[Path]): QApp.setFont(font) app = qw.QApplication(sys.argv) - window = MainWindow(cfg, cfg_path) + window = MainWindow(cfg_or_path) sys.exit(app.exec_()) @@ -72,14 +71,15 @@ class MainWindow(qw.QMainWindow): Main window. Control flow: - __init__ - load_cfg + __init__: either + - load_cfg + - load_cfg_from_path - # Opening a document - load_cfg + Opening a document: + - load_cfg_from_path """ - def __init__(self, cfg: Config, cfg_path: Optional[Path]): + def __init__(self, cfg_or_path: Union[Config, Path]): super().__init__() # Load UI. @@ -109,7 +109,14 @@ class MainWindow(qw.QMainWindow): self.corr_thread: Optional[CorrThread] = None # Bind config to UI. - self.load_cfg(cfg, cfg_path) + if isinstance(cfg_or_path, Config): + self.load_cfg(cfg_or_path, None) + elif isinstance(cfg_or_path, Path): + self.load_cfg_from_path(cfg_or_path) + else: + raise TypeError( + f"argument cfg={cfg_or_path} has invalid type {obj_name(cfg_or_path)}" + ) self.show() @@ -133,37 +140,6 @@ class MainWindow(qw.QMainWindow): channel_view: "ChannelTableView" channelsGroup: qw.QGroupBox - def closeEvent(self, event: QCloseEvent) -> None: - """Called on closing window.""" - if self.prompt_save(): - event.accept() - else: - event.ignore() - - def on_action_new(self): - if not self.prompt_save(): - return - cfg = default_config() - self.load_cfg(cfg, None) - - def on_action_open(self): - if not self.prompt_save(): - return - name, file_type = qw.QFileDialog.getOpenFileName( - self, "Open config", self.cfg_dir, "YAML files (*.yaml)" - ) - if name != "": - cfg_path = Path(name) - try: - # Raises YAML structural exceptions - cfg = yaml.load(cfg_path) - # Raises color getter exceptions - # ISSUE: catching an exception will leave UI in undefined state? - self.load_cfg(cfg, cfg_path) - except Exception as e: - qw.QMessageBox.critical(self, "Error loading file", str(e)) - return - def prompt_save(self) -> bool: """ Called when user is closing document @@ -188,6 +164,45 @@ class MainWindow(qw.QMainWindow): else: return self.on_action_save() + def closeEvent(self, event: QCloseEvent) -> None: + """Called on closing window.""" + if self.prompt_save(): + event.accept() + else: + event.ignore() + + def on_action_new(self): + if not self.prompt_save(): + return + cfg = default_config() + self.load_cfg(cfg, None) + + def on_action_open(self): + if not self.prompt_save(): + return + name, file_type = qw.QFileDialog.getOpenFileName( + self, "Open config", self.cfg_dir, "YAML files (*.yaml)" + ) + if name != "": + cfg_path = Path(name) + self.load_cfg_from_path(cfg_path) + + def load_cfg_from_path(self, cfg_path: Path): + # Bind GUI to dummy config, in case loading cfg_path raises Exception. + if self.model is None: + self.load_cfg(default_config(), None) + + try: + # Raises YAML structural exceptions + cfg = yaml.load(cfg_path) + + # Raises color getter exceptions + self.load_cfg(cfg, cfg_path) + + except Exception as e: + qw.QMessageBox.critical(self, "Error loading file", str(e)) + return + def load_cfg(self, cfg: Config, cfg_path: Optional[Path]): self._cfg_path = cfg_path self._any_unsaved = False