Commit e210051a authored by Kirill Unitsaev's avatar Kirill Unitsaev

widgets: init image widget

parent 420364fc
from gi.repository import Adw, Gtk, Gdk
from gi.repository import Gio
import os
from .BaseWidget import BaseWidget
class ImageChooserWidget(BaseWidget):
def create_row(self):
self.has_file_prefix = False
self.row = Adw.PreferencesRow(
activatable=False,
focusable=False
)
main_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=12,
margin_top=12,
margin_bottom=12,
margin_start=12,
margin_end=12,
)
if self.setting.name:
title_box = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL,
spacing=6,
)
title_label = Gtk.Label(
label=self.setting.name,
halign=Gtk.Align.START,
hexpand=True,
css_classes=["title-4"]
)
title_box.append(title_label)
if self.setting.help:
help_label = Gtk.Label(
label=self.setting.help,
halign=Gtk.Align.START,
css_classes=["dim-label"]
)
title_box.append(help_label)
main_box.append(title_box)
image_frame = Gtk.Frame(
css_classes=["view"],
halign=Gtk.Align.CENTER,
)
self.picture = Gtk.Picture(
content_fit=Gtk.ContentFit.COVER,
can_shrink=True,
)
self.picture.set_size_request(320, 180)
self.placeholder = Adw.StatusPage(
icon_name="image-missing-symbolic",
)
self.placeholder.set_size_request(320, 180)
self.image_stack = Gtk.Stack(
transition_type=Gtk.StackTransitionType.CROSSFADE,
transition_duration=150,
)
self.image_stack.add_named(self.placeholder, "placeholder")
self.image_stack.add_named(self.picture, "picture")
overlay = Gtk.Overlay()
overlay.set_child(self.image_stack)
self.change_button = Gtk.Button(
icon_name="document-edit-symbolic",
valign=Gtk.Align.END,
halign=Gtk.Align.END,
margin_bottom=5,
margin_end=5,
css_classes=["circular", "secondary"],
)
self.change_button.connect("clicked", self._on_change_clicked)
self.image_reset_button = Gtk.Button(
icon_name="edit-undo-symbolic",
margin_top=5,
margin_end=5,
css_classes=["circular", "secondary"],
)
self.image_reset_button.connect("clicked", self._on_reset_clicked)
self.image_reset_revealer = Gtk.Revealer(
transition_type=Gtk.RevealerTransitionType.CROSSFADE,
transition_duration=150,
child=self.image_reset_button,
reveal_child=False,
valign=Gtk.Align.START,
halign=Gtk.Align.END
)
overlay.add_overlay(self.change_button)
overlay.add_overlay(self.image_reset_revealer)
image_frame.set_child(overlay)
main_box.append(image_frame)
self.row.set_child(main_box)
self.update_display()
self._update_reset_visibility()
return self.row
def _normalize_path(self, path):
if not path or not isinstance(path, str):
return None
if path.startswith("file://"):
self.has_file_prefix = True
path = path[7:]
return os.path.expandvars(os.path.expanduser(path))
def update_display(self):
current = self._normalize_path(self.setting._get_backend_value())
if current and os.path.exists(current):
file = Gio.File.new_for_path(current)
try:
texture = Gdk.Texture.new_from_file(file)
self.picture.set_paintable(texture)
self.image_stack.set_visible_child_name("picture")
except Exception as e:
self.logger.error(f"Error loading image: {e}")
self.picture.set_paintable(None)
self.image_stack.set_visible_child_name("placeholder")
else:
self.picture.set_paintable(None)
self.image_stack.set_visible_child_name("placeholder")
self._update_reset_visibility()
def _on_reset_clicked(self, button):
default_value = self.setting.default
if default_value is not None:
self.setting._set_backend_value(default_value)
self.update_display()
def _update_reset_visibility(self):
current_value = self._normalize_path(self.setting._get_backend_value())
default_value = self._normalize_path(self.setting.default)
self.image_reset_revealer.set_reveal_child(
current_value != default_value if default_value is not None
else False
)
def _on_change_clicked(self, button):
dialog = Gtk.FileDialog()
if 'extensions' in self.setting.map:
filters = self._create_file_filters()
if filters.get_n_items() > 0:
dialog.set_filters(filters)
dialog.set_default_filter(filters.get_item(0))
current = self._normalize_path(self.setting._get_backend_value())
if current:
try:
current_file = Gio.File.new_for_path(current)
parent = current_file.get_parent()
if parent:
dialog.set_initial_folder(parent)
except Exception as e:
self.logger.error(f"Error setting initial folder: {e}")
try:
dialog.open(
parent=button.get_root(),
callback=self._on_file_selected
)
except Exception as e:
self.logger.error(f"File dialog error: {e}")
def _create_file_filters(self):
filters = Gio.ListStore.new(Gtk.FileFilter)
patterns = [p for p in self.setting.map.get('extensions', [])]
if patterns:
file_filter = Gtk.FileFilter()
file_filter.set_name(_("Image files"))
for pattern in patterns:
if pattern.startswith('.'):
file_filter.add_suffix(pattern[1:])
else:
file_filter.add_pattern(pattern)
filters.append(file_filter)
return filters
def _on_file_selected(self, dialog, result):
try:
file = dialog.open_finish(result)
if file:
path = file.get_path()
if self.has_file_prefix:
path = f"file://{path}"
self.setting._set_backend_value(path)
self.update_display()
except Exception as e:
if "dismissed" not in str(e).lower():
self.logger.error(f"File selection error: {e}")
......@@ -2,6 +2,7 @@ from .AvatarWidget import AvatarWidget
from .BooleanWidget import BooleanWidget
from .ChoiceWidget import ChoiceWidget
from .RadioChoiceWidget import RadioChoiceWidget
from .ThemeChooserWidget import ThemeChooserWidget
from .EntryWidget import EntryWidget
from .NumStepper import NumStepper
from .FileChooser import FileChooser
......@@ -9,6 +10,8 @@ from .ButtonWidget import ButtonWidget
from .InfoLabelWidget import InfoLabelWidget
from .InfoDictWidget import InfoDictWidget
from .DualListWidget import DualListWidget
from .ImageChooserWidget import ImageChooserWidget
from .DualImageChooserWidget import DualImageChooserWidget
import logging
logger = logging.getLogger(f"{__name__}")
......@@ -17,8 +20,10 @@ class WidgetFactory:
widget_map = {
'avatar': AvatarWidget,
'file': FileChooser,
'image': ImageChooserWidget,
'choice': ChoiceWidget,
'choice_radio': RadioChoiceWidget,
'theme_chooser': ThemeChooserWidget,
'boolean': BooleanWidget,
'entry': EntryWidget,
'number': NumStepper,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment