Seperate ui code
This commit is contained in:
parent
68a6f00fc7
commit
03e670ebd2
8 changed files with 253 additions and 181 deletions
|
@ -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)
|
||||
|
|
48
src/recoder/file_entry_row.py
Normal file
48
src/recoder/file_entry_row.py
Normal 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)
|
118
src/recoder/recoder_window.py
Normal file
118
src/recoder/recoder_window.py
Normal 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)
|
|
@ -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)
|
62
src/resources/file_entry_row.ui
Normal file
62
src/resources/file_entry_row.ui
Normal 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>
|
|
@ -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>
|
||||
<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>
|
|
@ -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>
|
||||
|
|
|
@ -7,7 +7,3 @@
|
|||
font-size: 16px;
|
||||
color: @theme_unfocused_fg_color;
|
||||
}
|
||||
|
||||
.debug-row {
|
||||
background: red;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue