3 Commits

8 changed files with 163 additions and 314 deletions

View File

@ -4,31 +4,7 @@ Changelog for shared_libs
- add Info Window for user in delete logfile
bevore delete logfile.
### Added
09.07.2025
- fix new icon for install Update
### Added
29.06.2025
- add new icon for install Update
- replace download with updater methode
- add methode for open lxtools_installer Appimage
- add german translation for logviewer
### Added
15-06-2025
- Update MessageDialog Class description
- import LxTools with try exception.
### Added
14-06-2025

View File

@ -1,13 +1,3 @@
# shared_libs
Module Project for apps by git.ilunix.de
Examples with a Theme from Projekt Wire-Py
# Screenshots
[![info_example.png](https://fb.ilunix.de/api/public/dl/KtaTPMMq?inline=true)](https://fb.ilunix.de/share/KtaTPMMq)
[![error_example.png](https://fb.ilunix.de/api/public/dl/cRO_ksrM?inline=true)](https://fb.ilunix.de/share/cRO_ksrM)
[![warning_example.png](https://fb.ilunix.de/api/public/dl/1JEdSJcI?inline=true)](https://fb.ilunix.de/share/1JEdSJcI)
[![question_light_example.png](https://fb.ilunix.de/api/public/dl/XxNey7y7?inline=true)](https://fb.ilunix.de/share/1XxNey7y7)
[![question_dark_example.png](https://fb.ilunix.de/api/public/dl/4HCxiNwB?inline=true)](https://fb.ilunix.de/share/4HCxiNwB)
[![example_with_own_title_and_icon.png](https://fb.ilunix.de/api/public/dl/uui8b1xx?inline=true)](https://fb.ilunix.de/share/uui8b1xx)
[![logviewer_example.png](https://fb.ilunix.de/api/public/dl/54OM6wUC?inline=true)](https://fb.ilunix.de/share/54OM6wUC)
Module Project for apps by git.ilunix.de

View File

@ -67,7 +67,7 @@ class GiteaUpdate:
"""
try:
to_down: str = f"wget -qP {Path.home()} {' '} {urld}"
to_down: str = f"wget -qP {Path.home()} {" "} {urld}"
result: int = subprocess.call(to_down, shell=True)
if result == 0:
shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000)

Binary file not shown.

View File

@ -1,163 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR polunga40@unity-mail.de, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-29 17:25+0200\n"
"PO-Revision-Date: 2025-06-29 18:00+0200\n"
"Last-Translator: Désiré Werner Menrath <polunga40@unity-mail.de>\n"
"Language-Team: \n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4.2\n"
#: gitea.py:127
msgid "Download Successful"
msgstr "Herunterladen erfolgreich"
#: gitea.py:128
msgid "Your zip file is in home directory"
msgstr "Ihre ZIP-Datei befindet sich im Home-Verzeichnis"
#: gitea.py:129
msgid "Download error"
msgstr "Fehler beim Herunterladen"
#: gitea.py:130
msgid "Download failed! Please try again"
msgstr "Herunterladen fehlgeschlagen! Bitte versuchen Sie es erneut."
#: gitea.py:131
msgid "Download failed! No internet connection!"
msgstr "Herunterladen fehlgeschlagen! Keine Internetverbindung!"
#: logviewer.py:102
msgid "Load Log"
msgstr "Logdatei laden"
#: logviewer.py:107
msgid "Options"
msgstr "Optionen"
#: logviewer.py:116
msgid "Disable Updates"
msgstr "Updates deaktivieren"
#: logviewer.py:149
msgid "About"
msgstr "Über"
#: logviewer.py:184
msgid "Update search off"
msgstr "Suche nach Updates ausgeschaltet"
#: logviewer.py:185
msgid "Updates you have disabled"
msgstr "Sie haben Updates deaktiviert"
#: logviewer.py:192
msgid "No Server Connection!"
msgstr "Keine Verbindung zum Server!"
#: logviewer.py:197
msgid "Could not connect to update server"
msgstr "Verbindung zum Update-Server nicht möglich"
#: logviewer.py:202
msgid "No Updates"
msgstr "Keine Updates verfügbar"
#: logviewer.py:203
msgid "Congratulations! Wire-Py is up to date"
msgstr "Glückwunsch! Wire-Py ist aktuell."
#: logviewer.py:223
msgid "Click to install new version"
msgstr "Klicken Sie, um die neue Version zu installieren"
#: logviewer.py:232
msgid ""
"Logviewer a simple Gui for View Logfiles.\n"
"\n"
"Logviewer is open source software written in Python.\n"
"\n"
"Email: polunga40@unity-mail.de also likes for donation.\n"
"\n"
"Use without warranty!\n"
msgstr ""
"Logviewer eine einfache GUI zur Anzeige von Protokolldateien.\n"
"\n"
"Logviewer ist Open-Source-Software, geschrieben in Python.\n"
"\n"
"E-Mail: polunga40@unity-mail.de (Spenden sind willkommen).\n"
"\n"
"Verwendung ohne Gewähr!\n"
#: logviewer.py:288
msgid "Disable Tooltips"
msgstr "Tooltips deaktivieren"
#: logviewer.py:291
msgid "Enable Tooltips"
msgstr "Tooltips aktivieren"
#: logviewer.py:319
msgid "Dark"
msgstr "Dunkel"
#: logviewer.py:321
msgid "Light"
msgstr "Hell"
#: logviewer.py:362
msgid "Copy"
msgstr "Kopieren"
#: logviewer.py:363
msgid "Paste"
msgstr "Einfügen"
#: logviewer.py:367
msgid "Search"
msgstr "Suchen"
#: logviewer.py:371
msgid "Delete_Log"
msgstr "Logdatei löschen"
#: logviewer.py:456
msgid "A mistake occurred: {str(e)}"
msgstr "Ein Fehler ist aufgetreten: {str(e)}"
#: logviewer.py:457
msgid ""
"A mistake occurred:\n"
"{str(e)}\n"
msgstr ""
"Ein Fehler ist aufgetreten:\n"
"{str(e)}\n"
#: logviewer.py:474
#, python-brace-format
msgid "A mistake occurred: {e}"
msgstr "Ein Fehler ist aufgetreten: {e}"
#: logviewer.py:475
#, python-brace-format
msgid ""
"A mistake occurred:\n"
"{e}\n"
msgstr ""
"Ein Fehler ist aufgetreten:\n"
"{e}\n"
#: logview_app_config.py:146
msgid "Click for Settings"
msgstr "Klick für Einstellungen"

View File

@ -51,14 +51,14 @@ class AppConfig:
# Updates
# 1 = 1. Year, 09 = Month of the Year, 2924 = Day and Year of the Year
VERSION: str = "v. 1.07.0925"
VERSION: str = "v. 1.06.0325"
UPDATE_URL: str = "https://git.ilunix.de/api/v1/repos/punix/shared_libs/releases"
DOWNLOAD_URL: str = "https://git.ilunix.de/punix/shared_libs/archive"
# UI configuration
UI_CONFIG: Dict[str, Any] = {
"window_title2": "LogViewer",
"window_size": (590, 460),
"window_size": (600, 383),
"font_family": "Ubuntu",
"font_size": 11,
"resizable_window": (True, True),
@ -66,6 +66,8 @@ class AppConfig:
# Images and icons paths
IMAGE_PATHS: Dict[str, Path] = {
"icon_info": "/usr/share/icons/lx-icons/64/info.png",
"icon_error": "/usr/share/icons/lx-icons/64/error.png",
"icon_log": "/usr/share/icons/lx-icons/48/log.png",
}

View File

@ -4,9 +4,7 @@ import logging
import tkinter as tk
from tkinter import TclError, filedialog, ttk
from pathlib import Path
import os
import webbrowser
import subprocess
from functools import partial
from shared_libs.gitea import GiteaUpdate
from shared_libs.message import MessageDialog
@ -19,6 +17,7 @@ from shared_libs.common_tools import (
)
import sys
from file_and_dir_ensure import prepare_app_environment
import webbrowser
class LogViewer(tk.Tk):
@ -46,12 +45,9 @@ class LogViewer(tk.Tk):
theme = ConfigManager.get("theme")
ThemeManager.change_theme(self, theme)
LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
self.createWidgets(modul_name, _)
self.createWidgets(_)
self.load_file(_, modul_name=modul_name)
self.log_icon = tk.PhotoImage(file="/usr/share/icons/lx-icons/48/log.png")
self.update_icon = tk.PhotoImage(
file="/usr/share/icons/lx-icons/16/settings.png"
)
self.log_icon = tk.PhotoImage(file=modul_name.AppConfig.IMAGE_PATHS["icon_log"])
self.iconphoto(True, self.log_icon)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=1)
@ -169,15 +165,6 @@ class LogViewer(tk.Tk):
else:
self.updates_lb.grid_remove()
def updater(self):
"""Start the lxtools_installer"""
tmp_dir = Path("/tmp/lxtools")
Path.mkdir(tmp_dir, exist_ok=True)
os.chdir(tmp_dir)
result = subprocess.run(["/usr/local/bin/lxtools_installer"], check=False)
if result.returncode != 0:
MessageDialog("error", result.stderr)
# Update the labels based on the result
def update_ui_for_update(self, res, modul_name, _):
"""Update UI elements based on an update check result"""
@ -214,20 +201,25 @@ class LogViewer(tk.Tk):
else:
self.set_update.set(value=0)
update_text = f"Update {res} {_('available!')}"
# Clear the label text since we'll show the button instead
self.update_label.set("")
# Create the update button
self.update_btn = ttk.Button(
self.menu_frame,
image=self.update_icon,
style="Toolbutton",
command=self.updater,
)
self.update_btn = ttk.Menubutton(self.menu_frame, text=update_text)
self.update_btn.grid(column=5, row=0, padx=0)
Tooltip(
self.update_btn, _("Click to install new version"), self.tooltip_state
self.update_btn, _("Click to download new version"), self.tooltip_state
)
self.download = tk.Menu(self, relief="flat")
self.update_btn.configure(menu=self.download, style="Toolbutton")
self.download.add_command(
label=_("Download"),
command=lambda: GiteaUpdate.download(
f"{modul_name.AppConfig.DOWNLOAD_URL}/{res}.zip", res
),
)
@staticmethod
@ -336,7 +328,7 @@ class LogViewer(tk.Tk):
# Update Menulfield
self.settings.entryconfigure(2, label=self.theme_label.get())
def createWidgets(self, modul_name, _):
def createWidgets(self, _):
text_frame = ttk.Frame(self)
text_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
@ -371,11 +363,11 @@ class LogViewer(tk.Tk):
self.text_area.bind("<Button-3>", self.show_context_menu)
self._entry.bind("<Button-3>", self.show_context_menu)
search_button = ttk.Button(next_frame, text=_("Search"), command=self._onFind)
search_button = ttk.Button(next_frame, text="Search", command=self._onFind)
search_button.grid(row=0, column=0, padx=5, pady=5, sticky=tk.EW)
delete_button = ttk.Button(
next_frame, text=_("Delete_Log"), command=self.delete_file
next_frame, text="Delete_Log", command=self.delete_file
)
delete_button.grid(row=0, column=2, padx=5, pady=5, sticky=tk.EW)
@ -466,7 +458,7 @@ class LogViewer(tk.Tk):
def directory_load(self, modul_name, _):
filepath = filedialog.askopenfilename(
initialdir=f"{Path.home() / '.local/share/lxlogs/'}",
initialdir=f"{Path.home() / ".local/share/lxlogs/"}",
title="Select a Logfile File",
filetypes=[("Logfiles", "*.log")],
)

View File

@ -2,104 +2,155 @@ import os
from typing import List, Optional, Dict
import tkinter as tk
from tkinter import ttk
from shared_libs.common_tools import LxTools
try:
from manager import LxTools
except (ModuleNotFoundError, NameError):
from shared_libs.common_tools import LxTools
"""
####################################################
Attention! MessageDialog returns different values.
From 3 buttons with Cancel, Cancel and the Close (x)
None returns. otherwise always False.
####################################################
Usage Examples
1. Basic Info Dialog
from tkinter import Tk
root = Tk()
dialog = MessageDialog(
message_type="info",
text="This is an information message.",
buttons=["OK"],
master=root,
)
result = dialog.show()
print("User clicked OK:", result)
-----------------------------------------------------
My Favorite Example,
for simply information message:
MessageDialog(text="This is an information message.")
result = MessageDialog(text="This is an information message.").show()
-----------------------------------------------------
Explanation: if you need the return value e.g. in the vaiable result,
you need to add .show(). otherwise only if no root.mainloop z.b is used to test the window.
#####################################################
2. Error Dialog with Custom Icon and Command
def on_cancel():
print("User canceled the operation.")
root = Tk()
result = MessageDialog(
message_type="error",
text="An error occurred during processing.",
buttons=["Retry", "Cancel"],
commands=[None, on_cancel],
icon="/path/to/custom/error_icon.png",
title="Critical Error"
).show()
print("User clicked Retry:", result)
-----------------------------------------------------
My Favorite Example,
for simply Error message:
MessageDialog(
"error",
text="An error occurred during processing.",
).show()
#####################################################
3. Confirmation Dialog with Yes/No Buttons
def on_confirm():
print("User confirmed the action.")
root = Tk()
dialog = MessageDialog(
message_type="ask",
text="Are you sure you want to proceed?",
buttons=["Yes", "No"],
commands=[on_confirm, None], # Either use comando or work with the values True and False
)
result = dialog.show()
print("User confirmed:", result)
-----------------------------------------------------
My Favorite Example,
for simply Question message:
dialog = MessageDialog(
"ask",
text="Are you sure you want to proceed?",
buttons=["Yes", "No"]
).show()
#####################################################
4. Warning Dialog with Custom Title
root = Tk()
dialog = MessageDialog(
message_type="warning",
text="This action cannot be undone.",
buttons=["Proceed", "Cancel"],
title="Warning: Irreversible Action"
)
result = dialog.show()
print("User proceeded:", result)
-----------------------------------------------------
And a special example for a "open link" button:
Be careful not to forget to import it into the script in which this dialog is used!!!
import webbrowser
from functools import partial
dialog = MessageDialog(
"ask",
text="Are you sure you want to proceed?",
buttons=["Yes", "Go to Exapmle"],
commands=[
None, # Default on "OK"
partial(webbrowser.open, "https://exapmle.com"),
],
icon="/pathh/to/custom/icon.png",
title="Example",
).show()
In all dialogues, a font can also be specified as a tuple. With font=("ubuntu", 11)
and wraplength=300, the text is automatically wrapped.
"""
class MessageDialog:
"""
A customizable message dialog window using tkinter for user interaction.
A customizable message dialog window using tkinter.
This class creates modal dialogs for displaying information, warnings, errors,
or questions to the user. It supports multiple button configurations, custom
icons, keyboard navigation, and command binding. The dialog is centered on the
screen and handles user interactions with focus management and accessibility.
or questions to the user. It supports multiple button configurations and custom
icons. The dialog is centered on the screen and handles user interactions.
Attributes:
message_type (str): Type of message ("info", "error", "warning", "ask").
text (str): Main message content to display.
text (str): Main message content.
buttons (List[str]): List of button labels (e.g., ["OK", "Cancel"]).
result (bool or None):
- True for positive actions (Yes, OK)
- False for negative actions (No, Cancel)
- None if "Cancel" was clicked with ≥3 buttons
icons: Dictionary mapping message types to tkinter.PhotoImage objects
result (bool): True if the user clicked a positive button (like "Yes" or "OK"), else False.
icons: Dictionary mapping message types to tkinter.PhotoImage objects.
Parameters:
message_type: Type of message dialog (default: "info")
text: Message content to display
buttons: List of button labels (default: ["OK"])
master: Parent tkinter window (optional)
commands: List of callables for each button (default: [None])
icon: Custom icon path (overrides default icons if provided)
title: Window title (default: derived from message_type)
font: Font tuple for text styling
wraplength: Text wrapping width in pixels
message_type: Type of message dialog (default: "info").
text: Message content to display.
buttons: List of button labels (default: ["OK"]).
master: Parent tkinter window (optional).
commands: List of callables for each button (default: [None]).
icon: Custom icon path (overrides default icons if provided).
title: Window title (default: derived from message_type).
Methods:
_get_title(): Returns the default window title based on message type.
_load_icons(): Loads icons from system paths or fallback locations.
_on_button_click(button_text): Sets result and closes the dialog.
show(): Displays the dialog and waits for user response.
Example Usage:
1. Basic Info Dialog:
>>> MessageDialog(
... text="This is an information message.")
>>> result = dialog.show()
>>> print("User clicked OK:", result)
Notes:
My Favorite Example,
for simply information message:
>>> MessageDialog(text="This is an information message.")
>>> result = MessageDialog(text="This is an information message.").show()
Example Usage:
2. Error Dialog with Custom Command:
>>> def on_retry():
... print("User selected Retry")
>>> dialog = MessageDialog(
... message_type="error",
... text="An error occurred during processing.",
... buttons=["Retry", "Cancel"],
... commands=[on_retry, None],
... title="Critical Error"
... )
>>> result = dialog.show()
>>> print("User selected Retry:", result)
Example Usage:
3. And a special example for a "open link" button:
Be careful not to forget to import it into the script in which
this dialog is used!!! import webbrowser from functools import partial
>>> MessageDialog(
... "info"
... text="This is an information message.",
... buttons=["Yes", "Go to Exapmle"],
... commands=[
... None, # Default on "OK"
... partial(webbrowser.open, "https://exapmle.com"),
... ],
... icon="/pathh/to/custom/icon.png",
... title="Example",
... )
Notes:
- Returns None if "Cancel" was clicked with ≥3 buttons
- Supports keyboard navigation (Left/Right arrows and Enter)
- Dialog automatically centers on screen
- Result is False for window close (X) with 2 buttons
- Font and wraplength parameters enable text styling
show(): Displays the dialog and waits for user response (returns self.result).
"""
DEFAULT_ICON_PATH = "/usr/share/icons/lx-icons"
@ -129,7 +180,7 @@ class MessageDialog:
self.window = tk.Toplevel(master)
self.window.grab_set()
self.window.resizable(False, False)
ttk.Style().configure("TButton")
ttk.Style().configure("TButton", font=("Helvetica", 11), padding=5)
self.buttons_widgets = []
self.current_button_index = 0
self._load_icons()
@ -140,7 +191,7 @@ class MessageDialog:
# Layout
frame = ttk.Frame(self.window)
frame.pack(expand=True, fill="both", padx=15, pady=8)
frame.pack(expand=True, fill="both")
# Grid-Configuration
frame.grid_rowconfigure(0, weight=1)
@ -167,12 +218,13 @@ class MessageDialog:
row=0,
column=1,
padx=(10, 20),
pady=(8, 20),
sticky="nsew",
)
# Create button frame
self.button_frame = ttk.Frame(frame)
self.button_frame.grid(row=1, columnspan=2, pady=(8, 10))
self.button_frame.grid(row=1, columnspan=2, pady=(15, 10))
for i, btn_text in enumerate(buttons):
if commands and len(commands) > i and commands[i] is not None:
@ -191,7 +243,7 @@ class MessageDialog:
)
padx_value = 50 if self.icon is not None and len(buttons) == 2 else 10
btn.pack(side="left" if i == 0 else "right", padx=padx_value, pady=5)
btn.pack(side="left" if i == 0 else "right", padx=padx_value, pady=15)
btn.focus_set() if i == 0 else None # Set focus on first button
self.buttons_widgets.append(btn)
@ -257,12 +309,12 @@ class MessageDialog:
if os.path.exists(icon_paths[key]):
self.icons[key] = tk.PhotoImage(file=icon_paths[key])
else:
if os.path.exists(fallback_paths[key]):
self.icons[key] = tk.PhotoImage(file=fallback_paths[key])
self.icons[key] = tk.PhotoImage(file=fallback_paths[key])
except Exception as e:
print(f"Error on load Icon '{[key]}': {e}")
self.icons[key] = tk.PhotoImage()
print(f"⚠️ No Icon found for '{key}'. Use standard Tkinter icon.")
return self.icons
def _get_icon_path(self) -> str: