Seperate ui code

This commit is contained in:
Jeena 2025-06-03 21:21:28 +09:00
parent 68a6f00fc7
commit 03e670ebd2
8 changed files with 253 additions and 181 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="FileEntryRow" parent="GtkListBoxRow">
<child>
<object class="GtkBox" id="outer_box">
<property name="orientation">vertical</property>
<property name="spacing">0</property>
<property name="margin-bottom">4</property>
<child>
<object class="GtkBox" id="row_content">
<property name="orientation">horizontal</property>
<property name="spacing">12</property>
<property name="margin-top">6</property>
<property name="margin-bottom">0</property>
<child>
<object class="GtkImage" id="icon">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="valign">center</property>
<property name="pixel-size">16</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label">
<property name="xalign">0</property>
<property name="hexpand">true</property>
<property name="ellipsize">end</property>
<property name="max-width-chars">40</property>
<style>
<class name="title"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="progress_label">
<property name="xalign">1</property>
<property name="valign">center</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLevelBar" id="level_bar">
<property name="hexpand">true</property>
<property name="valign">center</property>
<property name="orientation">horizontal</property>
<property name="sensitive">false</property>
<property name="min-value">0</property>
<property name="max-value">100</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
</object>
</child>
</object>
</child>
</template>
</interface>

View file

@ -3,38 +3,48 @@
<requires lib="gtk" version="4.0"/>
<requires lib="adw" version="1.0"/>
<object class="AdwApplicationWindow" id="main_window">
<template class="RecoderWindow" parent="AdwApplicationWindow">
<property name="title">Recoder</property>
<property name="default-width">700</property>
<property name="default-height">400</property>
<property name="content">
<child>
<object class="AdwToolbarView" id="toolbar_view">
<child type="top">
<object class="AdwHeaderBar" id="header_bar">
<!-- You can add title or buttons here if needed -->
<!-- Optional header bar content -->
</object>
</child>
<property name="content">
<object class="GtkBox" id="main_box">
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="vexpand">true</property>
<child>
<object class="GtkOverlay" id="overlay">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_window">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<child>
<child>
<object class="GtkListBox" id="listbox">
<property name="selection-mode">none</property>
<property name="vexpand">true</property>
<property name="show-separators">True</property>
<style>
<class name="rich-list"/>
</style>
</object>
</child>
</object>
</child>
<child type="overlay">
<object class="GtkLabel" id="drop_hint">
<property name="label">📂 Drop files here to get started</property>
@ -47,6 +57,7 @@
</child>
</object>
</child>
<child>
<object class="GtkProgressBar" id="progress_bar">
<property name="hexpand">true</property>
@ -56,6 +67,6 @@
</object>
</property>
</object>
</property>
</object>
</child>
</template>
</interface>

View file

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

View file

@ -7,7 +7,3 @@
font-size: 16px;
color: @theme_unfocused_fg_color;
}
.debug-row {
background: red;
}