Major bug fixes, optimization + major advance

This commit is contained in:
Jérémi N ‘EndMove’ 2022-09-05 23:13:38 +02:00
parent 539b75cb09
commit f8f7832dd7
Signed by: EndMove
GPG Key ID: 65C4A02E1F5371A4
11 changed files with 322 additions and 161 deletions

BIN
assets/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -17,6 +17,10 @@
"optionDest": "console",
"value": false
},
{
"optionDest": "icon_file",
"value": "C:/Users/super/Documents/Developpement/Python/WebPicDownloader/assets/logo.ico"
},
{
"optionDest": "name",
"value": "WebPicDownloader_v1.0.0"
@ -68,6 +72,10 @@
{
"optionDest": "argv_emulation",
"value": false
},
{
"optionDest": "datas",
"value": "C:/Users/super/Documents/Developpement/Python/WebPicDownloader/assets;assets/"
}
],
"nonPyinstallerOptions": {

View File

@ -14,5 +14,5 @@ class Frames(Enum):
@version 1.0.0
@since 2022-08-30
"""
Home = 1 # Home view
Info = 2 # Info & copyright view
HOME = 1 # Home view
INFO = 2 # Info & copyright view

View File

@ -1,14 +1,12 @@
import time
from controller.MainController import MainController
from model.WebPicDownloader import WebPicDownloader
from util.AsyncTask import AsyncTask
from model.WebPicDownloader import MessageType, WebPicDownloader
class HomeController:
"""
Controller - HomeController
desc...
This controller handles all the interaction directly related to the download.
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@ -16,13 +14,12 @@ class HomeController:
@since 2022-08-30
"""
# Variables
__main_controller = None
__main_controller: MainController = None
__view = None
__webpic: WebPicDownloader = None
__download_task = None
# Constructor
def __init__(self, controller: MainController, webpic) -> None:
def __init__(self, controller: MainController, webpic: WebPicDownloader) -> None:
"""
Constructor
@ -33,6 +30,11 @@ class HomeController:
self.__main_controller = controller
self.__webpic = webpic
# setup webpic event
webpic.set_messenger_callback(self.on_webpic_messenger)
webpic.set_success_callback(self.on_webpic_success)
webpic.set_failure_callback(self.on_webpic_failure)
# Subscribe to events
controller.subscribe_to_quite_event(self.on_quit)
@ -45,21 +47,9 @@ class HomeController:
* :view: -> The view that this controller manage.
"""
self.__view = view
self.__webpic.set_messenger_callback(self.on_webpic_messenger)
self.__webpic.set_success_callback(self.on_webpic_success)
self.__webpic.set_failure_callback(self.on_webpic_failure)
# END View method
# START View events
def on_change_view(self, frame) -> None:
"""
[event function for view]
=> Call this event method when the user requests to change the window.
* :frame: -> The frame we want to launch.
"""
self.__main_controller.change_frame(frame)
def on_download_requested(self, url: str, name: str) -> None:
"""
[event function for view]
@ -69,26 +59,45 @@ class HomeController:
* :name: -> The name of the folder in which put pictures.
"""
if url.strip() and name.strip():
self.__view.set_interface_state(True)
self.__view.clear_logs()
self.__webpic.start_downloading(url, name)
else:
self.__view.show_error_message("Opss, the url or folder name are not valid!")
# END View events
# START Webpic events
def on_webpic_messenger(self, message: str) -> None:
def on_webpic_messenger(self, message: str, type) -> None:
"""
[event function for webpic]
=> This event is called to communicate a message.
* :message: -> Message that webpic send to the controller.
* :type: -> Type of message that webpic send to the controller.
"""
self.__view.add_log(message)
match type:
case MessageType.LOG:
self.__view.add_log(message)
case MessageType.ERROR:
self.__view.show_error_message(message)
case MessageType.SUCCESS:
self.__view.show_success_message(message)
def on_webpic_success(self) -> None:
"""
[event function for webpic]
=> This event is called to indicate that the download has finished successfully.
"""
self.__view.show_success_message("The download has been successfully completed.")
self.__view.set_interface_state(False)
def on_webpic_failure(self) -> None:
"""
[event function for webpic]
=> This event is called to indicate that there was a problem during the download.
"""
self.__view.show_error_message("A critical error preventing the download occurred, check the logs.")
self.__view.set_interface_state(False)
# END Webpic events
# START Controller methods
@ -97,44 +106,13 @@ class HomeController:
[event function for controller]
=> Call this event when a request to exit is thrown.
"""
if self.__webpic.is_alive():
if self.__webpic.is_download_running():
if self.__main_controller.show_question_dialog(
"Are you sure?",
"Do you really want to quit while the download is running?\nThis will stop the download."
):
self.__webpic.stop_downloading()
time.sleep(4)
self.__webpic.stop_downloading() # hot stop deamon
return False
else:
return True
print("Quit... homecontroller END") # REMOVE
return True
self.__webpic.stop_downloading(block=True)
# END Controller methods
# START Task methods
def __async_task_start(self, url, name) -> None:
"""
[CallBack start function]
=> Start Callback function for asynctask, be careful once executed in asynctask this
function will keep its controller context. In short it's as if the thread was
launched in the controller and the execution never left it.
* :url: -> Url for webpic.
* :name: -> Working dir name for webpic.
"""
print("start callback called") # REMOVE
self.__view.clear_logs()
if self.__webpic.download(url, name):
self.__view.show_success_message("The download has been successfully completed.")
else:
self.__view.show_error_message("A critical error preventing the download occurred, check the logs.")
def __async_task_stop(self) -> None:
"""
[CallBack stop function]
=> End Callback function for asynctask, be careful once executed in asynctask this
function will keep its controller context. In short it's as if the thread was
launched in the controller and the execution never left it.
"""
print("stop callback called") # REMOVE
self.__webpic.stop()
# END Task methods

View File

@ -1,3 +1,4 @@
from controller.Frames import Frames
from controller.MainController import MainController
@ -5,7 +6,7 @@ class InfoController:
"""
Controller - InfoController
desc...
This controller manages the display of information in the information view.
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@ -13,11 +14,17 @@ class InfoController:
@since 2022-08-30
"""
# Variables
__main_controller = None
__main_controller: MainController = None
__view = None
# Constructor
def __init__(self, controller: MainController) -> None:
"""
Constructor
* :controller: -> The main application cpntroller.
"""
# Setup variables
self.__main_controller = controller
# START View methods
@ -28,14 +35,20 @@ class InfoController:
:view: -> The view that this controller manage.
"""
self.__view = view
self.__view.set_title(self.__main_controller.get_config('about_title'))
self.__view.set_content(self.__main_controller.get_config('about_content'))
self.__view.set_version(
f"version: {self.__main_controller.get_config('app_version')} - {self.__main_controller.get_config('app_version_date')}"
)
# END View method
# START View events
def on_change_view(self, frame) -> None:
def on_change_view(self, frame: Frames) -> None:
"""
[event function for view]
=> Call this event method when the user requests to change the window.
:frame: -> The frame we want to launch.
* :frame: -> The frame we want to launch.
"""
self.__main_controller.change_frame(frame)
# END View events

View File

@ -1,11 +1,13 @@
import os
from controller.Frames import Frames
class MainController:
"""
Controller - MainController
TODO desc...
This controller manages all the main interaction, change of windows,
dialogs, stop... It is the main controller.
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@ -44,7 +46,7 @@ class MainController:
[event function for view]
=> Event launch when you ask to open the current folder.
"""
os.startfile(self.get_config('app_folder'))
os.startfile(self.get_config('app_folder')) # Open the file explorer on working dir
def on_quite(self) -> None:
"""
@ -70,12 +72,11 @@ class MainController:
[event function for view]
=> Event launched when a request for more information arise.
"""
# TODO on_about
print("on_about")
self.change_frame(Frames.INFO)
# END View methods
# START Controller methods
def change_frame(self, frame) -> None:
def change_frame(self, frame: Frames) -> None:
"""
[function for controller]
=> Allows you to request a frame change in the main window.

41
main.py
View File

@ -1,4 +1,5 @@
import os
import sys
from controller.HomeController import HomeController
from controller.InfoController import InfoController
from controller.MainController import MainController
@ -9,11 +10,38 @@ from view.InfoView import InfoView
from view.MainWindow import MainWindow
def get_sys_directory() -> str:
"""
Recover the path of the application's resources.
"""
try:
directory = sys._MEIPASS
except Exception:
directory = os.getcwd()
return directory
def get_config() -> dict:
"""
Retrieve the application configuration
"""
return {
'app_name': "WebPicDownloader",
'app_name': 'WebPicDownloader',
'app_folder': os.getcwd(),
'app_version': "1.0.0" # This version must match with the version.txt at root
'app_version': '1.0.0', # This version must match with the version.txt at root
'app_version_date': '2022-09-05',
'sys_directory': get_sys_directory(),
'about_title': 'About WebPicDownloader',
'about_content':
"""This scraping software has been developed by EndMove
and is fully open-source. The source code is available
here: https://git.endmove.eu/EndMove/WebPicDownloader
EndMove is available at the following address for any
request contact@endmove.eu. In case of problemsplease
open an issue on the repository.
The logo of the software was made by Gashila"""
}
if __name__ == '__main__':
@ -30,7 +58,7 @@ if __name__ == '__main__':
config = get_config()
# Create utli/model
webpic = WebPicDownloader(path=config.get('app_folder'), asynchrone=True)
webpic = WebPicDownloader(path=config.get('app_folder'))
# Create app controllers
main_controller = MainController(config)
@ -43,11 +71,12 @@ if __name__ == '__main__':
info_controller = InfoView(main_window, info_controller)
# Add views to main window
main_window.add_view(Frames.Home, home_view)
main_window.add_view(Frames.Info, info_controller)
main_window.add_view(Frames.HOME, home_view)
main_window.add_view(Frames.INFO, info_controller)
# Choose the launching view
main_window.show_frame(Frames.Home)
main_window.show_frame(Frames.HOME)
# Start main windows looping (launch program)
main_window.mainloop()

View File

@ -1,10 +1,31 @@
import os
from enum import Enum
from threading import Semaphore, Thread
from urllib import request
from urllib.error import HTTPError, URLError
from bs4 import BeautifulSoup, Tag, ResultSet
class MessageType(Enum):
"""
MessageType
Is an enumeration to define the different types of messages sent by the webpic messenger.
There are 3 types of messages.
- log -> log
- error -> err
- success -> suc
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@version 1.0.0
@since 2022-09-05
"""
LOG = 'log'
ERROR = 'err'
SUCCESS = 'suc'
class WebPicDownloader(Thread):
"""
WebPicDownloader
@ -12,19 +33,18 @@ class WebPicDownloader(Thread):
Webpicdownloader is a scraping tool that allows you to browse a web page,
find the images and download them. This tool is easily usable and implementable
in an application. It has been designed to be executed in an integrated thread
in an asynchronous way as well as more classically in a synchronous way. This
tool allows to define 3 callback functions, one for events, one in case of
success and one in case of failure. It also has an integrated entry point
allowing it to be directly executed in terminal mode.
in an asynchronous way. This tool allows to define 3 callback functions, one for
events, one in case of success and one in case of failure. It also has an
integrated entry point allowing it to be directly executed in terminal mode.
@author EndMove <contact@endmove.eu>
@version 1.2.0
@version 1.2.1
"""
# Variables
__callbacks: dict = None # Callback dictionary
__settings: dict = None #
__dl_infos: dict = None #
__sem: Semaphore = None #
__settings: dict = None # Webpic basics settings
__dl_infos: dict = None # Download informations
__sem: Semaphore = None # Semaphore for the webpic worker
_exit: bool = None # When set to True quit the thread
@ -39,7 +59,6 @@ class WebPicDownloader(Thread):
* :path: -> Folder in which the tool will create the download folders and place the images.
* :headers: -> Dictionary allowing to define the different parameters present in the header
of the requests sent by WebPic.
* :asynchronous: -> True: launch the download in a thread, False: the opposite.
* :messenger: -> Callback function messenger (see setter).
* :success: -> Callback function success (see setter).
* :failure: -> Callback function failure (see setter).
@ -60,8 +79,6 @@ class WebPicDownloader(Thread):
'website_url': 'url',
'download_name': 'name',
'download_path': 'full_path',
'tot_image_count': 0,
'dl_image_count': 0,
'running': False
}
self.__sem = Semaphore(0)
@ -69,6 +86,7 @@ class WebPicDownloader(Thread):
self.start() # start deamon
# Internal functions
def __get_html(self, url: str) -> str:
"""
@ -82,6 +100,7 @@ class WebPicDownloader(Thread):
response = request.urlopen(req)
return response.read().decode('utf-8')
def __find_all_img(self, html: str) -> ResultSet:
"""
Internal Function #do-not-use#
@ -93,6 +112,7 @@ class WebPicDownloader(Thread):
soup = BeautifulSoup(html, 'html.parser')
return soup.find_all('img')
def __find_img_link(self, img: Tag) -> str:
"""
Internal Function #do-not-use#
@ -115,6 +135,7 @@ class WebPicDownloader(Thread):
raise ValueError("Bad image url")
return link
def __find_image_type(self, img_link: str) -> str:
"""
Internal Function #do-not-use#
@ -128,6 +149,7 @@ class WebPicDownloader(Thread):
type = type.split('?')[0]
return type
def __download_img(self, url: str, filename: str) -> None:
"""
Internal Function #do-not-use#
@ -141,6 +163,7 @@ class WebPicDownloader(Thread):
with open(filename, 'wb') as img:
img.write(raw_img)
def __initialize_folder(self, folder_path: str) -> None:
"""
Internal Function #do-not-use#
@ -154,12 +177,17 @@ class WebPicDownloader(Thread):
else:
raise ValueError("The folder already exists, it may already contain images")
def __msg(self, message: str) -> None:
def __msg(self, message: str, type:MessageType=MessageType.LOG) -> None:
"""
Internal Function #do-not-use#
=> Use the messenger callback to send a message.
* :message: -> the message to send through callback
* :type: -> message type, can be ['log', 'err', 'suc']
"""
self.__callbacks.get('messenger')(message)
self.__callbacks.get('messenger')(message, type)
# Public functions
def set_success_callback(self, callback) -> None:
@ -170,6 +198,7 @@ class WebPicDownloader(Thread):
"""
self.__callbacks['success'] = callback
def set_failure_callback(self, callback) -> None:
"""
Setter to define the callback function called when the download fails.
@ -178,6 +207,7 @@ class WebPicDownloader(Thread):
"""
self.__callbacks['failure'] = callback
def set_messenger_callback(self, callback) -> None:
"""
Setter to define the callback function called when new messages arrive.
@ -186,74 +216,103 @@ class WebPicDownloader(Thread):
"""
self.__callbacks['messenger'] = callback
def start_downloading(self, url: str, name: str) -> None:
"""
TODO desc
Start downloading all pictures of a website.
* :url: -> The url of the website to annalyse.
* :folder_name: -> The name of the folder in which to upload the photos.
"""
if self.__dl_infos.get('running'):
print("bussy")
if not self.is_alive:
self.__msg("Opss, the download thread is not running, please restart webpic.", MessageType.ERROR)
elif self.__dl_infos.get('running'):
self.__msg("Opss, the download thread is busy.", MessageType.ERROR)
else:
self.__dl_infos['website_url'] = url
self.__dl_infos['download_name'] = name
self.__sem.release()
def stop_downloading(self, block=False) -> None:
"""
TODO DESC
Stops the download after the current item is processed and exit the downloading thread.
<!> Attention once called it will not be possible any more to download. <!>
* :block: -> If true, the function will block until the worker has finished working, if
False(default value), the stop message will be thrown and the program will continue.
"""
self.__exit = True
self.__sem.release()
if block:
self.join()
def is_download_running(self) -> bool:
"""
Indique si un téléchargement est en cours
* RETURN -> True if yes, False else.
"""
return self.__dl_infos['running'];
# Thread corp function
def run(self) -> None:
while True:
self.__sem.acquire()
self.__sem.acquire() # waiting the authorization to process
if self.__exit:
if self.__exit: # check if the exiting is requested
return
self.__dl_infos['running'] = True # reserv run
self.__dl_infos['running'] = True # indicate that the thread is busy
try:
# parse infos from url
html = self.__get_html(self.__dl_infos.get('website_url')) # website html
images = self.__find_all_img(html) # find all img balises ing html
self.__dl_infos['tot_image_count'] = len(images) # count total image
self.__dl_infos['dl_image_count'] = 0 # set download count to 0
# setting up download informaations
tot_count = len(images) # count total image
dl_count = 0 # set download count to 0
self.__dl_infos['download_path'] = f"{self.__settings.get('root_path')}/{self.__dl_infos.get('download_name')}/" # format path
# init working directory
self.__initialize_folder(self.__dl_infos.get('download_path')) # Init download folder
self.__msg(f"WebPicDownloader found {self.__dl_infos.get('tot_image_count')} images on the website.")
self.__msg(f"WebPicDownloader found {tot_count} images on the website.")
# process pictures
# start images processing
for i, img in enumerate(images):
try:
self.__msg(f"Start downloading image {i}.")
img_link = self.__find_img_link(img) # find image link
self.__download_img(img_link, f"{self.__dl_infos.get('download_path')}image-{i}.{self.__find_image_type(img_link)}") # download the image
self.__msg(f"Download of image {i}, done!")
self.__dl_infos['dl_image_count'] += 1 # increment download counter
dl_count += 1 # increment download counter
except Exception as err:
self.__msg(f"ERROR: Unable to process image {i} -> err[{err}].")
self.__msg(f"WebPicDownloader has processed {self.__dl_infos.get('dl_image_count')} images out of {self.__dl_infos.get('tot_image_count')}.")
# end images processing
self.__msg(f"WebPicDownloader has processed {dl_count} images out of {tot_count}.")
self.__callbacks.get('success')() # success, launch callback
except Exception as err:
self.__msg(f"ERROR: An error occured -> err[{err}]")
self.__callbacks.get('failure')() # error, launch callback
self.__dl_infos['running'] = False # free run
self.__dl_infos['running'] = False # inficate that the thread is free
if __name__ == "__main__":
# Internal entry point for testing and consol use.
wpd = WebPicDownloader()
def lol(msg):
pass
wpd.set_messenger_callback(lol)
while True:
url = input("Website URL ? ")
name = input("Folder name ? ")
wpd.start_downloading(url, name)
if "n" == input("Do you want to continue [Y/n] ? ").lower():
wpd.stop_downloading()
break
wpd.stop_downloading(block=True)
print("Good bye !")

View File

@ -3,23 +3,26 @@ import tkinter.font as tfont
from tkinter import ttk
from tkinter import scrolledtext as tst
from controller.HomeController import HomeController
from view.MainWindow import MainWindow
class HomeView(ttk.Frame):
"""
View - MainWindow
View - HomeWindow
dec...
This view allows you to start the scraping/downloading process,
as well as to display the progress of the process.
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@version 1.0.0
@since 2022-08-30
@version 1.0.1
@since 2022-09-05
"""
# Variables
__controller: HomeController = None
# Constructor
def __init__(self, parent, controller: HomeController):
def __init__(self, parent: MainWindow, controller: HomeController):
"""
Constructor
@ -28,14 +31,14 @@ class HomeView(ttk.Frame):
"""
super().__init__(parent)
# Save and setup main controller
self.__controller = controller
controller.set_view(self)
# Init view
self.__init_content()
# START Internal function
# Save and setup controller
self.__controller = controller
controller.set_view(self)
# START Internal functions
def __init_content(self) -> None:
"""
[internal function]
@ -61,6 +64,7 @@ class HomeView(ttk.Frame):
self.log_textarea = tst.ScrolledText(self, font=log_textarea_font, wrap=tk.WORD, state=tk.DISABLED, width=40, height=8)#, font=("Times New Roman", 15))
self.log_textarea.grid(row=3, column=0, columnspan=2, sticky=tk.EW, pady=10, padx=10)
# Message state
self.message_label = ttk.Label(self, text='message label')
# Download button
@ -73,13 +77,15 @@ class HomeView(ttk.Frame):
=> Function called when a download is requested.
"""
self.__controller.on_download_requested(self.web_entry.get(), self.name_entry.get())
# END Internal function
# END Internal functions
# START Controller methods
def add_log(self, line: str) -> None:
"""
[function for controller]
TODO desc
=> Add a log in the textarea where the logs are displayed.
* :line: -> Log message to add.
"""
self.log_textarea.configure(state=tk.NORMAL)
self.log_textarea.insert(tk.END, f"~ {line}\n")
@ -89,7 +95,7 @@ class HomeView(ttk.Frame):
def clear_logs(self) -> None:
"""
[function for controller]
TODO desc
=> Clean the textarea where the logs are displayed.
"""
self.log_textarea.configure(state=tk.NORMAL)
self.log_textarea.delete('1.0', tk.END)
@ -98,25 +104,42 @@ class HomeView(ttk.Frame):
def show_error_message(self, message) -> None:
"""
[function for controller]
TODO desc
=> Display an error message on the interface.
* :message: -> Message to display.
"""
self.message_label.configure(text=message, foreground='red')
self.message_label.grid(row=4, column=0, columnspan=2, sticky=tk.NS, padx=2, pady=2)
self.message_label.after(30000, self.hide_message)
self.message_label.after(25000, self.hide_message)
def show_success_message(self, message) -> None:
"""
[function for controller]
TODO desc
=> Display a success message on the interface.
* :message: -> Message to display.
"""
self.message_label.configure(text=message, foreground='green')
self.message_label.grid(row=4, column=0, columnspan=2, sticky=tk.NS, padx=2, pady=2)
self.message_label.after(30000, self.hide_message)
self.message_label.after(25000, self.hide_message)
def hide_message(self) -> None:
"""
[function for controller]
TODO desc
[function for controller and this view]
=> Hide the message on the interface.
"""
self.message_label.grid_forget()
def set_interface_state(self, disable: bool) -> None:
"""
[function for controller]
=> Allows to change the status of the interface with which the user
interacts by activating/deactivating it.
* :disabled: -> True: interface disabled, False: interface enabled.
"""
state = tk.DISABLED if disable else tk.NORMAL
self.web_entry['state'] = state
self.name_entry['state'] = state
self.download_button['state'] = state
# END Controller methods

View File

@ -1,7 +1,10 @@
import tkinter as tk
from tkinter import font
from tkinter import ttk
from controller.Frames import Frames
from controller.InfoController import InfoController
from view.MainWindow import MainWindow
class InfoView(ttk.Frame):
"""
@ -18,7 +21,7 @@ class InfoView(ttk.Frame):
__controller: InfoController = None
# Constructor
def __init__(self, parent, controller: InfoController):
def __init__(self, parent: MainWindow, controller: InfoController):
"""
Constructor
@ -27,33 +30,72 @@ class InfoView(ttk.Frame):
"""
super().__init__(parent)
# create widgets
# label
self.label = ttk.Label(self, text='Email:')
self.label.grid(row=1, column=0)
# Init view
self.__init_content()
# email entry
# self.email_var = tk.StringVar()
# self.email_entry = ttk.Entry(self, textvariable=self.email_var, width=30)
# self.email_entry.grid(row=1, column=1, sticky=tk.NSEW)
# save button
self.save_button = ttk.Button(self, text='just a button', command=self.event_btn)
self.save_button.grid(row=1, column=3, padx=10)
# message
self.message_label = ttk.Label(self, text='Je suis super man comment allez vous heheheh je suis toutou', foreground='red')
self.message_label.grid(row=2, column=0, sticky=tk.EW)
# place this frame
# self.grid(row=0, column=0, padx=5, pady=5, sticky=tk.NSEW)
# self.pack(fill='both', expand=True)
# Save and setup main controller
# Save and setup controller
self.__controller = controller
controller.set_view(self)
def event_btn(self) -> None:
print("you clicked on the button that is on the info view!")
print("got redirected to the home view :D")
self.__controller.on_change_view(Frames.Home)
# START Internal functions
def __init_content(self) -> None:
"""
[internal function]
=> Initialize the view content.
"""
self.columnconfigure(0, weight=4)
# Back button
self.back_button = ttk.Button(self, text="Back", command=self.__event_button_back)
self.back_button.grid(row=0, column=0, sticky=tk.E, padx=5, pady=5, ipadx=1, ipady=1)
# About title
self.title_label_font = font.Font(self, size=16, weight=font.BOLD)
self.title_label = ttk.Label(self, text="A title", font=self.title_label_font)
self.title_label.grid(row=1, column=0, sticky=tk.NS, padx=2, pady=2)
# About content
self.content_label_font = font.Font(self, size=10)
self.content_label = ttk.Label(self, wraplength=400, justify='center', text='A long text', font=self.content_label_font, foreground='blue')
self.content_label.grid(row=2, column=0, sticky=tk.NS)
# About version
self.version_label = ttk.Label(self, text='version : 1.0.0 - 02-02-2022')
self.version_label.grid(row=3, column=0, sticky=tk.NS, pady=15)
def __event_button_back(self) -> None:
"""
[internal function]
=> Function called when back button pressed.
"""
self.__controller.on_change_view(Frames.HOME)
# END Internal functions
# START Controller methods
def set_title(self, title: str) -> None:
"""
[function for controller]
=> Define view/page info : title
* :title: -> Title for the view.
"""
self.title_label.configure(text=title)
def set_content(self, content: str) -> None:
"""
[function for controller]
=> Define view/page info : content
* :content: -> Content for the view.
"""
self.content_label.configure(text=content)
def set_version(self, version: str) -> None:
"""
[function for controller]
=> Define view/page info : version
* :version: -> Version for the view.
"""
self.version_label.configure(text=version)
# END Controller methods

