Remember most recently selected filetype in Render dialog

Some users prefer to always render their files as .mkv or another
filetype. This commit changes corrscope's render dialog to remember the
most recently selected filetype, and default to it on next render.
pull/493/head
nyanpasu64 2024-12-17 11:03:36 -08:00
rodzic 6243148685
commit 62b8053b96
5 zmienionych plików z 118 dodań i 57 usunięć

Wyświetl plik

@ -4,6 +4,7 @@
- Implement split stereo bar colors (#491)
- Add "Reload Font List" menu item to fix missing fonts (#492)
- Remember most recently selected filetype in Render dialog (#493)
### Major Changes

Wyświetl plik

@ -36,9 +36,6 @@ YAML_EXTS = [".yaml"]
# Default extension when writing Config.
YAML_NAME = YAML_EXTS[0]
# Default output extension, only used in GUI and unit tests
VIDEO_NAME = ".mp4"
DEFAULT_NAME = corrscope.app_name

Wyświetl plik

@ -566,17 +566,20 @@ class MainWindow(qw.QMainWindow, Ui_MainWindow):
cfg_filename = self.get_save_filename(cli.YAML_NAME)
# Folder is obtained from self.pref.file_dir_ref.
filters = ["YAML files (*.yaml)", "All files (*)"]
path = get_save_file_path(
extensions = {
".yaml": "YAML files (*.yaml)",
None: "All files (*)",
}
save_name = get_save_file_path(
self,
"Save As",
self.pref.file_dir_ref,
cfg_filename,
filters,
extensions,
cli.YAML_NAME,
)
if path:
self._cfg_path = path
if save_name:
self._cfg_path = Path(save_name.name)
self.load_title()
self.on_action_save()
return True
@ -618,30 +621,34 @@ class MainWindow(qw.QMainWindow, Ui_MainWindow):
return
# Name and extension (no folder).
video_filename = self.get_save_filename(cli.VIDEO_NAME)
filters = [
"MP4 files (*.mp4)",
"Matroska files (*.mkv)",
"WebM files (*.webm)",
"All files (*)",
]
video_stem = cli.get_file_stem(self._cfg_path, self.cfg, default="")
# Points to either `file_dir` or `render_dir`.
# Folder is obtained from `dir_ref`.
dir_ref = self.pref.render_dir_ref
path = get_save_file_path(
self, "Render to Video", dir_ref, video_filename, filters, cli.VIDEO_NAME
suffix = self.pref.render_ext
extensions = {
".mp4": "MP4 files (*.mp4)",
".mkv": "Matroska files (*.mkv)",
".webm": "WebM files (*.webm)",
None: "All files (*)",
}
assert gp.VIDEO_NAME in extensions
save_name = get_save_file_path(
self, "Render to Video", dir_ref, video_stem, extensions, suffix
)
if path:
name = str(path)
if save_name:
self.pref.render_ext = save_name.suffix
dlg = CorrProgressDialog(self, "Rendering video")
# FFmpegOutputConfig contains only hashable/immutable strs,
# so get_ffmpeg_cfg() can be shared across threads without copying.
# Optionally copy_config() first.
outputs = [self.cfg.get_ffmpeg_cfg(name)]
outputs = [self.cfg.get_ffmpeg_cfg(save_name.name)]
self.play_thread(outputs, PreviewOrRender.render, dlg)
def play_thread(

Wyświetl plik

@ -11,21 +11,17 @@ Name = str
Filter = str
@attr.dataclass
class FileName:
file_name: Optional[str]
file_list: Optional[List[str]]
sel_filter: str
T = TypeVar("T")
def _get_hist_name(
func: Callable[..., Tuple[str, str]],
func: Callable[..., Tuple[T, str]],
parent: qw.QWidget,
title: str,
history_dir: _gp.Ref[_gp.GlobalPrefs],
default_name: Optional[str],
filters: List[str],
) -> Optional[FileName]:
) -> Optional[T]:
"""
Get file name.
Default folder is history folder, and `default_name`.folder is discarded.
@ -48,14 +44,12 @@ def _get_hist_name(
if isinstance(name, list):
assert func == qw.QFileDialog.getOpenFileNames
dir = os.path.dirname(name[0])
history_dir.set(dir)
return FileName(None, name, sel_filter)
else:
assert isinstance(name, str)
dir = os.path.dirname(name)
history_dir.set(dir)
return FileName(name, None, sel_filter)
history_dir.set(dir)
return name
def get_open_file_name(
@ -68,8 +62,8 @@ def get_open_file_name(
qw.QFileDialog.getOpenFileName, parent, title, history_dir, None, filters
)
if name:
assert name.file_name is not None
return name.file_name
assert isinstance(name, str)
return name
return None
@ -83,35 +77,91 @@ def get_open_file_list(
qw.QFileDialog.getOpenFileNames, parent, title, history_dir, None, filters
)
if name:
assert name.file_list is not None
return name.file_list
assert isinstance(name, list)
return name
return None
# Returns Path for legacy reasons. Others return str.
@attr.dataclass
class KeyFilter:
key: Optional[str]
filter: str
@attr.dataclass
class SaveName:
name: str
# Used to key into get_save_file_path(filters).
suffix: Optional[str]
def get_save_file_path(
parent: qw.QWidget,
title: str,
history_dir: _gp.Ref[_gp.GlobalPrefs],
default_name: str,
filters: List[str],
default_suffix: str,
) -> Optional[Path]:
name = _get_hist_name(
qw.QFileDialog.getSaveFileName,
parent,
title,
history_dir,
default_name,
filters,
initial_stem: str,
filters: Dict[Optional[str], str],
suffix: Optional[str],
) -> Optional[SaveName]:
"""
Given a working directory and initial filename, request and return a filename to
save a file.
This function takes multiple filetypes, an initial filetype (by string
extension), and additionally returns the filetype the user selected.
"""
init_filter = filters.get(suffix, None)
# If unsupported save extension (newer version?), use default extension.
if not init_filter:
suffix, init_filter = next(iter(filters.items()))
# Get initial directory for the dialog.
init_path: str = history_dir.get()
# Get initial filename if present.
if initial_stem:
init_path = os.path.join(init_path, os.path.basename(initial_stem))
# Append file extension.
if suffix:
init_path += suffix
# Get filename from dialog.
filter_str = ";;".join(filters.values())
out_name, sel_filter = qw.QFileDialog.getSaveFileName(
parent, title, init_path, filter_str, init_filter
)
if name:
assert name.file_name is not None
path = Path(name.file_name)
if name.sel_filter == filters[0] and path.suffix == "":
path = path.with_suffix(default_suffix)
return path
else:
if not out_name:
return None
for suffix, filter in filters.items():
if filter == sel_filter:
out_suffix = suffix
break
else:
# getSaveFileName() will always return a filter from the list (with whitespace collapsed,
# https://github.com/qt/qtbase/blob/ec011141b8d17a2edc58e0d5b6ebb0f1632fff90/src/widgets/dialogs/qfiledialog.cpp#L1415).
# This block is only reachable if the returned filter is not in `filter_str` passed in.
# This indicates a bug in the caller (incorrect whitespace in filters.values()).
raise RuntimeError(
f"unrecognized file filter {repr(sel_filter)} in {list(filters.values())}"
)
user_suffix = Path(out_name).suffix
# If user explicitly types a different extension, use it next time (even if they
# don't change the dropdown). This works on Windows and FIXME Linux? Mac?
# FIXME it seems confusing to pick a different default extension if you type but don't dropdown? IDK.
if user_suffix and user_suffix in filters:
out_suffix = user_suffix
# Append suffix if missing in user-specified name but present in dialog.
# getSaveFileName() does not automatically append a suffix on Linux KDE.
if user_suffix == "" and out_suffix:
out_name += out_suffix
# Update recently used dir.
dir = os.path.dirname(out_name)
history_dir.set(dir)
return SaveName(out_name, out_suffix)

Wyświetl plik

@ -6,6 +6,9 @@ from atomicwrites import atomic_write
from corrscope.config import DumpableAttrs, yaml
from corrscope.settings import paths
# Default output extension.
VIDEO_NAME = ".mp4"
Attrs = TypeVar("Attrs", bound=DumpableAttrs)
@ -40,6 +43,9 @@ class GlobalPrefs(DumpableAttrs, always_dump="*"):
separate_render_dir: bool = False
render_dir: str = "" # Set to "" whenever separate_render_dir=False.
# Most recent video filetype, including period.
render_ext: Optional[str] = VIDEO_NAME
@property
def render_dir_ref(self) -> "Ref[GlobalPrefs]":
if self.separate_render_dir: