Reduced redundancy, logviewer fulll removed , add log_window and menu_bar
This commit is contained in:
513
common_tools.py
513
common_tools.py
@@ -1,16 +1,21 @@
|
||||
""" Classes Method and Functions for lx Apps """
|
||||
" Classes Method and Functions for lx Apps "
|
||||
|
||||
import logging
|
||||
import signal
|
||||
import base64
|
||||
from contextlib import contextmanager
|
||||
|
||||
from .logger import app_logger
|
||||
from subprocess import CompletedProcess, run
|
||||
import gettext
|
||||
import locale
|
||||
import re
|
||||
import sys
|
||||
import shutil
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import os
|
||||
from typing import Optional, Dict, Any, NoReturn
|
||||
from pathlib import Path
|
||||
from tkinter import Toplevel
|
||||
|
||||
|
||||
class CryptoUtil:
|
||||
@@ -34,14 +39,14 @@ class CryptoUtil:
|
||||
|
||||
# Output from Openssl Error
|
||||
if process.stderr:
|
||||
logging.error(process.stderr, exc_info=True)
|
||||
app_logger.log(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
logging.info("Files successfully decrypted...", exc_info=True)
|
||||
app_logger.log("Files successfully decrypted...")
|
||||
else:
|
||||
|
||||
logging.error(
|
||||
f"Error process decrypt: Code {process.returncode}", exc_info=True
|
||||
app_logger.log(
|
||||
f"Error process decrypt: Code {process.returncode}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -58,13 +63,13 @@ class CryptoUtil:
|
||||
|
||||
# Output from Openssl Error
|
||||
if process.stderr:
|
||||
logging.error(process.stderr, exc_info=True)
|
||||
app_logger.log(process.stderr)
|
||||
|
||||
if process.returncode == 0:
|
||||
logging.info("Files successfully encrypted...", exc_info=True)
|
||||
app_logger.log("Files successfully encrypted...")
|
||||
else:
|
||||
logging.error(
|
||||
f"Error process encrypt: Code {process.returncode}", exc_info=True
|
||||
app_logger.log(
|
||||
f"Error process encrypt: Code {process.returncode}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -83,9 +88,8 @@ class CryptoUtil:
|
||||
return True
|
||||
elif "False" in process.stdout:
|
||||
return False
|
||||
logging.error(
|
||||
f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}",
|
||||
exc_info=True,
|
||||
app_logger.log(
|
||||
f"Unexpected output from the external script:\nSTDOUT: {process.stdout}\nSTDERR: {process.stderr}"
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -110,7 +114,7 @@ class CryptoUtil:
|
||||
if len(decoded) != 32: # 32 bytes = 256 bits
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(f"Error on decode Base64: {e}", exc_info=True)
|
||||
app_logger.log(f"Error on decode Base64: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -273,17 +277,16 @@ class LxTools:
|
||||
# End program for certain signals, report to others only reception
|
||||
if signum in (signal.SIGINT, signal.SIGTERM):
|
||||
exit_code: int = 1
|
||||
logging.error(
|
||||
f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.",
|
||||
exc_info=True,
|
||||
app_logger.log(
|
||||
f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}."
|
||||
)
|
||||
LxTools.clean_files(file_path, file)
|
||||
logging.info("Breakdown by user...")
|
||||
app_logger.log("Breakdown by user...")
|
||||
sys.exit(exit_code)
|
||||
else:
|
||||
logging.info(f"Signal {signum} received and ignored.")
|
||||
app_logger.log(f"Signal {signum} received and ignored.")
|
||||
LxTools.clean_files(file_path, file)
|
||||
logging.error("Process unexpectedly ended...")
|
||||
app_logger.log("Process unexpectedly ended...")
|
||||
|
||||
# Register signal handlers for various signals
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
@@ -324,14 +327,14 @@ class ConfigManager:
|
||||
"""Load the config file and return the config as dict"""
|
||||
if not cls._config:
|
||||
try:
|
||||
lines = Path(cls._config_file).read_text(encoding="utf-8").splitlines()
|
||||
lines = Path(cls._config_file).read_text(
|
||||
encoding="utf-8").splitlines()
|
||||
cls._config = {
|
||||
"updates": lines[1].strip(),
|
||||
"theme": lines[3].strip(),
|
||||
"tooltips": lines[5].strip()
|
||||
== "True", # is converted here to boolean!!!
|
||||
"autostart": lines[7].strip() if len(lines) > 7 else "off",
|
||||
"logfile": lines[9].strip(),
|
||||
}
|
||||
except (IndexError, FileNotFoundError):
|
||||
# DeDefault values in case of error
|
||||
@@ -340,7 +343,6 @@ class ConfigManager:
|
||||
"theme": "light",
|
||||
"tooltips": "True", # Default Value as string!
|
||||
"autostart": "off",
|
||||
"logfile": LOG_FILE_PATH,
|
||||
}
|
||||
return cls._config
|
||||
|
||||
@@ -357,8 +359,6 @@ class ConfigManager:
|
||||
f"{str(cls._config['tooltips'])}\n",
|
||||
"# Autostart\n",
|
||||
f"{cls._config['autostart']}\n",
|
||||
"# Logfile\n",
|
||||
f"{cls._config['logfile']}\n",
|
||||
]
|
||||
Path(cls._config_file).write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
@@ -396,110 +396,435 @@ class ThemeManager:
|
||||
|
||||
@staticmethod
|
||||
def change_theme(root, theme_in_use, theme_name=None):
|
||||
"""Change application theme centrally"""
|
||||
"""
|
||||
Change application theme centrally.
|
||||
|
||||
Args:
|
||||
root: The root Tkinter window.
|
||||
theme_in_use (str): The name of the theme to apply.
|
||||
theme_name (Optional[str]): The name of the theme to save in the config.
|
||||
If None, the theme is not saved.
|
||||
"""
|
||||
root.tk.call("set_theme", theme_in_use)
|
||||
if theme_in_use == theme_name:
|
||||
ConfigManager.set("theme", theme_in_use)
|
||||
|
||||
|
||||
class Tooltip:
|
||||
"""Class for Tooltip
|
||||
from common_tools.py import Tooltip
|
||||
example: Tooltip(label, "Show tooltip on label")
|
||||
example: Tooltip(button, "Show tooltip on button")
|
||||
example: Tooltip(widget, "Text", state_var=tk.BooleanVar())
|
||||
example: Tooltip(widget, "Text", state_var=tk.BooleanVar(), x_offset=20, y_offset=10)
|
||||
"""
|
||||
A flexible tooltip class for Tkinter widgets that supports dynamic activation/deactivation.
|
||||
|
||||
info: label and button are parent widgets.
|
||||
NOTE: When using with state_var, pass the tk.BooleanVar object directly,
|
||||
NOT its value. For example: use state_var=my_bool_var, NOT state_var=my_bool_var.get()
|
||||
This class provides customizable tooltips that appear when the mouse hovers over a widget.
|
||||
It can be used for simple, always-active tooltips or for tooltips whose visibility is
|
||||
controlled by a `tk.BooleanVar`, allowing for global enable/disable functionality.
|
||||
|
||||
Attributes:
|
||||
widget (tk.Widget): The Tkinter widget to which the tooltip is attached.
|
||||
text (str): The text to display in the tooltip.
|
||||
wraplength (int): The maximum line length for the tooltip text before wrapping.
|
||||
state_var (Optional[tk.BooleanVar]): An optional Tkinter BooleanVar that controls
|
||||
the visibility of the tooltip. If True, the tooltip
|
||||
is active; if False, it is inactive. If None, the
|
||||
tooltip is always active.
|
||||
tooltip_window (Optional[tk.Toplevel]): The Toplevel window used to display the tooltip.
|
||||
id (Optional[str]): The ID of the `after` job used to schedule the tooltip display.
|
||||
|
||||
Usage Examples:
|
||||
# 1. Simple Tooltip (always active):
|
||||
# Tooltip(my_button, "This is a simple tooltip.")
|
||||
|
||||
# 2. State-Controlled Tooltip (can be enabled/disabled globally):
|
||||
# tooltip_state = tk.BooleanVar(value=True)
|
||||
# Tooltip(my_button, "This tooltip can be turned off!", state_var=tooltip_state)
|
||||
# # To toggle visibility:
|
||||
# # tooltip_state.set(False) # Tooltips will hide
|
||||
# # tooltip_state.set(True) # Tooltips will show again
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
widget: Any,
|
||||
text: str,
|
||||
state_var: Optional[tk.BooleanVar] = None,
|
||||
x_offset: int = 65,
|
||||
y_offset: int = 40,
|
||||
) -> None:
|
||||
"""Tooltip Class"""
|
||||
self.widget: Any = widget
|
||||
self.text: str = text
|
||||
self.tooltip_window: Optional[Toplevel] = None
|
||||
def __init__(self, widget, text, wraplength=250, state_var=None):
|
||||
self.widget = widget
|
||||
self.text = text
|
||||
self.wraplength = wraplength
|
||||
self.state_var = state_var
|
||||
self.x_offset = x_offset
|
||||
self.y_offset = y_offset
|
||||
|
||||
# Initial binding based on the current state
|
||||
self.tooltip_window = None
|
||||
self.id = None
|
||||
self.update_bindings()
|
||||
|
||||
# Add trace to the state_var if provided
|
||||
if self.state_var is not None:
|
||||
if self.state_var:
|
||||
self.state_var.trace_add("write", self.update_bindings)
|
||||
|
||||
def update_bindings(self, *args) -> None:
|
||||
"""Updates the bindings based on the current state"""
|
||||
# Remove existing bindings first
|
||||
def update_bindings(self, *args):
|
||||
"""
|
||||
Updates the event bindings for the widget based on the current state_var.
|
||||
If state_var is True or None, the <Enter>, <Leave>, and <ButtonPress> events
|
||||
are bound to show/hide the tooltip. Otherwise, they are unbound.
|
||||
"""
|
||||
self.widget.unbind("<Enter>")
|
||||
self.widget.unbind("<Leave>")
|
||||
self.widget.unbind("<ButtonPress>")
|
||||
|
||||
# Add new bindings if tooltips are enabled
|
||||
if self.state_var is None or self.state_var.get():
|
||||
self.widget.bind("<Enter>", self.show_tooltip)
|
||||
self.widget.bind("<Leave>", self.hide_tooltip)
|
||||
self.widget.bind("<Enter>", self.enter)
|
||||
self.widget.bind("<Leave>", self.leave)
|
||||
self.widget.bind("<ButtonPress>", self.leave)
|
||||
|
||||
def show_tooltip(self, event: Optional[Any] = None) -> None:
|
||||
"""Shows the tooltip"""
|
||||
def enter(self, event=None):
|
||||
"""
|
||||
Handles the <Enter> event. Schedules the tooltip to be shown after a delay
|
||||
if tooltips are enabled (via state_var).
|
||||
"""
|
||||
try:
|
||||
# Check if a modal dialog has the grab on the entire application
|
||||
if self.winfo_toplevel().grab_current() is not None:
|
||||
return
|
||||
except Exception:
|
||||
# This can happen if no grab is active. We can safely ignore it.
|
||||
pass
|
||||
|
||||
if self.state_var is None or self.state_var.get():
|
||||
self.schedule()
|
||||
|
||||
def leave(self, event=None):
|
||||
"""
|
||||
Handles the <Leave> event. Unschedules any pending tooltip display
|
||||
and immediately hides any visible tooltip.
|
||||
"""
|
||||
self.unschedule()
|
||||
self.hide_tooltip()
|
||||
|
||||
def schedule(self):
|
||||
"""
|
||||
Schedules the `show_tooltip` method to be called after a short delay.
|
||||
Cancels any previously scheduled calls to prevent flickering.
|
||||
"""
|
||||
self.unschedule()
|
||||
self.id = self.widget.after(250, self.show_tooltip)
|
||||
|
||||
def unschedule(self):
|
||||
"""
|
||||
Cancels any pending `show_tooltip` calls.
|
||||
"""
|
||||
id = self.id
|
||||
self.id = None
|
||||
if id:
|
||||
self.widget.after_cancel(id)
|
||||
|
||||
def show_tooltip(self, event=None):
|
||||
"""
|
||||
Displays the tooltip window. The tooltip is a Toplevel window containing a ttk.Label.
|
||||
It is positioned near the widget and styled for readability.
|
||||
"""
|
||||
if self.tooltip_window or not self.text:
|
||||
return
|
||||
|
||||
x: int
|
||||
y: int
|
||||
cx: int
|
||||
cy: int
|
||||
|
||||
x, y, cx, cy = self.widget.bbox("insert")
|
||||
x += self.widget.winfo_rootx() + self.x_offset
|
||||
y += self.widget.winfo_rooty() + self.y_offset
|
||||
|
||||
x, y, _, _ = self.widget.bbox("insert")
|
||||
x += self.widget.winfo_rootx() + 25
|
||||
y += self.widget.winfo_rooty() + 20
|
||||
self.tooltip_window = tw = tk.Toplevel(self.widget)
|
||||
tw.wm_overrideredirect(True)
|
||||
tw.wm_geometry(f"+{x}+{y}")
|
||||
tw.wm_geometry(f"+" + str(x) + "+" + str(y))
|
||||
label = ttk.Label(tw, text=self.text, justify=tk.LEFT, background="#FFFFE0", foreground="black",
|
||||
relief=tk.SOLID, borderwidth=1, wraplength=self.wraplength, padding=(4, 2, 4, 2))
|
||||
label.pack(ipadx=1)
|
||||
|
||||
label: tk.Label = tk.Label(
|
||||
tw,
|
||||
text=self.text,
|
||||
background="lightgreen",
|
||||
foreground="black",
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
padx=5,
|
||||
pady=5,
|
||||
)
|
||||
label.grid()
|
||||
|
||||
self.tooltip_window.after(2200, lambda: tw.destroy())
|
||||
|
||||
def hide_tooltip(self, event: Optional[Any] = None) -> None:
|
||||
"""Hides the tooltip"""
|
||||
if self.tooltip_window:
|
||||
self.tooltip_window.destroy()
|
||||
self.tooltip_window = None
|
||||
def hide_tooltip(self):
|
||||
"""
|
||||
Hides and destroys the tooltip window if it is currently visible.
|
||||
"""
|
||||
tw = self.tooltip_window
|
||||
self.tooltip_window = None
|
||||
if tw:
|
||||
tw.destroy()
|
||||
|
||||
|
||||
class LogConfig:
|
||||
"""
|
||||
A static class for configuring application-wide logging.
|
||||
|
||||
This class provides a convenient way to set up file-based logging for the application.
|
||||
It ensures that log messages are written to a specified file with a consistent format.
|
||||
|
||||
Methods:
|
||||
logger(file_path: str) -> None:
|
||||
Configures the root logger to write messages to the specified file.
|
||||
|
||||
Usage Example:
|
||||
# Assuming LOG_FILE_PATH is defined elsewhere (e.g., in a config file)
|
||||
# LogConfig.logger(LOG_FILE_PATH)
|
||||
# logging.info("This message will be written to the log file.")
|
||||
"""
|
||||
@staticmethod
|
||||
def logger(file_path) -> None:
|
||||
"""
|
||||
Configures the root logger to write messages to the specified file.
|
||||
|
||||
Args:
|
||||
file_path (str): The absolute path to the log file.
|
||||
"""
|
||||
file_handler = logging.FileHandler(
|
||||
filename=f"{file_path}",
|
||||
mode="a",
|
||||
encoding="utf-8",
|
||||
)
|
||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(levelname)s - %(message)s")
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.DEBUG) # Set the root logger level
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
|
||||
class IconManager:
|
||||
"""
|
||||
A class for central management and loading of application icons.
|
||||
|
||||
This class loads Tkinter PhotoImage objects from a specified base path,
|
||||
organizing them by logical names and providing a convenient way to retrieve them.
|
||||
It handles potential errors during image loading by creating a blank image placeholder.
|
||||
|
||||
Attributes:
|
||||
base_path (str): The base directory where icon subfolders (e.g., '16', '32', '48', '64') are located.
|
||||
icons (Dict[str, tk.PhotoImage]): A dictionary storing loaded PhotoImage objects,
|
||||
keyed by their logical names (e.g., 'computer_small', 'folder_large').
|
||||
|
||||
Methods:
|
||||
get_icon(name: str) -> Optional[tk.PhotoImage]:
|
||||
Retrieves a loaded icon by its logical name.
|
||||
|
||||
Usage Example:
|
||||
# Initialize the IconManager with the path to your icon directory
|
||||
# icon_manager = IconManager(base_path="/usr/share/icons/lx-icons/")
|
||||
|
||||
# Retrieve an icon
|
||||
# computer_icon = icon_manager.get_icon("computer_small")
|
||||
# if computer_icon:
|
||||
# my_label = tk.Label(root, image=computer_icon)
|
||||
# my_label.pack()
|
||||
"""
|
||||
|
||||
def __init__(self, base_path='/usr/share/icons/lx-icons/'):
|
||||
self.base_path = base_path
|
||||
self.icons = {}
|
||||
self._define_icon_paths()
|
||||
self._load_all()
|
||||
|
||||
def _define_icon_paths(self):
|
||||
self.icon_paths = {
|
||||
# 16x16
|
||||
'settings_16': '16/settings.png',
|
||||
|
||||
# 32x32
|
||||
'back': '32/arrow-left.png',
|
||||
'forward': '32/arrow-right.png',
|
||||
'up': '32/arrow-up.png',
|
||||
'audio_small': '32/audio.png',
|
||||
'icon_view': '32/carrel.png',
|
||||
'computer_small': '32/computer.png',
|
||||
'device_small': '32/device.png',
|
||||
'file_small': '32/document.png',
|
||||
'download_error_small': '32/download_error.png',
|
||||
'download_small': '32/download.png',
|
||||
'error_small': '32/error.png',
|
||||
'python_small': '32/file-python.png',
|
||||
'documents_small': '32/folder-water-documents.png',
|
||||
'downloads_small': '32/folder-water-download.png',
|
||||
'music_small': '32/folder-water-music.png',
|
||||
'pictures_small': '32/folder-water-pictures.png',
|
||||
'folder_small': '32/folder-water.png',
|
||||
'video_small': '32/folder-water-video.png',
|
||||
'hide': '32/hide.png',
|
||||
'home': '32/home.png',
|
||||
'about': '32/about.png',
|
||||
'info_small': '32/info.png',
|
||||
'light_small': '32/light.png',
|
||||
'dark_small': '32/dark.png',
|
||||
'update_small': '32/update.png',
|
||||
'no_update_small': '32/no_update.png',
|
||||
'tooltip_small': '32/tip.png',
|
||||
'no_tooltip_small': '32/no_tip.png',
|
||||
'list_view': '32/list.png',
|
||||
'log_small': '32/log.png',
|
||||
'log_blue_small': '32/log_blue.png',
|
||||
'lunix_tools_small': '32/Lunix_Tools.png',
|
||||
'key_small': '32/lxtools_key.png',
|
||||
'iso_small': '32/media-optical.png',
|
||||
'new_document_small': '32/new-document.png',
|
||||
'new_folder_small': '32/new-folder.png',
|
||||
'pdf_small': '32/pdf.png',
|
||||
'picture_small': '32/picture.png',
|
||||
'question_mark_small': '32/question_mark.png',
|
||||
'recursive_small': '32/recursive.png',
|
||||
'search_small': '32/search.png',
|
||||
'settings_small': '32/settings.png',
|
||||
'settings-2_small': '32/settings-2.png',
|
||||
'archive_small': '32/tar.png',
|
||||
'unhide': '32/unhide.png',
|
||||
'usb_small': '32/usb.png',
|
||||
'video_small_file': '32/video.png',
|
||||
'warning_small': '32/warning.png',
|
||||
'export_small': '32/wg_export.png',
|
||||
'import_small': '32/wg_import.png',
|
||||
'message_small': '32/wg_msg.png',
|
||||
'trash_small': '32/wg_trash.png',
|
||||
'trash_small2': '32/trash.png',
|
||||
'vpn_small': '32/wg_vpn.png',
|
||||
'vpn_start_small': '32/wg_vpn-start.png',
|
||||
'vpn_stop_small': '32/wg_vpn-stop.png',
|
||||
|
||||
# 48x48
|
||||
'back_large': '48/arrow-left.png',
|
||||
'forward_large': '48/arrow-right.png',
|
||||
'up_large': '48/arrow-up.png',
|
||||
'icon_view_large': '48/carrel.png',
|
||||
'computer_large': '48/computer.png',
|
||||
'device_large': '48/device.png',
|
||||
'download_error_large': '48/download_error.png',
|
||||
'download_large': '48/download.png',
|
||||
'error_large': '48/error.png',
|
||||
'documents_large': '48/folder-water-documents.png',
|
||||
'downloads_large': '48/folder-water-download.png',
|
||||
'music_large': '48/folder-water-music.png',
|
||||
'pictures_large': '48/folder-water-pictures.png',
|
||||
'folder_large_48': '48/folder-water.png',
|
||||
'video_large_folder': '48/folder-water-video.png',
|
||||
'hide_large': '48/hide.png',
|
||||
'home_large': '48/home.png',
|
||||
'info_large': '48/info.png',
|
||||
'light_large': '48/light.png',
|
||||
'dark_large': '48/dark.png',
|
||||
'update_large': '48/update.png',
|
||||
'no_update_large': '48/no_update.png',
|
||||
'tooltip_large': '48/tip.png',
|
||||
'no_tooltip_large': '48/no_tip.png',
|
||||
'about_large': '48/about.png',
|
||||
'list_view_large': '48/list.png',
|
||||
'log_large': '48/log.png',
|
||||
'log_blue_large': '48/log_blue.png',
|
||||
'lunix_tools_large': '48/Lunix_Tools.png',
|
||||
'new_document_large': '48/new-document.png',
|
||||
'new_folder_large': '48/new-folder.png',
|
||||
'question_mark_large': '48/question_mark.png',
|
||||
'search_large_48': '48/search.png',
|
||||
'settings_large': '48/settings.png',
|
||||
'unhide_large': '48/unhide.png',
|
||||
'usb_large': '48/usb.png',
|
||||
'warning_large_48': '48/warning.png',
|
||||
'export_large': '48/wg_export.png',
|
||||
'import_large': '48/wg_import.png',
|
||||
'message_large': '48/wg_msg.png',
|
||||
'trash_large': '48/wg_trash.png',
|
||||
'trash_large2': '48/trash.png',
|
||||
'vpn_large': '48/wg_vpn.png',
|
||||
'vpn_start_large': '48/wg_vpn-start.png',
|
||||
'vpn_stop_large': '48/wg_vpn-stop.png',
|
||||
|
||||
# 64x64
|
||||
'back_extralarge': '64/arrow-left.png',
|
||||
'forward_extralarge': '64/arrow-right.png',
|
||||
'up_extralarge': '64/arrow-up.png',
|
||||
'audio_large': '64/audio.png',
|
||||
'icon_view_extralarge': '64/carrel.png',
|
||||
'computer_extralarge': '64/computer.png',
|
||||
'device_extralarge': '64/device.png',
|
||||
'file_large': '64/document.png',
|
||||
'download_error_extralarge': '64/download_error.png',
|
||||
'download_extralarge': '64/download.png',
|
||||
'error_extralarge': '64/error.png',
|
||||
'python_large': '64/file-python.png',
|
||||
'documents_extralarge': '64/folder-water-documents.png',
|
||||
'downloads_extralarge': '64/folder-water-download.png',
|
||||
'music_extralarge': '64/folder-water-music.png',
|
||||
'pictures_extralarge': '64/folder-water-pictures.png',
|
||||
'folder_large': '64/folder-water.png',
|
||||
'video_extralarge_folder': '64/folder-water-video.png',
|
||||
'hide_extralarge': '64/hide.png',
|
||||
'home_extralarge': '64/home.png',
|
||||
'info_extralarge': '64/info.png',
|
||||
'light_extralarge': '64/light.png',
|
||||
'dark_extralarge': '64/dark.png',
|
||||
'update_extralarge': '64/update.png',
|
||||
'no_update_extralarge': '64/no_update.png',
|
||||
'tooltip_extralarge': '64/tip.png',
|
||||
'no_tooltip_extralarge': '64/no_tip.png',
|
||||
'about_extralarge': '64/about.png',
|
||||
'list_view_extralarge': '64/list.png',
|
||||
'log_extralarge': '64/log.png',
|
||||
'log_blue_extralarge': '64/log_blue.png',
|
||||
'lunix_tools_extralarge': '64/Lunix_Tools.png',
|
||||
'iso_large': '64/media-optical.png',
|
||||
'new_document_extralarge': '64/new-document.png',
|
||||
'new_folder_extralarge': '64/new-folder.png',
|
||||
'pdf_large': '64/pdf.png',
|
||||
'picture_large': '64/picture.png',
|
||||
'question_mark_extralarge': '64/question_mark.png',
|
||||
'recursive_large': '64/recursive.png',
|
||||
'search_large': '64/search.png',
|
||||
'settings_extralarge': '64/settings.png',
|
||||
'archive_large': '64/tar.png',
|
||||
'unhide_extralarge': '64/unhide.png',
|
||||
'usb_extralarge': '64/usb.png',
|
||||
'video_large': '64/video.png',
|
||||
'warning_large': '64/warning.png',
|
||||
'export_extralarge': '64/wg_export.png',
|
||||
'import_extralarge': '64/wg_import.png',
|
||||
'message_extralarge': '64/wg_msg.png',
|
||||
'trash_extralarge': '64/wg_trash.png',
|
||||
'trash_extralarge2': '64/trash.png',
|
||||
'vpn_extralarge': '64/wg_vpn.png',
|
||||
'vpn_start_extralarge': '64/wg_vpn-start.png',
|
||||
'vpn_stop_extralarge': '64/wg_vpn-stop.png',
|
||||
}
|
||||
|
||||
def _load_all(self):
|
||||
for key, rel_path in self.icon_paths.items():
|
||||
full_path = os.path.join(self.base_path, rel_path)
|
||||
try:
|
||||
self.icons[key] = tk.PhotoImage(file=full_path)
|
||||
except tk.TclError as e:
|
||||
print(f"Error loading icon '{key}' from '{full_path}': {e}")
|
||||
size = 32 # Default size
|
||||
if '16' in rel_path:
|
||||
size = 16
|
||||
elif '48' in rel_path:
|
||||
size = 48
|
||||
elif '64' in rel_path:
|
||||
size = 64
|
||||
self.icons[key] = tk.PhotoImage(width=size, height=size)
|
||||
|
||||
def get_icon(self, name):
|
||||
return self.icons.get(name)
|
||||
|
||||
|
||||
class Translate:
|
||||
|
||||
@staticmethod
|
||||
def setup_translations(app_name: str, locale_dir="/usr/share/locale/") -> gettext.gettext:
|
||||
"""
|
||||
Initialize translations and set the translation function
|
||||
Special method for translating strings in this file
|
||||
|
||||
Returns:
|
||||
The gettext translation function
|
||||
"""
|
||||
locale.bindtextdomain(app_name, locale_dir)
|
||||
gettext.bindtextdomain(app_name, locale_dir)
|
||||
gettext.textdomain(app_name)
|
||||
return gettext.gettext
|
||||
|
||||
|
||||
@contextmanager
|
||||
def message_box_animation(animated_icon):
|
||||
"""
|
||||
A context manager to handle starting and stopping an animated icon
|
||||
around an operation like showing a message box.
|
||||
|
||||
Args:
|
||||
animated_icon: The animated icon object with start() and stop() methods.
|
||||
"""
|
||||
if animated_icon:
|
||||
animated_icon.stop()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if animated_icon:
|
||||
animated_icon.start()
|
||||
|
||||
Reference in New Issue
Block a user