Revamp properties window

This commit is contained in:
Heliguy
2023-11-10 00:09:25 -05:00
parent 740338d515
commit f70f87f175
6 changed files with 322 additions and 169 deletions

51
src/app_row_widget.py Normal file
View File

@@ -0,0 +1,51 @@
import os
import pathlib
import subprocess
import re
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
from .properties_window import PropertiesWindow
from .filter_window import FilterWindow
from .common import myUtils
class AppRow(Adw.ActionRow):
def __init__(self, parent_window, host_flatpaks, flatpak_index, **kwargs):
super().__init__(**kwargs)
self.my_utils = myUtils(parent_window)
current_flatpak = host_flatpaks[flatpak_index]
self.app_name = current_flatpak[0]
self.app_id = current_flatpak[2]
self.origin_remote = current_flatpak[6]
self.install_type = current_flatpak[7]
self.app_ref = current_flatpak[8]
self.set_title(self.app_name)
self.set_subtitle(self.app_id)
self.add_prefix(self.my_utils.findAppIcon(self.app_id))
properties_button = Gtk.Button(icon_name="info-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("View Properties"))
properties_button.add_css_class("flat")
properties_button.connect("clicked", lambda *_: PropertiesWindow(flatpak_index, host_flatpaks, parent_window))
self.add_suffix(properties_button)
select_flatpak_tickbox = Gtk.CheckButton() # visible=self.in_batch_mode
select_flatpak_tickbox.add_css_class("selection-mode")
# select_flatpak_tickbox.connect("toggled", self.rowSelectHandler, index)
self.add_suffix(select_flatpak_tickbox)
row_menu = Gtk.MenuButton(icon_name="view-more-symbolic", valign=Gtk.Align.CENTER) # visible=not self.in_batch_mode
row_menu.add_css_class("flat")
row_menu_model = Gio.Menu()
copy_menu_model = Gio.Menu()
advanced_menu_model = Gio.Menu()
self.add_suffix(row_menu)
parent_window.create_action(("copy-name" + str(flatpak_index)), lambda *_, name=self.app_name, toast=_("Copied name"): self.copyItem(name, toast))
copy_menu_model.append_item(Gio.MenuItem.new(_("Copy Name"), f"win.copy-name{flatpak_index}"))
row_menu_model.append_submenu(_("Copy"), copy_menu_model)
row_menu.set_menu_model(row_menu_model)

View File

@@ -13,6 +13,7 @@ blueprints = custom_target('blueprints',
'downgrade.blp',
'search_install.blp',
'snapshots.blp',
'properties.blp',
),
output: '.',
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
@@ -63,6 +64,8 @@ warehouse_sources = [
'search_install_window.py',
'snapshots_window.py',
'snapshots.blp',
'app_row_widget.py',
'properties.blp',
]
install_data(warehouse_sources, install_dir: moduledir)

103
src/properties.blp Normal file
View File

@@ -0,0 +1,103 @@
using Gtk 4.0;
using Adw 1;
template PropertiesWindow : Adw.Window {
default-width: 350;
default-height: 600;
modal: true;
Adw.ToolbarView main_toolbar_view {
[top]
HeaderBar header_bar {
}
content:
Adw.ToastOverlay toast_overlay {
Box {
orientation: vertical;
Adw.Banner eol_app_banner {
}
Adw.Banner eol_runtime_banner {
}
Adw.Banner mask_banner {
}
ScrolledWindow {
Adw.Clamp {
Box {
orientation: vertical;
hexpand: false;
vexpand: true;
spacing: 12;
margin-top: 12;
margin-start: 12;
margin-end: 12;
margin-bottom: 12;
Image app_icon {
pixel-size: 100;
styles["icon-dropshadow"]
}
Adw.PreferencesGroup upper {
Adw.ActionRow data_row {
title: _("Loading User Data…");
[suffix]
Button open_data {
icon-name: "document-open-symbolic";
tooltip-text: _("Open User Data Folder");
valign: center;
visible: false;
styles["flat"]
}
[suffix]
Button trash_data {
icon-name: "brush-symbolic";
tooltip-text: _("Trash User Data");
valign: center;
visible: false;
styles["flat"]
}
[suffix]
Spinner spinner {
spinning: true;
}
}
Adw.ActionRow view_apps {
title: _("Show Apps Using this Runtime");
activatable: true;
[suffix]
Image {
icon-name: "funnel-symbolic";
}
}
Adw.ActionRow runtime {
title: _("Runtime");
[suffix]
Button runtime_properties {
icon-name: "info-symbolic";
valign: center;
styles["flat"]
}
[suffix]
Button runtime_copy {
icon-name: "edit-copy-symbolic";
valign: center;
styles["flat"]
}
}
Adw.ActionRow details {
title: _("Show Details in Store");
activatable: true;
[suffix]
Image {
icon-name: "arrow2-top-right-symbolic";
}
}
}
Adw.PreferencesGroup lower {
}
}
}
}
}
};
}
}

View File

@@ -2,191 +2,179 @@ from gi.repository import Gtk, Adw, GLib, Gdk, Gio
from .common import myUtils
import subprocess
import os
import pathlib
def show_properties_window(widget, index, window):
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/properties.ui")
class PropertiesWindow(Adw.Window):
__gtype_name__ = "PropertiesWindow"
new_env = dict( os.environ )
new_env['LC_ALL'] = 'C'
host_home = str(pathlib.Path.home())
user_data_path = host_home + "/.var/app/"
app_name = window.host_flatpaks[index][0]
app_id = window.host_flatpaks[index][2]
install_type = window.host_flatpaks[index][7]
app_ref = window.host_flatpaks[index][8]
data_folder = window.user_data_path + app_id
toast_overlay = Gtk.Template.Child()
upper = Gtk.Template.Child()
app_icon = Gtk.Template.Child()
data_row = Gtk.Template.Child()
open_data = Gtk.Template.Child()
trash_data = Gtk.Template.Child()
spinner = Gtk.Template.Child()
details = Gtk.Template.Child()
view_apps = Gtk.Template.Child()
runtime = Gtk.Template.Child()
runtime_properties = Gtk.Template.Child()
runtime_copy = Gtk.Template.Child()
lower = Gtk.Template.Child()
eol_app_banner = Gtk.Template.Child()
eol_runtime_banner = Gtk.Template.Child()
mask_banner = Gtk.Template.Child()
properties_window = Adw.Window(title=_("{} Properties").format(window.host_flatpaks[index][0]))
properties_window.set_default_size(350, 600)
properties_window.set_size_request(260, 230)
properties_window.set_modal(True)
properties_window.set_resizable(True)
outer_box = Gtk.Box(orientation="vertical")
properties_window.set_transient_for(window)
properties_scroll = Gtk.ScrolledWindow()
properties_toast_overlay = Adw.ToastOverlay()
properties_toast_overlay.set_child(outer_box)
properties_box = Gtk.Box(orientation="vertical", vexpand=True)
properties_clamp = Adw.Clamp()
eol_app_banner = Adw.Banner(title=_("{} has reached its End of Life and will not receive any security updates").format(app_name))
eol_runtime_banner = Adw.Banner(title=_("{}'s runtime has reached its End of Life and will not receive any security updates").format(app_name))
mask_banner = Adw.Banner(title=_("{} is masked and will not be updated").format(window.host_flatpaks[index][0]))
outer_box.append(eol_app_banner)
outer_box.append(eol_runtime_banner)
outer_box.append(mask_banner)
outer_box.append(properties_scroll)
properties_scroll.set_child(properties_clamp)
properties_clamp.set_child(properties_box)
properties_title_bar = Adw.ToolbarView()
properties_header_bar = Gtk.HeaderBar()
properties_title_bar.add_top_bar(properties_header_bar)
properties_title_bar.set_content(properties_toast_overlay)
user_data_list = Gtk.ListBox(selection_mode="none", margin_top=12, margin_bottom=0, margin_start=12, margin_end=12)
user_data_row = Adw.ActionRow(title="No User Data")
user_data_list.append(user_data_row)
user_data_list.add_css_class("boxed-list")
def copyItem(self, to_copy, to_toast=None):
self.get_clipboard().set(to_copy)
if to_toast:
self.toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(to_toast)))
my_utils = myUtils(window)
def open_button_handler(self, widget):
try:
Gio.AppInfo.launch_default_for_uri(f"file://{self.user_data_path}", None)
except GLib.GError:
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
system_mask_list = my_utils.getHostMasks("system")
user_mask_list = my_utils.getHostMasks("user")
def show_details(self, widget):
try:
Gio.AppInfo.launch_default_for_uri(f"appstream://{self.app_id}", None)
except GLib.GError:
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show details")))
def viewAppsHandler(button):
window.should_open_filter_window = False
window.filter_button.set_active(True)
window.applyFilter([[False], [False], "all", "all", [window.host_flatpaks[index][8]]])
window.should_open_filter_window = True
properties_window.close()
def getSizeCallback(self, *args):
self.open_data.set_visible(True)
self.open_data.connect("clicked", self.open_button_handler)
self.trash_data.set_visible(True)
self.data_row.set_title(_("User Data"))
self.data_row.set_subtitle(f"~{self.size}")
self.spinner.set_visible(False)
def key_handler(_a, event, _c, _d):
if event == Gdk.KEY_Escape:
properties_window.close()
def getSizeThread(self, *args):
self.size = self.my_utils.getSizeWithFormat(self.user_data_path)
event_controller = Gtk.EventControllerKey()
event_controller.connect("key-pressed", key_handler)
properties_window.add_controller(event_controller)
def on_response(_a, response_id, _b):
if response_id != "continue":
return
if my_utils.trashFolder(data_folder) == 0:
properties_toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data")))
user_data_list.remove(user_data_row)
user_data_list.append(Adw.ActionRow(title="No User Data"))
def generateUpper(self):
image = self.my_utils.findAppIcon(self.app_id)
self.runtime.set_subtitle(self.current_flatpak[13])
if image.get_paintable() == None:
self.app_icon.set_from_icon_name(image.get_icon_name())
else:
properties_toast_overlay.add_toast(Adw.Toast.new(_("Could not trash user data")))
self.app_icon.set_from_paintable(image.get_paintable())
def clean_button_handler(_widget):
dialog = Adw.MessageDialog.new(window, _("Send {}'s User Data to the Trash?").format(app_name))
dialog.set_body(_("Your files and data for this app will be sent to the trash."))
dialog.set_close_response("cancel")
if os.path.exists(self.user_data_path):
task = Gio.Task.new(None, None, self.getSizeCallback)
task.run_in_thread(self.getSizeThread)
else:
self.data_row.set_title(_("No User Data"))
self.spinner.set_visible(False)
if "runtime" in self.current_flatpak[12]:
self.runtime.set_visible(False)
else:
self.view_apps.set_visible(False)
def generateLower(self):
column_headers = [
_('Name'), _('Description'), _('App ID'), _('Version'), _('Branch'),
_('Arch'), _('Origin'), _('Installation'), _('Ref'), _('Active Commit'),
_('Latest Commit'), _('Installed Size'), _('Options')
]
for i in range(len(column_headers)):
if self.current_flatpak[i] == "":
continue
row = Adw.ActionRow(title=column_headers[i], activatable=True)
row.add_suffix(Gtk.Image.new_from_icon_name("edit-copy-symbolic"))
row.set_subtitle(GLib.markup_escape_text(self.current_flatpak[i]))
row.connect("activated", lambda *_a, row=row: self.copyItem(row.get_subtitle(), row.get_title()))
self.lower.add(row)
def viewAppsHandler(self, widget):
self.parent_window.should_open_filter_window = False
self.parent_window.filter_button.set_active(True)
self.parent_window.applyFilter([True, False, ["all"], ["all"], [self.app_ref]])
self.parent_window.should_open_filter_window = True
self.close()
def showPropertiesHandler(self):
runtime = self.current_flatpak[13]
for i in range(len(self.host_flatpaks)):
if runtime in self.host_flatpaks[i][8]:
PropertiesWindow(i, self.host_flatpaks, self.parent_window)
def trashDataHandler(self):
def onResponse(response, *args):
if response == "cancel":
return
if self.my_utils.trashFolder(self.user_data_path) == 0:
self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data")))
self.data_row.set_title(_("No User Data"))
self.data_row.set_subtitle("")
self.open_data.set_visible(False)
self.trash_data.set_visible(False)
else:
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not trash user data")))
dialog = Adw.MessageDialog.new(self, _("Send {}'s User Data to the Trash?").format(self.app_name))
dialog.add_response("cancel", _("Cancel"))
dialog.set_close_response("cancel")
dialog.add_response("continue", _("Trash Data"))
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_transient_for(properties_window)
dialog.connect("response", on_response, dialog.choose_finish)
Gtk.Window.present(dialog)
def open_button_handler(_widget):
try:
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)
except GLib.GError:
properties_toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
def copy_button_handler(widget, title, to_copy):
window.clipboard.set(to_copy)
properties_toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(title)))
dialog.connect("response", onResponse, dialog.choose_finish)
dialog.present()
image = my_utils.findAppIcon(window.host_flatpaks[index][2])
image.add_css_class("icon-dropshadow")
image.set_margin_top(6)
image.set_pixel_size(100)
properties_box.append(image)
def __init__(self, flatpak_index, host_flatpaks, parent_window, **kwargs):
super().__init__(**kwargs)
self.my_utils = myUtils(self)
self.current_flatpak = host_flatpaks[flatpak_index]
self.parent_window = parent_window
self.host_flatpaks = host_flatpaks
self.flatpak_index = flatpak_index
properties_list = Gtk.ListBox(selection_mode="none", margin_top=12, margin_bottom=12, margin_start=12, margin_end=12)
properties_list.add_css_class("boxed-list")
self.app_name = self.current_flatpak[0]
self.app_id = self.current_flatpak[2]
self.origin_remote = self.current_flatpak[6]
self.install_type = self.current_flatpak[7]
self.app_ref = self.current_flatpak[8]
self.user_data_path += self.app_id
path = str(window.user_data_path) + window.host_flatpaks[index][2]
self.details.connect("activated", self.show_details)
self.runtime_copy.connect("clicked", lambda *_: self.copyItem(self.runtime.get_subtitle(), self.runtime.get_title()))
self.runtime_properties.connect("clicked", lambda *_: self.close())
self.runtime_properties.connect("clicked", lambda *_: self.showPropertiesHandler())
self.view_apps.connect("activated", self.viewAppsHandler)
self.trash_data.connect("clicked", lambda *_: self.trashDataHandler())
if "eol" in self.current_flatpak[12]:
self.eol_app_banner.set_revealed(True)
self.eol_app_banner.set_title(_("{} has reached its End of Life and will not receive any security updates").format(self.app_name))
def size_thread(path):
size = f"{path}\n~{my_utils.getSizeWithFormat(path)}"
user_data_row.set_subtitle(size)
if self.current_flatpak[13] in parent_window.eol_list:
self.eol_runtime_banner.set_revealed(True)
self.eol_runtime_banner.set_title(_("{}'s runtime has reached its End of Life and will not receive any security updates").format(self.app_name))
def calc_size(path):
task = Gio.Task.new(None, None, None)
task.run_in_thread(lambda _task, _obj, _data, _cancellable: size_thread(path))
if self.app_id in self.my_utils.getHostMasks("system") or self.app_id in self.my_utils.getHostMasks("user"):
self.mask_banner.set_revealed(True)
self.mask_banner.set_title(_("{} is masked and will not be updated").format(self.app_name))
def showPropertiesHandler(button, query):
for i in range(len(window.host_flatpaks)):
if query in window.host_flatpaks[i][8]:
show_properties_window(button, i, window)
properties_window.close()
def key_handler(_a, event, _c, _d):
if event == Gdk.KEY_Escape:
self.close()
event_controller = Gtk.EventControllerKey()
event_controller.connect("key-pressed", key_handler)
self.add_controller(event_controller)
if os.path.exists(path):
user_data_row.set_title("User Data")
calc_size(path)
self.generateUpper()
self.generateLower()
open_button = Gtk.Button(icon_name="document-open-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("Open User Data Folder"))
open_button.add_css_class("flat")
open_button.connect("clicked", open_button_handler)
user_data_row.add_suffix(open_button)
clean_button = Gtk.Button(icon_name="brush-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("Trash User Data"))
clean_button.add_css_class("flat")
clean_button.connect("clicked", clean_button_handler)
user_data_row.add_suffix(clean_button)
details_row = Adw.ActionRow(title=_("Show Details in Store"), activatable=True)
details_row.add_suffix(Gtk.Image.new_from_icon_name("arrow2-top-right-symbolic"))
details_row.connect("activated", lambda *_: Gio.AppInfo.launch_default_for_uri(f"appstream://{app_id}", None))
user_data_list.append(details_row)
properties_box.append(user_data_list)
if "runtime" in window.host_flatpaks[index][12]:
dependant_runtimes = my_utils.getDependantRuntimes()
if app_ref in dependant_runtimes:
view_apps = Adw.ActionRow(title=_("Show Apps Using this Runtime"), activatable=True)
view_apps.add_suffix(Gtk.Image.new_from_icon_name("funnel-symbolic"))
view_apps.connect("activated", viewAppsHandler)
user_data_list.append(view_apps)
# view_apps_button = Gtk.Button(icon_name="funnel-symbolic", tooltip_text=_("Show Apps Using this Runtime"))
# view_apps_button.connect("clicked", viewAppsHandler)
# properties_header_bar.pack_start(view_apps_button)
column_headers = [_('Name'), _('Description'), _('App ID'), _('Version'), _('Branch'), _('Arch'), _('Origin'), _('Installation'), _('Ref'), _('Active Commit'), _('Latest Commit'), _('Installed Size'), _('Options'), _('Runtime')]
for column in range(len(window.host_flatpaks[index])):
visible = True
if window.host_flatpaks[index][column] == "":
visible = False
row_item = Adw.ActionRow(title=column_headers[column], activatable=True)
row_item.set_subtitle(GLib.markup_escape_text(window.host_flatpaks[index][column]))
row_item.connect("activated", copy_button_handler, column_headers[column], window.host_flatpaks[index][column])
if column == 13:
runtime_properties_button = Gtk.Button(icon_name="info-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("View Properties"), margin_end=6)
runtime_properties_button.add_css_class("flat")
runtime_properties_button.connect("clicked", showPropertiesHandler, row_item.get_subtitle())
row_item.add_suffix(runtime_properties_button)
if row_item.get_subtitle() in window.eol_list:
row_item.add_css_class("error")
eol_runtime_banner.set_revealed(True)
row_item.set_visible(visible)
row_item.add_suffix(Gtk.Image.new_from_icon_name("edit-copy-symbolic"))
properties_list.append(row_item)
properties_box.append(properties_list)
if "eol" in window.host_flatpaks[index][12]:
eol_app_banner.set_revealed(True)
def maskHandler():
x = my_utils.maskFlatpak(app_id, install_type, True)
if x == 0:
mask_banner.set_revealed(False)
window.flatpak_rows[index][7].set_visible(False) # Sets the mask label invisble
# properties_window.set_default_size(8, 8)
if app_id in system_mask_list or app_id in user_mask_list:
mask_banner.set_revealed(True)
mask_banner.set_button_label(_("Enable Updates"))
mask_banner.connect("button-clicked", lambda *_: maskHandler())
properties_window.set_content(properties_title_bar)
properties_window.present()
self.set_title(_("{} Properties").format(self.app_name))
self.set_size_request(260, 230)
self.set_transient_for(parent_window)
self.present()

View File

@@ -9,6 +9,7 @@
<file preprocess="xml-stripblanks">downgrade.ui</file>
<file preprocess="xml-stripblanks">search_install.ui</file>
<file preprocess="xml-stripblanks">snapshots.ui</file>
<file preprocess="xml-stripblanks">properties.ui</file>
<file>style.css</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<!-- <file preprocess="xml-stripblanks">../data/io.github.flattool.Warehouse.metainfo.xml.in</file> -->

View File

@@ -22,13 +22,15 @@ import subprocess
import re
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
from .properties_window import show_properties_window
from .properties_window import PropertiesWindow
from .filter_window import FilterWindow
from .common import myUtils
from .remotes_window import RemotesWindow
from .downgrade_window import DowngradeWindow
from .snapshots_window import SnapshotsWindow
from .app_row_widget import AppRow
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/window.ui")
class WarehouseWindow(Adw.ApplicationWindow):
__gtype_name__ = "WarehouseWindow"
@@ -273,6 +275,7 @@ class WarehouseWindow(Adw.ApplicationWindow):
# EOL = End Of Life, meaning the app will not be updated
eol_app_label = Gtk.Label(label=_("App EOL"), hexpand=True, wrap=True, justify=Gtk.Justification.RIGHT, valign=Gtk.Align.CENTER, tooltip_text=_("{} has reached its End of Life and will not receive any security updates").format(app_name))
eol_app_label.add_css_class("error")
flatpak_row.add_suffix(eol_app_label)
if self.host_flatpaks[index][13] in self.eol_list:
# EOL = End Of Life, meaning the runtime will not be updated
@@ -287,7 +290,7 @@ class WarehouseWindow(Adw.ApplicationWindow):
properties_button = Gtk.Button(icon_name="info-symbolic", valign=Gtk.Align.CENTER, tooltip_text=_("View Properties"))
properties_button.add_css_class("flat")
properties_button.connect("clicked", show_properties_window, index, self)
properties_button.connect("clicked", lambda *_, index=index: PropertiesWindow(index, self.host_flatpaks, self))
flatpak_row.add_suffix(properties_button)
select_flatpak_tickbox = Gtk.CheckButton(visible=self.in_batch_mode)
@@ -400,6 +403,10 @@ class WarehouseWindow(Adw.ApplicationWindow):
self.applyFilter(self.filter_list)
self.batchActionsEnable(False)
cool_row = AppRow(self, self.host_flatpaks, 7)
print(cool_row.app_id)
self.flatpaks_list_box.append(cool_row)
def openDataFolder(self, path):
try:
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)