View File

@ -1,5 +1,8 @@
import os
import sys
import tkinter as tk
from tkinter import messagebox
from controller.Frames import Frames
from controller.MainController import MainController
@ -7,7 +10,8 @@ class MainWindow(tk.Tk):
"""
View - MainWindow
TODO dec...
This view is the main view of the application, it manages the different frames/views
of the application, captures the events to send them to the main controller.
@author Jérémi Nihart / EndMove
@link https://git.endmove.eu/EndMove/WebPicDownloader
@ -47,8 +51,9 @@ class MainWindow(tk.Tk):
=> Initialize window parameters
"""
# self.title('My tkinter app')
# self.geometry('450x250')
self.geometry('430x310')
self.resizable(False, False)
self.iconbitmap(f"{self.__controller.get_config('sys_directory')}\\assets\logo.ico")
# self.config(bg='#f7ef38')
def __init_top_menu(self) -> None:
@ -99,7 +104,7 @@ class MainWindow(tk.Tk):
"""
self.title(title)
def show_frame(self, frame) -> None:
def show_frame(self, frame: Frames) -> None:
"""
[function for app & controller]
=> Allows to display the selected frame provided that it
@ -125,20 +130,23 @@ class MainWindow(tk.Tk):
def show_question_dialog(self, title: str, message: str, icon: str='question') -> bool:
"""
[function for controller]
=> TODO DESC
=> Display a question dialog to the user, which he can answer with yes or no.
* :message: ->
* RETURN ->
* :title: -> Title of the dialogue.
* :message: -> Message of the dialogue displayed to the user.
* :icon: -> Icon of the dialogue displayed to the user.
* RETURN -> True id the user selected yes, False else.
"""
return messagebox.askquestion(title, message, icon=icon)
return True if (messagebox.askquestion(title, message, icon=icon) == "yes") else False
def show_information_dialog(self, title: str, message: str, icon: str='information') -> None:
"""
[function for controller]
=> TODO DESC
=> Display an information dialog to the user.
* :message: ->
* RETURN ->
* :title: -> Title of the dialogue.
* :message: -> Message of the dialogue displayed to the user.
* :icon: -> Icon of the dialogue displayed to the user.
"""
messagebox.showinfo(self, title, message, icon=icon)
# END Controller methods