ui.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # SPDX-FileCopyrightText: Copyright (C) 2021 MH3SP Server Project
  4. # SPDX-License-Identifier: AGPL-3.0-or-later
  5. """UI helper module."""
  6. import logging
  7. try:
  8. # Python 3.x
  9. import tkinter as tk
  10. import tkinter.scrolledtext as ScrolledText
  11. from queue import Queue
  12. except ImportError:
  13. # Python 2.x
  14. import Tkinter as tk
  15. import ScrolledText
  16. from Queue import Queue
  17. WINDOWS = []
  18. EMITTERS = Queue()
  19. class LoggingHandler(logging.Handler):
  20. """This class allows to log to a Tkinter Text or ScrolledText widget.
  21. Adapted from:
  22. - https://stackoverflow.com/questions/13318742
  23. - https://gist.github.com/moshekaplan/c425f861de7bbf28ef06
  24. """
  25. def __init__(self, text):
  26. logging.Handler.__init__(self)
  27. self.text = text
  28. def emit(self, record):
  29. msg = self.format(record)
  30. def append():
  31. """Necessary because we can't modify it from other threads."""
  32. self.text.configure(state='normal')
  33. self.text.insert(tk.END, msg + '\n')
  34. self.text.configure(state='disabled')
  35. self.text.yview(tk.END) # Autoscroll to the bottom
  36. # Won't work on Python3.x
  37. # self.text.after(0, append)
  38. EMITTERS.put(lambda: self.text.after(0, append))
  39. class LoggerTk(tk.Tk):
  40. """Create a logging window."""
  41. def __init__(self, *args, **kwargs):
  42. tk.Tk.__init__(self, *args, **kwargs)
  43. text = ScrolledText.ScrolledText(self, state='disabled')
  44. text.configure(font='TkFixedFont')
  45. text.pack(expand=True, fill=tk.BOTH)
  46. global WINDOWS
  47. WINDOWS.append(self)
  48. self.handler = LoggingHandler(text)
  49. def get_handler(self):
  50. """Return the window's logging.Handler instance."""
  51. return self.handler
  52. def set_logger(self, logger):
  53. """Add the window's logging.Handler to the logger."""
  54. logger.addHandler(self.handler)
  55. def on_close():
  56. """Detach the handler if the window is closed."""
  57. global WINDOWS
  58. WINDOWS.remove(self)
  59. logger.removeHandler(self.handler)
  60. self.destroy()
  61. self.protocol("WM_DELETE_WINDOW", on_close)
  62. def update():
  63. """Manually update logger windows."""
  64. while not EMITTERS.empty():
  65. f = EMITTERS.get()
  66. f()
  67. for window in WINDOWS:
  68. window.update_idletasks()
  69. window.update()
  70. if __name__ == "__main__":
  71. print("UI default logger test")
  72. fmt = "%(asctime)s - %(levelname)s - %(message)s"
  73. logging.basicConfig(level=logging.INFO, format=fmt)
  74. t = LoggerTk()
  75. t.title("Tk Logger test")
  76. t.get_handler().setFormatter(logging.Formatter(fmt))
  77. t.set_logger(logging.getLogger())
  78. logging.info("Info log message")