From a08a6e381368beff9d60d9f7b80f3313e5d3c081 Mon Sep 17 00:00:00 2001 From: Michael Madsen Date: Tue, 6 Jan 2015 20:52:28 +0100 Subject: [PATCH] UI for pull request #10 --- core/baselines.py | 6 +- core/mods.py | 2 +- tkgui/graphics.py | 8 ++- tkgui/mods.py | 6 ++ tkgui/tab.py | 1 + tkgui/tkgui.py | 138 +++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 156 insertions(+), 5 deletions(-) diff --git a/core/baselines.py b/core/baselines.py index cf99731..92db79a 100644 --- a/core/baselines.py +++ b/core/baselines.py @@ -9,9 +9,10 @@ from . import paths, update from .lnp import lnp -def find_vanilla_raws(): +def find_vanilla_raws(download_missing=True): """Finds vanilla raws for the current version. Starts by unzipping any DF releases in baselines and preprocessing them. + If download_missing is set to True, missing baselines will be downloaded. Returns: Path to the vanilla 'raw' folder, eg 'LNP/Baselines/df_40_15/raw' @@ -25,7 +26,8 @@ def find_vanilla_raws(): version = 'df_' + str(lnp.df_info.version)[2:].replace('.', '_') if os.path.isdir(paths.get('baselines', version, 'raw')): return paths.get('baselines', version, 'raw') - update.download_df_baseline() + if download_missing: + update.download_df_baseline() return False def prepare_baselines(): diff --git a/core/mods.py b/core/mods.py index 1a42080..a67a5ea 100644 --- a/core/mods.py +++ b/core/mods.py @@ -182,7 +182,7 @@ def merge_raw_folders(mod_raw_folder, vanilla_raw_folder): def clear_temp(): """Resets the folder in which raws are mixed.""" - if not baselines.find_vanilla_raws(): + if not baselines.find_vanilla_raws(False): # TODO: add user warning re: missing baseline, download return None if os.path.exists(paths.get('baselines', 'temp')): diff --git a/tkgui/graphics.py b/tkgui/graphics.py index 8513616..1c438c7 100644 --- a/tkgui/graphics.py +++ b/tkgui/graphics.py @@ -9,7 +9,7 @@ from .tab import Tab import sys, os -from core import colors, graphics, paths +from core import colors, graphics, paths, download, baselines from core.lnp import lnp if sys.version_info[0] == 3: # Alternate import names @@ -190,6 +190,9 @@ def read_graphics(self): def install_graphics(self): """Installs a graphics pack.""" if len(self.graphicpacks.curselection()) != 0: + from .tkgui import TkGui + if not TkGui.check_vanilla_raws(): + return gfx_dir = self.graphicpacks.get(self.graphicpacks.curselection()[0]) if messagebox.askokcancel( message='Your graphics, settings and raws will be changed.', @@ -236,6 +239,9 @@ def update_savegames(): def simplify_graphics(self): """Removes unnecessary files from graphics packs.""" + from .tkgui import TkGui + if not TkGui.check_vanilla_raws(): + return self.read_graphics() for pack in self.graphics.get(): result = graphics.simplify_pack(pack) diff --git a/tkgui/mods.py b/tkgui/mods.py index b8b425b..38192c5 100644 --- a/tkgui/mods.py +++ b/tkgui/mods.py @@ -173,6 +173,9 @@ def remove_from_installed(self): def perform_merge(self): """Merge the selected mods, with background color for user feedback.""" + from .tkgui import TkGui + if not TkGui.check_vanilla_raws(): + return mods.clear_temp() # Set status to unknown before merging for i, _ in enumerate(self.installed_list.get(0, END)): @@ -202,4 +205,7 @@ def install_mods(): @staticmethod def simplify_mods(): """Simplify mods; runs on startup if called directly by button.""" + from .tkgui import TkGui + if not TkGui.check_vanilla_raws(): + return mods.simplify_mods() diff --git a/tkgui/tab.py b/tkgui/tab.py index 9e65c22..690ba0b 100644 --- a/tkgui/tab.py +++ b/tkgui/tab.py @@ -20,6 +20,7 @@ class Tab(Frame): def __init__(self, parent): #pylint:disable=super-init-not-called Frame.__init__(self, parent) + self.parent = parent self.pack(side=TOP, fill=BOTH, expand=Y) self.create_variables() self.create_controls() diff --git a/tkgui/tkgui.py b/tkgui/tkgui.py index a61871b..14d79d1 100644 --- a/tkgui/tkgui.py +++ b/tkgui/tkgui.py @@ -6,6 +6,7 @@ import os import sys +from threading import Semaphore from . import controls, binding from .child_windows import LogWindow, InitEditor, SelectDF, UpdateWindow @@ -20,7 +21,7 @@ from .mods import ModsTab from core.lnp import lnp -from core import df, launcher, paths, update, mods +from core import df, launcher, paths, update, mods, download, baselines if sys.version_info[0] == 3: # Alternate import names # pylint:disable=import-error @@ -111,6 +112,7 @@ def __init__(self): """ self.root = root = Tk() self.updateDays = IntVar() + self.downloadBaselines = BooleanVar() controls.init(self) binding.init(lnp) @@ -137,6 +139,16 @@ def __init__(self): self.logo = logo = get_image(get_resource('LNPSMALL')) Label(root, image=logo, anchor=CENTER).pack(fill=X) main.pack(side=TOP, fill=BOTH, expand=Y) + + self.download_panel = controls.create_control_group( + main, 'Download status') + self.download_text = StringVar() + self.download_status = Label( + self.download_panel, textvariable=self.download_text) + self.download_panel.pack(fill=X, expand=N, side=BOTTOM) + self.download_status.pack(side=BOTTOM) + self.download_status.grid(row=0, column=0) + self.n = n = Notebook(main) self.create_tab(OptionsTab, 'Options') @@ -169,14 +181,33 @@ def __init__(self): self.save_size = None root.update() root.minsize(width=root.winfo_width(), height=root.winfo_height()) + self.download_panel.pack_forget() + root.update() root.geometry('{}x{}'.format( lnp.userconfig.get_number('tkgui_width'), lnp.userconfig.get_number('tkgui_height'))) root.bind("", self.on_resize) + queue = download.get_queue('baselines') + queue.register_start_queue(self.start_download_queue) + queue.register_begin_download(self.start_download) + queue.register_progress(self.download_progress) + queue.register_end_download(self.end_download) + queue.register_end_queue(self.end_download_queue) + binding.update() root.bind('<>', lambda e: UpdateWindow(self.root)) + # Used for cross-thread signaling and communication during downloads + self.update_pending = Semaphore(1) + self.cross_thread_data = None + self.reply_semaphore = Semaphore(0) + self.download_text_string = '' + root.bind('<>', lambda e: self.confirm_downloading()) + root.bind('<>', lambda e: self.update_download_text()) + root.bind( + '<>', lambda e: self.download_panel.pack_forget()) + def on_resize(self, e): """Called when the window is resized.""" lnp.userconfig['tkgui_width'] = self.root.winfo_width() @@ -282,6 +313,11 @@ def create_menu(self, root): menu_updates.add_radiobutton( label=o, value=daylist[i], variable=self.updateDays, command=lambda i=i: self.configure_updates(daylist[i])) + self.downloadBaselines.set(lnp.userconfig.get_bool('downloadBaselines')) + menu_file.add_checkbutton( + label='Allow auto-download of baselines', onvalue=True, + offvalue=False, variable=self.downloadBaselines, + command=self.set_downloads) if sys.platform != 'darwin': menu_file.add_command( @@ -318,6 +354,10 @@ def configure_updates(self, days): self.updateDays.set(days) update.next_update(days) + def set_downloads(self): + """Sets the option for auto-download of baselines.""" + baselines.set_auto_download(self.downloadBaselines.get()) + @staticmethod def populate_menu(collection, menu, method): """ @@ -439,4 +479,100 @@ def set_option(field): def show_df_info(): """Shows basic information about the current DF install.""" messagebox.showinfo(title='DF info', message=str(lnp.df_info)) + + def confirm_downloading(self): + """Ask the user if downloading may proceed.""" + if self.cross_thread_data == 'baselines': + message = ( + 'PyLNP needs to download a copy of Dwarf Fortress to ' + 'complete this action. Is this OK?\n\nPlease note: You will ' + 'need to retry the action after the download completes.') + else: + message = ( + 'PyLNP needs to download data to process this action. ' + 'Is this OK?\n\nPlease note: You will need to retry the action ' + 'after the download completes.') + self.cross_thread_data = messagebox.askyesno( + message=message, title='Download data?', icon='question') + self.reply_semaphore.release() + + def start_download_queue(self, queue): + """Event handler for starting a download queue.""" + result = True + if not lnp.userconfig.get_bool('downloadBaselines'): + self.cross_thread_data = queue + self.root.event_generate('<>', when='tail') + self.reply_semaphore.acquire() + result = self.cross_thread_data + if result: + self.download_panel.pack(fill=X, expand=N, side=BOTTOM) + self.send_update_event() + return result + + def send_update_event(self, force=False): + """Schedules an update for the download text, if not already pending.""" + if self.update_pending.acquire(force): + self.root.event_generate('<>', when='tail') + + def start_download(self, queue, url, target): + """Event handler for the start of a download.""" + self.download_text_string = "Downloading %s..." % os.path.basename(url) + self.send_update_event(True) + + def update_download_text(self): + """Updates the text in the download information.""" + s = self.download_text_string + self.download_text.set(s) + # Delay to prevent crash from event flood + self.root.after(200, self.update_pending.release) + + def download_progress(self, queue, url, progress, total): + """Event handler for download progress.""" + if total != -1: + self.download_text_string = "Downloading %s... (%s/%s)" % ( + os.path.basename(url), progress, total) + else: + self.download_text_string = ( + "Downloading %s... (%s bytes downloaded)" % ( + os.path.basename(url), progress)) + self.send_update_event(True) + + def end_download(self, queue, url, target, success): + """Event handler for the end of a download.""" + if success: + self.download_text_string = "Download finished" + else: + self.download_text_string = "Download failed" + self.send_update_event(True) + + def end_download_queue(self, queue): + """Event handler for the end of a download queue.""" + self.root.after(5000, lambda: self.root.event_generate( + '<>', when='tail')) + self.send_update_event() + + @staticmethod + def check_vanilla_raws(): + """Validates status of vanilla raws are ready.""" + if not download.get_queue('baselines').empty(): + return False + raw_status = baselines.find_vanilla_raws() + if raw_status is None: + messagebox.showerror( + message='Your Dwarf Fortress version could not be detected ' + 'accurately, which is necessary to process this request.' + '\n\nYou will need to restore the file "release notes.txt" in ' + 'order to use this launcher feature.', title='Cannot continue') + return False + if raw_status is False: + if lnp.userconfig.get_bool('downloadBaselines'): + messagebox.showinfo( + message='A copy of Dwarf Fortress needs to be ' + 'downloaded in order to use this. It will start when ' + 'you close this dialog.\n\nPlease note: You ' + 'will need to retry the action after the download ' + 'completes.', title='Download required') + return False + return True + # vim:expandtab