Add preferences
This commit is contained in:
parent
3a341c2fe5
commit
4a6d85f7ea
9 changed files with 219 additions and 28 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ dist/
|
|||
src/recoder.egg-info/
|
||||
|
||||
*.gresource
|
||||
*.compiled
|
||||
|
|
14
dev-run.sh
Executable file
14
dev-run.sh
Executable 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
|
|
@ -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):
|
||||
|
|
58
src/recoder/preferences.py
Normal file
58
src/recoder/preferences.py
Normal 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)
|
|
@ -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)
|
36
src/resources/net.jeena.recoder.gschema.xml
Normal file
36
src/resources/net.jeena.recoder.gschema.xml
Normal 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>
|
||||
|
52
src/resources/preferences.ui
Normal file
52
src/resources/preferences.ui
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -25,6 +25,12 @@
|
|||
<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>
|
||||
|
@ -75,6 +81,7 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<object class="GtkProgressBar" id="progress_bar">
|
||||
<property name="hexpand">true</property>
|
Loading…
Add table
Add a link
Reference in a new issue