Add preferences

This commit is contained in:
Jeena 2025-06-05 12:37:55 +09:00
parent 3a341c2fe5
commit 4a6d85f7ea
9 changed files with 219 additions and 28 deletions

1
.gitignore vendored
View file

@ -11,3 +11,4 @@ dist/
src/recoder.egg-info/
*.gresource
*.compiled

14
dev-run.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/bash
# Compile resources
glib-compile-resources src/resources/resources.xml \
--target=src/recoder/resources.gresource \
--sourcedir=src/resources
# Compile GSettings schemas (using correct path)
glib-compile-schemas src/resources
# Run the app with environment variables
GSETTINGS_SCHEMA_DIR=$(pwd)/src/resources \
PYTHONPATH=$(pwd)/src \
python -m recoder.app

View file

@ -18,7 +18,7 @@ def load_resources():
def main():
load_resources()
from recoder.recoder_window import RecoderWindow # delayed import
from recoder.window import RecoderWindow # delayed import
class RecoderApp(Adw.Application):
def __init__(self):

View file

@ -0,0 +1,58 @@
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")
from gi.repository import Gtk, Gio, Adw
import re
@Gtk.Template(resource_path="/net/jeena/recoder/preferences.ui")
class RecoderPreferences(Adw.PreferencesWindow):
__gtype_name__ = "RecoderPreferences"
output_folder_entry = Gtk.Template.Child()
def __init__(self):
super().__init__()
self.settings = Gio.Settings.new("net.jeena.recoder.preferences")
current_value = self.settings.get_string("output-folder-template")
self.output_folder_entry.set_text(current_value)
self.output_folder_entry.connect("changed", self.on_output_folder_changed)
self.settings.connect("changed::output-folder-template", self.on_setting_changed)
def validate_template(self, text):
allowed_pattern = r'^[\w\s\-./~${}]+$'
if not re.match(allowed_pattern, text):
return False
if text.count("{{") != text.count("}}"):
return False
var_pattern = r'\{\{([a-zA-Z0-9_]+)\}\}'
for var in re.findall(r'\{\{.*?\}\}', text):
if not re.match(var_pattern, var):
return False
if '//' in text.replace('file://', ''):
return False
return True
def on_output_folder_changed(self, entry):
text = entry.get_text()
if self.validate_template(text):
self.settings.set_string("output-folder-template", text)
entry.remove_css_class("error")
else:
entry.add_css_class("error")
def on_setting_changed(self, settings, key):
if key == "output-folder-template":
new_val = settings.get_string(key)
if self.output_folder_entry.get_text() != new_val:
self.output_folder_entry.set_text(new_val)

View file

@ -14,9 +14,10 @@ from recoder.utils import extract_video_files, notify_done, play_complete_sound
from recoder.file_entry_row import FileEntryRow
from recoder.drop_handler import DropHandler
from recoder.app_state import AppState, AppStateManager, UIStateManager
from recoder.preferences import RecoderPreferences
@Gtk.Template(resource_path="/net/jeena/recoder/recoder_window.ui")
@Gtk.Template(resource_path="/net/jeena/recoder/window.ui")
class RecoderWindow(Adw.ApplicationWindow):
__gtype_name__ = "RecoderWindow"
@ -28,10 +29,19 @@ class RecoderWindow(Adw.ApplicationWindow):
btn_cancel = Gtk.Template.Child()
progress_bar = Gtk.Template.Child()
folder_label = Gtk.Template.Child()
btn_preferences = Gtk.Template.Child()
def __init__(self, application):
super().__init__(application=application)
self.state_settings = Gio.Settings.new("net.jeena.recoder.state")
# Bind window size and state to your window properties
self.state_settings.bind("width", self, "default-width", Gio.SettingsBindFlags.DEFAULT)
self.state_settings.bind("height", self, "default-height", Gio.SettingsBindFlags.DEFAULT)
self.state_settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
self.state_settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT)
self.file_items_to_process = []
self.current_folder_name = None
self.transcoder = None
@ -44,6 +54,7 @@ class RecoderWindow(Adw.ApplicationWindow):
self.btn_transcode.connect("clicked", self.on_transcode_clicked)
self.btn_cancel.connect("clicked", self.on_cancel_clicked)
self.btn_preferences.connect("clicked", self.on_preferences_clicked)
self.app_state_manager.state = AppState.IDLE
@ -57,10 +68,24 @@ class RecoderWindow(Adw.ApplicationWindow):
Notify.init("Recoder")
def process_drop_value(self, value):
self.preferences_window = None
# value could be a list of Gio.File or a single Gio.File
# Assuming it's a list:
def on_preferences_clicked(self, button):
if self.preferences_window is None:
self.preferences_window = RecoderPreferences()
self.preferences_window.set_transient_for(self)
self.preferences_window.set_modal(True)
self.preferences_window.connect("close-request", self.on_preferences_window_close)
self.preferences_window.present()
def on_preferences_window_close(self, window):
window.hide()
# Don't destroy, just hide
return True # stops further handlers, prevents default destruction
def process_drop_value(self, value):
folder_file = None
if isinstance(value, list) and len(value) > 0:
folder_file = value[0]
@ -68,7 +93,6 @@ class RecoderWindow(Adw.ApplicationWindow):
folder_file = value
if folder_file:
# Set the current folder name for UI
self.current_folder_name = folder_file.get_basename()
file_items = extract_video_files(value)
@ -107,8 +131,6 @@ class RecoderWindow(Adw.ApplicationWindow):
if not self.file_items_to_process:
return
# no need to remove drop_controller here
self.transcoder = Transcoder(self.file_items_to_process)
self.transcoder.connect("notify::batch-progress", self.on_transcoder_progress)
self.transcoder.connect("notify::batch-status", self.on_transcoder_status)

View file

@ -0,0 +1,36 @@
<schemalist>
<schema id="net.jeena.recoder.preferences" path="/net/jeena/recoder/preferences/" gettext-domain="recoder">
<key name="output-folder-template" type="s">
<default>'{{source_folder_name}}-transcoded'</default>
<summary>Template for output folder</summary>
<description>
Relative or absolute path template for transcoded files.
Supports {{source_folder_name}} as a variable.
</description>
</key>
</schema>
<schema id="net.jeena.recoder.state" path="/net/jeena/recoder/state/" gettext-domain="recoder">
<key name="width" type="i">
<default>600</default>
<summary>Window width</summary>
<description>Last saved width of the main window.</description>
</key>
<key name="height" type="i">
<default>350</default>
<summary>Window height</summary>
<description>Last saved height of the main window.</description>
</key>
<key name="is-maximized" type="b">
<default>false</default>
<summary>Window maximized state</summary>
<description>Whether the main window was maximized last time.</description>
</key>
<key name="is-fullscreen" type="b">
<default>false</default>
<summary>Window fullscreen state</summary>
<description>Whether the main window was fullscreen last time.</description>
</key>
</schema>
</schemalist>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="RecoderPreferences" parent="AdwPreferencesWindow">
<property name="title">Preferences</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="AdwPreferencesPage">
<property name="title">General</property>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="GtkLabel" id="description_label">
<property name="wrap">True</property>
<property name="justify">left</property>
<property name="margin-top">6</property>
<property name="margin-bottom">12</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="use-markup">True</property>
<property name="selectable">True</property>
<property name="label">
<![CDATA[
You can use:
• <tt>{{source_folder_name}}</tt> to reuse the original folder name
• Relative paths like <tt>../done/</tt>
• Absolute paths like <tt>/mnt/Export/</tt>
• <tt>~</tt> to refer to your home directory
• Simple names like <tt>output</tt> to create the folder inside the source folder
• Any combination of the above, e.g. <tt>../{{source_folder_name}}-dnxhd</tt>
]]>
</property>
</object>
</child>
<child>
<object class="AdwEntryRow" id="output_folder_entry">
<property name="hexpand">True</property>
<property name="title">Output Folder Template</property>
<property name="tooltip-text">Set output folder template</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -1,6 +1,7 @@
<gresources>
<gresource prefix="/net/jeena/recoder">
<file>recoder_window.ui</file>
<file>window.ui</file>
<file>preferences.ui</file>
<file>file_entry_row.ui</file>
<file>style.css</file>
</gresource>

View file

@ -12,25 +12,31 @@
<object class="AdwToolbarView" id="toolbar_view">
<child type="top">
<object class="AdwHeaderBar" id="header_bar">
<child>
<object class="GtkButton" id="btn_transcode">
<property name="label">Transcode</property>
<property name="sensitive">False</property>
</object>
</child>
<child type="title">
<object class="GtkLabel" id="folder_label">
<property name="ellipsize">end</property>
<property name="hexpand">True</property>
<property name="halign">center</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="btn_cancel">
<property name="label">Cancel</property>
<property name="visible">False</property>
</object>
</child>
<child>
<object class="GtkButton" id="btn_transcode">
<property name="label">Transcode</property>
<property name="sensitive">False</property>
</object>
</child>
<child type="title">
<object class="GtkLabel" id="folder_label">
<property name="ellipsize">end</property>
<property name="hexpand">True</property>
<property name="halign">center</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="btn_preferences">
<property name="icon-name">emblem-system-symbolic</property>
<property name="tooltip-text">Preferences</property>
</object>
</child>
<child type="end">
<object class="GtkButton" id="btn_cancel">
<property name="label">Cancel</property>
<property name="visible">False</property>
</object>
</child>
</object>
</child>
@ -75,6 +81,7 @@
</child>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar">
<property name="hexpand">true</property>