Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
tuneit
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
tuneit
Commits
da758584
Commit
da758584
authored
Jan 18, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
widgets: init image_dual widget
parent
e210051a
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
360 additions
and
0 deletions
+360
-0
DualImageChooserWidget.py
src/settings/setting/widgets/DualImageChooserWidget.py
+359
-0
__init__.py
src/settings/setting/widgets/__init__.py
+1
-0
No files found.
src/settings/setting/widgets/DualImageChooserWidget.py
0 → 100644
View file @
da758584
from
gi.repository
import
Adw
,
Gtk
,
Gdk
,
Gio
,
GLib
import
os
from
.BaseWidget
import
BaseWidget
class
DualImageChooserWidget
(
BaseWidget
):
ORIENTATION_THRESHOLD
=
750
IMAGE_WIDTH
=
220
PLACEHOLDER_HEIGHT
=
120
def
__init__
(
self
,
setting
):
super
()
.
__init__
(
setting
)
self
.
_surface_handler_id
=
None
self
.
_surface
=
None
def
create_row
(
self
):
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
.
VERTICAL
,
spacing
=
2
,
hexpand
=
True
,
)
title_label
=
Gtk
.
Label
(
label
=
self
.
setting
.
name
,
halign
=
Gtk
.
Align
.
START
,
)
title_box
.
append
(
title_label
)
if
self
.
setting
.
help
:
help_label
=
Gtk
.
Label
(
label
=
self
.
setting
.
help
,
halign
=
Gtk
.
Align
.
START
,
wrap
=
True
,
css_classes
=
[
"caption"
,
"dim-label"
]
)
title_box
.
append
(
help_label
)
main_box
.
append
(
title_box
)
self
.
images_box
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
HORIZONTAL
,
spacing
=
12
,
halign
=
Gtk
.
Align
.
CENTER
,
valign
=
Gtk
.
Align
.
START
,
homogeneous
=
True
,
)
labels
=
self
.
setting
.
map
.
get
(
'labels'
,
[
_
(
"Light"
),
_
(
"Dark"
)])
if
self
.
setting
.
map
else
[
_
(
"Light"
),
_
(
"Dark"
)]
self
.
pictures
=
[]
self
.
placeholders
=
[]
self
.
image_stacks
=
[]
self
.
reset_revealers
=
[]
for
i
in
range
(
2
):
image_container
=
self
.
_create_image_container
(
i
,
labels
[
i
]
if
i
<
len
(
labels
)
else
""
)
self
.
images_box
.
append
(
image_container
)
main_box
.
append
(
self
.
images_box
)
self
.
row
.
set_child
(
main_box
)
self
.
row
.
connect
(
"realize"
,
self
.
_on_realize
)
self
.
row
.
connect
(
"unrealize"
,
self
.
_on_unrealize
)
self
.
update_display
()
self
.
_update_reset_visibility
()
return
self
.
row
def
_on_realize
(
self
,
widget
):
window
=
widget
.
get_root
()
if
window
:
surface
=
window
.
get_surface
()
if
surface
:
self
.
_surface
=
surface
self
.
_surface_handler_id
=
surface
.
connect
(
"layout"
,
self
.
_on_window_layout
)
self
.
_update_orientation
(
surface
.
get_width
())
def
_on_unrealize
(
self
,
widget
):
if
self
.
_surface
and
self
.
_surface_handler_id
:
self
.
_surface
.
disconnect
(
self
.
_surface_handler_id
)
self
.
_surface_handler_id
=
None
self
.
_surface
=
None
def
_on_window_layout
(
self
,
surface
,
width
,
height
):
self
.
_update_orientation
(
width
)
def
_update_orientation
(
self
,
width
):
new_orientation
=
(
Gtk
.
Orientation
.
VERTICAL
if
width
<
self
.
ORIENTATION_THRESHOLD
else
Gtk
.
Orientation
.
HORIZONTAL
)
if
self
.
images_box
.
get_orientation
()
!=
new_orientation
:
self
.
images_box
.
set_orientation
(
new_orientation
)
def
_create_image_container
(
self
,
index
,
label
):
container
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
VERTICAL
,
spacing
=
6
,
)
image_frame
=
Gtk
.
Frame
(
css_classes
=
[
"view"
],
halign
=
Gtk
.
Align
.
CENTER
,
)
picture
=
Gtk
.
Picture
(
content_fit
=
Gtk
.
ContentFit
.
FILL
,
)
self
.
pictures
.
append
(
picture
)
placeholder
=
Gtk
.
Box
(
orientation
=
Gtk
.
Orientation
.
VERTICAL
,
halign
=
Gtk
.
Align
.
CENTER
,
valign
=
Gtk
.
Align
.
CENTER
,
width_request
=
self
.
IMAGE_WIDTH
,
height_request
=
self
.
PLACEHOLDER_HEIGHT
,
)
placeholder_icon
=
Gtk
.
Image
(
icon_name
=
"image-missing-symbolic"
,
pixel_size
=
48
,
css_classes
=
[
"dim-label"
],
)
placeholder
.
append
(
placeholder_icon
)
self
.
placeholders
.
append
(
placeholder
)
image_stack
=
Gtk
.
Stack
(
transition_type
=
Gtk
.
StackTransitionType
.
CROSSFADE
,
transition_duration
=
150
,
)
image_stack
.
add_named
(
placeholder
,
"placeholder"
)
image_stack
.
add_named
(
picture
,
"picture"
)
self
.
image_stacks
.
append
(
image_stack
)
overlay
=
Gtk
.
Overlay
()
overlay
.
set_child
(
image_stack
)
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"
],
)
change_button
.
connect
(
"clicked"
,
self
.
_on_change_clicked
,
index
)
image_reset_button
=
Gtk
.
Button
(
icon_name
=
"edit-undo-symbolic"
,
margin_top
=
5
,
margin_end
=
5
,
css_classes
=
[
"circular"
,
"secondary"
],
)
image_reset_button
.
connect
(
"clicked"
,
self
.
_on_image_reset_clicked
,
index
)
image_reset_revealer
=
Gtk
.
Revealer
(
transition_type
=
Gtk
.
RevealerTransitionType
.
CROSSFADE
,
transition_duration
=
150
,
child
=
image_reset_button
,
reveal_child
=
False
,
valign
=
Gtk
.
Align
.
START
,
halign
=
Gtk
.
Align
.
END
)
self
.
reset_revealers
.
append
(
image_reset_revealer
)
overlay
.
add_overlay
(
change_button
)
overlay
.
add_overlay
(
image_reset_revealer
)
image_frame
.
set_child
(
overlay
)
container
.
append
(
image_frame
)
if
label
:
label_widget
=
Gtk
.
Label
(
label
=
label
,
halign
=
Gtk
.
Align
.
CENTER
,
css_classes
=
[
"caption"
,
"dim-label"
]
)
container
.
append
(
label_widget
)
return
container
def
_get_values
(
self
):
values
=
self
.
setting
.
_get_backend_value
()
if
isinstance
(
values
,
list
)
and
len
(
values
)
>=
2
:
return
values
[
0
],
values
[
1
]
return
None
,
None
def
_get_defaults
(
self
):
default
=
self
.
setting
.
default
if
isinstance
(
default
,
list
)
and
len
(
default
)
>=
2
:
return
default
[
0
],
default
[
1
]
return
None
,
None
def
_set_value
(
self
,
index
,
value
):
backend_value
=
self
.
setting
.
_get_backend_value
()
if
isinstance
(
backend_value
,
list
)
and
len
(
backend_value
)
>=
2
:
values
=
list
(
backend_value
)
else
:
values
=
[
None
,
None
]
values
[
index
]
=
value
self
.
setting
.
_set_backend_value
(
values
)
def
_normalize_path
(
self
,
path
):
if
not
path
or
not
isinstance
(
path
,
str
):
return
None
if
path
.
startswith
(
'"'
)
and
path
.
endswith
(
'"'
):
path
=
path
[
1
:
-
1
]
if
path
.
startswith
(
"file://"
):
path
=
path
[
7
:]
return
os
.
path
.
expandvars
(
os
.
path
.
expanduser
(
path
))
def
_has_file_prefix
(
self
,
path
):
return
isinstance
(
path
,
str
)
and
path
.
startswith
(
"file://"
)
def
_format_path_for_save
(
self
,
path
,
original
):
if
not
path
:
return
path
if
self
.
_has_file_prefix
(
original
):
path
=
f
"file://{path}"
had_quotes
=
original
and
original
.
startswith
(
'"'
)
and
original
.
endswith
(
'"'
)
needs_quotes
=
' '
in
path
or
had_quotes
if
needs_quotes
and
not
path
.
startswith
(
'"'
):
path
=
f
'"{path}"'
return
path
def
update_display
(
self
):
for
i
in
range
(
2
):
self
.
_update_image
(
i
)
self
.
_update_reset_visibility
()
def
_update_image
(
self
,
index
):
values
=
self
.
_get_values
()
current
=
self
.
_normalize_path
(
values
[
index
])
if
current
and
os
.
path
.
exists
(
current
):
file
=
Gio
.
File
.
new_for_path
(
current
)
try
:
texture
=
Gdk
.
Texture
.
new_from_file
(
file
)
self
.
pictures
[
index
]
.
set_paintable
(
texture
)
width
=
texture
.
get_width
()
height
=
texture
.
get_height
()
if
width
>
0
and
height
>
0
:
target_height
=
int
(
self
.
IMAGE_WIDTH
*
height
/
width
)
self
.
pictures
[
index
]
.
set_size_request
(
self
.
IMAGE_WIDTH
,
target_height
)
self
.
image_stacks
[
index
]
.
set_visible_child_name
(
"picture"
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"Error loading image {index}: {e}"
)
self
.
pictures
[
index
]
.
set_paintable
(
None
)
self
.
image_stacks
[
index
]
.
set_visible_child_name
(
"placeholder"
)
else
:
self
.
pictures
[
index
]
.
set_paintable
(
None
)
self
.
image_stacks
[
index
]
.
set_visible_child_name
(
"placeholder"
)
def
_on_reset_clicked
(
self
,
button
):
default
=
self
.
setting
.
default
if
default
is
not
None
:
self
.
setting
.
_set_backend_value
(
default
)
self
.
update_display
()
def
_on_image_reset_clicked
(
self
,
button
,
index
):
defaults
=
self
.
_get_defaults
()
if
defaults
[
index
]
is
not
None
:
self
.
_set_value
(
index
,
defaults
[
index
])
self
.
update_display
()
def
_update_reset_visibility
(
self
):
defaults
=
self
.
_get_defaults
()
values
=
self
.
_get_values
()
for
i
in
range
(
2
):
current_value
=
self
.
_normalize_path
(
values
[
i
])
default_value
=
self
.
_normalize_path
(
defaults
[
i
])
self
.
reset_revealers
[
i
]
.
set_reveal_child
(
current_value
!=
default_value
if
default_value
is
not
None
else
False
)
def
_on_change_clicked
(
self
,
button
,
index
):
dialog
=
Gtk
.
FileDialog
()
if
self
.
setting
.
map
and
'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
))
values
=
self
.
_get_values
()
current
=
self
.
_normalize_path
(
values
[
index
])
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
=
lambda
d
,
r
:
self
.
_on_file_selected
(
d
,
r
,
index
)
)
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
,
index
):
try
:
file
=
dialog
.
open_finish
(
result
)
if
file
:
path
=
file
.
get_path
()
values
=
self
.
_get_values
()
path
=
self
.
_format_path_for_save
(
path
,
values
[
index
])
self
.
_set_value
(
index
,
path
)
self
.
update_display
()
except
Exception
as
e
:
if
"dismissed"
not
in
str
(
e
)
.
lower
():
self
.
logger
.
error
(
f
"File selection error: {e}"
)
src/settings/setting/widgets/__init__.py
View file @
da758584
...
@@ -21,6 +21,7 @@ class WidgetFactory:
...
@@ -21,6 +21,7 @@ class WidgetFactory:
'avatar'
:
AvatarWidget
,
'avatar'
:
AvatarWidget
,
'file'
:
FileChooser
,
'file'
:
FileChooser
,
'image'
:
ImageChooserWidget
,
'image'
:
ImageChooserWidget
,
'image_dual'
:
DualImageChooserWidget
,
'choice'
:
ChoiceWidget
,
'choice'
:
ChoiceWidget
,
'choice_radio'
:
RadioChoiceWidget
,
'choice_radio'
:
RadioChoiceWidget
,
'theme_chooser'
:
ThemeChooserWidget
,
'theme_chooser'
:
ThemeChooserWidget
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment