first commit of shared_libs coarse build
This commit is contained in:
526
logviewer.py
Executable file
526
logviewer.py
Executable file
@ -0,0 +1,526 @@
|
||||
#!/usr/bin/python3
|
||||
import argparse
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from tkinter import TclError, filedialog, ttk
|
||||
from pathlib import Path
|
||||
from shared_libs.gitea import GiteaUpdate
|
||||
from shared_libs.common_tools import (
|
||||
LogConfig,
|
||||
ConfigManager,
|
||||
ThemeManager,
|
||||
LxTools,
|
||||
Tooltip,
|
||||
)
|
||||
import sys
|
||||
from file_and_dir_ensure import prepare_app_environment
|
||||
import webbrowser
|
||||
|
||||
|
||||
class LogViewer(tk.Tk):
|
||||
def __init__(self, modul_name):
|
||||
super().__init__()
|
||||
|
||||
self.my_tool_tip = None
|
||||
self.modul_name = modul_name # Save the module name
|
||||
# from here the calls must be made with the module name
|
||||
_ = modul_name.AppConfig.setup_translations()
|
||||
|
||||
self.x_width = modul_name.AppConfig.UI_CONFIG["window_size"][0]
|
||||
self.y_height = modul_name.AppConfig.UI_CONFIG["window_size"][1]
|
||||
# Set the window size
|
||||
self.geometry(f"{self.x_width}x{self.y_height}")
|
||||
self.minsize(
|
||||
modul_name.AppConfig.UI_CONFIG["window_size"][0],
|
||||
modul_name.AppConfig.UI_CONFIG["window_size"][1],
|
||||
)
|
||||
self.title(modul_name.AppConfig.UI_CONFIG["window_title2"])
|
||||
self.tk.call(
|
||||
"source", f"{modul_name.AppConfig.SYSTEM_PATHS['tcl_path']}/water.tcl"
|
||||
)
|
||||
ConfigManager.init(modul_name.AppConfig.SETTINGS_FILE)
|
||||
theme = ConfigManager.get("theme")
|
||||
ThemeManager.change_theme(self, theme)
|
||||
LxTools.center_window_cross_platform(self, self.x_width, self.y_height)
|
||||
self.createWidgets(_)
|
||||
self.load_file(_, modul_name=modul_name)
|
||||
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)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# StringVar-Variables initialization
|
||||
self.tooltip_state = tk.BooleanVar()
|
||||
# Get value from configuration
|
||||
state = ConfigManager.get("tooltips")
|
||||
# NOTE: ConfigManager.get("tooltips") can return either a boolean value or a string,
|
||||
# depending on whether the value was loaded from the file (bool) or the default value is used (string).
|
||||
# The expression 'lines[5].strip() == "True"' in ConfigManager.load() converts the string to a boolean.
|
||||
# Convert to boolean and set
|
||||
if isinstance(state, bool):
|
||||
# If it's already a boolean, use directly
|
||||
self.tooltip_state.set(state)
|
||||
else:
|
||||
# If it's a string or something else
|
||||
self.tooltip_state.set(str(state) == "True")
|
||||
|
||||
self.tooltip_label = (
|
||||
tk.StringVar()
|
||||
) # StringVar-Variable for tooltip label for view Disabled/Enabled
|
||||
self.tooltip_update_label(modul_name, _)
|
||||
self.update_label = tk.StringVar() # StringVar-Variable for update label
|
||||
self.update_tooltip = (
|
||||
tk.StringVar()
|
||||
) # StringVar-Variable for update tooltip please not remove!
|
||||
self.update_foreground = tk.StringVar(value="red")
|
||||
|
||||
# Frame for Menu
|
||||
self.menu_frame = ttk.Frame(self)
|
||||
self.menu_frame.configure(relief="flat")
|
||||
if "'logview_app_config'" in f"{modul_name}".split():
|
||||
self.menu_frame.grid(column=0, row=0, columnspan=4, sticky=tk.NSEW)
|
||||
|
||||
# App Menu
|
||||
self.version_lb = ttk.Label(self.menu_frame, text=modul_name.AppConfig.VERSION)
|
||||
self.version_lb.config(font=("Ubuntu", 11), foreground="#00c4ff")
|
||||
self.version_lb.grid(column=0, row=0, rowspan=4, padx=10, pady=10)
|
||||
|
||||
Tooltip(
|
||||
self.version_lb,
|
||||
f"Version: {modul_name.AppConfig.VERSION[2:]}",
|
||||
self.tooltip_state,
|
||||
)
|
||||
self.load_button = ttk.Button(
|
||||
self.menu_frame,
|
||||
text=_("Load Log"),
|
||||
style="Toolbutton",
|
||||
command=lambda: self.directory_load(modul_name, _),
|
||||
)
|
||||
self.load_button.grid(column=1, row=0)
|
||||
self.options_btn = ttk.Menubutton(self.menu_frame, text=_("Options"))
|
||||
self.options_btn.grid(column=2, row=0)
|
||||
|
||||
Tooltip(self.options_btn, modul_name.Msg.TTIP["settings"], self.tooltip_state)
|
||||
|
||||
self.set_update = tk.IntVar()
|
||||
self.settings = tk.Menu(self, relief="flat")
|
||||
self.options_btn.configure(menu=self.settings, style="Toolbutton")
|
||||
self.settings.add_checkbutton(
|
||||
label=_("Disable Updates"),
|
||||
command=lambda: self.update_setting(self.set_update.get(), modul_name, _),
|
||||
variable=self.set_update,
|
||||
)
|
||||
|
||||
self.updates_lb = ttk.Label(self.menu_frame, textvariable=self.update_label)
|
||||
self.updates_lb.grid(column=5, row=0, padx=10)
|
||||
self.updates_lb.grid_remove()
|
||||
self.update_label.trace_add("write", self.update_label_display)
|
||||
self.update_foreground.trace_add("write", self.update_label_display)
|
||||
res = GiteaUpdate.api_down(
|
||||
modul_name.AppConfig.UPDATE_URL,
|
||||
modul_name.AppConfig.VERSION,
|
||||
ConfigManager.get("updates"),
|
||||
)
|
||||
self.update_ui_for_update(res, modul_name, _)
|
||||
|
||||
# Tooltip Menu
|
||||
self.settings.add_command(
|
||||
label=self.tooltip_label.get(),
|
||||
command=lambda: self.tooltips_toggle(modul_name, _),
|
||||
)
|
||||
# Label show dark or light
|
||||
self.theme_label = tk.StringVar()
|
||||
self.update_theme_label(modul_name, _)
|
||||
self.settings.add_command(
|
||||
label=self.theme_label.get(),
|
||||
command=lambda: self.on_theme_toggle(modul_name, _),
|
||||
)
|
||||
|
||||
# About BTN Menu / Label
|
||||
self.about_btn = ttk.Button(
|
||||
self.menu_frame,
|
||||
text=_("About"),
|
||||
style="Toolbutton",
|
||||
command=lambda: self.about(modul_name, _),
|
||||
)
|
||||
self.about_btn.grid(column=3, row=0)
|
||||
self.readme = tk.Menu(self)
|
||||
# self.grid_rowconfigure(0, weight=)
|
||||
self.grid_rowconfigure(1, weight=25)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
# Method that is called when the variable changes
|
||||
def update_label_display(self, *args):
|
||||
# Set the foreground color
|
||||
self.updates_lb.configure(foreground=self.update_foreground.get())
|
||||
|
||||
# Show or hide the label based on whether it contains text
|
||||
if self.update_label.get():
|
||||
# Make sure the label is in the correct position every time it's shown
|
||||
self.updates_lb.grid(column=5, row=0, padx=10)
|
||||
else:
|
||||
self.updates_lb.grid_remove()
|
||||
|
||||
# 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"""
|
||||
# First, remove the update button if it exists to avoid conflicts
|
||||
if hasattr(self, "update_btn"):
|
||||
self.update_btn.grid_forget()
|
||||
delattr(self, "update_btn")
|
||||
|
||||
if res == "False":
|
||||
self.set_update.set(value=1)
|
||||
self.update_label.set(_("Update search off"))
|
||||
self.update_tooltip.set(_("Updates you have disabled"))
|
||||
# Clear the foreground color as requested
|
||||
self.update_foreground.set("")
|
||||
# Set the tooltip for the label
|
||||
Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
|
||||
|
||||
elif res == "No Internet Connection!":
|
||||
self.update_label.set(_("No Server Connection!"))
|
||||
self.update_foreground.set("red")
|
||||
# Set the tooltip for "No Server Connection"
|
||||
Tooltip(
|
||||
self.updates_lb,
|
||||
_("Could not connect to update server"),
|
||||
self.tooltip_state,
|
||||
)
|
||||
|
||||
elif res == "No Updates":
|
||||
self.update_label.set(_("No Updates"))
|
||||
self.update_tooltip.set(_("Congratulations! Wire-Py is up to date"))
|
||||
self.update_foreground.set("")
|
||||
# Set the tooltip for the label
|
||||
Tooltip(self.updates_lb, self.update_tooltip.get(), self.tooltip_state)
|
||||
|
||||
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.Menubutton(self.menu_frame, text=update_text)
|
||||
self.update_btn.grid(column=5, row=0, padx=0)
|
||||
Tooltip(
|
||||
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
|
||||
def about(modul_name, _) -> None:
|
||||
"""
|
||||
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"
|
||||
"Email: polunga40@unity-mail.de also likes for donation.\n\n"
|
||||
"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,
|
||||
)
|
||||
|
||||
def update_setting(self, update_res, modul_name, _) -> None:
|
||||
"""write off or on in file
|
||||
Args:
|
||||
update_res (int): argument that is passed contains 0 or 1
|
||||
"""
|
||||
if update_res == 1:
|
||||
# Disable updates
|
||||
ConfigManager.set("updates", "off")
|
||||
# When updates are disabled, we know the result should be "False"
|
||||
self.update_ui_for_update("False", modul_name, _)
|
||||
else:
|
||||
# Enable updates
|
||||
ConfigManager.set("updates", "on")
|
||||
# When enabling updates, we need to actually check for updates
|
||||
try:
|
||||
# Force a fresh check by passing "on" as the update setting
|
||||
res = GiteaUpdate.api_down(
|
||||
modul_name.AppConfig.UPDATE_URL, modul_name.AppConfig.VERSION, "on"
|
||||
)
|
||||
|
||||
# Make sure the UI is updated regardless of the previous state
|
||||
if hasattr(self, "update_btn"):
|
||||
self.update_btn.grid_forget()
|
||||
if hasattr(self, "updates_lb"):
|
||||
self.updates_lb.grid_forget()
|
||||
|
||||
# Now update the UI with the fresh result
|
||||
self.update_ui_for_update(res, modul_name, _)
|
||||
except Exception as e:
|
||||
logging.error(f"Error checking for updates: {e}")
|
||||
# Fallback to a default message if there's an error
|
||||
self.update_ui_for_update("No Internet Connection!", modul_name, _)
|
||||
|
||||
def tooltip_update_label(self, modul_name, _) -> None:
|
||||
"""Updates the tooltip menu label based on the current tooltip status"""
|
||||
# Set the menu text based on the current status
|
||||
if self.tooltip_state.get():
|
||||
# If tooltips are enabled, the menu option should be to disable them
|
||||
self.tooltip_label.set(_("Disable Tooltips"))
|
||||
else:
|
||||
# If tooltips are disabled, the menu option should be to enable them
|
||||
self.tooltip_label.set(_("Enable Tooltips"))
|
||||
|
||||
def tooltips_toggle(self, modul_name, _):
|
||||
"""
|
||||
Toggles the visibility of tooltips (on/off) and updates
|
||||
the corresponding menu label. Inverts the current tooltip state
|
||||
(`self.tooltip_state`), saves the new value in the configuration,
|
||||
and applies the change immediately. Updates the menu entry's label to
|
||||
reflect the new tooltip status (e.g., "Tooltips: On" or "Tooltips: Off").
|
||||
"""
|
||||
# Toggle the boolean state
|
||||
new_bool_state = not self.tooltip_state.get()
|
||||
# Save the converted value in the configuration
|
||||
ConfigManager.set("tooltips", str(new_bool_state))
|
||||
# Update the tooltip_state variable for immediate effect
|
||||
self.tooltip_state.set(new_bool_state)
|
||||
|
||||
# Update the menu label
|
||||
self.tooltip_update_label(modul_name, _)
|
||||
|
||||
# Update the menu entry - find the correct index
|
||||
# This assumes it's the third item (index 2) in your menu
|
||||
self.settings.entryconfigure(1, label=self.tooltip_label.get())
|
||||
|
||||
def update_theme_label(self, modul_name, _) -> None:
|
||||
"""Update the theme label based on the current theme"""
|
||||
current_theme = ConfigManager.get("theme")
|
||||
if current_theme == "light":
|
||||
self.theme_label.set(_("Dark"))
|
||||
else:
|
||||
self.theme_label.set(_("Light"))
|
||||
|
||||
def on_theme_toggle(self, modul_name, _) -> None:
|
||||
"""Toggle between light and dark theme"""
|
||||
current_theme = ConfigManager.get("theme")
|
||||
new_theme = "dark" if current_theme == "light" else "light"
|
||||
ThemeManager.change_theme(self, new_theme, new_theme)
|
||||
self.update_theme_label(modul_name, _) # Update the theme label
|
||||
# Update Menulfield
|
||||
self.settings.entryconfigure(2, label=self.theme_label.get())
|
||||
|
||||
def createWidgets(self, _):
|
||||
|
||||
text_frame = ttk.Frame(self)
|
||||
text_frame.grid(row=1, column=0, padx=5, pady=5, sticky=tk.NSEW)
|
||||
text_frame.rowconfigure(0, weight=3)
|
||||
text_frame.columnconfigure(0, weight=1)
|
||||
next_frame = ttk.Frame(self)
|
||||
next_frame.grid(row=2, column=0, sticky=tk.NSEW)
|
||||
next_frame.rowconfigure(2, weight=1)
|
||||
next_frame.columnconfigure(1, weight=1)
|
||||
# Create a Text widget for displaying the log file
|
||||
self.text_area = tk.Text(
|
||||
text_frame, wrap=tk.WORD, padx=5, pady=5, relief="flat"
|
||||
)
|
||||
self.text_area.grid(row=0, column=0, sticky=tk.NSEW)
|
||||
self.text_area.tag_configure(
|
||||
"found-tag", foreground="yellow", background="green"
|
||||
)
|
||||
# Create a vertical scrollbar for the Text widget
|
||||
v_scrollbar = ttk.Scrollbar(
|
||||
text_frame, orient="vertical", command=self.text_area.yview
|
||||
)
|
||||
v_scrollbar.grid(row=0, column=1, sticky=tk.NS)
|
||||
self.text_area.configure(yscrollcommand=v_scrollbar.set)
|
||||
|
||||
self._entry = ttk.Entry(next_frame)
|
||||
self._entry.bind("<Return>", lambda e: self._onFind())
|
||||
self._entry.grid(row=0, column=1, padx=5, sticky=tk.EW)
|
||||
# Add a context menu to the Text widget
|
||||
self.context_menu = tk.Menu(self, tearoff=0)
|
||||
self.context_menu.add_command(label=_("Copy"), command=self.copy_text)
|
||||
self.context_menu.add_command(label=_("Paste"), command=self.paste_into_entry)
|
||||
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.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
|
||||
)
|
||||
delete_button.grid(row=0, column=2, padx=5, pady=5, sticky=tk.EW)
|
||||
|
||||
def show_text_menu(self, event):
|
||||
try:
|
||||
self.configure.tk_popup(event.x_root, event.y_root)
|
||||
finally:
|
||||
self.context_menu.grab_release()
|
||||
|
||||
def copy_text(self):
|
||||
|
||||
try:
|
||||
selected_text = self.text_area.selection_get()
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(selected_text)
|
||||
except tk.TclError:
|
||||
# No Text selected
|
||||
pass
|
||||
|
||||
def show_context_menu(self, event):
|
||||
try:
|
||||
self.context_menu.tk_popup(event.x_root, event.y_root)
|
||||
finally:
|
||||
self.context_menu.grab_release()
|
||||
|
||||
def paste_into_entry(self):
|
||||
try:
|
||||
text = self.clipboard_get()
|
||||
self._entry.delete(0, tk.END)
|
||||
self._entry.insert(tk.END, text)
|
||||
except tk.TclError:
|
||||
# No Text on Clipboard
|
||||
pass
|
||||
|
||||
def _onFind(self):
|
||||
searchText = self._entry.get()
|
||||
if len(searchText) == 0:
|
||||
return
|
||||
|
||||
# Set the search start position to the last found position (initial value: "1.0")
|
||||
start_pos = self.last_search_pos if hasattr(self, "last_search_pos") else "1.0"
|
||||
|
||||
var = tk.IntVar()
|
||||
foundIndex = self.text_area.search(
|
||||
searchText,
|
||||
start_pos,
|
||||
stopindex=tk.END,
|
||||
nocase=tk.YES,
|
||||
count=var,
|
||||
regexp=tk.YES,
|
||||
)
|
||||
|
||||
if not foundIndex:
|
||||
# No further entry found, reset to the beginning
|
||||
self.last_search_pos = "1.0"
|
||||
return
|
||||
|
||||
count = var.get()
|
||||
lastIndex = self.text_area.index(f"{foundIndex} + {count}c")
|
||||
|
||||
# Remove and reapply highlighting
|
||||
self.text_area.tag_remove("found-tag", "1.0", tk.END)
|
||||
self.text_area.tag_add("found-tag", foundIndex, lastIndex)
|
||||
|
||||
# Update the start position for the next search
|
||||
self.last_search_pos = lastIndex
|
||||
self.text_area.see(foundIndex)
|
||||
|
||||
def delete_file(self, modul_name):
|
||||
Path.unlink(modul_name.AppConfig.LOG_FILE_PATH)
|
||||
modul_name.AppConfig.ensure_log()
|
||||
|
||||
def load_file(self, _, modul_name):
|
||||
|
||||
try:
|
||||
if not modul_name.AppConfig.LOG_FILE_PATH:
|
||||
return
|
||||
|
||||
with open(
|
||||
modul_name.AppConfig.LOG_FILE_PATH, "r", encoding="utf-8"
|
||||
) as file:
|
||||
self.text_area.delete(1.0, tk.END)
|
||||
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"),
|
||||
)
|
||||
|
||||
def directory_load(self, modul_name, _):
|
||||
|
||||
filepath = filedialog.askopenfilename(
|
||||
initialdir=f"{Path.home() / ".local/share/lxlogs/"}",
|
||||
title="Select a Logfile File",
|
||||
filetypes=[("Logfiles", "*.log")],
|
||||
)
|
||||
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as file:
|
||||
self.text_area.delete(1.0, tk.END)
|
||||
self.text_area.insert(tk.END, file.read())
|
||||
except (IsADirectoryError, TypeError, FileNotFoundError):
|
||||
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"),
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# Create an ArgumentParser object
|
||||
parser = argparse.ArgumentParser(
|
||||
description="LogViewer with optional module loading."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--modul",
|
||||
type=str,
|
||||
default="logview_app_config",
|
||||
help="Give the name of the module to load.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
import importlib
|
||||
|
||||
try:
|
||||
modul = importlib.import_module(args.modul)
|
||||
except ModuleNotFoundError:
|
||||
print(f"Modul '{args.modul}' not found")
|
||||
print("For help use logviewer -h")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error load Modul: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
prepare_app_environment()
|
||||
app = LogViewer(modul)
|
||||
LogConfig.logger(ConfigManager.get("logfile"))
|
||||
"""
|
||||
the hidden files are hidden in Filedialog
|
||||
"""
|
||||
try:
|
||||
app.tk.call("tk_getOpenFile", "-foobarbaz")
|
||||
except TclError:
|
||||
pass
|
||||
app.tk.call("set", "::tk::dialog::file::showHiddenBtn", "1")
|
||||
app.tk.call("set", "::tk::dialog::file::showHiddenVar", "0")
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Reference in New Issue
Block a user