diff --git a/p3/app/icons/credits.svg b/p3/app/icons/credits.svg
new file mode 100644
index 00000000..92f26e76
--- /dev/null
+++ b/p3/app/icons/credits.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/p3/app/icons/report.svg b/p3/app/icons/report.svg
new file mode 100644
index 00000000..3308d41d
--- /dev/null
+++ b/p3/app/icons/report.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/p3/app/icons/sponsor.svg b/p3/app/icons/sponsor.svg
new file mode 100644
index 00000000..d63e30cb
--- /dev/null
+++ b/p3/app/icons/sponsor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/p3/app/icons/wiki.svg b/p3/app/icons/wiki.svg
new file mode 100644
index 00000000..78d6de2e
--- /dev/null
+++ b/p3/app/icons/wiki.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/p3/app/revealer.py b/p3/app/revealer.py
new file mode 100644
index 00000000..56838797
--- /dev/null
+++ b/p3/app/revealer.py
@@ -0,0 +1,74 @@
+from .gtk_common import Gtk
+from . import get_icon_path
+
+
+class SupportFooter(Gtk.Box):
+ def __init__(self, translations):
+ super().__init__(orientation=Gtk.Orientation.HORIZONTAL, spacing=20)
+ self.set_margin_top(10)
+ self.translations = translations or {}
+ self.set_margin_bottom(10)
+ self.set_halign(Gtk.Align.CENTER)
+
+ self.urls_labels = [
+ ("https://linux.toys/knowledgebase.html", "Wiki", "wiki.svg"),
+ ("https://github.com/psygreg/linuxtoys/issues/new?template=bug_report.md", self.translations.get('report_label', 'Report Bug'), "report.svg"),
+ ("https://linux.toys/credits.html", self.translations.get('credits_label', 'Credits'), "credits.svg"),
+ ("https://ko-fi.com/psygreg", self.translations.get('support_footer', 'Support this project'), "sponsor.svg")
+ ]
+
+ for i, (url, label, icon) in enumerate(self.urls_labels):
+ link_button = Gtk.LinkButton(uri=url, label=label)
+ if icon_path := get_icon_path(icon):
+ icon_img = Gtk.Image.new_from_file(icon_path)
+ link_button.set_image(icon_img)
+ self.pack_start(link_button, False, False, 0)
+
+ if i < len(self.urls_labels) - 1:
+ separator = Gtk.Separator(orientation=Gtk.Orientation.VERTICAL)
+ self.pack_start(separator, False, False, 0)
+
+
+class RevealerFooter(Gtk.Revealer):
+ def __init__(self, parent):
+ super().__init__()
+
+ self.parent = parent
+ self.set_transition_type(Gtk.RevealerTransitionType.SLIDE_UP)
+ self.set_transition_duration(300)
+
+ container = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
+
+ self.button_box = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
+ self.button_box.set_layout(Gtk.ButtonBoxStyle.CENTER)
+ self.button_box.set_margin_top(5)
+ self.button_box.set_margin_bottom(5)
+
+ self.button_next = Gtk.Button(label=self.parent.translations.get("next", "Next"))
+ self.button_next.set_image(Gtk.Image.new_from_icon_name("go-next", Gtk.IconSize.BUTTON))
+ self.button_next.set_always_show_image(True)
+ self.button_next.set_tooltip_text(self.parent.translations.get("next", "Next"))
+ self.button_next.set_size_request(125, 35)
+ self.button_next.connect("clicked", self._on_next_clicked)
+
+ self.button_cancel = Gtk.Button(label=self.parent.translations.get("cancel", "Cancel"))
+ self.button_cancel.set_image(Gtk.Image.new_from_icon_name("window-close", Gtk.IconSize.BUTTON))
+ self.button_cancel.set_always_show_image(True)
+ self.button_cancel.set_tooltip_text(self.parent.translations.get("cancel", "Cancel"))
+ self.button_cancel.set_size_request(125, 35)
+ self.button_cancel.connect("clicked", self._on_cancel_clicked)
+
+ self.button_box.add(self.button_cancel)
+ self.button_box.add(self.button_next)
+
+ self.support = SupportFooter(self.parent.translations)
+
+ container.pack_start(self.support, False, False, 0)
+ container.pack_start(self.button_box, False, False, 0)
+ self.add(container)
+
+ def _on_next_clicked(self, button):
+ self.parent.on_install_checklist(button)
+
+ def _on_cancel_clicked(self, button):
+ self.parent.on_cancel_checklist(button)
\ No newline at end of file
diff --git a/p3/app/style.css b/p3/app/style.css
index 44cd8a8e..5eaf4341 100644
--- a/p3/app/style.css
+++ b/p3/app/style.css
@@ -1,9 +1,10 @@
-/* Defines the style for each item in our grid (FlowBox) */
+* {
+ outline: none;
+}
.script-item {
background-color: rgba(0,0,0,0.1);
- border: 1px solid rgba(255, 255, 255, 0.3); /* borda branca semi-transparente */
+ border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
- /* Let GTK use system theme colors automatically */
}
.script-item-hover {
background-color: rgba(255, 255, 255, 0.05);
@@ -21,7 +22,4 @@
}
flowboxchild:selected {
border-radius: 16px;
-}
-flowboxchild {
- outline: none;
}
\ No newline at end of file
diff --git a/p3/app/window.py b/p3/app/window.py
index 9d69a42d..fbdbbcea 100644
--- a/p3/app/window.py
+++ b/p3/app/window.py
@@ -4,13 +4,13 @@
from . import parser
from . import header
-from . import footer
from . import compat
from . import head_menu
from . import reboot_helper
from . import search_helper
from . import get_icon_path
from . import term_view
+from . import revealer
class AppWindow(Gtk.ApplicationWindow):
def __init__(self, application, translations, *args, **kwargs):
@@ -100,8 +100,8 @@ def __init__(self, application, translations, *args, **kwargs):
self.search_view.add(self.search_flowbox)
self.main_stack.add_named(self.search_view, "search")
- self.footer_widget = footer.create_footer()
- main_vbox.pack_start(self.footer_widget, False, False, 0)
+ self.reveal = revealer.RevealerFooter(self)
+ main_vbox.pack_start(self.reveal, False, False, 0)
# --- Load Data and Connect Signals ---
self.load_categories()
@@ -342,6 +342,18 @@ def create_flowbox(self):
flowbox.set_row_spacing(12) ## espaço entre linhas
return flowbox
+ def _on_toggled_check(self, button):
+ if button.get_active():
+ if button not in self.check_buttons:
+ self.check_buttons.append(button)
+ else:
+ if button in self.check_buttons:
+ self.check_buttons.remove(button)
+
+ self.reveal.button_box.show_all()
+ self.reveal.support.hide()
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
+
def create_item_widget(self, item_info, checklist: bool = False):
import os
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12)
@@ -370,9 +382,9 @@ def create_item_widget(self, item_info, checklist: bool = False):
if checklist:
check = Gtk.CheckButton()
+ check.connect('toggled', self._on_toggled_check)
check.script_info = item_info
box.pack_start(check, False, False, 0)
- self.check_buttons.append(check)
if is_subcategory or (is_category_type and is_not_script) or (is_main_category and is_not_script):
# This is a category or subcategory - make it bold
@@ -512,17 +524,7 @@ def _load_scripts_into_flowbox(self, flowbox, category_info):
flowbox.add(widget)
if checklist_mode:
- # Clear previous checklist buttons from footer
- for child in self.footer_widget.checklist_button_box.get_children():
- self.footer_widget.checklist_button_box.remove(child)
-
- install_btn = Gtk.Button(label=self.translations.get('install_btn_label', 'Install'))
- cancel_btn = Gtk.Button(label=self.translations.get('cancel_btn_label', 'Cancel'))
- install_btn.connect("clicked", self.on_install_checklist)
- cancel_btn.connect("clicked", self.on_cancel_checklist)
- self.footer_widget.checklist_button_box.pack_start(install_btn, False, False, 0)
- self.footer_widget.checklist_button_box.pack_start(cancel_btn, False, False, 0)
- self.footer_widget.checklist_button_box.show_all()
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
def load_scripts(self, category_info):
"""Loads scripts for a category and connects their click event. Supports checklist mode."""
@@ -540,18 +542,14 @@ def on_install_checklist(self, button):
if not selected_scripts:
return
- self.open_term_view(selected_scripts)
+ for cb in self.check_buttons[:]: cb.set_active(False)
- for cb in self.check_buttons: cb.set_active(False)
+ self.open_term_view(selected_scripts)
def on_cancel_checklist(self, button):
"""Uncheck all boxes, remove checklist buttons from footer, and return to previous view."""
- for cb in self.check_buttons:
- cb.set_active(False)
- # Remove checklist buttons from footer
- for child in self.footer_widget.checklist_button_box.get_children():
- self.footer_widget.checklist_button_box.remove(child)
-
+ for cb in self.check_buttons[:]: cb.set_active(False)
+
# Use back button logic to go to the appropriate previous view
self.on_back_button_clicked(None)
@@ -605,9 +603,9 @@ def open_term_view(self, infos):
run_box = term_view.TermRunScripts(infos, self, self.translations)
self.header_widget.hide()
-
+ self.reveal.set_reveal_child(False)
+ self.check_buttons.clear()
self.back_button.show()
- self.footer_widget.hide()
child = self.main_stack.get_child_by_name("running_scripts")
if child is not None:
@@ -742,10 +740,6 @@ def _refresh_ui_with_new_translations(self):
if hasattr(self, 'menu_button'):
self.menu_button.refresh_menu_translations()
- # Refresh the footer with new translations
- if hasattr(self.footer_widget, 'refresh_translations'):
- self.footer_widget.refresh_translations(self.translations)
-
# Always reload categories with new translations (so they're ready when user navigates back)
self.load_categories()
@@ -767,24 +761,14 @@ def _refresh_ui_with_new_translations(self):
# Update title bar with fresh category name
category_name = self.current_category_info.get('name', 'Unknown')
self.header_bar.props.title = f"LinuxToys: {category_name}"
-
+
# Reload the scripts view with new translations
self.load_scripts(self.current_category_info)
-
+
# Update footer if in checklist mode
if (self.current_category_info and
self.current_category_info.get('display_mode', 'menu') == 'checklist'):
- # Clear and recreate checklist buttons with new translations
- for child in self.footer_widget.checklist_button_box.get_children():
- self.footer_widget.checklist_button_box.remove(child)
-
- install_btn = Gtk.Button(label=self.translations.get('install_btn_label', 'Install'))
- cancel_btn = Gtk.Button(label=self.translations.get('cancel_btn_label', 'Cancel'))
- install_btn.connect("clicked", self.on_install_checklist)
- cancel_btn.connect("clicked", self.on_cancel_checklist)
- self.footer_widget.checklist_button_box.pack_start(install_btn, False, False, 0)
- self.footer_widget.checklist_button_box.pack_start(cancel_btn, False, False, 0)
- self.footer_widget.checklist_button_box.show_all()
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
def _get_fresh_category_info_with_translations(self):
"""Get fresh category info with updated translations"""
@@ -1413,6 +1397,8 @@ def _display_search_results(self):
# Ensure the back button is visible when in search mode
self.back_button.show()
+ self.reveal.set_reveal_child(False)
+
# Disable drag-and-drop in search mode
self._disable_drag_and_drop()
@@ -1461,6 +1447,9 @@ def _clear_search_results(self):
self.main_stack.set_visible_child(self.scripts_view)
# Ensure back button is visible for category views
self.back_button.show()
+
+ if self.current_category_info.get('display_mode', 'menu') == 'checklist':
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
# Restore drag-and-drop state based on current category
if self._is_local_scripts_category(self.current_category_info):
@@ -1498,6 +1487,8 @@ def on_back_button_clicked(self, widget):
self.search_entry.set_text("")
self._clear_search_results()
return
+
+ self.check_buttons.clear()
if self.navigation_stack:
# Store current view for cleanup
@@ -1545,11 +1536,9 @@ def on_back_button_clicked(self, widget):
# Show footer only if checklist mode
if previous_category.get('display_mode', 'menu') == 'checklist':
- self.footer_widget.show()
- self.footer_widget.show_checklist_footer()
- self.footer_widget.set_margin_bottom(0)
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
else:
- self.footer_widget.hide()
+ self.reveal.set_reveal_child(False)
# Clean up the old view after transition
def cleanup_old_view():
@@ -1589,11 +1578,9 @@ def show_categories_view(self):
self.back_button.hide()
self.header_bar.props.title = "LinuxToys"
self._update_header() # Reset to default header
- self.header_widget.show()
- self.footer_widget.show()
- self.footer_widget.show_menu_footer()
- # Ensure footer has proper spacing
- self.footer_widget.set_margin_bottom(0)
+ self.reveal.set_reveal_child(True)
+ self.reveal.button_box.hide()
+ self.reveal.support.show_all()
# Disable drag-and-drop when viewing main categories
self._disable_drag_and_drop()
@@ -1629,9 +1616,6 @@ def show_scripts_view(self, category_info):
# Show footer only if checklist mode
if category_info and category_info.get('display_mode', 'menu') == 'checklist':
- self.footer_widget.show()
- self.footer_widget.show_checklist_footer()
- # Ensure footer has proper spacing for checklist mode
- self.footer_widget.set_margin_bottom(0)
+ self.reveal.set_reveal_child(len(self.check_buttons) >= 2)
else:
- self.footer_widget.hide()
+ self.reveal.set_reveal_child(False)
\ No newline at end of file