main.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import logging
  2. import queue
  3. import signal
  4. import threading
  5. import time
  6. import tkinter as tk
  7. from tkinter import (HORIZONTAL, VERTICAL, E, N, S, W, filedialog, messagebox,
  8. ttk)
  9. from tkinter.scrolledtext import ScrolledText
  10. from PIL import Image, ImageTk
  11. from common import log
  12. from qhandler import QueueHandler
  13. from simsdt import runsdt
  14. from simsdt.utils.excel_checker import check_excel_file
  15. from simsdt.utils.solver_checker import check_solver
  16. main_logger = logging.getLogger("simsdt.gui")
  17. class RunsdtThread(threading.Thread):
  18. def __init__(self, group=None, target=None, name=None,
  19. args=(), kwargs=None, *, daemon=None):
  20. super(RunsdtThread, self).__init__(group=group,
  21. target=target,
  22. name=name,
  23. daemon=daemon)
  24. self._stop_event = threading.Event()
  25. self.args = args
  26. self.kwargs = kwargs
  27. return
  28. def run(self):
  29. t = time.time()
  30. runsdt.main(self.args)
  31. elapsed_time = time.time() - t
  32. et = time.strftime("%H:%M:%S", time.gmtime(elapsed_time))
  33. main_logger.info('Tiempo de ejecución %s' % et)
  34. def stop(self):
  35. self._stop_event.set()
  36. class ConsoleUi:
  37. """Poll messages from a logging queue and display them in a scrolled text
  38. widget"""
  39. def __init__(self, frame):
  40. self.frame = frame
  41. trash_icon = Image.open('img/icons/icons8-eliminar-16.png')
  42. self.trash_icon = ImageTk.PhotoImage(trash_icon)
  43. # Create a ScrolledText wdiget
  44. self.scrolled_text = ScrolledText(frame, state='disabled', height=25)
  45. self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E))
  46. self.clear = ttk.Button(frame, text='Limpiar Log',
  47. command=self.clear_console,
  48. image=self.trash_icon,
  49. compoun='right')
  50. self.clear.grid(row=1, column=0, sticky=E, pady=(5, 5))
  51. # Font config
  52. self.scrolled_text.configure(font='TkFixedFont')
  53. self.scrolled_text.tag_config('INFO', foreground='black')
  54. self.scrolled_text.tag_config('DEBUG', foreground='gray')
  55. self.scrolled_text.tag_config('WARNING', foreground='orange')
  56. self.scrolled_text.tag_config('ERROR', foreground='red')
  57. self.scrolled_text.tag_config(
  58. 'CRITICAL', foreground='red', underline=1)
  59. # Create a logging handler using a queue
  60. self.log_queue = queue.Queue()
  61. self.queue_handler = QueueHandler(self.log_queue)
  62. formatter = logging.Formatter(
  63. '%(asctime)s: %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
  64. self.queue_handler.setFormatter(formatter)
  65. # self.queue_handler.setLevel(logging.INFO)
  66. main_logger.addHandler(self.queue_handler)
  67. # Start polling messages from the queue
  68. self.frame.after(100, self.poll_log_queue)
  69. def display(self, record):
  70. msg = self.queue_handler.format(record)
  71. self.scrolled_text.configure(state='normal')
  72. self.scrolled_text.insert(tk.END, msg + '\n', record.levelname)
  73. self.scrolled_text.configure(state='disabled')
  74. # Autoscroll to the bottom
  75. self.scrolled_text.yview(tk.END)
  76. def poll_log_queue(self):
  77. # Check every 100ms if there is a new message in the queue to display
  78. while True:
  79. try:
  80. record = self.log_queue.get(block=False)
  81. except queue.Empty:
  82. break
  83. else:
  84. self.display(record)
  85. self.frame.after(100, self.poll_log_queue)
  86. def clear_console(self):
  87. self.scrolled_text.configure(state='normal')
  88. self.scrolled_text.delete(1.0, tk.END)
  89. self.scrolled_text.configure(state='disabled')
  90. class FormUi:
  91. runsdt_thread = None
  92. def __init__(self, frame, q):
  93. self.frame = frame
  94. self.q = q
  95. image = Image.open('img/icons/icons8-play-16.png')
  96. self.photo = ImageTk.PhotoImage(image)
  97. opfile = Image.open('img/icons/icons8-abrir-carpeta-16.png')
  98. self.opfile = ImageTk.PhotoImage(opfile)
  99. # Add a text field for file path
  100. self.file_path = tk.StringVar()
  101. ttk.Label(self.frame, text='Archivo:').grid(
  102. column=0, row=0, sticky=W, pady=(5, 5), padx=(0, 10))
  103. self.entry = ttk.Entry(self.frame, textvariable=self.file_path,
  104. width=50)
  105. self.entry.grid(column=1, row=0, sticky=(W, E), pady=(5, 5))
  106. self.button = ttk.Button(
  107. self.frame, image=self.opfile, compound='center',
  108. command=self.open_file_dialog, width=3)
  109. self.button.grid(column=2, row=0, sticky=W)
  110. self.button_run = ttk.Button(self.frame, image=self.photo,
  111. compound="right", text='Ejecutar',
  112. command=self.run)
  113. self.button_run.grid(column=1, row=1, sticky=W)
  114. def open_file_dialog(self):
  115. # Open file dialog
  116. try:
  117. file = filedialog.askopenfilename(
  118. filetypes=[('Excel', '.xlsx')],
  119. title='Seleccione un archivo de subasta...')
  120. check_excel_file(file)
  121. except Exception as e:
  122. main_logger.error(e)
  123. self.file_path.set('')
  124. else:
  125. main_logger.info(f'Archivo seleccionado: {file}')
  126. self.file_path.set(file)
  127. def run(self):
  128. try:
  129. if not self.file_path.get():
  130. main_logger.error(
  131. 'Debe seleccionar un archivo para ejecutarlo')
  132. return
  133. check_solver()
  134. self.button_run.configure(state='disabled')
  135. main_logger.info('Ejecutando')
  136. self.runsdt_thread = RunsdtThread(args=(self.file_path.get()))
  137. self.runsdt_thread.start()
  138. self.check_thread()
  139. except Exception as e:
  140. main_logger.error(e)
  141. return
  142. def check_thread(self):
  143. if not self.runsdt_thread.is_alive():
  144. self.button_run.configure(state='normal')
  145. self.q.put(False)
  146. else:
  147. self.q.put(True)
  148. self.frame.after(100, self.check_thread)
  149. class StatusUi:
  150. def __init__(self, frame, q):
  151. self.q = q
  152. self.frame = frame
  153. self.pb_max = 100
  154. # ttk.Label(self.frame, text='This is just an example of a third frame').grid(
  155. # column=0, row=1, sticky=W)
  156. self.progress_bar = ttk.Progressbar(
  157. self.frame, mode='determinate')
  158. self.progress_bar.pack(expand=True, fill=tk.X, side=tk.TOP)
  159. self.frame.after(100, self.check_q)
  160. def check_q(self):
  161. # Check every 100ms if there is a new message in the queue to display
  162. while True:
  163. try:
  164. record = self.q.get(block=False)
  165. except queue.Empty:
  166. break
  167. else:
  168. if record:
  169. step = 1
  170. self.progress_bar.step(step)
  171. if step >= self.pb_max:
  172. step = 1
  173. else:
  174. step += 1
  175. else:
  176. self.progress_bar.config(value=0)
  177. self.frame.after(100, self.check_q)
  178. class App:
  179. def __init__(self, root):
  180. self.root = root
  181. # Queue a nivel de App para manejar el progress bar
  182. pbqueue = queue.Queue()
  183. root.title('SimSDT')
  184. root.iconbitmap("app.ico")
  185. root.columnconfigure(0, weight=1)
  186. root.rowconfigure(0, weight=1)
  187. # root.geometry("1080x960")
  188. # Create the panes and frames
  189. vertical_pane = ttk.PanedWindow(
  190. self.root, orient=VERTICAL)
  191. vertical_pane.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
  192. main_title = ttk.Label(
  193. vertical_pane,
  194. text='Simulación de Subastas de Derechos de Transmisión',
  195. font=('', '20', 'bold'), padding=(0, 5))
  196. vertical_pane.add(main_title, weight=0)
  197. horizontal_pane = ttk.PanedWindow(vertical_pane, orient=HORIZONTAL)
  198. vertical_pane.add(horizontal_pane, weight=1)
  199. form_frame = ttk.Labelframe(
  200. horizontal_pane, text="Configuración", padding=(5, 5))
  201. form_frame.columnconfigure(1, weight=1)
  202. horizontal_pane.add(form_frame, weight=1)
  203. console_frame = ttk.Labelframe(
  204. horizontal_pane, text="Consola", padding=(5, 5))
  205. console_frame.columnconfigure(0, weight=1)
  206. console_frame.rowconfigure(0, weight=1)
  207. horizontal_pane.add(console_frame, weight=1)
  208. third_frame = ttk.Labelframe(
  209. vertical_pane, text="Estado", padding=(5, 5))
  210. vertical_pane.add(third_frame, weight=0)
  211. # Initialize all frames
  212. self.form = FormUi(form_frame, pbqueue)
  213. self.console = ConsoleUi(console_frame)
  214. self.third = StatusUi(third_frame, pbqueue)
  215. self.root.protocol('WM_DELETE_WINDOW', self.quit)
  216. self.root.bind('<Control-q>', self.quit)
  217. signal.signal(signal.SIGINT, self.quit)
  218. print(root.geometry())
  219. def quit(self, *args):
  220. msg = 'Se esta ejecutando un proceso de optimización\n\n'
  221. msg += '¿Desea salir de la aplicación?'
  222. if self.form.runsdt_thread and self.form.runsdt_thread.is_alive():
  223. if messagebox.askokcancel("Salir de SimSDT", msg):
  224. main_logger.info('Cerrando aplicacion SimSDT')
  225. self.form.runsdt_thread.stop()
  226. self.root.destroy()
  227. else:
  228. main_logger.info('Saliendo de la aplicacion')
  229. self.root.destroy()
  230. def main():
  231. main_logger.info('SimSDT ha iniciado')
  232. root = tk.Tk()
  233. app = App(root)
  234. app.root.mainloop()
  235. if __name__ == '__main__':
  236. main()