#!/usr/bin/env python3 # -*- coding: utf-8 -*- # By Elvish_Hunter, April 2014 # You may notice that this script, unlike all our other Python mainline scripts, # has a .pyw extension, instead of .py. *This is deliberate*. On Windows, .pyw # scripts are started directly in GUI mode, without opening a prompt. # This is, after all, the behavior that we want. # threading and subprocess are needed to run wmllint without freezing the window # codecs is used to save files as UTF8 # queue is needed to exchange informations between threads # if we use the run_tool thread to do GUI stuff we obtain weird crashes # This happens because Tk is a single-thread GUI import sys,os,threading,subprocess,codecs import queue # tkinter modules from tkinter import * from tkinter.messagebox import * from tkinter.filedialog import * import tkinter.font as font # ttk must be called last from tkinter.ttk import * # we need to know in what series we are # so set it in a constant and change it for every new series # it must be a string WESNOTH_SERIES="1.13" # get the location where the script is placed # we'll check later if this is a Wesnoth directory # and use it to generate the command lines # os.path.realpath gets the full path of this script, # while removing any symlink # This allows users to create a link to the app on their desktop # os.path.normpath allows Windows users to see their standard path separators APP_DIR,APP_NAME=os.path.split(os.path.realpath(sys.argv[0])) upper_dir=APP_DIR.split(os.sep) upper_dir.pop() WESNOTH_DATA_DIR=os.sep.join(upper_dir) WESNOTH_CORE_DIR=os.path.normpath(os.path.join(WESNOTH_DATA_DIR,"core")) def wrap_elem(line): """If the supplied line contains spaces, return it wrapped between double quotes""" if ' ' in line: return "\"{0}\"".format(line) return line def run_tool(tool,queue,command): """Runs a maintenance tool with the desired arguments and pushes the output in the supplied queue""" # set the encoding for the subprocess # otherwise, with the new Unicode literals used by the wml tools, # we may get UnicodeDecodeErros env=os.environ env['PYTHONIOENCODING']="utf8" if sys.platform=="win32": # Windows wants a string, Linux wants a list and Polly wants a cracker # Windows wants also strings flavoured with double quotes # since maps return iterators, we must cast them as lists, otherwise join won't work # not doing this causes an OSError: [WinError 87] # this doesn't happen on Python 2.7, because here map() returns a list wrapped_line=list(map(wrap_elem,command)) queue.put_nowait(' '.join(wrapped_line)+"\n") si=subprocess.STARTUPINFO() si.dwFlags=subprocess.STARTF_USESHOWWINDOW|subprocess.SW_HIDE # to avoid showing a DOS prompt try: output=subprocess.check_output(' '.join(wrapped_line),stderr=subprocess.STDOUT,startupinfo=si,env=env) queue.put_nowait(str(output, "utf8")) except subprocess.CalledProcessError as error: # post the precise message and the remaining output as a tuple queue.put_nowait((tool,error.returncode,str(error.output, "utf8"))) else: # STARTUPINFO is not available, nor needed, outside of Windows queue.put_nowait(' '.join(command)+"\n") try: output=subprocess.check_output(command,stderr=subprocess.STDOUT,env=env) queue.put_nowait(str(output, "utf8")) except subprocess.CalledProcessError as error: # post the precise message and the remaining output as a tuple queue.put_nowait((tool,error.returncode, str(error.output, "utf8"))) def is_wesnoth_tools_path(path): """Checks if the supplied path may be a wesnoth/data/tools directory""" lower_path=path.lower() if "wesnoth" in lower_path and \ "data" in lower_path and \ "tools" in lower_path: return True return False def attach_context_menu(widget,function): # on Mac the right button fires a Button-2 event, or so I'm told # some mice don't even have two buttons, so the user is forced # to use Command + the only button # bear in mind that I don't have a Mac, so this point may be bugged # bind also the context menu key, for those keyboards that have it # that is, most of the Windows and Linux ones (however, in Win it's # called App, while on Linux is called Menu) # Mac doesn't have a context menu key on its keyboards, so no binding # bind also the Shift+F10 shortcut (same as Menu/App key) # the call to tk windowingsystem is justified by the fact # that it is possible to install X11 over Darwin windowingsystem = widget.tk.call('tk', 'windowingsystem') if windowingsystem == "win32": # Windows, both 32 and 64 bit widget.bind("", function) widget.bind("", function) widget.bind("", function) elif windowingsystem == "aqua": # MacOS with Aqua widget.bind("", function) widget.bind("", function) elif windowingsystem == "x11": # Linux, FreeBSD, Darwin with X11 widget.bind("", function) widget.bind("", function) widget.bind("", function) def attach_select_all(widget,function): # bind the "select all" key shortcut # again, Mac uses Command instead of Control windowingsystem = widget.tk.call('tk', 'windowingsystem') if windowingsystem == "win32": widget.bind("", function) elif windowingsystem == "aqua": widget.bind("", function) elif windowingsystem == "x11": widget.bind("", function) class Tooltip(Toplevel): def __init__(self,widget,text,tag=None): """A tooltip, or balloon. Displays the specified help text when the mouse pointer stays on the widget for more than 500 ms.""" # the master attribute retrieves the window where our "parent" widget is super().__init__(widget.master) self.widget=widget self.preshow_id=None self.show_id=None self.label=Label(self, text=text, background="#ffffe1", # background color used on Windows borderwidth=1, relief=SOLID, padding=1, # Tk has a bunch of predefined fonts # use the one specific for tooltips font=font.nametofont("TkTooltipFont")) self.label.pack() self.overrideredirect(True) # allow binding the tooltips to tagged elements of a widget # only Text, Canvas and Treeview support tags # and as such, they have a tag_bind method # if the widget doesn't support tags, bind directly to it if tag and hasattr(widget, "tag_bind") and callable(widget.tag_bind): self.widget.tag_bind(tag,"",self.preshow) self.widget.tag_bind(tag,"",self.hide) else: self.widget.bind("",self.preshow) self.widget.bind("",self.hide) self.bind_all("