From 03e670ebd28f3d0f511e5e7b5e490840b09fe181 Mon Sep 17 00:00:00 2001 From: Jeena Date: Tue, 3 Jun 2025 21:21:28 +0900 Subject: [PATCH] Seperate ui code --- src/recoder/app.py | 11 +- src/recoder/file_entry_row.py | 48 +++++ src/recoder/recoder_window.py | 118 +++++++++++++ src/recoder/ui.py | 165 ------------------ src/resources/file_entry_row.ui | 62 +++++++ .../{recoder.ui => recoder_window.ui} | 23 ++- src/resources/resources.xml | 3 +- src/resources/style.css | 4 - 8 files changed, 253 insertions(+), 181 deletions(-) create mode 100644 src/recoder/file_entry_row.py create mode 100644 src/recoder/recoder_window.py delete mode 100644 src/recoder/ui.py create mode 100644 src/resources/file_entry_row.ui rename src/resources/{recoder.ui => recoder_window.ui} (85%) diff --git a/src/recoder/app.py b/src/recoder/app.py index 557332a..a1bc48a 100755 --- a/src/recoder/app.py +++ b/src/recoder/app.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import sys -import os import gi gi.require_version('Gtk', '4.0') @@ -19,18 +18,20 @@ def load_resources(): def main(): load_resources() - from recoder.ui import RecoderWindow # delayed import + from recoder.recoder_window import RecoderWindow # delayed import class RecoderApp(Adw.Application): def __init__(self): - super().__init__(application_id="net.jeena.Recoder", - flags=Gio.ApplicationFlags.FLAGS_NONE) + super().__init__( + application_id="net.jeena.Recoder", + flags=Gio.ApplicationFlags.FLAGS_NONE + ) self.window = None def do_activate(self): if not self.window: self.window = RecoderWindow(self) - self.window.window.present() + self.window.present() app = RecoderApp() return app.run(sys.argv) diff --git a/src/recoder/file_entry_row.py b/src/recoder/file_entry_row.py new file mode 100644 index 0000000..91267e2 --- /dev/null +++ b/src/recoder/file_entry_row.py @@ -0,0 +1,48 @@ +import gi +gi.require_version("Gtk", "4.0") +from gi.repository import Gtk + +from recoder.models import FileStatus + +ICONS = { + FileStatus.WAITING: "media-playback-pause-symbolic", + FileStatus.PROCESSING: "view-refresh-symbolic", + FileStatus.DONE: "task-complete-symbolic", + FileStatus.ERROR: "dialog-error-symbolic", +} + +LABELS = { + FileStatus.WAITING: "Waiting", + FileStatus.DONE: "Done", + FileStatus.ERROR: "Error", +} + +@Gtk.Template(resource_path="/net/jeena/recoder/file_entry_row.ui") +class FileEntryRow(Gtk.ListBoxRow): + __gtype_name__ = "FileEntryRow" + + icon = Gtk.Template.Child() + label = Gtk.Template.Child() + progress_label = Gtk.Template.Child() + level_bar = Gtk.Template.Child() + + def __init__(self, item): + super().__init__() + self.item = item + self.item.connect("notify::status", self.update_display) + self.item.connect("notify::progress", self.update_display) + self.update_display() + + def update_display(self, *args): + basename = self.item.file.get_basename() + self.label.set_text(basename) + + icon_name = ICONS.get(self.item.status, "object-select-symbolic") + self.icon.set_from_icon_name(icon_name) + + if self.item.status == FileStatus.PROCESSING: + self.progress_label.set_text(f"{self.item.progress}%") + self.level_bar.set_value(self.item.progress) + else: + self.progress_label.set_text(LABELS.get(self.item.status, "")) + self.level_bar.set_value(100 if self.item.status == FileStatus.DONE else 0) diff --git a/src/recoder/recoder_window.py b/src/recoder/recoder_window.py new file mode 100644 index 0000000..4dffa69 --- /dev/null +++ b/src/recoder/recoder_window.py @@ -0,0 +1,118 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +gi.require_version('Notify', '0.7') + +from gi.repository import Gtk, Gdk, Gio, Adw, GLib, Notify +from functools import partial + +from recoder.transcoder import Transcoder +from recoder.utils import extract_video_files, notify_done, play_complete_sound +from recoder.file_entry_row import FileEntryRow + +@Gtk.Template(resource_path="/net/jeena/recoder/recoder_window.ui") +class RecoderWindow(Adw.ApplicationWindow): + __gtype_name__ = "RecoderWindow" + + overlay = Gtk.Template.Child() + progress_bar = Gtk.Template.Child() + drop_hint = Gtk.Template.Child() + listbox = Gtk.Template.Child() + scrolled_window = Gtk.Template.Child() + + def __init__(self, application): + super().__init__(application=application) + + self.file_items_to_process = [] + self.transcoder = None + self.file_rows = {} + + self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) + self.drop_target.connect("drop", self.on_drop) + self.drop_target.connect("enter", self.on_drop_enter) + self.drop_target.connect("leave", self.on_drop_leave) + self.add_controller(self.drop_target) + + css_provider = Gtk.CssProvider() + css_provider.load_from_resource("/net/jeena/recoder/style.css") + Gtk.StyleContext.add_provider_for_display( + Gdk.Display.get_default(), + css_provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + + Notify.init("Recoder") + + def on_drop_enter(self, drop_target, x, y): + self.overlay.add_css_class("drop-highlight") + return True + + def on_drop_leave(self, drop_target): + self.overlay.remove_css_class("drop-highlight") + return True + + def on_drop(self, drop_target, value, x, y): + GLib.idle_add(partial(self.process_drop_value, value)) + self.overlay.remove_overlay(self.drop_hint) + self.progress_bar.set_visible(True) + self.progress_bar.set_fraction(0.0) + self.drop_hint.set_visible(False) + return value + + def process_drop_value(self, value): + file_items = extract_video_files(value) + if not file_items: + return False + + self.clear_listbox() + self.file_rows.clear() + + for file_item in file_items: + row = FileEntryRow(file_item) + self.listbox.append(row) + self.file_rows[file_item] = row + + self.file_items_to_process = file_items + self.start_transcoding() + return False + + def clear_listbox(self): + child = self.listbox.get_first_child() + while child: + next_child = child.get_next_sibling() + self.listbox.remove(child) + child = next_child + + def start_transcoding(self): + if self.transcoder and self.transcoder.is_processing: + return + + self.remove_controller(self.drop_target) + + self.progress_bar.set_fraction(0.0) + self.progress_bar.set_text("Starting transcoding...") + + self.transcoder = Transcoder( + self.file_items_to_process, + progress_callback=self.update_progress, + done_callback=self._done_callback, + file_done_callback=self.mark_file_done, + ) + self.transcoder.start() + + def update_progress(self, text, fraction): + self.progress_bar.set_show_text(True) + self.progress_bar.set_text(text) + self.progress_bar.set_fraction(fraction) + + def mark_file_done(self, filepath): + if filepath in self.file_rows: + row = self.file_rows[filepath] + GLib.idle_add(row.mark_done) + + def _done_callback(self): + self.progress_bar.set_show_text(True) + self.progress_bar.set_text("Transcoding Complete!") + play_complete_sound() + notify_done("Recoder", "Transcoding finished!") + self.add_controller(self.drop_target) diff --git a/src/recoder/ui.py b/src/recoder/ui.py deleted file mode 100644 index d566844..0000000 --- a/src/recoder/ui.py +++ /dev/null @@ -1,165 +0,0 @@ -import os -import gi -gi.require_version('Gtk', '4.0') -gi.require_version('Notify', '0.7') - -from gi.repository import Gtk, Gdk, Gio, GLib, Notify -from functools import partial - -from recoder.transcoder import Transcoder -from recoder.utils import extract_video_files, notify_done, play_complete_sound -from recoder.models import FileStatus, FileItem - -class FileEntryRow(Gtk.ListBoxRow): - def __init__(self, item): - super().__init__() - self.item = item - - self.icon = Gtk.Image.new_from_icon_name("object-select-symbolic") - self.label = Gtk.Label(xalign=0, hexpand=True) - self.progress_label = Gtk.Label(xalign=1) - - hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) - hbox.append(self.icon) - hbox.append(self.label) - hbox.append(self.progress_label) - self.set_child(hbox) - - self.item.connect("notify::status", self.on_status_changed) - self.item.connect("notify::progress", self.on_progress_changed) - - self.update_display() - - def on_status_changed(self, *_): - self.update_display() - - def on_progress_changed(self, *_): - self.update_display() - - def update_display(self): - basename = self.item.file.get_basename() - self.label.set_text(basename) - - match self.item.status: - case FileStatus.WAITING: - self.icon.set_from_icon_name("media-playback-pause-symbolic") - self.progress_label.set_text("Waiting") - case FileStatus.PROCESSING: - self.icon.set_from_icon_name("view-refresh-symbolic") - self.progress_label.set_text(f"{self.item.progress}%") - case FileStatus.DONE: - self.icon.set_from_icon_name("task-complete-symbolic") - self.progress_label.set_text("Done") - case FileStatus.ERROR: - self.icon.set_from_icon_name("dialog-error-symbolic") - self.progress_label.set_text("Error") - - -class RecoderWindow: - def __init__(self, application, **kwargs): - self.file_items_to_process = [] - self.transcoder = None - self.file_rows = {} - - builder = Gtk.Builder() - builder.add_from_resource("/net/jeena/recoder/recoder.ui") - - self.window = builder.get_object("main_window") - self.window.set_application(application) - - self.overlay = builder.get_object("overlay") - self.progress = builder.get_object("progress_bar") - self.drop_hint = builder.get_object("drop_hint") - self.listbox = builder.get_object("listbox") - self.scrolled_window = builder.get_object("scrolled_window") - - self.drop_target = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY) - self.drop_target.connect("drop", self.on_drop) - self.drop_target.connect("enter", self.on_drop_enter) - self.drop_target.connect("leave", self.on_drop_leave) - self.window.add_controller(self.drop_target) - - css_provider = Gtk.CssProvider() - css_provider.load_from_resource("/net/jeena/recoder/style.css") - Gtk.StyleContext.add_provider_for_display( - Gdk.Display.get_default(), - css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, - ) - - Notify.init("Recoder") - - def on_drop_enter(self, drop_target, x, y): - self.overlay.add_css_class("drop-highlight") - return True - - def on_drop_leave(self, drop_target): - self.overlay.remove_css_class("drop-highlight") - return True - - def on_drop(self, drop_target, value, x, y): - GLib.idle_add(partial(self.process_drop_value, value)) - self.overlay.remove_overlay(self.drop_hint) - self.progress.set_visible(True) - self.progress.set_fraction(0.0) - self.drop_hint.set_visible(False) - return value - - def process_drop_value(self, value): - file_items = extract_video_files(value) - if not file_items: - return False - - # Clear previous rows - self.clear_listbox() - self.file_rows.clear() - - for file_item in file_items: - row = FileEntryRow(file_item) - self.listbox.append(row) - self.file_rows[file_item] = row - - self.file_items_to_process = file_items - self.start_transcoding() - return False - - def clear_listbox(self): - child = self.listbox.get_first_child() - while child: - next_child = child.get_next_sibling() - self.listbox.remove(child) - child = next_child - - def start_transcoding(self): - if self.transcoder and self.transcoder.is_processing: - return - - self.window.remove_controller(self.drop_target) - - self.progress.set_fraction(0.0) - self.progress.set_text("Starting transcoding...") - - self.transcoder = Transcoder( - self.file_items_to_process, - progress_callback=self.update_progress, - done_callback=self._done_callback, - file_done_callback=self.mark_file_done, - ) - self.transcoder.start() - - def update_progress(self, text, fraction): - self.progress.set_show_text(True) - self.progress.set_text(text) - self.progress.set_fraction(fraction) - - def mark_file_done(self, filepath): - if filepath in self.file_rows: - row = self.file_rows[filepath] - GLib.idle_add(row.mark_done) - - def _done_callback(self): - self.progress.set_show_text(True) - self.progress.set_text("Transcoding Complete!") - play_complete_sound() - notify_done("Recoder", "Transcoding finished!") - self.window.add_controller(self.drop_target) diff --git a/src/resources/file_entry_row.ui b/src/resources/file_entry_row.ui new file mode 100644 index 0000000..fbdd5c8 --- /dev/null +++ b/src/resources/file_entry_row.ui @@ -0,0 +1,62 @@ + + + + diff --git a/src/resources/recoder.ui b/src/resources/recoder_window.ui similarity index 85% rename from src/resources/recoder.ui rename to src/resources/recoder_window.ui index c54c8ec..d84dee0 100644 --- a/src/resources/recoder.ui +++ b/src/resources/recoder_window.ui @@ -3,38 +3,48 @@ - + diff --git a/src/resources/resources.xml b/src/resources/resources.xml index 40892f6..973c271 100644 --- a/src/resources/resources.xml +++ b/src/resources/resources.xml @@ -1,6 +1,7 @@ - recoder.ui + recoder_window.ui + file_entry_row.ui style.css diff --git a/src/resources/style.css b/src/resources/style.css index d6d1f4a..5d995f3 100644 --- a/src/resources/style.css +++ b/src/resources/style.css @@ -7,7 +7,3 @@ font-size: 16px; color: @theme_unfocused_fg_color; } - -.debug-row { - background: red; -} \ No newline at end of file