diff --git a/assets/logo.ico b/assets/logo.ico new file mode 100644 index 0000000..ca34922 Binary files /dev/null and b/assets/logo.ico differ diff --git a/autopy-conf.json b/autopy-conf.json index 740eed0..b8ba9be 100644 --- a/autopy-conf.json +++ b/autopy-conf.json @@ -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": { diff --git a/controller/Frames.py b/controller/Frames.py index 96a6856..5428027 100644 --- a/controller/Frames.py +++ b/controller/Frames.py @@ -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 diff --git a/controller/HomeController.py b/controller/HomeController.py index f7b0456..99b594b 100644 --- a/controller/HomeController.py +++ b/controller/HomeController.py @@ -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 \ No newline at end of file diff --git a/controller/InfoController.py b/controller/InfoController.py index e29b50c..d4681f0 100644 --- a/controller/InfoController.py +++ b/controller/InfoController.py @@ -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 diff --git a/controller/MainController.py b/controller/MainController.py index a0f7503..9ae2433 100644 --- a/controller/MainController.py +++ b/controller/MainController.py @@ -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. diff --git a/main.py b/main.py index 6043a7d..6617ef0 100644 --- a/main.py +++ b/main.py @@ -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() diff --git a/model/WebPicDownloader.py b/model/WebPicDownloader.py index 77cc698..6dd25d8 100644 --- a/model/WebPicDownloader.py +++ b/model/WebPicDownloader.py @@ -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 - @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 !") \ No newline at end of file diff --git a/view/HomeView.py b/view/HomeView.py index 5bb25ca..24dda9c 100644 --- a/view/HomeView.py +++ b/view/HomeView.py @@ -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 @@ -27,15 +30,15 @@ class HomeView(ttk.Frame): * :controller: -> The view controller """ super().__init__(parent) - - # Save and setup main controller - self.__controller = controller - controller.set_view(self) # Init view self.__init_content() + + # Save and setup controller + self.__controller = controller + controller.set_view(self) - # START Internal function + # 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 diff --git a/view/InfoView.py b/view/InfoView.py index 82ef8cd..30c2086 100644 --- a/view/InfoView.py +++ b/view/InfoView.py @@ -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 diff --git a/view/MainWindow.py b/view/MainWindow.py index 42d9dfa..7d76c8e 100644 --- a/view/MainWindow.py +++ b/view/MainWindow.py @@ -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