Compare commits
2 Commits
cf0288b519
...
781488c16f
Author | SHA1 | Date | |
---|---|---|---|
781488c16f | |||
c2552696e4 |
@ -5,6 +5,12 @@ Changelog for shared_libs
|
||||
- add Info Window for user in delete logfile
|
||||
bevore delete logfile.
|
||||
|
||||
### Added
|
||||
14-06-2025
|
||||
|
||||
- Added new MessageDialog module
|
||||
and replace LxTools.msg_window() with MessageDialog.
|
||||
|
||||
### Added
|
||||
03-06-2025
|
||||
|
||||
|
@ -239,72 +239,6 @@ class LxTools:
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def msg_window(
|
||||
image_path: Path,
|
||||
image_path2: Path,
|
||||
w_title: str,
|
||||
w_txt: str,
|
||||
txt2: Optional[str] = None,
|
||||
com: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Creates message windows
|
||||
|
||||
:param image_path2:
|
||||
:param image_path:
|
||||
AppConfig.IMAGE_PATHS["icon_info"] = Image for TK window which is displayed to the left of the text
|
||||
AppConfig.IMAGE_PATHS["icon_vpn"] = Image for Task Icon
|
||||
:argument w_title = Windows Title
|
||||
:argument w_txt = Text for Tk Window
|
||||
:argument txt2 = Text for Button two
|
||||
:argument com = function for Button command
|
||||
"""
|
||||
msg: tk.Toplevel = tk.Toplevel()
|
||||
msg.resizable(width=False, height=False)
|
||||
msg.title(w_title)
|
||||
msg.configure(pady=15, padx=15)
|
||||
|
||||
# load first image for a window
|
||||
try:
|
||||
msg.img = tk.PhotoImage(file=image_path)
|
||||
msg.i_window = tk.Label(msg, image=msg.img)
|
||||
except Exception as e:
|
||||
logging.error(f"Error on load Window Image: {e}", exc_info=True)
|
||||
msg.i_window = tk.Label(msg, text="Image not found")
|
||||
|
||||
label: tk.Label = tk.Label(msg, text=w_txt)
|
||||
label.grid(column=1, row=0)
|
||||
|
||||
if txt2 is not None and com is not None:
|
||||
label.config(font=("Ubuntu", 11), padx=15, justify="left")
|
||||
msg.i_window.grid(column=0, row=0, sticky="nw")
|
||||
button2: ttk.Button = ttk.Button(
|
||||
msg, text=f"{txt2}", command=com, padding=4
|
||||
)
|
||||
button2.grid(column=0, row=1, sticky="e", columnspan=2)
|
||||
button: ttk.Button = ttk.Button(
|
||||
msg, text="OK", command=msg.destroy, padding=4
|
||||
)
|
||||
button.grid(column=0, row=1, sticky="w", columnspan=2)
|
||||
else:
|
||||
label.config(font=("Ubuntu", 11), padx=15)
|
||||
msg.i_window.grid(column=0, row=0)
|
||||
button: ttk.Button = ttk.Button(
|
||||
msg, text="OK", command=msg.destroy, padding=4
|
||||
)
|
||||
button.grid(column=0, columnspan=2, row=1)
|
||||
|
||||
try:
|
||||
icon = tk.PhotoImage(file=image_path2)
|
||||
msg.iconphoto(True, icon)
|
||||
except Exception as e:
|
||||
logging.error(f"Error loading the window icon: {e}", exc_info=True)
|
||||
|
||||
msg.columnconfigure(0, weight=1)
|
||||
msg.rowconfigure(0, weight=1)
|
||||
msg.winfo_toplevel()
|
||||
|
||||
@staticmethod
|
||||
def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None:
|
||||
"""
|
||||
|
22
gitea.py
22
gitea.py
@ -6,6 +6,7 @@ from pathlib import Path
|
||||
import subprocess
|
||||
import shutil
|
||||
from shared_libs.common_tools import LxTools
|
||||
from shared_libs.message import MessageDialog
|
||||
|
||||
|
||||
class GiteaUpdate:
|
||||
@ -72,29 +73,18 @@ class GiteaUpdate:
|
||||
if result == 0:
|
||||
shutil.chown(f"{Path.home()}/{res}.zip", 1000, 1000)
|
||||
|
||||
LxTools.msg_window(
|
||||
AppConfig.IMAGE_PATHS["icon_info"],
|
||||
AppConfig.IMAGE_PATHS["icon_download"],
|
||||
Msg.STR["title"],
|
||||
Msg.STR["ok_message"],
|
||||
)
|
||||
MessageDialog("info", text=Msg.STR["ok_message"])
|
||||
|
||||
else:
|
||||
|
||||
LxTools.msg_window(
|
||||
AppConfig.IMAGE_PATHS["icon_error"],
|
||||
AppConfig.IMAGE_PATHS["icon_download_error"],
|
||||
Msg.STR["error_title"],
|
||||
Msg.STR["error_massage"],
|
||||
MessageDialog(
|
||||
"error", text=Msg.STR["error_message"], title=Msg.STR["error_title"]
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
|
||||
LxTools.msg_window(
|
||||
AppConfig.IMAGE_PATHS["icon_error"],
|
||||
AppConfig.IMAGE_PATHS["icon_msg"],
|
||||
Msg.STR["error_title"],
|
||||
Msg.STR["error_no_internet"],
|
||||
MessageDialog(
|
||||
"error", text=Msg.STR["error_no_internet"], title=Msg.STR["error_title"]
|
||||
)
|
||||
|
||||
|
||||
|
38
logviewer.py
38
logviewer.py
@ -4,7 +4,10 @@ import logging
|
||||
import tkinter as tk
|
||||
from tkinter import TclError, filedialog, ttk
|
||||
from pathlib import Path
|
||||
import webbrowser
|
||||
from functools import partial
|
||||
from shared_libs.gitea import GiteaUpdate
|
||||
from shared_libs.message import MessageDialog
|
||||
from shared_libs.common_tools import (
|
||||
LogConfig,
|
||||
ConfigManager,
|
||||
@ -224,10 +227,6 @@ class LogViewer(tk.Tk):
|
||||
"""
|
||||
a tk.Toplevel window
|
||||
"""
|
||||
|
||||
def link_btn() -> None:
|
||||
webbrowser.open("https://git.ilunix.de/punix/shared_libs")
|
||||
|
||||
msg_t = _(
|
||||
"Logviewer a simple Gui for View Logfiles.\n\n"
|
||||
"Logviewer is open source software written in Python.\n\n"
|
||||
@ -235,13 +234,16 @@ class LogViewer(tk.Tk):
|
||||
"Use without warranty!\n"
|
||||
)
|
||||
|
||||
LxTools.msg_window(
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_log"],
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_log"],
|
||||
_("Info"),
|
||||
msg_t,
|
||||
_("Go to shared_libs git"),
|
||||
link_btn,
|
||||
MessageDialog(
|
||||
"info",
|
||||
text=msg_t,
|
||||
buttons=["OK", "Go to Logviewer"],
|
||||
commands=[
|
||||
None, # Default on "OK"
|
||||
partial(webbrowser.open, "https://git.ilunix.de/punix/shared_libs"),
|
||||
],
|
||||
icon=modul_name.AppConfig.IMAGE_PATHS["icon_log"],
|
||||
title="Logviewer",
|
||||
)
|
||||
|
||||
def update_setting(self, update_res, modul_name, _) -> None:
|
||||
@ -451,12 +453,7 @@ class LogViewer(tk.Tk):
|
||||
self.text_area.insert(tk.END, file.read())
|
||||
except Exception as e:
|
||||
logging.error(_(f"A mistake occurred: {str(e)}"))
|
||||
LxTools.msg_window(
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_error"],
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_log"],
|
||||
"LogViewer",
|
||||
_(f"A mistake occurred:\n{str(e)}\n"),
|
||||
)
|
||||
MessageDialog("error", _(f"A mistake occurred:\n{str(e)}\n"))
|
||||
|
||||
def directory_load(self, modul_name, _):
|
||||
|
||||
@ -474,12 +471,7 @@ class LogViewer(tk.Tk):
|
||||
print("File load: abort by user...")
|
||||
except Exception as e:
|
||||
logging.error(_(f"A mistake occurred: {e}"))
|
||||
LxTools.msg_window(
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_error"],
|
||||
modul_name.AppConfig.IMAGE_PATHS["icon_log"],
|
||||
"LogViewer",
|
||||
_(f"A mistake occurred:\n{e}\n"),
|
||||
)
|
||||
MessageDialog("error", _(f"A mistake occurred:\n{e}\n"))
|
||||
|
||||
|
||||
def main():
|
||||
|
384
message.py
Normal file
384
message.py
Normal file
@ -0,0 +1,384 @@
|
||||
import os
|
||||
from typing import List, Optional, Dict
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from manager import Center
|
||||
|
||||
"""
|
||||
####################################################
|
||||
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.
|
||||
|
||||
This class creates modal dialogs for displaying information, warnings, errors,
|
||||
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.
|
||||
buttons (List[str]): List of button labels (e.g., ["OK", "Cancel"]).
|
||||
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).
|
||||
|
||||
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 (returns self.result).
|
||||
"""
|
||||
|
||||
DEFAULT_ICON_PATH = "/usr/share/icons/lx-icons"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message_type: str = "info",
|
||||
text: str = "",
|
||||
buttons: List[str] = ["OK"],
|
||||
master: Optional[tk.Tk] = None,
|
||||
commands: List[Optional[callable]] = [None],
|
||||
icon: str = None,
|
||||
title: str = None,
|
||||
font: tuple = None,
|
||||
wraplength: int = None,
|
||||
):
|
||||
self.message_type = message_type or "info" # Default is "info"
|
||||
self.text = text
|
||||
self.buttons = buttons
|
||||
self.master = master
|
||||
self.result: bool = False # Default is False
|
||||
|
||||
self.icon_path = self._get_icon_path()
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
# Window creation
|
||||
self.window = tk.Toplevel(master)
|
||||
self.window.grab_set()
|
||||
self.window.resizable(False, False)
|
||||
ttk.Style().configure("TButton", font=("Helvetica", 11), padding=5)
|
||||
self.buttons_widgets = []
|
||||
self.current_button_index = 0
|
||||
self._load_icons()
|
||||
|
||||
# Window title and icon
|
||||
self.window.title(self._get_title() if not self.title else self.title)
|
||||
self.window.iconphoto(False, self.icons[self.message_type])
|
||||
|
||||
# Layout
|
||||
frame = ttk.Frame(self.window)
|
||||
frame.pack(expand=True, fill="both")
|
||||
|
||||
# Grid-Configuration
|
||||
frame.grid_rowconfigure(0, weight=1)
|
||||
frame.grid_columnconfigure(0, weight=1)
|
||||
frame.grid_columnconfigure(1, weight=3)
|
||||
|
||||
# Icon and Text
|
||||
icon_label = ttk.Label(frame, image=self.icons[self.message_type])
|
||||
pady_value = 5 if self.icon is not None else 15
|
||||
icon_label.grid(
|
||||
row=0, column=0, padx=(20, 10), pady=(pady_value, 15), sticky="nsew"
|
||||
)
|
||||
|
||||
text_label = tk.Label(
|
||||
frame,
|
||||
text=text,
|
||||
wraplength=wraplength if wraplength else 300,
|
||||
justify="left",
|
||||
anchor="center",
|
||||
font=font if font else ("Helvetica", 12),
|
||||
pady=20,
|
||||
)
|
||||
text_label.grid(
|
||||
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=(15, 10))
|
||||
|
||||
for i, btn_text in enumerate(buttons):
|
||||
if commands and len(commands) > i and commands[i] is not None:
|
||||
# Button with individual command
|
||||
btn = ttk.Button(
|
||||
self.button_frame,
|
||||
text=btn_text,
|
||||
command=commands[i],
|
||||
)
|
||||
else:
|
||||
# Default button set self.result and close window
|
||||
btn = ttk.Button(
|
||||
self.button_frame,
|
||||
text=btn_text,
|
||||
command=lambda t=btn_text: self._on_button_click(t),
|
||||
)
|
||||
|
||||
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=15)
|
||||
btn.focus_set() if i == 0 else None # Set focus on first button
|
||||
self.buttons_widgets.append(btn)
|
||||
|
||||
self.window.bind("<Return>", lambda event: self._on_enter_pressed())
|
||||
self.window.bind("<Left>", lambda event: self._navigate_left())
|
||||
self.window.bind("<Right>", lambda event: self._navigate_right())
|
||||
self.window.update_idletasks()
|
||||
self.window.attributes("-alpha", 0.0) # 100% Transparencence
|
||||
self.window.after(200, lambda: self.window.attributes("-alpha", 100.0))
|
||||
self.window.update() # Window update before centering!
|
||||
Center.center_window_cross_platform(
|
||||
self.window, self.window.winfo_width(), self.window.winfo_height()
|
||||
)
|
||||
|
||||
# Close Window on Cancel
|
||||
self.window.protocol(
|
||||
"WM_DELETE_WINDOW", lambda: self._on_button_click("Cancel")
|
||||
)
|
||||
|
||||
def _get_title(self) -> str:
|
||||
return {
|
||||
"error": "Error",
|
||||
"info": "Info",
|
||||
"ask": "Question",
|
||||
"warning": "Warning",
|
||||
}[self.message_type]
|
||||
|
||||
def _load_icons(self):
|
||||
# Try to load the icon from the provided path
|
||||
self.icons = {}
|
||||
icon_paths: Dict[str, str] = {
|
||||
"error": os.path.join(self.icon_path, "64/error.png"),
|
||||
"info": os.path.join(self.icon_path, "64/info.png"),
|
||||
"warning": os.path.join(self.icon_path, "64/warning.png"),
|
||||
"ask": os.path.join(self.icon_path, "64/question_mark.png"),
|
||||
}
|
||||
|
||||
fallback_paths: Dict[str, str] = {
|
||||
"error": "./lx-icons/64/error.png",
|
||||
"info": "./lx-icons/64/info.png",
|
||||
"warning": "./lx-icons/64/warning.png",
|
||||
"ask": "./lx-icons/64/question_mark.png",
|
||||
}
|
||||
|
||||
for key in icon_paths:
|
||||
try:
|
||||
# Check if an individual icon is provided
|
||||
if (
|
||||
self.message_type == key
|
||||
and self.icon is not None
|
||||
and os.path.exists(self.icon)
|
||||
):
|
||||
try:
|
||||
self.icons[key] = tk.PhotoImage(file=self.icon)
|
||||
except Exception as e:
|
||||
print(
|
||||
f"Erro on loading individual icon '{key}': {e}\n",
|
||||
"Try to use the default icon",
|
||||
)
|
||||
|
||||
else:
|
||||
# Check for standard path
|
||||
if os.path.exists(icon_paths[key]):
|
||||
self.icons[key] = tk.PhotoImage(file=icon_paths[key])
|
||||
else:
|
||||
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:
|
||||
"""Get the path to the default icon."""
|
||||
if os.path.exists(self.DEFAULT_ICON_PATH):
|
||||
return self.DEFAULT_ICON_PATH
|
||||
else:
|
||||
# Fallback to the directory of the script
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
def _navigate_left(self):
|
||||
if not self.buttons_widgets:
|
||||
return
|
||||
self.current_button_index = (self.current_button_index - 1) % len(
|
||||
self.buttons_widgets
|
||||
)
|
||||
self.buttons_widgets[self.current_button_index].focus_set()
|
||||
|
||||
def _navigate_right(self):
|
||||
if not self.buttons_widgets:
|
||||
return
|
||||
self.current_button_index = (self.current_button_index + 1) % len(
|
||||
self.buttons_widgets
|
||||
)
|
||||
self.buttons_widgets[self.current_button_index].focus_set()
|
||||
|
||||
def _on_enter_pressed(self):
|
||||
focused = self.window.focus_get()
|
||||
if isinstance(focused, ttk.Button):
|
||||
focused.invoke()
|
||||
|
||||
def _on_button_click(self, button_text: str) -> None:
|
||||
"""
|
||||
Sets `self.result` based on the clicked button.
|
||||
- Returns `None` if the button is "Cancel", "Abort", or "Exit" **and** there are 3 or more buttons.
|
||||
- Returns `True` if the button is "Yes", "Ok", "Continue", "Next", or "Start".
|
||||
- Returns `False` in all other cases (e.g., "No", closing with X, or fewer than 3 buttons).
|
||||
"""
|
||||
# Check: If there are 3+ buttons and the button text matches "Cancel", "Abort", or "Exit"
|
||||
if len(self.buttons) >= 3 and button_text.lower() in [
|
||||
"cancel",
|
||||
"abort",
|
||||
"exit",
|
||||
]:
|
||||
self.result = None
|
||||
# Check: Button text is "Yes", "Ok", "Continue", "Next", or "Start"
|
||||
elif button_text.lower() in ["yes", "ok", "continue", "next", "start"]:
|
||||
self.result = True
|
||||
else:
|
||||
# Fallback for all other cases (e.g., "No", closing with X, or fewer than 3 buttons)
|
||||
self.result = False
|
||||
|
||||
self.window.destroy()
|
||||
|
||||
def show(self) -> Optional[bool]:
|
||||
"""
|
||||
Displays the dialog window and waits for user interaction.
|
||||
|
||||
Returns:
|
||||
bool or None:
|
||||
- `True` if "Yes", "Ok", etc. was clicked.
|
||||
- `False` if "No" was clicked, or the window was closed with X (when there are 2 buttons).
|
||||
- `None` if "Cancel", "Abort", or "Exit" was clicked **and** there are 3+ buttons,
|
||||
or the window was closed with X (when there are 3+ buttons).
|
||||
"""
|
||||
self.window.wait_window()
|
||||
return self.result
|
Reference in New Issue
Block a user