diff --git a/.gitignore b/.gitignore index dcee5d692..c5efd554c 100644 --- a/.gitignore +++ b/.gitignore @@ -118,4 +118,7 @@ ENV/ .idea eos.iml gitversion +.version +/.version *.swp + diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..26dd19431 --- /dev/null +++ b/.mailmap @@ -0,0 +1,14 @@ +cncfanatics cncfanatics +blitzmann +blitzmann blitzmann +blitzmann +blitzmann blitzman +blitzmann Ryan Holmes +blitzmann +Corollax Corollax +Corollax Corollax +Mr. Nukealizer Mr. Nukealizer +DarkPhoenix +Sakari Orisi +Will Wykeham Will Wykeham +OISumeko OISumeko \ No newline at end of file diff --git a/README.md b/README.md index fd87aed48..2b3170fc9 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,8 @@ The latest version along with release notes can always be found on the project's Windows and OS X users are supplied self-contained builds of pyfa on the [latest releases](https://github.com/pyfa-org/Pyfa/releases/latest) page. An `.exe` installer is also available for Windows builds. Linux users can run pyfa using their distribution's Python interpreter. There is no official self-contained package for Linux, however, there are a number of third-party packages available through distribution-specific repositories. #### OS X -There are two different distributives for OS X: `-mac` and `-mac-deprecated`. -* `-mac`: based on wxPython 3.0.2.0 and has updated libraries. This is the recommended build. -* `-mac-deprecated`: utilizes older binaries running on wxPython 2.8; because of this, some features are not available (currently CREST support and Attribute Overrides). Additionally, as development happens primarily on wxPython 3.0, a few GUI bugs may pop up as `-mac-deprecated` is not actively tested. However, due to some general issues with wxPython 3.0, especially on some newer OS X versions, `-mac-deprecated` is still offered for those that need it. - -There is also a [Homebrew](http://brew.sh) option for installing pyfa on OS X. Please note this is maintained by a third-party and is not tested by pyfa developers. Simply fire up in terminal: +Apart from the official release, there is also a [Homebrew](http://brew.sh) option for installing pyfa on OS X. Please note this is maintained by a third-party and is not tested by pyfa developers. Simply fire up in terminal: ``` $ brew install Caskroom/cask/pyfa ``` @@ -36,13 +32,8 @@ The following is a list of pyfa packages available for certain distributions. Pl ### Dependencies If you wish to help with development or simply need to run pyfa through a Python interpreter, the following software is required: -* Python 2.7 -* `wxPython` 2.8/3.0 -* `sqlalchemy` >= 1.0.5 -* `dateutil` -* `matplotlib` (for some Linux distributions you may need to install separate wxPython bindings such as `python-matplotlib-wx`) -* `requests` -* `logbook` >= 1.0.0 +* Python 3.6 +* Requirements as listed in `requirements.txt` ## Bug Reporting The preferred method of reporting bugs is through the project's [GitHub Issues interface](https://github.com/pyfa-org/Pyfa/issues). Alternatively, posting a report in the [pyfa thread](http://forums.eveonline.com/default.aspx?g=posts&t=247609) on the official EVE Online forums is acceptable. Guidelines for bug reporting can be found on [this wiki page](https://github.com/DarkFenX/Pyfa/wiki/Bug-Reporting). diff --git a/_development/helpers.py b/_development/helpers.py index 4ae17bc5e..8af8f9733 100644 --- a/_development/helpers.py +++ b/_development/helpers.py @@ -28,7 +28,7 @@ def DBInMemory_test(): gamedataCache = True saveddataCache = True gamedata_version = "" - gamedata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(unicode(__file__))), "..", "eve.db")) + gamedata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(str(__file__))), "..", "eve.db")) saveddata_connectionstring = 'sqlite:///:memory:' class ReadOnlyException(Exception): @@ -100,8 +100,8 @@ def DBInMemory(): import eos.db # Output debug info to help us troubleshoot Travis - print(eos.db.saveddata_engine) - print(eos.db.gamedata_engine) + print((eos.db.saveddata_engine)) + print((eos.db.gamedata_engine)) helper = { 'config': eos.config, diff --git a/_development/helpers_fits.py b/_development/helpers_fits.py index 61db7a3ff..a0506827d 100644 --- a/_development/helpers_fits.py +++ b/_development/helpers_fits.py @@ -41,7 +41,7 @@ def CurseFit(DB, Gamedata, Saveddata): mod.state = Saveddata['State'].ONLINE # Add 5 neuts - for _ in xrange(5): + for _ in range(5): fit.modules.append(mod) return fit @@ -60,7 +60,7 @@ def HeronFit(DB, Gamedata, Saveddata): mod.state = Saveddata['State'].ONLINE # Add 5 neuts - for _ in xrange(4): + for _ in range(4): fit.modules.append(mod) return fit \ No newline at end of file diff --git a/_development/helpers_locale.py b/_development/helpers_locale.py index 4d4d29f4e..27983278d 100644 --- a/_development/helpers_locale.py +++ b/_development/helpers_locale.py @@ -94,8 +94,8 @@ def GetUnicodePath(root, file=None, codec=None): path = os.path.join(path, file) if codec: - path = unicode(path, codec) + path = str(path, codec) else: - path = unicode(path) + path = str(path) return path diff --git a/config.py b/config.py index e87c9ec1b..24adc62c7 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,11 @@ import os import sys -from logbook import Logger +from logbook import CRITICAL, DEBUG, ERROR, FingersCrossedHandler, INFO, Logger, NestedSetup, NullHandler, \ + StreamHandler, TimedRotatingFileHandler, WARNING +import hashlib + +from cryptography.fernet import Fernet pyfalog = Logger(__name__) @@ -19,17 +23,38 @@ debug = False saveInRoot = False # Version data -version = "1.36.0" + +version = "2.0.1" tag = "Stable" expansionName = "YC120.3" expansionVersion = "1.8" evemonMinVersion = "4081" +minItemSearchLength = 3 + pyfaPath = None savePath = None saveDB = None gameDB = None logPath = None +loggingLevel = None +logging_setup = None +cipher = None +clientHash = None + +ESI_CACHE = 'esi_cache' + +LOGLEVEL_MAP = { + "critical": CRITICAL, + "error": ERROR, + "warning": WARNING, + "info": INFO, + "debug": DEBUG, +} + + +def getClientSecret(): + return clientHash def isFrozen(): @@ -45,23 +70,37 @@ def __createDirs(path): def getPyfaRoot(): - base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else sys.argv[0] + if hasattr(sys, '_MEIPASS'): + return sys._MEIPASS + base = getattr(sys.modules['__main__'], "__file__", sys.executable) if isFrozen() else __file__ root = os.path.dirname(os.path.realpath(os.path.abspath(base))) - root = unicode(root, sys.getfilesystemencoding()) + root = root return root +def getVersion(): + if os.path.isfile(os.path.join(pyfaPath, '.version')): + with open(os.path.join(pyfaPath, '.version')) as f: + gitVersion = f.readline() + return gitVersion + # if no version file exists, then user is running from source or not an official build + return version + " (git)" + + def getDefaultSave(): - return unicode(os.path.expanduser(os.path.join("~", ".pyfa")), sys.getfilesystemencoding()) + return os.path.expanduser(os.path.join("~", ".pyfa")) -def defPaths(customSavePath): +def defPaths(customSavePath=None): global debug global pyfaPath global savePath global saveDB global gameDB global saveInRoot + global logPath + global cipher + global clientHash pyfalog.debug("Configuring Pyfa") @@ -86,9 +125,19 @@ def defPaths(customSavePath): __createDirs(savePath) - if isFrozen(): - os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8') - os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem").encode('utf8') + secret_file = os.path.join(savePath, ".secret") + if not os.path.exists(secret_file): + with open(secret_file, "wb") as _file: + _file.write(Fernet.generate_key()) + + with open(secret_file, 'rb') as fp: + key = fp.read() + clientHash = hashlib.sha3_256(key).hexdigest() + cipher = Fernet(key) + + # if isFrozen(): + # os.environ["REQUESTS_CA_BUNDLE"] = os.path.join(pyfaPath, "cacert.pem") + # os.environ["SSL_CERT_FILE"] = os.path.join(pyfaPath, "cacert.pem") # The database where we store all the fits etc saveDB = os.path.join(savePath, "saveddata.db") @@ -100,6 +149,13 @@ def defPaths(customSavePath): if not gameDB: gameDB = os.path.join(pyfaPath, "eve.db") + if debug: + logFile = "pyfa_debug.log" + else: + logFile = "pyfa.log" + + logPath = os.path.join(savePath, logFile) + # DON'T MODIFY ANYTHING BELOW import eos.config @@ -109,6 +165,100 @@ def defPaths(customSavePath): eos.config.saveddata_connectionstring = "sqlite:///" + saveDB + "?check_same_thread=False" eos.config.gamedata_connectionstring = "sqlite:///" + gameDB + "?check_same_thread=False" + print(eos.config.saveddata_connectionstring) + print(eos.config.gamedata_connectionstring) + # initialize the settings from service.settings import EOSSettings eos.config.settings = EOSSettings.getInstance().EOSSettings # this is kind of confusing, but whatever + + +def defLogging(): + global debug + global logPath + global loggingLevel + global logging_setup + + try: + if debug: + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + StreamHandler( + sys.stdout, + bubble=False, + level=loggingLevel + ), + TimedRotatingFileHandler( + logPath, + level=0, + backup_count=3, + bubble=True, + date_format='%Y-%m-%d', + ), + ]) + else: + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + FingersCrossedHandler( + TimedRotatingFileHandler( + logPath, + level=0, + backup_count=3, + bubble=False, + date_format='%Y-%m-%d', + ), + action_level=ERROR, + buffer_size=1000, + # pull_information=True, + # reset=False, + ) + ]) + except: + print("Critical error attempting to setup logging. Falling back to console only.") + logging_setup = NestedSetup([ + # make sure we never bubble up to the stderr handler + # if we run out of setup handling + NullHandler(), + StreamHandler( + sys.stdout, + bubble=False + ) + ]) + + with logging_setup.threadbound(): + + # Output all stdout (print) messages as warnings + try: + sys.stdout = LoggerWriter(pyfalog.warning) + except: + pyfalog.critical("Cannot redirect. Continuing without writing stdout to log.") + + # Output all stderr (stacktrace) messages as critical + try: + sys.stderr = LoggerWriter(pyfalog.critical) + except: + pyfalog.critical("Cannot redirect. Continuing without writing stderr to log.") + + +class LoggerWriter(object): + def __init__(self, level): + # self.level is really like using log.debug(message) + # at least in my case + self.level = level + + def write(self, message): + # if statement reduces the amount of newlines that are + # printed to the logger + if message.strip() != '': + self.level(message.replace("\n", "")) + + def flush(self): + # create a flush method so things can be flushed when + # the system wants to. Not sure if simply 'printing' + # sys.stderr is the correct way to do it, but it seemed + # to work properly for me. + self.level(sys.stderr) diff --git a/dist_assets/linux/pyfa-linux.spec b/dist_assets/linux/pyfa-linux.spec new file mode 100644 index 000000000..bfc24a2f2 --- /dev/null +++ b/dist_assets/linux/pyfa-linux.spec @@ -0,0 +1,70 @@ +# -*- mode: python -*- + +import os +from itertools import chain +import subprocess + +label = subprocess.check_output([ + "git", "describe", "--tags"]).strip() + +with open('gitversion', 'w+') as f: + f.write(label.decode()) + +block_cipher = None + + +added_files = [ + ( 'imgs/gui/*.png', 'imgs/gui' ), + ( 'imgs/gui/*.gif', 'imgs/gui' ), + ( 'imgs/icons/*.png', 'imgs/icons' ), + ( 'imgs/renders/*.png', 'imgs/renders' ), + ( 'dist_assets/win/pyfa.ico', '.' ), + ( 'dist_assets/cacert.pem', '.' ), + ( 'eve.db', '.' ), + ( 'README.md', '.' ), + ( 'LICENSE', '.' ), + ( 'gitversion', '.' ), + ] + +import_these = [] + +# Walk directories that do dynamic importing +paths = ('eos/effects', 'eos/db/migrations', 'service/conversions') +for root, folders, files in chain.from_iterable(os.walk(path) for path in paths): + for file_ in files: + if file_.endswith(".py") and not file_.startswith("_"): + mod_name = "{}.{}".format( + root.replace("/", "."), + file_.split(".py")[0], + ) + import_these.append(mod_name) + + +a = Analysis(['pyfa.py'], + pathex=[], + binaries=[], + datas=added_files, + hiddenimports=import_these, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name='pyfa', + debug=False, + strip=False, + upx=True, + console=True ) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + strip=False, + upx=True, + name='pyfa') diff --git a/dist_assets/mac/pyfa.spec b/dist_assets/mac/pyfa.spec new file mode 100644 index 000000000..da72e4681 --- /dev/null +++ b/dist_assets/mac/pyfa.spec @@ -0,0 +1,76 @@ +# -*- mode: python -*- + +import os +from itertools import chain +import subprocess +import requests.certs + +label = subprocess.check_output([ + "git", "describe", "--tags"]).strip() + +with open('.version', 'w+') as f: + f.write(label.decode()) + +block_cipher = None + +added_files = [ + ('../../imgs/gui/*.png', 'imgs/gui'), + ('../../imgs/gui/*.gif', 'imgs/gui'), + ('../../imgs/icons/*.png', 'imgs/icons'), + ('../../imgs/renders/*.png', 'imgs/renders'), + ('../../dist_assets/win/pyfa.ico', '.'), + ('../../service/jargon/*.yaml', 'service/jargon'), + (requests.certs.where(), '.'), # is this needed anymore? + ('../../eve.db', '.'), + ('../../README.md', '.'), + ('../../LICENSE', '.'), + ('../../.version', '.'), + ] + + +import_these = [] + +icon = os.path.join(os.getcwd(), "dist_assets", "mac", "pyfa.icns") + +# Walk directories that do dynamic importing +paths = ('eos/effects', 'eos/db/migrations', 'service/conversions') +for root, folders, files in chain.from_iterable(os.walk(path) for path in paths): + for file_ in files: + if file_.endswith(".py") and not file_.startswith("_"): + mod_name = "{}.{}".format( + root.replace("/", "."), + file_.split(".py")[0], + ) + import_these.append(mod_name) + +a = Analysis([r'../../pyfa.py'], + pathex=[], + binaries=[], + datas=added_files, + hiddenimports=import_these, + hookspath=['dist_assets/pyinstaller_hooks'], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name='pyfa', + debug=False, + strip=False, + upx=True, + runtime_tmpdir=None, + console=False , + icon=icon, + ) + +app = BUNDLE(exe, + name='pyfa.app', + icon=icon, + bundle_identifier=None) \ No newline at end of file diff --git a/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py b/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py new file mode 100644 index 000000000..d73ba1ec3 --- /dev/null +++ b/dist_assets/pyinstaller_hooks/hook-matplotlib.backends.py @@ -0,0 +1,78 @@ +# This apes hook-matplotlib.backends.py, but REMOVES backends, all but +# the ones in the list below. +# Courtesy of https://github.com/bpteague/cytoflow/blob/70f9291/packaging/hook-matplotlib.backends.py + +KEEP = ["WXAgg", "WX", "agg"] + +from PyInstaller.compat import is_darwin +from PyInstaller.utils.hooks import ( + eval_statement, exec_statement, logger) + + +def get_matplotlib_backend_module_names(): + """ + List the names of all matplotlib backend modules importable under the + current Python installation. + Returns + ---------- + list + List of the fully-qualified names of all such modules. + """ + # Statement safely importing a single backend module. + import_statement = """ +import os, sys +# Preserve stdout. +sys_stdout = sys.stdout +try: + # Redirect output printed by this importation to "/dev/null", preventing + # such output from being erroneously interpreted as an error. + with open(os.devnull, 'w') as dev_null: + sys.stdout = dev_null + __import__('%s') +# If this is an ImportError, print this exception's message without a traceback. +# ImportError messages are human-readable and require no additional context. +except ImportError as exc: + sys.stdout = sys_stdout + print(exc) +# Else, print this exception preceded by a traceback. traceback.print_exc() +# prints to stderr rather than stdout and must not be called here! +except Exception: + sys.stdout = sys_stdout + import traceback + print(traceback.format_exc()) +""" + + # List of the human-readable names of all available backends. + backend_names = eval_statement( + 'import matplotlib; print(matplotlib.rcsetup.all_backends)') + + # List of the fully-qualified names of all importable backend modules. + module_names = [] + + # If the current system is not OS X and the "CocoaAgg" backend is available, + # remove this backend from consideration. Attempting to import this backend + # on non-OS X systems halts the current subprocess without printing output + # or raising exceptions, preventing its reliable detection. + if not is_darwin and 'CocoaAgg' in backend_names: + backend_names.remove('CocoaAgg') + + # For safety, attempt to import each backend in a unique subprocess. + for backend_name in backend_names: + if backend_name in KEEP: + continue + + module_name = 'matplotlib.backends.backend_%s' % backend_name.lower() + stdout = exec_statement(import_statement % module_name) + + # If no output was printed, this backend is importable. + if not stdout: + module_names.append(module_name) + logger.info(' Matplotlib backend "%s": removed' % backend_name) + + return module_names + +# Freeze all importable backends, as PyInstaller is unable to determine exactly +# which backends are required by the current program. +e=get_matplotlib_backend_module_names() +print(e) +excludedimports = e \ No newline at end of file diff --git a/dist_assets/win/dist.py b/dist_assets/win/dist.py new file mode 100644 index 000000000..a34ed3140 --- /dev/null +++ b/dist_assets/win/dist.py @@ -0,0 +1,46 @@ +# helper script to zip up pyinstaller distribution and create installer file + +import os.path +from subprocess import call +import zipfile + + +def zipdir(path, zip): + for root, dirs, files in os.walk(path): + for file in files: + zip.write(os.path.join(root, file)) + +config = {} + +exec(compile(open("config.py").read(), "config.py", 'exec'), config) + +iscc = "C:\Program Files (x86)\Inno Setup 5\ISCC.exe" # inno script location via wine + +print("Creating archive") + +source = os.path.join(os.getcwd(), "dist", "pyfa") + +fileName = "pyfa-{}-{}-{}-win".format( + config['version'], + config['expansionName'].lower(), + config['expansionVersion'] +) + +archive = zipfile.ZipFile(os.path.join(os.getcwd(), "dist", fileName + ".zip"), 'w', compression=zipfile.ZIP_DEFLATED) +zipdir(source, archive) +archive.close() + +print("Compiling EXE") + +expansion = "%s %s" % (config['expansionName'], config['expansionVersion']), + +call([ + iscc, + os.path.join(os.getcwd(), "dist_assets", "win", "pyfa-setup.iss"), + "/dMyAppVersion=%s" % (config['version']), + "/dMyAppExpansion=%s" % (expansion), + "/dMyAppDir=%s" % source, + "/dMyOutputDir=%s" % os.path.join(os.getcwd(), "dist"), + "/dMyOutputFile=%s" % fileName]) # stdout=devnull, stderr=devnull + +print("Done") diff --git a/scripts/pyfa-setup.iss b/dist_assets/win/pyfa-setup.iss similarity index 91% rename from scripts/pyfa-setup.iss rename to dist_assets/win/pyfa-setup.iss index 9c25f11f2..3d3e3df2d 100644 --- a/scripts/pyfa-setup.iss +++ b/dist_assets/win/pyfa-setup.iss @@ -19,7 +19,8 @@ #define MyAppExeName "pyfa.exe" ; What version starts with the new structure (1.x.0). This is used to determine if we run directory structure cleanup -#define VersionFlag 16 +#define MajorVersionFlag 2 +#define MinorVersionFlag 0 #ifndef MyOutputFile #define MyOutputFile LowerCase(StringChange(MyAppName+'-'+MyAppVersion+'-'+MyAppExpansion+'-win-wx3', " ", "-")) @@ -138,15 +139,19 @@ var V: Integer; iResultCode: Integer; sUnInstallString: string; - iOldVersion: Cardinal; + iOldVersionMajor: Cardinal; + iOldVersionMinor: Cardinal; begin Result := True; // in case when no previous version is found if RegValueExists(HKEY_LOCAL_MACHINE,'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', 'UninstallString') then //Your App GUID/ID begin RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', - 'MinorVersion', iOldVersion); - if iOldVersion < {#VersionFlag} then // If old version with old structure is installed. + 'MajorVersion', iOldVersionMajor); + RegQueryDWordValue(HKEY_LOCAL_MACHINE, + 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{3DA39096-C08D-49CD-90E0-1D177F32C8AA}_is1', + 'MinorVersion', iOldVersionMinor); + if (iOldVersionMajor < {#MajorVersionFlag}) or ((iOldVersionMajor = {#MajorVersionFlag}) and (iOldVersionMinor < {#MinorVersionFlag})) then // If old version with old structure is installed. begin V := MsgBox(ExpandConstant('An old version of pyfa was detected. Due to recent changes in the application structure, you must uninstall the previous version first. This will not affect your user data (saved fittings, characters, etc.). Do you want to uninstall now?'), mbInformation, MB_YESNO); //Custom Message if App installed if V = IDYES then diff --git a/dist_assets/win/pyfa.spec b/dist_assets/win/pyfa.spec index 0f61a860d..445f94824 100644 --- a/dist_assets/win/pyfa.spec +++ b/dist_assets/win/pyfa.spec @@ -1,35 +1,37 @@ # -*- mode: python -*- -# Note: This script is provided AS-IS for those that may be interested. -# pyfa does not currently support pyInstaller (or any other build process) 100% at the moment - -# Command line to build: -# (Run from directory where pyfa.py and pyfa.spec lives.) -# c:\Python27\scripts\pyinstaller.exe --clean --noconfirm --windowed --upx-dir=.\scripts\upx.exe pyfa.spec - -# Don't forget to change the path to where your pyfa.py and pyfa.spec lives -# pathex=['C:\\Users\\Ebag333\\Documents\\GitHub\\Ebag333\\Pyfa'], - import os +from itertools import chain +import subprocess +import requests.certs + +label = subprocess.check_output([ + "git", "describe", "--tags"]).strip() + +with open('.version', 'w+') as f: + f.write(label.decode()) block_cipher = None added_files = [ - ( 'imgs/gui/*.png', 'imgs/gui' ), - ( 'imgs/gui/*.gif', 'imgs/gui' ), - ( 'imgs/icons/*.png', 'imgs/icons' ), - ( 'imgs/renders/*.png', 'imgs/renders' ), - ( 'dist_assets/win/pyfa.ico', '.' ), - ( 'dist_assets/cacert.pem', '.' ), - ( 'eve.db', '.' ), - ( 'README.md', '.' ), - ( 'LICENSE', '.' ), + ('../../imgs/gui/*.png', 'imgs/gui'), + ('../../imgs/gui/*.gif', 'imgs/gui'), + ('../../imgs/icons/*.png', 'imgs/icons'), + ('../../imgs/renders/*.png', 'imgs/renders'), + ('../../service/jargon/*.yaml', 'service/jargon'), + ('../../dist_assets/win/pyfa.ico', '.'), + (requests.certs.where(), '.'), # is this needed anymore? + ('../../eve.db', '.'), + ('../../README.md', '.'), + ('../../LICENSE', '.'), + ('../../.version', '.'), ] import_these = [] -# Walk eos.effects and add all effects so we can import them properly -for root, folders, files in os.walk("eos/effects"): +# Walk directories that do dynamic importing +paths = ('eos/effects', 'eos/db/migrations', 'service/conversions') +for root, folders, files in chain.from_iterable(os.walk(path) for path in paths): for file_ in files: if file_.endswith(".py") and not file_.startswith("_"): mod_name = "{}.{}".format( @@ -38,25 +40,24 @@ for root, folders, files in os.walk("eos/effects"): ) import_these.append(mod_name) -a = Analysis( - ['pyfa.py'], - pathex=['C:\\projects\\pyfa\\'], +a = Analysis(['../../pyfa.py'], + pathex=[ + # Need this, see https://github.com/pyinstaller/pyinstaller/issues/1566 + # To get this, download and install windows 10 SDK + # If not building on Windows 10, this might be optional + r'C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86'], binaries=[], datas=added_files, hiddenimports=import_these, - hookspath=[], + hookspath=['dist_assets/pyinstaller_hooks'], runtime_hooks=[], - excludes=[], + excludes=['Tkinter'], win_no_prefer_redirects=False, win_private_assemblies=False, - cipher=block_cipher, - ) + cipher=block_cipher) -pyz = PYZ( - a.pure, - a.zipped_data, - cipher=block_cipher, - ) +pyz = PYZ(a.pure, a.zipped_data, + cipher=block_cipher) exe = EXE(pyz, a.scripts, @@ -67,7 +68,6 @@ exe = EXE(pyz, upx=True, name='pyfa', icon='dist_assets/win/pyfa.ico', - onefile=False, ) coll = COLLECT( @@ -77,7 +77,7 @@ coll = COLLECT( a.datas, strip=False, upx=True, - onefile=False, name='pyfa', icon='dist_assets/win/pyfa.ico', ) + diff --git a/dist_assets/win/version_resource.py b/dist_assets/win/version_resource.py new file mode 100644 index 000000000..a5018edd7 --- /dev/null +++ b/dist_assets/win/version_resource.py @@ -0,0 +1,45 @@ +# UTF-8 +# +# For more details about fixed file info 'ffi' see: +# http://msdn.microsoft.com/en-us/library/ms646997.aspx +VSVersionInfo( + ffi=FixedFileInfo( + # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) + # Set not needed items to zero 0. + filevers=(1, 15, 1, 0), + prodvers=(1, 15, 1, 0), + # Contains a bitmask that specifies the valid bits 'flags'r + mask=0x3f, + # Contains a bitmask that specifies the Boolean attributes of the file. + flags=0x0, + # The operating system for which this file was designed. + # 0x4 - NT and there is no need to change it. + OS=0x40004, + # The general type of file. + # 0x1 - the file is an application. + fileType=0x1, + # The function of the file. + # 0x0 - the function is not defined for this fileType + subtype=0x0, + # Creation date and time stamp. + date=(0, 0) + ), + kids=[ + StringFileInfo( + [ + StringTable( + u'040904E4', + [StringStruct(u'LegalCopyright', u''), + StringStruct(u'InternalName', u'pyfa.exe'), + StringStruct(u'FileVersion', u'1.15.1.0'), + StringStruct(u'CompanyName', u''), + StringStruct(u'OriginalFilename', u'pyfa.exe'), + StringStruct(u'ProductVersion', u'1.15.1.0'), + StringStruct(u'FileDescription', u'Python fitting assistant'), + StringStruct(u'LegalTrademarks', u''), + StringStruct(u'Comments', u''), + StringStruct(u'ProductName', u'pyfa')]) + ]), + VarFileInfo([VarStruct(u'Translation', [1033, 1252])]) + ] +) diff --git a/eos/capSim.py b/eos/capSim.py index 3a2f965c3..8b16c3e0f 100644 --- a/eos/capSim.py +++ b/eos/capSim.py @@ -1,6 +1,7 @@ import heapq import time from math import sqrt, exp +from functools import reduce DAY = 24 * 60 * 60 * 1000 @@ -88,7 +89,7 @@ class CapSimulator(object): mods[(duration, capNeed, clipSize, disableStagger, reloadTime)] = 1 # Loop over grouped modules, configure staggering and push to the simulation state - for (duration, capNeed, clipSize, disableStagger, reloadTime), amount in mods.iteritems(): + for (duration, capNeed, clipSize, disableStagger, reloadTime), amount in mods.items(): if self.stagger and not disableStagger: if clipSize == 0: duration = int(duration / amount) @@ -193,7 +194,7 @@ class CapSimulator(object): # calculate EVE's stability value try: - avgDrain = reduce(float.__add__, map(lambda x: x[2] / x[1], self.state), 0.0) + avgDrain = reduce(float.__add__, [x[2] / x[1] for x in self.state], 0.0) self.cap_stable_eve = 0.25 * (1.0 + sqrt(-(2.0 * avgDrain * tau - capCapacity) / capCapacity)) ** 2 except ValueError: self.cap_stable_eve = 0.0 diff --git a/eos/config.py b/eos/config.py index ec64e2bd3..6bc31eef8 100644 --- a/eos/config.py +++ b/eos/config.py @@ -11,14 +11,14 @@ debug = False gamedataCache = True saveddataCache = True gamedata_version = "" -gamedata_connectionstring = 'sqlite:///' + unicode(realpath(join(dirname(abspath(__file__)), "..", "eve.db")), sys.getfilesystemencoding()) +gamedata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "eve.db")) pyfalog.debug("Gamedata connection string: {0}", gamedata_connectionstring) if istravis is True or hasattr(sys, '_called_from_test'): # Running in Travis. Run saveddata database in memory. saveddata_connectionstring = 'sqlite:///:memory:' else: - saveddata_connectionstring = 'sqlite:///' + unicode(realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata.db")), sys.getfilesystemencoding()) + saveddata_connectionstring = 'sqlite:///' + realpath(join(dirname(abspath(__file__)), "..", "saveddata", "saveddata-py3-db.db")) pyfalog.debug("Saveddata connection string: {0}", saveddata_connectionstring) @@ -28,4 +28,4 @@ settings = { } # Autodetect path, only change if the autodetection bugs out. -path = dirname(unicode(__file__, sys.getfilesystemencoding())) +path = dirname(__file__) diff --git a/eos/db/__init__.py b/eos/db/__init__.py index a29bb86c7..dd027841b 100644 --- a/eos/db/__init__.py +++ b/eos/db/__init__.py @@ -22,7 +22,7 @@ import threading from sqlalchemy import MetaData, create_engine from sqlalchemy.orm import sessionmaker -import migration +from . import migration from eos import config from logbook import Logger @@ -76,7 +76,7 @@ sd_lock = threading.RLock() # noinspection PyPep8 from eos.db.gamedata import alphaClones, attribute, category, effect, group, icon, item, marketGroup, metaData, metaGroup, queries, traits, unit # noinspection PyPep8 -from eos.db.saveddata import booster, cargo, character, crest, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \ +from eos.db.saveddata import booster, cargo, character, damagePattern, databaseRepair, drone, fighter, fit, implant, implantSet, loadDefaultDatabaseValues, \ miscData, module, override, price, queries, skill, targetResists, user # Import queries diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py index 7a50d726c..737169897 100644 --- a/eos/db/gamedata/queries.py +++ b/eos/db/gamedata/queries.py @@ -81,7 +81,7 @@ def getItem(lookfor, eager=None): item = gamedata_session.query(Item).get(lookfor) else: item = gamedata_session.query(Item).options(*processEager(eager)).filter(Item.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): if lookfor in itemNameMap: id = itemNameMap[lookfor] if eager is None: @@ -154,7 +154,7 @@ def getGroup(lookfor, eager=None): group = gamedata_session.query(Group).get(lookfor) else: group = gamedata_session.query(Group).options(*processEager(eager)).filter(Group.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): if lookfor in groupNameMap: id = groupNameMap[lookfor] if eager is None: @@ -181,7 +181,7 @@ def getCategory(lookfor, eager=None): else: category = gamedata_session.query(Category).options(*processEager(eager)).filter( Category.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): if lookfor in categoryNameMap: id = categoryNameMap[lookfor] if eager is None: @@ -210,7 +210,7 @@ def getMetaGroup(lookfor, eager=None): else: metaGroup = gamedata_session.query(MetaGroup).options(*processEager(eager)).filter( MetaGroup.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): if lookfor in metaGroupNameMap: id = metaGroupNameMap[lookfor] if eager is None: @@ -245,7 +245,7 @@ def getMarketGroup(lookfor, eager=None): def getItemsByCategory(filter, where=None, eager=None): if isinstance(filter, int): filter = Category.ID == filter - elif isinstance(filter, basestring): + elif isinstance(filter, str): filter = Category.name == filter else: raise TypeError("Need integer or string as argument") @@ -257,7 +257,7 @@ def getItemsByCategory(filter, where=None, eager=None): @cachedQuery(3, "where", "nameLike", "join") def searchItems(nameLike, where=None, join=None, eager=None): - if not isinstance(nameLike, basestring): + if not isinstance(nameLike, str): raise TypeError("Need string as argument") if join is None: @@ -268,7 +268,7 @@ def searchItems(nameLike, where=None, join=None, eager=None): items = gamedata_session.query(Item).options(*processEager(eager)).join(*join) for token in nameLike.split(' '): - token_safe = u"%{0}%".format(sqlizeString(token)) + token_safe = "%{0}%".format(sqlizeString(token)) if where is not None: items = items.filter(and_(Item.name.like(token_safe, escape="\\"), where)) else: @@ -279,12 +279,12 @@ def searchItems(nameLike, where=None, join=None, eager=None): @cachedQuery(3, "where", "nameLike", "join") def searchSkills(nameLike, where=None, eager=None): - if not isinstance(nameLike, basestring): + if not isinstance(nameLike, str): raise TypeError("Need string as argument") items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category) for token in nameLike.split(' '): - token_safe = u"%{0}%".format(sqlizeString(token)) + token_safe = "%{0}%".format(sqlizeString(token)) if where is not None: items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where)) else: @@ -322,7 +322,7 @@ def getVariations(itemids, groupIDs=None, where=None, eager=None): @cachedQuery(1, "attr") def getAttributeInfo(attr, eager=None): - if isinstance(attr, basestring): + if isinstance(attr, str): filter = AttributeInfo.name == attr elif isinstance(attr, int): filter = AttributeInfo.ID == attr @@ -337,7 +337,7 @@ def getAttributeInfo(attr, eager=None): @cachedQuery(1, "field") def getMetaData(field): - if isinstance(field, basestring): + if isinstance(field, str): data = gamedata_session.query(MetaData).get(field) else: raise TypeError("Need string as argument") @@ -367,7 +367,7 @@ def getRequiredFor(itemID, attrMapping): skillToLevelClauses = [] - for attrSkill, attrLevel in attrMapping.iteritems(): + for attrSkill, attrLevel in attrMapping.items(): skillToLevelClauses.append(and_(Attribute1.attributeID == attrSkill, Attribute2.attributeID == attrLevel)) queryOr = or_(*skillToLevelClauses) diff --git a/eos/db/migration.py b/eos/db/migration.py index 3d8921eb2..4832daaa9 100644 --- a/eos/db/migration.py +++ b/eos/db/migration.py @@ -3,7 +3,7 @@ import shutil import time import config -import migrations +from . import migrations pyfalog = Logger(__name__) @@ -34,7 +34,7 @@ def update(saveddata_engine): shutil.copyfile(config.saveDB, toFile) - for version in xrange(dbVersion, appVersion): + for version in range(dbVersion, appVersion): func = migrations.updates[version + 1] if func: pyfalog.info("Applying database update: {0}", version + 1) diff --git a/eos/db/migrations/__init__.py b/eos/db/migrations/__init__.py index 94b33409a..e9797c352 100644 --- a/eos/db/migrations/__init__.py +++ b/eos/db/migrations/__init__.py @@ -15,7 +15,27 @@ updates = {} appVersion = 0 prefix = __name__ + "." -for importer, modname, ispkg in pkgutil.iter_modules(__path__, prefix): + +# load modules to work based with and without pyinstaller +# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py +# see: https://github.com/pyinstaller/pyinstaller/issues/1905 + +# load modules using iter_modules() +# (should find all filters in normal build, but not pyinstaller) +module_names = [m[1] for m in pkgutil.iter_modules(__path__, prefix)] + +# special handling for PyInstaller +importers = map(pkgutil.get_importer, __path__) +toc = set() +for i in importers: + if hasattr(i, 'toc'): + toc |= i.toc + +for elm in toc: + if elm.startswith(prefix): + module_names.append(elm) + +for modname in module_names: # loop through python files, extracting update number and function, and # adding it to a list modname_tail = modname.rsplit('.', 1)[-1] diff --git a/eos/db/migrations/upgrade1.py b/eos/db/migrations/upgrade1.py index 22292dc4f..9059b28de 100644 --- a/eos/db/migrations/upgrade1.py +++ b/eos/db/migrations/upgrade1.py @@ -91,7 +91,7 @@ def upgrade(saveddata_engine): saveddata_engine.execute("ALTER TABLE fits ADD COLUMN targetResistsID INTEGER;") # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade11.py b/eos/db/migrations/upgrade11.py index 2811cf5be..248822484 100644 --- a/eos/db/migrations/upgrade11.py +++ b/eos/db/migrations/upgrade11.py @@ -108,7 +108,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade12.py b/eos/db/migrations/upgrade12.py index 59d6d08d9..6d8e56d3a 100644 --- a/eos/db/migrations/upgrade12.py +++ b/eos/db/migrations/upgrade12.py @@ -332,7 +332,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade18.py b/eos/db/migrations/upgrade18.py index 4a13b7d57..8594c950e 100644 --- a/eos/db/migrations/upgrade18.py +++ b/eos/db/migrations/upgrade18.py @@ -60,7 +60,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade22.py b/eos/db/migrations/upgrade22.py index fb821c2d1..568351a25 100644 --- a/eos/db/migrations/upgrade22.py +++ b/eos/db/migrations/upgrade22.py @@ -29,7 +29,7 @@ def upgrade(saveddata_engine): "targetResists": 2 } - for table in tables.keys(): + for table in list(tables.keys()): # midnight brain, there's probably a much more simple way to do this, but fuck it if tables[table] > 0: diff --git a/eos/db/migrations/upgrade25.py b/eos/db/migrations/upgrade25.py index bf334c37d..703048f37 100644 --- a/eos/db/migrations/upgrade25.py +++ b/eos/db/migrations/upgrade25.py @@ -4204,7 +4204,7 @@ conversion2 = { def upgrade(saveddata_engine): # First we want to get a list of fittings that are completely fitted out with subsystems - oldItems = [str(x) for x in conversion2.iterkeys()] + oldItems = [str(x) for x in conversion2.keys()] # I can't figure out a way to get IN operator to work when supplying a list using a parameterized query. So I'm # doing it the shitty way by formatting the SQL string. Don't do this kids! @@ -4239,7 +4239,7 @@ def upgrade(saveddata_engine): # if something fails, fuck it, we tried. It'll default to the generic conversion below continue - for oldItem, newItem in conversion2.iteritems(): + for oldItem, newItem in conversion2.items(): saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (newItem, oldItem)) saveddata_engine.execute('UPDATE "cargo" SET "itemID" = ? WHERE "itemID" = ?', diff --git a/eos/db/migrations/upgrade4.py b/eos/db/migrations/upgrade4.py index d1e46d10a..b94d965f2 100644 --- a/eos/db/migrations/upgrade4.py +++ b/eos/db/migrations/upgrade4.py @@ -133,7 +133,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade7.py b/eos/db/migrations/upgrade7.py index fbb74f910..21797e5c5 100644 --- a/eos/db/migrations/upgrade7.py +++ b/eos/db/migrations/upgrade7.py @@ -17,7 +17,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert ships - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "fits" SET "shipID" = ? WHERE "shipID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/migrations/upgrade8.py b/eos/db/migrations/upgrade8.py index 0051034ec..7c1327416 100644 --- a/eos/db/migrations/upgrade8.py +++ b/eos/db/migrations/upgrade8.py @@ -77,7 +77,7 @@ CONVERSIONS = { def upgrade(saveddata_engine): # Convert modules - for replacement_item, list in CONVERSIONS.iteritems(): + for replacement_item, list in CONVERSIONS.items(): for retired_item in list: saveddata_engine.execute('UPDATE "modules" SET "itemID" = ? WHERE "itemID" = ?', (replacement_item, retired_item)) diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py index 7dbf2a45d..ba1ddad73 100644 --- a/eos/db/saveddata/__init__.py +++ b/eos/db/saveddata/__init__.py @@ -12,7 +12,6 @@ __all__ = [ "miscData", "targetResists", "override", - "crest", "implantSet", "loadDefaultDatabaseValues" ] diff --git a/eos/db/saveddata/character.py b/eos/db/saveddata/character.py index c87817541..739a34a98 100644 --- a/eos/db/saveddata/character.py +++ b/eos/db/saveddata/character.py @@ -17,24 +17,24 @@ # along with eos. If not, see . # =============================================================================== -from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime, Float +from sqlalchemy import Table, Column, Integer, ForeignKey, String, DateTime, Float, UniqueConstraint from sqlalchemy.orm import relation, mapper import datetime from eos.db import saveddata_meta from eos.db.saveddata.implant import charImplants_table -from eos.effectHandlerHelpers import HandledImplantBoosterList +from eos.effectHandlerHelpers import HandledImplantBoosterList, HandledSsoCharacterList from eos.saveddata.implant import Implant from eos.saveddata.user import User from eos.saveddata.character import Character, Skill +from eos.saveddata.ssocharacter import SsoCharacter + + + characters_table = Table("characters", saveddata_meta, Column("ID", Integer, primary_key=True), Column("name", String, nullable=False), - Column("apiID", Integer), - Column("apiKey", String), - Column("defaultChar", Integer), - Column("chars", String, nullable=True), Column("defaultLevel", Integer, nullable=True), Column("alphaCloneID", Integer, nullable=True), Column("ownerID", ForeignKey("users.ID"), nullable=True), @@ -42,6 +42,28 @@ characters_table = Table("characters", saveddata_meta, Column("created", DateTime, nullable=True, default=datetime.datetime.now), Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now)) +sso_table = Table("ssoCharacter", saveddata_meta, + Column("ID", Integer, primary_key=True), + Column("client", String, nullable=False), + Column("characterID", Integer, nullable=False), + Column("characterName", String, nullable=False), + Column("refreshToken", String, nullable=False), + Column("accessToken", String, nullable=False), + Column("accessTokenExpires", DateTime, nullable=False), + Column("created", DateTime, nullable=True, default=datetime.datetime.now), + Column("modified", DateTime, nullable=True, onupdate=datetime.datetime.now), + UniqueConstraint('client', 'characterID', name='uix_client_characterID'), + UniqueConstraint('client', 'characterName', name='uix_client_characterName') + ) + +sso_character_map_table = Table("ssoCharacterMap", saveddata_meta, + Column("characterID", ForeignKey("characters.ID"), primary_key=True), + Column("ssoCharacterID", ForeignKey("ssoCharacter.ID"), primary_key=True), + ) + + +mapper(SsoCharacter, sso_table) + mapper(Character, characters_table, properties={ "_Character__alphaCloneID": characters_table.c.alphaCloneID, @@ -63,5 +85,10 @@ mapper(Character, characters_table, primaryjoin=charImplants_table.c.charID == characters_table.c.ID, secondaryjoin=charImplants_table.c.implantID == Implant.ID, secondary=charImplants_table), + "_Character__ssoCharacters" : relation( + SsoCharacter, + collection_class=HandledSsoCharacterList, + backref='characters', + secondary=sso_character_map_table) } ) diff --git a/eos/db/saveddata/crest.py b/eos/db/saveddata/crest.py deleted file mode 100644 index 28f77a983..000000000 --- a/eos/db/saveddata/crest.py +++ /dev/null @@ -1,34 +0,0 @@ -# =============================================================================== -# Copyright (C) 2010 Diego Duclos -# -# This file is part of eos. -# -# eos is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# eos is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with eos. If not, see . -# =============================================================================== - -from sqlalchemy import Table, Column, Integer, String, DateTime -from sqlalchemy.orm import mapper -import datetime - -from eos.db import saveddata_meta -from eos.saveddata.crestchar import CrestChar - -crest_table = Table("crest", saveddata_meta, - Column("ID", Integer, primary_key=True), - Column("name", String, nullable=False, unique=True), - Column("refresh_token", String, nullable=False), - # These records aren't updated. Instead, they are dropped and created, hence we don't have a modified field - Column("created", DateTime, nullable=True, default=datetime.datetime.now)) - -mapper(CrestChar, crest_table) diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py index fd8ae93db..e582eef87 100644 --- a/eos/db/saveddata/queries.py +++ b/eos/db/saveddata/queries.py @@ -27,7 +27,7 @@ from eos.db.saveddata.fit import projectedFits_table from eos.db.util import processEager, processWhere from eos.saveddata.price import Price from eos.saveddata.user import User -from eos.saveddata.crestchar import CrestChar +from eos.saveddata.ssocharacter import SsoCharacter from eos.saveddata.damagePattern import DamagePattern from eos.saveddata.targetResists import TargetResists from eos.saveddata.character import Character @@ -109,9 +109,9 @@ if configVal is True: if type not in queryCache: return functionCache = queryCache[type] - for _, localCache in functionCache.iteritems(): + for _, localCache in functionCache.items(): toDelete = set() - for cacheKey, info in localCache.iteritems(): + for cacheKey, info in localCache.items(): IDs = info[1] if ID in IDs: toDelete.add(cacheKey) @@ -156,7 +156,7 @@ def getUser(lookfor, eager=None): eager = processEager(eager) with sd_lock: user = saveddata_session.query(User).options(*eager).filter(User.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): eager = processEager(eager) with sd_lock: user = saveddata_session.query(User).options(*eager).filter(User.username == lookfor).first() @@ -175,7 +175,7 @@ def getCharacter(lookfor, eager=None): eager = processEager(eager) with sd_lock: character = saveddata_session.query(Character).options(*eager).filter(Character.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): eager = processEager(eager) with sd_lock: character = saveddata_session.query(Character).options(*eager).filter( @@ -337,7 +337,7 @@ def clearPrices(): def getMiscData(field): - if isinstance(field, basestring): + if isinstance(field, str): with sd_lock: data = saveddata_session.query(MiscData).get(field) else: @@ -391,7 +391,7 @@ def getDamagePattern(lookfor, eager=None): with sd_lock: pattern = saveddata_session.query(DamagePattern).options(*eager).filter( DamagePattern.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): eager = processEager(eager) with sd_lock: pattern = saveddata_session.query(DamagePattern).options(*eager).filter( @@ -412,7 +412,7 @@ def getTargetResists(lookfor, eager=None): with sd_lock: pattern = saveddata_session.query(TargetResists).options(*eager).filter( TargetResists.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): eager = processEager(eager) with sd_lock: pattern = saveddata_session.query(TargetResists).options(*eager).filter( @@ -433,7 +433,7 @@ def getImplantSet(lookfor, eager=None): with sd_lock: pattern = saveddata_session.query(ImplantSet).options(*eager).filter( TargetResists.ID == lookfor).first() - elif isinstance(lookfor, basestring): + elif isinstance(lookfor, str): eager = processEager(eager) with sd_lock: pattern = saveddata_session.query(ImplantSet).options(*eager).filter(TargetResists.name == lookfor).first() @@ -443,10 +443,10 @@ def getImplantSet(lookfor, eager=None): def searchFits(nameLike, where=None, eager=None): - if not isinstance(nameLike, basestring): + if not isinstance(nameLike, str): raise TypeError("Need string as argument") # Prepare our string for request - nameLike = u"%{0}%".format(sqlizeString(nameLike)) + nameLike = "%{0}%".format(sqlizeString(nameLike)) # Add any extra components to the search to our where clause filter = processWhere(Fit.name.like(nameLike, escape="\\"), where) @@ -467,29 +467,28 @@ def getProjectedFits(fitID): raise TypeError("Need integer as argument") -def getCrestCharacters(eager=None): +def getSsoCharacters(clientHash, eager=None): eager = processEager(eager) with sd_lock: - characters = saveddata_session.query(CrestChar).options(*eager).all() + characters = saveddata_session.query(SsoCharacter).filter(SsoCharacter.client == clientHash).options(*eager).all() return characters -@cachedQuery(CrestChar, 1, "lookfor") -def getCrestCharacter(lookfor, eager=None): +@cachedQuery(SsoCharacter, 1, "lookfor", "clientHash") +def getSsoCharacter(lookfor, clientHash, eager=None): + filter = SsoCharacter.client == clientHash + if isinstance(lookfor, int): - if eager is None: - with sd_lock: - character = saveddata_session.query(CrestChar).get(lookfor) - else: - eager = processEager(eager) - with sd_lock: - character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.ID == lookfor).first() - elif isinstance(lookfor, basestring): - eager = processEager(eager) - with sd_lock: - character = saveddata_session.query(CrestChar).options(*eager).filter(CrestChar.name == lookfor).first() + filter = and_(filter, SsoCharacter.ID == lookfor) + elif isinstance(lookfor, str): + filter = and_(filter, SsoCharacter.characterName == lookfor) else: raise TypeError("Need integer or string as argument") + + eager = processEager(eager) + with sd_lock: + character = saveddata_session.query(SsoCharacter).options(*eager).filter(filter).first() + return character @@ -515,8 +514,8 @@ def removeInvalid(fits): invalids = [f for f in fits if f.isInvalid] if invalids: - map(fits.remove, invalids) - map(saveddata_session.delete, invalids) + list(map(fits.remove, invalids)) + list(map(saveddata_session.delete, invalids)) saveddata_session.commit() return fits @@ -544,7 +543,7 @@ def commit(): try: saveddata_session.commit() saveddata_session.flush() - except Exception: + except Exception as ex: saveddata_session.rollback() exc_info = sys.exc_info() - raise exc_info[0], exc_info[1], exc_info[2] + raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) diff --git a/eos/db/util.py b/eos/db/util.py index 03a09aceb..7fcf3504d 100644 --- a/eos/db/util.py +++ b/eos/db/util.py @@ -39,7 +39,7 @@ def processEager(eager): return tuple() else: l = [] - if isinstance(eager, basestring): + if isinstance(eager, str): eager = (eager,) for e in eager: @@ -50,7 +50,7 @@ def processEager(eager): def _replacements(eagerString): splitEager = eagerString.split(".") - for i in xrange(len(splitEager)): + for i in range(len(splitEager)): part = splitEager[i] replacement = replace.get(part) if replacement: diff --git a/eos/effectHandlerHelpers.py b/eos/effectHandlerHelpers.py index 0b0b5e795..f890ae0ed 100644 --- a/eos/effectHandlerHelpers.py +++ b/eos/effectHandlerHelpers.py @@ -115,7 +115,7 @@ class HandledList(list): class HandledModuleList(HandledList): def append(self, mod): emptyPosition = float("Inf") - for i in xrange(len(self)): + for i in range(len(self)): currMod = self[i] if currMod.isEmpty and not mod.isEmpty and currMod.slot == mod.slot: currPos = mod.position or i @@ -149,7 +149,7 @@ class HandledModuleList(HandledList): oldPos = mod.position mod.position = None - for i in xrange(oldPos, len(self)): + for i in range(oldPos, len(self)): self[i].position -= 1 def toDummy(self, index): @@ -205,6 +205,16 @@ class HandledImplantBoosterList(HandledList): HandledList.append(self, thing) +class HandledSsoCharacterList(list): + def append(self, character): + old = next((x for x in self if x.client == character.client), None) + if old is not None: + pyfalog.warning("Removing SSO Character with same hash: {}".format(repr(old))) + list.remove(self, old) + + list.append(self, character) + + class HandledProjectedModList(HandledList): def append(self, proj): if proj.isInvalid: diff --git a/eos/effects/capacitorcapacitymultiply.py b/eos/effects/capacitorcapacitymultiply.py index 20a75537d..91f3e6ed4 100644 --- a/eos/effects/capacitorcapacitymultiply.py +++ b/eos/effects/capacitorcapacitymultiply.py @@ -10,4 +10,6 @@ type = "passive" def handler(fit, module, context): - fit.ship.multiplyItemAttr("capacitorCapacity", module.getModifiedItemAttr("capacitorCapacityMultiplier")) + # We default this to None as there are times when the source attribute doesn't exist (for example, Cap Power Relay). + # It will return 0 as it doesn't exist, which would nullify whatever the target attribute is + fit.ship.multiplyItemAttr("capacitorCapacity", module.getModifiedItemAttr("capacitorCapacityMultiplier", None)) diff --git a/eos/effects/chargebonuswarfarecharge.py b/eos/effects/chargebonuswarfarecharge.py index 600379c44..b0998fbb4 100644 --- a/eos/effects/chargebonuswarfarecharge.py +++ b/eos/effects/chargebonuswarfarecharge.py @@ -6,6 +6,6 @@ type = "active" def handler(fit, module, context): - for x in xrange(1, 4): + for x in range(1, 4): value = module.getModifiedChargeAttr("warfareBuff{}Multiplier".format(x)) module.multiplyItemAttr("warfareBuff{}Value".format(x), value) diff --git a/eos/effects/modifyboostereffectchancewithboosterchancebonuspostpercent.py b/eos/effects/modifyboostereffectchancewithboosterchancebonuspostpercent.py index 6f123d02c..604e3e14b 100644 --- a/eos/effects/modifyboostereffectchancewithboosterchancebonuspostpercent.py +++ b/eos/effects/modifyboostereffectchancewithboosterchancebonuspostpercent.py @@ -8,7 +8,7 @@ type = "passive" def handler(fit, container, context): level = container.level if "skill" in context else 1 - for i in xrange(5): + for i in range(5): attr = "boosterEffectChance{0}".format(i + 1) fit.boosters.filteredItemBoost(lambda booster: attr in booster.itemModifiedAttributes, attr, container.getModifiedItemAttr("boosterChanceBonus") * level) diff --git a/eos/effects/modulebonuswarfarelinkarmor.py b/eos/effects/modulebonuswarfarelinkarmor.py index 2c792da83..be5843988 100644 --- a/eos/effects/modulebonuswarfarelinkarmor.py +++ b/eos/effects/modulebonuswarfarelinkarmor.py @@ -16,7 +16,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedChargeAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedChargeAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/modulebonuswarfarelinkinfo.py b/eos/effects/modulebonuswarfarelinkinfo.py index 491d824ad..dd1e1111b 100644 --- a/eos/effects/modulebonuswarfarelinkinfo.py +++ b/eos/effects/modulebonuswarfarelinkinfo.py @@ -7,7 +7,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedChargeAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedChargeAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/modulebonuswarfarelinkmining.py b/eos/effects/modulebonuswarfarelinkmining.py index fa28fcf4d..55876bc34 100644 --- a/eos/effects/modulebonuswarfarelinkmining.py +++ b/eos/effects/modulebonuswarfarelinkmining.py @@ -7,7 +7,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedChargeAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedChargeAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/modulebonuswarfarelinkshield.py b/eos/effects/modulebonuswarfarelinkshield.py index f8d3130e0..45cd0ac6f 100644 --- a/eos/effects/modulebonuswarfarelinkshield.py +++ b/eos/effects/modulebonuswarfarelinkshield.py @@ -7,7 +7,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedChargeAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedChargeAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/modulebonuswarfarelinkskirmish.py b/eos/effects/modulebonuswarfarelinkskirmish.py index 84b0cea3e..fc400c791 100644 --- a/eos/effects/modulebonuswarfarelinkskirmish.py +++ b/eos/effects/modulebonuswarfarelinkskirmish.py @@ -7,7 +7,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedChargeAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedChargeAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/moduletitaneffectgenerator.py b/eos/effects/moduletitaneffectgenerator.py index 901a0e89e..741de5ba4 100644 --- a/eos/effects/moduletitaneffectgenerator.py +++ b/eos/effects/moduletitaneffectgenerator.py @@ -6,7 +6,7 @@ type = "active", "gang" def handler(fit, module, context, **kwargs): - for x in xrange(1, 5): + for x in range(1, 5): if module.getModifiedItemAttr("warfareBuff{}ID".format(x)): value = module.getModifiedItemAttr("warfareBuff{}Value".format(x)) id = module.getModifiedItemAttr("warfareBuff{}ID".format(x)) diff --git a/eos/effects/poweroutputmultiply.py b/eos/effects/poweroutputmultiply.py index 3a0e5c6a1..f4d5eb600 100644 --- a/eos/effects/poweroutputmultiply.py +++ b/eos/effects/poweroutputmultiply.py @@ -9,4 +9,6 @@ type = "passive" def handler(fit, module, context): - fit.ship.multiplyItemAttr("powerOutput", module.getModifiedItemAttr("powerOutputMultiplier")) + # We default this to None as there are times when the source attribute doesn't exist (for example, Cap Power Relay). + # It will return 0 as it doesn't exist, which would nullify whatever the target attribute is + fit.ship.multiplyItemAttr("powerOutput", module.getModifiedItemAttr("powerOutputMultiplier", None)) diff --git a/eos/effects/shieldcapacitymultiply.py b/eos/effects/shieldcapacitymultiply.py index 71c91f19e..3c9aeea6d 100644 --- a/eos/effects/shieldcapacitymultiply.py +++ b/eos/effects/shieldcapacitymultiply.py @@ -9,4 +9,6 @@ type = "passive" def handler(fit, module, context): - fit.ship.multiplyItemAttr("shieldCapacity", module.getModifiedItemAttr("shieldCapacityMultiplier")) + # We default this to None as there are times when the source attribute doesn't exist (for example, Cap Power Relay). + # It will return 0 as it doesn't exist, which would nullify whatever the target attribute is + fit.ship.multiplyItemAttr("shieldCapacity", module.getModifiedItemAttr("shieldCapacityMultiplier", None)) diff --git a/eos/effects/techtwocommandburstbonus.py b/eos/effects/techtwocommandburstbonus.py new file mode 100644 index 000000000..e69de29bb diff --git a/eos/effects/warpdisruptsphere.py b/eos/effects/warpdisruptsphere.py index f8ce29992..22787465b 100644 --- a/eos/effects/warpdisruptsphere.py +++ b/eos/effects/warpdisruptsphere.py @@ -1,7 +1,3 @@ -# warpDisruptSphere -# -# Used by: -# Modules from group: Warp Disrupt Field Generator (7 of 7) # warpDisruptSphere # diff --git a/eos/effects/warpscrambleblockmwdwithnpceffect.py b/eos/effects/warpscrambleblockmwdwithnpceffect.py index 09bbeb6a7..d96a9f5d3 100644 --- a/eos/effects/warpscrambleblockmwdwithnpceffect.py +++ b/eos/effects/warpscrambleblockmwdwithnpceffect.py @@ -16,7 +16,10 @@ def handler(fit, module, context): # this is such a dirty hack for mod in fit.modules: - if not mod.isEmpty and mod.item.requiresSkill("High Speed Maneuvering") and mod.state > State.ONLINE: + if not mod.isEmpty and mod.state > State.ONLINE and ( + mod.item.requiresSkill("Micro Jump Drive Operation") + or mod.item.requiresSkill("High Speed Maneuvering") + ): mod.state = State.ONLINE if not mod.isEmpty and mod.item.requiresSkill("Micro Jump Drive Operation") and mod.state > State.ONLINE: mod.state = State.ONLINE diff --git a/eos/events.py b/eos/events.py index de8bdfadb..dd4b07b79 100644 --- a/eos/events.py +++ b/eos/events.py @@ -58,7 +58,7 @@ def rel_listener(target, value, initiator): if not target or (isinstance(value, Module) and value.isEmpty): return - print "{} has had a relationship change :D".format(target) + print("{} has had a relationship change :D".format(target)) target.modified = datetime.datetime.now() diff --git a/eos/gamedata.py b/eos/gamedata.py index a960e47e2..af7d413a7 100644 --- a/eos/gamedata.py +++ b/eos/gamedata.py @@ -22,13 +22,10 @@ import re from sqlalchemy.orm import reconstructor import eos.db -from eqBase import EqBase +from .eqBase import EqBase from eos.saveddata.price import Price as types_Price +from collections import OrderedDict -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict from logbook import Logger @@ -160,8 +157,6 @@ class Effect(EqBase): if it doesn't, set dummy values and add a dummy handler """ - pyfalog.debug("Generate effect handler for {}".format(self.name)) - try: self.__effectModule = effectModule = __import__('eos.effects.' + self.handlerName, fromlist=True) self.__handler = getattr(effectModule, "handler", effectDummy) @@ -258,7 +253,7 @@ class Item(EqBase): return default def isType(self, type): - for effect in self.effects.itervalues(): + for effect in self.effects.values(): if effect.isType(type): return True @@ -299,7 +294,7 @@ class Item(EqBase): self.__requiredSkills = requiredSkills # Map containing attribute IDs we may need for required skills # { requiredSkillX : requiredSkillXLevel } - combinedAttrIDs = set(self.srqIDMap.iterkeys()).union(set(self.srqIDMap.itervalues())) + combinedAttrIDs = set(self.srqIDMap.keys()).union(set(self.srqIDMap.values())) # Map containing result of the request # { attributeID : attributeValue } skillAttrs = {} @@ -309,7 +304,7 @@ class Item(EqBase): attrVal = attrInfo[2] skillAttrs[attrID] = attrVal # Go through all attributeID pairs - for srqIDAtrr, srqLvlAttr in self.srqIDMap.iteritems(): + for srqIDAtrr, srqLvlAttr in self.srqIDMap.items(): # Check if we have both in returned result if srqIDAtrr in skillAttrs and srqLvlAttr in skillAttrs: skillID = int(skillAttrs[srqIDAtrr]) @@ -383,7 +378,7 @@ class Item(EqBase): race = None # Check primary and secondary required skills' races if race is None: - skillRaces = tuple(filter(lambda rid: rid, (s.raceID for s in tuple(self.requiredSkills.keys())))) + skillRaces = tuple([rid for rid in (s.raceID for s in tuple(self.requiredSkills.keys())) if rid]) if sum(skillRaces) in map: race = map[sum(skillRaces)] if race == "angelserp": @@ -405,7 +400,7 @@ class Item(EqBase): if self.__assistive is None: assistive = False # Go through all effects and find first assistive - for effect in self.effects.itervalues(): + for effect in self.effects.values(): if effect.isAssistance is True: # If we find one, stop and mark item as assistive assistive = True @@ -420,7 +415,7 @@ class Item(EqBase): if self.__offensive is None: offensive = False # Go through all effects and find first offensive - for effect in self.effects.itervalues(): + for effect in self.effects.values(): if effect.isOffensive is True: # If we find one, stop and mark item as offensive offensive = True @@ -429,8 +424,8 @@ class Item(EqBase): return self.__offensive def requiresSkill(self, skill, level=None): - for s, l in self.requiredSkills.iteritems(): - if isinstance(skill, basestring): + for s, l in self.requiredSkills.items(): + if isinstance(skill, str): if s.name == skill and (level is None or l == level): return True @@ -468,7 +463,7 @@ class Item(EqBase): return self.__price def __repr__(self): - return u"Item(ID={}, name={}) at {}".format( + return "Item(ID={}, name={}) at {}".format( self.ID, self.name, hex(id(self)) ) @@ -522,9 +517,9 @@ class Icon(EqBase): class MarketGroup(EqBase): def __repr__(self): - return u"MarketGroup(ID={}, name={}, parent={}) at {}".format( + return "MarketGroup(ID={}, name={}, parent={}) at {}".format( self.ID, self.name, getattr(self.parent, "name", None), self.name, hex(id(self)) - ).encode('utf8') + ) class MetaGroup(EqBase): diff --git a/eos/graph/__init__.py b/eos/graph/__init__.py index 44802171f..fe8e8d25d 100644 --- a/eos/graph/__init__.py +++ b/eos/graph/__init__.py @@ -25,7 +25,7 @@ class Graph(object): self.fit = fit self.data = {} if data is not None: - for name, d in data.iteritems(): + for name, d in data.items(): self.setData(Data(name, d)) self.function = function @@ -39,7 +39,7 @@ class Graph(object): def getIterator(self): pointNames = [] pointIterators = [] - for data in self.data.itervalues(): + for data in self.data.values(): pointNames.append(data.name) pointIterators.append(data) @@ -48,7 +48,7 @@ class Graph(object): def _iterator(self, pointNames, pointIterators): for pointValues in itertools.product(*pointIterators): point = {} - for i in xrange(len(pointValues)): + for i in range(len(pointValues)): point[pointNames[i]] = pointValues[i] yield point, self.function(point) @@ -61,12 +61,12 @@ class Data(object): self.data = self.parseString(dataString) def parseString(self, dataString): - if not isinstance(dataString, basestring): + if not isinstance(dataString, str): return Constant(dataString), dataList = [] for data in dataString.split(";"): - if isinstance(data, basestring) and "-" in data: + if isinstance(data, str) and "-" in data: # Dealing with a range dataList.append(Range(data, self.step)) else: @@ -85,7 +85,7 @@ class Data(object): class Constant(object): def __init__(self, const): - if isinstance(const, basestring): + if isinstance(const, str): self.value = None if const == "" else float(const) else: self.value = const diff --git a/eos/graph/fitDps.py b/eos/graph/fitDps.py index 12cfda8d1..6aead3583 100644 --- a/eos/graph/fitDps.py +++ b/eos/graph/fitDps.py @@ -63,10 +63,10 @@ class FitDpsGraph(Graph): ew['signatureRadius'].sort(key=abssort) ew['velocity'].sort(key=abssort) - for attr, values in ew.iteritems(): + for attr, values in ew.items(): val = data[attr] try: - for i in xrange(len(values)): + for i in range(len(values)): bonus = values[i] val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289) data[attr] = val @@ -81,7 +81,7 @@ class FitDpsGraph(Graph): total += dps * self.calculateTurretMultiplier(mod, data) elif mod.hardpoint == Hardpoint.MISSILE: - if mod.state >= State.ACTIVE and mod.maxRange >= distance: + if mod.state >= State.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance: total += dps * self.calculateMissileMultiplier(mod, data) if distance <= fit.extraAttributes["droneControlRange"]: diff --git a/eos/modifiedAttributeDict.py b/eos/modifiedAttributeDict.py index 4106b306b..b4c73c958 100644 --- a/eos/modifiedAttributeDict.py +++ b/eos/modifiedAttributeDict.py @@ -28,23 +28,17 @@ cappingAttrKeyCache = {} class ItemAttrShortcut(object): - def getModifiedItemAttr(self, key, default=None): + def getModifiedItemAttr(self, key, default=0): return_value = self.itemModifiedAttributes.get(key) - if return_value is None and default is not None: - return_value = default - - return return_value + return return_value or default class ChargeAttrShortcut(object): - def getModifiedChargeAttr(self, key, default=None): + def getModifiedChargeAttr(self, key, default=0): return_value = self.chargeModifiedAttributes.get(key) - if return_value is None and default is not None: - return_value = default - - return return_value + return return_value or default class ModifiedAttributeDict(collections.MutableMapping): @@ -165,9 +159,9 @@ class ModifiedAttributeDict(collections.MutableMapping): def __len__(self): keys = set() - keys.update(self.original.iterkeys()) - keys.update(self.__modified.iterkeys()) - keys.update(self.__intermediary.iterkeys()) + keys.update(iter(self.original.keys())) + keys.update(iter(self.__modified.keys())) + keys.update(iter(self.__intermediary.keys())) return len(keys) def __calculateValue(self, key): @@ -231,11 +225,11 @@ class ModifiedAttributeDict(collections.MutableMapping): val *= multiplier # Each group is penalized independently # Things in different groups will not be stack penalized between each other - for penalizedMultipliers in penalizedMultiplierGroups.itervalues(): + for penalizedMultipliers in penalizedMultiplierGroups.values(): # A quick explanation of how this works: # 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them - l1 = filter(lambda _val: _val > 1, penalizedMultipliers) - l2 = filter(lambda _val: _val < 1, penalizedMultipliers) + l1 = [_val for _val in penalizedMultipliers if _val > 1] + l2 = [_val for _val in penalizedMultipliers if _val < 1] # 2: The most significant bonuses take the smallest penalty, # This means we'll have to sort abssort = lambda _val: -abs(_val - 1) @@ -245,7 +239,7 @@ class ModifiedAttributeDict(collections.MutableMapping): # Any module after the first takes penalties according to: # 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289) for l in (l1, l2): - for i in xrange(len(l)): + for i in range(len(l)): bonus = l[i] val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289) val += postIncrease @@ -388,7 +382,7 @@ class ModifiedAttributeDict(collections.MutableMapping): """Force value to attribute and prohibit any changes to it""" self.__forced[attributeName] = value self.__placehold(attributeName) - self.__afflict(attributeName, u"\u2263", value) + self.__afflict(attributeName, "\u2263", value) @staticmethod def getResistance(fit, effect): diff --git a/eos/saveddata/booster.py b/eos/saveddata/booster.py index 413661e86..655a3d3f1 100644 --- a/eos/saveddata/booster.py +++ b/eos/saveddata/booster.py @@ -116,7 +116,7 @@ class Booster(HandledItem, ItemAttrShortcut): if not self.active: return - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and \ (effect.isType("passive") or effect.isType("boosterSideEffect")): if effect.isType("boosterSideEffect") and effect not in self.activeSideEffectEffects: diff --git a/eos/saveddata/boosterSideEffect.py b/eos/saveddata/boosterSideEffect.py index 3fd39ac4e..ad0814928 100644 --- a/eos/saveddata/boosterSideEffect.py +++ b/eos/saveddata/boosterSideEffect.py @@ -39,7 +39,7 @@ class BoosterSideEffect(object): self.__effect = None if self.effectID: - self.__effect = next((x for x in self.booster.item.effects.itervalues() if x.ID == self.effectID), None) + self.__effect = next((x for x in self.booster.item.effects.values() if x.ID == self.effectID), None) if self.__effect is None: pyfalog.error("Effect (id: {0}) does not exist", self.effectID) return diff --git a/eos/saveddata/cargo.py b/eos/saveddata/cargo.py index 7b6e71349..11c017370 100644 --- a/eos/saveddata/cargo.py +++ b/eos/saveddata/cargo.py @@ -77,8 +77,8 @@ class Cargo(HandledItem, ItemAttrShortcut): "amount": lambda _val: isinstance(_val, int) } - if key == "amount" and val > sys.maxint: - val = sys.maxint + if key == "amount" and val > sys.maxsize: + val = sys.maxsize if not map[key](val): raise ValueError(str(val) + " is not a valid value for " + key) diff --git a/eos/saveddata/character.py b/eos/saveddata/character.py index 3c0e629d1..55edd9121 100644 --- a/eos/saveddata/character.py +++ b/eos/saveddata/character.py @@ -52,7 +52,6 @@ class Character(object): self.addSkill(Skill(self, item.ID, self.defaultLevel)) self.__implants = HandledImplantBoosterList() - self.apiKey = None @reconstructor def init(self): @@ -119,16 +118,9 @@ class Character(object): return all0 - def apiUpdateCharSheet(self, skills, secStatus=0): - for skillRow in skills: - try: - skill = self.getSkill(int(skillRow["typeID"])) - skill.setLevel(int(skillRow["level"]), persist=True, ignoreRestrict=True) - except: - # if setting a skill doesn't work, it's not the end of the world, just quietly pass - pass - - self.secStatus = secStatus + def clearSkills(self): + del self.__skills[:] + self.__skillIdMap.clear() @property def ro(self): @@ -162,7 +154,7 @@ class Character(object): name += " *" if self.alphaCloneID: - name += u' (\u03B1)' + name += ' (\u03B1)' return name @@ -170,6 +162,18 @@ class Character(object): def name(self, name): self.savedName = name + def setSsoCharacter(self, character, clientHash): + if character is not None: + self.__ssoCharacters.append(character) + else: + for x in self.__ssoCharacters: + if x.client == clientHash: + self.__ssoCharacters.remove(x) + + + def getSsoCharacter(self, clientHash): + return next((x for x in self.__ssoCharacters if x.client == clientHash), None) + @property def alphaCloneID(self): return self.__alphaCloneID @@ -199,7 +203,7 @@ class Character(object): del self.__skillIdMap[skill.itemID] def getSkill(self, item): - if isinstance(item, basestring): + if isinstance(item, str): item = self.getSkillNameMap()[item] elif isinstance(item, int): item = self.getSkillIDMap()[item] @@ -269,20 +273,17 @@ class Character(object): def __deepcopy__(self, memo): copy = Character("%s copy" % self.name, initSkills=False) - copy.apiKey = self.apiKey - copy.apiID = self.apiID for skill in self.skills: copy.addSkill(Skill(copy, skill.itemID, skill.level, False, skill.learned)) return copy - @validates("ID", "name", "apiKey", "ownerID") + @validates("ID", "name", "ownerID") def validator(self, key, val): map = { "ID" : lambda _val: isinstance(_val, int), "name" : lambda _val: True, - "apiKey" : lambda _val: _val is None or (isinstance(_val, basestring) and len(_val) > 0), "ownerID": lambda _val: isinstance(_val, int) or _val is None } @@ -344,13 +345,13 @@ class Skill(HandledItem): elif self.character.name == "All 0": self.activeLevel = self.__level = 0 elif self.character.alphaClone: - return min(self.activeLevel, self.character.alphaClone.getSkillLevel(self)) or 0 + return min(self.activeLevel or 0, self.character.alphaClone.getSkillLevel(self) or 0) return self.activeLevel or 0 def setLevel(self, level, persist=False, ignoreRestrict=False): - if (level < 0 or level > 5) and level is not None: + if level is not None and (level < 0 or level > 5): raise ValueError(str(level) + " is not a valid value for level") if hasattr(self, "_Skill__ro") and self.__ro is True: @@ -362,9 +363,9 @@ class Skill(HandledItem): # which affects performance. Should have a checkSkillLevels() or something that is more efficient for bulk. if not ignoreRestrict and eos.config.settings['strictSkillLevels']: start = time.time() - for item, rlevel in self.item.requiredFor.iteritems(): + for item, rlevel in self.item.requiredFor.items(): if item.group.category.ID == 16: # Skill category - if level < rlevel: + if level is None or level < rlevel: skill = self.character.getSkill(item.ID) # print "Removing skill: {}, Dependant level: {}, Required level: {}".format(skill, level, rlevel) skill.setLevel(None, persist) @@ -392,7 +393,7 @@ class Skill(HandledItem): if key in self.item.attributes: return self.item.attributes[key].value else: - return None + return 0 def calculateModifiedAttributes(self, fit, runTime): if self.__suppressed: # or not self.learned - removed for GH issue 101 @@ -402,7 +403,7 @@ class Skill(HandledItem): if item is None: return - for effect in item.effects.itervalues(): + for effect in item.effects.values(): if effect.runTime == runTime and \ effect.isType("passive") and \ (not fit.isStructure or effect.isType("structure")) and \ diff --git a/eos/saveddata/drone.py b/eos/saveddata/drone.py index 2ce1b52ab..da4c512f2 100644 --- a/eos/saveddata/drone.py +++ b/eos/saveddata/drone.py @@ -73,7 +73,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__itemModifiedAttributes.overrides = self.__item.overrides self.__chargeModifiedAttributes = ModifiedAttributeDict() - chargeID = self.getModifiedItemAttr("entityMissileTypeID") + # pheonix todo: check the attribute itself, not the modified. this will always return 0 now. + chargeID = self.getModifiedItemAttr("entityMissileTypeID", None) if chargeID is not None: charge = eos.db.getItem(int(chargeID)) self.__charge = charge @@ -102,7 +103,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @property def cycleTime(self): - return max(self.getModifiedItemAttr("duration"), 0) + return max(self.getModifiedItemAttr("duration", 0), 0) @property def dealsDamage(self): @@ -138,8 +139,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): cycleTime = self.getModifiedItemAttr(attr) volley = sum( - map(lambda d: (getter("%sDamage" % d) or 0) * (1 - getattr(targetResists, "%sAmount" % d, 0)), - self.DAMAGE_TYPES)) + [(getter("%sDamage" % d) or 0) * (1 - getattr(targetResists, "%sAmount" % d, 0)) for d in self.DAMAGE_TYPES]) volley *= self.amountActive volley *= self.getModifiedItemAttr("damageMultiplier") or 1 self.__volley = volley @@ -155,7 +155,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): getter = self.getModifiedItemAttr cycleTime = self.getModifiedItemAttr(attr) - volley = sum(map(lambda d: getter(d), self.MINING_ATTRIBUTES)) * self.amountActive + volley = sum([getter(d) for d in self.MINING_ATTRIBUTES]) * self.amountActive self.__miningyield = volley / (cycleTime / 1000.0) else: self.__miningyield = 0 @@ -168,7 +168,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): "energyDestabilizationRange", "empFieldRange", "ecmBurstRange", "maxRange") for attr in attrs: - maxRange = self.getModifiedItemAttr(attr) + maxRange = self.getModifiedItemAttr(attr, None) if maxRange is not None: return maxRange if self.charge is not None: @@ -184,7 +184,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def falloff(self): attrs = ("falloff", "falloffEffectiveness") for attr in attrs: - falloff = self.getModifiedItemAttr(attr) + falloff = self.getModifiedItemAttr(attr, None) if falloff is not None: return falloff @@ -236,7 +236,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): context = ("drone",) projected = False - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and \ effect.activeByDefault and \ ((projected is True and effect.isType("projected")) or @@ -251,7 +251,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): i += 1 if self.charge: - for effect in self.charge.effects.itervalues(): + for effect in self.charge.effects.values(): if effect.runTime == runTime and effect.activeByDefault: effect.handler(fit, self, ("droneCharge",)) @@ -263,8 +263,8 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def fits(self, fit): fitDroneGroupLimits = set() - for i in xrange(1, 3): - groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i) + for i in range(1, 3): + groneGrp = fit.ship.getModifiedItemAttr("allowedDroneGroup%d" % i, None) if groneGrp is not None: fitDroneGroupLimits.add(int(groneGrp)) if len(fitDroneGroupLimits) == 0: diff --git a/eos/saveddata/fighter.py b/eos/saveddata/fighter.py index d2b1b45b7..180838e31 100644 --- a/eos/saveddata/fighter.py +++ b/eos/saveddata/fighter.py @@ -98,7 +98,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def __getAbilities(self): """Returns list of FighterAbilities that are loaded with data""" - return [FighterAbility(effect) for effect in self.item.effects.values()] + return [FighterAbility(effect) for effect in list(self.item.effects.values())] def __calculateSlot(self, item): types = { @@ -110,7 +110,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): "StandupHeavy": Slot.FS_HEAVY } - for t, slot in types.iteritems(): + for t, slot in types.items(): if self.getModifiedItemAttr("fighterSquadronIs{}".format(t)): return slot @@ -202,12 +202,12 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): "energyDestabilizationRange", "empFieldRange", "ecmBurstRange", "maxRange") for attr in attrs: - maxRange = self.getModifiedItemAttr(attr) + maxRange = self.getModifiedItemAttr(attr, None) if maxRange is not None: return maxRange if self.charge is not None: - delay = self.getModifiedChargeAttr("explosionDelay") - speed = self.getModifiedChargeAttr("maxVelocity") + delay = self.getModifiedChargeAttr("explosionDelay", None) + speed = self.getModifiedChargeAttr("maxVelocity", None) if delay is not None and speed is not None: return delay / 1000.0 * speed @@ -218,7 +218,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def falloff(self): attrs = ("falloff", "falloffEffectiveness") for attr in attrs: - falloff = self.getModifiedItemAttr(attr) + falloff = self.getModifiedItemAttr(attr, None) if falloff is not None: return falloff diff --git a/eos/saveddata/fighterAbility.py b/eos/saveddata/fighterAbility.py index b6b099a5c..8c32be23d 100644 --- a/eos/saveddata/fighterAbility.py +++ b/eos/saveddata/fighterAbility.py @@ -57,7 +57,7 @@ class FighterAbility(object): self.__effect = None if self.effectID: - self.__effect = next((x for x in self.fighter.item.effects.itervalues() if x.ID == self.effectID), None) + self.__effect = next((x for x in self.fighter.item.effects.values() if x.ID == self.effectID), None) if self.__effect is None: pyfalog.error("Effect (id: {0}) does not exist", self.effectID) return @@ -127,8 +127,8 @@ class FighterAbility(object): if self.attrPrefix == "fighterAbilityLaunchBomb": # bomb calcs - volley = sum(map(lambda attr: (self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * ( - 1 - getattr(targetResists, "%sAmount" % attr, 0)), self.DAMAGE_TYPES)) + volley = sum([(self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * ( + 1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES]) else: volley = sum(map(lambda d2, d: (self.fighter.getModifiedItemAttr( diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py index fa9432291..cb5bedcf9 100644 --- a/eos/saveddata/fit.py +++ b/eos/saveddata/fit.py @@ -258,11 +258,11 @@ class Fit(object): def projectedFits(self): # only in extreme edge cases will the fit be invalid, but to be sure do # not return them. - return [fit for fit in self.__projectedFits.values() if not fit.isInvalid] + return [fit for fit in list(self.__projectedFits.values()) if not fit.isInvalid] @property def commandFits(self): - return [fit for fit in self.__commandFits.values() if not fit.isInvalid] + return [fit for fit in list(self.__commandFits.values()) if not fit.isInvalid] def getProjectionInfo(self, fitID): return self.projectedOnto.get(fitID, None) @@ -492,7 +492,7 @@ class Fit(object): def __runCommandBoosts(self, runTime="normal"): pyfalog.debug("Applying gang boosts for {0}", repr(self)) - for warfareBuffID in self.commandBonuses.keys(): + for warfareBuffID in list(self.commandBonuses.keys()): # Unpack all data required to run effect properly effect_runTime, value, thing, effect = self.commandBonuses[warfareBuffID] @@ -676,7 +676,7 @@ class Fit(object): def __resetDependentCalcs(self): self.calculated = False - for value in self.projectedOnto.values(): + for value in list(self.projectedOnto.values()): if value.victim_fit: # removing a self-projected fit causes victim fit to be None. @todo: look into why. :3 value.victim_fit.calculated = False @@ -707,14 +707,14 @@ class Fit(object): self.__resetDependentCalcs() # For fits that are under local's Command, we do the same thing - for value in self.boostedOnto.values(): + for value in list(self.boostedOnto.values()): # apparently this is a thing that happens when removing a command fit from a fit and then switching to # that command fit. Same as projected clears, figure out why. if value.boosted_fit: value.boosted_fit.__resetDependentCalcs() if targetFit and type == CalcType.PROJECTED: - pyfalog.debug(u"Calculating projections from {0} to target {1}", repr(self), repr(targetFit)) + pyfalog.debug("Calculating projections from {0} to target {1}", repr(self), repr(targetFit)) projectionInfo = self.getProjectionInfo(targetFit.ID) # Start applying any command fits that we may have. @@ -756,7 +756,7 @@ class Fit(object): # Loop through our run times here. These determine which effects are run in which order. for runTime in ("early", "normal", "late"): - pyfalog.debug("Run time: {0}", runTime) + # pyfalog.debug("Run time: {0}", runTime) # Items that are unrestricted. These items are run on the local fit # first and then projected onto the target fit it one is designated u = [ @@ -795,7 +795,7 @@ class Fit(object): # targetFit.register(item, origin=self) item.calculateModifiedAttributes(targetFit, runTime, False, True) - pyfalog.debug("Command Bonuses: {}".format(self.commandBonuses)) + # pyfalog.debug("Command Bonuses: {}".format(self.commandBonuses)) # If we are calculating our local or projected fit and have command bonuses, apply them if type != CalcType.COMMAND and self.commandBonuses: @@ -838,7 +838,7 @@ class Fit(object): for item in c: if item is not None: # apply effects onto target fit x amount of times - for _ in xrange(projectionInfo.amount): + for _ in range(projectionInfo.amount): targetFit.register(item, origin=self) item.calculateModifiedAttributes(targetFit, runTime, True) @@ -854,7 +854,7 @@ class Fit(object): for slotType in (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM, Slot.SERVICE): amount = self.getSlotsFree(slotType, True) if amount > 0: - for _ in xrange(int(amount)): + for _ in range(int(amount)): self.modules.append(Module.buildEmpty(slotType)) if amount < 0: @@ -870,7 +870,7 @@ class Fit(object): self.modules.remove(mod) def unfill(self): - for i in xrange(len(self.modules) - 1, -1, -1): + for i in range(len(self.modules) - 1, -1, -1): mod = self.modules[i] if mod.isEmpty: del self.modules[i] @@ -878,7 +878,7 @@ class Fit(object): @property def modCount(self): x = 0 - for i in xrange(len(self.modules) - 1, -1, -1): + for i in range(len(self.modules) - 1, -1, -1): mod = self.modules[i] if not mod.isEmpty: x += 1 @@ -1143,14 +1143,16 @@ class Fit(object): except AttributeError: usesCap = False - cycleTime = mod.rawCycleTime - amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) # Normal Repairers if usesCap and not mod.charge: + cycleTime = mod.rawCycleTime + amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) sustainable[attr] -= amount / (cycleTime / 1000.0) repairers.append(mod) # Ancillary Armor reps etc elif usesCap and mod.charge: + cycleTime = mod.rawCycleTime + amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) if mod.charge.name == "Nanite Repair Paste": multiplier = mod.getModifiedItemAttr("chargedArmorDamageMultiplier") or 1 else: @@ -1158,7 +1160,9 @@ class Fit(object): sustainable[attr] -= amount * multiplier / (cycleTime / 1000.0) repairers.append(mod) # Ancillary Shield boosters etc - elif not usesCap: + elif not usesCap and mod.item.group.name in ("Ancillary Shield Booster", "Ancillary Remote Shield Booster"): + cycleTime = mod.rawCycleTime + amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) if self.factorReload and mod.charge: reloadtime = mod.reloadTime else: @@ -1527,11 +1531,11 @@ class Fit(object): return copy_ship def __repr__(self): - return u"Fit(ID={}, ship={}, name={}) at {}".format( + return "Fit(ID={}, ship={}, name={}) at {}".format( self.ID, self.ship.item.name, self.name, hex(id(self)) - ).encode('utf8') + ) def __str__(self): - return u"{} ({})".format( + return "{} ({})".format( self.name, self.ship.item.name - ).encode('utf8') + ) diff --git a/eos/saveddata/implant.py b/eos/saveddata/implant.py index 869e5c608..d61b9c43b 100644 --- a/eos/saveddata/implant.py +++ b/eos/saveddata/implant.py @@ -93,7 +93,7 @@ class Implant(HandledItem, ItemAttrShortcut): return if not self.active: return - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and effect.isType("passive") and effect.activeByDefault: effect.handler(fit, self, ("implant",)) diff --git a/eos/saveddata/mode.py b/eos/saveddata/mode.py index 50413906d..f0d950549 100644 --- a/eos/saveddata/mode.py +++ b/eos/saveddata/mode.py @@ -50,6 +50,6 @@ class Mode(ItemAttrShortcut, HandledItem): def calculateModifiedAttributes(self, fit, runTime, forceProjected=False): if self.item: - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and effect.activeByDefault: effect.handler(fit, self, context=("module",)) diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index c55bc60cc..185ec50e4 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -182,7 +182,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @property def numShots(self): if self.charge is None: - return None + return 0 if self.__chargeCycles is None and self.charge: numCharges = self.numCharges # Usual ammo like projectiles and missiles @@ -217,7 +217,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): armorRep = self.getModifiedItemAttr("armorDamageAmount") or 0 shieldRep = self.getModifiedItemAttr("shieldBonus") or 0 if not cycles or (not armorRep and not shieldRep): - return None + return 0 hp = round((armorRep + shieldRep) * cycles) return hp @@ -255,7 +255,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): "ecmBurstRange", "warpScrambleRange", "cargoScanRange", "shipScanRange", "surveyScanRange") for attr in attrs: - maxRange = self.getModifiedItemAttr(attr) + maxRange = self.getModifiedItemAttr(attr, None) if maxRange is not None: return maxRange if self.charge is not None: @@ -284,7 +284,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def falloff(self): attrs = ("falloffEffectiveness", "falloff", "shipScanFalloff") for attr in attrs: - falloff = self.getModifiedItemAttr(attr) + falloff = self.getModifiedItemAttr(attr, None) if falloff is not None: return falloff @@ -333,9 +333,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): else: func = self.getModifiedItemAttr - volley = sum(map( - lambda attr: (func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)), - self.DAMAGE_TYPES)) + volley = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES]) volley *= self.getModifiedItemAttr("damageMultiplier") or 1 # Disintegrator-specific ramp-up multiplier volley *= (self.getModifiedItemAttr("damageMultiplierBonusMax") or 0) + 1 @@ -424,19 +422,19 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): fitsOnType = set() fitsOnGroup = set() - shipType = self.getModifiedItemAttr("fitsToShipType") + shipType = self.getModifiedItemAttr("fitsToShipType", None) if shipType is not None: fitsOnType.add(shipType) - for attr in self.itemModifiedAttributes.keys(): + for attr in list(self.itemModifiedAttributes.keys()): if attr.startswith("canFitShipType"): - shipType = self.getModifiedItemAttr(attr) + shipType = self.getModifiedItemAttr(attr, None) if shipType is not None: fitsOnType.add(shipType) - for attr in self.itemModifiedAttributes.keys(): + for attr in list(self.itemModifiedAttributes.keys()): if attr.startswith("canFitShipGroup"): - shipGroup = self.getModifiedItemAttr(attr) + shipGroup = self.getModifiedItemAttr(attr, None) if shipGroup is not None: fitsOnGroup.add(shipGroup) @@ -468,7 +466,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): return False # Check max group fitted - max = self.getModifiedItemAttr("maxGroupFitted") + max = self.getModifiedItemAttr("maxGroupFitted", None) if max is not None: current = 0 # if self.owner != fit else -1 # Disabled, see #1278 for mod in fit.modules: @@ -481,11 +479,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): # Check this only if we're told to do so if hardpointLimit: if self.hardpoint == Hardpoint.TURRET: - if (fit.ship.getModifiedItemAttr('turretSlotsLeft') or 0) - fit.getHardpointsUsed(Hardpoint.TURRET) < 1: + if fit.ship.getModifiedItemAttr('turretSlotsLeft') - fit.getHardpointsUsed(Hardpoint.TURRET) < 1: return False elif self.hardpoint == Hardpoint.MISSILE: - if (fit.ship.getModifiedItemAttr('launcherSlotsLeft') or 0) - fit.getHardpointsUsed( - Hardpoint.MISSILE) < 1: + if fit.ship.getModifiedItemAttr('launcherSlotsLeft') - fit.getHardpointsUsed(Hardpoint.MISSILE) < 1: return False return True @@ -515,7 +512,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): return True # Check if the local module is over it's max limit; if it's not, we're fine - maxGroupActive = self.getModifiedItemAttr("maxGroupActive") + maxGroupActive = self.getModifiedItemAttr("maxGroupActive", None) if maxGroupActive is None and projectedOnto is None: return True @@ -563,7 +560,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): chargeGroup = charge.groupID for i in range(5): - itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i)) + itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None) if itemChargeGroup is None: continue if itemChargeGroup == chargeGroup: @@ -574,7 +571,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def getValidCharges(self): validCharges = set() for i in range(5): - itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i)) + itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None) if itemChargeGroup is not None: g = eos.db.getGroup(int(itemChargeGroup), eager=("items.icon", "items.attributes")) if g is None: @@ -595,7 +592,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if item is None: return Hardpoint.NONE - for effectName, slot in effectHardpointMap.iteritems(): + for effectName, slot in effectHardpointMap.items(): if effectName in item.effects: return slot @@ -613,7 +610,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): } if item is None: return None - for effectName, slot in effectSlotMap.iteritems(): + for effectName, slot in effectSlotMap.items(): if effectName in item.effects: return slot if item.group.name == "Effect Beacon": @@ -667,7 +664,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if self.charge is not None: # fix for #82 and it's regression #106 if not projected or (self.projected and not forceProjected) or gang: - for effect in self.charge.effects.itervalues(): + for effect in self.charge.effects.values(): if effect.runTime == runTime and \ effect.activeByDefault and \ (effect.isType("offline") or @@ -686,7 +683,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if self.item: if self.state >= State.OVERHEATED: - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and \ effect.isType("overheat") \ and not forceProjected \ @@ -694,7 +691,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): and ((gang and effect.isType("gang")) or not gang): effect.handler(fit, self, context) - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and \ effect.activeByDefault and \ (effect.isType("offline") or @@ -754,13 +751,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): @property def rawCycleTime(self): speed = max( - self.getModifiedItemAttr("speed"), # Most weapons - self.getModifiedItemAttr("duration"), # Most average modules - self.getModifiedItemAttr("durationSensorDampeningBurstProjector"), - self.getModifiedItemAttr("durationTargetIlluminationBurstProjector"), - self.getModifiedItemAttr("durationECMJammerBurstProjector"), - self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector"), - 0, # Return 0 if none of the above are valid + self.getModifiedItemAttr("speed", 0), # Most weapons + self.getModifiedItemAttr("duration", 0), # Most average modules + self.getModifiedItemAttr("durationSensorDampeningBurstProjector", 0), + self.getModifiedItemAttr("durationTargetIlluminationBurstProjector", 0), + self.getModifiedItemAttr("durationECMJammerBurstProjector", 0), + self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector", 0) ) return speed @@ -795,7 +791,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def __repr__(self): if self.item: - return u"Module(ID={}, name={}) at {}".format( + return "Module(ID={}, name={}) at {}".format( self.item.ID, self.item.name, hex(id(self)) ) else: diff --git a/eos/saveddata/ship.py b/eos/saveddata/ship.py index 90586dbd7..56b3f7c0d 100644 --- a/eos/saveddata/ship.py +++ b/eos/saveddata/ship.py @@ -87,7 +87,7 @@ class Ship(ItemAttrShortcut, HandledItem): def calculateModifiedAttributes(self, fit, runTime, forceProjected=False): if forceProjected: return - for effect in self.item.effects.itervalues(): + for effect in self.item.effects.values(): if effect.runTime == runTime and \ effect.isType("passive") and \ effect.activeByDefault: diff --git a/eos/saveddata/crestchar.py b/eos/saveddata/ssocharacter.py similarity index 58% rename from eos/saveddata/crestchar.py rename to eos/saveddata/ssocharacter.py index ce6ab14fe..d1234c2c4 100644 --- a/eos/saveddata/crestchar.py +++ b/eos/saveddata/ssocharacter.py @@ -18,17 +18,32 @@ # =============================================================================== from sqlalchemy.orm import reconstructor - +import datetime +import time # from tomorrow import threads -class CrestChar(object): - def __init__(self, id, name, refresh_token=None): - self.ID = id - self.name = name - self.refresh_token = refresh_token +class SsoCharacter(object): + def __init__(self, charID, name, client, accessToken=None, refreshToken=None): + self.characterID = charID + self.characterName = name + self.client = client + self.accessToken = accessToken + self.refreshToken = refreshToken + self.accessTokenExpires = None + @reconstructor def init(self): pass + + def is_token_expired(self): + if self.accessTokenExpires is None: + return True + return datetime.datetime.now() >= self.accessTokenExpires + + def __repr__(self): + return "SsoCharacter(ID={}, name={}, client={}) at {}".format( + self.ID, self.characterName, self.client, hex(id(self)) + ) diff --git a/eos/saveddata/user.py b/eos/saveddata/user.py index 39e2eaed2..b623452db 100644 --- a/eos/saveddata/user.py +++ b/eos/saveddata/user.py @@ -33,7 +33,7 @@ class User(object): def encodeAndSetPassword(self, pw): h = hashlib.new("sha256") - salt = "".join([random.choice(string.letters) for _ in xrange(32)]) + salt = "".join([random.choice(string.letters) for _ in range(32)]) h.update(pw) h.update(salt) self.password = ("%s%s" % (h.hexdigest(), salt)) @@ -45,14 +45,14 @@ class User(object): h = hashlib.new("sha256") h.update(pw) h.update(salt) - return self.password == (u"%s%s" % (h.hexdigest(), salt)) + return self.password == ("%s%s" % (h.hexdigest(), salt)) @validates("ID", "username", "password", "admin") def validator(self, key, val): map = { "ID" : lambda _val: isinstance(_val, int), - "username": lambda _val: isinstance(_val, basestring), - "password": lambda _val: isinstance(_val, basestring) and len(_val) == 96, + "username": lambda _val: isinstance(_val, str), + "password": lambda _val: isinstance(_val, str) and len(_val) == 96, "admin" : lambda _val: isinstance(_val, bool) } diff --git a/gui/additionsPane.py b/gui/additionsPane.py index b12fe5036..8bc72f8ec 100644 --- a/gui/additionsPane.py +++ b/gui/additionsPane.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.builtinAdditionPanes.boosterView import BoosterView from gui.builtinAdditionPanes.cargoView import CargoView from gui.builtinAdditionPanes.commandView import CommandView @@ -29,22 +29,22 @@ from gui.builtinAdditionPanes.fighterView import FighterView from gui.builtinAdditionPanes.implantView import ImplantView from gui.builtinAdditionPanes.notesView import NotesView from gui.builtinAdditionPanes.projectedView import ProjectedView -from gui.chromeTabs import PFNotebook -from gui.pyfatogglepanel import TogglePanel +from gui.chrome_tabs import ChromeNotebook +from gui.toggle_panel import TogglePanel class AdditionsPane(TogglePanel): def __init__(self, parent): - TogglePanel.__init__(self, parent, forceLayout=1) + TogglePanel.__init__(self, parent, force_layout=1) self.SetLabel("Additions") - pane = self.GetContentPane() + pane = self.GetContentPanel() baseSizer = wx.BoxSizer(wx.HORIZONTAL) pane.SetSizer(baseSizer) - self.notebook = PFNotebook(pane, False) + self.notebook = ChromeNotebook(pane, False) self.notebook.SetMinSize((-1, 1000)) baseSizer.Add(self.notebook, 1, wx.EXPAND) @@ -59,28 +59,28 @@ class AdditionsPane(TogglePanel): notesImg = BitmapLoader.getImage("skill_small", "gui") self.drone = DroneView(self.notebook) - self.notebook.AddPage(self.drone, "Drones", tabImage=droneImg, showClose=False) + self.notebook.AddPage(self.drone, "Drones", image=droneImg, closeable=False) self.fighter = FighterView(self.notebook) - self.notebook.AddPage(self.fighter, "Fighters", tabImage=fighterImg, showClose=False) + self.notebook.AddPage(self.fighter, "Fighters", image=fighterImg, closeable=False) self.cargo = CargoView(self.notebook) - self.notebook.AddPage(self.cargo, "Cargo", tabImage=cargoImg, showClose=False) + self.notebook.AddPage(self.cargo, "Cargo", image=cargoImg, closeable=False) self.implant = ImplantView(self.notebook) - self.notebook.AddPage(self.implant, "Implants", tabImage=implantImg, showClose=False) + self.notebook.AddPage(self.implant, "Implants", image=implantImg, closeable=False) self.booster = BoosterView(self.notebook) - self.notebook.AddPage(self.booster, "Boosters", tabImage=boosterImg, showClose=False) + self.notebook.AddPage(self.booster, "Boosters", image=boosterImg, closeable=False) self.projectedPage = ProjectedView(self.notebook) - self.notebook.AddPage(self.projectedPage, "Projected", tabImage=projectedImg, showClose=False) + self.notebook.AddPage(self.projectedPage, "Projected", image=projectedImg, closeable=False) self.gangPage = CommandView(self.notebook) - self.notebook.AddPage(self.gangPage, "Command", tabImage=gangImg, showClose=False) + self.notebook.AddPage(self.gangPage, "Command", image=gangImg, closeable=False) self.notes = NotesView(self.notebook) - self.notebook.AddPage(self.notes, "Notes", tabImage=notesImg, showClose=False) + self.notebook.AddPage(self.notes, "Notes", image=notesImg, closeable=False) self.notebook.SetSelection(0) @@ -92,19 +92,16 @@ class AdditionsPane(TogglePanel): def getName(self, idx): return self.PANES[idx] - def toggleContent(self, event): - TogglePanel.toggleContent(self, event) - h = self.headerPanel.GetSize()[1] + 4 + def ToggleContent(self, event): + TogglePanel.ToggleContent(self, event) + h = self.header_panel.GetSize()[1] + 4 if self.IsCollapsed(): self.old_pos = self.parent.GetSashPosition() self.parent.SetMinimumPaneSize(h) self.parent.SetSashPosition(h * -1, True) - # only available in >= wx2.9 - if getattr(self.parent, "SetSashInvisible", None): - self.parent.SetSashInvisible(True) + self.parent.SetSashInvisible(True) else: - if getattr(self.parent, "SetSashInvisible", None): - self.parent.SetSashInvisible(False) + self.parent.SetSashInvisible(False) self.parent.SetMinimumPaneSize(200) self.parent.SetSashPosition(self.old_pos, True) diff --git a/gui/bitmapLoader.py b/gui/bitmap_loader.py similarity index 78% rename from gui/bitmapLoader.py rename to gui/bitmap_loader.py index eb53b1f1d..9330f1fc3 100644 --- a/gui/bitmapLoader.py +++ b/gui/bitmap_loader.py @@ -17,9 +17,10 @@ # along with pyfa. If not, see . # ============================================================================= -import cStringIO +import io import os.path import zipfile +from collections import OrderedDict # noinspection PyPackageRequirements import wx @@ -29,23 +30,18 @@ import config from logbook import Logger logging = Logger(__name__) -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict - class BitmapLoader(object): try: archive = zipfile.ZipFile(os.path.join(config.pyfaPath, 'imgs.zip'), 'r') logging.info("Using zipped image files.") - except IOError: + except (IOError, TypeError): logging.info("Using local image files.") archive = None - cachedBitmaps = OrderedDict() - dontUseCachedBitmaps = False - max_bmps = 500 + cached_bitmaps = OrderedDict() + dont_use_cached_bitmaps = False + max_cached_bitmaps = 500 @classmethod def getStaticBitmap(cls, name, parent, location): @@ -55,25 +51,25 @@ class BitmapLoader(object): @classmethod def getBitmap(cls, name, location): - if cls.dontUseCachedBitmaps: + if cls.dont_use_cached_bitmaps: img = cls.getImage(name, location) if img is not None: return img.ConvertToBitmap() path = "%s%s" % (name, location) - if len(cls.cachedBitmaps) == cls.max_bmps: - cls.cachedBitmaps.popitem(False) + if len(cls.cached_bitmaps) == cls.max_cached_bitmaps: + cls.cached_bitmaps.popitem(False) - if path not in cls.cachedBitmaps: + if path not in cls.cached_bitmaps: img = cls.getImage(name, location) if img is not None: bmp = img.ConvertToBitmap() else: bmp = None - cls.cachedBitmaps[path] = bmp + cls.cached_bitmaps[path] = bmp else: - bmp = cls.cachedBitmaps[path] + bmp = cls.cached_bitmaps[path] return bmp @@ -88,14 +84,14 @@ class BitmapLoader(object): try: img_data = cls.archive.read(path) - sbuf = cStringIO.StringIO(img_data) + sbuf = io.StringIO(img_data) return wx.ImageFromStream(sbuf) except KeyError: - print("Missing icon file from zip: {0}".format(path)) + print(("Missing icon file from zip: {0}".format(path))) else: path = os.path.join(config.pyfaPath, 'imgs' + os.sep + location + os.sep + filename) if os.path.exists(path): return wx.Image(path) else: - print("Missing icon file: {0}".format(path)) + print(("Missing icon file: {0}".format(path))) diff --git a/gui/builtinAdditionPanes/boosterView.py b/gui/builtinAdditionPanes/boosterView.py index ab80209ec..8923f83de 100644 --- a/gui/builtinAdditionPanes/boosterView.py +++ b/gui/builtinAdditionPanes/boosterView.py @@ -28,12 +28,12 @@ from gui.utils.staticHelpers import DragDropHelper from service.fit import Fit -class BoosterViewDrop(wx.PyDropTarget): +class BoosterViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(BoosterViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -131,6 +131,7 @@ class BoosterView(d.Display): fit = sFit.getFit(fitID) if not fit or fit.isStructure: + event.Skip() return trigger = sFit.addBooster(fitID, event.itemID) diff --git a/gui/builtinAdditionPanes/cargoView.py b/gui/builtinAdditionPanes/cargoView.py index cb6a5b47e..ed5687cfc 100644 --- a/gui/builtinAdditionPanes/cargoView.py +++ b/gui/builtinAdditionPanes/cargoView.py @@ -28,12 +28,12 @@ from service.fit import Fit from service.market import Market -class CargoViewDrop(wx.PyDropTarget): +class CargoViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(CargoViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -88,7 +88,7 @@ class CargoView(d.Display): row = event.GetIndex() if row != -1: - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "cargo:" + str(row) data.SetText(dataStr) @@ -119,14 +119,14 @@ class CargoView(d.Display): module = fit.modules[modIdx] if dstRow != -1: # we're swapping with cargo - if mstate.CmdDown(): # if copying, append to cargo + if mstate.cmdDown: # if copying, append to cargo sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID) else: # else, move / swap sFit.moveCargoToModule(self.mainFrame.getActiveFit(), module.position, dstRow) else: # dragging to blank spot, append sFit.addCargo(self.mainFrame.getActiveFit(), module.item.ID) - if not mstate.CmdDown(): # if not copying, remove module + if not mstate.cmdDown: # if not copying, remove module sFit.removeModule(self.mainFrame.getActiveFit(), module.position) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit(), action="moddel", typeID=module.item.ID)) diff --git a/gui/builtinAdditionPanes/commandView.py b/gui/builtinAdditionPanes/commandView.py index e903f0ac3..5ee0d1872 100644 --- a/gui/builtinAdditionPanes/commandView.py +++ b/gui/builtinAdditionPanes/commandView.py @@ -43,12 +43,12 @@ class DummyEntry(object): self.item = DummyItem(txt) -class CommandViewDrop(wx.PyDropTarget): +class CommandViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(CommandViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -119,7 +119,7 @@ class CommandView(d.Display): def startDrag(self, event): row = event.GetIndex() if row != -1 and isinstance(self.get(row), es_Drone): - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "command:" + str(self.GetItemData(row)) data.SetText(dataStr) @@ -136,6 +136,8 @@ class CommandView(d.Display): sFit = Fit.getInstance() fit = sFit.getFit(event.fitID) + CommandFits.populateFits(event) + self.Parent.Parent.DisablePage(self, not fit or fit.isStructure) # Clear list and get out if current fitId is None @@ -167,6 +169,8 @@ class CommandView(d.Display): self.update(stuff) + event.Skip() + def get(self, row): if row == -1: return None diff --git a/gui/builtinAdditionPanes/droneView.py b/gui/builtinAdditionPanes/droneView.py index 7fde0a105..e9b893047 100644 --- a/gui/builtinAdditionPanes/droneView.py +++ b/gui/builtinAdditionPanes/droneView.py @@ -21,6 +21,7 @@ import wx import gui.globalEvents as GE +import gui.mainFrame from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED from gui.display import Display from gui.builtinViewColumns.state import State @@ -30,12 +31,12 @@ from service.fit import Fit from service.market import Market -class DroneViewDrop(wx.PyDropTarget): +class DroneViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(DroneViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -66,6 +67,8 @@ class DroneView(Display): self.hoveredRow = None self.hoveredColumn = None + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.mainFrame.Bind(ITEM_SELECTED, self.addItem) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) @@ -101,7 +104,7 @@ class DroneView(Display): if self.DEFAULT_COLS[col] == "Miscellanea": tooltip = self.activeColumns[col].getToolTip(mod) if tooltip is not None: - self.SetToolTipString(tooltip) + self.SetToolTip(tooltip) else: self.SetToolTip(None) else: @@ -123,7 +126,7 @@ class DroneView(Display): def startDrag(self, event): row = event.GetIndex() if row != -1: - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "drone:" + str(row) data.SetText(dataStr) @@ -207,6 +210,7 @@ class DroneView(Display): fit = sFit.getFit(fitID) if not fit or fit.isStructure: + event.Skip() return trigger = sFit.addDrone(fitID, event.itemID) diff --git a/gui/builtinAdditionPanes/fighterView.py b/gui/builtinAdditionPanes/fighterView.py index 3e7017794..3292583fd 100644 --- a/gui/builtinAdditionPanes/fighterView.py +++ b/gui/builtinAdditionPanes/fighterView.py @@ -32,12 +32,12 @@ from service.fit import Fit from service.market import Market -class FighterViewDrop(wx.PyDropTarget): +class FighterViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(FighterViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -60,7 +60,7 @@ class FighterView(wx.Panel): mainSizer.Add(self.fighterDisplay, 1, wx.EXPAND, 0) textSizer = wx.BoxSizer(wx.HORIZONTAL) - textSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + textSizer.AddStretchSpacer() for x in self.labels: lbl = wx.StaticText(self, wx.ID_ANY, x.capitalize()) @@ -75,7 +75,7 @@ class FighterView(wx.Panel): lbl = wx.StaticText(self, wx.ID_ANY, "0") setattr(self, "label%sTotal" % (x.capitalize()), lbl) textSizer.Add(lbl, 0, wx.ALIGN_CENTER) - textSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + textSizer.AddStretchSpacer() mainSizer.Add(textSizer, 0, wx.EXPAND, 5) @@ -97,7 +97,7 @@ class FighterView(wx.Panel): slot = getattr(Slot, "F_{}".format(x.upper())) used = fit.getSlotsUsed(slot) total = fit.getNumSlots(slot) - color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings_GetColour( + color = wx.Colour(204, 51, 51) if used > total else wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOWTEXT) lbl = getattr(self, "label%sUsed" % x.capitalize()) @@ -110,6 +110,8 @@ class FighterView(wx.Panel): self.Refresh() + event.Skip() + class FighterDisplay(d.Display): DEFAULT_COLS = ["State", @@ -166,7 +168,7 @@ class FighterDisplay(d.Display): if self.DEFAULT_COLS[col] == "Miscellanea": tooltip = self.activeColumns[col].getToolTip(mod) if tooltip is not None: - self.SetToolTipString(tooltip) + self.SetToolTip(tooltip) else: self.SetToolTip(None) else: @@ -188,7 +190,7 @@ class FighterDisplay(d.Display): def startDrag(self, event): row = event.GetIndex() if row != -1: - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "fighter:" + str(row) data.SetText(dataStr) diff --git a/gui/builtinAdditionPanes/implantView.py b/gui/builtinAdditionPanes/implantView.py index 2531f997a..b1d7bbe66 100644 --- a/gui/builtinAdditionPanes/implantView.py +++ b/gui/builtinAdditionPanes/implantView.py @@ -41,12 +41,12 @@ class ImplantView(wx.Panel): mainSizer.Add(self.implantDisplay, 1, wx.EXPAND, 0) radioSizer = wx.BoxSizer(wx.HORIZONTAL) - radioSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + radioSizer.AddStretchSpacer() self.rbFit = wx.RadioButton(self, id=wx.ID_ANY, label="Use Fit-specific Implants", style=wx.RB_GROUP) self.rbChar = wx.RadioButton(self, id=wx.ID_ANY, label="Use Character Implants") radioSizer.Add(self.rbFit, 0, wx.ALL, 5) radioSizer.Add(self.rbChar, 0, wx.ALL, 5) - radioSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + radioSizer.AddStretchSpacer() mainSizer.Add(radioSizer, 0, wx.EXPAND, 5) @@ -71,6 +71,8 @@ class ImplantView(wx.Panel): self.rbFit.Enable(fit is not None) self.rbChar.Enable(fit is not None) + event.Skip() + def OnRadioSelect(self, event): fitID = self.mainFrame.getActiveFit() sFit = Fit.getInstance() @@ -150,6 +152,7 @@ class ImplantDisplay(d.Display): fit = sFit.getFit(fitID) if not fit or fit.isStructure: + event.Skip() return trigger = sFit.addImplant(fitID, event.itemID) diff --git a/gui/builtinAdditionPanes/notesView.py b/gui/builtinAdditionPanes/notesView.py index 5c44a26c2..35a75b5a8 100644 --- a/gui/builtinAdditionPanes/notesView.py +++ b/gui/builtinAdditionPanes/notesView.py @@ -40,6 +40,8 @@ class NotesView(wx.Panel): self.lastFitId = event.fitID self.editNotes.SetValue(fit.notes or "") + event.Skip() + def onText(self, event): # delay the save so we're not writing to sqlite on every keystroke self.saveTimer.Stop() # cancel the existing timer diff --git a/gui/builtinAdditionPanes/projectedView.py b/gui/builtinAdditionPanes/projectedView.py index 93350e7f7..a5654d1a4 100644 --- a/gui/builtinAdditionPanes/projectedView.py +++ b/gui/builtinAdditionPanes/projectedView.py @@ -47,12 +47,12 @@ class DummyEntry(object): self.item = DummyItem(txt) -class ProjectedViewDrop(wx.PyDropTarget): +class ProjectedViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(ProjectedViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -139,7 +139,7 @@ class ProjectedView(d.Display): def startDrag(self, event): row = event.GetIndex() if row != -1 and isinstance(self.get(row), es_Drone): - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "projected:" + str(self.GetItemData(row)) data.SetText(dataStr) @@ -229,6 +229,8 @@ class ProjectedView(d.Display): self.update(stuff) + event.Skip() + def get(self, row): if row == -1: return None diff --git a/gui/builtinContextMenus/changeAffectingSkills.py b/gui/builtinContextMenus/changeAffectingSkills.py index a708ec51f..148c18218 100644 --- a/gui/builtinContextMenus/changeAffectingSkills.py +++ b/gui/builtinContextMenus/changeAffectingSkills.py @@ -3,7 +3,7 @@ from gui.contextMenu import ContextMenu import gui.mainFrame # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from eos.saveddata.character import Skill import gui.globalEvents as GE from service.fit import Fit @@ -49,7 +49,7 @@ class ChangeAffectingSkills(ContextMenu): if cont[attrName] == 0: continue - for fit, afflictors in cont.getAfflictions(attrName).iteritems(): + for fit, afflictors in cont.getAfflictions(attrName).items(): for afflictor, modifier, amount, used in afflictors: # only add Skills if not isinstance(afflictor, Skill): @@ -89,12 +89,12 @@ class ChangeAffectingSkills(ContextMenu): if bitmap is not None: skillItem.SetBitmap(bitmap) - for i in xrange(-1, 6): + for i in range(-1, 6): levelItem = self.addSkill(rootMenu if msw else grandSub, skill, i) - grandSub.AppendItem(levelItem) + grandSub.Append(levelItem) if (not skill.learned and i == -1) or (skill.learned and skill.level == i): levelItem.Check(True) - sub.AppendItem(skillItem) + sub.Append(skillItem) return sub diff --git a/gui/builtinContextMenus/commandFits.py b/gui/builtinContextMenus/commandFits.py index 782e3cbff..936dd0cc0 100644 --- a/gui/builtinContextMenus/commandFits.py +++ b/gui/builtinContextMenus/commandFits.py @@ -30,6 +30,7 @@ class CommandFits(ContextMenu): if evt is None or not ids.isdisjoint(cls.commandTypeIDs): # we are adding or removing an item that defines a command fit. Need to refresh fit list cls.populateFits(evt) + evt.Skip() @classmethod def populateFits(cls, evt): @@ -50,7 +51,7 @@ class CommandFits(ContextMenu): return "Command Fits" def addFit(self, menu, fit, includeShip=False): - label = fit.name if not includeShip else u"({}) {}".format(fit.ship.item.name, fit.name) + label = fit.name if not includeShip else "({}) {}".format(fit.ship.item.name, fit.name) id = ContextMenu.nextID() self.fitMenuItemIds[id] = fit menuItem = wx.MenuItem(menu, id, label) @@ -67,7 +68,7 @@ class CommandFits(ContextMenu): if len(self.__class__.commandFits) < 15: for fit in sorted(self.__class__.commandFits, key=lambda x: x.name): menuItem = self.addFit(rootMenu if msw else sub, fit, True) - sub.AppendItem(menuItem) + sub.Append(menuItem) else: typeDict = {} @@ -84,9 +85,9 @@ class CommandFits(ContextMenu): for fit in sorted(typeDict[ship], key=lambda x: x.name): fitItem = self.addFit(rootMenu if msw else grandSub, fit, False) - grandSub.AppendItem(fitItem) + grandSub.Append(fitItem) - sub.AppendItem(shipItem) + sub.Append(shipItem) return sub diff --git a/gui/builtinContextMenus/damagePattern.py b/gui/builtinContextMenus/damagePattern.py index 8399dd069..ca65cf9c9 100644 --- a/gui/builtinContextMenus/damagePattern.py +++ b/gui/builtinContextMenus/damagePattern.py @@ -3,15 +3,12 @@ import gui.mainFrame import gui.globalEvents as GE # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.fit import Fit from service.damagePattern import DamagePattern as import_DamagePattern from service.settings import ContextMenuSettings -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict +from collections import OrderedDict class DamagePattern(ContextMenu): @@ -52,7 +49,7 @@ class DamagePattern(ContextMenu): self.singles.append(pattern) # return list of names, with singles first followed by submenu names - self.m = map(lambda p: p.name, self.singles) + self.subMenus.keys() + self.m = [p.name for p in self.singles] + list(self.subMenus.keys()) return self.m def addPattern(self, rootMenu, pattern): @@ -96,7 +93,7 @@ class DamagePattern(ContextMenu): # Items that have a parent for pattern in self.subMenus[self.m[i]]: - sub.AppendItem(self.addPattern(rootMenu if msw else sub, pattern)) + sub.Append(self.addPattern(rootMenu if msw else sub, pattern)) return sub diff --git a/gui/builtinContextMenus/droneSplit.py b/gui/builtinContextMenus/droneSplit.py index 7a121ae96..ec52db3f8 100644 --- a/gui/builtinContextMenus/droneSplit.py +++ b/gui/builtinContextMenus/droneSplit.py @@ -45,7 +45,7 @@ class DroneSpinner(wx.Dialog): bSizer1.Add(self.spinner, 1, wx.ALL, 5) - self.button = wx.Button(self, wx.ID_OK, u"Split") + self.button = wx.Button(self, wx.ID_OK, "Split") bSizer1.Add(self.button, 0, wx.ALL, 5) self.SetSizer(bSizer1) diff --git a/gui/builtinContextMenus/factorReload.py b/gui/builtinContextMenus/factorReload.py index 6770e64af..5600c4c5c 100644 --- a/gui/builtinContextMenus/factorReload.py +++ b/gui/builtinContextMenus/factorReload.py @@ -3,7 +3,7 @@ import gui.mainFrame import gui.globalEvents as GE # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.fit import Fit from service.settings import ContextMenuSettings @@ -31,9 +31,7 @@ class FactorReload(ContextMenu): def getBitmap(self, context, selection): sFit = Fit.getInstance() - fitID = self.mainFrame.getActiveFit() - fit = sFit.getFit(fitID) - if fit.factorReload: + if sFit.serviceFittingOptions["useGlobalForceReload"]: return BitmapLoader.getBitmap("state_active_small", "gui") else: return None diff --git a/gui/builtinContextMenus/fighterAbilities.py b/gui/builtinContextMenus/fighterAbilities.py index 6f6bc8051..76eab5c1e 100644 --- a/gui/builtinContextMenus/fighterAbilities.py +++ b/gui/builtinContextMenus/fighterAbilities.py @@ -44,7 +44,7 @@ class FighterAbility(ContextMenu): if not ability.effect.isImplemented: continue menuItem = self.addAbility(rootMenu if msw else sub, ability) - sub.AppendItem(menuItem) + sub.Append(menuItem) menuItem.Check(ability.active) return sub diff --git a/gui/builtinContextMenus/implantSets.py b/gui/builtinContextMenus/implantSets.py index 0ef7cb2ee..1eb12d1c3 100644 --- a/gui/builtinContextMenus/implantSets.py +++ b/gui/builtinContextMenus/implantSets.py @@ -61,7 +61,7 @@ class ImplantSets(ContextMenu): mitem = wx.MenuItem(rootMenu, id, set.name) bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem) self.idmap[id] = set - m.AppendItem(mitem) + m.Append(mitem) return m diff --git a/gui/builtinContextMenus/itemStats.py b/gui/builtinContextMenus/itemStats.py index 4e1b8187e..6221fa1e0 100644 --- a/gui/builtinContextMenus/itemStats.py +++ b/gui/builtinContextMenus/itemStats.py @@ -47,7 +47,7 @@ class ItemStats(ContextMenu): mstate = wx.GetMouseState() reuse = False - if mstate.CmdDown(): + if mstate.cmdDown: reuse = True if self.mainFrame.GetActiveStatsWindow() is None and reuse: diff --git a/gui/builtinContextMenus/metaSwap.py b/gui/builtinContextMenus/metaSwap.py index e4432abdd..a5f6371e3 100644 --- a/gui/builtinContextMenus/metaSwap.py +++ b/gui/builtinContextMenus/metaSwap.py @@ -91,7 +91,7 @@ class MetaSwap(ContextMenu): # Sort items by metalevel, and group within that metalevel items = list(self.variations) - print context + print(context) if "implantItem" in context: # sort implants based on name items.sort(key=lambda x: x.name) @@ -116,14 +116,14 @@ class MetaSwap(ContextMenu): if thisgroup != group and context not in ("implantItem", "boosterItem"): group = thisgroup id = ContextMenu.nextID() - m.Append(id, u'─ %s ─' % group) + m.Append(id, '─ %s ─' % group) m.Enable(id, False) id = ContextMenu.nextID() mitem = wx.MenuItem(rootMenu, id, item.name) bindmenu.Bind(wx.EVT_MENU, self.handleModule, mitem) self.moduleLookup[id] = item - m.AppendItem(mitem) + m.Append(mitem) return m def handleModule(self, event): diff --git a/gui/builtinContextMenus/moduleAmmoPicker.py b/gui/builtinContextMenus/moduleAmmoPicker.py index 1af970190..47a6fc87d 100644 --- a/gui/builtinContextMenus/moduleAmmoPicker.py +++ b/gui/builtinContextMenus/moduleAmmoPicker.py @@ -9,7 +9,7 @@ from eos.saveddata.module import Hardpoint import gui.mainFrame import gui.globalEvents as GE from gui.contextMenu import ContextMenu -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.settings import ContextMenuSettings @@ -50,7 +50,7 @@ class ModuleAmmoPicker(ContextMenu): return False self.modules = modules - self.charges = list(filter(lambda charge: Market.getInstance().getPublicityByItem(charge), validCharges)) + self.charges = list([charge for charge in validCharges if Market.getInstance().getPublicityByItem(charge)]) return len(self.charges) > 0 def getText(self, itmContext, selection): @@ -58,9 +58,9 @@ class ModuleAmmoPicker(ContextMenu): def turretSorter(self, charge): damage = 0 - range_ = (self.module.getModifiedItemAttr("maxRange") or 0) * \ + range_ = (self.module.getModifiedItemAttr("maxRange")) * \ (charge.getAttribute("weaponRangeMultiplier") or 1) - falloff = (self.module.getModifiedItemAttr("falloff") or 0) * \ + falloff = (self.module.getModifiedItemAttr("falloff")) * \ (charge.getAttribute("fallofMultiplier") or 1) for type_ in self.DAMAGE_TYPES: d = charge.getAttribute("%sDamage" % type_) @@ -108,7 +108,7 @@ class ModuleAmmoPicker(ContextMenu): def nameSorter(self, charge): parts = charge.name.split(" ") - return map(self.numericConverter, parts) + return list(map(self.numericConverter, parts)) def addCharge(self, menu, charge): id_ = ContextMenu.nextID() @@ -127,7 +127,7 @@ class ModuleAmmoPicker(ContextMenu): @staticmethod def addSeperator(m, text): id_ = ContextMenu.nextID() - m.Append(id_, u'─ %s ─' % text) + m.Append(id_, '─ %s ─' % text) m.Enable(id_, False) def getSubMenu(self, context, selection, rootMenu, i, pitem): @@ -137,7 +137,7 @@ class ModuleAmmoPicker(ContextMenu): hardpoint = self.module.hardpoint moduleName = self.module.item.name # Make sure we do not consider mining turrets as combat turrets - if hardpoint == Hardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount") is None: + if hardpoint == Hardpoint.TURRET and self.module.getModifiedItemAttr("miningAmount", None) is None: self.addSeperator(m, "Long Range") items = [] range_ = None @@ -170,15 +170,15 @@ class ModuleAmmoPicker(ContextMenu): sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch) self.addSeperator(sub, "Less Damage") item.SetSubMenu(sub) - sub.AppendItem(self.addCharge(rootMenu if msw else sub, base)) + sub.Append(self.addCharge(rootMenu if msw else sub, base)) - sub.AppendItem(self.addCharge(rootMenu if msw else sub, charge)) + sub.Append(self.addCharge(rootMenu if msw else sub, charge)) if sub is not None: self.addSeperator(sub, "More Damage") for item in items: - m.AppendItem(item) + m.Append(item) self.addSeperator(m, "Short Range") elif hardpoint == Hardpoint.MISSILE and moduleName != 'Festival Launcher': @@ -203,23 +203,23 @@ class ModuleAmmoPicker(ContextMenu): sub.Bind(wx.EVT_MENU, self.handleAmmoSwitch) self.addSeperator(sub, "Less Damage") item.SetSubMenu(sub) - m.AppendItem(item) + m.Append(item) if charge.name not in ("Light Defender Missile I", "Heavy Defender Missile I"): - sub.AppendItem(self.addCharge(rootMenu if msw else sub, charge)) + sub.Append(self.addCharge(rootMenu if msw else sub, charge)) else: defender = charge if defender is not None: - m.AppendItem(self.addCharge(rootMenu if msw else m, defender)) + m.Append(self.addCharge(rootMenu if msw else m, defender)) if sub is not None: self.addSeperator(sub, "More Damage") else: self.charges.sort(key=self.nameSorter) for charge in self.charges: - m.AppendItem(self.addCharge(rootMenu if msw else m, charge)) + m.Append(self.addCharge(rootMenu if msw else m, charge)) - m.AppendItem(self.addCharge(rootMenu if msw else m, None)) + m.Append(self.addCharge(rootMenu if msw else m, None)) return m def handleAmmoSwitch(self, event): diff --git a/gui/builtinContextMenus/priceOptions.py b/gui/builtinContextMenus/priceOptions.py index 905ca9d4b..c9cb7f74a 100644 --- a/gui/builtinContextMenus/priceOptions.py +++ b/gui/builtinContextMenus/priceOptions.py @@ -35,7 +35,7 @@ class PriceOptions(ContextMenu): for option in self.optionList: menuItem = self.addOption(rootMenu if msw else sub, option) - sub.AppendItem(menuItem) + sub.Append(menuItem) menuItem.Check(self.settings.get(option.lower())) return sub diff --git a/gui/builtinContextMenus/tabbedFits.py b/gui/builtinContextMenus/tabbedFits.py index 30a7bee7a..c55a90b35 100644 --- a/gui/builtinContextMenus/tabbedFits.py +++ b/gui/builtinContextMenus/tabbedFits.py @@ -38,15 +38,15 @@ class TabbedFits(ContextMenu): else: bindmenu = m - for page in self.mainFrame.fitMultiSwitch.pages: + for page in self.mainFrame.fitMultiSwitch._pages: if isinstance(page, BlankPage): continue fit = sFit.getFit(page.activeFitID, basic=True) id = ContextMenu.nextID() - mitem = wx.MenuItem(rootMenu, id, u"{}: {}".format(fit.ship.item.name, fit.name)) + mitem = wx.MenuItem(rootMenu, id, "{}: {}".format(fit.ship.item.name, fit.name)) bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem) self.fitLookup[id] = fit - m.AppendItem(mitem) + m.Append(mitem) return m diff --git a/gui/builtinContextMenus/tacticalMode.py b/gui/builtinContextMenus/tacticalMode.py index 8b87dd10b..e09b26940 100644 --- a/gui/builtinContextMenus/tacticalMode.py +++ b/gui/builtinContextMenus/tacticalMode.py @@ -49,7 +49,7 @@ class TacticalMode(ContextMenu): for mode in self.modes: menuItem = self.addMode(rootMenu if msw else sub, mode) - sub.AppendItem(menuItem) + sub.Append(menuItem) menuItem.Check(self.currMode.item == mode.item) return sub diff --git a/gui/builtinContextMenus/targetResists.py b/gui/builtinContextMenus/targetResists.py index 95468ecd0..03478b8d2 100644 --- a/gui/builtinContextMenus/targetResists.py +++ b/gui/builtinContextMenus/targetResists.py @@ -3,15 +3,11 @@ import gui.mainFrame import gui.globalEvents as GE # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.targetResists import TargetResists as svc_TargetResists from service.fit import Fit from service.settings import ContextMenuSettings - -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict +from collections import OrderedDict class TargetResists(ContextMenu): @@ -87,15 +83,15 @@ class TargetResists(ContextMenu): else: self.singles.append(pattern) - sub.AppendItem(self.addPattern(rootMenu if msw else sub, None)) # Add reset + sub.Append(self.addPattern(rootMenu if msw else sub, None)) # Add reset sub.AppendSeparator() # Single items, no parent for pattern in self.singles: - sub.AppendItem(self.addPattern(rootMenu if msw else sub, pattern)) + sub.Append(self.addPattern(rootMenu if msw else sub, pattern)) # Items that have a parent - for menuName, patterns in self.subMenus.items(): + for menuName, patterns in list(self.subMenus.items()): # Create parent item for root menu that is simply name of parent item = wx.MenuItem(rootMenu, ContextMenu.nextID(), menuName) @@ -108,8 +104,8 @@ class TargetResists(ContextMenu): # Append child items to child menu for pattern in patterns: - grandSub.AppendItem(self.addPattern(rootMenu if msw else grandSub, pattern)) - sub.AppendItem(item) # finally, append parent item to root menu + grandSub.Append(self.addPattern(rootMenu if msw else grandSub, pattern)) + sub.Append(item) # finally, append parent item to root menu return sub diff --git a/gui/builtinContextMenus/whProjector.py b/gui/builtinContextMenus/whProjector.py index a677ceabf..bafca94ab 100644 --- a/gui/builtinContextMenus/whProjector.py +++ b/gui/builtinContextMenus/whProjector.py @@ -34,7 +34,7 @@ class WhProjector(ContextMenu): subItem = wx.MenuItem(sub, wx.ID_ANY, swType) grandSub = wx.Menu() subItem.SetSubMenu(grandSub) - sub.AppendItem(subItem) + sub.Append(subItem) for swData in sorted(effdata[swType], key=lambda tpl: tpl[2]): wxid = ContextMenu.nextID() @@ -45,7 +45,7 @@ class WhProjector(ContextMenu): rootMenu.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem) else: grandSub.Bind(wx.EVT_MENU, self.handleSelection, grandSubItem) - grandSub.AppendItem(grandSubItem) + grandSub.Append(grandSubItem) return sub def handleSelection(self, event): diff --git a/gui/builtinGraphs/fitDps.py b/gui/builtinGraphs/fitDps.py index ae03c180b..d848d3f9c 100644 --- a/gui/builtinGraphs/fitDps.py +++ b/gui/builtinGraphs/fitDps.py @@ -18,7 +18,7 @@ # ============================================================================= from gui.graph import Graph -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from eos.graph.fitDps import FitDpsGraph as FitDps from eos.graph import Data import gui.mainFrame @@ -54,7 +54,7 @@ class FitDpsGraph(Graph): def getIcons(self): icons = {} sAttr = Attribute.getInstance() - for key, attrName in self.propertyAttributeMap.iteritems(): + for key, attrName in self.propertyAttributeMap.items(): iconFile = sAttr.getAttributeInfo(attrName).icon.iconFile bitmap = BitmapLoader.getBitmap(iconFile, "icons") if bitmap: @@ -69,7 +69,7 @@ class FitDpsGraph(Graph): fitDps.clearData() variable = None - for fieldName, value in fields.iteritems(): + for fieldName, value in fields.items(): d = Data(fieldName, value) if not d.isConstant(): if variable is None: diff --git a/gui/builtinItemStatsViews/itemAffectedBy.py b/gui/builtinItemStatsViews/itemAffectedBy.py index b29d84b89..ed4280045 100644 --- a/gui/builtinItemStatsViews/itemAffectedBy.py +++ b/gui/builtinItemStatsViews/itemAffectedBy.py @@ -14,7 +14,7 @@ from eos.saveddata.fit import Fit import gui.mainFrame from gui.contextMenu import ContextMenu -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class ItemAffectedBy(wx.Panel): @@ -43,17 +43,17 @@ class ItemAffectedBy(wx.Panel): mainSizer.Add(self.m_staticline, 0, wx.EXPAND) bSizer = wx.BoxSizer(wx.HORIZONTAL) - self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0) + self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, "Expand All", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0) + self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, "Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0) + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, "Toggle View", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) if stuff is not None: - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) + self.refreshBtn = wx.Button(self, wx.ID_ANY, "Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree) @@ -74,9 +74,9 @@ class ItemAffectedBy(wx.Panel): def spawnMenu(self, item): self.affectedBy.SelectItem(item) - stuff = self.affectedBy.GetPyData(item) + stuff = self.affectedBy.GetItemData(item) # String is set as data when we are dealing with attributes, not stuff containers - if stuff is None or isinstance(stuff, basestring): + if stuff is None or isinstance(stuff, str): return contexts = [] @@ -109,10 +109,10 @@ class ItemAffectedBy(wx.Panel): self.Freeze() for item in self.treeItems: - change = self.affectedBy.GetPyData(item) + change = self.affectedBy.GetItemData(item) display = self.affectedBy.GetItemText(item) self.affectedBy.SetItemText(item, change) - self.affectedBy.SetPyData(item, display) + self.affectedBy.SetItemData(item, display) self.Thaw() @@ -141,7 +141,7 @@ class ItemAffectedBy(wx.Panel): # sheri was here del self.treeItems[:] root = self.affectedBy.AddRoot("WINPWNZ0R") - self.affectedBy.SetPyData(root, None) + self.affectedBy.SetItemData(root, None) self.imageList = wx.ImageList(16, 16) self.affectedBy.SetImageList(self.imageList) @@ -185,7 +185,7 @@ class ItemAffectedBy(wx.Panel): if attributes[attrName] == (attributes.getOriginal(attrName, 0)): continue - for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): + for fit, afflictors in attributes.getAfflictions(attrName).items(): for afflictor, modifier, amount, used in afflictors: if not used or afflictor.item is None: @@ -216,7 +216,7 @@ class ItemAffectedBy(wx.Panel): (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) # Make sure projected fits are on top - rootOrder = container.keys() + rootOrder = list(container.keys()) rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) # Now, we take our created dictionary and start adding stuff to our tree @@ -230,7 +230,7 @@ class ItemAffectedBy(wx.Panel): parent = child attributes = container[thing] - attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName) + attrOrder = sorted(list(attributes.keys()), key=self.sortAttrDisplayName) for attrName in attrOrder: attrInfo = self.stuff.item.attributes.get(attrName) @@ -257,7 +257,7 @@ class ItemAffectedBy(wx.Panel): # this is the attribute node child = self.affectedBy.AppendItem(parent, display, attrIcon) - self.affectedBy.SetPyData(child, saved) + self.affectedBy.SetItemData(child, saved) self.treeItems.append(child) items = attributes[attrName] @@ -284,12 +284,12 @@ class ItemAffectedBy(wx.Panel): penalized += "(penalized)" if 'r' in attrModifier: penalized += "(resisted)" - attrModifier = "*" + attrModifier = "*" # this is the Module node, the attribute will be attached to this display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized) treeItem = self.affectedBy.AppendItem(child, display, itemIcon) - self.affectedBy.SetPyData(treeItem, afflictor) + self.affectedBy.SetItemData(treeItem, afflictor) def buildModuleView(self, root): """ @@ -314,7 +314,7 @@ class ItemAffectedBy(wx.Panel): if attributes[attrName] == (attributes.getOriginal(attrName, 0)): continue - for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): + for fit, afflictors in attributes.getAfflictions(attrName).items(): for afflictor, modifier, amount, used in afflictors: if not used or getattr(afflictor, 'item', None) is None: continue @@ -350,7 +350,7 @@ class ItemAffectedBy(wx.Panel): info[2].append((attrName, modifier, amount)) # Make sure projected fits are on top - rootOrder = container.keys() + rootOrder = list(container.keys()) rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) # Now, we take our created dictionary and start adding stuff to our tree @@ -364,7 +364,7 @@ class ItemAffectedBy(wx.Panel): parent = child items = container[thing] - order = items.keys() + order = list(items.keys()) order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x)) for itemName in order: @@ -389,7 +389,7 @@ class ItemAffectedBy(wx.Panel): # this is the Module node, the attribute will be attached to this child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) - self.affectedBy.SetPyData(child, afflictors.pop()) + self.affectedBy.SetItemData(child, afflictors.pop()) if counter > 0: attributes = [] @@ -416,7 +416,7 @@ class ItemAffectedBy(wx.Panel): penalized += "(penalized)" if 'r' in attrModifier: penalized += "(resisted)" - attrModifier = "*" + attrModifier = "*" attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, attrAmount, penalized, attrIcon)) @@ -443,5 +443,5 @@ class ItemAffectedBy(wx.Panel): saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) treeitem = self.affectedBy.AppendItem(child, display, attrIcon) - self.affectedBy.SetPyData(treeitem, saved) + self.affectedBy.SetItemData(treeitem, saved) self.treeItems.append(treeitem) diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py index cbb8cf246..e40710c45 100644 --- a/gui/builtinItemStatsViews/itemAttributes.py +++ b/gui/builtinItemStatsViews/itemAttributes.py @@ -1,13 +1,12 @@ -import sys import csv import config # noinspection PyPackageRequirements import wx -from helpers import AutoListCtrl +from .helpers import AutoListCtrl -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.market import Market from service.attribute import Attribute from gui.utils.numberFormatter import formatAmount @@ -34,19 +33,19 @@ class ItemParams(wx.Panel): mainSizer.Add(self.m_staticline, 0, wx.EXPAND) bSizer = wx.BoxSizer(wx.HORIZONTAL) - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, " ", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, "Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, u"Export Item Stats", wx.DefaultPosition, wx.DefaultSize, + self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, "Export Item Stats", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL) if stuff is not None: - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) + self.refreshBtn = wx.Button(self, wx.ID_ANY, "Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) @@ -166,7 +165,7 @@ class ItemParams(wx.Panel): self.imageList = wx.ImageList(16, 16) self.paramList.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL) - names = list(self.attrValues.iterkeys()) + names = list(self.attrValues.keys()) names.sort() idNameMap = {} @@ -203,7 +202,7 @@ class ItemParams(wx.Panel): else: attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - index = self.paramList.InsertImageStringItem(sys.maxint, attrName, attrIcon) + index = self.paramList.InsertItem(self.paramList.GetItemCount(), attrName, attrIcon) idNameMap[idCount] = attrName self.paramList.SetItemData(index, idCount) idCount += 1 @@ -222,11 +221,12 @@ class ItemParams(wx.Panel): else: valueUnitDefault = formatAmount(valueDefault, 3, 0, 0) - self.paramList.SetStringItem(index, 1, valueUnit) + self.paramList.SetItem(index, 1, valueUnit) if self.stuff is not None: - self.paramList.SetStringItem(index, 2, valueUnitDefault) - - self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) + self.paramList.SetItem(index, 2, valueUnitDefault) + # @todo: pheonix, this lamda used cmp() which no longer exists in py3. Probably a better way to do this in the + # long run, take a look + self.paramList.SortItems(lambda id1, id2: (idNameMap[id1] > idNameMap[id2]) - (idNameMap[id1] < idNameMap[id2])) self.paramList.RefreshRows() self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) self.Layout() @@ -252,7 +252,7 @@ class ItemParams(wx.Panel): "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), "Modifier Percent" : ( lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), - "Volume" : (lambda: value, u"m\u00B3"), + "Volume" : (lambda: value, "m\u00B3"), "Sizeclass" : (lambda: value, ""), "Absolute Percent" : (lambda: (value * 100), unitName), "Milliseconds" : (lambda: value / 1000.0, unitName), @@ -266,7 +266,7 @@ class ItemParams(wx.Panel): v = override[0]() if isinstance(v, str): fvalue = v - elif isinstance(v, (int, float, long)): + elif isinstance(v, (int, float)): fvalue = formatAmount(v, 3, 0, 0) else: fvalue = v diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py index 4e560883e..9e9ca53af 100644 --- a/gui/builtinItemStatsViews/itemCompare.py +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -1,9 +1,7 @@ -import sys - # noinspection PyPackageRequirements import wx -from helpers import AutoListCtrl +from .helpers import AutoListCtrl from service.price import Price as ServicePrice from service.market import Market from service.attribute import Attribute @@ -30,17 +28,17 @@ class ItemCompare(wx.Panel): self.sortReverse = False self.item = item self.items = sorted(items, - key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else None) + key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else 0) self.attrs = {} # get a dict of attrName: attrInfo of all unique attributes across all items for item in self.items: - for attr in item.attributes.keys(): + for attr in list(item.attributes.keys()): if item.attributes[attr].info.displayName: self.attrs[attr] = item.attributes[attr].info # Process attributes for items and find ones that differ - for attr in self.attrs.keys(): + for attr in list(self.attrs.keys()): value = None for item in self.items: @@ -65,14 +63,14 @@ class ItemCompare(wx.Panel): mainSizer.Add(self.m_staticline, 0, wx.EXPAND) bSizer = wx.BoxSizer(wx.HORIZONTAL) - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, " ", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, "Toggle view mode", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, + self.refreshBtn = wx.Button(self, wx.ID_ANY, "Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) @@ -109,7 +107,7 @@ class ItemCompare(wx.Panel): def processPrices(self, prices): for i, price in enumerate(prices): - self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True)) + self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True)) def PopulateList(self, sort=None): @@ -126,7 +124,7 @@ class ItemCompare(wx.Panel): try: # Remember to reduce by 1, because the attrs array # starts at 0 while the list has the item name as column 0. - attr = str(self.attrs.keys()[sort - 1]) + attr = str(list(self.attrs.keys())[sort - 1]) func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None except IndexError: # Clicked on a column that's not part of our array (price most likely) @@ -147,7 +145,7 @@ class ItemCompare(wx.Panel): self.paramList.SetColumnWidth(len(self.attrs) + 1, 60) for item in self.items: - i = self.paramList.InsertStringItem(sys.maxint, item.name) + i = self.paramList.InsertItem(self.paramList.GetItemCount(), item.name) for x, attr in enumerate(self.attrs.keys()): if attr in item.attributes: info = self.attrs[attr] @@ -159,10 +157,10 @@ class ItemCompare(wx.Panel): else: valueUnit = formatAmount(value, 3, 0, 0) - self.paramList.SetStringItem(i, x + 1, valueUnit) + self.paramList.SetItem(i, x + 1, valueUnit) # Add prices - self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True)) + self.paramList.SetItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True)) self.paramList.RefreshRows() self.Layout() @@ -185,7 +183,7 @@ class ItemCompare(wx.Panel): "Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName), "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), "Modifier Percent" : (lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), - "Volume" : (lambda: value, u"m\u00B3"), + "Volume" : (lambda: value, "m\u00B3"), "Sizeclass" : (lambda: value, ""), "Absolute Percent" : (lambda: (value * 100), unitName), "Milliseconds" : (lambda: value / 1000.0, unitName), @@ -199,7 +197,7 @@ class ItemCompare(wx.Panel): v = override[0]() if isinstance(v, str): fvalue = v - elif isinstance(v, (int, float, long)): + elif isinstance(v, (int, float)): fvalue = formatAmount(v, 3, 0, 0) else: fvalue = v diff --git a/gui/builtinItemStatsViews/itemDependants.py b/gui/builtinItemStatsViews/itemDependants.py index 3a8110b55..9f7e459c9 100644 --- a/gui/builtinItemStatsViews/itemDependants.py +++ b/gui/builtinItemStatsViews/itemDependants.py @@ -1,7 +1,7 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class ItemDependents(wx.Panel): @@ -19,7 +19,7 @@ class ItemDependents(wx.Panel): self.SetSizer(mainSizer) self.root = self.reqTree.AddRoot("WINRARZOR") - self.reqTree.SetPyData(self.root, None) + self.reqTree.SetItemData(self.root, None) self.imageList = wx.ImageList(16, 16) self.reqTree.SetImageList(self.imageList) @@ -32,7 +32,7 @@ class ItemDependents(wx.Panel): def getFullSkillTree(self, parentSkill, parent, sbIconId): levelToItems = {} - for item, level in parentSkill.requiredFor.iteritems(): + for item, level in parentSkill.requiredFor.items(): if level not in levelToItems: levelToItems[level] = [] levelToItems[level].append(item) diff --git a/gui/builtinItemStatsViews/itemDescription.py b/gui/builtinItemStatsViews/itemDescription.py index c94bd2b66..ddf30dfb4 100644 --- a/gui/builtinItemStatsViews/itemDescription.py +++ b/gui/builtinItemStatsViews/itemDescription.py @@ -11,8 +11,8 @@ class ItemDescription(wx.Panel): mainSizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(mainSizer) - bgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - fgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + bgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + fgcolor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) self.description = wx.html.HtmlWindow(self) diff --git a/gui/builtinItemStatsViews/itemEffects.py b/gui/builtinItemStatsViews/itemEffects.py index 81617fdc6..e586786a6 100644 --- a/gui/builtinItemStatsViews/itemEffects.py +++ b/gui/builtinItemStatsViews/itemEffects.py @@ -1,4 +1,3 @@ -import sys import os import subprocess import config @@ -6,7 +5,7 @@ import config # noinspection PyPackageRequirements import wx -from helpers import AutoListCtrl +from .helpers import AutoListCtrl class ItemEffects(wx.Panel): @@ -47,11 +46,11 @@ class ItemEffects(wx.Panel): item = self.item effects = item.effects - names = list(effects.iterkeys()) + names = list(effects.keys()) names.sort() for name in names: - index = self.effectList.InsertStringItem(sys.maxint, name) + index = self.effectList.InsertItem(self.effectList.GetItemCount(), name) if effects[name].isImplemented: if effects[name].activeByDefault: @@ -72,11 +71,11 @@ class ItemEffects(wx.Panel): else: effectRunTime = "" - self.effectList.SetStringItem(index, 1, activeByDefault) - self.effectList.SetStringItem(index, 2, effectTypeText) + self.effectList.SetItem(index, 1, activeByDefault) + self.effectList.SetItem(index, 2, effectTypeText) if config.debug: - self.effectList.SetStringItem(index, 3, effectRunTime) - self.effectList.SetStringItem(index, 4, str(effects[name].ID)) + self.effectList.SetItem(index, 3, effectRunTime) + self.effectList.SetItem(index, 4, str(effects[name].ID)) self.effectList.RefreshRows() self.Layout() diff --git a/gui/builtinItemStatsViews/itemProperties.py b/gui/builtinItemStatsViews/itemProperties.py index b8ce86dd5..afc8c2645 100644 --- a/gui/builtinItemStatsViews/itemProperties.py +++ b/gui/builtinItemStatsViews/itemProperties.py @@ -1,9 +1,7 @@ -import sys - # noinspection PyPackageRequirements import wx -from helpers import AutoListCtrl +from .helpers import AutoListCtrl class ItemProperties(wx.Panel): @@ -27,7 +25,7 @@ class ItemProperties(wx.Panel): mainSizer.Add(self.m_staticline, 0, wx.EXPAND) bSizer = wx.BoxSizer(wx.HORIZONTAL) - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, " ", wx.DefaultPosition, wx.DefaultSize, 0) bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) @@ -79,7 +77,7 @@ class ItemProperties(wx.Panel): attrName = name.title() value = getattr(self.item, name) - index = self.paramList.InsertStringItem(sys.maxint, attrName) + index = self.paramList.InsertItem(self.paramList.GetItemCount(), attrName) # index = self.paramList.InsertImageStringItem(sys.maxint, attrName) idNameMap[idCount] = attrName self.paramList.SetItemData(index, idCount) @@ -87,13 +85,13 @@ class ItemProperties(wx.Panel): valueUnit = str(value) - self.paramList.SetStringItem(index, 1, valueUnit) + self.paramList.SetItem(index, 1, valueUnit) except: # TODO: Add logging to this. # We couldn't get a property for some reason. Skip it for now. continue - self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) + self.paramList.SortItems(lambda id1, id2: (idNameMap[id1] > idNameMap[id2]) - (idNameMap[id1] < idNameMap[id2])) self.paramList.RefreshRows() self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) self.Layout() diff --git a/gui/builtinItemStatsViews/itemRequirements.py b/gui/builtinItemStatsViews/itemRequirements.py index ff493bcab..dc3c947cd 100644 --- a/gui/builtinItemStatsViews/itemRequirements.py +++ b/gui/builtinItemStatsViews/itemRequirements.py @@ -1,7 +1,7 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class ItemRequirements(wx.Panel): @@ -19,7 +19,7 @@ class ItemRequirements(wx.Panel): self.SetSizer(mainSizer) self.root = self.reqTree.AddRoot("WINRARZOR") - self.reqTree.SetPyData(self.root, None) + self.reqTree.SetItemData(self.root, None) self.imageList = wx.ImageList(16, 16) self.reqTree.SetImageList(self.imageList) @@ -32,7 +32,7 @@ class ItemRequirements(wx.Panel): self.Layout() def getFullSkillTree(self, parentSkill, parent, sbIconId): - for skill, level in parentSkill.requiredSkills.iteritems(): + for skill, level in parentSkill.requiredSkills.items(): child = self.reqTree.AppendItem(parent, "%s %s" % (skill.name, self.romanNb[int(level)]), sbIconId) if skill.ID not in self.skillIdHistory: self.getFullSkillTree(skill, child, sbIconId) diff --git a/gui/builtinMarketBrowser/itemView.py b/gui/builtinMarketBrowser/itemView.py index 3c971a699..7a26cfed1 100644 --- a/gui/builtinMarketBrowser/itemView.py +++ b/gui/builtinMarketBrowser/itemView.py @@ -1,5 +1,6 @@ import wx +import config import gui.builtinMarketBrowser.pfSearchBox as SBox from gui.contextMenu import ContextMenu from gui.display import Display @@ -9,7 +10,7 @@ from gui.utils.staticHelpers import DragDropHelper from logbook import Logger -import events +from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES, MAX_RECENTLY_USED_MODULES, ItemSelected pyfalog = Logger(__name__) @@ -66,7 +67,7 @@ class ItemView(Display): row = self.GetFirstSelected() if row != -1: - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "market:" + str(self.active[row].ID) pyfalog.debug("Dragging from market: " + dataStr) @@ -89,10 +90,10 @@ class ItemView(Display): for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) - wx.PostEvent(self.mainFrame, events.ItemSelected(itemID=self.active[sel].ID)) + wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) def storeRecentlyUsedMarketItem(self, itemID): - if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > events.MAX_RECENTLY_USED_MODULES: + if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES: self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0) self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID) @@ -103,8 +104,8 @@ class ItemView(Display): sel = self.marketView.GetSelection() if sel.IsOk(): # Get data field of the selected item (which is a marketGroup ID if anything was selected) - seldata = self.marketView.GetPyData(sel) - if seldata is not None and seldata != events.RECENTLY_USED_MODULES: + seldata = self.marketView.GetItemData(sel) + if seldata is not None and seldata != RECENTLY_USED_MODULES: # If market group treeview item doesn't have children (other market groups or dummies), # then it should have items in it and we want to request them if self.marketView.ItemHasChildren(sel) is False: @@ -117,7 +118,7 @@ class ItemView(Display): items = set() else: # If method was called but selection wasn't actually made or we have a hit on recently used modules - if seldata == events.RECENTLY_USED_MODULES: + if seldata == RECENTLY_USED_MODULES: items = self.recentlyUsedModules else: items = set() @@ -126,7 +127,7 @@ class ItemView(Display): self.updateItemStore(items) # Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered) - if seldata is not events.RECENTLY_USED_MODULES: + if seldata is not RECENTLY_USED_MODULES: self.setToggles() else: self.marketBrowser.searchMode = True @@ -170,10 +171,6 @@ class ItemView(Display): if len(realsearch) == 0: self.selectionMade() return - # Show nothing if query is too short - elif len(realsearch) < 3: - self.clearSearch() - return self.marketBrowser.searchMode = True self.sMkt.searchItems(search, self.populateSearch) @@ -204,13 +201,14 @@ class ItemView(Display): try: mktgrpid = sMkt.getMarketGroupByItem(item).ID except AttributeError: - mktgrpid = None - print("unable to find market group for", item.name) + mktgrpid = -1 + print(("unable to find market group for", item.name)) parentname = sMkt.getParentItemByItem(item).name # Get position of market group metagrpid = sMkt.getMetaGroupIdByItem(item) metatab = self.metaMap.get(metagrpid) metalvl = self.metalvls.get(item.ID, 0) + return catname, mktgrpid, parentname, metatab, metalvl, item.name def contextMenu(self, event): @@ -266,7 +264,7 @@ class ItemView(Display): """ revmap = {} i = 0 - for mgids in self.sMkt.META_MAP.itervalues(): + for mgids in self.sMkt.META_MAP.values(): for mgid in mgids: revmap[mgid] = i i += 1 diff --git a/gui/builtinMarketBrowser/marketTree.py b/gui/builtinMarketBrowser/marketTree.py index c55c7e3b5..8b1286e50 100644 --- a/gui/builtinMarketBrowser/marketTree.py +++ b/gui/builtinMarketBrowser/marketTree.py @@ -1,7 +1,7 @@ import wx from gui.cachingImageList import CachingImageList -import gui.builtinMarketBrowser.events as events +from gui.builtinMarketBrowser.events import RECENTLY_USED_MODULES from logbook import Logger @@ -24,7 +24,7 @@ class MarketTree(wx.TreeCtrl): sMkt = self.sMkt for mktGrp in sMkt.getMarketRoot(): iconId = self.addImage(sMkt.getIconByMarketGroup(mktGrp)) - childId = self.AppendItem(self.root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) + childId = self.AppendItem(self.root, mktGrp.name, iconId, data=mktGrp.ID) # All market groups which were never expanded are dummies, here we assume # that all root market groups are expandable self.AppendItem(childId, "dummy") @@ -32,7 +32,7 @@ class MarketTree(wx.TreeCtrl): # Add recently used modules node rumIconId = self.addImage("market_small", "gui") - self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=wx.TreeItemData(events.RECENTLY_USED_MODULES)) + self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=RECENTLY_USED_MODULES) # Bind our lookup method to when the tree gets expanded self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) @@ -52,14 +52,14 @@ class MarketTree(wx.TreeCtrl): self.Delete(child) # And add real market group contents sMkt = self.sMkt - currentMktGrp = sMkt.getMarketGroup(self.GetPyData(root), eager="children") + currentMktGrp = sMkt.getMarketGroup(self.GetItemData(root), eager="children") for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): # If market should have items but it doesn't, do not show it if sMkt.marketGroupValidityCheck(childMktGrp) is False: continue iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) try: - childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) + childId = self.AppendItem(root, childMktGrp.name, iconId, data=childMktGrp.ID) except Exception as e: pyfalog.debug("Error appending item.") pyfalog.debug(e) @@ -88,7 +88,7 @@ class MarketTree(wx.TreeCtrl): for i in range(len(jumpList) - 1, -1, -1): target = jumpList[i] child, cookie = self.GetFirstChild(item) - while self.GetItemPyData(child) != target: + while self.GetItemData(child) != target: child, cookie = self.GetNextChild(item, cookie) item = child diff --git a/gui/builtinMarketBrowser/pfSearchBox.py b/gui/builtinMarketBrowser/pfSearchBox.py index f5f8d8c6d..8d0871f6a 100644 --- a/gui/builtinMarketBrowser/pfSearchBox.py +++ b/gui/builtinMarketBrowser/pfSearchBox.py @@ -1,7 +1,7 @@ # noinspection PyPackageRequirements import wx -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils SearchButton, EVT_SEARCH_BTN = wx.lib.newevent.NewEvent() CancelButton, EVT_CANCEL_BTN = wx.lib.newevent.NewEvent() @@ -59,6 +59,8 @@ class PFSearchBox(wx.Window): self.EditBox.Bind(wx.EVT_TEXT, self.OnText) self.EditBox.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + self.SetMinSize(size) def OnText(self, event): @@ -224,10 +226,10 @@ class PFSearchBox(wx.Window): self.EditBox.SetSize((self.cancelButtonX - self.padding - self.editX, -1)) def OnPaint(self, event): - dc = wx.BufferedPaintDC(self) + dc = wx.AutoBufferedPaintDC(self) - bkColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - sepColor = colorUtils.GetSuitableColor(bkColor, 0.2) + bkColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + sepColor = colorUtils.GetSuitable(bkColor, 0.2) rect = self.GetRect() if self.resized: diff --git a/gui/builtinMarketBrowser/searchBox.py b/gui/builtinMarketBrowser/searchBox.py index 4132f451b..99bfa2983 100644 --- a/gui/builtinMarketBrowser/searchBox.py +++ b/gui/builtinMarketBrowser/searchBox.py @@ -1,5 +1,5 @@ -from gui.bitmapLoader import BitmapLoader -from pfSearchBox import PFSearchBox +from gui.bitmap_loader import BitmapLoader +from .pfSearchBox import PFSearchBox class SearchBox(PFSearchBox): diff --git a/gui/builtinPreferenceViews/__init__.py b/gui/builtinPreferenceViews/__init__.py index 8b61beb13..32117a9ec 100644 --- a/gui/builtinPreferenceViews/__init__.py +++ b/gui/builtinPreferenceViews/__init__.py @@ -1,6 +1,3 @@ -# noinspection PyPackageRequirements -import wx - __all__ = [ "pyfaGeneralPreferences", "pyfaHTMLExportPreferences", @@ -9,8 +6,6 @@ __all__ = [ "pyfaDatabasePreferences", "pyfaLoggingPreferences", "pyfaEnginePreferences", - "pyfaStatViewPreferences", -] + "pyfaEsiPreferences", + "pyfaStatViewPreferences"] -if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - __all__.append("pyfaCrestPreferences") diff --git a/gui/builtinPreferenceViews/dummyView.py b/gui/builtinPreferenceViews/dummyView.py index 5bded0d05..959057f11 100644 --- a/gui/builtinPreferenceViews/dummyView.py +++ b/gui/builtinPreferenceViews/dummyView.py @@ -50,7 +50,7 @@ class DummyView(PreferenceView): def initHeader(self, panel): headerSizer = wx.BoxSizer(wx.VERTICAL) - self.stTitle = wx.StaticText(panel, wx.ID_ANY, u"Dummy", wx.DefaultPosition, wx.DefaultSize, 0) + self.stTitle = wx.StaticText(panel, wx.ID_ANY, "Dummy", wx.DefaultPosition, wx.DefaultSize, 0) self.stTitle.Wrap(-1) self.stTitle.SetFont(wx.Font(14, 70, 90, 90, False, wx.EmptyString)) headerSizer.Add(self.stTitle, 0, wx.ALL, 5) @@ -60,10 +60,10 @@ class DummyView(PreferenceView): def initContent(self, panel): contentSizer = wx.BoxSizer(wx.VERTICAL) - self.m_checkBox2 = wx.CheckBox(panel, wx.ID_ANY, u"Check Me!", wx.DefaultPosition, wx.DefaultSize, 0) + self.m_checkBox2 = wx.CheckBox(panel, wx.ID_ANY, "Check Me!", wx.DefaultPosition, wx.DefaultSize, 0) contentSizer.Add(self.m_checkBox2, 0, wx.ALL, 5) - self.m_radioBtn2 = wx.RadioButton(panel, wx.ID_ANY, u"RadioBtn", wx.DefaultPosition, wx.DefaultSize, 0) + self.m_radioBtn2 = wx.RadioButton(panel, wx.ID_ANY, "RadioBtn", wx.DefaultPosition, wx.DefaultSize, 0) contentSizer.Add(self.m_radioBtn2, 0, wx.ALL, 5) self.m_slider2 = wx.Slider(panel, wx.ID_ANY, 50, 0, 100, wx.DefaultPosition, wx.DefaultSize, wx.SL_HORIZONTAL) @@ -80,14 +80,13 @@ class DummyView(PreferenceView): def initFooter(self, panel): footerSizer = wx.BoxSizer(wx.HORIZONTAL) - footerSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) - - self.btnRestore = wx.Button(panel, wx.ID_ANY, u"Restore", wx.DefaultPosition, wx.DefaultSize, 0) + footerSizer.AddStretchSpacer() + self.btnRestore = wx.Button(panel, wx.ID_ANY, "Restore", wx.DefaultPosition, wx.DefaultSize, 0) self.btnRestore.Enable(False) footerSizer.Add(self.btnRestore, 0, wx.ALL, 5) - self.btnApply = wx.Button(panel, wx.ID_ANY, u"Apply", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnApply = wx.Button(panel, wx.ID_ANY, "Apply", wx.DefaultPosition, wx.DefaultSize, 0) footerSizer.Add(self.btnApply, 0, wx.ALL, 5) return footerSizer diff --git a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py index 58ed81556..458424dce 100644 --- a/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py +++ b/gui/builtinPreferenceViews/pyfaContextMenuPreferences.py @@ -1,7 +1,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import gui.mainFrame from service.settings import ContextMenuSettings @@ -23,7 +23,7 @@ class PFContextMenuPref(PreferenceView): mainSizer.Add(self.stTitle, 0, wx.ALL, 5) self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, - u"Disabling context menus can improve responsiveness.", + "Disabling context menus can improve responsiveness.", wx.DefaultPosition, wx.DefaultSize, 0) self.stSubTitle.Wrap(-1) mainSizer.Add(self.stSubTitle, 0, wx.ALL, 5) diff --git a/gui/builtinPreferenceViews/pyfaCrestPreferences.py b/gui/builtinPreferenceViews/pyfaCrestPreferences.py deleted file mode 100644 index 4a9208d0a..000000000 --- a/gui/builtinPreferenceViews/pyfaCrestPreferences.py +++ /dev/null @@ -1,151 +0,0 @@ -# noinspection PyPackageRequirements -import wx - -from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader - -import gui.mainFrame - -from service.settings import CRESTSettings - -# noinspection PyPackageRequirements -from wx.lib.intctrl import IntCtrl - -if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - from service.crest import Crest - - -class PFCrestPref(PreferenceView): - title = "CREST" - - def populatePanel(self, panel): - - self.mainFrame = gui.mainFrame.MainFrame.getInstance() - self.settings = CRESTSettings.getInstance() - self.dirtySettings = False - dlgWidth = panel.GetParent().GetParent().ClientSize.width - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0) - self.stTitle.Wrap(-1) - self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) - - mainSizer.Add(self.stTitle, 0, wx.ALL, 5) - - self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) - - self.stInfo = wx.StaticText(panel, wx.ID_ANY, - u"Please see the pyfa wiki on GitHub for information regarding these options.", - wx.DefaultPosition, wx.DefaultSize, 0) - self.stInfo.Wrap(dlgWidth - 50) - mainSizer.Add(self.stInfo, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) - - rbSizer = wx.BoxSizer(wx.HORIZONTAL) - self.rbMode = wx.RadioBox(panel, -1, "Mode", wx.DefaultPosition, wx.DefaultSize, - ['Implicit', 'User-supplied details'], 1, wx.RA_SPECIFY_COLS) - self.rbServer = wx.RadioBox(panel, -1, "Server", wx.DefaultPosition, wx.DefaultSize, - ['Tranquility', 'Singularity'], 1, wx.RA_SPECIFY_COLS) - - self.rbMode.SetSelection(self.settings.get('mode')) - self.rbServer.SetSelection(self.settings.get('server')) - - rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5) - rbSizer.Add(self.rbServer, 1, wx.ALL, 5) - - self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange) - self.rbServer.Bind(wx.EVT_RADIOBOX, self.OnServerChange) - - mainSizer.Add(rbSizer, 1, wx.ALL | wx.EXPAND, 0) - - timeoutSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.stTimout = wx.StaticText(panel, wx.ID_ANY, u"Timeout (seconds):", wx.DefaultPosition, wx.DefaultSize, 0) - self.stTimout.Wrap(-1) - - timeoutSizer.Add(self.stTimout, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - - self.intTimeout = IntCtrl(panel, max=300000, limited=True, value=self.settings.get('timeout')) - timeoutSizer.Add(self.intTimeout, 0, wx.ALL, 5) - self.intTimeout.Bind(wx.lib.intctrl.EVT_INT, self.OnTimeoutChange) - - mainSizer.Add(timeoutSizer, 0, wx.ALL | wx.EXPAND, 0) - - detailsTitle = wx.StaticText(panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0) - detailsTitle.Wrap(-1) - detailsTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) - - mainSizer.Add(detailsTitle, 0, wx.ALL, 5) - mainSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, - wx.EXPAND, 5) - - fgAddrSizer = wx.FlexGridSizer(2, 2, 0, 0) - fgAddrSizer.AddGrowableCol(1) - fgAddrSizer.SetFlexibleDirection(wx.BOTH) - fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) - - self.stSetID = wx.StaticText(panel, wx.ID_ANY, u"Client ID:", wx.DefaultPosition, wx.DefaultSize, 0) - self.stSetID.Wrap(-1) - fgAddrSizer.Add(self.stSetID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - - self.inputClientID = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition, - wx.DefaultSize, 0) - - fgAddrSizer.Add(self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) - - self.stSetSecret = wx.StaticText(panel, wx.ID_ANY, u"Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0) - self.stSetSecret.Wrap(-1) - - fgAddrSizer.Add(self.stSetSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - - self.inputClientSecret = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition, - wx.DefaultSize, 0) - - fgAddrSizer.Add(self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) - - self.btnApply = wx.Button(panel, wx.ID_ANY, u"Save Client Settings", wx.DefaultPosition, wx.DefaultSize, 0) - self.btnApply.Bind(wx.EVT_BUTTON, self.OnBtnApply) - - mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5) - mainSizer.Add(self.btnApply, 0, wx.ALIGN_RIGHT, 5) - - self.ToggleProxySettings(self.settings.get('mode')) - - panel.SetSizer(mainSizer) - panel.Layout() - - def OnTimeoutChange(self, event): - self.settings.set('timeout', event.GetEventObject().GetValue()) - - def OnModeChange(self, event): - self.settings.set('mode', event.GetInt()) - self.ToggleProxySettings(self.settings.get('mode')) - Crest.restartService() - - def OnServerChange(self, event): - self.settings.set('server', event.GetInt()) - Crest.restartService() - - def OnBtnApply(self, event): - self.settings.set('clientID', self.inputClientID.GetValue().strip()) - self.settings.set('clientSecret', self.inputClientSecret.GetValue().strip()) - sCrest = Crest.getInstance() - sCrest.delAllCharacters() - - def ToggleProxySettings(self, mode): - if mode: - self.stSetID.Enable() - self.inputClientID.Enable() - self.stSetSecret.Enable() - self.inputClientSecret.Enable() - else: - self.stSetID.Disable() - self.inputClientID.Disable() - self.stSetSecret.Disable() - self.inputClientSecret.Disable() - - def getImage(self): - return BitmapLoader.getBitmap("eve", "gui") - - -PFCrestPref.register() diff --git a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py index ab93d4655..5ecdf9570 100644 --- a/gui/builtinPreferenceViews/pyfaDatabasePreferences.py +++ b/gui/builtinPreferenceViews/pyfaDatabasePreferences.py @@ -1,7 +1,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils import helpers_wxPython as wxHelpers import config from eos.db.saveddata.queries import clearPrices, clearDamagePatterns, clearTargetResists @@ -24,7 +24,7 @@ class PFGeneralPref(PreferenceView): self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) mainSizer.Add(self.stTitle, 0, wx.ALL, 5) - self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, u"(Cannot be changed while pyfa is running. Set via command line switches.)", + self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, "(Cannot be changed while pyfa is running. Set via command line switches.)", wx.DefaultPosition, wx.DefaultSize, 0) self.stSubTitle.Wrap(-1) mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3) @@ -33,11 +33,11 @@ class PFGeneralPref(PreferenceView): mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) # Save in Root - self.cbsaveInRoot = wx.CheckBox(panel, wx.ID_ANY, u"Using Executable Path for Saved Fit Database and Settings", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbsaveInRoot = wx.CheckBox(panel, wx.ID_ANY, "Using Executable Path for Saved Fit Database and Settings", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbsaveInRoot, 0, wx.ALL | wx.EXPAND, 5) # Database path - self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, u"pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stSetUserPath = wx.StaticText(panel, wx.ID_ANY, "pyfa User Path:", wx.DefaultPosition, wx.DefaultSize, 0) self.stSetUserPath.Wrap(-1) mainSizer.Add(self.stSetUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) self.inputUserPath = wx.TextCtrl(panel, wx.ID_ANY, config.savePath, wx.DefaultPosition, wx.DefaultSize, 0) @@ -46,7 +46,7 @@ class PFGeneralPref(PreferenceView): mainSizer.Add(self.inputUserPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) # Save DB - self.stFitDB = wx.StaticText(panel, wx.ID_ANY, u"Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stFitDB = wx.StaticText(panel, wx.ID_ANY, "Fitting Database:", wx.DefaultPosition, wx.DefaultSize, 0) self.stFitDB.Wrap(-1) mainSizer.Add(self.stFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -56,7 +56,7 @@ class PFGeneralPref(PreferenceView): mainSizer.Add(self.inputFitDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) # Game Data DB - self.stGameDB = wx.StaticText(panel, wx.ID_ANY, u"Game Database:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stGameDB = wx.StaticText(panel, wx.ID_ANY, "Game Database:", wx.DefaultPosition, wx.DefaultSize, 0) self.stGameDB.Wrap(-1) mainSizer.Add(self.stGameDB, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -76,15 +76,15 @@ class PFGeneralPref(PreferenceView): mainSizer.Add(self.m_staticline3, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) btnSizer = wx.BoxSizer(wx.VERTICAL) - btnSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + btnSizer.AddStretchSpacer() - self.btnDeleteDamagePatterns = wx.Button(panel, wx.ID_ANY, u"Delete All Damage Pattern Profiles", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnDeleteDamagePatterns = wx.Button(panel, wx.ID_ANY, "Delete All Damage Pattern Profiles", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.btnDeleteDamagePatterns, 0, wx.ALL, 5) - self.btnDeleteTargetResists = wx.Button(panel, wx.ID_ANY, u"Delete All Target Resist Profiles", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnDeleteTargetResists = wx.Button(panel, wx.ID_ANY, "Delete All Target Resist Profiles", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.btnDeleteTargetResists, 0, wx.ALL, 5) - self.btnPrices = wx.Button(panel, wx.ID_ANY, u"Delete All Prices", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnPrices = wx.Button(panel, wx.ID_ANY, "Delete All Prices", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.btnPrices, 0, wx.ALL, 5) mainSizer.Add(btnSizer, 0, wx.EXPAND, 5) @@ -97,17 +97,17 @@ class PFGeneralPref(PreferenceView): panel.Layout() def DeleteDamagePatterns(self, event): - question = u"This is a destructive action that will delete all damage pattern profiles.\nAre you sure you want to do this?" + question = "This is a destructive action that will delete all damage pattern profiles.\nAre you sure you want to do this?" if wxHelpers.YesNoDialog(question, "Confirm"): clearDamagePatterns() def DeleteTargetResists(self, event): - question = u"This is a destructive action that will delete all target resist profiles.\nAre you sure you want to do this?" + question = "This is a destructive action that will delete all target resist profiles.\nAre you sure you want to do this?" if wxHelpers.YesNoDialog(question, "Confirm"): clearTargetResists() def DeletePrices(self, event): - question = u"This is a destructive action that will delete all cached prices out of the database.\nAre you sure you want to do this?" + question = "This is a destructive action that will delete all cached prices out of the database.\nAre you sure you want to do this?" if wxHelpers.YesNoDialog(question, "Confirm"): clearPrices() diff --git a/gui/builtinPreferenceViews/pyfaEnginePreferences.py b/gui/builtinPreferenceViews/pyfaEnginePreferences.py index f9fba7612..815e1b124 100644 --- a/gui/builtinPreferenceViews/pyfaEnginePreferences.py +++ b/gui/builtinPreferenceViews/pyfaEnginePreferences.py @@ -3,7 +3,7 @@ import logging import wx from service.fit import Fit -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.preferenceView import PreferenceView from service.settings import EOSSettings @@ -24,7 +24,7 @@ class PFFittingEnginePref(PreferenceView): mainSizer = wx.BoxSizer(wx.VERTICAL) - helpCursor = wx.StockCursor(wx.CURSOR_QUESTION_ARROW) + helpCursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW) self.engine_settings = EOSSettings.getInstance() @@ -36,25 +36,25 @@ class PFFittingEnginePref(PreferenceView): self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) - self.cbGlobalForceReload = wx.CheckBox(panel, wx.ID_ANY, u"Factor in reload time when calculating capacitor usage, damage, and tank.", + self.cbGlobalForceReload = wx.CheckBox(panel, wx.ID_ANY, "Factor in reload time when calculating capacitor usage, damage, and tank.", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbGlobalForceReload, 0, wx.ALL | wx.EXPAND, 5) self.cbStrictSkillLevels = wx.CheckBox(panel, wx.ID_ANY, - u"Enforce strict skill level requirements", + "Enforce strict skill level requirements", wx.DefaultPosition, wx.DefaultSize, 0) self.cbStrictSkillLevels.SetCursor(helpCursor) self.cbStrictSkillLevels.SetToolTip(wx.ToolTip( - u'When enabled, skills will check their dependencies\' requirements when their levels change and reset ' + - u'skills that no longer meet the requirement.\neg: Setting Drones from level V to IV will reset the Heavy ' + - u'Drone Operation skill, as that requires Drones V')) + 'When enabled, skills will check their dependencies\' requirements when their levels change and reset ' + + 'skills that no longer meet the requirement.\neg: Setting Drones from level V to IV will reset the Heavy ' + + 'Drone Operation skill, as that requires Drones V')) mainSizer.Add(self.cbStrictSkillLevels, 0, wx.ALL | wx.EXPAND, 5) self.cbUniversalAdaptiveArmorHardener = wx.CheckBox(panel, wx.ID_ANY, - u"When damage profile is Uniform, set Reactive Armor " + - u"Hardener to match (old behavior).", + "When damage profile is Uniform, set Reactive Armor " + + "Hardener to match (old behavior).", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbUniversalAdaptiveArmorHardener, 0, wx.ALL | wx.EXPAND, 5) diff --git a/gui/builtinPreferenceViews/pyfaEsiPreferences.py b/gui/builtinPreferenceViews/pyfaEsiPreferences.py new file mode 100644 index 000000000..ef81b7480 --- /dev/null +++ b/gui/builtinPreferenceViews/pyfaEsiPreferences.py @@ -0,0 +1,203 @@ +# noinspection PyPackageRequirements +import wx + +from gui.preferenceView import PreferenceView +from gui.bitmap_loader import BitmapLoader + +import gui.mainFrame + +from service.settings import EsiSettings + +# noinspection PyPackageRequirements +from wx.lib.intctrl import IntCtrl + +from service.esi import Esi + + +class PFEsiPref(PreferenceView): + title = "EVE SSO" + + def populatePanel(self, panel): + + self.mainFrame = gui.mainFrame.MainFrame.getInstance() + self.settings = EsiSettings.getInstance() + self.dirtySettings = False + dlgWidth = panel.GetParent().GetParent().ClientSize.width + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.stTitle = wx.StaticText(panel, wx.ID_ANY, self.title, wx.DefaultPosition, wx.DefaultSize, 0) + self.stTitle.Wrap(-1) + self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) + + mainSizer.Add(self.stTitle, 0, wx.ALL, 5) + + self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + self.stInfo = wx.StaticText(panel, wx.ID_ANY, + "Please see the pyfa wiki on GitHub for information regarding these options.", + wx.DefaultPosition, wx.DefaultSize, 0) + self.stInfo.Wrap(dlgWidth - 50) + mainSizer.Add(self.stInfo, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + rbSizer = wx.BoxSizer(wx.HORIZONTAL) + self.rbMode = wx.RadioBox(panel, -1, "Login Authentication Method", wx.DefaultPosition, wx.DefaultSize, + ['Local Server', 'Manual'], 1, wx.RA_SPECIFY_COLS) + self.rbMode.SetItemToolTip(0, "This options starts a local webserver that the web application will call back to with information about the character login.") + self.rbMode.SetItemToolTip(1, "This option prompts users to copy and paste information from the web application to allow for character login. Use this if having issues with the local server.") + + self.rbSsoMode = wx.RadioBox(panel, -1, "SSO Mode", wx.DefaultPosition, wx.DefaultSize, + ['pyfa.io', 'Custom application'], 1, wx.RA_SPECIFY_COLS) + self.rbSsoMode.SetItemToolTip(0, "This options routes SSO Logins through pyfa.io, allowing you to easily login without any configuration. When in doubt, use this option.") + self.rbSsoMode.SetItemToolTip(1, "This option goes through EVE SSO directly, but requires more configuration. Use this is pyfa.io is blocked for some reason, or if you do not wish to route data throguh pyfa.io.") + + self.rbMode.SetSelection(self.settings.get('loginMode')) + self.rbSsoMode.SetSelection(self.settings.get('ssoMode')) + + rbSizer.Add(self.rbSsoMode, 1, wx.ALL, 5) + rbSizer.Add(self.rbMode, 1, wx.TOP | wx.RIGHT, 5) + + self.rbMode.Bind(wx.EVT_RADIOBOX, self.OnModeChange) + self.rbSsoMode.Bind(wx.EVT_RADIOBOX, self.OnSSOChange) + + mainSizer.Add(rbSizer, 1, wx.ALL | wx.EXPAND, 0) + + detailsTitle = wx.StaticText(panel, wx.ID_ANY, "Custom Application", wx.DefaultPosition, wx.DefaultSize, 0) + detailsTitle.Wrap(-1) + detailsTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) + + mainSizer.Add(detailsTitle, 0, wx.ALL, 5) + mainSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, + wx.EXPAND, 5) + + # self.stInfo = wx.StaticText(panel, wx.ID_ANY, + # u"Using custom applications details will let pyfa to access the SSO under your application, rather than the pyfa application that is automatically set up. This requires you to set up your own ESI client application and accept CCPs License Agreement. Additionally, when setting up your client, make sure the callback url is set to 'http://localhost:6461'. Please see the pyfa wiki for more information regarding this", + # wx.DefaultPosition, wx.DefaultSize, 0) + # self.stInfo.Wrap(dlgWidth) + # mainSizer.Add(self.stInfo, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + fgAddrSizer = wx.FlexGridSizer(2, 2, 0, 0) + fgAddrSizer.AddGrowableCol(1) + fgAddrSizer.SetFlexibleDirection(wx.BOTH) + fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) + + self.stSetID = wx.StaticText(panel, wx.ID_ANY, u"Client ID:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stSetID.Wrap(-1) + fgAddrSizer.Add(self.stSetID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + + self.inputClientID = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition, + wx.DefaultSize, 0) + + fgAddrSizer.Add(self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + + self.stSetSecret = wx.StaticText(panel, wx.ID_ANY, u"Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stSetSecret.Wrap(-1) + + fgAddrSizer.Add(self.stSetSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + + self.inputClientSecret = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition, + wx.DefaultSize, 0) + + fgAddrSizer.Add(self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + + self.inputClientID.Bind(wx.EVT_TEXT, self.OnClientDetailChange) + self.inputClientSecret.Bind(wx.EVT_TEXT, self.OnClientDetailChange) + + mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5) + + + + timeoutSizer = wx.BoxSizer(wx.HORIZONTAL) + + # self.stTimout = wx.StaticText(panel, wx.ID_ANY, "Timeout (seconds):", wx.DefaultPosition, wx.DefaultSize, 0) + # self.stTimout.Wrap(-1) + # + # timeoutSizer.Add(self.stTimout, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + + # self.intTimeout = IntCtrl(panel, max=300000, limited=True, value=self.settings.get('timeout')) + # timeoutSizer.Add(self.intTimeout, 0, wx.ALL, 5) + # self.intTimeout.Bind(wx.lib.intctrl.EVT_INT, self.OnTimeoutChange) + # + # mainSizer.Add(timeoutSizer, 0, wx.ALL | wx.EXPAND, 0) + + # detailsTitle = wx.StaticText(panel, wx.ID_ANY, "CREST client details", wx.DefaultPosition, wx.DefaultSize, 0) + # detailsTitle.Wrap(-1) + # detailsTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) + # + # mainSizer.Add(detailsTitle, 0, wx.ALL, 5) + # mainSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, + # wx.EXPAND, 5) + + # fgAddrSizer = wx.FlexGridSizer(2, 2, 0, 0) + # fgAddrSizer.AddGrowableCol(1) + # fgAddrSizer.SetFlexibleDirection(wx.BOTH) + # fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) + # + # self.stSetID = wx.StaticText(panel, wx.ID_ANY, "Client ID:", wx.DefaultPosition, wx.DefaultSize, 0) + # self.stSetID.Wrap(-1) + # fgAddrSizer.Add(self.stSetID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + # + # self.inputClientID = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientID'), wx.DefaultPosition, + # wx.DefaultSize, 0) + # + # fgAddrSizer.Add(self.inputClientID, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + # + # self.stSetSecret = wx.StaticText(panel, wx.ID_ANY, "Client Secret:", wx.DefaultPosition, wx.DefaultSize, 0) + # self.stSetSecret.Wrap(-1) + # + # fgAddrSizer.Add(self.stSetSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + # + # self.inputClientSecret = wx.TextCtrl(panel, wx.ID_ANY, self.settings.get('clientSecret'), wx.DefaultPosition, + # wx.DefaultSize, 0) + # + # fgAddrSizer.Add(self.inputClientSecret, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + # + # self.btnApply = wx.Button(panel, wx.ID_ANY, "Save Client Settings", wx.DefaultPosition, wx.DefaultSize, 0) + # self.btnApply.Bind(wx.EVT_BUTTON, self.OnBtnApply) + # + # mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5) + # mainSizer.Add(self.btnApply, 0, wx.ALIGN_RIGHT, 5) + + # self.ToggleProxySettings(self.settings.get('loginMode')) + + self.ToggleSSOMode(self.settings.get('ssoMode')) + panel.SetSizer(mainSizer) + panel.Layout() + + def OnTimeoutChange(self, event): + self.settings.set('timeout', event.GetEventObject().GetValue()) + + def OnModeChange(self, event): + self.settings.set('loginMode', event.GetInt()) + + def OnSSOChange(self, event): + self.settings.set('ssoMode', event.GetInt()) + self.ToggleSSOMode(event.GetInt()) + + def ToggleSSOMode(self, mode): + if mode: + self.stSetID.Enable() + self.inputClientID.Enable() + self.stSetSecret.Enable() + self.inputClientSecret.Enable() + self.rbMode.Disable() + else: + self.stSetID.Disable() + self.inputClientID.Disable() + self.stSetSecret.Disable() + self.inputClientSecret.Disable() + self.rbMode.Enable() + + def OnClientDetailChange(self, evt): + self.settings.set('clientID', self.inputClientID.GetValue().strip()) + self.settings.set('clientSecret', self.inputClientSecret.GetValue().strip()) + + # sEsi = Esi.getInstance() + # sEsi.delAllCharacters() + # + + def getImage(self): + return BitmapLoader.getBitmap("eve", "gui") + + +PFEsiPref.register() diff --git a/gui/builtinPreferenceViews/pyfaGaugePreferences.py b/gui/builtinPreferenceViews/pyfaGaugePreferences.py index 5f6509dc0..c0ca68a98 100644 --- a/gui/builtinPreferenceViews/pyfaGaugePreferences.py +++ b/gui/builtinPreferenceViews/pyfaGaugePreferences.py @@ -1,15 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - # noinspection PyPackageRequirements import wx import copy from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader -from gui.utils import colorUtils -import gui.utils.drawUtils as drawUtils +from gui.bitmap_loader import BitmapLoader +from gui.utils.color import CalculateTransition +import gui.utils.draw as drawUtils ########################################################################### @@ -49,6 +48,7 @@ class PFGaugePreview(wx.Window): self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave) self.Bind(wx.EVT_TIMER, self.OnTimer) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBk) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) def OnEraseBk(self, event): pass @@ -109,44 +109,45 @@ class PFGaugePreview(wx.Window): self.Refresh() def OnPaint(self, event): - rect = self.GetClientRect() - dc = wx.BufferedPaintDC(self) - dc.SetBackground(wx.Brush(self.bkColor)) - dc.Clear() - - value = float(self.value) - if self.percS >= 100: - w = rect.width - else: - w = rect.width * (float(value) / 100) - r = copy.copy(rect) - r.width = w - - color = colorUtils.CalculateTransitionColor(self.colorS, self.colorE, float(value) / 100) - if self.gradientStart > 0: - gcolor = colorUtils.BrightenColor(color, float(self.gradientStart) / 100) - gMid = colorUtils.BrightenColor(color, float(self.gradientStart / 2) / 100) - else: - gcolor = colorUtils.DarkenColor(color, float(-self.gradientStart) / 100) - gMid = colorUtils.DarkenColor(color, float(-self.gradientStart / 2) / 100) - - gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor) - dc.DrawBitmap(gBmp, 0, 0) - dc.SetFont(self.font) - - r = copy.copy(rect) - r.left += 1 - r.top += 1 - - formatStr = "{0:." + str(self._fractionDigits) + "f}%" - value = (self.percE - self.percS) * value / (self.percE - self.percS) - value = self.percS + (self.percE - self.percS) * value / 100 - - dc.SetTextForeground(wx.Colour(80, 80, 80)) - dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER) - - dc.SetTextForeground(wx.Colour(255, 255, 255)) - dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER) + pass + # rect = self.GetClientRect() + # dc = wx.AutoBufferedPaintDC(self) + # dc.SetBackground(wx.Brush(self.bkColor)) + # dc.Clear() + # + # value = float(self.value) + # if self.percS >= 100: + # w = rect.width + # else: + # w = rect.width * (float(value) / 100) + # r = copy.copy(rect) + # r.width = w + # + # color = CalculateTransitionColor(self.colorS, self.colorE, float(value) / 100) + # if self.gradientStart > 0: + # gcolor = color.BrightenColor(color, float(self.gradientStart) / 100) + # gMid = color.BrightenColor(color, float(self.gradientStart / 2) / 100) + # else: + # gcolor = color.DarkenColor(color, float(-self.gradientStart) / 100) + # gMid = color.DarkenColor(color, float(-self.gradientStart / 2) / 100) + # + # gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor) + # dc.DrawBitmap(gBmp, 0, 0) + # dc.SetFont(self.font) + # + # r = copy.copy(rect) + # r.left += 1 + # r.top += 1 + # + # formatStr = "{0:." + str(self._fractionDigits) + "f}%" + # value = (self.percE - self.percS) * value / (self.percE - self.percS) + # value = self.percS + (self.percE - self.percS) * value / 100 + # + # dc.SetTextForeground(wx.Colour(80, 80, 80)) + # dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER) + # + # dc.SetTextForeground(wx.Colour(255, 255, 255)) + # dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER) class PFGaugePref(PreferenceView): @@ -160,7 +161,7 @@ class PFGaugePref(PreferenceView): gSizer1 = wx.BoxSizer(wx.HORIZONTAL) - self.st0100 = wx.StaticText(panel, wx.ID_ANY, u"0 - 100", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) + self.st0100 = wx.StaticText(panel, wx.ID_ANY, "0 - 100", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.st0100.Wrap(-1) gSizer1.Add(self.st0100, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -185,7 +186,7 @@ class PFGaugePref(PreferenceView): gSizer2 = wx.BoxSizer(wx.HORIZONTAL) - self.st100101 = wx.StaticText(panel, wx.ID_ANY, u"100 - 101", wx.DefaultPosition, wx.DefaultSize, + self.st100101 = wx.StaticText(panel, wx.ID_ANY, "100 - 101", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.st100101.Wrap(-1) gSizer2.Add(self.st100101, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -211,7 +212,7 @@ class PFGaugePref(PreferenceView): gSizer3 = wx.BoxSizer(wx.HORIZONTAL) - self.st101103 = wx.StaticText(panel, wx.ID_ANY, u"101 - 103", wx.DefaultPosition, wx.DefaultSize, + self.st101103 = wx.StaticText(panel, wx.ID_ANY, "101 - 103", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.st101103.Wrap(-1) gSizer3.Add(self.st101103, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -237,7 +238,7 @@ class PFGaugePref(PreferenceView): gSizer4 = wx.BoxSizer(wx.HORIZONTAL) - self.st103105 = wx.StaticText(panel, wx.ID_ANY, u"103 - 105", wx.DefaultPosition, wx.DefaultSize, + self.st103105 = wx.StaticText(panel, wx.ID_ANY, "103 - 105", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.st103105.Wrap(-1) gSizer4.Add(self.st103105, 1, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -284,20 +285,20 @@ class PFGaugePref(PreferenceView): buttonsSizer = wx.BoxSizer(wx.HORIZONTAL) - self.cbLink = wx.CheckBox(panel, wx.ID_ANY, u"Link Colors", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbLink = wx.CheckBox(panel, wx.ID_ANY, "Link Colors", wx.DefaultPosition, wx.DefaultSize, 0) buttonsSizer.Add(self.cbLink, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 5) self.sliderGradientStart = wx.Slider(panel, wx.ID_ANY, self.gradientStart, -100, 100, wx.DefaultPosition, (127, -1), wx.SL_HORIZONTAL | wx.SL_LABELS) buttonsSizer.Add(self.sliderGradientStart, 1, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - self.btnRestore = wx.Button(panel, wx.ID_ANY, u"Restore Defaults", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnRestore = wx.Button(panel, wx.ID_ANY, "Restore Defaults", wx.DefaultPosition, wx.DefaultSize, 0) buttonsSizer.Add(self.btnRestore, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - self.btnDump = wx.Button(panel, wx.ID_ANY, u"Dump Colors", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnDump = wx.Button(panel, wx.ID_ANY, "Dump Colors", wx.DefaultPosition, wx.DefaultSize, 0) buttonsSizer.Add(self.btnDump, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - self.btnOk = wx.Button(panel, wx.ID_ANY, u"Apply", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnOk = wx.Button(panel, wx.ID_ANY, "Apply", wx.DefaultPosition, wx.DefaultSize, 0) buttonsSizer.Add(self.btnOk, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) footerSizer.Add(buttonsSizer, 1, wx.ALIGN_RIGHT, 5) @@ -432,11 +433,11 @@ class PFGaugePref(PreferenceView): event.Skip() def DumpColours(self, event): - print("Gradient start: %d" % self.sliderGradientStart.GetValue()) - print(" 0 <-> 100 Start: ", self.c0100S, " End: ", self.c0100E) - print("100 <-> 101 Start: ", self.c100101S, " End: ", self.c100101E) - print("101 <-> 103 Start: ", self.c101103S, " End: ", self.c101103E) - print("103 <-> 105 Start: ", self.c103105S, " End: ", self.c103105E) + print(("Gradient start: %d" % self.sliderGradientStart.GetValue())) + print((" 0 <-> 100 Start: ", self.c0100S, " End: ", self.c0100E)) + print(("100 <-> 101 Start: ", self.c100101S, " End: ", self.c100101E)) + print(("101 <-> 103 Start: ", self.c101103S, " End: ", self.c101103E)) + print(("103 <-> 105 Start: ", self.c103105S, " End: ", self.c103105E)) event.Skip() diff --git a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py index 6f1acaab1..412e1a988 100644 --- a/gui/builtinPreferenceViews/pyfaGeneralPreferences.py +++ b/gui/builtinPreferenceViews/pyfaGeneralPreferences.py @@ -3,7 +3,7 @@ import wx from wx.lib.intctrl import IntCtrl from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import gui.mainFrame import gui.globalEvents as GE @@ -21,7 +21,7 @@ class PFGeneralPref(PreferenceView): self.openFitsSettings = SettingsProvider.getInstance().getSettings("pyfaPrevOpenFits", {"enabled": False, "pyfaOpenFits": []}) - helpCursor = wx.StockCursor(wx.CURSOR_QUESTION_ARROW) + helpCursor = wx.Cursor(wx.CURSOR_QUESTION_ARROW) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -34,59 +34,59 @@ class PFGeneralPref(PreferenceView): self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) - self.cbGlobalChar = wx.CheckBox(panel, wx.ID_ANY, u"Use global character", wx.DefaultPosition, wx.DefaultSize, + self.cbGlobalChar = wx.CheckBox(panel, wx.ID_ANY, "Use global character", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbGlobalChar, 0, wx.ALL | wx.EXPAND, 5) - self.cbGlobalDmgPattern = wx.CheckBox(panel, wx.ID_ANY, u"Use global damage pattern", wx.DefaultPosition, + self.cbGlobalDmgPattern = wx.CheckBox(panel, wx.ID_ANY, "Use global damage pattern", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbGlobalDmgPattern, 0, wx.ALL | wx.EXPAND, 5) - self.cbCompactSkills = wx.CheckBox(panel, wx.ID_ANY, u"Compact skills needed tooltip", wx.DefaultPosition, + self.cbCompactSkills = wx.CheckBox(panel, wx.ID_ANY, "Compact skills needed tooltip", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbCompactSkills, 0, wx.ALL | wx.EXPAND, 5) - self.cbFitColorSlots = wx.CheckBox(panel, wx.ID_ANY, u"Color fitting view by slot", wx.DefaultPosition, + self.cbFitColorSlots = wx.CheckBox(panel, wx.ID_ANY, "Color fitting view by slot", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbFitColorSlots, 0, wx.ALL | wx.EXPAND, 5) - self.cbReopenFits = wx.CheckBox(panel, wx.ID_ANY, u"Reopen previous fits on startup", wx.DefaultPosition, + self.cbReopenFits = wx.CheckBox(panel, wx.ID_ANY, "Reopen previous fits on startup", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbReopenFits, 0, wx.ALL | wx.EXPAND, 5) - self.cbRackSlots = wx.CheckBox(panel, wx.ID_ANY, u"Separate Racks", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbRackSlots = wx.CheckBox(panel, wx.ID_ANY, "Separate Racks", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbRackSlots, 0, wx.ALL | wx.EXPAND, 5) labelSizer = wx.BoxSizer(wx.VERTICAL) - self.cbRackLabels = wx.CheckBox(panel, wx.ID_ANY, u"Show Rack Labels", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbRackLabels = wx.CheckBox(panel, wx.ID_ANY, "Show Rack Labels", wx.DefaultPosition, wx.DefaultSize, 0) labelSizer.Add(self.cbRackLabels, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(labelSizer, 0, wx.LEFT | wx.EXPAND, 30) - self.cbShowTooltip = wx.CheckBox(panel, wx.ID_ANY, u"Show tab tooltips", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbShowTooltip = wx.CheckBox(panel, wx.ID_ANY, "Show tab tooltips", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbShowTooltip, 0, wx.ALL | wx.EXPAND, 5) - self.cbMarketShortcuts = wx.CheckBox(panel, wx.ID_ANY, u"Show market shortcuts", wx.DefaultPosition, + self.cbMarketShortcuts = wx.CheckBox(panel, wx.ID_ANY, "Show market shortcuts", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbMarketShortcuts, 0, wx.ALL | wx.EXPAND, 5) - self.cbGaugeAnimation = wx.CheckBox(panel, wx.ID_ANY, u"Animate gauges", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbGaugeAnimation = wx.CheckBox(panel, wx.ID_ANY, "Animate gauges", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbGaugeAnimation, 0, wx.ALL | wx.EXPAND, 5) - self.cbExportCharges = wx.CheckBox(panel, wx.ID_ANY, u"Export loaded charges", wx.DefaultPosition, + self.cbExportCharges = wx.CheckBox(panel, wx.ID_ANY, "Export loaded charges", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbExportCharges, 0, wx.ALL | wx.EXPAND, 5) - self.cbOpenFitInNew = wx.CheckBox(panel, wx.ID_ANY, u"Open fittings in a new page by default", + self.cbOpenFitInNew = wx.CheckBox(panel, wx.ID_ANY, "Open fittings in a new page by default", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbOpenFitInNew, 0, wx.ALL | wx.EXPAND, 5) - self.cbShowShipBrowserTooltip = wx.CheckBox(panel, wx.ID_ANY, u"Show ship browser tooltip", + self.cbShowShipBrowserTooltip = wx.CheckBox(panel, wx.ID_ANY, "Show ship browser tooltip", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbShowShipBrowserTooltip, 0, wx.ALL | wx.EXPAND, 5) priceSizer = wx.BoxSizer(wx.HORIZONTAL) - self.stDefaultSystem = wx.StaticText(panel, wx.ID_ANY, u"Default Market Prices:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stDefaultSystem = wx.StaticText(panel, wx.ID_ANY, "Default Market Prices:", wx.DefaultPosition, wx.DefaultSize, 0) self.stDefaultSystem.Wrap(-1) priceSizer.Add(self.stDefaultSystem, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) self.stDefaultSystem.SetCursor(helpCursor) @@ -95,7 +95,7 @@ class PFGeneralPref(PreferenceView): 'source fails. The system you choose is absolute and requests will not be made against other systems.')) self.chPriceSource = wx.Choice(panel, choices=sorted(Price.sources.keys())) - self.chPriceSystem = wx.Choice(panel, choices=Price.systemsList.keys()) + self.chPriceSystem = wx.Choice(panel, choices=list(Price.systemsList.keys())) priceSizer.Add(self.chPriceSource, 1, wx.ALL | wx.EXPAND, 5) priceSizer.Add(self.chPriceSystem, 1, wx.ALL | wx.EXPAND, 5) @@ -103,7 +103,7 @@ class PFGeneralPref(PreferenceView): delayTimer = wx.BoxSizer(wx.HORIZONTAL) - self.stMarketDelay = wx.StaticText(panel, wx.ID_ANY, u"Market Search Delay (ms):", wx.DefaultPosition, wx.DefaultSize, 0) + self.stMarketDelay = wx.StaticText(panel, wx.ID_ANY, "Market Search Delay (ms):", wx.DefaultPosition, wx.DefaultSize, 0) self.stMarketDelay.Wrap(-1) self.stMarketDelay.SetCursor(helpCursor) self.stMarketDelay.SetToolTip( diff --git a/gui/builtinPreferenceViews/pyfaHTMLExportPreferences.py b/gui/builtinPreferenceViews/pyfaHTMLExportPreferences.py index a60cc24df..d73cc8e58 100644 --- a/gui/builtinPreferenceViews/pyfaHTMLExportPreferences.py +++ b/gui/builtinPreferenceViews/pyfaHTMLExportPreferences.py @@ -3,11 +3,12 @@ import wx import os from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import gui.mainFrame from service.settings import HTMLExportSettings +import wx.lib.agw.hyperlink class PFHTMLExportPref(PreferenceView): @@ -37,10 +38,9 @@ class PFHTMLExportPref(PreferenceView): self.stDesc.Wrap(dlgWidth - 50) mainSizer.Add(self.stDesc, 0, wx.ALL, 5) - self.PathLinkCtrl = wx.HyperlinkCtrl(panel, wx.ID_ANY, self.HTMLExportSettings.getPath(), - u'file:///{}'.format(self.HTMLExportSettings.getPath()), + self.PathLinkCtrl = wx.lib.agw.hyperlink.HyperLinkCtrl(panel, wx.ID_ANY, self.HTMLExportSettings.getPath(), wx.DefaultPosition, wx.DefaultSize, - wx.HL_ALIGN_LEFT | wx.NO_BORDER | wx.HL_CONTEXTMENU) + URL='file:///{}'.format(self.HTMLExportSettings.getPath()),) mainSizer.Add(self.PathLinkCtrl, 0, wx.ALL | wx.EXPAND, 5) self.fileSelectDialog = wx.FileDialog(None, "Save Fitting As...", @@ -56,7 +56,7 @@ class PFHTMLExportPref(PreferenceView): self.stDesc4.Wrap(dlgWidth - 50) mainSizer.Add(self.stDesc4, 0, wx.ALL, 5) - self.exportMinimal = wx.CheckBox(panel, wx.ID_ANY, u"Enable minimal format", wx.DefaultPosition, + self.exportMinimal = wx.CheckBox(panel, wx.ID_ANY, "Enable minimal format", wx.DefaultPosition, wx.DefaultSize, 0) self.exportMinimal.SetValue(self.HTMLExportSettings.getMinimalEnabled()) self.exportMinimal.Bind(wx.EVT_CHECKBOX, self.OnMinimalEnabledChange) @@ -67,7 +67,7 @@ class PFHTMLExportPref(PreferenceView): def setPathLinkCtrlValues(self, path): self.PathLinkCtrl.SetLabel(self.HTMLExportSettings.getPath()) - self.PathLinkCtrl.SetURL(u'file:///{}'.format(self.HTMLExportSettings.getPath())) + self.PathLinkCtrl.SetURL('file:///{}'.format(self.HTMLExportSettings.getPath())) self.PathLinkCtrl.SetSize(wx.DefaultSize) self.PathLinkCtrl.Refresh() diff --git a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py index 5a7ffccc1..6de36df1c 100644 --- a/gui/builtinPreferenceViews/pyfaLoggingPreferences.py +++ b/gui/builtinPreferenceViews/pyfaLoggingPreferences.py @@ -1,7 +1,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import config from logbook import Logger @@ -25,7 +25,7 @@ class PFGeneralPref(PreferenceView): self.stTitle.SetFont(wx.Font(12, 70, 90, 90, False, wx.EmptyString)) mainSizer.Add(self.stTitle, 0, wx.ALL, 5) - self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, u"(Cannot be changed while pyfa is running. Set via command line switches.)", + self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, "(Cannot be changed while pyfa is running. Set via command line switches.)", wx.DefaultPosition, wx.DefaultSize, 0) self.stSubTitle.Wrap(-1) mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3) @@ -34,7 +34,7 @@ class PFGeneralPref(PreferenceView): mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) # Database path - self.stLogPath = wx.StaticText(panel, wx.ID_ANY, u"Log file location:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stLogPath = wx.StaticText(panel, wx.ID_ANY, "Log file location:", wx.DefaultPosition, wx.DefaultSize, 0) self.stLogPath.Wrap(-1) mainSizer.Add(self.stLogPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) self.inputLogPath = wx.TextCtrl(panel, wx.ID_ANY, config.logPath, wx.DefaultPosition, wx.DefaultSize, 0) @@ -42,14 +42,23 @@ class PFGeneralPref(PreferenceView): self.inputLogPath.SetBackgroundColour((200, 200, 200)) mainSizer.Add(self.inputLogPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + import requests + self.certPath = wx.StaticText(panel, wx.ID_ANY, "Cert Path:", wx.DefaultPosition, wx.DefaultSize, 0) + self.certPath .Wrap(-1) + mainSizer.Add(self.certPath, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) + self.certPathCtrl = wx.TextCtrl(panel, wx.ID_ANY, requests.certs.where(), wx.DefaultPosition, wx.DefaultSize, 0) + self.certPathCtrl.SetEditable(False) + self.certPathCtrl.SetBackgroundColour((200, 200, 200)) + mainSizer.Add(self.certPathCtrl, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) + # Debug Logging - self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, u"Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbdebugLogging = wx.CheckBox(panel, wx.ID_ANY, "Debug Logging Enabled", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbdebugLogging, 0, wx.ALL | wx.EXPAND, 5) - self.stDumpLogs = wx.StaticText(panel, wx.ID_ANY, u"Pressing this button will cause all logs in memory to write to the log file:", + self.stDumpLogs = wx.StaticText(panel, wx.ID_ANY, "Pressing this button will cause all logs in memory to write to the log file:", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.stDumpLogs, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - self.btnDumpLogs = wx.Button(panel, wx.ID_ANY, u"Dump All Logs", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnDumpLogs = wx.Button(panel, wx.ID_ANY, "Dump All Logs", wx.DefaultPosition, wx.DefaultSize, 0) self.btnDumpLogs.Bind(wx.EVT_BUTTON, OnDumpLogs) mainSizer.Add(self.btnDumpLogs, 0, wx.ALIGN_LEFT, 5) diff --git a/gui/builtinPreferenceViews/pyfaNetworkPreferences.py b/gui/builtinPreferenceViews/pyfaNetworkPreferences.py index e20f8a01d..4f9887f8a 100644 --- a/gui/builtinPreferenceViews/pyfaNetworkPreferences.py +++ b/gui/builtinPreferenceViews/pyfaNetworkPreferences.py @@ -2,7 +2,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import gui.mainFrame from service.settings import NetworkSettings @@ -30,18 +30,18 @@ class PFNetworkPref(PreferenceView): self.m_staticline1 = wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) mainSizer.Add(self.m_staticline1, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) - self.cbEnableNetwork = wx.CheckBox(panel, wx.ID_ANY, u"Enable Network", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbEnableNetwork = wx.CheckBox(panel, wx.ID_ANY, "Enable Network", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.cbEnableNetwork, 0, wx.ALL | wx.EXPAND, 5) subSizer = wx.BoxSizer(wx.VERTICAL) - self.cbEve = wx.CheckBox(panel, wx.ID_ANY, u"EVE Servers (API && CREST import)", wx.DefaultPosition, + self.cbEve = wx.CheckBox(panel, wx.ID_ANY, "EVE Servers (API && CREST import)", wx.DefaultPosition, wx.DefaultSize, 0) subSizer.Add(self.cbEve, 0, wx.ALL | wx.EXPAND, 5) - self.cbPricing = wx.CheckBox(panel, wx.ID_ANY, u"Pricing updates", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbPricing = wx.CheckBox(panel, wx.ID_ANY, "Pricing updates", wx.DefaultPosition, wx.DefaultSize, 0) subSizer.Add(self.cbPricing, 0, wx.ALL | wx.EXPAND, 5) - self.cbPyfaUpdate = wx.CheckBox(panel, wx.ID_ANY, u"Pyfa Update checks", wx.DefaultPosition, wx.DefaultSize, 0) + self.cbPyfaUpdate = wx.CheckBox(panel, wx.ID_ANY, "Pyfa Update checks", wx.DefaultPosition, wx.DefaultSize, 0) subSizer.Add(self.cbPyfaUpdate, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(subSizer, 0, wx.LEFT | wx.EXPAND, 30) @@ -80,11 +80,11 @@ class PFNetworkPref(PreferenceView): ptypeSizer = wx.BoxSizer(wx.HORIZONTAL) - self.stPType = wx.StaticText(panel, wx.ID_ANY, u"Mode:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stPType = wx.StaticText(panel, wx.ID_ANY, "Mode:", wx.DefaultPosition, wx.DefaultSize, 0) self.stPType.Wrap(-1) ptypeSizer.Add(self.stPType, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) - self.chProxyTypeChoices = [u"No proxy", u"Auto-detected proxy settings", u"Manual proxy settings"] + self.chProxyTypeChoices = ["No proxy", "Auto-detected proxy settings", "Manual proxy settings"] self.chProxyType = wx.Choice(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, self.chProxyTypeChoices, 0) self.chProxyType.SetSelection(self.nMode) @@ -98,7 +98,7 @@ class PFNetworkPref(PreferenceView): fgAddrSizer.SetFlexibleDirection(wx.BOTH) fgAddrSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) - self.stPSetAddr = wx.StaticText(panel, wx.ID_ANY, u"Addr:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stPSetAddr = wx.StaticText(panel, wx.ID_ANY, "Addr:", wx.DefaultPosition, wx.DefaultSize, 0) self.stPSetAddr.Wrap(-1) fgAddrSizer.Add(self.stPSetAddr, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -106,7 +106,7 @@ class PFNetworkPref(PreferenceView): fgAddrSizer.Add(self.editProxySettingsAddr, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND, 5) - self.stPSetPort = wx.StaticText(panel, wx.ID_ANY, u"Port:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stPSetPort = wx.StaticText(panel, wx.ID_ANY, "Port:", wx.DefaultPosition, wx.DefaultSize, 0) self.stPSetPort.Wrap(-1) fgAddrSizer.Add(self.stPSetPort, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -118,14 +118,14 @@ class PFNetworkPref(PreferenceView): mainSizer.Add(fgAddrSizer, 0, wx.EXPAND, 5) # proxy auth information: login and pass - self.stPSetLogin = wx.StaticText(panel, wx.ID_ANY, u"Username:", wx.DefaultPosition, wx.DefaultSize, 0) + self.stPSetLogin = wx.StaticText(panel, wx.ID_ANY, "Username:", wx.DefaultPosition, wx.DefaultSize, 0) self.stPSetLogin.Wrap(-1) self.editProxySettingsLogin = wx.TextCtrl(panel, wx.ID_ANY, self.nAuth[0], wx.DefaultPosition, wx.DefaultSize, - 0) - self.stPSetPassword = wx.StaticText(panel, wx.ID_ANY, u"Password:", wx.DefaultPosition, wx.DefaultSize, 0) + 0) + self.stPSetPassword = wx.StaticText(panel, wx.ID_ANY, "Password:", wx.DefaultPosition, wx.DefaultSize, 0) self.stPSetPassword.Wrap(-1) self.editProxySettingsPassword = wx.TextCtrl(panel, wx.ID_ANY, self.nAuth[1], wx.DefaultPosition, - wx.DefaultSize, wx.TE_PASSWORD) + wx.DefaultSize, wx.TE_PASSWORD) pAuthSizer = wx.BoxSizer(wx.HORIZONTAL) pAuthSizer.Add(self.stPSetLogin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) pAuthSizer.Add(self.editProxySettingsLogin, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) @@ -133,15 +133,15 @@ class PFNetworkPref(PreferenceView): pAuthSizer.Add(self.editProxySettingsPassword, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) mainSizer.Add(pAuthSizer, 0, wx.EXPAND, 5) - self.stPSAutoDetected = wx.StaticText(panel, wx.ID_ANY, u"Auto-detected: ", wx.DefaultPosition, wx.DefaultSize, - 0) + self.stPSAutoDetected = wx.StaticText(panel, wx.ID_ANY, "Auto-detected: ", wx.DefaultPosition, wx.DefaultSize, + 0) self.stPSAutoDetected.Wrap(-1) mainSizer.Add(self.stPSAutoDetected, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 5) btnSizer = wx.BoxSizer(wx.HORIZONTAL) - btnSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + btnSizer.AddStretchSpacer() - self.btnApply = wx.Button(panel, wx.ID_ANY, u"Apply Proxy Settings", wx.DefaultPosition, wx.DefaultSize, 0) + self.btnApply = wx.Button(panel, wx.ID_ANY, "Apply Proxy Settings", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.btnApply, 0, wx.ALL, 5) @@ -150,10 +150,10 @@ class PFNetworkPref(PreferenceView): proxy = self.settings.autodetect() if proxy is not None: - addr, port = proxy - txt = addr + ":" + str(port) + addr, port = proxy + txt = addr + ":" + str(port) else: - txt = "None" + txt = "None" self.stPSAutoDetected.SetLabel("Auto-detected: " + txt) self.stPSAutoDetected.Disable() diff --git a/gui/builtinPreferenceViews/pyfaStatViewPreferences.py b/gui/builtinPreferenceViews/pyfaStatViewPreferences.py index 800b64851..e9e0d8cb0 100644 --- a/gui/builtinPreferenceViews/pyfaStatViewPreferences.py +++ b/gui/builtinPreferenceViews/pyfaStatViewPreferences.py @@ -2,7 +2,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.settings import StatViewSettings @@ -27,7 +27,7 @@ class PFStatViewPref(PreferenceView): mainSizer.Add(self.stTitle, 0, wx.ALL, 5) self.stSubTitle = wx.StaticText(panel, wx.ID_ANY, - u"Changes require restart of pyfa to take effect.", + "Changes require restart of pyfa to take effect.", wx.DefaultPosition, wx.DefaultSize, 0) self.stSubTitle.Wrap(-1) mainSizer.Add(self.stSubTitle, 0, wx.ALL, 3) diff --git a/gui/builtinPreferenceViews/pyfaUpdatePreferences.py b/gui/builtinPreferenceViews/pyfaUpdatePreferences.py index cfc1bef11..f07d0380f 100644 --- a/gui/builtinPreferenceViews/pyfaUpdatePreferences.py +++ b/gui/builtinPreferenceViews/pyfaUpdatePreferences.py @@ -2,7 +2,7 @@ import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from service.settings import UpdateSettings @@ -33,7 +33,7 @@ class PFUpdatePref(PreferenceView): self.stDesc.Wrap(dlgWidth - 50) mainSizer.Add(self.stDesc, 0, wx.ALL, 5) - self.suppressPrerelease = wx.CheckBox(panel, wx.ID_ANY, u"Allow pre-release notifications", wx.DefaultPosition, + self.suppressPrerelease = wx.CheckBox(panel, wx.ID_ANY, "Allow pre-release notifications", wx.DefaultPosition, wx.DefaultSize, 0) self.suppressPrerelease.Bind(wx.EVT_CHECKBOX, self.OnPrereleaseStateChange) self.suppressPrerelease.SetValue(not self.UpdateSettings.get('prerelease')) @@ -52,11 +52,11 @@ class PFUpdatePref(PreferenceView): "You can choose to reset notification suppression for this release, " "or download the new release from GitHub.") - self.versionSizer.AddSpacer((5, 5), 0, wx.EXPAND, 5) + self.versionSizer.AddStretchSpacer() self.versionSizer.Add(wx.StaticLine(panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, wx.EXPAND, 5) - self.versionSizer.AddSpacer((5, 5), 0, wx.EXPAND, 5) + self.versionSizer.AddStretchSpacer() self.versionSizer.Add(self.versionTitle, 0, wx.EXPAND, 5) self.versionDesc = wx.StaticText(panel, wx.ID_ANY, self.versionInfo, wx.DefaultPosition, wx.DefaultSize, 0) diff --git a/gui/builtinShipBrowser/categoryItem.py b/gui/builtinShipBrowser/categoryItem.py index 875454e43..b88557d22 100644 --- a/gui/builtinShipBrowser/categoryItem.py +++ b/gui/builtinShipBrowser/categoryItem.py @@ -4,11 +4,11 @@ import wx from logbook import Logger from gui.builtinShipBrowser.sfBrowserItem import SFBrowserItem -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils import gui.utils.fonts as fonts -from gui.bitmapLoader import BitmapLoader -import events +from gui.bitmap_loader import BitmapLoader +from .events import Stage2Selected pyfalog = Logger(__name__) @@ -20,7 +20,7 @@ class CategoryItem(SFBrowserItem): if categoryID: self.shipBmp = BitmapLoader.getBitmap("ship_small", "gui") else: - self.shipBmp = wx.EmptyBitmap(16, 16) + self.shipBmp = wx.Bitmap(16, 16) self.dropShadowBitmap = drawUtils.CreateDropShadowBitmap(self.shipBmp, 0.2) @@ -77,7 +77,7 @@ class CategoryItem(SFBrowserItem): def selectCategory(self, event): categoryID = self.categoryID - wx.PostEvent(self.shipBrowser, events.Stage2Selected(categoryID=categoryID, back=False)) + wx.PostEvent(self.shipBrowser, Stage2Selected(categoryID=categoryID, back=False)) def MouseLeftUp(self, event): self.selectCategory(event) @@ -100,8 +100,8 @@ class CategoryItem(SFBrowserItem): # rect = self.GetRect() self.UpdateElementsPos(mdc) - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - textColor = colorUtils.GetSuitableColor(windowColor, 1) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + textColor = colorUtils.GetSuitable(windowColor, 1) mdc.SetTextForeground(textColor) mdc.DrawBitmap(self.dropShadowBitmap, self.shipBmpx + 1, self.shipBmpy + 1) diff --git a/gui/builtinShipBrowser/fitItem.py b/gui/builtinShipBrowser/fitItem.py index ad553a106..3d782fe5f 100644 --- a/gui/builtinShipBrowser/fitItem.py +++ b/gui/builtinShipBrowser/fitItem.py @@ -2,6 +2,7 @@ import re import time +import config import wx from logbook import Logger @@ -9,11 +10,11 @@ from logbook import Logger import gui.builtinShipBrowser.sfBrowserItem as SFItem import gui.globalEvents as GE import gui.mainFrame -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils import gui.utils.fonts as fonts -import events -from gui.bitmapLoader import BitmapLoader +from .events import ImportSelected, SearchSelected, FitSelected, BoosterListUpdated, Stage3Selected, FitRenamed, FitRemoved +from gui.bitmap_loader import BitmapLoader from gui.builtinShipBrowser.pfBitmapFrame import PFBitmapFrame from service.fit import Fit @@ -58,6 +59,9 @@ class FitItem(SFItem.SFBrowserItem): self.shipFittingInfo = shipFittingInfo self.shipName, self.shipTrait, self.fitName, self.fitBooster, self.timestamp, self.notes = shipFittingInfo + if config.debug: + self.fitName = '({}) {}'.format(self.fitID, self.fitName) + self.shipTrait = re.sub("<.*?>", " ", self.shipTrait) # see GH issue #62 @@ -72,9 +76,9 @@ class FitItem(SFItem.SFBrowserItem): self.acceptBmp = BitmapLoader.getBitmap("faccept_small", "gui") self.shipEffBk = BitmapLoader.getBitmap("fshipbk_big", "gui") - img = wx.ImageFromBitmap(self.shipEffBk) + img = self.shipEffBk.ConvertToImage() img = img.Mirror(False) - self.shipEffBkMirrored = wx.BitmapFromImage(img) + self.shipEffBkMirrored = wx.Bitmap(img) self.dragTLFBmp = None @@ -154,8 +158,8 @@ class FitItem(SFItem.SFBrowserItem): if self.shipTrait and sFit.serviceFittingOptions["showShipBrowserTooltip"]: notes = "" if self.notes: - notes = u'─' * 20 + u"\nNotes: {}\n".format(self.notes[:197] + '...' if len(self.notes) > 200 else self.notes) - self.SetToolTip(wx.ToolTip(u'{}\n{}{}\n{}'.format(self.shipName, notes, u'─' * 20, self.shipTrait))) + notes = '─' * 20 + "\nNotes: {}\n".format(self.notes[:197] + '...' if len(self.notes) > 200 else self.notes) + self.SetToolTip(wx.ToolTip('{}\n{}{}\n{}'.format(self.shipName, notes, '─' * 20, self.shipTrait))) def OnKeyUp(self, event): if event.GetKeyCode() in (32, 13): # space and enter @@ -171,7 +175,7 @@ class FitItem(SFItem.SFBrowserItem): self.fitBooster = not self.fitBooster self.boosterBtn.Show(self.fitBooster) self.Refresh() - wx.PostEvent(self.mainFrame, events.BoosterListUpdated()) + wx.PostEvent(self.mainFrame, BoosterListUpdated()) event.Skip() def OnProjectToFit(self, event): @@ -285,6 +289,7 @@ class FitItem(SFItem.SFBrowserItem): def editLostFocus(self, event): self.RestoreEditButton() self.Refresh() + event.Skip() def editCheckEsc(self, event): if event.GetKeyCode() == wx.WXK_ESCAPE: @@ -303,8 +308,8 @@ class FitItem(SFItem.SFBrowserItem): sFit = Fit.getInstance() fitID = sFit.copyFit(self.fitID) self.shipBrowser.fitIDMustEditName = fitID - wx.PostEvent(self.shipBrowser, events.Stage3Selected(shipID=self.shipID)) - wx.PostEvent(self.mainFrame, events.FitSelected(fitID=fitID)) + wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=self.shipID)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def renameBtnCB(self): if self.tcFitName.IsShown(): @@ -327,7 +332,7 @@ class FitItem(SFItem.SFBrowserItem): if fitName: self.fitName = fitName sFit.renameFit(self.fitID, self.fitName) - wx.PostEvent(self.mainFrame, events.FitRenamed(fitID=self.fitID)) + wx.PostEvent(self.mainFrame, FitRenamed(fitID=self.fitID)) else: self.tcFitName.SetValue(self.fitName) @@ -337,7 +342,7 @@ class FitItem(SFItem.SFBrowserItem): return # to prevent accidental deletion, give dialog confirmation unless shift is depressed - if wx.GetMouseState().ShiftDown() or wx.GetMouseState().MiddleDown(): + if wx.GetMouseState().ShiftDown() or wx.GetMouseState().MiddleIsDown(): self.deleteFit() else: dlg = wx.MessageDialog( @@ -369,21 +374,21 @@ class FitItem(SFItem.SFBrowserItem): sFit.deleteFit(self.fitID) # Notify other areas that a fit has been deleted - wx.PostEvent(self.mainFrame, events.FitRemoved(fitID=self.fitID)) + wx.PostEvent(self.mainFrame, FitRemoved(fitID=self.fitID)) # todo: would a simple RefreshList() work here instead of posting that a stage has been selected? if self.shipBrowser.GetActiveStage() == 5: - wx.PostEvent(self.shipBrowser, events.ImportSelected(fits=self.shipBrowser.lastdata, recent=self.shipBrowser.recentFits)) + wx.PostEvent(self.shipBrowser, ImportSelected(fits=self.shipBrowser.lastdata, recent=self.shipBrowser.recentFits)) elif self.shipBrowser.GetActiveStage() == 4: - wx.PostEvent(self.shipBrowser, events.SearchSelected(text=self.shipBrowser.navpanel.lastSearch, back=True)) + wx.PostEvent(self.shipBrowser, SearchSelected(text=self.shipBrowser.navpanel.lastSearch, back=True)) else: - wx.PostEvent(self.shipBrowser, events.Stage3Selected(shipID=self.shipID)) + wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=self.shipID)) def MouseLeftUp(self, event): if self.dragging and self.dragged: self.OnMouseCaptureLost(event) - targetWnd = wx.FindWindowAtPointer() + targetWnd, _ = wx.FindWindowAtPointer() if not targetWnd: return @@ -433,9 +438,9 @@ class FitItem(SFItem.SFBrowserItem): def selectFit(self, event=None, newTab=False): if newTab: - wx.PostEvent(self.mainFrame, events.FitSelected(fitID=self.fitID, startup=2)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID, startup=2)) else: - wx.PostEvent(self.mainFrame, events.FitSelected(fitID=self.fitID)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=self.fitID)) def RestoreEditButton(self): self.tcFitName.Show(False) @@ -480,8 +485,8 @@ class FitItem(SFItem.SFBrowserItem): def DrawItem(self, mdc): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - textColor = colorUtils.GetSuitableColor(windowColor, 1) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + textColor = colorUtils.GetSuitable(windowColor, 1) mdc.SetTextForeground(textColor) @@ -521,7 +526,7 @@ class FitItem(SFItem.SFBrowserItem): self.AdjustControlSizePos(self.tcFitName, self.textStartx, self.toolbarx - self.editWidth - self.padding) tdc = wx.MemoryDC() - self.dragTLFBmp = wx.EmptyBitmap((self.toolbarx if self.toolbarx < 200 else 200), rect.height, 24) + self.dragTLFBmp = wx.Bitmap((self.toolbarx if self.toolbarx < 200 else 200), rect.height, 24) tdc.SelectObject(self.dragTLFBmp) tdc.Blit(0, 0, (self.toolbarx if self.toolbarx < 200 else 200), rect.height, mdc, 0, 0, wx.COPY) tdc.SelectObject(wx.NullBitmap) @@ -569,7 +574,7 @@ class FitItem(SFItem.SFBrowserItem): def RenderBackground(self): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) # activeFitID = self.mainFrame.getActiveFit() state = self.GetState() diff --git a/gui/builtinShipBrowser/navigationPanel.py b/gui/builtinShipBrowser/navigationPanel.py index cd80b658e..204802c45 100644 --- a/gui/builtinShipBrowser/navigationPanel.py +++ b/gui/builtinShipBrowser/navigationPanel.py @@ -5,11 +5,11 @@ from logbook import Logger import gui.builtinShipBrowser.sfBrowserItem as SFItem import gui.mainFrame -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils import gui.utils.fonts as fonts -import events -from gui.bitmapLoader import BitmapLoader +from .events import FitSelected, SearchSelected, ImportSelected, Stage1Selected, Stage2Selected, Stage3Selected +from gui.bitmap_loader import BitmapLoader from service.fit import Fit pyfalog = Logger(__name__) @@ -29,11 +29,11 @@ class NavigationPanel(SFItem.SFBrowserItem): switchImg = BitmapLoader.getImage("fit_switch_view_mode_small", "gui") switchImg = switchImg.AdjustChannels(1, 1, 1, 0.4) - self.switchBmpD = wx.BitmapFromImage(switchImg) + self.switchBmpD = wx.Bitmap(switchImg) recentImg = BitmapLoader.getImage("frecent_small", "gui") recentImg = recentImg.AdjustChannels(1, 1, 1, 0.4) - self.recentBmpD = wx.BitmapFromImage(recentImg) + self.recentBmpD = wx.Bitmap(recentImg) self.resetBmp = self.AdjustChannels(self.resetBmpH) self.rewBmp = self.AdjustChannels(self.rewBmpH) @@ -87,7 +87,7 @@ class NavigationPanel(SFItem.SFBrowserItem): realsearch = search.replace("*", "") if len(realsearch) >= 3: self.lastSearch = search - wx.PostEvent(self.shipBrowser, events.SearchSelected(text=search, back=False)) + wx.PostEvent(self.shipBrowser, SearchSelected(text=search, back=False)) def ToggleSearchBox(self): if self.BrowserSearchBox.IsShown(): @@ -122,7 +122,7 @@ class NavigationPanel(SFItem.SFBrowserItem): self.btnRecent.normalBmp = self.recentBmpD if emitEvent: - wx.PostEvent(self.shipBrowser, events.Stage1Selected()) + wx.PostEvent(self.shipBrowser, Stage1Selected()) else: self.shipBrowser.recentFits = True self.btnRecent.label = "Hide Recent Fits" @@ -131,7 +131,7 @@ class NavigationPanel(SFItem.SFBrowserItem): if emitEvent: sFit = Fit.getInstance() fits = sFit.getRecentFits() - wx.PostEvent(self.shipBrowser, events.ImportSelected(fits=fits, back=True, recent=True)) + wx.PostEvent(self.shipBrowser, ImportSelected(fits=fits, back=True, recent=True)) def ToggleEmptyGroupsView(self): if self.shipBrowser.filterShipsWithNoFits: @@ -146,10 +146,10 @@ class NavigationPanel(SFItem.SFBrowserItem): stage = self.shipBrowser.GetActiveStage() if stage == 1: - wx.PostEvent(self.shipBrowser, events.Stage1Selected()) + wx.PostEvent(self.shipBrowser, Stage1Selected()) elif stage == 2: categoryID = self.shipBrowser.GetStageData(stage) - wx.PostEvent(self.shipBrowser, events.Stage2Selected(categoryID=categoryID, back=True)) + wx.PostEvent(self.shipBrowser, Stage2Selected(categoryID=categoryID, back=True)) def ShowNewFitButton(self, show): self.btnNew.Show(show) @@ -167,8 +167,8 @@ class NavigationPanel(SFItem.SFBrowserItem): sFit = Fit.getInstance() fitID = sFit.newFit(shipID, "%s fit" % shipName) self.shipBrowser.fitIDMustEditName = fitID - wx.PostEvent(self.Parent, events.Stage3Selected(shipID=shipID)) - wx.PostEvent(self.mainFrame, events.FitSelected(fitID=fitID)) + wx.PostEvent(self.Parent, Stage3Selected(shipID=shipID)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def OnHistoryReset(self): self.ToggleRecentShips(False, False) @@ -184,9 +184,9 @@ class NavigationPanel(SFItem.SFBrowserItem): @staticmethod def AdjustChannels(bitmap): - img = wx.ImageFromBitmap(bitmap) + img = bitmap.ConvertToImage() img = img.AdjustChannels(1.05, 1.05, 1.05, 1) - return wx.BitmapFromImage(img) + return wx.Bitmap(img) def UpdateElementsPos(self, mdc): rect = self.GetRect() @@ -211,9 +211,9 @@ class NavigationPanel(SFItem.SFBrowserItem): def DrawItem(self, mdc): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - textColor = colorUtils.GetSuitableColor(windowColor, 1) - sepColor = colorUtils.GetSuitableColor(windowColor, 0.2) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + textColor = colorUtils.GetSuitable(windowColor, 1) + sepColor = colorUtils.GetSuitable(windowColor, 0.2) mdc.SetTextForeground(textColor) @@ -230,7 +230,7 @@ class NavigationPanel(SFItem.SFBrowserItem): def RenderBackground(self): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) sFactor = 0.1 @@ -259,15 +259,15 @@ class NavigationPanel(SFItem.SFBrowserItem): def gotoStage(self, stage, data=None): self.shipBrowser.recentFits = False if stage == 1: - wx.PostEvent(self.Parent, events.Stage1Selected()) + wx.PostEvent(self.Parent, Stage1Selected()) elif stage == 2: - wx.PostEvent(self.Parent, events.Stage2Selected(categoryID=data, back=True)) + wx.PostEvent(self.Parent, Stage2Selected(categoryID=data, back=True)) elif stage == 3: - wx.PostEvent(self.Parent, events.Stage3Selected(shipID=data)) + wx.PostEvent(self.Parent, Stage3Selected(shipID=data)) elif stage == 4: self.shipBrowser._activeStage = 4 - wx.PostEvent(self.Parent, events.SearchSelected(text=data, back=True)) + wx.PostEvent(self.Parent, SearchSelected(text=data, back=True)) elif stage == 5: - wx.PostEvent(self.Parent, events.ImportSelected(fits=data)) + wx.PostEvent(self.Parent, ImportSelected(fits=data)) else: - wx.PostEvent(self.Parent, events.Stage1Selected()) + wx.PostEvent(self.Parent, Stage1Selected()) diff --git a/gui/builtinShipBrowser/pfBitmapFrame.py b/gui/builtinShipBrowser/pfBitmapFrame.py index e05e643fc..85ad487d6 100644 --- a/gui/builtinShipBrowser/pfBitmapFrame.py +++ b/gui/builtinShipBrowser/pfBitmapFrame.py @@ -7,7 +7,7 @@ class PFBitmapFrame(wx.Frame): style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP) img = bitmap.ConvertToImage() img = img.ConvertToGreyscale() - bitmap = wx.BitmapFromImage(img) + bitmap = wx.Bitmap(img) self.bitmap = bitmap self.SetSize((bitmap.GetWidth(), bitmap.GetHeight())) self.Bind(wx.EVT_PAINT, self.OnWindowPaint) @@ -19,6 +19,8 @@ class PFBitmapFrame(wx.Frame): self.transp = 0 self.SetSize((bitmap.GetWidth(), bitmap.GetHeight())) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + self.SetTransparent(0) self.Refresh() @@ -49,8 +51,12 @@ class PFBitmapFrame(wx.Frame): pass def OnWindowPaint(self, event): + # todo: evaluate wx.DragImage, might make this class obsolete, however might also lose our customizations + # (like the sexy fade-in animation) rect = self.GetRect() - canvas = wx.EmptyBitmap(rect.width, rect.height) + canvas = wx.Bitmap(rect.width, rect.height) + # todo: convert to context manager after updating to wxPython >v4.0.1 (4.0.1 has a bug, see #1421) + # See #1418 for discussion mdc = wx.BufferedPaintDC(self) mdc.SelectObject(canvas) mdc.DrawBitmap(self.bitmap, 0, 0) diff --git a/gui/builtinShipBrowser/pfListPane.py b/gui/builtinShipBrowser/pfListPane.py index e6689ce8d..cbdebff05 100644 --- a/gui/builtinShipBrowser/pfListPane.py +++ b/gui/builtinShipBrowser/pfListPane.py @@ -23,14 +23,13 @@ import wx class PFListPane(wx.ScrolledWindow): def __init__(self, parent): - wx.ScrolledWindow.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(1, 1), - style=wx.TAB_TRAVERSAL) + wx.ScrolledWindow.__init__(self, parent, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL) self._wList = [] self._wCount = 0 self.itemsHeight = 1 - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) self.SetVirtualSize((1, 1)) self.SetScrollRate(0, 1) @@ -127,7 +126,7 @@ class PFListPane(wx.ScrolledWindow): maxy = 0 selected = None - for i in xrange(len(self._wList)): + for i in range(len(self._wList)): iwidth, iheight = self._wList[i].GetSize() xa, ya = self.CalcScrolledPosition((0, maxy)) self._wList[i].SetPosition((xa, ya)) @@ -144,7 +143,7 @@ class PFListPane(wx.ScrolledWindow): elif doFocus: self.SetFocus() - for i in xrange(len(self._wList)): + for i in range(len(self._wList)): iwidth, iheight = self._wList[i].GetSize() self._wList[i].SetSize((cwidth, iheight)) if doRefresh is True: diff --git a/gui/builtinShipBrowser/pfStaticText.py b/gui/builtinShipBrowser/pfStaticText.py index 66a602239..f5122ecfa 100644 --- a/gui/builtinShipBrowser/pfStaticText.py +++ b/gui/builtinShipBrowser/pfStaticText.py @@ -9,7 +9,7 @@ pyfalog = Logger(__name__) class PFStaticText(wx.Panel): def __init__(self, parent, label=wx.EmptyString): wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=parent.GetSize()) - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) mainSizer = wx.BoxSizer(wx.VERTICAL) text = wx.StaticText(self, wx.ID_ANY, label, wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_CENTRE) diff --git a/gui/builtinShipBrowser/pfWidgetContainer.py b/gui/builtinShipBrowser/pfWidgetContainer.py index 305bd6385..e3219530a 100644 --- a/gui/builtinShipBrowser/pfWidgetContainer.py +++ b/gui/builtinShipBrowser/pfWidgetContainer.py @@ -1,6 +1,6 @@ from gui.builtinShipBrowser.pfListPane import PFListPane import gui.mainFrame -import gui.utils.animUtils as animUtils +import gui.utils.anim as animUtils class PFWidgetsContainer(PFListPane): diff --git a/gui/builtinShipBrowser/raceSelector.py b/gui/builtinShipBrowser/raceSelector.py index 3807bdf66..809474249 100644 --- a/gui/builtinShipBrowser/raceSelector.py +++ b/gui/builtinShipBrowser/raceSelector.py @@ -3,11 +3,11 @@ import wx from logbook import Logger -import gui.utils.animEffects as animEffects -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils -import events -from gui.bitmapLoader import BitmapLoader +import gui.utils.anim_effects as animEffects +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils +from .events import Stage2Selected +from gui.bitmap_loader import BitmapLoader pyfalog = Logger(__name__) @@ -70,7 +70,7 @@ class RaceSelector(wx.Window): if layout == wx.VERTICAL: img = img.Scale(self.minWidth, 8, wx.IMAGE_QUALITY_HIGH) - self.bmpArrow = wx.BitmapFromImage(img) + self.bmpArrow = wx.Bitmap(img) self.RebuildRaces(self.shipBrowser.RACE_ORDER) @@ -85,6 +85,8 @@ class RaceSelector(wx.Window): self.Layout() + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + def OnMouseMove(self, event): mx, my = event.GetPosition() @@ -93,9 +95,9 @@ class RaceSelector(wx.Window): self.hoveredItem = location self.Refresh() if location is not None: - self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + self.SetCursor(wx.Cursor(wx.CURSOR_HAND)) else: - self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) def OnSizeUpdate(self, event): self.CalcButtonsBarPos() @@ -134,7 +136,7 @@ class RaceSelector(wx.Window): if stage == 2: categoryID = self.shipBrowser.GetStageData(stage) - wx.PostEvent(self.shipBrowser, events.Stage2Selected(categoryID=categoryID, back=True)) + wx.PostEvent(self.shipBrowser, Stage2Selected(categoryID=categoryID, back=True)) event.Skip() def HitTest(self, mx, my): @@ -166,11 +168,11 @@ class RaceSelector(wx.Window): def OnPaint(self, event): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - # bkColor = colorUtils.GetSuitableColor(windowColor, 0.1) - sepColor = colorUtils.GetSuitableColor(windowColor, 0.2) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + # bkColor = colorUtils.GetSuitable(windowColor, 0.1) + sepColor = colorUtils.GetSuitable(windowColor, 0.2) - mdc = wx.BufferedPaintDC(self) + mdc = wx.AutoBufferedPaintDC(self) bkBitmap = drawUtils.RenderGradientBar(windowColor, rect.width, rect.height, 0.1, 0.1, 0.2, 2) mdc.DrawBitmap(bkBitmap, 0, 0, True) @@ -184,12 +186,12 @@ class RaceSelector(wx.Window): if self.shipBrowser.GetRaceFilterState(self.raceNames[self.raceBmps.index(raceBmp)]): bmp = raceBmp else: - img = wx.ImageFromBitmap(raceBmp) + img = raceBmp.ConvertToImage() if self.hoveredItem == self.raceBmps.index(raceBmp): img = img.AdjustChannels(1, 1, 1, 0.7) else: img = img.AdjustChannels(1, 1, 1, 0.4) - bmp = wx.BitmapFromImage(img) + bmp = wx.Bitmap(img) if self.layout == wx.VERTICAL: mdc.DrawBitmap(dropShadow, rect.width - self.buttonsPadding - bmp.GetWidth() + 1, y + 1) @@ -257,7 +259,7 @@ class RaceSelector(wx.Window): def OnWindowLeave(self, event): if self.hoveredItem is not None: self.hoveredItem = None - self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) self.Refresh() if not self.animate: diff --git a/gui/builtinShipBrowser/sfBrowserItem.py b/gui/builtinShipBrowser/sfBrowserItem.py index 4f33f101f..16b7a4f24 100644 --- a/gui/builtinShipBrowser/sfBrowserItem.py +++ b/gui/builtinShipBrowser/sfBrowserItem.py @@ -1,6 +1,6 @@ # noinspection PyPackageRequirements import wx -import gui.utils.drawUtils as drawUtils +import gui.utils.draw as drawUtils SB_ITEM_NORMAL = 0 SB_ITEM_SELECTED = 1 @@ -122,7 +122,7 @@ class PFToolbar(object): if not state & BTN_HOVER: button.SetState(state | BTN_HOVER) self.hoverLabel = button.GetLabel() - self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) + self.Parent.SetCursor(wx.Cursor(wx.CURSOR_HAND)) doRefresh = True else: if state & BTN_HOVER: @@ -133,7 +133,7 @@ class PFToolbar(object): bx += bwidth + self.padding if not changeCursor: - self.Parent.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) + self.Parent.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) return doRefresh def MouseClick(self, event): @@ -241,7 +241,7 @@ class PFToolbar(object): class SFBrowserItem(wx.Window): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=(0, 16), style=0): wx.Window.__init__(self, parent, id, pos, size, style) - + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) self.highlighted = False self.selected = False self.bkBitmap = None @@ -280,7 +280,7 @@ class SFBrowserItem(wx.Window): wx.Window.Refresh(self) def OnPaint(self, event): - mdc = wx.BufferedPaintDC(self) + mdc = wx.AutoBufferedPaintDC(self) self.RenderBackground() @@ -399,7 +399,7 @@ class SFBrowserItem(wx.Window): def RenderBackground(self): rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) state = self.GetState() diff --git a/gui/builtinShipBrowser/shipItem.py b/gui/builtinShipBrowser/shipItem.py index 3ef8163df..4c679fb8a 100644 --- a/gui/builtinShipBrowser/shipItem.py +++ b/gui/builtinShipBrowser/shipItem.py @@ -5,11 +5,11 @@ from logbook import Logger import gui.builtinShipBrowser.sfBrowserItem as SFItem import gui.mainFrame -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils +import gui.utils.color as colorUtils +import gui.utils.draw as drawUtils import gui.utils.fonts as fonts -import events -from gui.bitmapLoader import BitmapLoader +from .events import Stage3Selected, Stage2Selected, Stage1Selected, FitSelected +from gui.bitmap_loader import BitmapLoader from gui.contextMenu import ContextMenu from service.fit import Fit from service.market import Market @@ -50,9 +50,9 @@ class ShipItem(SFItem.SFBrowserItem): self.shipEffBk = BitmapLoader.getBitmap("fshipbk_big", "gui") - img = wx.ImageFromBitmap(self.shipEffBk) + img = self.shipEffBk.ConvertToImage() img = img.Mirror(False) - self.shipEffBkMirrored = wx.BitmapFromImage(img) + self.shipEffBkMirrored = wx.Bitmap(img) self.raceBmp = BitmapLoader.getBitmap("race_%s_small" % self.shipRace, "gui") @@ -147,7 +147,7 @@ class ShipItem(SFItem.SFBrowserItem): else: shipName, shipTrait, fittings = self.shipFittingInfo if fittings > 0: - wx.PostEvent(self.shipBrowser, events.Stage3Selected(shipID=self.shipID, back=True)) + wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=self.shipID, back=True)) else: self.newBtnCB() @@ -186,8 +186,8 @@ class ShipItem(SFItem.SFBrowserItem): sFit = Fit.getInstance() fitID = sFit.newFit(self.shipID, self.tcFitName.GetValue()) - wx.PostEvent(self.shipBrowser, events.Stage3Selected(shipID=self.shipID, back=False)) - wx.PostEvent(self.mainFrame, events.FitSelected(fitID=fitID)) + wx.PostEvent(self.shipBrowser, Stage3Selected(shipID=self.shipID, back=False)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def UpdateElementsPos(self, mdc): rect = self.GetRect() @@ -232,8 +232,8 @@ class ShipItem(SFItem.SFBrowserItem): def DrawItem(self, mdc): # rect = self.GetRect() - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - textColor = colorUtils.GetSuitableColor(windowColor, 1) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + textColor = colorUtils.GetSuitable(windowColor, 1) mdc.SetTextForeground(textColor) diff --git a/gui/builtinStatsViews/capacitorViewFull.py b/gui/builtinStatsViews/capacitorViewFull.py index 49bb9d59b..8ff7d6a13 100644 --- a/gui/builtinStatsViews/capacitorViewFull.py +++ b/gui/builtinStatsViews/capacitorViewFull.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount @@ -45,7 +45,7 @@ class CapacitorViewFull(StatsView): panel = "full" - sizerCapacitor = wx.GridSizer(1, 2) + sizerCapacitor = wx.GridSizer(1, 2, 0, 0) contentSizer.Add(sizerCapacitor, 0, wx.EXPAND, 0) # Capacitor capacity and time baseBox = wx.BoxSizer(wx.HORIZONTAL) @@ -91,7 +91,7 @@ class CapacitorViewFull(StatsView): baseBox.Add(bitmap, 0, wx.ALIGN_CENTER) # Recharge - chargeSizer = wx.FlexGridSizer(2, 3) + chargeSizer = wx.FlexGridSizer(2, 3, 0, 0) baseBox.Add(chargeSizer, 0, wx.ALIGN_CENTER) chargeSizer.Add(wx.StaticText(parent, wx.ID_ANY, "+ "), 0, wx.ALIGN_CENTER) @@ -124,7 +124,7 @@ class CapacitorViewFull(StatsView): label = getattr(self, labelName % panel) value = value() if fit is not None else 0 value = value if value is not None else 0 - if isinstance(value, basestring): + if isinstance(value, str): label.SetLabel(value) label.SetToolTip(wx.ToolTip(value)) else: diff --git a/gui/builtinStatsViews/firepowerViewFull.py b/gui/builtinStatsViews/firepowerViewFull.py index 618ca52de..98d54d714 100644 --- a/gui/builtinStatsViews/firepowerViewFull.py +++ b/gui/builtinStatsViews/firepowerViewFull.py @@ -21,7 +21,7 @@ import wx import gui.mainFrame from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from service.fit import Fit @@ -53,7 +53,7 @@ class FirepowerViewFull(StatsView): panel = "full" - sizerFirepower = wx.FlexGridSizer(1, 4) + sizerFirepower = wx.FlexGridSizer(1, 4, 0, 0) sizerFirepower.AddGrowableCol(1) contentSizer.Add(sizerFirepower, 0, wx.EXPAND, 0) @@ -128,8 +128,8 @@ class FirepowerViewFull(StatsView): # Remove effective label hsizer = self.headerPanel.GetSizer() - hsizer.Remove(self.stEff) - self.stEff.Destroy() + hsizer.Hide(self.stEff) + #self.stEff.Destroy() # Get the new view view = StatsView.getView("miningyieldViewFull")(self.parent) diff --git a/gui/builtinStatsViews/miningyieldViewFull.py b/gui/builtinStatsViews/miningyieldViewFull.py index 912bddc2c..7793a2850 100644 --- a/gui/builtinStatsViews/miningyieldViewFull.py +++ b/gui/builtinStatsViews/miningyieldViewFull.py @@ -21,7 +21,7 @@ import wx import gui.mainFrame from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from service.fit import Fit @@ -48,7 +48,7 @@ class MiningYieldViewFull(StatsView): panel = "full" - sizerMiningYield = wx.FlexGridSizer(1, 4) + sizerMiningYield = wx.FlexGridSizer(1, 4, 0, 0) sizerMiningYield.AddGrowableCol(1) contentSizer.Add(sizerMiningYield, 0, wx.EXPAND, 0) @@ -69,7 +69,7 @@ class MiningYieldViewFull(StatsView): hbox = wx.BoxSizer(wx.HORIZONTAL) box.Add(hbox, 1, wx.ALIGN_CENTER) - lbl = wx.StaticText(parent, wx.ID_ANY, u"0.0 m\u00B3/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0.0 m\u00B3/s") setattr(self, "label%sminingyield%s" % (panel.capitalize(), miningType.capitalize()), lbl) hbox.Add(lbl, 0, wx.ALIGN_CENTER) @@ -90,7 +90,7 @@ class MiningYieldViewFull(StatsView): hbox = wx.BoxSizer(wx.HORIZONTAL) box.Add(hbox, 1, wx.EXPAND) - lbl = wx.StaticText(parent, wx.ID_ANY, u"0.0 m\u00B3/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0.0 m\u00B3/s") setattr(self, "label%sminingyieldTotal" % panel.capitalize(), lbl) hbox.Add(lbl, 0, wx.ALIGN_LEFT) @@ -123,16 +123,16 @@ class MiningYieldViewFull(StatsView): # Get the TogglePanel tp = self.panel.GetParent() # Bind the new panel's children to allow context menu access - self.parent.applyBinding(self.parent, tp.GetContentPane()) + self.parent.applyBinding(self.parent, tp.content_panel) tp.SetLabel(view.getHeaderText(fit)) view.refreshPanel(fit) def refreshPanel(self, fit): # If we did anything intresting, we'd update our labels to reflect the new fit's stats here - stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, 3, 0, 0, u"%s m\u00B3/s", None), - ("labelFullminingyieldDrone", lambda: fit.droneYield, 3, 0, 0, u"%s m\u00B3/s", None), - ("labelFullminingyieldTotal", lambda: fit.totalYield, 3, 0, 0, u"%s m\u00B3/s", None)) + stats = (("labelFullminingyieldMiner", lambda: fit.minerYield, 3, 0, 0, "%s m\u00B3/s", None), + ("labelFullminingyieldDrone", lambda: fit.droneYield, 3, 0, 0, "%s m\u00B3/s", None), + ("labelFullminingyieldTotal", lambda: fit.totalYield, 3, 0, 0, "%s m\u00B3/s", None)) counter = 0 for labelName, value, prec, lowest, highest, valueFormat, altFormat in stats: diff --git a/gui/builtinStatsViews/outgoingViewFull.py b/gui/builtinStatsViews/outgoingViewFull.py index 346aacc7a..be24ee6af 100644 --- a/gui/builtinStatsViews/outgoingViewFull.py +++ b/gui/builtinStatsViews/outgoingViewFull.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount @@ -44,7 +44,7 @@ class OutgoingViewFull(StatsView): parent = self.panel = contentPanel self.headerPanel = headerPanel - sizerOutgoing = wx.GridSizer(1, 4) + sizerOutgoing = wx.GridSizer(1, 4, 0, 0) contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0) @@ -63,9 +63,9 @@ class OutgoingViewFull(StatsView): baseBox.Add(BitmapLoader.getStaticBitmap("%s_big" % image, parent, "gui"), 0, wx.ALIGN_CENTER) if "Capacitor" in outgoingType: - lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s") else: - lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s") lbl.SetToolTip(wx.ToolTip(tooltip)) @@ -81,10 +81,10 @@ class OutgoingViewFull(StatsView): # If we did anything intresting, we'd update our labels to reflect the new fit's stats here stats = [ - ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None), + ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None), ] counter = 0 diff --git a/gui/builtinStatsViews/outgoingViewMinimal.py b/gui/builtinStatsViews/outgoingViewMinimal.py index d2bf2c118..21fe9bede 100644 --- a/gui/builtinStatsViews/outgoingViewMinimal.py +++ b/gui/builtinStatsViews/outgoingViewMinimal.py @@ -43,7 +43,7 @@ class OutgoingViewMinimal(StatsView): parent = self.panel = contentPanel self.headerPanel = headerPanel - sizerOutgoing = wx.GridSizer(1, 4) + sizerOutgoing = wx.GridSizer(1, 4, 0, 0) contentSizer.Add(sizerOutgoing, 0, wx.EXPAND, 0) @@ -62,9 +62,9 @@ class OutgoingViewMinimal(StatsView): baseBox.Add(wx.StaticText(contentPanel, wx.ID_ANY, label), 0, wx.ALIGN_CENTER) if "Capacitor" in outgoingType: - lbl = wx.StaticText(parent, wx.ID_ANY, u"0 GJ/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0 GJ/s") else: - lbl = wx.StaticText(parent, wx.ID_ANY, u"0 HP/s") + lbl = wx.StaticText(parent, wx.ID_ANY, "0 HP/s") lbl.SetToolTip(wx.ToolTip(tooltip)) @@ -80,10 +80,10 @@ class OutgoingViewMinimal(StatsView): # If we did anything intresting, we'd update our labels to reflect the new fit's stats here stats = [ - ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, u"%s HP/s", None), - ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, u"%s GJ/s", None), + ("labelRemoteArmor", lambda: fit.remoteReps["Armor"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteShield", lambda: fit.remoteReps["Shield"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteHull", lambda: fit.remoteReps["Hull"], 3, 0, 0, "%s HP/s", None), + ("labelRemoteCapacitor", lambda: fit.remoteReps["Capacitor"], 3, 0, 0, "%s GJ/s", None), ] counter = 0 diff --git a/gui/builtinStatsViews/priceViewFull.py b/gui/builtinStatsViews/priceViewFull.py index b315c6161..9eb03ea22 100644 --- a/gui/builtinStatsViews/priceViewFull.py +++ b/gui/builtinStatsViews/priceViewFull.py @@ -19,9 +19,8 @@ # noinspection PyPackageRequirements import wx - -from gui.bitmapLoader import BitmapLoader from gui.statsView import StatsView +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from service.price import Price from service.settings import PriceMenuSettings @@ -50,7 +49,7 @@ class PriceViewFull(StatsView): headerContentSizer.Add(self.labelEMStatus) headerPanel.GetParent().AddToggleItem(self.labelEMStatus) - gridPrice = wx.GridSizer(2, 3) + gridPrice = wx.GridSizer(2, 3, 0, 0) contentSizer.Add(gridPrice, 0, wx.EXPAND | wx.ALL, 0) for _type in ("ship", "fittings", "total", "drones", "cargoBay", "character"): if _type in "ship": diff --git a/gui/builtinStatsViews/priceViewMinimal.py b/gui/builtinStatsViews/priceViewMinimal.py index 3a0af082c..ec1d5357f 100644 --- a/gui/builtinStatsViews/priceViewMinimal.py +++ b/gui/builtinStatsViews/priceViewMinimal.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from service.price import Price from service.settings import PriceMenuSettings @@ -49,7 +49,7 @@ class PriceViewMinimal(StatsView): headerContentSizer.Add(self.labelEMStatus) headerPanel.GetParent().AddToggleItem(self.labelEMStatus) - gridPrice = wx.GridSizer(1, 3) + gridPrice = wx.GridSizer(1, 3, 0, 0) contentSizer.Add(gridPrice, 0, wx.EXPAND | wx.ALL, 0) for _type in ("ship", "fittings", "total"): image = "%sPrice_big" % _type if _type != "ship" else "ship_big" diff --git a/gui/builtinStatsViews/rechargeViewFull.py b/gui/builtinStatsViews/rechargeViewFull.py index 18af61d73..eadcf537f 100644 --- a/gui/builtinStatsViews/rechargeViewFull.py +++ b/gui/builtinStatsViews/rechargeViewFull.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount import gui.mainFrame from gui.builtinStatsViews.resistancesViewFull import EFFECTIVE_HP_TOGGLED @@ -55,7 +55,7 @@ class RechargeViewFull(StatsView): self.panel = contentPanel self.headerPanel = headerPanel - sizerTankStats = wx.FlexGridSizer(3, 5) + sizerTankStats = wx.FlexGridSizer(3, 5, 0, 0) for i in range(4): sizerTankStats.AddGrowableCol(i + 1) diff --git a/gui/builtinStatsViews/resistancesViewFull.py b/gui/builtinStatsViews/resistancesViewFull.py index 9946310ec..684a0e617 100644 --- a/gui/builtinStatsViews/resistancesViewFull.py +++ b/gui/builtinStatsViews/resistancesViewFull.py @@ -20,11 +20,12 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader -from gui.pygauge import PyGauge +from gui.bitmap_loader import BitmapLoader +from gui.pyfa_gauge import PyGauge from gui.utils.numberFormatter import formatAmount import gui.mainFrame import gui.globalEvents as GE +from gui.utils import fonts EffectiveHpToggled, EFFECTIVE_HP_TOGGLED = wx.lib.newevent.NewEvent() @@ -93,7 +94,7 @@ class ResistancesViewFull(StatsView): self.stEHPs.Bind(wx.EVT_BUTTON, self.toggleEHP) - for i in xrange(4): + for i in range(4): sizerResistances.AddGrowableCol(i + 1) sizerResistances.Add(self.stEHPs, wx.GBPosition(row, col), wx.GBSpan(1, 1), wx.ALIGN_CENTER) @@ -122,6 +123,8 @@ class ResistancesViewFull(StatsView): continue currGColour = 0 + font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) + for damageType in ("em", "thermal", "kinetic", "explosive"): box = wx.BoxSizer(wx.HORIZONTAL) sizerResistances.Add(box, wx.GBPosition(row, col), wx.GBSpan(1, 1), wx.ALIGN_CENTER) @@ -133,7 +136,7 @@ class ResistancesViewFull(StatsView): bc = pgColour[1] currGColour += 1 - lbl = PyGauge(contentPanel, wx.ID_ANY, 100) + lbl = PyGauge(contentPanel, font, 100) lbl.SetMinSize((48, 16)) lbl.SetBackgroundColour(wx.Colour(bc[0], bc[1], bc[2])) lbl.SetBarColour(wx.Colour(fc[0], fc[1], fc[2])) diff --git a/gui/builtinStatsViews/resourcesViewFull.py b/gui/builtinStatsViews/resourcesViewFull.py index 3171aaa4b..77a1eaae5 100644 --- a/gui/builtinStatsViews/resourcesViewFull.py +++ b/gui/builtinStatsViews/resourcesViewFull.py @@ -20,10 +20,11 @@ # noinspection PyPackageRequirements import wx from gui.statsView import StatsView -from gui.bitmapLoader import BitmapLoader -from gui.pygauge import PyGauge +from gui.bitmap_loader import BitmapLoader +from gui.pyfa_gauge import PyGauge import gui.mainFrame -from gui.chromeTabs import EVT_NOTEBOOK_PAGE_CHANGED +from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED +from gui.utils import fonts from eos.saveddata.module import Hardpoint @@ -101,7 +102,7 @@ class ResourcesViewFull(StatsView): panel = "full" base = sizerResources - sizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + sizer.AddStretchSpacer() # Turrets & launcher hardslots display tooltipText = {"turret": "Turret hardpoints", "launcher": "Launcher hardpoints", "drones": "Drones active", "fighter": "Fighter squadrons active", "calibration": "Calibration"} @@ -133,7 +134,9 @@ class ResourcesViewFull(StatsView): # Hack - We add a spacer after each thing, but we are always hiding something. The spacer is stil there. # This way, we only have one space after the drones/fighters if type_ != "drones": - sizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) + sizer.AddStretchSpacer() + + gauge_font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) # PG, Cpu & drone stuff tooltipText = {"cpu": "CPU", "pg": "PowerGrid", "droneBay": "Drone bay", "fighterBay": "Fighter bay", @@ -169,14 +172,14 @@ class ResourcesViewFull(StatsView): setattr(self, "label%sTotal%s" % (panel.capitalize(), capitalizedType), lbl) absolute.Add(lbl, 0, wx.ALIGN_LEFT) - units = {"cpu": " tf", "pg": " MW", "droneBandwidth": " mbit/s", "droneBay": u" m\u00B3", - "fighterBay": u" m\u00B3", "cargoBay": u" m\u00B3"} + units = {"cpu": " tf", "pg": " MW", "droneBandwidth": " mbit/s", "droneBay": " m\u00B3", + "fighterBay": " m\u00B3", "cargoBay": " m\u00B3"} lbl = wx.StaticText(parent, wx.ID_ANY, "%s" % units[type_]) absolute.Add(lbl, 0, wx.ALIGN_LEFT) # Gauges modif. - Darriele - gauge = PyGauge(parent, wx.ID_ANY, 1) + gauge = PyGauge(parent, gauge_font, 1) gauge.SetValueRange(0, 0) gauge.SetMinSize((self.getTextExtentW("1.999M/1.99M MW"), 23)) gauge.SetFractionDigits(2) @@ -275,7 +278,7 @@ class ResourcesViewFull(StatsView): totalCalibrationPoints = value labelTCP = label - if isinstance(value, basestring): + if isinstance(value, str): label.SetLabel(value) label.SetToolTip(wx.ToolTip(value)) else: @@ -283,7 +286,7 @@ class ResourcesViewFull(StatsView): label.SetToolTip(wx.ToolTip("%.1f" % value)) colorWarn = wx.Colour(204, 51, 51) - colorNormal = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + colorNormal = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) if usedTurretHardpoints > totalTurretHardpoints: colorT = colorWarn diff --git a/gui/builtinStatsViews/targetingMiscViewFull.py b/gui/builtinStatsViews/targetingMiscViewFull.py index 0ee84b9dd..e43f04418 100644 --- a/gui/builtinStatsViews/targetingMiscViewFull.py +++ b/gui/builtinStatsViews/targetingMiscViewFull.py @@ -21,11 +21,7 @@ import wx from gui.statsView import StatsView from gui.utils.numberFormatter import formatAmount - -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict +from collections import OrderedDict class TargetingMiscViewFull(StatsView): @@ -48,13 +44,13 @@ class TargetingMiscViewFull(StatsView): self.panel = contentPanel self.headerPanel = headerPanel - gridTargetingMisc = wx.FlexGridSizer(1, 3) + gridTargetingMisc = wx.FlexGridSizer(1, 3, 0, 0) contentSizer.Add(gridTargetingMisc, 0, wx.EXPAND | wx.ALL, 0) gridTargetingMisc.AddGrowableCol(0) gridTargetingMisc.AddGrowableCol(2) # Targeting - gridTargeting = wx.FlexGridSizer(5, 2) + gridTargeting = wx.FlexGridSizer(5, 2, 0, 0) gridTargeting.AddGrowableCol(1) gridTargetingMisc.Add(gridTargeting, 0, wx.ALIGN_LEFT | wx.ALL, 5) @@ -79,7 +75,7 @@ class TargetingMiscViewFull(StatsView): # Misc gridTargetingMisc.Add(wx.StaticLine(contentPanel, wx.ID_ANY, style=wx.VERTICAL), 0, wx.EXPAND, 3) - gridMisc = wx.FlexGridSizer(5, 2) + gridMisc = wx.FlexGridSizer(5, 2, 0, 0) gridMisc.AddGrowableCol(1) gridTargetingMisc.Add(gridMisc, 0, wx.ALIGN_LEFT | wx.ALL, 5) @@ -87,7 +83,7 @@ class TargetingMiscViewFull(StatsView): ("Align time", "AlignTime", "s"), ("Signature", "SigRadius", "m"), ("Warp Speed", "WarpSpeed", "AU/s"), - ("Cargo", "Cargo", u"m\u00B3")) + ("Cargo", "Cargo", "m\u00B3")) for header, labelShort, unit in labels: gridMisc.Add(wx.StaticText(contentPanel, wx.ID_ANY, "%s: " % header), 0, wx.ALIGN_LEFT) @@ -157,7 +153,7 @@ class TargetingMiscViewFull(StatsView): ("labelFullAlignTime", {"main": lambda: fit.alignTime}, 3, 0, 0, "s"), ("labelFullSigRadius", {"main": lambda: fit.ship.getModifiedItemAttr("signatureRadius")}, 3, 0, 9, ""), ("labelFullWarpSpeed", {"main": lambda: fit.warpSpeed}, 3, 0, 0, "AU/s"), - ("labelFullCargo", cargoValues, 4, 0, 9, u"m\u00B3")) + ("labelFullCargo", cargoValues, 4, 0, 9, "m\u00B3")) counter = 0 RADII = [("Pod", 25), ("Interceptor", 33), ("Frigate", 38), @@ -167,13 +163,13 @@ class TargetingMiscViewFull(StatsView): for labelName, valueDict, prec, lowest, highest, unit in stats: label = getattr(self, labelName) newValues = {} - for valueAlias, value in valueDict.items(): + for valueAlias, value in list(valueDict.items()): value = value() if fit is not None else 0 value = value if value is not None else 0 newValues[valueAlias] = value if self._cachedValues[counter] != newValues: mainValue = newValues["main"] - otherValues = dict((k, newValues[k]) for k in filter(lambda k: k != "main", newValues)) + otherValues = dict((k, newValues[k]) for k in [k for k in newValues if k != "main"]) if labelName == "labelFullCargo": # Get sum of all cargoholds except for maintenance bay additionalCargo = sum(otherValues.values()) @@ -215,11 +211,11 @@ class TargetingMiscViewFull(StatsView): agility = "Agility:\t%.3fx" % (fit.ship.getModifiedItemAttr("agility") or 0) label.SetToolTip(wx.ToolTip("%s\n%s\n%s" % (alignTime, mass, agility))) elif labelName == "labelFullCargo": - tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, newValues["main"])] - for attrName, tipAlias in cargoNamesOrder.items(): + tipLines = ["Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, newValues["main"])] + for attrName, tipAlias in list(cargoNamesOrder.items()): if newValues[attrName] > 0: - tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, newValues[attrName])) - label.SetToolTip(wx.ToolTip(u"\n".join(tipLines))) + tipLines.append("{}: {:,.2f}m\u00B3".format(tipAlias, newValues[attrName])) + label.SetToolTip(wx.ToolTip("\n".join(tipLines))) else: label.SetToolTip(wx.ToolTip("%.1f" % mainValue)) else: @@ -246,11 +242,11 @@ class TargetingMiscViewFull(StatsView): cachedCargo = self._cachedValues[counter] # if you add stuff to cargo, the capacity doesn't change and thus it is still cached # This assures us that we force refresh of cargo tooltip - tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, cachedCargo["main"])] - for attrName, tipAlias in cargoNamesOrder.items(): + tipLines = ["Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, cachedCargo["main"])] + for attrName, tipAlias in list(cargoNamesOrder.items()): if cachedCargo[attrName] > 0: - tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, cachedCargo[attrName])) - label.SetToolTip(wx.ToolTip(u"\n".join(tipLines))) + tipLines.append("{}: {:,.2f}m\u00B3".format(tipAlias, cachedCargo[attrName])) + label.SetToolTip(wx.ToolTip("\n".join(tipLines))) else: label.SetToolTip(wx.ToolTip("")) diff --git a/gui/builtinStatsViews/targetingMiscViewMinimal.py b/gui/builtinStatsViews/targetingMiscViewMinimal.py index 489aacf52..9d4761bf3 100644 --- a/gui/builtinStatsViews/targetingMiscViewMinimal.py +++ b/gui/builtinStatsViews/targetingMiscViewMinimal.py @@ -21,11 +21,7 @@ import wx from gui.statsView import StatsView from gui.utils.numberFormatter import formatAmount - -try: - from collections import OrderedDict -except ImportError: - from utils.compat import OrderedDict +from collections import OrderedDict class TargetingMiscViewMinimal(StatsView): @@ -48,13 +44,13 @@ class TargetingMiscViewMinimal(StatsView): self.panel = contentPanel self.headerPanel = headerPanel - gridTargetingMisc = wx.FlexGridSizer(1, 3) + gridTargetingMisc = wx.FlexGridSizer(1, 3, 0, 0) contentSizer.Add(gridTargetingMisc, 0, wx.EXPAND | wx.ALL, 0) gridTargetingMisc.AddGrowableCol(0) gridTargetingMisc.AddGrowableCol(2) # Targeting - gridTargeting = wx.FlexGridSizer(5, 2) + gridTargeting = wx.FlexGridSizer(5, 2, 0, 0) gridTargeting.AddGrowableCol(1) gridTargetingMisc.Add(gridTargeting, 0, wx.ALIGN_LEFT | wx.ALL, 5) @@ -79,7 +75,7 @@ class TargetingMiscViewMinimal(StatsView): # Misc gridTargetingMisc.Add(wx.StaticLine(contentPanel, wx.ID_ANY, style=wx.VERTICAL), 0, wx.EXPAND, 3) - gridMisc = wx.FlexGridSizer(5, 2) + gridMisc = wx.FlexGridSizer(5, 2, 0, 0) gridMisc.AddGrowableCol(1) gridTargetingMisc.Add(gridMisc, 0, wx.ALIGN_LEFT | wx.ALL, 5) @@ -87,7 +83,7 @@ class TargetingMiscViewMinimal(StatsView): ("Align time", "AlignTime", "s"), ("Signature", "SigRadius", "m"), ("Warp Speed", "WarpSpeed", "AU/s"), - ("Cargo", "Cargo", u"m\u00B3")) + ("Cargo", "Cargo", "m\u00B3")) for header, labelShort, unit in labels: gridMisc.Add(wx.StaticText(contentPanel, wx.ID_ANY, "%s: " % header), 0, wx.ALIGN_LEFT) @@ -154,7 +150,7 @@ class TargetingMiscViewMinimal(StatsView): ("labelFullAlignTime", {"main": lambda: fit.alignTime}, 3, 0, 0, "s"), ("labelFullSigRadius", {"main": lambda: fit.ship.getModifiedItemAttr("signatureRadius")}, 3, 0, 9, ""), ("labelFullWarpSpeed", {"main": lambda: fit.warpSpeed}, 3, 0, 0, "AU/s"), - ("labelFullCargo", cargoValues, 4, 0, 9, u"m\u00B3")) + ("labelFullCargo", cargoValues, 4, 0, 9, "m\u00B3")) counter = 0 RADII = [("Pod", 25), ("Interceptor", 33), ("Frigate", 38), @@ -164,13 +160,13 @@ class TargetingMiscViewMinimal(StatsView): for labelName, valueDict, prec, lowest, highest, unit in stats: label = getattr(self, labelName) newValues = {} - for valueAlias, value in valueDict.items(): + for valueAlias, value in list(valueDict.items()): value = value() if fit is not None else 0 value = value if value is not None else 0 newValues[valueAlias] = value if self._cachedValues[counter] != newValues: mainValue = newValues["main"] - otherValues = dict((k, newValues[k]) for k in filter(lambda k: k != "main", newValues)) + otherValues = dict((k, newValues[k]) for k in [k for k in newValues if k != "main"]) if labelName == "labelFullCargo": # Get sum of all cargoholds except for maintenance bay additionalCargo = sum(otherValues.values()) @@ -209,11 +205,11 @@ class TargetingMiscViewMinimal(StatsView): agility = "Agility:\t%.3fx" % (fit.ship.getModifiedItemAttr("agility") or 0) label.SetToolTip(wx.ToolTip("%s\n%s\n%s" % (alignTime, mass, agility))) elif labelName == "labelFullCargo": - tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, newValues["main"])] - for attrName, tipAlias in cargoNamesOrder.items(): + tipLines = ["Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, newValues["main"])] + for attrName, tipAlias in list(cargoNamesOrder.items()): if newValues[attrName] > 0: - tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, newValues[attrName])) - label.SetToolTip(wx.ToolTip(u"\n".join(tipLines))) + tipLines.append("{}: {:,.2f}m\u00B3".format(tipAlias, newValues[attrName])) + label.SetToolTip(wx.ToolTip("\n".join(tipLines))) else: label.SetToolTip(wx.ToolTip("%.1f" % mainValue)) else: @@ -242,11 +238,11 @@ class TargetingMiscViewMinimal(StatsView): cachedCargo = self._cachedValues[counter] # if you add stuff to cargo, the capacity doesn't change and thus it is still cached # This assures us that we force refresh of cargo tooltip - tipLines = [u"Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, cachedCargo["main"])] - for attrName, tipAlias in cargoNamesOrder.items(): + tipLines = ["Cargohold: {:,.2f}m\u00B3 / {:,.2f}m\u00B3".format(fit.cargoBayUsed, cachedCargo["main"])] + for attrName, tipAlias in list(cargoNamesOrder.items()): if cachedCargo[attrName] > 0: - tipLines.append(u"{}: {:,.2f}m\u00B3".format(tipAlias, cachedCargo[attrName])) - label.SetToolTip(wx.ToolTip(u"\n".join(tipLines))) + tipLines.append("{}: {:,.2f}m\u00B3".format(tipAlias, cachedCargo[attrName])) + label.SetToolTip(wx.ToolTip("\n".join(tipLines))) else: label.SetToolTip(wx.ToolTip("")) diff --git a/gui/builtinViewColumns/ammo.py b/gui/builtinViewColumns/ammo.py index 069328a32..862148285 100644 --- a/gui/builtinViewColumns/ammo.py +++ b/gui/builtinViewColumns/ammo.py @@ -21,7 +21,7 @@ import wx from eos.saveddata.fighter import Fighter from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class Ammo(ViewColumn): diff --git a/gui/builtinViewColumns/attributeDisplay.py b/gui/builtinViewColumns/attributeDisplay.py index 855bfe2a1..7ad8743d7 100644 --- a/gui/builtinViewColumns/attributeDisplay.py +++ b/gui/builtinViewColumns/attributeDisplay.py @@ -21,7 +21,7 @@ import wx from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from service.attribute import Attribute @@ -72,7 +72,7 @@ class AttributeDisplay(ViewColumn): def getText(self, mod): if hasattr(mod, "item"): - attr = mod.getModifiedItemAttr(self.info.name) + attr = mod.getModifiedItemAttr(self.info.name, None) else: if self.direct: info = self.directInfo @@ -80,16 +80,19 @@ class AttributeDisplay(ViewColumn): else: attr = mod.getAttribute(self.info.name) + if attr is None: + return "" + if self.info.name == "volume": str_ = (formatAmount(attr, 3, 0, 3)) if hasattr(mod, "amount"): - str_ += u"m\u00B3 (%s m\u00B3)" % (formatAmount(attr * mod.amount, 3, 0, 3)) + str_ += "m\u00B3 (%s m\u00B3)" % (formatAmount(attr * mod.amount, 3, 0, 3)) attr = str_ if isinstance(attr, (float, int)): attr = (formatAmount(attr, 3, 0, 3)) - return attr if attr is not None else "" + return attr def getImageId(self, mod): return -1 diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py index 3a8bc6fa7..8ed1466ac 100644 --- a/gui/builtinViewColumns/baseName.py +++ b/gui/builtinViewColumns/baseName.py @@ -71,9 +71,9 @@ class BaseName(ViewColumn): elif isinstance(stuff, Rack): if FitSvc.getInstance().serviceFittingOptions["rackLabels"]: if stuff.slot == Slot.MODE: - return u'─ Tactical Mode ─' + return '─ Tactical Mode ─' else: - return u'─ {} Slots ─'.format(Slot.getName(stuff.slot).capitalize()) + return '─ {} Slots ─'.format(Slot.getName(stuff.slot).capitalize()) else: return "" elif isinstance(stuff, Module): @@ -91,7 +91,7 @@ class BaseName(ViewColumn): if marketShortcut: # use unicode subscript to display shortcut value - shortcut = unichr(marketShortcut + 8320) + u" " + shortcut = chr(marketShortcut + 8320) + " " del item.marketShortcut return shortcut + item.name diff --git a/gui/builtinViewColumns/capacitorUse.py b/gui/builtinViewColumns/capacitorUse.py index 5d0287375..8770a9d4c 100644 --- a/gui/builtinViewColumns/capacitorUse.py +++ b/gui/builtinViewColumns/capacitorUse.py @@ -24,7 +24,7 @@ from eos.saveddata.mode import Mode from service.attribute import Attribute from gui.utils.numberFormatter import formatAmount from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class CapacitorUse(ViewColumn): diff --git a/gui/builtinViewColumns/maxRange.py b/gui/builtinViewColumns/maxRange.py index d4b3367da..ef420c344 100644 --- a/gui/builtinViewColumns/maxRange.py +++ b/gui/builtinViewColumns/maxRange.py @@ -23,7 +23,7 @@ import wx from eos.saveddata.mode import Mode from service.attribute import Attribute from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount @@ -58,7 +58,7 @@ class MaxRange(ViewColumn): if isinstance(stuff, Mode): return "" - maxRange = stuff.maxRange if hasattr(stuff, "maxRange") else stuff.getModifiedItemAttr("maxRange") + maxRange = stuff.maxRange if hasattr(stuff, "maxRange") else stuff.getModifiedItemAttr("maxRange", None) falloff = stuff.falloff if falloff: falloff = "+%sm" % formatAmount(falloff, 3, 0, 3) diff --git a/gui/builtinViewColumns/misc.py b/gui/builtinViewColumns/misc.py index e122b1e27..d89db812e 100644 --- a/gui/builtinViewColumns/misc.py +++ b/gui/builtinViewColumns/misc.py @@ -24,7 +24,7 @@ from service.fit import Fit from service.market import Market import gui.mainFrame from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount from gui.utils.listFormatter import formatList from eos.saveddata.drone import Drone @@ -223,7 +223,7 @@ class Miscellanea(ViewColumn): "falloff range": falloffRangeBonus, "tracking speed": trackingSpeedBonus} - isTrackingDisruptor = any(map(lambda x: x is not None and x != 0, trackingDisruptorAttributes.values())) + isTrackingDisruptor = any([x is not None and x != 0 for x in list(trackingDisruptorAttributes.values())]) # Then get the attributes for guidance disruptors explosionVelocityBonus = stuff.getModifiedItemAttr("aoeVelocityBonus") @@ -238,7 +238,7 @@ class Miscellanea(ViewColumn): "flight time": flightTimeBonus, "missile velocity": missileVelocityBonus} - isGuidanceDisruptor = any(map(lambda x: x is not None and x != 0, guidanceDisruptorAttributes.values())) + isGuidanceDisruptor = any([x is not None and x != 0 for x in list(guidanceDisruptorAttributes.values())]) if isTrackingDisruptor: attributes = trackingDisruptorAttributes @@ -247,12 +247,12 @@ class Miscellanea(ViewColumn): else: return "", None - display = max(attributes.values(), key=lambda x: abs(x)) + display = max(list(attributes.values()), key=lambda x: abs(x)) text = "{0}%".format(formatAmount(display, 3, 0, 3, forceSign=True)) ttEntries = [] - for attributeName, attributeValue in attributes.items(): + for attributeName, attributeValue in list(attributes.items()): if attributeValue == display: ttEntries.append(attributeName) diff --git a/gui/builtinViewColumns/price.py b/gui/builtinViewColumns/price.py index 3797052a2..728376bef 100644 --- a/gui/builtinViewColumns/price.py +++ b/gui/builtinViewColumns/price.py @@ -24,7 +24,7 @@ from eos.saveddata.cargo import Cargo from eos.saveddata.drone import Drone from service.price import Price as ServicePrice from gui.viewColumn import ViewColumn -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.numberFormatter import formatAmount diff --git a/gui/builtinViews/emptyView.py b/gui/builtinViews/emptyView.py index ebf667726..3e6d2b467 100644 --- a/gui/builtinViews/emptyView.py +++ b/gui/builtinViews/emptyView.py @@ -1,7 +1,7 @@ # noinspection PyPackageRequirements import wx import gui.globalEvents as GE -from gui.chromeTabs import EVT_NOTEBOOK_PAGE_CHANGED +from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED import gui.mainFrame @@ -13,12 +13,13 @@ class BlankPage(wx.Panel): self.parent = parent self.parent.Bind(EVT_NOTEBOOK_PAGE_CHANGED, self.pageChanged) - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=None)) def Destroy(self): - self.parent.Unbind(EVT_NOTEBOOK_PAGE_CHANGED, handler=self.pageChanged) + # todo: This unbind caused fits to not recalc when switching to their tabs; find out why + # self.parent.Unbind(EVT_NOTEBOOK_PAGE_CHANGED) wx.Panel.Destroy(self) def pageChanged(self, event): diff --git a/gui/builtinViews/entityEditor.py b/gui/builtinViews/entityEditor.py index 6bde57a11..b706c4a76 100644 --- a/gui/builtinViews/entityEditor.py +++ b/gui/builtinViews/entityEditor.py @@ -1,11 +1,11 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader -class BaseValidator(wx.PyValidator): +class BaseValidator(wx.Validator): def __init__(self): - wx.PyValidator.__init__(self) + wx.Validator.__init__(self) def Validate(self, win): raise NotImplementedError() @@ -22,7 +22,9 @@ class TextEntryValidatedDialog(wx.TextEntryDialog): wx.TextEntryDialog.__init__(self, parent, *args, **kargs) self.parent = parent - self.txtctrl = self.FindWindowById(3000) + # See https://github.com/wxWidgets/Phoenix/issues/611 + self.txtctrl = self.FindWindowById(3000, self) + if validator: self.txtctrl.SetValidator(validator()) @@ -42,7 +44,7 @@ class EntityEditor(wx.Panel): self.choices = [] self.choices.sort(key=lambda p: p.name) - self.entityChoices = wx.Choice(self, choices=map(lambda p: p.name, self.choices)) + self.entityChoices = wx.Choice(self, choices=[p.name for p in self.choices]) self.navSizer.Add(self.entityChoices, 1, wx.ALL, 5) buttons = (("new", wx.ART_NEW, self.OnNew), @@ -60,7 +62,7 @@ class EntityEditor(wx.Panel): btn.SetMinSize(size) btn.SetMaxSize(size) - btn.SetToolTipString("{} {}".format(name.capitalize(), self.entityName)) + btn.SetToolTip("{} {}".format(name.capitalize(), self.entityName)) btn.Bind(wx.EVT_BUTTON, func) setattr(self, "btn%s" % name.capitalize(), btn) self.navSizer.Add(btn, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 2) @@ -96,8 +98,8 @@ class EntityEditor(wx.Panel): def OnNew(self, event): dlg = TextEntryValidatedDialog(self, self.validator, - u"Enter a name for your new {}:".format(self.entityName), - u"New {}".format(self.entityName)) + "Enter a name for your new {}:".format(self.entityName), + "New {}".format(self.entityName)) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_OK: @@ -110,10 +112,10 @@ class EntityEditor(wx.Panel): def OnCopy(self, event): dlg = TextEntryValidatedDialog(self, self.validator, - u"Enter a name for your {} copy:".format(self.entityName), - u"Copy {}".format(self.entityName)) + "Enter a name for your {} copy:".format(self.entityName), + "Copy {}".format(self.entityName)) active = self.getActiveEntity() - dlg.SetValue(u"{} Copy".format(active.name)) + dlg.SetValue("{} Copy".format(active.name)) dlg.txtctrl.SetInsertionPointEnd() dlg.CenterOnParent() @@ -124,8 +126,8 @@ class EntityEditor(wx.Panel): def OnRename(self, event): dlg = TextEntryValidatedDialog(self, self.validator, - u"Enter a new name for your {}:".format(self.entityName), - u"Rename {}".format(self.entityName)) + "Enter a new name for your {}:".format(self.entityName), + "Rename {}".format(self.entityName)) active = self.getActiveEntity() dlg.SetValue(active.name) dlg.txtctrl.SetInsertionPointEnd() @@ -138,9 +140,9 @@ class EntityEditor(wx.Panel): def OnDelete(self, event): dlg = wx.MessageDialog(self, - u"Do you really want to delete the {} {}?".format(self.getActiveEntity().name, + "Do you really want to delete the {} {}?".format(self.getActiveEntity().name, self.entityName), - u"Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) + "Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) dlg.CenterOnParent() if dlg.ShowModal() == wx.ID_YES: @@ -152,7 +154,7 @@ class EntityEditor(wx.Panel): self.choices = self.getEntitiesFromContext() self.entityChoices.Clear() - self.entityChoices.AppendItems(map(lambda p: p.name, self.choices)) + self.entityChoices.AppendItems([p.name for p in self.choices]) if selected: idx = self.choices.index(selected) self.entityChoices.SetSelection(idx) diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index 5702503f3..9e4e65904 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -25,15 +25,15 @@ import gui.mainFrame from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED import gui.display as d from gui.contextMenu import ContextMenu -import gui.builtinShipBrowser.events as sbEvents +from gui.builtinShipBrowser.events import EVT_FIT_RENAMED, EVT_FIT_REMOVED, FitSelected, EVT_FIT_SELECTED import gui.multiSwitch from eos.saveddata.mode import Mode from eos.saveddata.module import Module, Slot, Rack from gui.builtinViewColumns.state import State -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import gui.builtinViews.emptyView from logbook import Logger -from gui.chromeTabs import EVT_NOTEBOOK_PAGE_CHANGED +from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED from service.fit import Fit from service.market import Market @@ -50,12 +50,13 @@ class FitSpawner(gui.multiSwitch.TabSpawner): def __init__(self, multiSwitch): self.multiSwitch = multiSwitch self.mainFrame = mainFrame = gui.mainFrame.MainFrame.getInstance() - mainFrame.Bind(sbEvents.EVT_FIT_SELECTED, self.fitSelected) - self.multiSwitch.tabsContainer.handleDrag = self.handleDrag + mainFrame.Bind(EVT_FIT_SELECTED, self.fitSelected) + self.multiSwitch.tabs_container.handleDrag = self.handleDrag def fitSelected(self, event): count = -1 - for index, page in enumerate(self.multiSwitch.pages): + # @todo pheonix: _pages is supposed to be private? + for index, page in enumerate(self.multiSwitch._pages): if not isinstance(page, gui.builtinViews.emptyView.BlankPage): # Don't try and process it if it's a blank page. try: if page.activeFitID == event.fitID: @@ -76,13 +77,18 @@ class FitSpawner(gui.multiSwitch.TabSpawner): if from_import or (not openFitInNew and mstate.CmdDown()) or startup or (openFitInNew and not mstate.CmdDown()): self.multiSwitch.AddPage() - view = FittingView(self.multiSwitch) - self.multiSwitch.ReplaceActivePage(view) + view = self.multiSwitch.GetSelectedPage() + + if not isinstance(view, FittingView): + view = FittingView(self.multiSwitch) + print("###################### Created new view:" + repr(view)) + self.multiSwitch.ReplaceActivePage(view) + view.fitSelected(event) def handleDrag(self, type, fitID): if type == "fit": - for page in self.multiSwitch.pages: + for page in self.multiSwitch._pages: if isinstance(page, FittingView) and page.activeFitID == fitID: index = self.multiSwitch.GetPageIndex(page) self.multiSwitch.SetSelection(index) @@ -103,12 +109,12 @@ FitSpawner.register() # Drag'n'drop handler -class FittingViewDrop(wx.PyDropTarget): +class FittingViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(FittingViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID - self.dropData = wx.PyTextDataObject() + self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): @@ -139,8 +145,8 @@ class FittingView(d.Display): self.Show(False) self.parent = parent self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) - self.mainFrame.Bind(sbEvents.EVT_FIT_RENAMED, self.fitRenamed) - self.mainFrame.Bind(sbEvents.EVT_FIT_REMOVED, self.fitRemoved) + self.mainFrame.Bind(EVT_FIT_RENAMED, self.fitRenamed) + self.mainFrame.Bind(EVT_FIT_REMOVED, self.fitRemoved) self.mainFrame.Bind(ITEM_SELECTED, self.appendItem) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) @@ -166,6 +172,8 @@ class FittingView(d.Display): self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) self.parent.Bind(EVT_NOTEBOOK_PAGE_CHANGED, self.pageChanged) + print("------------------ new fitting view -------------------") + print(self) def OnLeaveWindow(self, event): self.SetToolTip(None) @@ -185,7 +193,7 @@ class FittingView(d.Display): mod = self.mods[self.GetItemData(row)] tooltip = self.activeColumns[col].getToolTip(mod) if tooltip is not None: - self.SetToolTipString(tooltip) + self.SetToolTip(tooltip) else: self.SetToolTip(None) else: @@ -211,14 +219,15 @@ class FittingView(d.Display): def handleDrag(self, type, fitID): # Those are drags coming from pyfa sources, NOT builtin wx drags if type == "fit": - wx.PostEvent(self.mainFrame, sbEvents.FitSelected(fitID=fitID)) + wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def Destroy(self): - self.parent.Unbind(EVT_NOTEBOOK_PAGE_CHANGED, handler=self.pageChanged) - self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.fitChanged) - self.mainFrame.Unbind(sbEvents.EVT_FIT_RENAMED, handler=self.fitRenamed) - self.mainFrame.Unbind(sbEvents.EVT_FIT_REMOVED, handler=self.fitRemoved) - self.mainFrame.Unbind(ITEM_SELECTED, handler=self.appendItem) + print("+++++ Destroy " + repr(self)) + print(self.parent.Unbind(EVT_NOTEBOOK_PAGE_CHANGED)) + print(self.mainFrame.Unbind(GE.FIT_CHANGED)) + print(self.mainFrame.Unbind(EVT_FIT_RENAMED)) + print(self.mainFrame.Unbind(EVT_FIT_REMOVED)) + print(self.mainFrame.Unbind(ITEM_SELECTED)) d.Display.Destroy(self) @@ -238,7 +247,7 @@ class FittingView(d.Display): row = event.GetIndex() if row != -1 and row not in self.blanks and isinstance(self.mods[row], Module) and not self.mods[row].isEmpty: - data = wx.PyTextDataObject() + data = wx.TextDataObject() dataStr = "fitting:" + str(self.mods[row].modPosition) data.SetText(dataStr) @@ -278,20 +287,26 @@ class FittingView(d.Display): If fit is removed and active, the page is deleted. We also refresh the fit of the new current page in case delete fit caused change in stats (projected) + todo: move this to the notebook, not the page. We don't want the page being responsible for deleting itself """ + print('_+_+_+_+_+_ Fit Removed: {} {} activeFitID: {}, eventFitID: {}'.format(repr(self), str(bool(self)), self.activeFitID, event.fitID)) pyfalog.debug("FittingView::fitRemoved") if event.fitID == self.getActiveFit(): pyfalog.debug(" Deleted fit is currently active") self.parent.DeletePage(self.parent.GetPageIndex(self)) - try: - # Sometimes there is no active page after deletion, hence the try block - sFit = Fit.getInstance() - sFit.refreshFit(self.getActiveFit()) - wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID)) - except wx._core.PyDeadObjectError: - pyfalog.warning("Caught dead object") - pass + try: + # Sometimes there is no active page after deletion, hence the try block + sFit = Fit.getInstance() + + # stopgap for #1384 + fit = sFit.getFit(self.getActiveFit()) + if fit: + sFit.refreshFit(self.getActiveFit()) + wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.activeFitID)) + except RuntimeError: + pyfalog.warning("Caught dead object") + pass event.Skip() @@ -303,6 +318,8 @@ class FittingView(d.Display): event.Skip() def fitSelected(self, event): + print('====== Fit Selected: ' + repr(self) + str(bool(self))) + if self.parent.IsActive(self): fitID = event.fitID startup = getattr(event, "startup", False) @@ -313,6 +330,7 @@ class FittingView(d.Display): self.Show(fitID is not None) self.slotsChanged() sFit.switchFit(fitID) + # @todo pheonix: had to disable this as it was causing a crash at the wxWidgets level. Dunno why, investigate wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) event.Skip() @@ -322,7 +340,7 @@ class FittingView(d.Display): fit = sFit.getFit(self.getActiveFit(), basic=True) bitmap = BitmapLoader.getImage("race_%s_small" % fit.ship.item.race, "gui") - text = u"%s: %s" % (fit.ship.item.name, fit.name) + text = "%s: %s" % (fit.ship.item.name, fit.name) pageIndex = self.parent.GetPageIndex(self) if pageIndex is not None: @@ -509,6 +527,8 @@ class FittingView(d.Display): self.populate(self.mods) def fitChanged(self, event): + print('====== Fit Changed: {} {} activeFitID: {}, eventFitID: {}'.format(repr(self), str(bool(self)), self.activeFitID, event.fitID)) + try: if self.activeFitID is not None and self.activeFitID == event.fitID: self.generateMods() @@ -519,7 +539,7 @@ class FittingView(d.Display): self.Refresh() self.Show(self.activeFitID is not None and self.activeFitID == event.fitID) - except wx._core.PyDeadObjectError: + except RuntimeError: pyfalog.error("Caught dead object") finally: event.Skip() @@ -604,14 +624,14 @@ class FittingView(d.Display): sFit = Fit.getInstance() fitID = self.mainFrame.getActiveFit() - ctrl = wx.GetMouseState().CmdDown() or wx.GetMouseState().MiddleDown() + ctrl = event.cmdDown or event.middleIsDown click = "ctrl" if ctrl is True else "right" if event.GetButton() == 3 else "left" sFit.toggleModulesState(fitID, self.mods[self.GetItemData(row)], mods, click) # update state tooltip tooltip = self.activeColumns[col].getToolTip(self.mods[self.GetItemData(row)]) if tooltip: - self.SetToolTipString(tooltip) + self.SetToolTip(tooltip) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit())) else: @@ -645,7 +665,7 @@ class FittingView(d.Display): slot = Slot.getValue(slotType) slotMap[slot] = fit.getSlotsFree(slot) < 0 - font = (self.GetClassDefaultAttributes()).font + font = wx.Font(self.GetClassDefaultAttributes().font) for i, mod in enumerate(self.mods): self.SetItemBackgroundColour(i, self.GetBackgroundColour()) @@ -670,15 +690,15 @@ class FittingView(d.Display): self.Thaw() self.itemCount = self.GetItemCount() - if 'wxMac' in wx.PlatformInfo: - try: - self.MakeSnapshot() - except Exception as e: - pyfalog.critical("Failed to make snapshot") - pyfalog.critical(e) + # if 'wxMac' in wx.PlatformInfo: + # try: + # self.MakeSnapshot() + # except Exception as e: + # pyfalog.critical("Failed to make snapshot") + # pyfalog.critical(e) def OnShow(self, event): - if event.GetShow(): + if self and not self.IsShown(): try: self.MakeSnapshot() except Exception as e: @@ -691,14 +711,13 @@ class FittingView(d.Display): # noinspection PyPropertyAccess def MakeSnapshot(self, maxColumns=1337): - if self.FVsnapshot: del self.FVsnapshot - tbmp = wx.EmptyBitmap(16, 16) + tbmp = wx.Bitmap(16, 16) tdc = wx.MemoryDC() tdc.SelectObject(tbmp) - font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) + font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) tdc.SetFont(font) columnsWidths = [] @@ -711,6 +730,7 @@ class FittingView(d.Display): except Exception as e: pyfalog.critical("Failed to get fit") pyfalog.critical(e) + return if fit is None: return @@ -732,7 +752,7 @@ class FittingView(d.Display): break name = col.getText(st) - if not isinstance(name, basestring): + if not isinstance(name, str): name = "" nx, ny = tdc.GetTextExtent(name) @@ -762,7 +782,7 @@ class FittingView(d.Display): name = col.columnText imgId = col.imageId - if not isinstance(name, basestring): + if not isinstance(name, str): name = "" opts = wx.HeaderButtonParams() @@ -771,7 +791,7 @@ class FittingView(d.Display): opts.m_labelText = name if imgId != -1: - opts.m_labelBitmap = wx.EmptyBitmap(isize, isize) + opts.m_labelBitmap = wx.Bitmap(isize, isize) width = render.DrawHeaderButton(self, tdc, (0, 0, 16, 16), sortArrow=wx.HDR_SORT_ICON_NONE, params=opts) @@ -787,15 +807,15 @@ class FittingView(d.Display): maxWidth += columnsWidths[i] mdc = wx.MemoryDC() - mbmp = wx.EmptyBitmap(maxWidth, maxRowHeight * rows + padding * 4 + headerSize) + mbmp = wx.Bitmap(maxWidth, maxRowHeight * rows + padding * 4 + headerSize) mdc.SelectObject(mbmp) - mdc.SetBackground(wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))) + mdc.SetBackground(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))) mdc.Clear() mdc.SetFont(font) - mdc.SetTextForeground(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)) + mdc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) cx = padding for i, col in enumerate(self.activeColumns): @@ -805,7 +825,7 @@ class FittingView(d.Display): name = col.columnText imgId = col.imageId - if not isinstance(name, basestring): + if not isinstance(name, str): name = "" opts = wx.HeaderButtonParams() @@ -839,7 +859,7 @@ class FittingView(d.Display): break name = col.getText(st) - if not isinstance(name, basestring): + if not isinstance(name, str): name = "" imgId = col.getImageId(st) diff --git a/gui/builtinViews/implantEditor.py b/gui/builtinViews/implantEditor.py index 827d514a2..b391c1812 100644 --- a/gui/builtinViews/implantEditor.py +++ b/gui/builtinViews/implantEditor.py @@ -5,7 +5,7 @@ from wx.lib.buttons import GenBitmapButton import gui.builtinMarketBrowser.pfSearchBox as SBox import gui.display as d -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.marketBrowser import SearchBox from service.market import Market @@ -23,7 +23,7 @@ class BaseImplantEditorView(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL) - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) pmainSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -54,7 +54,7 @@ class BaseImplantEditorView(wx.Panel): pmainSizer.Add(availableSizer, 1, wx.ALL | wx.EXPAND, 5) buttonSizer = wx.BoxSizer(wx.VERTICAL) - buttonSizer.AddSpacer((0, 0), 1) + buttonSizer.AddStretchSpacer() self.btnAdd = GenBitmapButton(self, wx.ID_ADD, BitmapLoader.getBitmap("fit_add_small", "gui"), style=wx.BORDER_NONE) @@ -64,7 +64,7 @@ class BaseImplantEditorView(wx.Panel): style=wx.BORDER_NONE) buttonSizer.Add(self.btnRemove, 0) - buttonSizer.AddSpacer((0, 0), 1) + buttonSizer.AddStretchSpacer() pmainSizer.Add(buttonSizer, 0, wx.EXPAND, 0) characterImplantSizer = wx.BoxSizer(wx.VERTICAL) @@ -79,7 +79,7 @@ class BaseImplantEditorView(wx.Panel): sMkt = Market.getInstance() for mktGrp in sMkt.getImplantTree(): iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(mktGrp)) - childId = self.availableImplantsTree.AppendItem(root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) + childId = self.availableImplantsTree.AppendItem(root, mktGrp.name, iconId, data=mktGrp.ID) if sMkt.marketGroupHasTypesCheck(mktGrp) is False: self.availableImplantsTree.AppendItem(childId, "dummy") @@ -142,10 +142,10 @@ class BaseImplantEditorView(wx.Panel): # if the dummy item is a market group, replace with actual market groups if text == "dummy": # Add 'real stoof!' instead - currentMktGrp = sMkt.getMarketGroup(tree.GetPyData(parent), eager="children") + currentMktGrp = sMkt.getMarketGroup(tree.GetItemData(parent), eager="children") for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(childMktGrp)) - childId = tree.AppendItem(parent, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) + childId = tree.AppendItem(parent, childMktGrp.name, iconId, data=childMktGrp.ID) if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: tree.AppendItem(childId, "dummy") else: @@ -153,11 +153,11 @@ class BaseImplantEditorView(wx.Panel): # replace dummy with actual items if text == "itemdummy": - currentMktGrp = sMkt.getMarketGroup(tree.GetPyData(parent)) + currentMktGrp = sMkt.getMarketGroup(tree.GetItemData(parent)) items = sMkt.getItemsByMarketGroup(currentMktGrp) for item in items: iconId = self.addMarketViewImage(item.icon.iconFile) - tree.AppendItem(parent, item.name, iconId, data=wx.TreeItemData(item)) + tree.AppendItem(parent, item.name, iconId, data=item) tree.SortChildren(parent) @@ -185,7 +185,7 @@ class BaseImplantEditorView(wx.Panel): nchilds = self.availableImplantsTree.GetChildrenCount(root) if nchilds == 0: - item = self.availableImplantsTree.GetPyData(root) + item = self.availableImplantsTree.GetItemData(root) self.addImplantToContext(item) else: event.Skip() diff --git a/gui/cachingImageList.py b/gui/cachingImageList.py index 4bbef1e23..578da97c7 100644 --- a/gui/cachingImageList.py +++ b/gui/cachingImageList.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class CachingImageList(wx.ImageList): diff --git a/gui/characterEditor.py b/gui/characterEditor.py index 3ef4b1ba8..f7520e42c 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -19,27 +19,33 @@ # noinspection PyPackageRequirements import wx +import wx.dataview +import wx.lib.agw.hyperlink -from utils.floatspin import FloatSpin # noinspection PyPackageRequirements import wx.lib.newevent # noinspection PyPackageRequirements -import wx.gizmos -from gui.bitmapLoader import BitmapLoader +from wx.dataview import TreeListCtrl +from gui.bitmap_loader import BitmapLoader from gui.contextMenu import ContextMenu import gui.globalEvents as GE from gui.builtinViews.implantEditor import BaseImplantEditorView from gui.builtinViews.entityEditor import EntityEditor, BaseValidator, TextEntryValidatedDialog from service.fit import Fit from service.character import Character +from service.esi import Esi from service.network import AuthenticationError, TimeoutError from service.market import Market from logbook import Logger +from wx.lib.agw.floatspin import FloatSpin + + from gui.utils.clipboard import toClipboard, fromClipboard -import utils.roman as roman +import roman import re +import webbrowser pyfalog = Logger(__name__) @@ -72,9 +78,9 @@ class CharacterTextValidor(BaseValidator): raise ValueError("Character name already in use, please choose another.") return True - except ValueError, e: + except ValueError as e: pyfalog.error(e) - wx.MessageBox(u"{}".format(e), "Error") + wx.MessageBox("{}".format(e), "Error") textCtrl.SetFocus() return False @@ -143,10 +149,10 @@ class CharacterEntityEditor(EntityEditor): class CharacterEditor(wx.Frame): def __init__(self, parent): - wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition, + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="pyfa: Character Editor", pos=wx.DefaultPosition, size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER) - i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("character_small", "gui")) self.SetIcon(i) self.mainFrame = parent @@ -170,7 +176,7 @@ class CharacterEditor(wx.Frame): self.viewsNBContainer.AddPage(self.sview, "Skills") self.viewsNBContainer.AddPage(self.iview, "Implants") - self.viewsNBContainer.AddPage(self.aview, "API") + self.viewsNBContainer.AddPage(self.aview, "EVE SSO") mainSizer.Add(self.viewsNBContainer, 1, wx.EXPAND | wx.ALL, 5) @@ -303,7 +309,7 @@ class SkillTreeView(wx.Panel): wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL) self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) pmainSizer = wx.BoxSizer(wx.VERTICAL) @@ -338,35 +344,38 @@ class SkillTreeView(wx.Panel): self.searchTimer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.populateSkillTreeSkillSearch, self.searchTimer) - tree = self.skillTreeListCtrl = wx.gizmos.TreeListCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + tree = self.skillTreeListCtrl = TreeListCtrl(self, wx.ID_ANY, style=wx.dataview.TL_DEFAULT_STYLE) pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 5) self.imageList = wx.ImageList(16, 16) tree.SetImageList(self.imageList) - self.skillBookImageId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) + self.skillBookImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small", "gui"))) + self.skillBookDirtyImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small_red", "gui"))) - tree.AddColumn("Skill") - tree.AddColumn("Level") - tree.SetMainColumn(0) + tree.AppendColumn("Skill") + tree.AppendColumn("Level") + # tree.SetMainColumn(0) - self.root = tree.AddRoot("Skills") - tree.SetItemText(self.root, "Levels", 1) + self.root = tree.GetRootItem() + # self.root = tree.AppendItem(root, "Skills") + # + # tree.SetItemText(self.root, 1, "Levels") - tree.SetColumnWidth(0, 500) + # tree.SetColumnWidth(0, 300) self.btnSecStatus = wx.Button(self, wx.ID_ANY, "Sec Status: {0:.2f}".format(char.secStatus or 0.0)) self.btnSecStatus.Bind(wx.EVT_BUTTON, self.onSecStatus) self.populateSkillTree() - tree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) - tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu) + tree.Bind(wx.dataview.EVT_TREELIST_ITEM_EXPANDING, self.expandLookup) + tree.Bind(wx.dataview.EVT_TREELIST_ITEM_CONTEXT_MENU, self.scheduleMenu) bSizerButtons = wx.BoxSizer(wx.HORIZONTAL) bSizerButtons.Add(self.btnSecStatus, 0, wx.ALL, 5) - bSizerButtons.AddSpacer((0, 0), 1, wx.EXPAND, 5) + bSizerButtons.AddStretchSpacer() importExport = (("Import", wx.ART_FILE_OPEN, "from"), ("Export", wx.ART_FILE_SAVE_AS, "to")) @@ -381,7 +390,7 @@ class SkillTreeView(wx.Panel): btn.Layout() setattr(self, "{}Btn".format(name.lower()), btn) btn.Enable(True) - btn.SetToolTipString("%s skills %s clipboard" % (name, direction)) + btn.SetToolTip("%s skills %s clipboard" % (name, direction)) bSizerButtons.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT | wx.ALL, 5) btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Skills".format(name.lower()))) @@ -403,7 +412,7 @@ class SkillTreeView(wx.Panel): self.levelIds[idUnlearned] = "Not learned" self.levelChangeMenu.Append(idUnlearned, "Unlearn") - for level in xrange(6): + for level in range(6): id = wx.NewId() self.levelIds[id] = level self.levelChangeMenu.Append(id, "Level %d" % level) @@ -505,15 +514,17 @@ class SkillTreeView(wx.Panel): root = self.root tree = self.skillTreeListCtrl - tree.DeleteChildren(root) + tree.DeleteAllItems() for id, name in sChar.getSkillsByName(search): iconId = self.skillBookImageId - childId = tree.AppendItem(root, name, iconId, data=wx.TreeItemData(('skill', id))) level, dirty = sChar.getSkillLevel(char.ID, id) - tree.SetItemText(childId, "Level %d" % int(level) if isinstance(level, float) else level, 1) + if dirty: - tree.SetItemTextColour(childId, wx.BLUE) + iconId = self.skillBookDirtyImageId + + childId = tree.AppendItem(root, name, iconId, data=('skill', id)) + tree.SetItemText(childId, 1, "Level %d" % int(level) if isinstance(level, float) else level) def populateSkillTree(self, event=None): sChar = Character.getInstance() @@ -528,56 +539,56 @@ class SkillTreeView(wx.Panel): self.btnSecStatus.Enable() groups = sChar.getSkillGroups() - imageId = self.skillBookImageId root = self.root tree = self.skillTreeListCtrl - tree.DeleteChildren(root) + tree.DeleteAllItems() for id, name in groups: - childId = tree.AppendItem(root, name, imageId) - tree.SetPyData(childId, ('group', id)) - tree.AppendItem(childId, "dummy") + imageId = self.skillBookImageId if id in dirtyGroups: - tree.SetItemTextColour(childId, wx.BLUE) + imageId = self.skillBookDirtyImageId - tree.SortChildren(root) + childId = tree.AppendItem(root, name, imageId, data=('group', id)) + tree.AppendItem(childId, "dummy") if event: event.Skip() def expandLookup(self, event): - root = event.Item + root = event.GetItem() tree = self.skillTreeListCtrl - child, cookie = tree.GetFirstChild(root) + child = tree.GetFirstChild(root) if tree.GetItemText(child) == "dummy": - tree.Delete(child) + tree.DeleteItem(child) # Get the real intrestin' stuff sChar = Character.getInstance() char = self.charEditor.entityEditor.getActiveEntity() - data = tree.GetPyData(root) + data = tree.GetItemData(root) for id, name in sChar.getSkills(data[1]): iconId = self.skillBookImageId - childId = tree.AppendItem(root, name, iconId, data=wx.TreeItemData(('skill', id))) level, dirty = sChar.getSkillLevel(char.ID, id) - tree.SetItemText(childId, "Level %d" % int(level) if isinstance(level, float) else level, 1) - if dirty: - tree.SetItemTextColour(childId, wx.BLUE) - tree.SortChildren(root) + if dirty: + iconId = self.skillBookDirtyImageId + + childId = tree.AppendItem(root, name, iconId, data=('skill', id)) + + tree.SetItemText(childId, 1, "Level %d" % int(level) if isinstance(level, float) else level) def scheduleMenu(self, event): event.Skip() - wx.CallAfter(self.spawnMenu, event.Item) + wx.CallAfter(self.spawnMenu, event.GetItem()) def spawnMenu(self, item): - self.skillTreeListCtrl.SelectItem(item) - if self.skillTreeListCtrl.GetChildrenCount(item) > 0: + self.skillTreeListCtrl.Select(item) + thing = self.skillTreeListCtrl.GetFirstChild(item).IsOk() + if thing: return char = self.charEditor.entityEditor.getActiveEntity() sMkt = Market.getInstance() - id = self.skillTreeListCtrl.GetPyData(item)[1] + id = self.skillTreeListCtrl.GetItemData(item)[1] if char.name not in ("All 0", "All 5"): self.levelChangeMenu.selection = sMkt.getItem(id) self.PopupMenu(self.levelChangeMenu) @@ -591,7 +602,7 @@ class SkillTreeView(wx.Panel): sChar = Character.getInstance() char = self.charEditor.entityEditor.getActiveEntity() selection = self.skillTreeListCtrl.GetSelection() - dataType, skillID = self.skillTreeListCtrl.GetPyData(selection) + dataType, skillID = self.skillTreeListCtrl.GetItemData(selection) if level is not None: sChar.changeLevel(char.ID, skillID, level, persist=True) @@ -604,41 +615,43 @@ class SkillTreeView(wx.Panel): # level setting. We don't want to refresh tree, as that will lose all expanded categories and users location # within the tree. Thus, we loop through the tree and refresh the info. # @todo: when collapsing branch, remove the data. This will make this loop more performant - child, cookie = self.skillTreeListCtrl.GetFirstChild(self.root) + + child = self.skillTreeListCtrl.GetFirstChild(self.root) def _setTreeSkillLevel(treeItem, skillID): lvl, dirty = sChar.getSkillLevel(char.ID, skillID) self.skillTreeListCtrl.SetItemText(treeItem, - "Level {}".format(int(lvl)) if not isinstance(lvl, basestring) else lvl, - 1) + 1, + "Level {}".format(int(lvl)) if not isinstance(lvl, str) else lvl) + if not dirty: - self.skillTreeListCtrl.SetItemTextColour(treeItem, None) + self.skillTreeListCtrl.SetItemImage(treeItem, self.skillBookImageId) while child.IsOk(): # child = Skill category - dataType, id = self.skillTreeListCtrl.GetPyData(child) + dataType, id = self.skillTreeListCtrl.GetItemData(child) + if dataType == 'skill': _setTreeSkillLevel(child, id) else: - grand, cookie2 = self.skillTreeListCtrl.GetFirstChild(child) - + grand = self.skillTreeListCtrl.GetFirstChild(child) while grand.IsOk(): if self.skillTreeListCtrl.GetItemText(grand) != "dummy": - _, skillID = self.skillTreeListCtrl.GetPyData(grand) + _, skillID = self.skillTreeListCtrl.GetItemData(grand) _setTreeSkillLevel(grand, skillID) - grand, cookie2 = self.skillTreeListCtrl.GetNextChild(child, cookie2) + grand = self.skillTreeListCtrl.GetNextSibling(grand) - child, cookie = self.skillTreeListCtrl.GetNextChild(self.root, cookie) + child = self.skillTreeListCtrl.GetNextSibling(child) dirtySkills = sChar.getDirtySkills(char.ID) dirtyGroups = set([skill.item.group.ID for skill in dirtySkills]) parentID = self.skillTreeListCtrl.GetItemParent(selection) - parent = self.skillTreeListCtrl.GetPyData(parentID) + parent = self.skillTreeListCtrl.GetItemData(parentID) if parent: if parent[1] in dirtyGroups: - self.skillTreeListCtrl.SetItemTextColour(parentID, None) + self.skillTreeListCtrl.SetItemImage(parentID, self.skillBookImageId) event.Skip() @@ -709,20 +722,24 @@ class APIView(wx.Panel): wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL) self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor - self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) - - self.apiUrlCreatePredefined = u"https://community.eveonline.com/support/api-key/CreatePredefined?accessMask=8" - self.apiUrlKeyList = u"https://community.eveonline.com/support/api-key/" + self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) pmainSizer = wx.BoxSizer(wx.VERTICAL) hintSizer = wx.BoxSizer(wx.HORIZONTAL) hintSizer.AddStretchSpacer() self.stDisabledTip = wx.StaticText(self, wx.ID_ANY, - u"You cannot add API Details for All 0 and All 5 characters.\n" - u"Please select another character or make a new one.", style=wx.ALIGN_CENTER) + "You cannot link All 0 or All 5 characters to an EVE character.\n" + "Please select another character or make a new one.", style=wx.ALIGN_CENTER) self.stDisabledTip.Wrap(-1) hintSizer.Add(self.stDisabledTip, 0, wx.TOP | wx.BOTTOM, 10) + + self.noCharactersTip = wx.StaticText(self, wx.ID_ANY, + "You haven't logging into EVE SSO with any characters yet. Please use the " + "button below to log into EVE.", style=wx.ALIGN_CENTER) + self.noCharactersTip.Wrap(-1) + hintSizer.Add(self.noCharactersTip, 0, wx.TOP | wx.BOTTOM, 10) + self.stDisabledTip.Hide() hintSizer.AddStretchSpacer() pmainSizer.Add(hintSizer, 0, wx.EXPAND, 5) @@ -732,99 +749,97 @@ class APIView(wx.Panel): fgSizerInput.SetFlexibleDirection(wx.BOTH) fgSizerInput.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) - self.m_staticIDText = wx.StaticText(self, wx.ID_ANY, u"keyID:", wx.DefaultPosition, wx.DefaultSize, 0) - self.m_staticIDText.Wrap(-1) - fgSizerInput.Add(self.m_staticIDText, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) - - self.inputID = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) - fgSizerInput.Add(self.inputID, 1, wx.ALL | wx.EXPAND, 5) - - self.m_staticKeyText = wx.StaticText(self, wx.ID_ANY, u"vCode:", wx.DefaultPosition, wx.DefaultSize, 0) - self.m_staticKeyText.Wrap(-1) - fgSizerInput.Add(self.m_staticKeyText, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) - - self.inputKey = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0) - fgSizerInput.Add(self.inputKey, 0, wx.ALL | wx.EXPAND, 5) - - self.m_staticCharText = wx.StaticText(self, wx.ID_ANY, u"Character:", wx.DefaultPosition, wx.DefaultSize, 0) + self.m_staticCharText = wx.StaticText(self, wx.ID_ANY, "Character:", wx.DefaultPosition, wx.DefaultSize, 0) self.m_staticCharText.Wrap(-1) - fgSizerInput.Add(self.m_staticCharText, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 5) + fgSizerInput.Add(self.m_staticCharText, 0, wx.ALL | wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL, 10) self.charChoice = wx.Choice(self, wx.ID_ANY, style=0) - self.charChoice.Append("No Selection", 0) - fgSizerInput.Add(self.charChoice, 1, wx.ALL | wx.EXPAND, 5) - - self.charChoice.Enable(False) + fgSizerInput.Add(self.charChoice, 1, wx.ALL | wx.EXPAND, 10) pmainSizer.Add(fgSizerInput, 0, wx.EXPAND, 5) - btnSizer = wx.BoxSizer(wx.HORIZONTAL) - btnSizer.AddStretchSpacer() - - self.btnFetchCharList = wx.Button(self, wx.ID_ANY, u"Get Characters") - btnSizer.Add(self.btnFetchCharList, 0, wx.ALL, 2) - self.btnFetchCharList.Bind(wx.EVT_BUTTON, self.fetchCharList) - - self.btnFetchSkills = wx.Button(self, wx.ID_ANY, u"Fetch Skills") - btnSizer.Add(self.btnFetchSkills, 0, wx.ALL, 2) - self.btnFetchSkills.Bind(wx.EVT_BUTTON, self.fetchSkills) - self.btnFetchSkills.Enable(False) - - btnSizer.AddStretchSpacer() - pmainSizer.Add(btnSizer, 0, wx.EXPAND, 5) - + self.addButton = wx.Button(self, wx.ID_ANY, "Log In with EVE SSO", wx.DefaultPosition, wx.DefaultSize, 0) + self.addButton.Bind(wx.EVT_BUTTON, self.addCharacter) + pmainSizer.Add(self.addButton, 0, wx.ALL | wx.ALIGN_CENTER, 5) self.stStatus = wx.StaticText(self, wx.ID_ANY, wx.EmptyString) pmainSizer.Add(self.stStatus, 0, wx.ALL, 5) - - pmainSizer.AddStretchSpacer() - self.stAPITip = wx.StaticText(self, wx.ID_ANY, - u"You can create a pre-defined key here (only CharacterSheet is required):", - wx.DefaultPosition, wx.DefaultSize, 0) - self.stAPITip.Wrap(-1) - - pmainSizer.Add(self.stAPITip, 0, wx.ALL, 2) - - self.hlEveAPI = wx.HyperlinkCtrl(self, wx.ID_ANY, self.apiUrlCreatePredefined, self.apiUrlCreatePredefined, - wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) - pmainSizer.Add(self.hlEveAPI, 0, wx.ALL, 2) - - self.stAPITip2 = wx.StaticText(self, wx.ID_ANY, u"Or, you can choose an existing key from:", wx.DefaultPosition, - wx.DefaultSize, 0) - self.stAPITip2.Wrap(-1) - pmainSizer.Add(self.stAPITip2, 0, wx.ALL, 2) - - self.hlEveAPI2 = wx.HyperlinkCtrl(self, wx.ID_ANY, self.apiUrlKeyList, self.apiUrlKeyList, wx.DefaultPosition, - wx.DefaultSize, wx.HL_DEFAULT_STYLE) - pmainSizer.Add(self.hlEveAPI2, 0, wx.ALL, 2) - + self.charEditor.mainFrame.Bind(GE.EVT_SSO_LOGOUT, self.ssoListChanged) + self.charEditor.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoListChanged) self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged) + self.charChoice.Bind(wx.EVT_CHOICE, self.ssoCharChanged) + self.SetSizer(pmainSizer) self.Layout() - self.charChanged(None) + self.ssoListChanged(None) + + def ssoCharChanged(self, event): + sChar = Character.getInstance() + activeChar = self.charEditor.entityEditor.getActiveEntity() + sChar.setSsoCharacter(activeChar.ID, self.getActiveCharacter()) + event.Skip() + + def addCharacter(self, event): + sEsi = Esi.getInstance() + sEsi.login() + + def getActiveCharacter(self): + selection = self.charChoice.GetCurrentSelection() + return self.charChoice.GetClientData(selection) if selection is not -1 else None + + def ssoListChanged(self, event): + if not self: # todo: fix event not unbinding properly + return + sEsi = Esi.getInstance() + ssoChars = sEsi.getSsoCharacters() + + if len(ssoChars) == 0: + self.charChoice.Hide() + self.m_staticCharText.Hide() + self.noCharactersTip.Show() + else: + self.noCharactersTip.Hide() + self.m_staticCharText.Show() + self.charChoice.Show() + + self.charChanged(event) def charChanged(self, event): sChar = Character.getInstance() + sEsi = Esi.getInstance() + activeChar = self.charEditor.entityEditor.getActiveEntity() - ID, key, char, chars = sChar.getApiDetails(activeChar.ID) - self.inputID.SetValue(str(ID)) - self.inputKey.SetValue(key) + if event and event.EventType == GE.EVT_SSO_LOGIN.typeId and hasattr(event, 'character'): + # Automatically assign the character that was just logged into + sChar.setSsoCharacter(activeChar.ID, event.character.ID) + + sso = sChar.getSsoCharacter(activeChar.ID) + + ssoChars = sEsi.getSsoCharacters() self.charChoice.Clear() - if chars: - for charName in chars: - self.charChoice.Append(charName) - self.charChoice.SetStringSelection(char) - self.charChoice.Enable(True) - self.btnFetchSkills.Enable(True) - else: - self.charChoice.Append("No characters...", 0) - self.charChoice.SetSelection(0) - self.charChoice.Enable(False) - self.btnFetchSkills.Enable(False) + noneID = self.charChoice.Append("None", None) + for char in ssoChars: + currId = self.charChoice.Append(char.characterName, char.ID) + + if sso is not None and char.ID == sso.ID: + self.charChoice.SetSelection(currId) + if sso is None: + self.charChoice.SetSelection(noneID) + + + # + # if chars: + # for charName in chars: + # self.charChoice.Append(charName) + # self.charChoice.SetStringSelection(char) + # else: + # self.charChoice.Append("No characters...", 0) + # self.charChoice.SetSelection(0) + # if activeChar.name in ("All 0", "All 5"): self.Enable(False) self.stDisabledTip.Show() @@ -837,47 +852,6 @@ class APIView(wx.Panel): if event is not None: event.Skip() - def fetchCharList(self, event): - self.stStatus.SetLabel("") - if self.inputID.GetLineText(0) == "" or self.inputKey.GetLineText(0) == "": - self.stStatus.SetLabel("Invalid keyID or vCode!") - return - - sChar = Character.getInstance() - try: - activeChar = self.charEditor.entityEditor.getActiveEntity() - list = sChar.apiCharList(activeChar.ID, self.inputID.GetLineText(0), self.inputKey.GetLineText(0)) - except AuthenticationError, e: - msg = "Authentication failure. Please check keyID and vCode combination." - pyfalog.info(msg) - self.stStatus.SetLabel(msg) - except TimeoutError, e: - msg = "Request timed out. Please check network connectivity and/or proxy settings." - pyfalog.info(msg) - self.stStatus.SetLabel(msg) - except Exception, e: - pyfalog.error(e) - self.stStatus.SetLabel("Error:\n%s" % e.message) - else: - self.charChoice.Clear() - for charName in list: - self.charChoice.Append(charName) - - self.btnFetchSkills.Enable(True) - self.charChoice.Enable(True) - - self.Layout() - - self.charChoice.SetSelection(0) - - def fetchSkills(self, event): - charName = self.charChoice.GetString(self.charChoice.GetSelection()) - if charName: - sChar = Character.getInstance() - activeChar = self.charEditor.entityEditor.getActiveEntity() - sChar.apiFetch(activeChar.ID, charName, self.__fetchCallback) - self.stStatus.SetLabel("Getting skills for {}".format(charName)) - def __fetchCallback(self, e=None): charName = self.charChoice.GetString(self.charChoice.GetSelection()) if e is None: @@ -893,12 +867,12 @@ class SecStatusDialog(wx.Dialog): def __init__(self, parent, sec): wx.Dialog.__init__(self, parent, title="Set Security Status", size=(275, 175)) - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) bSizer1 = wx.BoxSizer(wx.VERTICAL) self.m_staticText1 = wx.StaticText(self, wx.ID_ANY, - u"Security Status is used in some CONCORD hull calculations; you can set the characters security status here", + "Security Status is used in some CONCORD hull calculations; you can set the characters security status here", wx.DefaultPosition, wx.DefaultSize, 0) self.m_staticText1.Wrap(-1) bSizer1.Add(self.m_staticText1, 1, wx.ALL | wx.EXPAND, 5) diff --git a/gui/characterSelection.py b/gui/characterSelection.py index 6fc901f1c..cc63a1f82 100644 --- a/gui/characterSelection.py +++ b/gui/characterSelection.py @@ -19,15 +19,15 @@ # noinspection PyPackageRequirements import wx +from gui.bitmap_loader import BitmapLoader from logbook import Logger import gui.globalEvents as GE import gui.mainFrame -from gui.bitmapLoader import BitmapLoader -from gui.utils.clipboard import toClipboard from service.character import Character from service.fit import Fit +from gui.utils.clipboard import toClipboard pyfalog = Logger(__name__) @@ -61,7 +61,7 @@ class CharacterSelection(wx.Panel): self.btnRefresh.SetMinSize(size) self.btnRefresh.SetMaxSize(size) - self.btnRefresh.SetToolTipString("Refresh API") + self.btnRefresh.SetToolTip("Refresh API") self.btnRefresh.Bind(wx.EVT_BUTTON, self.refreshApi) self.btnRefresh.Enable(False) @@ -79,6 +79,7 @@ class CharacterSelection(wx.Panel): self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.SetMinSize(wx.Size(25, -1)) + self.toggleRefreshButton() self.charChoice.Enable(False) @@ -142,7 +143,7 @@ class CharacterSelection(wx.Panel): sFit = Fit.getInstance() sFit.changeChar(fitID, charID) - choice.Append(u"\u2015 Open Character Editor \u2015", -1) + choice.Append("\u2015 Open Character Editor \u2015", -1) self.charCache = self.charChoice.GetCurrentSelection() if event is not None: @@ -151,9 +152,7 @@ class CharacterSelection(wx.Panel): def refreshApi(self, event): self.btnRefresh.Enable(False) sChar = Character.getInstance() - ID, key, charName, chars = sChar.getApiDetails(self.getActiveCharacter()) - if charName: - sChar.apiFetch(self.getActiveCharacter(), charName, self.refreshAPICallback) + sChar.apiFetch(self.getActiveCharacter(), self.refreshAPICallback) def refreshAPICallback(self, e=None): self.btnRefresh.Enable(True) @@ -161,11 +160,11 @@ class CharacterSelection(wx.Panel): self.refreshCharacterList() else: exc_type, exc_obj, exc_trace = e - pyfalog.warn("Error fetching API information for character") + pyfalog.warn("Error fetching skill information for character") pyfalog.warn(exc_obj) wx.MessageBox( - "Error fetching API information, please check your API details in the character editor and try again later", + "Error fetching skill information", "Error", wx.ICON_ERROR | wx.STAY_ON_TOP) def charChanged(self, event): @@ -178,16 +177,23 @@ class CharacterSelection(wx.Panel): self.charChoice.SetSelection(self.charCache) self.mainFrame.showCharacterEditor(event) return - if sChar.getCharName(charID) not in ("All 0", "All 5") and sChar.apiEnabled(charID): - self.btnRefresh.Enable(True) - else: - self.btnRefresh.Enable(False) + + self.toggleRefreshButton() sFit = Fit.getInstance() sFit.changeChar(fitID, charID) self.charCache = self.charChoice.GetCurrentSelection() wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID)) + def toggleRefreshButton(self): + charID = self.getActiveCharacter() + sChar = Character.getInstance() + char = sChar.getCharacter(charID) + if sChar.getCharName(charID) not in ("All 0", "All 5") and sChar.getSsoCharacter(char.ID) is not None: + self.btnRefresh.Enable(True) + else: + self.btnRefresh.Enable(False) + def selectChar(self, charID): choice = self.charChoice numItems = len(choice.GetItems()) @@ -212,7 +218,7 @@ class CharacterSelection(wx.Panel): if event.fitID is None: self.skillReqsStaticBitmap.SetBitmap(self.cleanSkills) - self.skillReqsStaticBitmap.SetToolTipString("No active fit") + self.skillReqsStaticBitmap.SetToolTip("No active fit") else: sCharacter = Character.getInstance() self.reqs = sCharacter.checkRequirements(fit) @@ -233,7 +239,7 @@ class CharacterSelection(wx.Panel): else: tip += self._buildSkillsTooltip(self.reqs) self.skillReqsStaticBitmap.SetBitmap(self.redSkills) - self.skillReqsStaticBitmap.SetToolTipString(tip.strip()) + self.skillReqsStaticBitmap.SetToolTip(tip.strip()) if newCharID is None: sChar = Character.getInstance() @@ -244,6 +250,8 @@ class CharacterSelection(wx.Panel): if not fit.calculated: self.charChanged(None) + self.toggleRefreshButton() + event.Skip() def exportSkills(self, evt): @@ -260,11 +268,11 @@ class CharacterSelection(wx.Panel): sCharacter = Character.getInstance() if tabulationLevel == 0: - for item, subReqs in reqs.iteritems(): + for item, subReqs in reqs.items(): tip += "%s:\n" % item.name tip += self._buildSkillsTooltip(subReqs, item.name, 1) else: - for name, info in reqs.iteritems(): + for name, info in reqs.items(): level, ID, more = info sCharacter.skillReqsDict['skills'].append({ 'item': currItem, @@ -286,11 +294,11 @@ class CharacterSelection(wx.Panel): sCharacter = Character.getInstance() if tabulationLevel == 0: - for item, subReqs in reqs.iteritems(): + for item, subReqs in reqs.items(): skillsMap = self._buildSkillsTooltipCondensed(subReqs, item.name, 1, skillsMap) sorted(skillsMap, key=skillsMap.get) else: - for name, info in reqs.iteritems(): + for name, info in reqs.items(): level, ID, more = info sCharacter.skillReqsDict['skills'].append({ 'item': currItem, diff --git a/gui/chromeTabs.py b/gui/chromeTabs.py deleted file mode 100644 index f978e9dd3..000000000 --- a/gui/chromeTabs.py +++ /dev/null @@ -1,1439 +0,0 @@ -# ============================================================================= -# Copyright (C) 2010 Darriele -# -# This file is part of pyfa. -# -# pyfa is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# pyfa is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with pyfa. If not, see . -# ============================================================================= - -# noinspection PyPackageRequirements -import wx -# noinspection PyPackageRequirements -import wx.lib.newevent -import gui.utils.colorUtils as colorUtils -import gui.utils.drawUtils as drawUtils -import gui.utils.fonts as fonts -from gui.bitmapLoader import BitmapLoader -from logbook import Logger -from service.fit import Fit - -pyfalog = Logger(__name__) - -_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent() -_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent() -_PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent() -_PageClosing, EVT_NOTEBOOK_PAGE_CLOSING = wx.lib.newevent.NewEvent() -PageAdded, EVT_NOTEBOOK_PAGE_ADDED = wx.lib.newevent.NewEvent() -PageClosed, EVT_NOTEBOOK_PAGE_CLOSED = wx.lib.newevent.NewEvent() - - -class VetoAble(object): - def __init__(self): - self.__vetoed = False - - def Veto(self): - self.__vetoed = True - - def isVetoed(self): - return self.__vetoed - - -class NotebookTabChangeEvent(object): - def __init__(self, old, new): - self.__old = old - self.__new = new - - def GetOldSelection(self): - return self.__old - - def GetSelection(self): - return self.__new - - OldSelection = property(GetOldSelection) - Selection = property(GetSelection) - - -class PageChanging(_PageChanging, NotebookTabChangeEvent, VetoAble): - def __init__(self, old, new): - NotebookTabChangeEvent.__init__(self, old, new) - _PageChanging.__init__(self) - VetoAble.__init__(self) - - -class PageChanged(_PageChanged, NotebookTabChangeEvent): - def __init__(self, old, new): - NotebookTabChangeEvent.__init__(self, old, new) - _PageChanged.__init__(self) - - -class PageClosing(_PageClosing, VetoAble): - def __init__(self, i): - self.__index = i - _PageClosing.__init__(self) - VetoAble.__init__(self) - self.Selection = property(self.GetSelection) - - def GetSelection(self): - return self.__index - - -class PageAdding(_PageAdding, VetoAble): - def __init__(self): - _PageAdding.__init__(self) - VetoAble.__init__(self) - - -class PFNotebook(wx.Panel): - def __init__(self, parent, canAdd=True): - """ - Instance of Pyfa Notebook. Initializes general layout, includes methods - for setting current page, replacing pages, etc - - parent - wx parent element - canAdd - True if tabs be deleted and added, passed directly to - PFTabsContainer - """ - - wx.Panel.__init__(self, parent, wx.ID_ANY, size=(-1, -1)) - - self.pages = [] - self.activePage = None - - mainSizer = wx.BoxSizer(wx.VERTICAL) - - tabsSizer = wx.BoxSizer(wx.VERTICAL) - self.tabsContainer = PFTabsContainer(self, canAdd=canAdd) - tabsSizer.Add(self.tabsContainer, 0, wx.EXPAND) - - style = wx.DOUBLE_BORDER if 'wxMSW' in wx.PlatformInfo else wx.SIMPLE_BORDER - - contentSizer = wx.BoxSizer(wx.VERTICAL) - self.pageContainer = wx.Panel(self, style=style) - self.pageContainer.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) - contentSizer.Add(self.pageContainer, 1, wx.EXPAND, 5) - - mainSizer.Add(tabsSizer, 0, wx.EXPAND, 5) - mainSizer.Add(contentSizer, 1, wx.EXPAND | wx.BOTTOM, 2) - - self.SetSizer(mainSizer) - self.Bind(wx.EVT_SIZE, self.OnSize) - self.Layout() - - def GetPage(self, i): - return self.pages[i] - - def SetPage(self, i, page): - if i >= len(self.pages) or i is None or page is None: - return - - oldPage = self.pages[i] - self.pages[i] = page - if oldPage == self.activePage: - oldPage.Destroy() - self.activePage = page - else: - oldPage.Destroy() - - page.Reparent(self.pageContainer) - - if self.activePage == page: - self.ShowActive() - - @staticmethod - def GetBorders(): - """Gets border widths to better determine page size in ShowActive()""" - - bx = wx.SystemSettings_GetMetric(wx.SYS_BORDER_X) - by = wx.SystemSettings_GetMetric(wx.SYS_BORDER_Y) - - if bx < 0: - bx = 1 - if by < 0: - by = 1 - - return bx, by - - def ReplaceActivePage(self, page): - self.SetPage(self.GetSelection(), page) - - def GetSelectedPage(self): - return self.activePage - - def GetPageIndex(self, page): - return self.pages.index(page) if page in self.pages else None - - def GetSelection(self): - return self.GetPageIndex(self.activePage) - - def GetCurrentPage(self): - return self.activePage - - def GetPageCount(self): - return len(self.pages) - - def NextPage(self): - """Used with keyboard shortcut for next page navigation""" - cpage = self.GetSelection() - - if cpage is None: - return - - if cpage < self.GetPageCount() - 1: - self.SetSelection(cpage + 1) - npage = cpage + 1 - else: - self.SetSelection(0) - npage = 0 - - wx.PostEvent(self, PageChanged(cpage, npage)) - - def PrevPage(self): - """Used with keyboard shortcut for previous page navigation""" - cpage = self.GetSelection() - - if cpage is None: - return - - if cpage > 0: - self.SetSelection(cpage - 1) - npage = cpage - 1 - else: - self.SetSelection(self.GetPageCount() - 1) - npage = self.GetPageCount() - 1 - - wx.PostEvent(self, PageChanged(cpage, npage)) - - def AddPage(self, tabWnd=None, tabTitle="Empty Tab", tabImage=None, showClose=True): - if self.activePage: - self.activePage.Hide() - - if not tabWnd: - tabWnd = wx.Panel(self) - - tabWnd.Reparent(self.pageContainer) - - self.pageContainer.Layout() - - self.pages.append(tabWnd) - self.tabsContainer.AddTab(tabTitle, tabImage, showClose) - - self.activePage = tabWnd - self.ShowActive(True) - - def DisablePage(self, page, toggle): - idx = self.GetPageIndex(page) - - if toggle and page == self.activePage: - try: - # Set page to the first non-disabled page - self.SetSelection(next(i for i, _ in enumerate(self.pages) if not self.tabsContainer.tabs[i].disabled)) - except StopIteration: - self.SetSelection(0) - - self.tabsContainer.DisableTab(idx, toggle) - - def SetSelection(self, page): - oldsel = self.GetSelection() - if oldsel != page: - self.activePage.Hide() - self.activePage = self.pages[page] - self.tabsContainer.SetSelected(page) - self.ShowActive() - - def DeletePage(self, n, internal=False): - """ - Deletes page. - - n -- index of page to be deleted - internal -- True if we're deleting the page from the PFTabsContainer - """ - page = self.pages[n] - self.pages.remove(page) - page.Destroy() - - if not internal: - # If we're not deleting from the tab, delete the tab - # (deleting from the tab automatically deletes itself) - self.tabsContainer.DeleteTab(n, True) - - sel = self.tabsContainer.GetSelected() - if sel is not None: - self.activePage = self.pages[sel] - self.ShowActive() - wx.PostEvent(self, PageChanged(-1, sel)) - else: - self.activePage = None - - def SwitchPages(self, src, dest): - self.pages[src], self.pages[dest] = self.pages[dest], self.pages[src] - - def ShowActive(self, resizeOnly=False): - """ - Sets the size of the page and shows. The sizing logic adjusts for some - minor sizing errors (scrollbars going beyond bounds) - - resizeOnly -- if we are not interested in showing the page, only setting - the size - - @todo: is resizeOnly still needed? Was introduced with 8b8b97 in mid 2011 - to fix a resizing bug with blank pages, cannot reproduce 13Sept2014 - """ - - ww, wh = self.pageContainer.GetSize() - bx, by = self.GetBorders() - ww -= bx * 4 - wh -= by * 4 - self.activePage.SetSize((max(ww, -1), max(wh, -1))) - self.activePage.SetPosition((0, 0)) - - if not resizeOnly: - self.activePage.Show() - - self.Layout() - - def IsActive(self, page): - return self.activePage == page - - def SetPageTitle(self, i, text, refresh=True): - tab = self.tabsContainer.tabs[i] - tab.text = text - if refresh: - self.tabsContainer.AdjustTabsSize() - self.Refresh() - - def SetPageIcon(self, i, icon, refresh=True): - tab = self.tabsContainer.tabs[i] - tab.tabImg = icon - if refresh: - self.tabsContainer.AdjustTabsSize() - self.Refresh() - - def SetPageTextIcon(self, i, text=wx.EmptyString, icon=None): - self.SetPageTitle(i, text, False) - self.SetPageIcon(i, icon, False) - self.tabsContainer.AdjustTabsSize() - self.Refresh() - - def Refresh(self): - self.tabsContainer.Refresh() - - def OnSize(self, event): - w, h = self.GetSize() - self.tabsContainer.SetSize((w, -1)) - self.tabsContainer.UpdateSize() - self.tabsContainer.Refresh() - self.Layout() - - if self.activePage: - self.ShowActive() - event.Skip() - - -class PFTabRenderer(object): - def __init__(self, size=(36, 24), text=wx.EmptyString, img=None, inclination=6, closeButton=True): - """ - Renders a new tab - - text -- tab label - img -- wxImage of tab icon - inclination -- does not seem to affect class, maybe used to be a variable - for custom drawn tab inclinations before there were bitmaps? - closeButton -- True if tab can be closed - """ - # tab left/right zones inclination - self.ctabLeft = BitmapLoader.getImage("ctableft", "gui") - self.ctabMiddle = BitmapLoader.getImage("ctabmiddle", "gui") - self.ctabRight = BitmapLoader.getImage("ctabright", "gui") - self.ctabClose = BitmapLoader.getImage("ctabclose", "gui") - - self.leftWidth = self.ctabLeft.GetWidth() - self.rightWidth = self.ctabRight.GetWidth() - self.middleWidth = self.ctabMiddle.GetWidth() - self.closeBtnWidth = self.ctabClose.GetWidth() - - width, height = size - if width < self.leftWidth + self.rightWidth + self.middleWidth: - width = self.leftWidth + self.rightWidth + self.middleWidth - if height < self.ctabMiddle.GetHeight(): - height = self.ctabMiddle.GetHeight() - - self.inclination = inclination - self.text = text - self.disabled = False - self.tabSize = (width, height) - self.closeButton = closeButton - self.selected = False - self.closeBtnHovering = False - self.tabBitmap = None - self.tabBackBitmap = None - self.cbSize = 5 - self.padding = 4 - self.font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) - - self.tabImg = img - self.position = (0, 0) # Not used internally for rendering - helper for tab container - self.InitTab() - - def SetPosition(self, position): - self.position = position - - def GetPosition(self): - return self.position - - def GetSize(self): - return self.tabSize - - def SetSize(self, size): - w, h = size - if w < self.leftWidth + self.rightWidth + self.middleWidth: - w = self.leftWidth + self.rightWidth + self.middleWidth - if h < self.ctabMiddle.GetHeight(): - h = self.ctabMiddle.GetHeight() - - self.tabSize = (w, h) - self.InitTab() - - def SetSelected(self, sel=True): - self.selected = sel - self.InitTab() - - def GetSelected(self): - return self.selected - - def IsSelected(self): - return self.selected - - def ShowCloseButtonHovering(self, hover=True): - if self.closeBtnHovering != hover: - self.closeBtnHovering = hover - self._Render() - - def GetCloseButtonHoverStatus(self): - return self.closeBtnHovering - - def GetTabRegion(self): - nregion = self.CopyRegion(self.tabRegion) - nregion.SubtractRegion(self.closeBtnRegion) if self.closeButton else self.tabRegion - return nregion - - def GetCloseButtonRegion(self): - return self.CopyRegion(self.closeBtnRegion) - - def GetMinSize(self): - ebmp = wx.EmptyBitmap(1, 1) - mdc = wx.MemoryDC() - mdc.SelectObject(ebmp) - mdc.SetFont(self.font) - textSizeX, textSizeY = mdc.GetTextExtent(self.text) - totalSize = self.leftWidth + self.rightWidth + textSizeX + self.closeBtnWidth / 2 + 16 + self.padding * 2 - mdc.SelectObject(wx.NullBitmap) - return totalSize, self.tabHeight - - def SetTabImage(self, img): - self.tabImg = img - - @staticmethod - def CopyRegion(region): - rect = region.GetBox() - - newRegion = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) - newRegion.IntersectRegion(region) - - return newRegion - - def InitTab(self): - self.tabWidth, self.tabHeight = self.tabSize - - self.contentWidth = self.tabWidth - self.leftWidth - self.rightWidth - self.tabRegion = None - self.closeBtnRegion = None - - self.InitColors() - self.InitBitmaps() - - self.ComposeTabBack() - self.InitTabRegions() - self._Render() - - def InitBitmaps(self): - """ - Creates bitmap for tab - - Takes the bitmaps already set and replaces a known color (black) with - the needed color, while also considering selected state. Color dependant - on platform -- see InitColors(). - """ - if self.selected: - tr, tg, tb = self.selectedColor - else: - tr, tg, tb = self.inactiveColor - - ctabLeft = self.ctabLeft.Copy() - ctabRight = self.ctabRight.Copy() - ctabMiddle = self.ctabMiddle.Copy() - - ctabLeft.Replace(0, 0, 0, tr, tg, tb) - ctabRight.Replace(0, 0, 0, tr, tg, tb) - ctabMiddle.Replace(0, 0, 0, tr, tg, tb) - - self.ctabLeftBmp = wx.BitmapFromImage(ctabLeft) - self.ctabRightBmp = wx.BitmapFromImage(ctabRight) - self.ctabMiddleBmp = wx.BitmapFromImage(ctabMiddle) - self.ctabCloseBmp = wx.BitmapFromImage(self.ctabClose) - - def ComposeTabBack(self): - """ - Creates the tab background bitmap based upon calculated dimension values - and modified bitmaps via InitBitmaps() - """ - bkbmp = wx.EmptyBitmap(self.tabWidth, self.tabHeight) - - mdc = wx.MemoryDC() - mdc.SelectObject(bkbmp) - - # mdc.SetBackground(wx.Brush((0x12, 0x23, 0x32))) - mdc.Clear() - - mdc.DrawBitmap(self.ctabLeftBmp, 0, 0) # set the left bitmap - - # convert middle bitmap and scale to tab width - cm = self.ctabMiddleBmp.ConvertToImage() - mimg = cm.Scale(self.contentWidth, self.ctabMiddle.GetHeight(), wx.IMAGE_QUALITY_NORMAL) - mbmp = wx.BitmapFromImage(mimg) - mdc.DrawBitmap(mbmp, self.leftWidth, 0) # set middle bitmap, offset by left - - # set right bitmap offset by left + middle - mdc.DrawBitmap(self.ctabRightBmp, self.contentWidth + self.leftWidth, 0) - - mdc.SelectObject(wx.NullBitmap) - - # bkbmp.SetMaskColour((0x12, 0x23, 0x32)) - - if self.tabBackBitmap: - del self.tabBackBitmap - - self.tabBackBitmap = bkbmp - - def InitTabRegions(self): - """ - Initializes regions for tab, which makes it easier to determine if - given coordinates are incluced in a region - """ - self.tabRegion = wx.RegionFromBitmap(self.tabBackBitmap) - self.closeBtnRegion = wx.RegionFromBitmap(self.ctabCloseBmp) - self.closeBtnRegion.Offset( - self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth() / 2, - (self.tabHeight - self.ctabCloseBmp.GetHeight()) / 2 - ) - - def InitColors(self): - """Determines colors used for tab, based on system settings""" - self.tabColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) - self.inactiveColor = colorUtils.GetSuitableColor(self.tabColor, 0.25) - self.selectedColor = colorUtils.GetSuitableColor(self.tabColor, 0.10) - - def Render(self): - return self.tabBitmap - - def _Render(self): - """Renders the tab, complete with the icon, text, and close button""" - if self.tabBitmap: - del self.tabBitmap - - height = self.tabHeight - - # rect = wx.Rect(0, 0, self.tabWidth, self.tabHeight) - - canvas = wx.EmptyBitmap(self.tabWidth, self.tabHeight, 24) - - mdc = wx.MemoryDC() - - mdc.SelectObject(canvas) - # mdc.SetBackground(wx.Brush ((0x12,0x23,0x32))) - mdc.Clear() - - # r = copy.copy(rect) - # r.top = r.left = 0 - # r.height = height - mdc.DrawBitmap(self.tabBackBitmap, 0, 0, True) - - if self.tabImg: - bmp = wx.BitmapFromImage(self.tabImg.ConvertToGreyscale() if self.disabled else self.tabImg) - if self.contentWidth > 16: # @todo: is this conditional relevant anymore? - # Draw tab icon - mdc.DrawBitmap(bmp, self.leftWidth + self.padding - bmp.GetWidth() / 2, (height - bmp.GetHeight()) / 2) - textStart = self.leftWidth + self.padding + bmp.GetWidth() / 2 - else: - textStart = self.leftWidth - - mdc.SetFont(self.font) - - maxsize = self.tabWidth - textStart - self.rightWidth - self.padding * 4 - color = self.selectedColor if self.selected else self.inactiveColor - - mdc.SetTextForeground(colorUtils.GetSuitableColor(color, 1)) - - text = drawUtils.GetPartialText(mdc, self.text, maxsize, "") - tx, ty = mdc.GetTextExtent(text) - mdc.DrawText(text, textStart + self.padding, height / 2 - ty / 2) - - if self.closeButton: - if self.closeBtnHovering: - cbmp = self.ctabCloseBmp - else: - cimg = self.ctabCloseBmp.ConvertToImage() - cimg = cimg.AdjustChannels(0.7, 0.7, 0.7, 0.3) - cbmp = wx.BitmapFromImage(cimg) - - mdc.DrawBitmap( - cbmp, - self.contentWidth + self.leftWidth - self.ctabCloseBmp.GetWidth() / 2, - (height - self.ctabCloseBmp.GetHeight()) / 2, - ) - - mdc.SelectObject(wx.NullBitmap) - - canvas.SetMaskColour((0x12, 0x23, 0x32)) - img = canvas.ConvertToImage() - - if not img.HasAlpha(): - img.InitAlpha() - - bmp = wx.BitmapFromImage(img) - self.tabBitmap = bmp - - def __repr__(self): - return "PFTabRenderer(text={}, disabled={}) at {}".format( - self.text, self.disabled, hex(id(self)) - ) - - -class PFAddRenderer(object): - def __init__(self): - """Renders the add tab button""" - self.addImg = BitmapLoader.getImage("ctabadd", "gui") - self.width = self.addImg.GetWidth() - self.height = self.addImg.GetHeight() - - self.region = None - self.tbmp = wx.BitmapFromImage(self.addImg) - self.addBitmap = None - - self.position = (0, 0) - self.highlighted = False - - self.InitRenderer() - - def GetPosition(self): - return self.position - - def SetPosition(self, pos): - self.position = pos - - def GetSize(self): - return self.width, self.height - - def GetHeight(self): - return self.height - - def GetWidth(self): - return self.width - - def InitRenderer(self): - self.region = self.CreateRegion() - self._Render() - - def CreateRegion(self): - region = wx.RegionFromBitmap(self.tbmp) - return region - - @staticmethod - def CopyRegion(region): - rect = region.GetBox() - - newRegion = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) - newRegion.IntersectRegion(region) - - return newRegion - - def GetRegion(self): - return self.CopyRegion(self.region) - - def Highlight(self, highlight=False): - self.highlighted = highlight - self._Render() - - def IsHighlighted(self): - return self.highlighted - - def Render(self): - return self.addBitmap - - def _Render(self): - if self.addBitmap: - del self.addBitmap - - alpha = 1 if self.highlighted else 0.3 - - img = self.addImg.AdjustChannels(1, 1, 1, alpha) - bbmp = wx.BitmapFromImage(img) - self.addBitmap = bbmp - - -class PFTabsContainer(wx.Panel): - def __init__(self, parent, pos=(0, 0), size=(100, 22), id=wx.ID_ANY, canAdd=True): - """ - Defines the tab container. Handles functions such as tab selection and - dragging, and defines minimum width of tabs (all tabs are of equal width, - which is determined via widest tab). Also handles the tab preview, if any. - """ - - wx.Panel.__init__(self, parent, id, pos, size) - if wx.VERSION >= (3, 0): - self.SetBackgroundStyle(wx.BG_STYLE_PAINT) - - self.tabs = [] - width, height = size - self.width = width - self.height = height - self.containerHeight = height - self.startDrag = False - self.dragging = False - self.sFit = Fit.getInstance() - self.efxBmp = None - - self.inclination = 7 - if canAdd: - self.reserved = 48 - else: - self.reserved = self.inclination * 4 - - self.dragTrail = 3 # pixel distance to drag before we actually start dragging - self.dragx = 0 - self.dragy = 0 - self.draggedTab = None - self.dragTrigger = self.dragTrail - - self.showAddButton = canAdd - - self.tabContainerWidth = width - self.reserved - self.tabMinWidth = width - self.tabShadow = None - - self.addButton = PFAddRenderer() - self.addBitmap = self.addButton.Render() - - self.previewTimer = None - self.previewTimerID = wx.ID_ANY - self.previewWnd = None - self.previewBmp = None - self.previewPos = None - self.previewTab = None - - self.Bind(wx.EVT_TIMER, self.OnTimer) - self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) - - self.Bind(wx.EVT_PAINT, self.OnPaint) - self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase) - self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) - self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) - self.Bind(wx.EVT_MIDDLE_UP, self.OnMiddleUp) - self.Bind(wx.EVT_MOTION, self.OnMotion) - self.Bind(wx.EVT_SIZE, self.OnSize) - self.Bind(wx.EVT_SYS_COLOUR_CHANGED, self.OnSysColourChanged) - self.tabShadow = PFTabRenderer((self.tabMinWidth, self.height + 1), inclination=self.inclination) - - def OnSysColourChanged(self, event): - for tab in self.tabs: - tab.InitTab() - self.Refresh() - - def OnSize(self, event): - self.UpdateSize() - event.Skip() - - def UpdateSize(self): - width, _ = self.GetSize() - if width != self.width: - self.width = width - self.tabContainerWidth = self.width - self.reserved - self.AdjustTabsSize() - - def OnLeftDown(self, event): - """Determines what happens when user left clicks (down)""" - mposx, mposy = event.GetPosition() - if not self.startDrag: - tab = self.FindTabAtPos(mposx, mposy) - if tab: - self.CheckTabSelected(tab, mposx, mposy) - if self.showAddButton: - # If we can add tabs, we can drag them. Set flag - self.startDrag = True - tx, ty = tab.GetPosition() - self.dragx = mposx - tx - self.dragy = self.containerHeight - self.height - self.Refresh() - - self.draggedTab = tab - - def OnLeftUp(self, event): - """Determines what happens when user left clicks (up)""" - mposx, mposy = event.GetPosition() - if self.startDrag and self.dragging: - self.dragging = False - self.startDrag = False - self.draggedTab = None - self.dragTrigger = self.dragTrail - self.UpdateTabsPosition() - self.Refresh() - if self.HasCapture(): - self.ReleaseMouse() - - return - - if self.startDrag: - self.startDrag = False - self.dragTrigger = self.dragTrail - - # Checks if we selected the add button and, if True, returns - if self.CheckAddButton(mposx, mposy): - return - - # If there are no tabs, don't waste time - if self.GetTabsCount() == 0: - return - - # Gets selected tab (was set when user down clicked) - selTab = self.GetSelectedTab() - - # Check if we selected close button for selected tab - if self.CheckTabClose(selTab, mposx, mposy): - return - - # Check if we selected close button for all others - for tab in self.tabs: - if self.CheckTabClose(tab, mposx, mposy): - return - - def OnMiddleUp(self, event): - mposx, mposy = event.GetPosition() - - tab = self.FindTabAtPos(mposx, mposy) - - if tab is None or not tab.closeButton: # if not able to close, return False - return False - - index = self.tabs.index(tab) - ev = PageClosing(index) - wx.PostEvent(self.Parent, ev) - if ev.isVetoed(): - return False - - index = self.GetTabIndex(tab) - self.DeleteTab(index) - wx.PostEvent(self.Parent, PageClosed(index=index)) - sel = self.GetSelected() - - if sel is not None: - wx.PostEvent(self.Parent, PageChanged(-1, sel)) - - def GetSelectedTab(self): - for tab in self.tabs: - if tab.GetSelected(): - return tab - return None - - def GetSelected(self): - for tab in self.tabs: - if tab.GetSelected(): - return self.tabs.index(tab) - return None - - def SetSelected(self, tabIndex): - oldSelTab = self.GetSelectedTab() - oldSelTab.SetSelected(False) - self.tabs[tabIndex].SetSelected(True) - self.Refresh() - - def CheckTabSelected(self, tab, x, y): - """ - Selects the tab at x, y. If the tab at x, y is already selected, simply - return true. Otherwise, perform TabHitTest and set tab at position to - selected - """ - oldSelTab = self.GetSelectedTab() - if oldSelTab == tab: - return True - - if self.TabHitTest(tab, x, y): - if tab.disabled: - return - tab.SetSelected(True) - oldSelTab.SetSelected(False) - - ev = PageChanging(self.tabs.index(oldSelTab), self.tabs.index(tab)) - wx.PostEvent(self.Parent, ev) - - if ev.isVetoed(): - return False - - self.Refresh() - selTab = self.tabs.index(tab) - self.Parent.SetSelection(selTab) - - wx.PostEvent(self.Parent, PageChanged(self.tabs.index(oldSelTab), self.tabs.index(tab))) - - return True - - return False - - def CheckTabClose(self, tab, x, y): - """Determines if close button was selected for the given tab.""" - if not tab.closeButton: # if not able to close, return False - return False - - closeBtnReg = tab.GetCloseButtonRegion() - tabPosX, tabPosY = tab.GetPosition() - closeBtnReg.Offset(tabPosX, tabPosY) - - if closeBtnReg.Contains(x, y): - index = self.tabs.index(tab) - ev = PageClosing(index) - wx.PostEvent(self.Parent, ev) - if ev.isVetoed(): - return False - - index = self.GetTabIndex(tab) - self.DeleteTab(index) - wx.PostEvent(self.Parent, PageClosed(index=index)) - sel = self.GetSelected() - - if sel is not None: - wx.PostEvent(self.Parent, PageChanged(-1, sel)) - - return True - return False - - def CheckAddButton(self, x, y): - """Determines if add button was selected.""" - if not self.showAddButton: # if not able to add, return False - return - - reg = self.addButton.GetRegion() - ax, ay = self.addButton.GetPosition() - reg.Offset(ax, ay) - - if reg.Contains(x, y): - ev = PageAdding() - wx.PostEvent(self.Parent, ev) - if ev.isVetoed(): - return False - - self.Parent.AddPage() - wx.PostEvent(self.Parent, PageAdded()) - return True - - def CheckCloseButtons(self, x, y): - """ - Checks if mouse pos at x, y is over a close button. If so, set the - close hovering flag for that tab - """ - dirty = False - # @todo: maybe change to for...else - for tab in self.tabs: - closeBtnReg = tab.GetCloseButtonRegion() - tabPos = tab.GetPosition() - tabPosX, tabPosY = tabPos - closeBtnReg.Offset(tabPosX, tabPosY) - - if closeBtnReg.Contains(x, y): - if not tab.GetCloseButtonHoverStatus(): - tab.ShowCloseButtonHovering(True) - dirty = True - else: - if tab.GetCloseButtonHoverStatus(): - tab.ShowCloseButtonHovering(False) - dirty = True - if dirty: - self.Refresh() - - def FindTabAtPos(self, x, y): - if self.GetTabsCount() == 0: - return None - - selTab = self.GetSelectedTab() - if self.TabHitTest(selTab, x, y): - return selTab - - for tab in self.tabs: - if self.TabHitTest(tab, x, y): - return tab - return None - - @staticmethod - def TabHitTest(tab, x, y): - tabRegion = tab.GetTabRegion() - tabPos = tab.GetPosition() - tabPosX, tabPosY = tabPos - tabRegion.Offset(tabPosX, tabPosY) - - if tabRegion.Contains(x, y): - return True - - return False - - def GetTabAtLeft(self, tabIndex): - return self.tabs[tabIndex - 1] if tabIndex > 0 else None - - def GetTabAtRight(self, tabIndex): - return self.tabs[tabIndex + 1] if tabIndex < self.GetTabsCount() - 1 else None - - def SwitchTabs(self, src, dest, draggedTab=None): - self.tabs[src], self.tabs[dest] = self.tabs[dest], self.tabs[src] - self.UpdateTabsPosition(draggedTab) - self.Parent.SwitchPages(src, dest) - self.Refresh() - - def GetTabIndex(self, tab): - return self.tabs.index(tab) - - def OnMotion(self, event): - """ - Determines what happens when the mouse moves. This handles primarily - dragging (region tab can be dragged) as well as checking if we are over - an actionable button. - """ - mposx, mposy = event.GetPosition() - - if self.startDrag: - if not self.dragging: - if self.dragTrigger < 0: - self.dragging = True - self.dragTrigger = self.dragTrail - self.CaptureMouse() - else: - self.dragTrigger -= 1 - if self.dragging: - dtx = mposx - self.dragx - w, h = self.draggedTab.GetSize() - - if dtx < 0: - dtx = 0 - if dtx + w > self.tabContainerWidth + self.inclination * 2: - dtx = self.tabContainerWidth - w + self.inclination * 2 - self.draggedTab.SetPosition((dtx, self.dragy)) - - index = self.GetTabIndex(self.draggedTab) - - leftTab = self.GetTabAtLeft(index) - rightTab = self.GetTabAtRight(index) - - if leftTab: - lw, lh = leftTab.GetSize() - lx, ly = leftTab.GetPosition() - - if lx + lw / 2 - self.inclination * 2 > dtx: - self.SwitchTabs(index - 1, index, self.draggedTab) - return - - if rightTab: - rw, rh = rightTab.GetSize() - rx, ry = rightTab.GetPosition() - - if rx + rw / 2 + self.inclination * 2 < dtx + w: - self.SwitchTabs(index + 1, index, self.draggedTab) - return - self.UpdateTabsPosition(self.draggedTab) - self.Refresh() - return - return - - self.CheckCloseButtons(mposx, mposy) - self.CheckAddHighlighted(mposx, mposy) - self.CheckTabPreview(mposx, mposy) - - event.Skip() - - def CheckTabPreview(self, mposx, mposy): - """ - Checks to see if we have a tab preview and sets up the timer for it - to display - """ - if not self.sFit.serviceFittingOptions["showTooltip"] or False: - return - - if self.previewTimer: - if self.previewTimer.IsRunning(): - if self.previewWnd: - self.previewTimer.Stop() - return - - if self.previewWnd: - self.previewWnd.Show(False) - del self.previewWnd - self.previewWnd = None - - for tab in self.tabs: - if not tab.GetSelected(): - if self.TabHitTest(tab, mposx, mposy): - try: - page = self.Parent.GetPage(self.GetTabIndex(tab)) - if hasattr(page, "Snapshot"): - if not self.previewTimer: - self.previewTimer = wx.Timer(self, self.previewTimerID) - - self.previewTab = tab - self.previewTimer.Start(500, True) - break - except Exception as e: - pyfalog.critical("Exception caught in CheckTabPreview.") - pyfalog.critical(e) - - def CheckAddHighlighted(self, x, y): - """ - Checks to see if x, y are in add button region, and sets the highlight - flag - """ - if not self.showAddButton: - return - - reg = self.addButton.GetRegion() - ax, ay = self.addButton.GetPosition() - reg.Offset(ax, ay) - - if reg.Contains(x, y): - if not self.addButton.IsHighlighted(): - self.addButton.Highlight(True) - self.Refresh() - else: - if self.addButton.IsHighlighted(): - self.addButton.Highlight(False) - self.Refresh() - - def OnPaint(self, event): - if "wxGTK" in wx.PlatformInfo: - mdc = wx.AutoBufferedPaintDC(self) - else: - mdc = wx.BufferedPaintDC(self) - - if 'wxMac' in wx.PlatformInfo and wx.VERSION < (3, 0): - color = wx.Colour(0, 0, 0) - brush = wx.Brush(color) - - # noinspection PyPackageRequirements,PyUnresolvedReferences,PyUnresolvedReferences,PyUnresolvedReferences - from Carbon.Appearance import kThemeBrushDialogBackgroundActive - # noinspection PyUnresolvedReferences - brush.MacSetTheme(kThemeBrushDialogBackgroundActive) - else: - color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DFACE) - brush = wx.Brush(color) - - if "wxGTK" not in wx.PlatformInfo: - mdc.SetBackground(brush) - mdc.Clear() - - selected = None - - tabsWidth = 0 - - for tab in self.tabs: - tabsWidth += tab.tabWidth - self.inclination * 2 - - if self.showAddButton: - ax, ay = self.addButton.GetPosition() - mdc.DrawBitmap(self.addButton.Render(), ax, ay, True) - - for i in range(len(self.tabs) - 1, -1, -1): - tab = self.tabs[i] - posx, posy = tab.GetPosition() - - if not tab.IsSelected(): - mdc.DrawBitmap(self.efxBmp, posx, posy, True) - bmp = tab.Render() - img = bmp.ConvertToImage() - img = img.AdjustChannels(1, 1, 1, 0.85) - bmp = wx.BitmapFromImage(img) - mdc.DrawBitmap(bmp, posx, posy, True) - - else: - selected = tab - - if selected: - posx, posy = selected.GetPosition() - mdc.DrawBitmap(self.efxBmp, posx, posy, True) - - bmp = selected.Render() - - if self.dragging: - img = bmp.ConvertToImage() - img = img.AdjustChannels(1.2, 1.2, 1.2, 0.7) - bmp = wx.BitmapFromImage(img) - - mdc.DrawBitmap(bmp, posx, posy, True) - - def OnErase(self, event): - pass - - def UpdateTabFX(self): - w, h = self.tabShadow.GetSize() - if self.efxBmp is None or w != self.tabMinWidth: - self.tabShadow.SetSize((self.tabMinWidth, self.height + 1)) - fxBmp = self.tabShadow.Render() - - simg = fxBmp.ConvertToImage() - if not simg.HasAlpha(): - simg.InitAlpha() - simg = simg.Blur(2) - simg = simg.AdjustChannels(0.3, 0.3, 0.3, 0.35) - - self.efxBmp = wx.BitmapFromImage(simg) - - def AddTab(self, title=wx.EmptyString, img=None, showClose=False): - self.ClearTabsSelected() - - tabRenderer = PFTabRenderer((120, self.height), title, img, self.inclination, closeButton=showClose) - tabRenderer.SetSelected(True) - - self.tabs.append(tabRenderer) - self.AdjustTabsSize() - self.Refresh() - - def ClearTabsSelected(self): - for tab in self.tabs: - tab.SetSelected(False) - - def DisableTab(self, tab, disabled=True): - tabRenderer = self.tabs[tab] - tabRenderer.disabled = disabled - - self.AdjustTabsSize() - self.Refresh() - - def DeleteTab(self, tab, external=False): - tabRenderer = self.tabs[tab] - wasSelected = tabRenderer.GetSelected() - self.tabs.remove(tabRenderer) - - if tabRenderer: - del tabRenderer - - if wasSelected and self.GetTabsCount() > 0: - if tab > self.GetTabsCount() - 1: - self.tabs[self.GetTabsCount() - 1].SetSelected(True) - else: - self.tabs[tab].SetSelected(True) - - if not external: - self.Parent.DeletePage(tab, True) - - self.AdjustTabsSize() - self.Refresh() - - def GetTabsCount(self): - return len(self.tabs) - - def AdjustTabsSize(self): - tabMinWidth = 9000000 # Really, it should be over 9000 - tabMaxWidth = 0 - for tab in self.tabs: - mw, mh = tab.GetMinSize() - if tabMinWidth > mw: - tabMinWidth = mw - if tabMaxWidth < mw: - tabMaxWidth = mw - if tabMaxWidth < 100: - tabMaxWidth = 100 - if self.GetTabsCount() > 0: - if (self.GetTabsCount()) * (tabMaxWidth - self.inclination * 2) > self.tabContainerWidth: - self.tabMinWidth = float(self.tabContainerWidth) / float(self.GetTabsCount()) + self.inclination * 2 - else: - self.tabMinWidth = tabMaxWidth - - if self.tabMinWidth < 1: - self.tabMinWidth = 1 - for tab in self.tabs: - tab.GetSize() - tab.SetSize((self.tabMinWidth, self.height)) - - if self.GetTabsCount() > 0: - self.UpdateTabFX() - - self.UpdateTabsPosition() - - def UpdateTabsPosition(self, skipTab=None): - tabsWidth = 0 - for tab in self.tabs: - tabsWidth += tab.tabWidth - self.inclination * 2 - - pos = tabsWidth - selected = None - selpos = None - for i in range(len(self.tabs) - 1, -1, -1): - tab = self.tabs[i] - width = tab.tabWidth - self.inclination * 2 - pos -= width - if not tab.IsSelected(): - tab.SetPosition((pos, self.containerHeight - self.height)) - else: - selected = tab - selpos = pos - if selected is not skipTab: - selected.SetPosition((selpos, self.containerHeight - self.height)) - self.addButton.SetPosition( - ( - round(tabsWidth) + self.inclination * 2, - self.containerHeight - self.height / 2 - self.addButton.GetHeight() / 3 - ) - ) - - def OnLeaveWindow(self, event): - - if self.startDrag and not self.dragging: - self.dragging = False - self.startDrag = False - self.draggedTab = None - self.dragTrigger = self.dragTrail - if self.HasCapture(): - self.ReleaseMouse() - - if self.previewWnd: - self.previewWnd.Show(False) - del self.previewWnd - self.previewWnd = None - event.Skip() - - def OnTimer(self, event): - mposx, mposy = wx.GetMousePosition() - cposx, cposy = self.ScreenToClient((mposx, mposy)) - if self.FindTabAtPos(cposx, cposy) == self.previewTab: - if not self.previewTab.GetSelected(): - page = self.Parent.GetPage(self.GetTabIndex(self.previewTab)) - if page.Snapshot(): - self.previewWnd = PFNotebookPagePreview( - self, - (mposx + 3, mposy + 3), - page.Snapshot(), - self.previewTab.text - ) - self.previewWnd.Show() - - event.Skip() - - -class PFNotebookPagePreview(wx.Frame): - def __init__(self, parent, pos, bitmap, title): - wx.Frame.__init__( - self, - parent, - id=wx.ID_ANY, - title=wx.EmptyString, - pos=pos, - size=wx.DefaultSize, - style=wx.NO_BORDER | wx.FRAME_NO_TASKBAR | wx.STAY_ON_TOP - ) - - self.title = title - self.bitmap = bitmap - self.SetSize((bitmap.GetWidth(), bitmap.GetHeight())) - self.Bind(wx.EVT_PAINT, self.OnWindowPaint) - self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnWindowEraseBk) - self.Bind(wx.EVT_TIMER, self.OnTimer) - - self.timer = wx.Timer(self, wx.ID_ANY) - self.timerSleep = None - self.timerSleepId = wx.NewId() - self.direction = 1 - self.padding = 15 - self.transp = 0 - - hfont = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) - self.SetFont(hfont) - - tx, ty = self.GetTextExtent(self.title) - tx += self.padding * 2 - - if bitmap.GetWidth() < tx: - width = tx - else: - width = bitmap.GetWidth() - - self.SetSize((width, bitmap.GetHeight() + 16)) - - self.SetTransparent(0) - self.Refresh() - - def OnTimer(self, event): - self.transp += 20 * self.direction - - if self.transp > 220: - self.transp = 220 - self.timer.Stop() - - if self.transp < 0: - self.transp = 0 - self.timer.Stop() - wx.Frame.Show(self, False) - self.Destroy() - return - self.SetTransparent(self.transp) - - def RaiseParent(self): - wnd = self - lastwnd = None - while wnd is not None: - lastwnd = wnd - wnd = wnd.Parent - if lastwnd: - lastwnd.Raise() - - def Show(self, showWnd=True): - if showWnd: - wx.Frame.Show(self, showWnd) - self.RaiseParent() - self.direction = 1 - self.timer.Start(10) - else: - self.direction = -1 - self.timer.Start(10) - - def OnWindowEraseBk(self, event): - pass - - def OnWindowPaint(self, event): - rect = self.GetRect() - canvas = wx.EmptyBitmap(rect.width, rect.height) - mdc = wx.BufferedPaintDC(self) - mdc.SelectObject(canvas) - color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - mdc.SetBackground(wx.Brush(color)) - mdc.Clear() - - font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) - mdc.SetFont(font) - - x, y = mdc.GetTextExtent(self.title) - - mdc.SetBrush(wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT))) - mdc.DrawRectangle(0, 0, rect.width, 16) - - mdc.SetTextForeground(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)) - - mdc.DrawText(self.title, (rect.width - x) / 2, (16 - y) / 2) - - mdc.DrawBitmap(self.bitmap, 0, 16) - - mdc.SetPen(wx.Pen("#000000", width=1)) - mdc.SetBrush(wx.TRANSPARENT_BRUSH) - - mdc.DrawRectangle(0, 16, rect.width, rect.height - 16) diff --git a/gui/chrome_tabs.py b/gui/chrome_tabs.py new file mode 100644 index 000000000..c03edacab --- /dev/null +++ b/gui/chrome_tabs.py @@ -0,0 +1,1481 @@ +#=============================================================================== +# +# ToDo: Bug - when selecting close on a tab, sometimes the tab to the right is +# selected, most likely due to determination of mouse position +# ToDo: Tab Selection seems overly complicated. OnLeftDown finds tab at +# position, and then call's CheckTabSelected which calls TabHitTest (when +# we are already aware it will return due to FindTabAtPos) +# ToDo: Perhaps a better way of finding tabs at position instead of looping +# through them and getting their regions. Perhaps some smart trickery with +# mouse pos x (all tabs have same width, so we divide x by width to find +# tab index?). This will also help with finding close buttons. +# ToDo: Fix page preview code (PFNotebookPagePreview) +# +#= ============================================================================== + +import wx +import wx.lib.newevent + +from gui.bitmap_loader import BitmapLoader +from gui.utils import draw +from gui.utils import color as color_utils +from service.fit import Fit + +_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent() +_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent() +_PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent() +_PageClosing, EVT_NOTEBOOK_PAGE_CLOSING = wx.lib.newevent.NewEvent() +PageAdded, EVT_NOTEBOOK_PAGE_ADDED = wx.lib.newevent.NewEvent() +PageClosed, EVT_NOTEBOOK_PAGE_CLOSED = wx.lib.newevent.NewEvent() + + +class VetoAble(): + def __init__(self): + self.__vetoed = False + + def Veto(self): + self.__vetoed = True + + def isVetoed(self): + return self.__vetoed + + +class NotebookTabChangeEvent(): + def __init__(self, old, new): + self.__old = old + self.__new = new + + def GetOldSelection(self): + return self.__old + + def GetSelection(self): + return self.__new + + OldSelection = property(GetOldSelection) + Selection = property(GetSelection) + + +class PageChanging(_PageChanging, NotebookTabChangeEvent, VetoAble): + def __init__(self, old, new): + _PageChanging.__init__(self) + NotebookTabChangeEvent.__init__(self, old, new) + VetoAble.__init__(self) + + +class PageChanged(_PageChanged, NotebookTabChangeEvent): + def __init__(self, old, new): + _PageChanged.__init__(self) + NotebookTabChangeEvent.__init__(self, old, new) + + +class PageClosing(_PageClosing, VetoAble): + def __init__(self, i): + _PageClosing.__init__(self) + self.__index = i + VetoAble.__init__(self) + self.Selection = property(self.GetSelection) + + def GetSelection(self): + return self.__index + + +class PageAdding(_PageAdding, VetoAble): + def __init__(self): + _PageAdding.__init__(self) + VetoAble.__init__(self) + + +class ChromeNotebook(wx.Panel): + + def __init__(self, parent, can_add=True): + """ + Instance of Notebook. Initializes general layout, includes methods + for setting current page, replacing pages, any public function for the + notebook + """ + super().__init__(parent, wx.ID_ANY, size=(-1, -1)) + + self._pages = [] + self._active_page = None + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + tabs_sizer = wx.BoxSizer(wx.VERTICAL) + self.tabs_container = _TabsContainer(self, can_add=can_add) + tabs_sizer.Add(self.tabs_container, 0, wx.EXPAND) + + if 'wxMSW' in wx.PlatformInfo: + style = wx.DOUBLE_BORDER + else: + style = wx.SIMPLE_BORDER + + back_color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + + content_sizer = wx.BoxSizer(wx.VERTICAL) + self.page_container = wx.Panel(self, style=style) + self.page_container.SetBackgroundColour(back_color) + content_sizer.Add(self.page_container, 1, wx.EXPAND, 5) + + main_sizer.Add(tabs_sizer, 0, wx.EXPAND, 5) + main_sizer.Add(content_sizer, 1, wx.EXPAND | wx.BOTTOM, 2) + + self.SetSizer(main_sizer) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Layout() + + def GetPage(self, i): + return self._pages[i] + + def SetPage(self, i, page): + if i >= len(self._pages) or i is None or page is None: + return + + old_page = self._pages[i] + self._pages[i] = page + if old_page == self._active_page: + old_page.Destroy() + self._active_page = page + else: + old_page.Destroy() + + page.Reparent(self.page_container) + + if self._active_page == page: + self.ShowActive() + + def GetBorders(self): + """Gets border widths to better determine page size in ShowActive()""" + + bx = wx.SystemSettings.GetMetric(wx.SYS_BORDER_X) + by = wx.SystemSettings.GetMetric(wx.SYS_BORDER_Y) + + if bx < 0: + bx = 1 + if by < 0: + by = 1 + + return bx, by + + def ReplaceActivePage(self, page): + self.SetPage(self.GetSelection(), page) + + def GetSelectedPage(self): + return self._active_page + + def GetPageIndex(self, page): + return self._pages.index(page) if page in self._pages else None + + def GetSelection(self): + return self.GetPageIndex(self._active_page) + + def GetCurrentPage(self): + return self._active_page + + def GetPageCount(self): + return len(self._pages) + + def NextPage(self): + """Used with keyboard shortcut for next page navigation""" + current_page = self.GetSelection() + + if current_page is None: + return + + if current_page < self.GetPageCount() - 1: + self.SetSelection(current_page + 1) + new_page = current_page + 1 + else: + self.SetSelection(0) + new_page = 0 + + wx.PostEvent(self, PageChanged(current_page, new_page)) + + def PrevPage(self): + """Used with keyboard shortcut for previous page navigation""" + current_page = self.GetSelection() + + if current_page is None: + return + + if current_page > 0: + self.SetSelection(current_page - 1) + new_page = current_page - 1 + else: + self.SetSelection(self.GetPageCount() - 1) + new_page = self.GetPageCount() - 1 + + wx.PostEvent(self, PageChanged(current_page, new_page)) + + def AddPage(self, win=None, title="Empty Tab", image: wx.Image=None, closeable=True): + if self._active_page: + self._active_page.Hide() + + if not win: + win = wx.Panel(self) + + win.Reparent(self.page_container) + + self.page_container.Layout() + + self._pages.append(win) + self.tabs_container.AddTab(title, image, closeable) + + self._active_page = win + self.ShowActive(True) + + def DisablePage(self, page, toggle): + idx = self.GetPageIndex(page) + + if toggle and page == self._active_page: + try: + # Set page to the first non-disabled page + self.SetSelection(next(i for i, _ in enumerate(self._pages) if not self.tabs_container.tabs[i].disabled)) + except StopIteration: + self.SetSelection(0) + + self.tabs_container.DisableTab(idx, toggle) + + def SetSelection(self, page): + old_selection = self.GetSelection() + if old_selection != page: + self._active_page.Hide() + self._active_page = self._pages[page] + self.tabs_container.SetSelected(page) + self.ShowActive() + + def DeletePage(self, n): + page = self._pages[n] + self._pages.remove(page) + page.Destroy() + + self.tabs_container.DeleteTab(n) + + selection = self.tabs_container.GetSelected() + if selection is not None: + self._active_page = self._pages[selection] + self.ShowActive() + wx.PostEvent(self, PageChanged(-1, selection)) + else: + self._active_page = None + + def SwitchPages(self, src, dst): + self._pages[src], self._pages[dst] = self._pages[dst], self._pages[src] + + def ShowActive(self, resize_only=False): + """ + Sets the size of the page and shows. The sizing logic adjusts for some + minor sizing errors (scrollbars going beyond bounds) + + resize_only + if we are not interested in showing the page, only setting the size + + @todo: is resize_only still needed? Was introduced with 8b8b97 in mid + 2011 to fix a resizing bug with blank _pages, cannot reproduce + 13Sept2014 + """ + + ww, wh = self.page_container.GetSize() + bx, by = self.GetBorders() + ww -= bx * 4 + wh -= by * 4 + self._active_page.SetSize((max(ww, -1), max(wh, -1))) + self._active_page.SetPosition((0, 0)) + + if not resize_only: + self._active_page.Show() + + self.Layout() + + def IsActive(self, page): + return self._active_page == page + + def SetPageTitle(self, i, text, refresh=True): + tab = self.tabs_container.tabs[i] + tab.text = text + if refresh: + self.tabs_container.AdjustTabsSize() + self.Refresh() + + def SetPageIcon(self, i, icon, refresh=True): + tab = self.tabs_container.tabs[i] + tab.tab_img = icon + if refresh: + self.tabs_container.AdjustTabsSize() + self.Refresh() + + def SetPageTextIcon(self, i, text=wx.EmptyString, icon=None): + self.SetPageTitle(i, text, False) + self.SetPageIcon(i, icon, False) + self.tabs_container.AdjustTabsSize() + self.Refresh() + + def Refresh(self): + self.tabs_container.Refresh() + + def OnSize(self, event): + w, h = self.GetSize() + self.tabs_container.SetSize((w, -1)) + self.tabs_container.UpdateSize() + self.tabs_container.Refresh() + self.Layout() + + if self._active_page: + self.ShowActive() + event.Skip() + + +class _TabRenderer: + def __init__(self, size=(36, 24), text=wx.EmptyString, img: wx.Image=None, + closeable=True): + + # tab left/right zones inclination + self.ctab_left = BitmapLoader.getImage("ctableft", "gui") + self.ctab_middle = BitmapLoader.getImage("ctabmiddle", "gui") + self.ctab_right = BitmapLoader.getImage("ctabright", "gui") + self.ctab_close = BitmapLoader.getImage("ctabclose", "gui") + + self.left_width = self.ctab_left.GetWidth() + self.right_width = self.ctab_right.GetWidth() + self.middle_width = self.ctab_middle.GetWidth() + self.close_btn_width = self.ctab_close.GetWidth() + + width, height = size + + self.min_width = self.left_width + self.right_width + self.middle_width + self.min_height = self.ctab_middle.GetHeight() + + # set minimum width and height to what is allotted to images + width = max(width, self.min_width) + height = max(height, self.min_height) + + self.disabled = False + self.text = text + self.tab_size = (width, height) + self.closeable = closeable + self.selected = False + self.close_btn_hovering = False + self.tab_bitmap = None + self.tab_back_bitmap = None + self.padding = 4 + self.font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False) + + self.tab_img = img + self.position = (0, 0) # Not used internally for rendering - helper for tab container + self.InitTab() + + def SetPosition(self, position): + self.position = position + + def GetPosition(self): + return self.position + + def GetSize(self): + return self.tab_size + + def SetSize(self, size): + width, height = size + + width = max(width, self.min_width) + height = max(height, self.min_height) + + self.tab_size = (width, height) + self.InitTab() + + def SetSelected(self, sel=True): + self.selected = sel + self.InitTab() + + def GetSelected(self): + return self.selected + + def IsSelected(self): + return self.selected + + def ShowCloseButtonHovering(self, hover=True): + if self.close_btn_hovering != hover: + self.close_btn_hovering = hover + self._Render() + + def GetCloseButtonHoverStatus(self): + return self.close_btn_hovering + + def GetTabRegion(self): + new_region = self.CopyRegion(self.tab_region) + new_region.Subtract(self.close_region) if self.closeable else self.tab_region + return new_region + + def GetCloseButtonRegion(self): + return self.CopyRegion(self.close_region) + + def GetMinSize(self): + ebmp = wx.Bitmap(1, 1) + mdc = wx.MemoryDC() + mdc.SelectObject(ebmp) + mdc.SetFont(self.font) + textSizeX, textSizeY = mdc.GetTextExtent(self.text) + totalSize = self.left_width + self.right_width + textSizeX + self.close_btn_width / 2 + 16 + self.padding* 2 + mdc.SelectObject(wx.NullBitmap) + return totalSize, self.tab_height + + def SetTabImage(self, img): + self.tab_img = img + + def CopyRegion(self, region): + rect = region.GetBox() + + newRegion = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) + newRegion.Intersect(region) + + return newRegion + + def InitTab(self): + self.tab_width, self.tab_height = self.tab_size + + self.content_width = self.tab_width - self.left_width - self.right_width + self.tab_region = None + self.close_region = None + + self.InitColors() + self.InitBitmaps() + + self.ComposeTabBack() + self.InitTabRegions() + self._Render() + + def InitBitmaps(self): + """ + Creates bitmap for tab + + Takes the bitmaps already set and replaces a known color (black) with + the needed color, while also considering selected state. Color dependant + on platform -- see InitColors(). + """ + if self.selected: + tr, tg, tb, ta = self.selected_color + else: + tr, tg, tb, ta = self.inactive_color + + ctab_left = self.ctab_left.Copy() + ctab_right = self.ctab_right.Copy() + ctab_middle = self.ctab_middle.Copy() + + ctab_left.Replace(0, 0, 0, tr, tg, tb) + ctab_right.Replace(0, 0, 0, tr, tg, tb) + ctab_middle.Replace(0, 0, 0, tr, tg, tb) + + self.ctab_left_bmp = wx.Bitmap(ctab_left) + self.ctab_right_bmp = wx.Bitmap(ctab_right) + self.ctab_middle_bmp = wx.Bitmap(ctab_middle) + self.ctab_close_bmp = wx.Bitmap(self.ctab_close) + + def ComposeTabBack(self): + """ + Creates the tab background bitmap based upon calculated dimension values + and modified bitmaps via InitBitmaps() + """ + bk_bmp = wx.Bitmap(self.tab_width, self.tab_height) + + mdc = wx.MemoryDC() + mdc.SelectObject(bk_bmp) + mdc.Clear() + + # draw the left bitmap + mdc.DrawBitmap(self.ctab_left_bmp, 0, 0) + + # convert middle bitmap and scale to tab width + cm = self.ctab_middle_bmp.ConvertToImage() + mimg = cm.Scale(self.content_width, self.ctab_middle.GetHeight(), + wx.IMAGE_QUALITY_NORMAL) + mbmp = wx.Bitmap(mimg) + + # draw middle bitmap, offset by left + mdc.DrawBitmap(mbmp, self.left_width, 0) + + # draw right bitmap offset by left + middle + mdc.DrawBitmap(self.ctab_right_bmp, + self.content_width + self.left_width, 0) + + mdc.SelectObject(wx.NullBitmap) + + if self.tab_back_bitmap: + del self.tab_back_bitmap + + self.tab_back_bitmap = bk_bmp + + def InitTabRegions(self): + """ + Initializes regions for tab, which makes it easier to determine if + given coordinates are included in a region + """ + self.tab_region = wx.Region(self.tab_back_bitmap) + self.close_region = wx.Region(self.ctab_close_bmp) + + x_offset = self.content_width \ + + self.left_width \ + - self.ctab_close_bmp.GetWidth() / 2 + y_offset = (self.tab_height - self.ctab_close_bmp.GetHeight()) / 2 + self.close_region.Offset(x_offset, y_offset) + + def InitColors(self): + """Determines colors used for tab, based on system settings""" + self.tab_color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + self.inactive_color = color_utils.GetSuitable(self.tab_color, 0.25) + self.selected_color = color_utils.GetSuitable(self.tab_color, 0.10) + + def Render(self): + return self.tab_bitmap + + def _Render(self): + """Renders the tab, complete with the icon, text, and close button""" + if self.tab_bitmap: + del self.tab_bitmap + + height = self.tab_height + + canvas = wx.Bitmap(self.tab_width, self.tab_height, 24) + + mdc = wx.MemoryDC() + + mdc.SelectObject(canvas) + mdc.Clear() + + mdc.DrawBitmap(self.tab_back_bitmap, 0, 0, True) + + # draw the tab icon + if self.tab_img: + bmp = wx.Bitmap(self.tab_img.ConvertToGreyscale() if self.disabled else self.tab_img) + # @todo: is this conditional relevant anymore? + if self.content_width > 16: + # Draw tab icon + mdc.DrawBitmap( + bmp, + self.left_width + self.padding - bmp.GetWidth() / 2, + (height - bmp.GetHeight()) / 2) + text_start = self.left_width + self.padding + bmp.GetWidth() / 2 + else: + text_start = self.left_width + + mdc.SetFont(self.font) + + maxsize = self.tab_width \ + - text_start \ + - self.right_width \ + - self.padding * 4 + color = self.selected_color if self.selected else self.inactive_color + + mdc.SetTextForeground(color_utils.GetSuitable(color, 1)) + + # draw text (with no ellipses) + text = draw.GetPartialText(mdc, self.text, maxsize, "") + tx, ty = mdc.GetTextExtent(text) + mdc.DrawText(text, text_start + self.padding, height / 2 - ty / 2) + + # draw close button + if self.closeable: + if self.close_btn_hovering: + cbmp = self.ctab_close_bmp + else: + cimg = self.ctab_close_bmp.ConvertToImage() + cimg = cimg.AdjustChannels(0.7, 0.7, 0.7, 0.3) + cbmp = wx.Bitmap(cimg) + + mdc.DrawBitmap( + cbmp, + self.content_width + self.left_width - cbmp.GetWidth() / 2, + (height - cbmp.GetHeight()) / 2) + + mdc.SelectObject(wx.NullBitmap) + + canvas.SetMaskColour((0x12, 0x23, 0x32)) + img = canvas.ConvertToImage() + + if not img.HasAlpha(): + img.InitAlpha() + + bmp = wx.Bitmap(img) + self.tab_bitmap = bmp + + def __repr__(self): + return "_TabRenderer(text={}, disabled={}) at {}".format( + self.text, self.disabled, hex(id(self)) + ) + + +class _AddRenderer: + def __init__(self): + """Renders the add tab button""" + self.add_img = BitmapLoader.getImage("ctabadd", "gui") + self.width = self.add_img.GetWidth() + self.height = self.add_img.GetHeight() + + self.region = None + self.tbmp = wx.Bitmap(self.add_img) + self.add_bitmap = None + + self.position = (0, 0) + self.highlighted = False + + self.InitRenderer() + + def GetPosition(self): + return self.position + + def SetPosition(self, pos): + self.position = pos + + def GetSize(self): + return self.width, self.height + + def GetHeight(self): + return self.height + + def GetWidth(self): + return self.width + + def InitRenderer(self): + self.region = self.CreateRegion() + self._Render() + + def CreateRegion(self): + region = wx.Region(self.tbmp) + return region + + def CopyRegion(self, region): + rect = region.GetBox() + + new_region = wx.Region(rect.X, rect.Y, rect.Width, rect.Height) + new_region.Intersect(region) + + return new_region + + def GetRegion(self): + return self.CopyRegion(self.region) + + def Highlight(self, highlight=False): + self.highlighted = highlight + self._Render() + + def IsHighlighted(self): + return self.highlighted + + def Render(self): + return self.add_bitmap + + def _Render(self): + if self.add_bitmap: + del self.add_bitmap + + alpha = 1 if self.highlighted else 0.3 + + img = self.add_img.AdjustChannels(1, 1, 1, alpha) + bmp = wx.Bitmap(img) + self.add_bitmap = bmp + + +class _TabsContainer(wx.Panel): + def __init__(self, parent, pos=(50, 0), size=(100, 22), id=wx.ID_ANY, + can_add=True): + """ + Defines the tab container. Handles functions such as tab selection and + dragging, and defines minimum width of tabs (all tabs are of equal + width, which is determined via widest tab). Also handles the tab + preview, if any. + """ + + super().__init__(parent, id, pos, size) + + self.tabs = [] + self.width, self.height = size + self.container_height = self.height + self.start_drag = False + self.dragging = False + + # amount of overlap of tabs? + self.inclination = 7 + + if can_add: + self.reserved = 48 + else: + self.reserved = self.inclination * 4 + + # pixel distance to drag before we actually start dragging + self.drag_trail = 5 + + self.dragx = 0 + self.dragy = 0 + self.dragged_tab = None + self.drag_trigger = self.drag_trail + + self.show_add_button = can_add + + self.tab_container_width = self.width - self.reserved + self.tab_min_width = self.width + self.tab_shadow = _TabRenderer((self.tab_min_width, self.height + 1)) + + self.add_button = _AddRenderer() + self.add_bitmap = self.add_button.Render() + + self.preview_timer = None + self.preview_timer_id = wx.ID_ANY + self.preview_wnd = None + self.preview_bmp = None + self.preview_pos = None + self.preview_tab = None + + self.Bind(wx.EVT_TIMER, self.OnTimer) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase) + self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) + self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) + self.Bind(wx.EVT_MOTION, self.OnMotion) + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_SYS_COLOUR_CHANGED, self.OnSysColourChanged) + + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + + def OnSysColourChanged(self, event): + for tab in self.tabs: + tab.InitTab() + self.Refresh() + + def OnSize(self, event): + self.UpdateSize() + event.Skip() + + def UpdateSize(self): + """Update tab sizes based on new tab container size""" + width, _ = self.GetSize() + if width != self.width: + self.width = width + self.tab_container_width = self.width - self.reserved + self.AdjustTabsSize() + + def OnLeftDown(self, event): + """ Select tab on mouse down and start dragging logic """ + mposx, mposy = event.GetPosition() + if not self.start_drag: + tab = self.FindTabAtPos(mposx, mposy) + if tab: + self.CheckTabSelected(tab, mposx, mposy) + if self.show_add_button: + # If we can add tabs, we can drag them. Set flag + self.start_drag = True + tx, ty = tab.GetPosition() + self.dragx = mposx - tx + self.dragy = self.container_height - self.height + self.Refresh() + + self.dragged_tab = tab + + def OnMotion(self, event): + """ + Determines what happens when the mouse moves. This handles primarily + dragging (region tab can be dragged) as well as checking if we are over + an actionable button. + """ + mposx, mposy = event.GetPosition() + + if self.start_drag: + if not self.dragging: + if self.drag_trigger < 0: + self.dragging = True + self.drag_trigger = self.drag_trail + self.CaptureMouse() + else: + self.drag_trigger -= 1 + if self.dragging: + # we wish to keep tab within tab container boundaries. To do + # this, we must detect when mouse moves outside of boundaries. + # Set hard limits to 0 and the size of tab container. + dtx = mposx - self.dragx + w, h = self.dragged_tab.GetSize() + + dtx = max(dtx, 0) + + if dtx + w > self.tab_container_width + self.inclination * 2: + dtx = self.tab_container_width - w + self.inclination * 2 + + self.dragged_tab.SetPosition((dtx, self.dragy)) + + # we must modify the surrounding tabs + index = self.GetTabIndex(self.dragged_tab) + + left_tab = self.GetTabAtLeft(index) + right_tab = self.GetTabAtRight(index) + + if left_tab: + lw, lh = left_tab.GetSize() + lx, ly = left_tab.GetPosition() + + if lx + lw / 2 - self.inclination * 2 > dtx: + self.SwitchTabs(index - 1, index, self.dragged_tab) + + if right_tab: + rw, rh = right_tab.GetSize() + rx, ry = right_tab.GetPosition() + + if rx + rw / 2 + self.inclination * 2 < dtx + w: + self.SwitchTabs(index + 1, index, self.dragged_tab) + + self.UpdateTabsPosition(self.dragged_tab) + self.Refresh() + return + + # If we aren't dragging, check for actionable buttons under mouse + self.CheckCloseHighlighted(mposx, mposy) + self.CheckAddHighlighted(mposx, mposy) + self.CheckTabPreview(mposx, mposy) + + event.Skip() + + def OnLeftUp(self, event): + """Determines what happens when user left clicks (up)""" + mposx, mposy = event.GetPosition() + if self.start_drag and self.dragging: + self.dragging = False + self.start_drag = False + self.dragged_tab = None + self.drag_trigger = self.drag_trail + self.UpdateTabsPosition() + self.Refresh() + + if self.HasCapture(): + self.ReleaseMouse() + + return + + if self.start_drag: + self.start_drag = False + self.drag_trigger = self.drag_trail + + # Checks if we selected the add button and, if True, returns + if self.CheckAddButton(mposx, mposy): + return + + # If there are no tabs, don't waste time + if self.GetTabsCount() == 0: + return + + # Gets selected tab (was set when user down clicked) + sel_tab = self.GetSelectedTab() + + # Check if we selected close button for selected tab + if self.CheckTabClose(sel_tab, mposx, mposy): + return + + # Check if we selected close button for all others + for tab in self.tabs: + if self.CheckTabClose(tab, mposx, mposy): + return + + def DisableTab(self, tab, disabled=True): + tb_renderer = self.tabs[tab] + tb_renderer.disabled = disabled + + self.AdjustTabsSize() + self.Refresh() + + def GetSelectedTab(self): + for tab in self.tabs: + if tab.GetSelected(): + return tab + return None + + def GetSelected(self): + for tab in self.tabs: + if tab.GetSelected(): + return self.tabs.index(tab) + return None + + def SetSelected(self, tabIndex): + """Set tab as selected given its index""" + old_sel_tab = self.GetSelectedTab() + old_sel_tab.SetSelected(False) + self.tabs[tabIndex].SetSelected(True) + self.Refresh() + + def CheckTabSelected(self, tab, x, y): + """ + Selects the tab at x, y. If the tab at x, y is already selected, simply + return true. Otherwise, perform TabHitTest and set tab at position to + selected + """ + old_sel_tab = self.GetSelectedTab() + if old_sel_tab == tab: + return True + + if self.TabHitTest(tab, x, y): + if tab.disabled: + return + tab.SetSelected(True) + old_sel_tab.SetSelected(False) + + ev = PageChanging(self.tabs.index(old_sel_tab), self.tabs.index(tab)) + wx.PostEvent(self.Parent, ev) + + if ev.isVetoed(): + return False + + self.Refresh() + sel_tab = self.tabs.index(tab) + self.Parent.SetSelection(sel_tab) + + wx.PostEvent(self.Parent, PageChanged(self.tabs.index(old_sel_tab), + self.tabs.index(tab))) + + return True + + return False + + def CheckTabClose(self, tab, x, y): + """ + Determines if close button was selected for the given tab by comparing + x and y position with known close button region + """ + if not tab.closeable: # if not able to close, return False + return False + + region = tab.GetCloseButtonRegion() + posx, posy = tab.GetPosition() + region.Offset(posx, posy) + + if region.Contains(x, y): + index = self.tabs.index(tab) + ev = PageClosing(index) + wx.PostEvent(self.Parent, ev) + + if ev.isVetoed(): + return False + + index = self.GetTabIndex(tab) + self.Parent.DeletePage(index) + wx.PostEvent(self.Parent, PageClosed(index=index)) + + sel = self.GetSelected() + if sel is not None: + wx.PostEvent(self.Parent, PageChanged(-1, sel)) + + return True + return False + + def CheckAddButton(self, x, y): + """ + Determines if add button was selected by comparing x and y position with + add button region + """ + if not self.show_add_button: # if not able to add, return False + return + + region = self.add_button.GetRegion() + ax, ay = self.add_button.GetPosition() + region.Offset(ax, ay) + + if region.Contains(x, y): + ev = PageAdding() + wx.PostEvent(self.Parent, ev) + if ev.isVetoed(): + return False + + self.Parent.AddPage() + wx.PostEvent(self.Parent, PageAdded()) + return True + + def CheckCloseHighlighted(self, x, y): + """ + Checks if mouse pos at x, y is over a close button. If so, set the + close hovering flag for that tab + """ + dirty = False + + for tab in self.tabs: + region = tab.GetCloseButtonRegion() + posx, posy = tab.GetPosition() + region.Offset(posx, posy) + + if region.Contains(x, y): + if not tab.GetCloseButtonHoverStatus(): + tab.ShowCloseButtonHovering(True) + dirty = True + else: + if tab.GetCloseButtonHoverStatus(): + tab.ShowCloseButtonHovering(False) + dirty = True + if dirty: + self.Refresh() + break + + def FindTabAtPos(self, x, y): + if self.GetTabsCount() == 0: + return None + + # test current tab first + sel_tab = self.GetSelectedTab() + if self.TabHitTest(sel_tab, x, y): + return sel_tab + + # test all other tabs next + for tab in self.tabs: + if self.TabHitTest(tab, x, y): + return tab + + return None + + def TabHitTest(self, tab, x, y): + """ Test if x and y are contained within a tabs region """ + tabRegion = tab.GetTabRegion() + tabPos = tab.GetPosition() + tabPosX, tabPosY = tabPos + tabRegion.Offset(tabPosX, tabPosY) + + if tabRegion.Contains(x, y): + return True + + return False + + def GetTabAtLeft(self, tab_index): + return self.tabs[tab_index - 1] if tab_index > 0 else None + + def GetTabAtRight(self, tab_index): + if tab_index < self.GetTabsCount() - 1: + return self.tabs[tab_index + 1] + else: + return None + + def SwitchTabs(self, src, dst, dragged_tab=None): + self.tabs[src], self.tabs[dst] = self.tabs[dst], self.tabs[src] + self.UpdateTabsPosition(dragged_tab) + self.Parent.SwitchPages(src, dst) + self.Refresh() + + def GetTabIndex(self, tab): + return self.tabs.index(tab) + + def CheckTabPreview(self, mposx, mposy): + """ + Checks to see if we have a tab preview and sets up the timer for it + to display + """ + sFit = Fit.getInstance() + if not sFit.serviceFittingOptions["showTooltip"] or False: + return + + if self.preview_timer: + if self.preview_timer.IsRunning(): + if self.preview_wnd: + self.preview_timer.Stop() + return + + if self.preview_wnd: + self.preview_wnd.Show(False) + del self.preview_wnd + self.preview_wnd = None + + for tab in self.tabs: + if not tab.GetSelected(): + if self.TabHitTest(tab, mposx, mposy): + try: + page = self.Parent.GetPage(self.GetTabIndex(tab)) + if hasattr(page, "Snapshot"): + if not self.preview_timer: + self.preview_timer = wx.Timer( + self, self.preview_timer_id) + + self.preview_tab = tab + self.preview_timer.Start(500, True) + break + except: + pass + + def CheckAddHighlighted(self, x, y): + """ + Checks to see if x, y are in add button region, and sets the highlight + flag + """ + if not self.show_add_button: + return + + region = self.add_button.GetRegion() + ax, ay = self.add_button.GetPosition() + region.Offset(ax, ay) + + if region.Contains(x, y): + if not self.add_button.IsHighlighted(): + self.add_button.Highlight(True) + self.Refresh() + else: + if self.add_button.IsHighlighted(): + self.add_button.Highlight(False) + self.Refresh() + + def OnPaint(self, event): + mdc = wx.AutoBufferedPaintDC(self) + + # if 'wxMac' in wx.PlatformInfo: + # color = wx.Colour(0, 0, 0) + # brush = wx.Brush(color) + # # @todo: what needs to be changed with wxPheonix? + # from Carbon.Appearance import kThemeBrushDialogBackgroundActive + # brush.MacSetTheme(kThemeBrushDialogBackgroundActive) + # else: + color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) + brush = wx.Brush(color) + + if "wxGTK" not in wx.PlatformInfo: + mdc.SetBackground(brush) + mdc.Clear() + + selected = None + + if self.show_add_button: + ax, ay = self.add_button.GetPosition() + mdc.DrawBitmap(self.add_button.Render(), ax, ay, True) + + for i in range(len(self.tabs) - 1, -1, -1): + tab = self.tabs[i] + posx, posy = tab.GetPosition() + + if not tab.IsSelected(): + # drop shadow first + mdc.DrawBitmap(self.fx_bmp, posx, posy, True) + bmp = tab.Render() + img = bmp.ConvertToImage() + img = img.AdjustChannels(1, 1, 1, 0.85) + bmp = wx.Bitmap(img) + mdc.DrawBitmap(bmp, posx, posy, True) + else: + selected = tab + + # we draw selected tab separately (instead of in else) so as to not hide + # the front bit behind the preceding tab + if selected: + posx, posy = selected.GetPosition() + # drop shadow first + mdc.DrawBitmap(self.fx_bmp, posx, posy, True) + + bmp = selected.Render() + + if self.dragging: + img = bmp.ConvertToImage() + img = img.AdjustChannels(1.2, 1.2, 1.2, 0.7) + bmp = wx.Bitmap(img) + + mdc.DrawBitmap(bmp, posx, posy, True) + + def OnErase(self, event): + pass + + def UpdateTabFX(self): + """ Updates tab drop shadow bitmap """ + self.tab_shadow.SetSize((self.tab_min_width, self.height + 1)) + fx_bmp = self.tab_shadow.Render() + + img = fx_bmp.ConvertToImage() + if not img.HasAlpha(): + img.InitAlpha() + img = img.Blur(2) + img = img.AdjustChannels(0.3, 0.3, 0.3, 0.35) + + self.fx_bmp = wx.Bitmap(img) + + def AddTab(self, title=wx.EmptyString, img=None, closeable=False): + self.ClearTabsSelected() + + tab_renderer = _TabRenderer((200, self.height), title, img, closeable) + tab_renderer.SetSelected(True) + + self.tabs.append(tab_renderer) + self.AdjustTabsSize() + self.Refresh() + + def ClearTabsSelected(self): + for tab in self.tabs: + tab.SetSelected(False) + + def DeleteTab(self, tab): + tab_renderer = self.tabs[tab] + was_selected = tab_renderer.GetSelected() + self.tabs.remove(tab_renderer) + + if tab_renderer: + del tab_renderer + + # determine our new selection + if was_selected and self.GetTabsCount() > 0: + if tab > self.GetTabsCount() - 1: + self.tabs[self.GetTabsCount() - 1].SetSelected(True) + else: + self.tabs[tab].SetSelected(True) + + self.AdjustTabsSize() + self.Refresh() + + def GetTabsCount(self): + return len(self.tabs) + + def AdjustTabsSize(self): + """ + Adjust tab sizes to ensure that they are all consistent and can fit into + the tab container. + """ + + # first we loop through our tabs and calculate the the largest tab. This + # is the size that we will base our calculations off + + max_width = 100 # Tab should be at least 100 + for tab in self.tabs: + mw, _ = tab.GetMinSize() # Tab min size includes tab contents + max_width = max(mw, max_width) + + # Divide tab container by number of tabs and add inclination. This will + # return the ideal max size for the containers size + if self.GetTabsCount() > 0: + dx = self.tab_container_width / self.GetTabsCount() + self.inclination * 2 + self.tab_min_width = min(dx, max_width) + + # Apply new size to all tabs + for tab in self.tabs: + tab.SetSize((self.tab_min_width, self.height)) + + if self.GetTabsCount() > 0: + # update drop shadow based on new sizes + self.UpdateTabFX() + + self.UpdateTabsPosition() + + def UpdateTabsPosition(self, skip_tab=None): + tabsWidth = 0 + for tab in self.tabs: + tabsWidth += tab.tab_width - self.inclination * 2 + + pos = tabsWidth + selected = None + for i in range(len(self.tabs) - 1, -1, -1): + tab = self.tabs[i] + width = tab.tab_width - self.inclination * 2 + pos -= width + if not tab.IsSelected(): + tab.SetPosition((pos, self.container_height - self.height)) + else: + selected = tab + selpos = pos + + if selected is not skip_tab: + selected.SetPosition((selpos, self.container_height - self.height)) + + self.add_button.SetPosition((round(tabsWidth) + self.inclination * 2, + self.container_height - self.height / 2 - self.add_button.GetHeight() / 3)) + + def OnLeaveWindow(self, event): + if self.start_drag and not self.dragging: + self.dragging = False + self.start_drag = False + self.dragged_tab = None + self.drag_trigger = self.drag_trail + if self.HasCapture(): + self.ReleaseMouse() + + if self.preview_wnd: + self.preview_wnd.Show(False) + del self.preview_wnd + self.preview_wnd = None + event.Skip() + + def OnTimer(self, event): + mposx, mposy = wx.GetMousePosition() + cposx, cposy = self.ScreenToClient((mposx, mposy)) + + if self.FindTabAtPos(cposx, cposy) == self.preview_tab: + if not self.preview_tab.GetSelected(): + page = self.Parent.GetPage(self.GetTabIndex(self.preview_tab)) + if page.Snapshot(): + + self.preview_wnd = PFNotebookPagePreview( + self, + (mposx + 3, mposy + 3), + page.Snapshot(), + self.preview_tab.text) + self.preview_wnd.Show() + + event.Skip() + + +class PFNotebookPagePreview(wx.Frame): + def __init__(self, parent, pos, bitmap, title): + super().__init__(parent, id=wx.ID_ANY, title=wx.EmptyString, pos=pos, + size=wx.DefaultSize, style=wx.NO_BORDER | + wx.FRAME_NO_TASKBAR | + wx.STAY_ON_TOP) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + self.title = title + self.bitmap = bitmap + self.SetSize((bitmap.GetWidth(), bitmap.GetHeight())) + self.Bind(wx.EVT_PAINT, self.OnWindowPaint) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnWindowEraseBk) + self.Bind(wx.EVT_TIMER, self.OnTimer) + + self.timer = wx.Timer(self, wx.ID_ANY) + self.timerSleep = None + self.timerSleepId = wx.NewId() + self.direction = 1 + self.padding = 15 + self.transp = 0 + + hfont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False) + self.SetFont(hfont) + + tx, ty = self.GetTextExtent(self.title) + tx += self.padding * 2 + + if bitmap.GetWidth() < tx: + width = tx + else: + width = bitmap.GetWidth() + + self.SetSize((width, bitmap.GetHeight() + 16)) + + self.SetTransparent(0) + self.Refresh() + + def OnTimer(self, event): + self.transp += 20 * self.direction + + if self.transp > 220: + self.transp = 220 + self.timer.Stop() + + if self.transp < 0: + self.transp = 0 + self.timer.Stop() + wx.Frame.Show(self, False) + self.Destroy() + return + self.SetTransparent(self.transp) + + def RaiseParent(self): + wnd = self + lastwnd = None + while wnd is not None: + lastwnd = wnd + wnd = wnd.Parent + if lastwnd: + lastwnd.Raise() + + def Show(self, showWnd=True): + if showWnd: + wx.Frame.Show(self, showWnd) + self.RaiseParent() + self.direction = 1 + self.timer.Start(10) + else: + self.direction = -1 + self.timer.Start(10) + + def OnWindowEraseBk(self, event): + pass + + def OnWindowPaint(self, event): + rect = self.GetRect() + canvas = wx.Bitmap(rect.width, rect.height) + mdc = wx.BufferedPaintDC(self) + mdc.SelectObject(canvas) + color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) + mdc.SetBackground(wx.Brush(color)) + mdc.Clear() + + font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False) + mdc.SetFont(font) + + x, y = mdc.GetTextExtent(self.title) + + mdc.SetBrush(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT))) + mdc.DrawRectangle(0, 0, rect.width, 16) + + mdc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) + + mdc.DrawBitmap(self.bitmap, 0, 16) + + mdc.SetPen(wx.Pen("#000000", width=1)) + mdc.SetBrush(wx.TRANSPARENT_BRUSH) + + mdc.DrawRectangle(0, 16, rect.width, rect.height - 16) + + +if __name__ == "__main__": + + # need to set up some paths, since bitmap loader requires config to have things + # Should probably change that so that it's not dependant on config + import os + os.chdir('..') + import config + config.defPaths(None) + + class Frame(wx.Frame): + def __init__(self, title): + super().__init__(None, title=title, size=(1000, 500)) + + if 'wxMSW' in wx.PlatformInfo: + color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) + self.SetBackgroundColour(color) + + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) + main_sizer.Add(splitter, 1, wx.EXPAND | wx.ALL, 2) + + # Main test notebook + self.notebook = ChromeNotebook(splitter) + + # Tests can_add, has dummy tabs + notebook2 = ChromeNotebook(splitter, can_add=False) + + self.statusbar = self.CreateStatusBar() + + panel = wx.Panel(self) + box = wx.BoxSizer(wx.VERTICAL) + + head = wx.StaticText(panel, -1, "Chome Tabs Test") + head.SetFont(wx.Font(14, wx.SWISS, wx.NORMAL, wx.BOLD)) + box.Add(head, 0, wx.ALL, 10) + + self.tctrl = wx.TextCtrl(panel, wx.ID_ANY, "Tab Name") + + self.close_check = wx.CheckBox(panel, label="Closable?") + self.close_check.SetValue(True) + + self.icon_check = wx.CheckBox(panel, label="Icon?") + self.icon_check.SetValue(True) + + button = wx.Button(panel, wx.ID_ANY, "Create") + button.Bind(wx.EVT_BUTTON, self.OnCreate) + + box.Add(self.tctrl, 0, wx.ALL, 5) + box.Add(self.close_check, 0, wx.ALL, 5) + box.Add(self.icon_check, 0, wx.ALL, 5) + box.Add(button, 0, wx.ALL, 10) + + self.notebook.AddPage(panel, "Tab1", closeable=False) + + # Add dummy pages + notebook2.AddPage() + notebook2.AddPage() + + splitter.SplitVertically(self.notebook, notebook2) + + panel.SetSizer(box) + panel.Layout() + self.SetSizer(main_sizer) + + def OnCreate(self, event): + tab_name = self.tctrl.GetValue() + tab_icon = BitmapLoader.getImage("ship_small", "gui") + self.notebook.AddPage( + title=tab_name, + image=tab_icon if self.icon_check.GetValue() else None, + closeable=self.close_check.GetValue()) + + app = wx.App(redirect=False) # Error messages go to popup window + top = Frame("Test Chrome Tabs") + top.Show() + app.MainLoop() + diff --git a/gui/contextMenu.py b/gui/contextMenu.py index 043140db3..11d574157 100644 --- a/gui/contextMenu.py +++ b/gui/contextMenu.py @@ -72,7 +72,7 @@ class ContextMenu(object): display_amount += 1 texts = m.getText(itemContext, selection) - if isinstance(texts, basestring): + if isinstance(texts, str): texts = (texts,) bitmap = m.getBitmap(srcContext, selection) @@ -112,7 +112,7 @@ class ContextMenu(object): else: rootItem.SetBitmap(bitmap) - rootMenu.AppendItem(rootItem) + rootMenu.Append(rootItem) empty = False diff --git a/gui/copySelectDialog.py b/gui/copySelectDialog.py index 57ce48f47..5675c835e 100644 --- a/gui/copySelectDialog.py +++ b/gui/copySelectDialog.py @@ -27,25 +27,25 @@ class CopySelectDialog(wx.Dialog): copyFormatEftImps = 1 copyFormatXml = 2 copyFormatDna = 3 - copyFormatCrest = 4 + copyFormatEsi = 4 copyFormatMultiBuy = 5 def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Select a format", size=(-1, -1), + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Select a format", size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE) mainSizer = wx.BoxSizer(wx.VERTICAL) - copyFormats = [u"EFT", u"EFT (Implants)", u"XML", u"DNA", u"CREST", u"MultiBuy"] - copyFormatTooltips = {CopySelectDialog.copyFormatEft: u"EFT text format", - CopySelectDialog.copyFormatEftImps: u"EFT text format", - CopySelectDialog.copyFormatXml: u"EVE native XML format", - CopySelectDialog.copyFormatDna: u"A one-line text format", - CopySelectDialog.copyFormatCrest: u"A JSON format used for EVE CREST", - CopySelectDialog.copyFormatMultiBuy: u"MultiBuy text format"} - selector = wx.RadioBox(self, wx.ID_ANY, label=u"Copy to the clipboard using:", choices=copyFormats, + copyFormats = ["EFT", "EFT (Implants)", "XML", "DNA", "CREST", "MultiBuy"] + copyFormatTooltips = {CopySelectDialog.copyFormatEft: "EFT text format", + CopySelectDialog.copyFormatEftImps: "EFT text format", + CopySelectDialog.copyFormatXml: "EVE native XML format", + CopySelectDialog.copyFormatDna: "A one-line text format", + CopySelectDialog.copyFormatEsi: "A JSON format used for EVE CREST", + CopySelectDialog.copyFormatMultiBuy: "MultiBuy text format"} + selector = wx.RadioBox(self, wx.ID_ANY, label="Copy to the clipboard using:", choices=copyFormats, style=wx.RA_SPECIFY_ROWS) selector.Bind(wx.EVT_RADIOBOX, self.Selected) - for format, tooltip in copyFormatTooltips.iteritems(): + for format, tooltip in copyFormatTooltips.items(): selector.SetItemToolTip(format, tooltip) self.copyFormat = CopySelectDialog.copyFormatEft diff --git a/gui/devTools.py b/gui/devTools.py new file mode 100644 index 000000000..98a14662c --- /dev/null +++ b/gui/devTools.py @@ -0,0 +1,114 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + +# noinspection PyPackageRequirements +import wx +from logbook import Logger +import gc +import eos +import time +import threading +from gui.builtinShipBrowser.events import FitSelected + + +pyfalog = Logger(__name__) + + +class DevTools(wx.Dialog): + DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive") + + def __init__(self, parent): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Damage Pattern Editor", size=wx.Size(400, 240)) + + self.block = False + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) + + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.id_get = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition) + mainSizer.Add(self.id_get, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + self.idBtn = wx.Button(self, wx.ID_ANY, "Print object", wx.DefaultPosition, wx.DefaultSize, 0) + mainSizer.Add(self.idBtn, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + self.idBtn.Bind(wx.EVT_BUTTON, self.objects_by_id) + + self.gcCollect = wx.Button(self, wx.ID_ANY, "GC Collect", wx.DefaultPosition, wx.DefaultSize, 0) + mainSizer.Add(self.gcCollect, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + self.gcCollect.Bind(wx.EVT_BUTTON, self.gc_collect) + + self.fitTest = wx.Button(self, wx.ID_ANY, "Test fits", wx.DefaultPosition, wx.DefaultSize, 0) + mainSizer.Add(self.fitTest, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5) + + self.fitTest .Bind(wx.EVT_BUTTON, self.fit_test) + + self.SetSizer(mainSizer) + + self.Layout() + self.CenterOnParent() + self.Show() + + def objects_by_id(self, evt): + input = self.id_get.GetValue() + if input.startswith("0x"): + input = int(input, 16) + + print("Finding {} ({})".format(str(input), hex(input))) + + for obj in gc.get_objects(): + if id(obj) == input: + print(obj) + print(bool(obj)) + print(str(len(gc.get_referents(obj))) + " references") + + break + else: + print(None) + + def gc_collect(self, evt): + print(gc.collect()) + print(gc.get_debug()) + print(gc.get_stats()) + + def fit_test(self, evt): + fits = eos.db.getFitList() + self.thread = FitTestThread([x.ID for x in fits], self.Parent) + self.thread.start() + + +class FitTestThread(threading.Thread): + def __init__(self, fitIDs, mainFrame): + threading.Thread.__init__(self) + self.name = "FitTestThread" + self.mainFrame = mainFrame + self.stopRunning = False + self.fits = fitIDs + + def stop(self): + self.stopRunning = True + + def run(self): + # wait 1 second just in case a lot of modifications get made + if self.stopRunning: + return + + for fit in self.fits: + time.sleep(1) + e = FitSelected(fitID=fit) + wx.PostEvent(self.mainFrame, e) diff --git a/gui/display.py b/gui/display.py index 2ae83aa44..8ee42692e 100644 --- a/gui/display.py +++ b/gui/display.py @@ -17,7 +17,6 @@ # along with pyfa. If not, see . # ============================================================================= -import sys # noinspection PyPackageRequirements import wx import gui.mainFrame @@ -38,9 +37,6 @@ class Display(wx.ListCtrl): self.Bind(wx.EVT_LIST_COL_END_DRAG, self.resizeChecker) self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self.resizeSkip) - if "wxMSW" in wx.PlatformInfo: - self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBk) - self.mainFrame = gui.mainFrame.MainFrame.getInstance() i = 0 @@ -55,7 +51,7 @@ class Display(wx.ListCtrl): name, type, defaultValue = param value = params[x] if len(params) > x else defaultValue value = value if value != "" else defaultValue - if type == bool and isinstance(value, basestring): + if type == bool and isinstance(value, str): value = bool(value) if value.lower() != "false" and value != "0" else False paramDict[name] = value col = colClass(self, paramDict) @@ -69,7 +65,7 @@ class Display(wx.ListCtrl): info = wx.ListItem() # noinspection PyPropertyAccess info.m_mask = wx.LIST_MASK_WIDTH - self.InsertColumnInfo(i, info) + self.InsertColumn(i, info) self.SetColumnWidth(i, 0) self.imageListBase = self.imageList.ImageCount @@ -113,50 +109,16 @@ class Display(wx.ListCtrl): return rowIndex, 0, -1 - def OnEraseBk(self, event): - if self.GetItemCount() > 0: - width, height = self.GetClientSize() - dc = event.GetDC() - - dc.DestroyClippingRegion() - dc.SetClippingRegion(0, 0, width, height) - x, y, w, h = dc.GetClippingBox() - - topItem = self.GetTopItem() - bottomItem = topItem + self.GetCountPerPage() - - if bottomItem >= self.GetItemCount(): - bottomItem = self.GetItemCount() - 1 - - topRect = self.GetItemRect(topItem, wx.LIST_RECT_LABEL) - bottomRect = self.GetItemRect(bottomItem, wx.LIST_RECT_BOUNDS) - - items_rect = wx.Rect(topRect.left, 0, bottomRect.right - topRect.left, bottomRect.bottom) - - updateRegion = wx.Region(x, y, w, h) - updateRegion.SubtractRect(items_rect) - - dc.DestroyClippingRegion() - dc.SetClippingRegionAsRegion(updateRegion) - - dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID)) - dc.Clear() - - dc.DestroyClippingRegion() - - else: - event.Skip() - # noinspection PyPropertyAccess def addColumn(self, i, col): self.activeColumns.append(col) info = wx.ListItem() - info.m_mask = col.mask | wx.LIST_MASK_FORMAT | wx.LIST_MASK_WIDTH - info.m_image = col.imageId - info.m_text = col.columnText - info.m_width = -1 - info.m_format = wx.LIST_FORMAT_LEFT - self.InsertColumnInfo(i, info) + info.SetMask(col.mask | wx.LIST_MASK_FORMAT | wx.LIST_MASK_WIDTH) + info.SetImage(col.imageId) + info.SetText(col.columnText) + info.SetWidth(-1) + info.SetAlign(wx.LIST_FORMAT_LEFT) + self.InsertColumn(i, info) col.resized = False if i == 0 and col.size != wx.LIST_AUTOSIZE_USEHEADER: col.size += 4 @@ -219,13 +181,13 @@ class Display(wx.ListCtrl): if listItemCount < stuffItemCount: for i in range(stuffItemCount - listItemCount): - self.InsertStringItem(sys.maxint, "") + self.InsertItem(self.GetItemCount(), "") if listItemCount > stuffItemCount: if listItemCount - stuffItemCount > 20 > stuffItemCount: self.DeleteAllItems() for i in range(stuffItemCount): - self.InsertStringItem(sys.maxint, "") + self.InsertItem(self.GetItemCount(), "") else: for i in range(listItemCount - stuffItemCount): self.DeleteItem(self.getLastItem()) @@ -247,7 +209,7 @@ class Display(wx.ListCtrl): newText = col.getText(st) if newText is False: col.delayedText(st, self, colItem) - newText = u"\u21bb" + newText = "\u21bb" newImageId = col.getImageId(st) diff --git a/gui/errorDialog.py b/gui/errorDialog.py index 378ddddd2..706e3c5a3 100644 --- a/gui/errorDialog.py +++ b/gui/errorDialog.py @@ -17,42 +17,57 @@ # along with pyfa. If not, see . # =============================================================================== -import platform +# import platform import sys - +# # noinspection PyPackageRequirements import wx +import traceback +import config +from logbook import Logger +from service.prereqsCheck import version_block -try: - import config -except: - config = None +pyfalog = Logger(__name__) -try: - import sqlalchemy - sqlalchemy_version = sqlalchemy.__version__ -except: - sqlalchemy_version = "Unknown" +class ErrorHandler(object): + __parent = None + __frame = None -try: - from logbook import __version__ as logbook_version -except: - logbook_version = "Unknown" + @classmethod + def HandleException(cls, exc_type, exc_value, exc_traceback): + with config.logging_setup.threadbound(): + # Print the base level traceback + t = traceback.format_exception(exc_type, exc_value, exc_traceback) + pyfalog.critical("\n\n" + "".join(t)) + + if cls.__parent is None: + app = wx.App(False) + cls.__frame = ErrorFrame(None) + cls.__frame.addException("".join(t)) + app.MainLoop() + sys.exit() + else: + if not cls.__frame: + cls.__frame = ErrorFrame(cls.__parent) + cls.__frame.Show() + cls.__frame.addException("".join(t)) + + @classmethod + def SetParent(cls, parent): + cls.__parent = parent class ErrorFrame(wx.Frame): - def __init__(self, exception=None, tb=None, error_title='Error!'): - v = sys.version_info - - wx.Frame.__init__(self, None, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600), + def __init__(self, parent=None, error_title='Error!'): + wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="pyfa error", pos=wx.DefaultPosition, size=wx.Size(500, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER | wx.STAY_ON_TOP) desc = "pyfa has experienced an unexpected issue. Below is a message that contains crucial\n" \ "information about how this was triggered. Please contact the developers with the\n" \ "information provided through the EVE Online forums or file a GitHub issue." - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) if 'wxMSW' in wx.PlatformInfo: self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) @@ -74,69 +89,32 @@ class ErrorFrame(wx.Frame): descText = wx.StaticText(self, wx.ID_ANY, desc) box.Add(descText, 1, wx.ALL, 5) - github = wx.HyperlinkCtrl(self, wx.ID_ANY, "Github", "https://github.com/pyfa-org/Pyfa/issues", - wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) - box.Add(github, 0, wx.ALL, 5) - - eveForums = wx.HyperlinkCtrl(self, wx.ID_ANY, "EVE Forums", "https://forums.eveonline.com/t/27156", - wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE) - box.Add(eveForums, 0, wx.ALL, 5) + # github = wx.lib.agw.hyperlink.HyperLinkCtrl(self, wx.ID_ANY, label="Github", URL="https://github.com/pyfa-org/Pyfa/issues") + # box.Add(github, 0, wx.ALL, 5) + # + # eveForums = wx.lib.agw.hyperlink.HyperLinkCtrl(self, wx.ID_ANY, label="EVE Forums", URL="https://forums.eveonline.com/t/27156") + # box.Add(eveForums, 0, wx.ALL, 5) # mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5) - errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, (-1, 400), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) - errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) - mainSizer.Add(errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5) - - try: - errorTextCtrl.AppendText("OS version: \t" + str(platform.platform())) - except: - errorTextCtrl.AppendText("OS version: Unknown") - errorTextCtrl.AppendText("\n") - - try: - errorTextCtrl.AppendText("Python: \t" + '{}.{}.{}'.format(v.major, v.minor, v.micro)) - except: - errorTextCtrl.AppendText("Python: Unknown") - errorTextCtrl.AppendText("\n") - - try: - errorTextCtrl.AppendText("wxPython: \t" + wx.VERSION_STRING) - except: - errorTextCtrl.AppendText("wxPython: Unknown") - errorTextCtrl.AppendText("\n") - - errorTextCtrl.AppendText("SQLAlchemy: \t" + str(sqlalchemy_version)) - errorTextCtrl.AppendText("\n") - - errorTextCtrl.AppendText("Logbook: \t" + str(logbook_version)) - errorTextCtrl.AppendText("\n") - - try: - errorTextCtrl.AppendText("pyfa version: {0} {1} - {2} {3}".format(config.version, config.tag, config.expansionName, config.expansionVersion)) - except: - errorTextCtrl.AppendText("pyfa version: Unknown") - errorTextCtrl.AppendText('\n') - - errorTextCtrl.AppendText("pyfa root: " + str(config.pyfaPath or "Unknown")) - errorTextCtrl.AppendText('\n') - errorTextCtrl.AppendText("save path: " + str(config.savePath or "Unknown")) - errorTextCtrl.AppendText('\n') - errorTextCtrl.AppendText("fs encoding: " + str(sys.getfilesystemencoding() or "Unknown")) - errorTextCtrl.AppendText('\n\n') - - errorTextCtrl.AppendText("EXCEPTION: " + str(exception or "Unknown")) - errorTextCtrl.AppendText('\n\n') - - if tb: - for line in tb: - errorTextCtrl.AppendText(line) - errorTextCtrl.Layout() + self.errorTextCtrl = wx.TextCtrl(self, wx.ID_ANY, version_block.strip(), wx.DefaultPosition, + (-1, 400), wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) + self.errorTextCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) + mainSizer.Add(self.errorTextCtrl, 0, wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, 5) + self.errorTextCtrl.AppendText("\n") + self.errorTextCtrl.Layout() self.SetSizer(mainSizer) mainSizer.Layout() self.Layout() self.Centre(wx.BOTH) + self.Bind(wx.EVT_CLOSE, self.OnClose) self.Show() + + def OnClose(self, evt): + self.Hide() + + def addException(self, text): + self.errorTextCtrl.AppendText("\n{}\n\n{}".format("#" * 20, text)) diff --git a/gui/crestFittings.py b/gui/esiFittings.py similarity index 58% rename from gui/crestFittings.py rename to gui/esiFittings.py index 30953b120..98944fde1 100644 --- a/gui/crestFittings.py +++ b/gui/esiFittings.py @@ -1,5 +1,3 @@ -import time -import webbrowser import json # noinspection PyPackageRequirements import wx @@ -15,13 +13,14 @@ from gui.display import Display import gui.globalEvents as GE from logbook import Logger +from service.esi import Esi +from service.esiAccess import APIException +from service.port import ESIExportException + pyfalog = Logger(__name__) -if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - from service.crest import Crest, CrestModes - -class CrestFittings(wx.Frame): +class EveFittings(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Browse EVE Fittings", pos=wx.DefaultPosition, size=wx.Size(550, 450), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL) @@ -30,22 +29,15 @@ class CrestFittings(wx.Frame): self.mainFrame = parent mainSizer = wx.BoxSizer(wx.VERTICAL) - sCrest = Crest.getInstance() + sEsi = Esi.getInstance() characterSelectSizer = wx.BoxSizer(wx.HORIZONTAL) - if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s" % sCrest.implicitCharacter.name, - wx.DefaultPosition, wx.DefaultSize) - self.stLogged.Wrap(-1) + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + characterSelectSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) + self.updateCharList() - characterSelectSizer.Add(self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) - else: - self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - characterSelectSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) - self.updateCharList() - - self.fetchBtn = wx.Button(self, wx.ID_ANY, u"Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5) + self.fetchBtn = wx.Button(self, wx.ID_ANY, "Fetch Fits", wx.DefaultPosition, wx.DefaultSize, 5) characterSelectSizer.Add(self.fetchBtn, 0, wx.ALL, 5) mainSizer.Add(characterSelectSizer, 0, wx.EXPAND, 5) @@ -64,8 +56,8 @@ class CrestFittings(wx.Frame): fitSizer.Add(self.fitView, 1, wx.ALL | wx.EXPAND, 5) btnSizer = wx.BoxSizer(wx.HORIZONTAL) - self.importBtn = wx.Button(self, wx.ID_ANY, u"Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5) - self.deleteBtn = wx.Button(self, wx.ID_ANY, u"Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5) + self.importBtn = wx.Button(self, wx.ID_ANY, "Import to pyfa", wx.DefaultPosition, wx.DefaultSize, 5) + self.deleteBtn = wx.Button(self, wx.ID_ANY, "Delete from EVE", wx.DefaultPosition, wx.DefaultSize, 5) btnSizer.Add(self.importBtn, 1, wx.ALL, 5) btnSizer.Add(self.deleteBtn, 1, wx.ALL, 5) fitSizer.Add(btnSizer, 0, wx.EXPAND) @@ -77,112 +69,109 @@ class CrestFittings(wx.Frame): self.importBtn.Bind(wx.EVT_BUTTON, self.importFitting) self.deleteBtn.Bind(wx.EVT_BUTTON, self.deleteFitting) - self.mainFrame.Bind(GE.EVT_SSO_LOGOUT, self.ssoLogout) - self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoLogin) self.Bind(wx.EVT_CLOSE, self.OnClose) self.statusbar = wx.StatusBar(self) self.statusbar.SetFieldsCount() self.SetStatusBar(self.statusbar) - self.cacheTimer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.updateCacheStatus, self.cacheTimer) - self.SetSizer(mainSizer) self.Layout() self.Centre(wx.BOTH) - def ssoLogin(self, event): - self.updateCharList() - event.Skip() - def updateCharList(self): - sCrest = Crest.getInstance() - chars = sCrest.getCrestCharacters() + sEsi = Esi.getInstance() + chars = sEsi.getSsoCharacters() if len(chars) == 0: self.Close() self.charChoice.Clear() for char in chars: - self.charChoice.Append(char.name, char.ID) + self.charChoice.Append(char.characterName, char.ID) self.charChoice.SetSelection(0) - def updateCacheStatus(self, event): - t = time.gmtime(self.cacheTime - time.time()) - if t < 0: - self.cacheTimer.Stop() - else: - sTime = time.strftime("%H:%M:%S", t) - self.statusbar.SetStatusText("Cached for %s" % sTime, 0) - - def ssoLogout(self, event): - if event.type == CrestModes.IMPLICIT: - self.Close() - else: - self.updateCharList() - event.Skip() # continue event - def OnClose(self, event): - self.mainFrame.Unbind(GE.EVT_SSO_LOGOUT, handler=self.ssoLogout) - self.mainFrame.Unbind(GE.EVT_SSO_LOGIN, handler=self.ssoLogin) + self.mainFrame.Unbind(GE.EVT_SSO_LOGOUT) + self.mainFrame.Unbind(GE.EVT_SSO_LOGIN) + # self.cacheTimer.Stop() # must be manually stopped, otherwise crash. See https://github.com/wxWidgets/Phoenix/issues/632 event.Skip() def getActiveCharacter(self): - sCrest = Crest.getInstance() - - if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - return sCrest.implicitCharacter.ID - selection = self.charChoice.GetCurrentSelection() return self.charChoice.GetClientData(selection) if selection is not None else None def fetchFittings(self, event): - sCrest = Crest.getInstance() + sEsi = Esi.getInstance() + waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self) + try: - waitDialog = wx.BusyInfo("Fetching fits, please wait...", parent=self) - fittings = sCrest.getFittings(self.getActiveCharacter()) - self.cacheTime = fittings.get('cached_until') - self.updateCacheStatus(None) - self.cacheTimer.Start(1000) - self.fitTree.populateSkillTree(fittings) + self.fittings = sEsi.getFittings(self.getActiveCharacter()) + # self.cacheTime = fittings.get('cached_until') + # self.updateCacheStatus(None) + # self.cacheTimer.Start(1000) + self.fitTree.populateSkillTree(self.fittings) del waitDialog except requests.exceptions.ConnectionError: msg = "Connection error, please check your internet connection" pyfalog.error(msg) self.statusbar.SetStatusText(msg) + except APIException as ex: + del waitDialog # Can't do this in a finally because then it obscures the message dialog + ESIExceptionHandler(self, ex) + except Exception as ex: + del waitDialog + raise ex def importFitting(self, event): selection = self.fitView.fitSelection if not selection: return - data = self.fitTree.fittingsTreeCtrl.GetPyData(selection) + data = self.fitTree.fittingsTreeCtrl.GetItemData(selection) sPort = Port.getInstance() fits = sPort.importFitFromBuffer(data) self.mainFrame._openAfterImport(fits) def deleteFitting(self, event): - sCrest = Crest.getInstance() + sEsi = Esi.getInstance() selection = self.fitView.fitSelection if not selection: return - data = json.loads(self.fitTree.fittingsTreeCtrl.GetPyData(selection)) + data = json.loads(self.fitTree.fittingsTreeCtrl.GetItemData(selection)) dlg = wx.MessageDialog(self, - "Do you really want to delete %s (%s) from EVE?" % (data['name'], data['ship']['name']), + "Do you really want to delete %s (%s) from EVE?" % (data['name'], getItem(data['ship_type_id']).name), "Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION) if dlg.ShowModal() == wx.ID_YES: try: - sCrest.delFitting(self.getActiveCharacter(), data['fittingID']) + sEsi.delFitting(self.getActiveCharacter(), data['fitting_id']) + # repopulate the fitting list + self.fitTree.populateSkillTree(self.fittings) + self.fitView.update([]) except requests.exceptions.ConnectionError: msg = "Connection error, please check your internet connection" pyfalog.error(msg) self.statusbar.SetStatusText(msg) +class ESIExceptionHandler(object): + # todo: make this a generate excetpion handler for all calls + def __init__(self, parentWindow, ex): + if ex.response['error'].startswith('Token is not valid') or ex.response['error'] == 'invalid_token': # todo: this seems messy, figure out a better response + dlg = wx.MessageDialog(parentWindow, + "There was an error validating characters' SSO token. Please try " + "logging into the character again to reset the token.", "Invalid Token", + wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + pyfalog.error(ex) + else: + # We don't know how to handle the error, raise it for the global error handler to pick it up + raise ex + + class ExportToEve(wx.Frame): def __init__(self, parent): wx.Frame.__init__(self, parent, id=wx.ID_ANY, title="Export fit to EVE", pos=wx.DefaultPosition, @@ -191,23 +180,16 @@ class ExportToEve(wx.Frame): self.mainFrame = parent self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) - sCrest = Crest.getInstance() + sEsi = Esi.getInstance() mainSizer = wx.BoxSizer(wx.VERTICAL) hSizer = wx.BoxSizer(wx.HORIZONTAL) - if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - self.stLogged = wx.StaticText(self, wx.ID_ANY, "Currently logged in as %s" % sCrest.implicitCharacter.name, - wx.DefaultPosition, wx.DefaultSize) - self.stLogged.Wrap(-1) + self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) + hSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) + self.updateCharList() + self.charChoice.SetSelection(0) - hSizer.Add(self.stLogged, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) - else: - self.charChoice = wx.Choice(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, []) - hSizer.Add(self.charChoice, 1, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5) - self.updateCharList() - self.charChoice.SetSelection(0) - - self.exportBtn = wx.Button(self, wx.ID_ANY, u"Export Fit", wx.DefaultPosition, wx.DefaultSize, 5) + self.exportBtn = wx.Button(self, wx.ID_ANY, "Export Fit", wx.DefaultPosition, wx.DefaultSize, 5) hSizer.Add(self.exportBtn, 0, wx.ALL, 5) mainSizer.Add(hSizer, 0, wx.EXPAND, 5) @@ -218,52 +200,34 @@ class ExportToEve(wx.Frame): self.statusbar.SetFieldsCount(2) self.statusbar.SetStatusWidths([100, -1]) - self.mainFrame.Bind(GE.EVT_SSO_LOGOUT, self.ssoLogout) - self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.ssoLogin) self.Bind(wx.EVT_CLOSE, self.OnClose) - self.SetSizer(hSizer) + self.SetSizer(mainSizer) self.SetStatusBar(self.statusbar) self.Layout() self.Centre(wx.BOTH) def updateCharList(self): - sCrest = Crest.getInstance() - chars = sCrest.getCrestCharacters() + sEsi = Esi.getInstance() + chars = sEsi.getSsoCharacters() if len(chars) == 0: self.Close() self.charChoice.Clear() for char in chars: - self.charChoice.Append(char.name, char.ID) + self.charChoice.Append(char.characterName, char.ID) self.charChoice.SetSelection(0) - def ssoLogin(self, event): - self.updateCharList() - event.Skip() - - def ssoLogout(self, event): - if event.type == CrestModes.IMPLICIT: - self.Close() - else: - self.updateCharList() - event.Skip() # continue event - def OnClose(self, event): - self.mainFrame.Unbind(GE.EVT_SSO_LOGOUT, handler=self.ssoLogout) - self.mainFrame.Unbind(GE.EVT_SSO_LOGIN, handler=self.ssoLogin) + self.mainFrame.Unbind(GE.EVT_SSO_LOGOUT) + self.mainFrame.Unbind(GE.EVT_SSO_LOGIN) event.Skip() def getActiveCharacter(self): - sCrest = Crest.getInstance() - - if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - return sCrest.implicitCharacter.ID - selection = self.charChoice.GetCurrentSelection() return self.charChoice.GetClientData(selection) if selection is not None else None @@ -278,29 +242,36 @@ class ExportToEve(wx.Frame): return self.statusbar.SetStatusText("Sending request and awaiting response", 1) - sCrest = Crest.getInstance() + sEsi = Esi.getInstance() try: sFit = Fit.getInstance() - data = sPort.exportCrest(sFit.getFit(fitID)) - res = sCrest.postFitting(self.getActiveCharacter(), data) + data = sPort.exportESI(sFit.getFit(fitID)) + res = sEsi.postFitting(self.getActiveCharacter(), data) - self.statusbar.SetStatusText("%d: %s" % (res.status_code, res.reason), 0) - try: - text = json.loads(res.text) - self.statusbar.SetStatusText(text['message'], 1) - except ValueError: - pyfalog.warning("Value error on loading JSON.") - self.statusbar.SetStatusText("", 1) + self.statusbar.SetStatusText("", 0) + self.statusbar.SetStatusText("", 1) + # try: + # text = json.loads(res.text) + # self.statusbar.SetStatusText(text['message'], 1) + # except ValueError: + # pyfalog.warning("Value error on loading JSON.") + # self.statusbar.SetStatusText("", 1) except requests.exceptions.ConnectionError: msg = "Connection error, please check your internet connection" pyfalog.error(msg) self.statusbar.SetStatusText(msg) + except ESIExportException as ex: + pyfalog.error(ex) + self.statusbar.SetStatusText("ERROR", 0) + self.statusbar.SetStatusText(ex.args[0], 1) + except APIException as ex: + ESIExceptionHandler(self, ex) -class CrestMgmt(wx.Dialog): +class SsoCharacterMgmt(wx.Dialog): def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="CREST Character Management", pos=wx.DefaultPosition, + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="SSO Character Management", pos=wx.DefaultPosition, size=wx.Size(550, 250), style=wx.DEFAULT_DIALOG_STYLE) self.mainFrame = parent mainSizer = wx.BoxSizer(wx.HORIZONTAL) @@ -308,7 +279,7 @@ class CrestMgmt(wx.Dialog): self.lcCharacters = wx.ListCtrl(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT) self.lcCharacters.InsertColumn(0, heading='Character') - self.lcCharacters.InsertColumn(1, heading='Refresh Token') + self.lcCharacters.InsertColumn(1, heading='Character ID') self.popCharList() @@ -316,10 +287,10 @@ class CrestMgmt(wx.Dialog): btnSizer = wx.BoxSizer(wx.VERTICAL) - self.addBtn = wx.Button(self, wx.ID_ANY, u"Add Character", wx.DefaultPosition, wx.DefaultSize, 0) + self.addBtn = wx.Button(self, wx.ID_ANY, "Add Character", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.addBtn, 0, wx.ALL | wx.EXPAND, 5) - self.deleteBtn = wx.Button(self, wx.ID_ANY, u"Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0) + self.deleteBtn = wx.Button(self, wx.ID_ANY, "Revoke Character", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.deleteBtn, 0, wx.ALL | wx.EXPAND, 5) mainSizer.Add(btnSizer, 0, wx.EXPAND, 5) @@ -335,18 +306,20 @@ class CrestMgmt(wx.Dialog): self.Centre(wx.BOTH) def ssoLogin(self, event): - self.popCharList() - event.Skip() + if (self): + #todo: these events don't unbind properly when window is closed (?), hence the `if`. Figure out better way of doing this. + self.popCharList() + event.Skip() def popCharList(self): - sCrest = Crest.getInstance() - chars = sCrest.getCrestCharacters() + sEsi = Esi.getInstance() + chars = sEsi.getSsoCharacters() self.lcCharacters.DeleteAllItems() for index, char in enumerate(chars): - self.lcCharacters.InsertStringItem(index, char.name) - self.lcCharacters.SetStringItem(index, 1, char.refresh_token) + self.lcCharacters.InsertItem(index, char.characterName) + self.lcCharacters.SetItem(index, 1, str(char.characterID)) self.lcCharacters.SetItemData(index, char.ID) self.lcCharacters.SetColumnWidth(0, wx.LIST_AUTOSIZE) @@ -354,16 +327,15 @@ class CrestMgmt(wx.Dialog): @staticmethod def addChar(event): - sCrest = Crest.getInstance() - uri = sCrest.startServer() - webbrowser.open(uri) + sEsi = Esi.getInstance() + sEsi.login() def delChar(self, event): item = self.lcCharacters.GetFirstSelected() if item > -1: charID = self.lcCharacters.GetItemData(item) - sCrest = Crest.getInstance() - sCrest.delCrestCharacter(charID) + sEsi = Esi.getInstance() + sEsi.delSsoCharacter(charID) self.popCharList() @@ -379,7 +351,7 @@ class FittingsTreeView(wx.Panel): self.root = tree.AddRoot("Fits") self.populateSkillTree(None) - self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.displayFit) + self.Bind(wx.EVT_TREE_SEL_CHANGED, self.displayFit) self.SetSizer(pmainSizer) @@ -392,24 +364,29 @@ class FittingsTreeView(wx.Panel): tree = self.fittingsTreeCtrl tree.DeleteChildren(root) - dict = {} - fits = data['items'] - for fit in fits: - if fit['ship']['name'] not in dict: - dict[fit['ship']['name']] = [] - dict[fit['ship']['name']].append(fit) + sEsi = Esi.getInstance() - for name, fits in dict.iteritems(): + dict = {} + fits = data + for fit in fits: + if (fit['fitting_id'] in sEsi.fittings_deleted): + continue + ship = getItem(fit['ship_type_id']) + if ship.name not in dict: + dict[ship.name] = [] + dict[ship.name].append(fit) + + for name, fits in dict.items(): shipID = tree.AppendItem(root, name) for fit in fits: fitId = tree.AppendItem(shipID, fit['name']) - tree.SetPyData(fitId, json.dumps(fit)) + tree.SetItemData(fitId, json.dumps(fit)) tree.SortChildren(root) def displayFit(self, event): selection = self.fittingsTreeCtrl.GetSelection() - data = self.fittingsTreeCtrl.GetPyData(selection) + data = self.fittingsTreeCtrl.GetItemData(selection) if data is None: event.Skip() @@ -420,7 +397,7 @@ class FittingsTreeView(wx.Panel): for item in fit['items']: try: - cargo = Cargo(getItem(item['type']['id'])) + cargo = Cargo(getItem(item['type_id'])) cargo.amount = item['quantity'] list.append(cargo) except Exception as e: diff --git a/gui/globalEvents.py b/gui/globalEvents.py index 1c1cc7e38..53784fcb6 100644 --- a/gui/globalEvents.py +++ b/gui/globalEvents.py @@ -5,5 +5,6 @@ FitChanged, FIT_CHANGED = wx.lib.newevent.NewEvent() CharListUpdated, CHAR_LIST_UPDATED = wx.lib.newevent.NewEvent() CharChanged, CHAR_CHANGED = wx.lib.newevent.NewEvent() +SsoLoggingIn, EVT_SSO_LOGGING_IN = wx.lib.newevent.NewEvent() SsoLogin, EVT_SSO_LOGIN = wx.lib.newevent.NewEvent() SsoLogout, EVT_SSO_LOGOUT = wx.lib.newevent.NewEvent() diff --git a/gui/graphFrame.py b/gui/graphFrame.py index 9c58a32b6..a36c9c66a 100644 --- a/gui/graphFrame.py +++ b/gui/graphFrame.py @@ -28,7 +28,7 @@ import gui.display import gui.mainFrame import gui.globalEvents as GE from gui.graph import Graph -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader import traceback pyfalog = Logger(__name__) @@ -93,15 +93,15 @@ class GraphFrame(wx.Frame): graphFrame_enabled = True if int(mpl.__version__[0]) < 1: - print("pyfa: Found matplotlib version ", mpl.__version__, " - activating OVER9000 workarounds") + print(("pyfa: Found matplotlib version ", mpl.__version__, " - activating OVER9000 workarounds")) print("pyfa: Recommended minimum matplotlib version is 1.0.0") self.legendFix = True mplImported = True - wx.Frame.__init__(self, parent, title=u"pyfa: Graph Generator", style=style, size=(520, 390)) + wx.Frame.__init__(self, parent, title="pyfa: Graph Generator", style=style, size=(520, 390)) - i = wx.IconFromBitmap(BitmapLoader.getBitmap("graphs_small", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("graphs_small", "gui")) self.SetIcon(i) self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.CreateStatusBar() @@ -143,7 +143,7 @@ class GraphFrame(wx.Frame): dummyBox = wx.BoxSizer(wx.VERTICAL) self.gridPanel.SetSizer(dummyBox) - self.gridSizer = wx.FlexGridSizer(0, 4) + self.gridSizer = wx.FlexGridSizer(0, 4, 0, 0) self.gridSizer.AddGrowableCol(1) dummyBox.Add(self.gridSizer, 0, wx.EXPAND) @@ -179,7 +179,7 @@ class GraphFrame(wx.Frame): def getValues(self): values = {} - for fieldName, field in self.fields.iteritems(): + for fieldName, field in self.fields.items(): values[fieldName] = field.GetValue() return values @@ -193,14 +193,14 @@ class GraphFrame(wx.Frame): self.fields.clear() # Setup textboxes - for field, defaultVal in view.getFields().iteritems(): + for field, defaultVal in view.getFields().items(): textBox = wx.TextCtrl(self.gridPanel, wx.ID_ANY, style=0) self.fields[field] = textBox textBox.Bind(wx.EVT_TEXT, self.onFieldChanged) sizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3) if defaultVal is not None: - if not isinstance(defaultVal, basestring): + if not isinstance(defaultVal, str): defaultVal = ("%f" % defaultVal).rstrip("0") if defaultVal[-1:] == ".": defaultVal += "0" @@ -229,6 +229,15 @@ class GraphFrame(wx.Frame): def draw(self, event=None): global mpl_version + if event is not None: + event.Skip() + + # todo: FIX THIS, see #1430. draw() is not being unbound properly when the window closes, this is an easy fix, + # but not a proper solution + if not self: + pyfalog.warning("GraphFrame handled event, however GraphFrame no longer exists. Ignoring event") + return + values = self.getValues() view = self.getView() self.subplot.clear() @@ -247,9 +256,9 @@ class GraphFrame(wx.Frame): self.subplot.plot(x, y) legend.append(fit.name) - except: - pyfalog.warning(u"Invalid values in '{0}'", fit.name) - self.SetStatusText(u"Invalid values in '%s'" % fit.name) + except Exception as ex: + pyfalog.warning("Invalid values in '{0}'", fit.name) + self.SetStatusText("Invalid values in '%s'" % fit.name) self.canvas.draw() return @@ -299,8 +308,6 @@ class GraphFrame(wx.Frame): self.canvas.draw() self.SetStatusText("") - if event is not None: - event.Skip() def onFieldChanged(self, event): self.draw() diff --git a/gui/itemStats.py b/gui/itemStats.py index dec1bc12c..3440fb322 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -23,7 +23,7 @@ import wx import config from service.market import Market import gui.mainFrame -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.builtinItemStatsViews.itemTraits import ItemTraits from gui.builtinItemStatsViews.itemDescription import ItemDescription @@ -84,7 +84,7 @@ class ItemStatsDialog(wx.Dialog): iconFile = "%s%s%s" % (before, sep, "0%s" % after if len(after) < 2 else after) itemImg = BitmapLoader.getBitmap(iconFile, "icons") if itemImg is not None: - self.SetIcon(wx.IconFromBitmap(itemImg)) + self.SetIcon(wx.Icon(itemImg)) self.SetTitle("%s: %s%s" % ("%s Stats" % itmContext if itmContext is not None else "Stats", item.name, " (%d)" % item.ID if config.debug else "")) @@ -99,7 +99,7 @@ class ItemStatsDialog(wx.Dialog): self.mainSizer.Add(self.container, 1, wx.EXPAND) if "wxGTK" in wx.PlatformInfo: - self.closeBtn = wx.Button(self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0) + self.closeBtn = wx.Button(self, wx.ID_ANY, "Close", wx.DefaultPosition, wx.DefaultSize, 0) self.mainSizer.Add(self.closeBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 5) self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent) diff --git a/gui/mainFrame.py b/gui/mainFrame.py index d4b23f3a4..4a8a6d1a1 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -25,8 +25,6 @@ import sqlalchemy # noinspection PyPackageRequirements import wx # noinspection PyPackageRequirements -from wx._core import PyDeadObjectError -# noinspection PyPackageRequirements from wx.lib.wordwrap import wordwrap # noinspection PyPackageRequirements from wx.lib.inspection import InspectionTool @@ -39,10 +37,10 @@ import config from eos.config import gamedata_version import gui.aboutData -from gui.chromeTabs import PFNotebook +from gui.chrome_tabs import ChromeNotebook import gui.globalEvents as GE -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.mainMenuBar import MainMenuBar from gui.additionsPane import AdditionsPane from gui.marketBrowser import MarketBrowser @@ -56,8 +54,10 @@ from gui.characterSelection import CharacterSelection from gui.patternEditor import DmgPatternEditorDlg from gui.resistsEditor import ResistsEditorDlg from gui.setEditor import ImplantSetEditorDlg +from gui.devTools import DevTools from gui.preferenceDialog import PreferenceDialog from gui.graphFrame import GraphFrame +from gui.ssoLogin import SsoLogin from gui.copySelectDialog import CopySelectDialog from gui.utils.clipboard import toClipboard, fromClipboard from gui.updateDialog import UpdateDialog @@ -69,6 +69,7 @@ from service.settings import SettingsProvider from service.fit import Fit from service.character import Character from service.update import Update +from service.esiAccess import SsoMode # import this to access override setting from eos.modifiedAttributeDict import ModifiedAttributeDict @@ -81,11 +82,10 @@ from time import gmtime, strftime import threading import webbrowser +import wx.adv -if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - from service.crest import Crest - from service.crest import CrestModes - from gui.crestFittings import CrestFittings, ExportToEve, CrestMgmt +from service.esi import Esi, LoginMethod +from gui.esiFittings import EveFittings, ExportToEve, SsoCharacterMgmt disableOverrideEditor = False @@ -93,7 +93,7 @@ try: from gui.propertyEditor import AttributeEditor except ImportError as e: AttributeEditor = None - print("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message) + print(("Error loading Attribute Editor: %s.\nAccess to Attribute Editor is disabled." % e.message)) disableOverrideEditor = True pyfalog = Logger(__name__) @@ -139,7 +139,8 @@ class OpenFitsThread(threading.Thread): wx.CallAfter(self.callback) -class MainFrame(wx.Frame, IPortUser): +# todo: include IPortUser again +class MainFrame(wx.Frame): __instance = None @classmethod @@ -163,7 +164,7 @@ class MainFrame(wx.Frame, IPortUser): self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) # Load and set the icon for pyfa main window - i = wx.IconFromBitmap(BitmapLoader.getBitmap("pyfa", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("pyfa", "gui")) self.SetIcon(i) # Create the layout and windows @@ -177,17 +178,17 @@ class MainFrame(wx.Frame, IPortUser): self.fitMultiSwitch = MultiSwitch(self.fitting_additions_split) self.additionsPane = AdditionsPane(self.fitting_additions_split) - self.notebookBrowsers = PFNotebook(self.browser_fitting_split, False) + self.notebookBrowsers = ChromeNotebook(self.browser_fitting_split, False) marketImg = BitmapLoader.getImage("market_small", "gui") shipBrowserImg = BitmapLoader.getImage("ship_small", "gui") self.marketBrowser = MarketBrowser(self.notebookBrowsers) - self.notebookBrowsers.AddPage(self.marketBrowser, "Market", tabImage=marketImg, showClose=False) + self.notebookBrowsers.AddPage(self.marketBrowser, "Market", image=marketImg, closeable=False) self.marketBrowser.splitter.SetSashPosition(self.marketHeight) self.shipBrowser = ShipBrowser(self.notebookBrowsers) - self.notebookBrowsers.AddPage(self.shipBrowser, "Fittings", tabImage=shipBrowserImg, showClose=False) + self.notebookBrowsers.AddPage(self.shipBrowser, "Fittings", image=shipBrowserImg, closeable=False) self.notebookBrowsers.SetSelection(1) @@ -205,6 +206,7 @@ class MainFrame(wx.Frame, IPortUser): self.charSelection = CharacterSelection(self) cstatsSizer.Add(self.charSelection, 0, wx.EXPAND) + # @todo pheonix: fix all stats stuff self.statsPane = StatsPane(self) cstatsSizer.Add(self.statsPane, 0, wx.EXPAND) @@ -236,15 +238,19 @@ class MainFrame(wx.Frame, IPortUser): self.sUpdate = Update.getInstance() self.sUpdate.CheckUpdate(self.ShowUpdateBox) - if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - self.Bind(GE.EVT_SSO_LOGIN, self.onSSOLogin) - self.Bind(GE.EVT_SSO_LOGOUT, self.onSSOLogout) + self.Bind(GE.EVT_SSO_LOGIN, self.onSSOLogin) + self.Bind(GE.EVT_SSO_LOGGING_IN, self.ShowSsoLogin) - self.titleTimer = wx.Timer(self) - self.Bind(wx.EVT_TIMER, self.updateTitle, self.titleTimer) + def ShowSsoLogin(self, event): + if getattr(event, "login_mode", LoginMethod.SERVER) == LoginMethod.MANUAL and getattr(event, "sso_mode", SsoMode.AUTO) == SsoMode.AUTO: + dlg = SsoLogin(self) + if dlg.ShowModal() == wx.ID_OK: + sEsi = Esi.getInstance() + # todo: verify that this is a correct SSO Info block + sEsi.handleLogin({'SSOInfo': [dlg.ssoInfoCtrl.Value.strip()]}) - def ShowUpdateBox(self, release): - dlg = UpdateDialog(self, release) + def ShowUpdateBox(self, release, version): + dlg = UpdateDialog(self, release, version) dlg.ShowModal() def LoadPreviousOpenFits(self): @@ -343,7 +349,7 @@ class MainFrame(wx.Frame, IPortUser): # save open fits self.prevOpenFits['pyfaOpenFits'] = [] # clear old list - for page in self.fitMultiSwitch.pages: + for page in self.fitMultiSwitch._pages: m = getattr(page, "getActiveFit", None) if m is not None: self.prevOpenFits['pyfaOpenFits'].append(m()) @@ -357,34 +363,37 @@ class MainFrame(wx.Frame, IPortUser): event.Skip() def ShowAboutBox(self, evt): - v = sys.version_info - info = wx.AboutDialogInfo() + info = wx.adv.AboutDialogInfo() info.Name = "pyfa" - info.Version = gui.aboutData.versionString + info.Version = config.getVersion() # gui.aboutData.versionString + # + # try: + # import matplotlib + # matplotlib_version = matplotlib.__version__ + # except: + # matplotlib_version = None + # + # info.Description = wordwrap(gui.aboutData.description + "\n\nDevelopers:\n\t" + + # "\n\t".join(gui.aboutData.developers) + + # "\n\nAdditional credits:\n\t" + + # "\n\t".join(gui.aboutData.credits) + + # "\n\nLicenses:\n\t" + + # "\n\t".join(gui.aboutData.licenses) + + # "\n\nEVE Data: \t" + gamedata_version + + # "\nPython: \t\t" + '{}.{}.{}'.format(v.major, v.minor, v.micro) + + # "\nwxPython: \t" + wx.__version__ + + # "\nSQLAlchemy: \t" + sqlalchemy.__version__ + + # "\nmatplotlib: \t {}".format(matplotlib_version if matplotlib_version else "Not Installed"), + # 500, wx.ClientDC(self)) + # if "__WXGTK__" in wx.PlatformInfo: + # forumUrl = "http://forums.eveonline.com/default.aspx?g=posts&t=466425" + # else: + # forumUrl = "http://forums.eveonline.com/default.aspx?g=posts&t=466425" + # info.WebSite = (forumUrl, "pyfa thread at EVE Online forum") + wx.adv.AboutBox(info) - try: - import matplotlib - matplotlib_version = matplotlib.__version__ - except: - matplotlib_version = None - - info.Description = wordwrap(gui.aboutData.description + "\n\nDevelopers:\n\t" + - "\n\t".join(gui.aboutData.developers) + - "\n\nAdditional credits:\n\t" + - "\n\t".join(gui.aboutData.credits) + - "\n\nLicenses:\n\t" + - "\n\t".join(gui.aboutData.licenses) + - "\n\nEVE Data: \t" + gamedata_version + - "\nPython: \t\t" + '{}.{}.{}'.format(v.major, v.minor, v.micro) + - "\nwxPython: \t" + wx.__version__ + - "\nSQLAlchemy: \t" + sqlalchemy.__version__ + - "\nmatplotlib: \t {}".format(matplotlib_version if matplotlib_version else "Not Installed"), - 500, wx.ClientDC(self)) - - forumUrl = "https://forums.eveonline.com/t/27156" - - info.WebSite = (forumUrl, "pyfa thread at EVE Online forum") - wx.AboutBox(info) + def showDevTools(self, event): + DevTools(self) def showCharacterEditor(self, event): dlg = CharacterEditor(self) @@ -402,7 +411,7 @@ class MainFrame(wx.Frame, IPortUser): dlg.ShowModal() try: dlg.Destroy() - except PyDeadObjectError: + except RuntimeError: pyfalog.error("Tried to destroy an object that doesn't exist in .") def showImplantSetEditor(self, event): @@ -412,7 +421,7 @@ class MainFrame(wx.Frame, IPortUser): """ Export active fit """ sFit = Fit.getInstance() fit = sFit.getFit(self.getActiveFit()) - defaultFile = u"%s - %s.xml" % (fit.ship.item.name, fit.name) if fit else None + defaultFile = "%s - %s.xml" % (fit.ship.item.name, fit.name) if fit else None dlg = wx.FileDialog(self, "Save Fitting As...", wildcard="EVE XML fitting files (*.xml)|*.xml", @@ -426,10 +435,10 @@ class MainFrame(wx.Frame, IPortUser): if '.' not in os.path.basename(path): path += ".xml" else: - print("oops, invalid fit format %d" % format_) + print(("oops, invalid fit format %d" % format_)) try: dlg.Destroy() - except PyDeadObjectError: + except RuntimeError: pyfalog.error("Tried to destroy an object that doesn't exist in .") return @@ -439,7 +448,7 @@ class MainFrame(wx.Frame, IPortUser): try: dlg.Destroy() - except PyDeadObjectError: + except RuntimeError: pyfalog.error("Tried to destroy an object that doesn't exist in .") def showPreferenceDialog(self, event): @@ -472,6 +481,7 @@ class MainFrame(wx.Frame, IPortUser): # Widgets Inspector if config.debug: self.Bind(wx.EVT_MENU, self.openWXInspectTool, id=self.widgetInspectMenuID) + self.Bind(wx.EVT_MENU, self.showDevTools, id=menuBar.devToolsId) # About self.Bind(wx.EVT_MENU, self.ShowAboutBox, id=wx.ID_ABOUT) # Char editor @@ -606,68 +616,26 @@ class MainFrame(wx.Frame, IPortUser): wx.PostEvent(self, GE.FitChanged(fitID=fitID)) def eveFittings(self, event): - dlg = CrestFittings(self) + dlg = EveFittings(self) dlg.Show() - def updateTitle(self, event): - sCrest = Crest.getInstance() - char = sCrest.implicitCharacter - if char: - t = time.gmtime(char.eve.expires - time.time()) - sTime = time.strftime("%H:%M:%S", t if t >= 0 else 0) - newTitle = "%s | %s - %s" % (self.title, char.name, sTime) - self.SetTitle(newTitle) - def onSSOLogin(self, event): menu = self.GetMenuBar() menu.Enable(menu.eveFittingsId, True) menu.Enable(menu.exportToEveId, True) - if event.type == CrestModes.IMPLICIT: - menu.SetLabel(menu.ssoLoginId, "Logout Character") - self.titleTimer.Start(1000) - - def onSSOLogout(self, event): - self.titleTimer.Stop() - self.SetTitle(self.title) - + def updateEsiMenus(self, type): menu = self.GetMenuBar() - if event.type == CrestModes.IMPLICIT or event.numChars == 0: - menu.Enable(menu.eveFittingsId, False) - menu.Enable(menu.exportToEveId, False) + sEsi = Esi.getInstance() - if event.type == CrestModes.IMPLICIT: - menu.SetLabel(menu.ssoLoginId, "Login to EVE") - - def updateCrestMenus(self, type): - # in case we are logged in when switching, change title back - self.titleTimer.Stop() - self.SetTitle(self.title) - - menu = self.GetMenuBar() - sCrest = Crest.getInstance() - - if type == CrestModes.IMPLICIT: - menu.SetLabel(menu.ssoLoginId, "Login to EVE") - menu.Enable(menu.eveFittingsId, False) - menu.Enable(menu.exportToEveId, False) - else: - menu.SetLabel(menu.ssoLoginId, "Manage Characters") - enable = len(sCrest.getCrestCharacters()) == 0 - menu.Enable(menu.eveFittingsId, not enable) - menu.Enable(menu.exportToEveId, not enable) + menu.SetLabel(menu.ssoLoginId, "Manage Characters") + enable = len(sEsi.getSsoCharacters()) == 0 + menu.Enable(menu.eveFittingsId, not enable) + menu.Enable(menu.exportToEveId, not enable) def ssoHandler(self, event): - sCrest = Crest.getInstance() - if sCrest.settings.get('mode') == CrestModes.IMPLICIT: - if sCrest.implicitCharacter is not None: - sCrest.logout() - else: - uri = sCrest.startServer() - webbrowser.open(uri) - else: - dlg = CrestMgmt(self) - dlg.Show() + dlg = SsoCharacterMgmt(self) + dlg.Show() def exportToEve(self, event): dlg = ExportToEve(self) @@ -742,9 +710,9 @@ class MainFrame(wx.Frame, IPortUser): fit = db_getFit(self.getActiveFit()) toClipboard(Port.exportDna(fit)) - def clipboardCrest(self): + def clipboardEsi(self): fit = db_getFit(self.getActiveFit()) - toClipboard(Port.exportCrest(fit)) + toClipboard(Port.exportESI(fit)) def clipboardXml(self): fit = db_getFit(self.getActiveFit()) @@ -768,7 +736,7 @@ class MainFrame(wx.Frame, IPortUser): CopySelectDialog.copyFormatEftImps: self.clipboardEftImps, CopySelectDialog.copyFormatXml: self.clipboardXml, CopySelectDialog.copyFormatDna: self.clipboardDna, - CopySelectDialog.copyFormatCrest: self.clipboardCrest, + CopySelectDialog.copyFormatEsi: self.clipboardEsi, CopySelectDialog.copyFormatMultiBuy: self.clipboardMultiBuy} dlg = CopySelectDialog(self) dlg.ShowModal() @@ -778,7 +746,7 @@ class MainFrame(wx.Frame, IPortUser): try: dlg.Destroy() - except PyDeadObjectError: + except RuntimeError: pyfalog.error("Tried to destroy an object that doesn't exist in .") def exportSkillsNeeded(self, event): @@ -834,7 +802,7 @@ class MainFrame(wx.Frame, IPortUser): self.progressDialog.ShowModal() try: dlg.Destroy() - except PyDeadObjectError: + except RuntimeError: pyfalog.error("Tried to destroy an object that doesn't exist in .") def backupToXml(self, event): @@ -1025,7 +993,7 @@ class MainFrame(wx.Frame, IPortUser): # Find a widget to be selected in the tree. Use either the # one under the cursor, if any, or this frame. - wnd = wx.FindWindowAtPointer() + wnd, _ = wx.FindWindowAtPointer() if not wnd: wnd = self InspectionTool().Show(wnd, True) diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py index bade53d3d..9c421f779 100644 --- a/gui/mainMenuBar.py +++ b/gui/mainMenuBar.py @@ -25,14 +25,13 @@ from service.character import Character from service.fit import Fit import gui.graphFrame import gui.globalEvents as GE -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from logbook import Logger -pyfalog = Logger(__name__) +# from service.crest import Crest +# from service.crest import CrestModes -if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - from service.crest import Crest - from service.crest import CrestModes +pyfalog = Logger(__name__) class MainMenuBar(wx.MenuBar): @@ -59,7 +58,9 @@ class MainMenuBar(wx.MenuBar): self.toggleOverridesId = wx.NewId() self.importDatabaseDefaultsId = wx.NewId() self.toggleIgnoreRestrictionID = wx.NewId() + self.devToolsId = wx.NewId() + # pheonix: evaluate if this is needed if 'wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0): wx.ID_COPY = wx.NewId() wx.ID_PASTE = wx.NewId() @@ -107,23 +108,23 @@ class MainMenuBar(wx.MenuBar): charEditItem = wx.MenuItem(windowMenu, self.characterEditorId, "&Character Editor\tCTRL+E") charEditItem.SetBitmap(BitmapLoader.getBitmap("character_small", "gui")) - windowMenu.AppendItem(charEditItem) + windowMenu.Append(charEditItem) damagePatternEditItem = wx.MenuItem(windowMenu, self.damagePatternEditorId, "Damage Pattern Editor\tCTRL+D") damagePatternEditItem.SetBitmap(BitmapLoader.getBitmap("damagePattern_small", "gui")) - windowMenu.AppendItem(damagePatternEditItem) + windowMenu.Append(damagePatternEditItem) targetResistsEditItem = wx.MenuItem(windowMenu, self.targetResistsEditorId, "Target Resists Editor\tCTRL+R") targetResistsEditItem.SetBitmap(BitmapLoader.getBitmap("explosive_small", "gui")) - windowMenu.AppendItem(targetResistsEditItem) + windowMenu.Append(targetResistsEditItem) implantSetEditItem = wx.MenuItem(windowMenu, self.implantSetEditorId, "Implant Set Editor\tCTRL+I") implantSetEditItem.SetBitmap(BitmapLoader.getBitmap("hardwire_small", "gui")) - windowMenu.AppendItem(implantSetEditItem) + windowMenu.Append(implantSetEditItem) graphFrameItem = wx.MenuItem(windowMenu, self.graphFrameId, "Graphs\tCTRL+G") graphFrameItem.SetBitmap(BitmapLoader.getBitmap("graphs_small", "gui")) - windowMenu.AppendItem(graphFrameItem) + windowMenu.Append(graphFrameItem) if not gui.graphFrame.graphFrame_enabled: self.Enable(self.graphFrameId, False) @@ -131,31 +132,28 @@ class MainMenuBar(wx.MenuBar): preferencesShortCut = "CTRL+," if 'wxMac' in wx.PlatformInfo else "CTRL+P" preferencesItem = wx.MenuItem(windowMenu, wx.ID_PREFERENCES, "Preferences\t" + preferencesShortCut) preferencesItem.SetBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) - windowMenu.AppendItem(preferencesItem) + windowMenu.Append(preferencesItem) - if 'wxMac' not in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3, 0)): - self.sCrest = Crest.getInstance() + # self.sEsi = Crest.getInstance() - # CREST Menu - crestMenu = wx.Menu() - self.Append(crestMenu, "&CREST") - if self.sCrest.settings.get('mode') != CrestModes.IMPLICIT: - crestMenu.Append(self.ssoLoginId, "Manage Characters") - else: - crestMenu.Append(self.ssoLoginId, "Login to EVE") - crestMenu.Append(self.eveFittingsId, "Browse EVE Fittings") - crestMenu.Append(self.exportToEveId, "Export To EVE") + # CREST Menu + esiMMenu = wx.Menu() + self.Append(esiMMenu, "EVE &SSO") - if self.sCrest.settings.get('mode') == CrestModes.IMPLICIT or len(self.sCrest.getCrestCharacters()) == 0: - self.Enable(self.eveFittingsId, False) - self.Enable(self.exportToEveId, False) + esiMMenu.Append(self.ssoLoginId, "Manage Characters") + esiMMenu.Append(self.eveFittingsId, "Browse EVE Fittings") + esiMMenu.Append(self.exportToEveId, "Export To EVE") - if not self.mainFrame.disableOverrideEditor: - windowMenu.AppendSeparator() - attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+B") - attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) - windowMenu.AppendItem(attrItem) - windowMenu.Append(self.toggleOverridesId, "Turn Overrides On") + # if self.sEsi.settings.get('mode') == CrestModes.IMPLICIT or len(self.sEsi.getCrestCharacters()) == 0: + self.Enable(self.eveFittingsId, True) + self.Enable(self.exportToEveId, True) + + if not self.mainFrame.disableOverrideEditor: + windowMenu.AppendSeparator() + attrItem = wx.MenuItem(windowMenu, self.attrEditorId, "Attribute Overrides\tCTRL+B") + attrItem.SetBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) + windowMenu.Append(attrItem) + windowMenu.Append(self.toggleOverridesId, "Turn Overrides On") # Help menu helpMenu = wx.Menu() @@ -170,6 +168,8 @@ class MainMenuBar(wx.MenuBar): if config.debug: helpMenu.Append(self.mainFrame.widgetInspectMenuID, "Open Widgets Inspect tool", "Open Widgets Inspect tool") + helpMenu.Append(self.devToolsId, "Open Dev Tools", + "Dev Tools") self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) diff --git a/gui/marketBrowser.py b/gui/marketBrowser.py index 480836c04..d39477523 100644 --- a/gui/marketBrowser.py +++ b/gui/marketBrowser.py @@ -64,7 +64,7 @@ class MarketBrowser(wx.Panel): vbox.Add(p, 0, wx.EXPAND) self.metaButtons = [] btn = None - for name in self.sMkt.META_MAP.keys(): + for name in list(self.sMkt.META_MAP.keys()): btn = MetaButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT) setattr(self, name, btn) box.Add(btn, 1, wx.ALIGN_CENTER) @@ -78,10 +78,10 @@ class MarketBrowser(wx.Panel): def toggleMetaButton(self, event): """Process clicks on toggle buttons""" - appendMeta = wx.GetMouseState().CmdDown() + mstate = wx.GetMouseState() clickedBtn = event.EventObject - if appendMeta: + if mstate.cmdDown: activeBtns = [btn for btn in self.metaButtons if btn.GetValue()] if activeBtns: clickedBtn.setUserSelection(clickedBtn.GetValue()) diff --git a/gui/multiSwitch.py b/gui/multiSwitch.py index 2efe80bc0..b199848eb 100644 --- a/gui/multiSwitch.py +++ b/gui/multiSwitch.py @@ -17,13 +17,13 @@ # along with pyfa. If not, see . # ============================================================================= -from gui.chromeTabs import PFNotebook +from gui.chrome_tabs import ChromeNotebook import gui.builtinViews.emptyView -class MultiSwitch(PFNotebook): +class MultiSwitch(ChromeNotebook): def __init__(self, parent): - PFNotebook.__init__(self, parent) + ChromeNotebook.__init__(self, parent) # self.AddPage() # now handled by mainFrame self.handlers = handlers = [] for type in TabSpawner.tabTypes: @@ -40,10 +40,10 @@ class MultiSwitch(PFNotebook): tabWnd = gui.builtinViews.emptyView.BlankPage(self) tabWnd.handleDrag = lambda type, info: self.handleDrag(type, info) - PFNotebook.AddPage(self, tabWnd, tabTitle, tabImage, True) + ChromeNotebook.AddPage(self, tabWnd, tabTitle, tabImage, True) def DeletePage(self, n, *args, **kwargs): - PFNotebook.DeletePage(self, n, *args, **kwargs) + ChromeNotebook.DeletePage(self, n, *args, **kwargs) if self.GetPageCount() == 0: self.AddPage() diff --git a/gui/patternEditor.py b/gui/patternEditor.py index 879683373..3bed203a2 100644 --- a/gui/patternEditor.py +++ b/gui/patternEditor.py @@ -19,7 +19,7 @@ # noinspection PyPackageRequirements import wx -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader # noinspection PyPackageRequirements from wx.lib.intctrl import IntCtrl from gui.utils.clipboard import toClipboard, fromClipboard @@ -51,7 +51,7 @@ class DmgPatternTextValidor(BaseValidator): return True except ValueError as e: pyfalog.error(e) - wx.MessageBox(u"{}".format(e), "Error") + wx.MessageBox("{}".format(e), "Error") textCtrl.SetFocus() return False @@ -89,10 +89,10 @@ class DmgPatternEditorDlg(wx.Dialog): DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive") def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Damage Pattern Editor", size=wx.Size(400, 240)) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Damage Pattern Editor", size=wx.Size(400, 240)) self.block = False - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -128,7 +128,7 @@ class DmgPatternEditorDlg(wx.Dialog): # set text edit setattr(self, "%sEdit" % type_, IntCtrl(self, wx.ID_ANY, 0, wx.DefaultPosition, defSize)) - setattr(self, "%sPerc" % type_, wx.StaticText(self, wx.ID_ANY, u"0%")) + setattr(self, "%sPerc" % type_, wx.StaticText(self, wx.ID_ANY, "0%")) editObj = getattr(self, "%sEdit" % type_) dmgeditSizer.Add(bmp, 0, style, border) @@ -147,7 +147,7 @@ class DmgPatternEditorDlg(wx.Dialog): footerSizer = wx.BoxSizer(wx.HORIZONTAL) perSizer = wx.BoxSizer(wx.VERTICAL) - self.stNotice = wx.StaticText(self, wx.ID_ANY, u"") + self.stNotice = wx.StaticText(self, wx.ID_ANY, "") self.stNotice.Wrap(-1) perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5) @@ -160,7 +160,7 @@ class DmgPatternEditorDlg(wx.Dialog): mainSizer.Add(contentSizer, 1, wx.EXPAND, 0) if "wxGTK" in wx.PlatformInfo: - self.closeBtn = wx.Button(self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0) + self.closeBtn = wx.Button(self, wx.ID_ANY, "Close", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.closeBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 5) self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent) @@ -179,7 +179,7 @@ class DmgPatternEditorDlg(wx.Dialog): btn.Layout() setattr(self, name, btn) btn.Enable(True) - btn.SetToolTipString("%s patterns %s clipboard" % (name, direction)) + btn.SetToolTip("%s patterns %s clipboard" % (name, direction)) footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower()))) @@ -200,7 +200,7 @@ class DmgPatternEditorDlg(wx.Dialog): return p = self.entityEditor.getActiveEntity() - total = sum(map(lambda attr: getattr(self, "%sEdit" % attr).GetValue(), self.DAMAGE_TYPES)) + total = sum([getattr(self, "%sEdit" % attr).GetValue() for attr in self.DAMAGE_TYPES]) for type_ in self.DAMAGE_TYPES: editObj = getattr(self, "%sEdit" % type_) percObj = getattr(self, "%sPerc" % type_) diff --git a/gui/preferenceDialog.py b/gui/preferenceDialog.py index 2ed0f829a..37ca5ddaf 100644 --- a/gui/preferenceDialog.py +++ b/gui/preferenceDialog.py @@ -20,14 +20,14 @@ # noinspection PyPackageRequirements import wx from gui.preferenceView import PreferenceView -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader class PreferenceDialog(wx.Dialog): def __init__(self, parent): wx.Dialog.__init__(self, parent, id=wx.ID_ANY, size=wx.DefaultSize, style=wx.DEFAULT_DIALOG_STYLE) self.SetTitle("pyfa - Preferences") - i = wx.IconFromBitmap(BitmapLoader.getBitmap("preferences_small", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("preferences_small", "gui")) self.SetIcon(i) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -38,7 +38,7 @@ class PreferenceDialog(wx.Dialog): # self.listview.SetSize((500, -1)) self.imageList = wx.ImageList(32, 32) - self.listbook.SetImageList(self.imageList) + self.listbook.AssignImageList(self.imageList) mainSizer.Add(self.listbook, 1, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.LEFT, 5) @@ -46,8 +46,8 @@ class PreferenceDialog(wx.Dialog): mainSizer.Add(self.m_staticline2, 0, wx.EXPAND, 5) btnSizer = wx.BoxSizer(wx.HORIZONTAL) - btnSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5) - self.btnOK = wx.Button(self, wx.ID_ANY, u"OK", wx.DefaultPosition, wx.DefaultSize, 0) + btnSizer.AddStretchSpacer() + self.btnOK = wx.Button(self, wx.ID_ANY, "OK", wx.DefaultPosition, wx.DefaultSize, 0) btnSizer.Add(self.btnOK, 0, wx.ALL, 5) mainSizer.Add(btnSizer, 0, wx.EXPAND, 5) self.SetSizer(mainSizer) @@ -55,23 +55,23 @@ class PreferenceDialog(wx.Dialog): self.Centre(wx.BOTH) for prefView in PreferenceView.views: - page = wx.Panel(self.listbook) + page = wx.ScrolledWindow(self.listbook) + page.SetScrollbars(1, 1, 20, 20) bmp = prefView.getImage() if bmp: imgID = self.imageList.Add(bmp) else: imgID = -1 prefView.populatePanel(page) + self.listbook.AddPage(page, prefView.title, imageId=imgID) - # Set the height based on a condition. Can all the panels fit in the current height? - # If not, use the .GetBestVirtualSize() to ensure that all content is available. minHeight = 550 bestFit = self.GetBestVirtualSize() if minHeight > bestFit[1]: - self.SetSizeWH(650, minHeight) + self.SetSize(650, minHeight) else: - self.SetSizeWH(650, bestFit[1]) + self.SetSize(650, bestFit[1]) self.Layout() diff --git a/gui/preferenceView.py b/gui/preferenceView.py index a52a720e4..9ce2e676d 100644 --- a/gui/preferenceView.py +++ b/gui/preferenceView.py @@ -43,7 +43,7 @@ from gui.builtinPreferenceViews import ( # noqa: E402, F401 pyfaGeneralPreferences, pyfaNetworkPreferences, pyfaHTMLExportPreferences, - pyfaCrestPreferences, + pyfaEsiPreferences, pyfaContextMenuPreferences, pyfaStatViewPreferences, pyfaUpdatePreferences, diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index b6f4ab1a1..68cc93ee8 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -19,7 +19,7 @@ import gui.display as d import gui.globalEvents as GE import gui.builtinMarketBrowser.pfSearchBox as SBox from gui.marketBrowser import SearchBox -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader pyfalog = Logger(__name__) @@ -30,7 +30,7 @@ class AttributeEditor(wx.Frame): size=wx.Size(650, 600), style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.TAB_TRAVERSAL) - i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("fit_rename_small", "gui")) self.SetIcon(i) self.mainFrame = parent @@ -48,7 +48,7 @@ class AttributeEditor(wx.Frame): self.Bind(wx.EVT_MENU, self.OnExport, fileExport) self.Bind(wx.EVT_MENU, self.OnClear, fileClear) - i = wx.IconFromBitmap(BitmapLoader.getBitmap("fit_rename_small", "gui")) + i = wx.Icon(BitmapLoader.getBitmap("fit_rename_small", "gui")) self.SetIcon(i) self.mainFrame = parent @@ -70,7 +70,7 @@ class AttributeEditor(wx.Frame): mainSizer.Add(leftPanel, 1, wx.ALL | wx.EXPAND, 5) rightSizer = wx.BoxSizer(wx.VERTICAL) - self.btnRemoveOverrides = wx.Button(panel, wx.ID_ANY, u"Remove Overides for Item", wx.DefaultPosition, + self.btnRemoveOverrides = wx.Button(panel, wx.ID_ANY, "Remove Overides for Item", wx.DefaultPosition, wx.DefaultSize, 0) self.pg = AttributeGrid(panel) rightSizer.Add(self.pg, 1, wx.ALL | wx.EXPAND, 5) @@ -102,9 +102,11 @@ class AttributeEditor(wx.Frame): style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() - with open(path, 'rb') as csvfile: + with open(path, 'r') as csvfile: spamreader = csv.reader(csvfile) for row in spamreader: + if len(row) == 0: # csvwriter seems to added blank lines to the end sometimes + continue itemID, attrID, value = row item = getItem(int(itemID)) attr = getAttributeInfo(int(attrID)) @@ -123,10 +125,10 @@ class AttributeEditor(wx.Frame): if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() - with open(path, 'wb') as csvfile: + with open(path, 'w', encoding='utf-8') as csvfile: writer = csv.writer(csvfile) for item in items: - for key, override in item.overrides.iteritems(): + for key, override in item.overrides.items(): writer.writerow([item.ID, override.attrID, override.value]) def OnClear(self, event): @@ -145,7 +147,7 @@ class AttributeEditor(wx.Frame): # them due to the eve/user database disconnect. We must loop through # all items that have overrides and remove them for item in items: - for _, x in item.overrides.items(): + for _, x in list(item.overrides.items()): item.deleteOverride(x.attr) self.itemView.updateItems(True) self.pg.Clear() @@ -247,7 +249,7 @@ class AttributeGrid(wxpg.PropertyGrid): if self.item is None: return - for x in self.item.overrides.values(): + for x in list(self.item.overrides.values()): self.item.deleteOverride(x.attr) self.itemView.updateItems(True) self.ClearModifiedStatus() diff --git a/gui/pyfa_gauge.py b/gui/pyfa_gauge.py new file mode 100644 index 000000000..0144f651c --- /dev/null +++ b/gui/pyfa_gauge.py @@ -0,0 +1,449 @@ +# =============================================================================== +# PyfaGauge is a generic Gauge implementation tailored for pyfa (the Python +# Fitting Assistant). It uses the easeOutQuad equation from +# caurina.transitions.Tweener to do animations +# +# ToDo: make SetGradient(from and not dependant on value) +# ToDo: fix 0 range (currently resets range to 0.01, but this causes problems if +# we really set range at 0.01). Perhaps make it -1 and test percentage as +# a negativeor something. +# ToDo: possibly devise a way to determine transition percents on init +# (currently hardcoded) +# +# =============================================================================== + +import copy +import wx + +from gui.utils import color as color_utils +from gui.utils import draw, anim_effects +from service.fit import Fit + + +class PyGauge(wx.Window): + def __init__(self, parent, font, max_range=100, size=(-1, 30), *args, + **kargs): + + super().__init__(parent, size=size, *args, **kargs) + + self._size = size + + self._border_colour = wx.BLACK + self._bar_colour = None + self._bar_gradient = None + + self._border_padding = 0 + self._max_range = max_range + self._value = 0 + + self._fraction_digits = 0 + + self._timer_id = wx.NewId() + self._timer = None + + self._oldValue = 0 + + self._anim_duration = 500 + self._anim_step = 0 + self._period = 20 + self._anim_value = 0 + self._anim_direction = 0 + self.anim_effect = anim_effects.OUT_QUAD + + # transition colors used based on how full (or overfilled) the gauge is. + self.transition_colors = [ + (wx.Colour(191, 191, 191), wx.Colour(96, 191, 0)), # < 0-100% + (wx.Colour(191, 167, 96), wx.Colour(255, 191, 0)), # < 100-101% + (wx.Colour(255, 191, 0), wx.Colour(255, 128, 0)), # < 101-103% + (wx.Colour(255, 128, 0), wx.Colour(255, 0, 0)) # < 103-105% + ] + + self.gradient_effect = -35 + + self._percentage = 0 + self._old_percentage = 0 + self._show_remaining = False + + self.font = font + + self.SetBackgroundColour(wx.Colour(51, 51, 51)) + + self._tooltip = wx.ToolTip("0.00/100.00") + self.SetToolTip(self._tooltip) + + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_TIMER, self.OnTimer) + self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter) + self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + + def OnEraseBackground(self, event): + pass + + def OnWindowEnter(self, event): + self._show_remaining = True + self.Refresh() + + def OnWindowLeave(self, event): + self._show_remaining = False + self.Refresh() + + def GetBorderColour(self): + return self._border_colour + + def SetBorderColour(self, colour): + self._border_colour = colour + + def GetBarColour(self): + return self._bar_colour + + def SetBarColour(self, colour): + self._bar_colour = colour + + def SetFractionDigits(self, digits): + self._fraction_digits = digits + + def GetBarGradient(self): + if self._bar_gradient is None: + return None + + return self._bar_gradient[0] + + def SetBarGradient(self, gradient=None): + if gradient is None: + self._bar_gradient = None + else: + if not isinstance(gradient, list): + self._bar_gradient = [gradient] + else: + self._bar_gradient = list(gradient) + + def GetBorderPadding(self): + return self._border_padding + + def SetBorderPadding(self, padding): + self._border_padding = padding + + def GetRange(self): + """ Returns the maximum value of the gauge. """ + return self._max_range + + def Animate(self): + sFit = Fit.getInstance() + if sFit.serviceFittingOptions["enableGaugeAnimation"]: + if not self._timer: + self._timer = wx.Timer(self, self._timer_id) + + self._anim_step = 0 + self._timer.Start(self._period) + else: + self._anim_value = self._percentage + self.Refresh() + + def SetRange(self, range, reinit=False, animate=True): + """ + Sets the range of the gauge. The gauge length is its + value as a proportion of the range. + """ + + if self._max_range == range: + return + + # we cannot have a range of zero (laws of physics, etc), so we set it + if range <= 0: + self._max_range = 0.01 + else: + self._max_range = range + + if reinit is False: + self._old_percentage = self._percentage + self._percentage = (self._value / self._max_range) * 100 + else: + self._old_percentage = self._percentage + self._percentage = 0 + self._value = 0 + + if animate: + self.Animate() + + self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range if self._max_range > 0.01 else 0)) + + def GetValue(self): + return self._value + + def SetValue(self, value, animate=True): + """ Sets the current position of the gauge. """ + if self._value == value: + return + + self._old_percentage = self._percentage + self._value = value + + if value < 0: + self._value = 0 + + self._percentage = (self._value / self._max_range) * 100 + + if animate: + self.Animate() + + self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._max_range)) + + def SetValueRange(self, value, range, reinit=False): + """ Set both value and range of the gauge. """ + range_ = float(range) + + if range_ <= 0: + self._max_range = 0.01 + else: + self._max_range = range_ + + value = float(value) + + self._value = value + if value < 0: + self._value = float(0) + + if reinit is False: + self._old_percentage = self._percentage + self._percentage = (self._value / self._max_range) * 100 + + else: + self._old_percentage = self._percentage + self._percentage = 0 + + self.Animate() + self._tooltip.SetTip("%.2f/%.2f" % + (self._value, self._max_range if float(self._max_range) > 0.01 else 0)) + + def OnPaint(self, event): + dc = wx.AutoBufferedPaintDC(self) + rect = self.GetClientRect() + + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + + colour = self.GetBackgroundColour() + + dc.SetBrush(wx.Brush(colour)) + dc.SetPen(wx.Pen(colour)) + + dc.DrawRectangle(rect) + + value = self._percentage + + if self._timer: + if self._timer.IsRunning(): + value = self._anim_value + + if self._border_colour: + dc.SetPen(wx.Pen(self.GetBorderColour())) + dc.DrawRectangle(rect) + pad = 1 + self.GetBorderPadding() + rect.Deflate(pad, pad) + + if self.GetBarColour(): + # if we have a bar color set, then we will use this + + colour = self.GetBarColour() + dc.SetBrush(wx.Brush(colour)) + dc.SetPen(wx.Pen(colour)) + + # calculate width of bar and draw it + if value > 100: + w = rect.width + else: + w = rect.width * (float(value) / 100) + + r = copy.copy(rect) + r.width = w + dc.DrawRectangle(r) + else: + # if bar color is not set, then we use pre-defined transitions + # for the colors based on the percentage value + + # calculate width of bar + if value > 100: + w = rect.width + else: + w = rect.width * (float(value) / 100) + r = copy.copy(rect) + r.width = w + + # determine transition range number and calculate xv (which is the + # progress between the two transition ranges) + pv = value + if pv <= 100: + xv = pv / 100 + transition = 0 + elif pv <= 101: + xv = pv - 100 + transition = 1 + elif pv <= 103: + xv = (pv - 101) / 2 + transition = 2 + elif pv <= 105: + xv = (pv - 103) / 2 + transition = 3 + else: + pv = 106 + xv = pv - 100 + transition = -1 + + if transition != -1: + start_color, end_color = self.transition_colors[transition] + color = color_utils.CalculateTransition(start_color, end_color, + xv) + else: + color = wx.Colour(191, 48, 48) # dark red + + color_factor = self.gradient_effect / 100 + mid_factor = (self.gradient_effect / 2) / 100 + + if self.gradient_effect > 0: + gradient_color = color_utils.Brighten(color, color_factor) + gradient_mid = color_utils.Brighten(color, mid_factor) + else: + gradient_color = color_utils.Darken(color, color_factor * -1) + gradient_mid = color_utils.Darken(color, mid_factor * -1) + + # draw bar + gradient_bitmap = draw.DrawGradientBar( + r.width, + r.height, + gradient_mid, + color, + gradient_color + ) + dc.DrawBitmap(gradient_bitmap, r.left, r.top) + + # font stuff begins here + dc.SetFont(self.font) + + # determine shadow position + r = copy.copy(rect) + r.left += 1 + r.top += 1 + + if self._max_range == 0.01 and self._value > 0: + format = u'\u221e' # infinity symbol + # drop shadow + dc.SetTextForeground(wx.Colour(80, 80, 80)) # dark grey + dc.DrawLabel(format, r, wx.ALIGN_CENTER) + # text + dc.SetTextForeground(wx.WHITE) + dc.DrawLabel(format, rect, wx.ALIGN_CENTER) + else: + if not self.GetBarColour() and self._show_remaining: + # we only do these for gradients with mouse over + range = self._max_range if self._max_range > 0.01 else 0 + value = range - self._value + if value < 0: + label = "over" + value = -value + else: + label = "left" + format = "{0:." + str(self._fraction_digits) + "f} " + label + else: + format = "{0:." + str(self._fraction_digits) + "f}%" + + # drop shadow + dc.SetTextForeground(wx.Colour(80, 80, 80)) + dc.DrawLabel(format.format(value), r, wx.ALIGN_CENTER) + # text + dc.SetTextForeground(wx.WHITE) + dc.DrawLabel(format.format(value), rect, wx.ALIGN_CENTER) + + def OnTimer(self, event): + old_value = self._old_percentage + value = self._percentage + start = 0 + + # -1 = left direction, 1 = right direction + direction = 1 if old_value < value else -1 + + end = direction * (value - old_value) + + self._anim_direction = direction + step = self.anim_effect(self._anim_step, start, end, self._anim_duration) + + self._anim_step += self._period + + if self._timer_id == event.GetId(): + stop_timer = False + + if self._anim_step > self._anim_duration: + stop_timer = True + + # add new value to the animation if we haven't reached our goal + # otherwise, stop animation + if direction == 1: + if old_value + step < value: + self._anim_value = old_value + step + else: + stop_timer = True + else: + if old_value - step > value: + self._anim_value = old_value - step + else: + stop_timer = True + + if stop_timer: + self._timer.Stop() + + self.Refresh() + + +if __name__ == "__main__": + def frange(x, y, jump): + while x < y: + yield x + x += jump + + class MyPanel(wx.Panel): + def __init__(self, parent, size=(500, 500)): + wx.Panel.__init__(self, parent, size=size) + box = wx.BoxSizer(wx.VERTICAL) + + # tests the colors of transition based on percentage used + # list of test values (from 99 -> 106 in increments of 0.5) + tests = [x for x in frange(99, 106.5, .5)] + + font = wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False) + + for i, value in enumerate(tests): + self.gauge = PyGauge(self, font, size=(100, 25)) + self.gauge.SetValueRange(value, 100) + self.gauge.SetFractionDigits(2) + box.Add(self.gauge, 0, wx.ALL, 2) + + gauge = PyGauge(self, font, size=(100, 25)) + gauge.SetBackgroundColour(wx.Colour(52, 86, 98)) + gauge.SetBarColour(wx.Colour(38, 133, 198)) + gauge.SetValue(59) + gauge.SetFractionDigits(1) + box.Add(gauge, 0, wx.ALL, 2) + + self.SetSizer(box) + self.Layout() + + # see animation going backwards with last gauge + wx.CallLater(2000, self.ChangeValues) + + def ChangeValues(self): + self.gauge.SetValueRange(4, 100) + + class Frame(wx.Frame): + def __init__(self, title, size=(500, 800)): + wx.Frame.__init__(self, None, title=title, size=size) + self.statusbar = self.CreateStatusBar() + main_sizer = wx.BoxSizer(wx.VERTICAL) + panel = MyPanel(self, size=size) + main_sizer.Add(panel) + self.SetSizer(main_sizer) + + app = wx.App(redirect=False) # Error messages go to popup window + top = Frame("Test Chrome Tabs") + top.Show() + app.MainLoop() diff --git a/gui/pyfatogglepanel.py b/gui/pyfatogglepanel.py deleted file mode 100644 index cdd4398bb..000000000 --- a/gui/pyfatogglepanel.py +++ /dev/null @@ -1,199 +0,0 @@ -########################################################################### -# pyfatogllepanel.py -# -# Author: Darriele - HomeWorld -# -# Project home: https://github.com/pyfa-org/Pyfa - pyfa project -# Some portions of code are based on -# AGW:pycollapsiblepane generic implementation of wx.CollapsiblePane -# AGW:pycollapsiblepane credits ( from the original source file used ): -# Andrea Gavana, @ 09 Aug 2007 -# Latest Revision: 12 Apr 2010, 12.00 GMT -# -# Module description: -# TogglePanel class is a wx.collipsablepane like implementation that uses -# some optimization from awg::pycollipsablepane to provide certain -# features tailored for PYFA needs. -# -# This module is part of PYFA (PYthon Fitting Assitant) and it shares the same -# licence ( read PYFA licence notice: gpl.txt ) -# -# Notes: leave the commented code as it is, those line will be removed someday -########################################################################### - -# noinspection PyPackageRequirements -import wx -from gui.bitmapLoader import BitmapLoader - - -class TogglePanel(wx.Panel): - def __init__(self, parent, forceLayout=-1): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(-1, -1), - style=wx.TAB_TRAVERSAL) - - self._toggle = 1 - self.parent = parent - self.forceLayout = forceLayout - self.bkColour = self.GetBackgroundColour() - - # Create the main sizer of this panel - self.mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(self.mainSizer) - # parentSize = parent.GetMinSize() - - # Create the header panel - self.headerPanel = wx.Panel(self) - self.mainSizer.Add(self.headerPanel, 0, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, 1) - - # Load expanded/collapsed bitmaps from the icons folder - self.bmpExpanded = BitmapLoader.getBitmap("down-arrow2", "gui") - self.bmpCollapsed = BitmapLoader.getBitmap("up-arrow2", "gui") - - # Make the bitmaps have the same color as window text - sysTextColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) - - img = self.bmpExpanded.ConvertToImage() - img.Replace(0, 0, 0, sysTextColour[0], sysTextColour[1], sysTextColour[2]) - self.bmpExpanded = wx.BitmapFromImage(img) - - img = self.bmpCollapsed.ConvertToImage() - img.Replace(0, 0, 0, sysTextColour[0], sysTextColour[1], sysTextColour[2]) - self.bmpCollapsed = wx.BitmapFromImage(img) - - self.headerBmp = wx.StaticBitmap(self.headerPanel) - self.headerBmp.SetBitmap(self.bmpExpanded) - - # Create the header sizer and add static bitmap and static text controls to it - - headerSizer = wx.BoxSizer(wx.HORIZONTAL) - self.headerPanel.SetSizer(headerSizer) - - hbmpSizer = wx.BoxSizer(wx.HORIZONTAL) - hlblSizer = wx.BoxSizer(wx.HORIZONTAL) - self.hcntSizer = wx.BoxSizer(wx.HORIZONTAL) - - hbmpSizer.Add(self.headerBmp, 0, 0, 5) - - self.headerLabel = wx.StaticText(self.headerPanel, wx.ID_ANY, u"PYFA", wx.DefaultPosition, wx.DefaultSize, 0) - hlblSizer.Add(self.headerLabel, 0, wx.EXPAND, 5) - - headerSizer.Add(hbmpSizer, 0, wx.RIGHT, 5) - headerSizer.Add(hlblSizer, 0, wx.RIGHT, 5) - headerSizer.Add(self.hcntSizer, 0, wx.RIGHT, 5) - - # Set the static text font weight to BOLD - - headerFont = parent.GetFont() - headerFont.SetWeight(wx.BOLD) - self.headerLabel.SetFont(headerFont) - - # Create the content panel and its main sizer - - self.contentSizer = wx.BoxSizer(wx.VERTICAL) - self.contentPanel = wx.Panel(self) - self.contentPanel.SetSizer(self.contentSizer) - - self.mainSizer.Add(self.contentPanel, 0, wx.EXPAND | wx.RIGHT | wx.LEFT, 5) - - self.Layout() - - # Connect Events - self.headerLabel.Bind(wx.EVT_LEFT_UP, self.toggleContent) - self.headerBmp.Bind(wx.EVT_LEFT_UP, self.toggleContent) - self.headerPanel.Bind(wx.EVT_LEFT_UP, self.toggleContent) - - def __del__(self): - pass - - def AddToggleItem(self, hitem): - hitem.Bind(wx.EVT_LEFT_UP, self.toggleContent) - - def GetHeaderContentSizer(self): - return self.hcntSizer - - def GetHeaderPanel(self): - return self.headerPanel - - def InsertItemInHeader(self, item): - self.hcntSizer.Add(item, 0, 0, 0) - self.Layout() - - def AddSizer(self, sizer): - self.contentSizer.Add(sizer, 0, wx.EXPAND | wx.ALL, 0) - self.Layout() - - def GetContentPane(self): - return self.contentPanel - - def SetLabel(self, label): - self.headerLabel.SetLabel(label) - - def IsCollapsed(self): - """ Returns ``True`` if the pane window is currently hidden. """ - if self._toggle == 1: - return False - else: - return True - - def IsExpanded(self): - """ Returns ``True`` if the pane window is currently shown. """ - if self._toggle == 1: - return False - else: - return True - - def OnStateChange(self, sz): - """ - Handles the status changes (collapsing/expanding). - - :param sz: an instance of `wx.Size`. - """ - - # minimal size has priority over the best size so set here our min size - self.SetMinSize(sz) - self.SetSize(sz) - - self.parent.GetSizer().SetSizeHints(self.parent) - - if self.IsCollapsed(): - # expanded . collapsed transition - if self.parent.GetSizer(): - # we have just set the size hints... - sz = self.parent.GetSizer().CalcMin() - - # use SetClientSize() and not SetSize() otherwise the size for - # e.g. a wxFrame with a menubar wouldn't be correctly set - self.parent.SetClientSize(sz) - else: - self.parent.Layout() - else: - # collapsed . expanded transition - # force our parent to "fit", i.e. expand so that it can honour - # our minimal size - self.parent.Fit() - - # Toggle the content panel (hide/show) - def toggleContent(self, event): - self.Freeze() - - if self._toggle == 1: - self.contentMinSize = self.contentPanel.GetSize() - self.contentPanel.Hide() - self.headerBmp.SetBitmap(self.bmpCollapsed) - else: - self.contentPanel.Show() - self.headerBmp.SetBitmap(self.bmpExpanded) - - self._toggle *= -1 - self.Layout() - self.Thaw() - - if self.forceLayout == -1: - if wx.VERSION >= (3, 0): - x, y = self.GetBestSize() - y -= self.contentPanel.GetSize()[1] - else: - x, y = self.GetBestSize() - self.OnStateChange((x, y)) - else: - self.parent.Layout() diff --git a/gui/pygauge.py b/gui/pygauge.py deleted file mode 100644 index dbb0c4531..000000000 --- a/gui/pygauge.py +++ /dev/null @@ -1,448 +0,0 @@ -# --------------------------------------------------------------------------------- # -# PYFAGAUGE wxPython IMPLEMENTATION -# -# Darriele, @ 08/30/2010 -# Updated: 09/07/2010 -# Based on AWG : pygauge code -# --------------------------------------------------------------------------------- # - -""" -PyfaGauge is a generic Gauge implementation tailored for PYFA (Python Fitting Assistant) -It uses the easeOutQuad equation from caurina.transitions.Tweener to do the animation stuff -""" - -# noinspection PyPackageRequirements -import wx -import copy - -from gui.utils import colorUtils -import gui.utils.drawUtils as drawUtils -import gui.utils.animEffects as animEffects -import gui.utils.fonts as fonts - -from service.fit import Fit - - -class PyGauge(wx.PyWindow): - """ - This class provides a visual alternative for `wx.Gauge`. It currently - only support determinant mode (see SetValue and SetRange) - """ - - def __init__(self, parent, id=wx.ID_ANY, range=100, pos=wx.DefaultPosition, - size=(-1, 30), style=0): - """ - Default class constructor. - - :param `parent`: parent window. Must not be ``None``; - :param `id`: window identifier. A value of -1 indicates a default value; - :param `pos`: the control position. A value of (-1, -1) indicates a default position, - chosen by either the windowing system or wxPython, depending on platform; - :param `size`: the control size. A value of (-1, -1) indicates a default size, - chosen by either the windowing system or wxPython, depending on platform. - """ - - wx.PyWindow.__init__(self, parent, id, pos, size, style) - - self._size = size - - self._border_colour = wx.BLACK - self._barColour = self._barColourSorted = [wx.Colour(212, 228, 255)] - self._barGradient = self._barGradientSorted = None - - self._border_padding = 0 - self._range = range - self._value = 0 - - self._fractionDigits = 0 - - self._timerId = wx.NewId() - self._timer = None - - self._oldValue = 0 - - self._animDuration = 500 - self._animStep = 0 - self._period = 20 - self._animValue = 0 - self._animDirection = 0 - self.animEffect = animEffects.OUT_QUAD - - self.transitionsColors = [(wx.Colour(191, 191, 191, 255), wx.Colour(96, 191, 0, 255)), - (wx.Colour(191, 167, 96, 255), wx.Colour(255, 191, 0, 255)), - (wx.Colour(255, 191, 0, 255), wx.Colour(255, 128, 0, 255)), - (wx.Colour(255, 128, 0, 255), wx.Colour(255, 0, 0, 255))] - self.gradientEffect = -35 - - self._percentage = 0 - self._oldPercentage = 0 - self._showRemaining = False - - self.font = wx.Font(fonts.NORMAL, wx.SWISS, wx.NORMAL, wx.NORMAL, False) - - self.SetBarGradient((wx.Colour(119, 119, 119), wx.Colour(153, 153, 153))) - self.SetBackgroundColour(wx.Colour(51, 51, 51)) - self._tooltip = wx.ToolTip("") - self.SetToolTip(self._tooltip) - self._tooltip.SetTip("0.00/100.00") - - self.Bind(wx.EVT_PAINT, self.OnPaint) - self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) - self.Bind(wx.EVT_TIMER, self.OnTimer) - self.Bind(wx.EVT_ENTER_WINDOW, self.OnWindowEnter) - self.Bind(wx.EVT_LEAVE_WINDOW, self.OnWindowLeave) - - def OnWindowEnter(self, event): - self._showRemaining = True - self.Refresh() - - def OnWindowLeave(self, event): - self._showRemaining = False - self.Refresh() - - def DoGetBestSize(self): - """ - Overridden base class virtual. Determines the best size of the - button based on the label and bezel size. - """ - - return wx.Size(self._size[0], self._size[1]) - - def GetBorderColour(self): - return self._border_colour - - def SetBorderColour(self, colour): - self._border_colour = colour - - SetBorderColor = SetBorderColour - GetBorderColor = GetBorderColour - - def GetBarColour(self): - return self._barColour[0] - - def SetBarColour(self, colour): - if not isinstance(colour, list): - self._barColour = [colour] - else: - self._barColour = list(colour) - - SetBarColor = SetBarColour - GetBarColor = GetBarColour - - def SetFractionDigits(self, digits): - self._fractionDigits = digits - - def GetBarGradient(self): - """ Returns a tuple containing the gradient start and end colours. """ - - if self._barGradient is None: - return None - - return self._barGradient[0] - - def SetBarGradient(self, gradient=None): - """ - Sets the bar gradient. This overrides the BarColour. - - :param gradient: a tuple containing the gradient start and end colours. - """ - if gradient is None: - self._barGradient = None - else: - if not isinstance(gradient, list): - self._barGradient = [gradient] - else: - self._barGradient = list(gradient) - - def GetBorderPadding(self): - """ Gets the border padding. """ - - return self._border_padding - - def SetBorderPadding(self, padding): - """ - Sets the border padding. - - :param padding: pixels between the border and the progress bar. - """ - - self._border_padding = padding - - def GetRange(self): - """ Returns the maximum value of the gauge. """ - - return self._range - - def Animate(self): - sFit = Fit.getInstance() - if sFit.serviceFittingOptions["enableGaugeAnimation"]: - if not self._timer: - self._timer = wx.Timer(self, self._timerId) - self._animStep = 0 - self._timer.Start(self._period) - else: - self._animValue = self._percentage - self.Refresh() - - def SetRange(self, range, reinit=False): - """ - Sets the range of the gauge. The gauge length is its - value as a proportion of the range. - - :param reinit: - :param range: The maximum value of the gauge. - """ - - if self._range == range: - return - - range_ = float(range) - - if range_ <= 0: - self._range = 0.01 - else: - self._range = range_ - - if reinit is False: - self._oldPercentage = self._percentage - self._percentage = (self._value / self._range) * 100 - else: - self._oldPercentage = self._percentage - self._percentage = 0 - self._value = 0 - - self.Animate() - - self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range > 0.01 else 0)) - - def GetValue(self): - """ Returns the current position of the gauge. """ - - return self._value - - def SetValue(self, value): - """ Sets the current position of the gauge. """ - if self._value == value: - return - - value = float(value) - self._oldPercentage = self._percentage - self._value = value - if value < 0: - self._value = 0 - self._percentage = (self._value / self._range) * 100 - - self.Animate() - - self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range)) - - def SetValueRange(self, value, range, reinit=False): - if self._value == value and self._range == range: - return - - range_ = float(range) - - if range_ <= 0: - self._range = 0.01 - else: - self._range = range_ - - value = float(value) - - self._value = value - if value < 0: - self._value = float(0) - - if reinit is False: - self._oldPercentage = self._percentage - self._percentage = (self._value / self._range) * 100 - - else: - self._oldPercentage = self._percentage - self._percentage = 0 - - self.Animate() - self._tooltip.SetTip("%.2f/%.2f" % (self._value, self._range if self._range > 0.01 else 0)) - - @staticmethod - def OnEraseBackground(event): - """ - Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{PyGauge}. - - :param event: a `wx.EraseEvent` event to be processed. - - :note: This method is intentionally empty to reduce flicker. - """ - - pass - - def OnPaint(self, event): - """ - Handles the ``wx.EVT_PAINT`` event for L{PyGauge}. - - :param event: a `wx.PaintEvent` event to be processed. - """ - - dc = wx.BufferedPaintDC(self) - rect = self.GetClientRect() - - dc.SetBackground(wx.Brush(self.GetBackgroundColour())) - dc.Clear() - - colour = self.GetBackgroundColour() - - dc.SetBrush(wx.Brush(colour)) - dc.SetPen(wx.Pen(colour)) - - dc.DrawRectangleRect(rect) - - value = self._percentage - if self._timer: - if self._timer.IsRunning(): - value = self._animValue - - if self._border_colour: - dc.SetPen(wx.Pen(self.GetBorderColour())) - dc.DrawRectangleRect(rect) - pad = 1 + self.GetBorderPadding() - rect.Deflate(pad, pad) - - if self.GetBarGradient(): - - if value > 100: - w = rect.width - else: - w = rect.width * (float(value) / 100) - r = copy.copy(rect) - r.width = w - - if r.width > 0: - # If we draw it with zero width, GTK throws errors. This way, - # only draw it if the gauge will actually show something. - # We stick other calculations in this block to avoid wasting - # time on them if not needed. See GH issue #282 - - pv = value - - if pv <= 100: - xv = pv / 100 - transition = 0 - - elif pv <= 101: - xv = pv - 100 - transition = 1 - - elif pv <= 103: - xv = (pv - 101) / 2 - transition = 2 - - elif pv <= 105: - xv = (pv - 103) / 2 - transition = 3 - - else: - pv = 106 - xv = pv - 100 - transition = -1 - - if transition != -1: - colorS, colorE = self.transitionsColors[transition] - color = colorUtils.CalculateTransitionColor(colorS, colorE, xv) - else: - color = wx.Colour(191, 48, 48) - - if self.gradientEffect > 0: - gcolor = colorUtils.BrightenColor(color, float(self.gradientEffect) / 100) - gMid = colorUtils.BrightenColor(color, float(self.gradientEffect / 2) / 100) - else: - gcolor = colorUtils.DarkenColor(color, float(-self.gradientEffect) / 100) - gMid = colorUtils.DarkenColor(color, float(-self.gradientEffect / 2) / 100) - - gBmp = drawUtils.DrawGradientBar(r.width, r.height, gMid, color, gcolor) - dc.DrawBitmap(gBmp, r.left, r.top) - - else: - colour = self.GetBarColour() - dc.SetBrush(wx.Brush(colour)) - dc.SetPen(wx.Pen(colour)) - if value > 100: - w = rect.width - else: - w = rect.width * (float(value) / 100) - r = copy.copy(rect) - r.width = w - dc.DrawRectangleRect(r) - - dc.SetFont(self.font) - - r = copy.copy(rect) - r.left += 1 - r.top += 1 - if self._range == 0.01 and self._value > 0: - formatStr = u'\u221e' - dc.SetTextForeground(wx.Colour(80, 80, 80)) - dc.DrawLabel(formatStr, r, wx.ALIGN_CENTER) - - dc.SetTextForeground(wx.Colour(255, 255, 255)) - dc.DrawLabel(formatStr, rect, wx.ALIGN_CENTER) - else: - if self.GetBarGradient() and self._showRemaining: - range = self._range if self._range > 0.01 else 0 - value = range - self._value - if value < 0: - label = "over" - value = -value - else: - label = "left" - formatStr = "{0:." + str(self._fractionDigits) + "f} " + label - - else: - formatStr = "{0:." + str(self._fractionDigits) + "f}%" - - dc.SetTextForeground(wx.Colour(80, 80, 80)) - dc.DrawLabel(formatStr.format(value), r, wx.ALIGN_CENTER) - - dc.SetTextForeground(wx.Colour(255, 255, 255)) - dc.DrawLabel(formatStr.format(value), rect, wx.ALIGN_CENTER) - - def OnTimer(self, event): - """ - Handles the ``wx.EVT_TIMER`` event for L{PyfaGauge}. - - :param event: a timer event - """ - oldValue = self._oldPercentage - value = self._percentage - start = 0 - - direction = 1 if oldValue < value else -1 - - end = direction * (value - oldValue) - - self._animDirection = direction - step = self.animEffect(self._animStep, start, end, self._animDuration) - - self._animStep += self._period - - if self._timerId == event.GetId(): - stop_timer = False - - if self._animStep > self._animDuration: - stop_timer = True - - if direction == 1: - if (oldValue + step) < value: - self._animValue = oldValue + step - else: - stop_timer = True - else: - if (oldValue - step) > value: - self._animValue = oldValue - step - - else: - stop_timer = True - - if stop_timer: - self._timer.Stop() - - self.Refresh() diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py index c8bab84bb..b6997e19d 100644 --- a/gui/resistsEditor.py +++ b/gui/resistsEditor.py @@ -20,7 +20,7 @@ # noinspection PyPackageRequirements import wx from service.targetResists import TargetResists -from gui.bitmapLoader import BitmapLoader +from gui.bitmap_loader import BitmapLoader from gui.utils.clipboard import toClipboard, fromClipboard from gui.builtinViews.entityEditor import EntityEditor, BaseValidator from logbook import Logger @@ -49,7 +49,7 @@ class TargetResistsTextValidor(BaseValidator): return True except ValueError as e: pyfalog.error(e) - wx.MessageBox(u"{}".format(e), "Error") + wx.MessageBox("{}".format(e), "Error") textCtrl.SetFocus() return False @@ -87,10 +87,10 @@ class ResistsEditorDlg(wx.Dialog): DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive") def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Target Resists Editor", size=wx.Size(350, 240)) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Target Resists Editor", size=wx.Size(350, 240)) self.block = False - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -124,7 +124,7 @@ class ResistsEditorDlg(wx.Dialog): setattr(self, "%sEdit" % type_, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize)) editObj = getattr(self, "%sEdit" % type_) resistEditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) - resistEditSizer.Add(wx.StaticText(self, wx.ID_ANY, u"%", wx.DefaultPosition, wx.DefaultSize, 0), 0, + resistEditSizer.Add(wx.StaticText(self, wx.ID_ANY, "%", wx.DefaultPosition, wx.DefaultSize, 0), 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated) @@ -138,7 +138,7 @@ class ResistsEditorDlg(wx.Dialog): footerSizer = wx.BoxSizer(wx.HORIZONTAL) perSizer = wx.BoxSizer(wx.VERTICAL) - self.stNotice = wx.StaticText(self, wx.ID_ANY, u"") + self.stNotice = wx.StaticText(self, wx.ID_ANY, "") self.stNotice.Wrap(-1) perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5) @@ -151,7 +151,7 @@ class ResistsEditorDlg(wx.Dialog): mainSizer.Add(contentSizer, 1, wx.EXPAND, 0) if "wxGTK" in wx.PlatformInfo: - self.closeBtn = wx.Button(self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0) + self.closeBtn = wx.Button(self, wx.ID_ANY, "Close", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.closeBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 5) self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent) @@ -170,7 +170,7 @@ class ResistsEditorDlg(wx.Dialog): btn.Layout() setattr(self, name, btn) btn.Enable(True) - btn.SetToolTipString("%s patterns %s clipboard" % (name, direction)) + btn.SetToolTip("%s patterns %s clipboard" % (name, direction)) footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) btn.Bind(wx.EVT_BUTTON, getattr(self, "{}Patterns".format(name.lower()))) diff --git a/gui/setEditor.py b/gui/setEditor.py index d7239254c..caf9d7eaf 100644 --- a/gui/setEditor.py +++ b/gui/setEditor.py @@ -50,7 +50,7 @@ class ImplantTextValidor(BaseValidator): return True except ValueError as e: pyfalog.error(e) - wx.MessageBox(u"{}".format(e), "Error") + wx.MessageBox("{}".format(e), "Error") textCtrl.SetFocus() return False @@ -114,10 +114,10 @@ class ImplantSetEditor(BaseImplantEditorView): class ImplantSetEditorDlg(wx.Dialog): def __init__(self, parent): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Implant Set Editor", size=wx.Size(640, 600)) + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Implant Set Editor", size=wx.Size(640, 600)) self.block = False - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -135,12 +135,12 @@ class ImplantSetEditorDlg(wx.Dialog): footerSizer = wx.BoxSizer(wx.HORIZONTAL) - self.stNotice = wx.StaticText(self, wx.ID_ANY, u"") + self.stNotice = wx.StaticText(self, wx.ID_ANY, "") self.stNotice.Wrap(-1) footerSizer.Add(self.stNotice, 1, wx.BOTTOM | wx.TOP | wx.LEFT, 5) if "wxGTK" in wx.PlatformInfo: - self.closeBtn = wx.Button(self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0) + self.closeBtn = wx.Button(self, wx.ID_ANY, "Close", wx.DefaultPosition, wx.DefaultSize, 0) mainSizer.Add(self.closeBtn, 0, wx.ALL | wx.ALIGN_RIGHT, 5) self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent) @@ -157,7 +157,7 @@ class ImplantSetEditorDlg(wx.Dialog): btn.Layout() setattr(self, name, btn) btn.Enable(True) - btn.SetToolTipString("%s implant sets %s clipboard" % (name, direction)) + btn.SetToolTip("%s implant sets %s clipboard" % (name, direction)) footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT) mainSizer.Add(footerSizer, 0, wx.ALL | wx.EXPAND, 5) diff --git a/gui/shipBrowser.py b/gui/shipBrowser.py index 935797718..2b12edd8c 100644 --- a/gui/shipBrowser.py +++ b/gui/shipBrowser.py @@ -11,7 +11,7 @@ from gui.builtinShipBrowser.shipItem import ShipItem from service.fit import Fit from service.market import Market -import gui.builtinShipBrowser.events as events +from gui.builtinShipBrowser.events import EVT_SB_IMPORT_SEL, EVT_SB_STAGE1_SEL, EVT_SB_STAGE2_SEL, EVT_SB_STAGE3_SEL, EVT_SB_SEARCH_SEL from gui.builtinShipBrowser.pfWidgetContainer import PFWidgetsContainer from gui.builtinShipBrowser.navigationPanel import NavigationPanel from gui.builtinShipBrowser.raceSelector import RaceSelector @@ -50,7 +50,7 @@ class ShipBrowser(wx.Panel): if race: self.racesFilter[race] = False - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) mainSizer = wx.BoxSizer(wx.VERTICAL) @@ -76,11 +76,11 @@ class ShipBrowser(wx.Panel): self.Show() self.Bind(wx.EVT_SIZE, self.SizeRefreshList) - self.Bind(events.EVT_SB_STAGE2_SEL, self.stage2) - self.Bind(events.EVT_SB_STAGE1_SEL, self.stage1) - self.Bind(events.EVT_SB_STAGE3_SEL, self.stage3) - self.Bind(events.EVT_SB_SEARCH_SEL, self.searchStage) - self.Bind(events.EVT_SB_IMPORT_SEL, self.importStage) + self.Bind(EVT_SB_STAGE2_SEL, self.stage2) + self.Bind(EVT_SB_STAGE1_SEL, self.stage1) + self.Bind(EVT_SB_STAGE3_SEL, self.stage3) + self.Bind(EVT_SB_SEARCH_SEL, self.searchStage) + self.Bind(EVT_SB_IMPORT_SEL, self.importStage) self.mainFrame.Bind(GE.FIT_CHANGED, self.RefreshList) @@ -214,12 +214,12 @@ class ShipBrowser(wx.Panel): if ship.race not in racesList: racesList.append(ship.race) - for race, state in self.racesFilter.iteritems(): + for race, state in self.racesFilter.items(): if race in racesList: subRacesFilter[race] = self.racesFilter[race] override = True - for race, state in subRacesFilter.iteritems(): + for race, state in subRacesFilter.items(): if state: override = False break @@ -386,7 +386,7 @@ class ShipBrowser(wx.Panel): self.lpane.AddWidget(FitItem(self.lpane, ID, (shipName, shipTrait, name, booster, timestamp, notes), shipID)) if len(ships) == 0 and len(fitList) == 0: - self.lpane.AddWidget(PFStaticText(self.lpane, label=u"No matching results.")) + self.lpane.AddWidget(PFStaticText(self.lpane, label="No matching results.")) self.lpane.RefreshList(doFocus=False) self.lpane.Thaw() diff --git a/gui/ssoLogin.py b/gui/ssoLogin.py new file mode 100644 index 000000000..2ff364752 --- /dev/null +++ b/gui/ssoLogin.py @@ -0,0 +1,25 @@ +import wx + +class SsoLogin(wx.Dialog): + def __init__(self, parent): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="SSO Login", size=wx.Size(400, 240)) + + bSizer1 = wx.BoxSizer(wx.VERTICAL) + + text = wx.StaticText(self, wx.ID_ANY, "Copy and paste the block of text provided by pyfa.io, then click OK") + bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10) + + self.ssoInfoCtrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, (-1, -1), style=wx.TE_MULTILINE) + self.ssoInfoCtrl.SetFont(wx.Font(8, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)) + self.ssoInfoCtrl.Layout() + + bSizer1.Add(self.ssoInfoCtrl, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10) + + bSizer3 = wx.BoxSizer(wx.VERTICAL) + bSizer3.Add(wx.StaticLine(self, wx.ID_ANY), 0, wx.BOTTOM | wx.EXPAND, 10) + + bSizer3.Add(self.CreateStdDialogButtonSizer(wx.OK | wx.CANCEL), 0, wx.EXPAND) + bSizer1.Add(bSizer3, 0, wx.ALL | wx.EXPAND, 10) + + self.SetSizer(bSizer1) + self.Center() diff --git a/gui/statsPane.py b/gui/statsPane.py index c186c4531..b3984ccfa 100644 --- a/gui/statsPane.py +++ b/gui/statsPane.py @@ -28,7 +28,7 @@ import gui.globalEvents as GE # import gui.builtinViews.fittingView as fv from gui.statsView import StatsView from gui.contextMenu import ContextMenu -from gui.pyfatogglepanel import TogglePanel +from gui.toggle_panel import TogglePanel from logbook import Logger pyfalog = Logger(__name__) @@ -96,7 +96,7 @@ class StatsPane(wx.Panel): i = 0 for viewName in self.DEFAULT_VIEWS: tp = TogglePanel(self) - contentPanel = tp.GetContentPane() + contentPanel = tp.GetContentPanel() contentPanel.viewName = viewName try: diff --git a/gui/toggle_panel.py b/gui/toggle_panel.py new file mode 100644 index 000000000..2f7421d38 --- /dev/null +++ b/gui/toggle_panel.py @@ -0,0 +1,201 @@ +# =============================================================================== +# TogglePanel is based on PyCollapsiblePane - includes a few improvements +# such as adding items to header, lack of button implementation ("GTK +# expander" style is implemented with plain text with unicode arrows rather +# than drawn geometry), etc. +# +# When adding TogglePanel to sizer, it is important to ensure the following: +# sizer is vertical +# set proportion = 0 +# +# ToDo: Create animations for collapsing / expanding +# +# =============================================================================== + +import wx + + +class TogglePanel (wx.Panel): + def __init__(self, parent, force_layout=False, *args, **kargs): + super().__init__(parent, *args, **kargs) + + self._toggled = True + self.parent = parent + self.force_layout = force_layout + + # Create the main sizer of this panel + self.main_sizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(self.main_sizer) + + # Create the header panel, set sizer, and add to main sizer + self.header_panel = wx.Panel(self) + header_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.header_panel.SetSizer(header_sizer) + + self.main_sizer.Add(self.header_panel, 0, wx.EXPAND | wx.TOP | + wx.BOTTOM | wx.RIGHT, 1) + + # Add arrow + self.header_arrow = wx.StaticText(self.header_panel, wx.ID_ANY, + "\u25bc", size=wx.Size((10, -1))) + header_sizer.Add(self.header_arrow, 0, wx.RIGHT, 5) + + # Add header text + self.header_label = wx.StaticText(self.header_panel, wx.ID_ANY, "") + font = parent.GetFont() + font.SetWeight(wx.BOLD) + self.header_label.SetFont(font) + header_sizer.Add(self.header_label, 0, wx.RIGHT, 5) + + # Add a sizer for additional header items if we need it + self.hcontent_sizer = wx.BoxSizer(wx.HORIZONTAL) + header_sizer.Add(self.hcontent_sizer, 0, wx.RIGHT, 5) + + # Create the content panel, set sizer, and add to main sizer + self.content_panel = wx.Panel(self) + self.content_sizer = wx.BoxSizer(wx.VERTICAL) + self.content_panel.SetSizer(self.content_sizer) + + self.main_sizer.Add(self.content_panel, 0, wx.EXPAND | wx.RIGHT | + wx.LEFT, 5) + + self.Layout() + + # Connect Events + self.header_label.Bind(wx.EVT_LEFT_UP, self.ToggleContent) + self.header_arrow.Bind(wx.EVT_LEFT_UP, self.ToggleContent) + self.header_panel.Bind(wx.EVT_LEFT_UP, self.ToggleContent) + + def __del__(self): + pass + + def AddToggleItem(self, item): + item.Bind(wx.EVT_LEFT_UP, self.ToggleContent) + + def GetHeaderContentSizer(self): + return self.hcontent_sizer + + def GetHeaderPanel(self): + return self.header_panel + + def InsertItemInHeader(self, item): + self.hcontent_sizer.Add(item, 0, 0, 0) + self.AddToggleItem(item) + self.Layout() + + def AddSizer(self, sizer): + self.content_sizer.Add(sizer, 0, wx.EXPAND | wx.ALL, 0) + self.Layout() + + def GetContentPanel(self): + return self.content_panel + + def SetLabel(self, label): + self.header_label.SetLabel(label) + + def IsCollapsed(self): + return not self._toggled + + def IsExpanded(self): + return self._toggled + + def OnStateChange(self, sz): + self.SetSize(sz) + + self.parent.GetSizer().SetSizeHints(self.parent) + + if not self._toggled: + if self.parent.GetSizer(): + # we have just set the size hints... + sz = self.parent.GetSizer().CalcMin() + + # use SetClientSize() and not SetSize() otherwise the size for + # e.g. a wxFrame with a menubar wouldn't be correctly set + self.parent.SetClientSize(sz) + else: + self.parent.Layout() + else: + # force our parent to "fit", i.e. expand so that it can honor + # our minimal size + self.parent.Fit() + + def ToggleContent(self, event): + # self.Freeze() + + if self._toggled: + # If we are expanded, save previous size and collapse by setting + # content height to 0 + self.content_min_size = self.content_panel.GetSize() + self.content_panel.SetMinSize((self.content_min_size[0], 0)) + self.header_arrow.SetLabel("\u25b6") + else: + # If we are collapsed, set content size to previously saved value + self.content_panel.SetMinSize(self.content_min_size) + self.header_arrow.SetLabel("\u25bc") + + self._toggled = not self._toggled + + # self.Thaw() + + if self.force_layout: + self.parent.Layout() + else: + self.OnStateChange(self.GetBestSize()) + + +if __name__ == "__main__": + + from wx.lib.inspection import InspectionTool + + class MainPanel(wx.Panel): + def __init__(self, parent): + super().__init__(parent, size=(-1, -1)) + + if 'wxMSW' in wx.PlatformInfo: + color = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE) + self.SetBackgroundColour(color) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + # Generate 3 test panels with different font sizes + for x in range(3): + toggle_panel = TogglePanel(self) + toggle_panel.SetLabel("Test Header") + + content_panel = toggle_panel.GetContentPanel() + content_panel.SetBackgroundColour(wx.WHITE) + + content_sizer = wx.BoxSizer(wx.HORIZONTAL) + header = wx.StaticText(content_panel, -1, "TogglePanel Test") + header.SetFont(wx.Font(10 + (x * 3), wx.SWISS, wx.NORMAL, wx.BOLD)) + content_sizer.Add(header, 0, wx.ALL, 10) + content_panel.SetSizer(content_sizer) + + main_sizer.Add(toggle_panel, 0, wx.EXPAND | wx.ALL, 2) + + self.SetSizer(main_sizer) + + class Frame(wx.Frame): + def __init__(self, title): + super().__init__(None, title=title, size=(500, 500)) + main_sizer = wx.BoxSizer(wx.VERTICAL) + + self.statsPane = MainPanel(self) + main_sizer.Add(self.statsPane, 0, wx.EXPAND) + + self.SetSizerAndFit(main_sizer) + + if not InspectionTool().initialized: + InspectionTool().Init() + + # Find a widget to be selected in the tree. Use either the + # one under the cursor, if any, or this frame. + wnd, _ = wx.FindWindowAtPointer() + if not wnd: + wnd = self + InspectionTool().Show(wnd, True) + + app = wx.App(redirect=False) # Error messages go to popup window + top = Frame("Test Toggle Panel") + top.Show() + app.MainLoop() diff --git a/gui/updateDialog.py b/gui/updateDialog.py index 019901819..c575eeaf3 100644 --- a/gui/updateDialog.py +++ b/gui/updateDialog.py @@ -22,66 +22,63 @@ import wx # noinspection PyPackageRequirements import dateutil.parser from service.settings import UpdateSettings as svc_UpdateSettings +import wx.html2 +import webbrowser +import re +import markdown2 + +# HTML template. We link to a bootstrap cdn for quick and easy css, and include some additional teaks. +html_tmpl = """ + + +

pyfa {0}

+
{1}
+
+{2} +{3} +""" class UpdateDialog(wx.Dialog): - def __init__(self, parent, release): - wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="Pyfa Update", pos=wx.DefaultPosition, - size=wx.Size(400, 300), style=wx.DEFAULT_DIALOG_STYLE) + def __init__(self, parent, release, version): + wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="pyfa Update Available", pos=wx.DefaultPosition, + size=wx.Size(550, 450), style=wx.DEFAULT_DIALOG_STYLE) self.UpdateSettings = svc_UpdateSettings.getInstance() self.releaseInfo = release - self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) + self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) mainSizer = wx.BoxSizer(wx.VERTICAL) - headSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.headingText = wx.StaticText(self, wx.ID_ANY, "Pyfa Update Available!", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_CENTRE) - self.headingText.Wrap(-1) - self.headingText.SetFont(wx.Font(14, 74, 90, 92, False)) - - headSizer.Add(self.headingText, 1, wx.ALL, 5) - mainSizer.Add(headSizer, 0, wx.EXPAND, 5) - - mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0, - wx.EXPAND | wx.ALL, 5) - - versionSizer = wx.BoxSizer(wx.HORIZONTAL) - - if self.releaseInfo['prerelease']: - self.releaseText = wx.StaticText(self, wx.ID_ANY, "Pre-release", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_RIGHT) - self.releaseText.SetFont(wx.Font(12, 74, 90, 92, False)) - self.releaseText.SetForegroundColour(wx.Colour(230, 0, 0)) - else: - self.releaseText = wx.StaticText(self, wx.ID_ANY, "Stable", wx.DefaultPosition, wx.DefaultSize, - wx.ALIGN_RIGHT) - self.releaseText.SetFont(wx.Font(12, 74, 90, 90, False)) - - self.releaseText.Wrap(-1) - - versionSizer.Add(self.releaseText, 1, wx.ALL, 5) - - self.versionText = wx.StaticText(self, wx.ID_ANY, self.releaseInfo['tag_name'], wx.DefaultPosition, - wx.DefaultSize, wx.ALIGN_LEFT) - self.versionText.Wrap(-1) - self.versionText.SetFont(wx.Font(12, 74, 90, 90, False)) - - versionSizer.Add(self.versionText, 1, wx.ALL, 5) - versionSizer.AddSpacer((15, 5), 0, wx.EXPAND, 5) - - mainSizer.Add(versionSizer, 0, wx.EXPAND, 5) - mainSizer.AddSpacer((0, 5), 0, wx.EXPAND, 5) - releaseDate = dateutil.parser.parse(self.releaseInfo['published_at']) notesSizer = wx.BoxSizer(wx.HORIZONTAL) - self.notesTextCtrl = wx.TextCtrl(self, wx.ID_ANY, str(releaseDate.date()) + ":\n\n" + self.releaseInfo['body'], - wx.DefaultPosition, wx.DefaultSize, - wx.TE_AUTO_URL | wx.TE_MULTILINE | wx.TE_READONLY | wx.DOUBLE_BORDER | wx.TRANSPARENT_WINDOW) + self.browser = wx.html2.WebView.New(self) + self.browser.Bind(wx.html2.EVT_WEBVIEW_NEWWINDOW, self.OnNewWindow) - notesSizer.Add(self.notesTextCtrl, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 5) + link_patterns = [ + (re.compile("([0-9a-f]{6,40})", re.I), r"https://github.com/pyfa-org/Pyfa/commit/\1"), + (re.compile("#(\d+)", re.I), r"https://github.com/pyfa-org/Pyfa/issues/\1"), + (re.compile("@(\w+)", re.I), r"https://github.com/\1") + ] + + markdowner = markdown2.Markdown( + extras=['cuddled-lists', 'fenced-code-blocks', 'target-blank-links', 'toc', 'link-patterns'], + link_patterns=link_patterns) + + self.browser.SetPage(html_tmpl.format( + self.releaseInfo['tag_name'], + releaseDate.strftime('%B %d, %Y'), + "

This is a pre-release, be prepared for unstable features

" if version.is_prerelease else "", + markdowner.convert(self.releaseInfo['body']) + ),"") + + notesSizer.Add(self.browser, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 5) mainSizer.Add(notesSizer, 1, wx.EXPAND, 5) self.supressCheckbox = wx.CheckBox(self, wx.ID_ANY, "Don't remind me again for this release", @@ -119,6 +116,10 @@ class UpdateDialog(wx.Dialog): def OnClose(self, e): self.Close() + def OnNewWindow(self, event): + url = event.GetURL() + webbrowser.open(url) + def SuppressChange(self, e): if self.supressCheckbox.IsChecked(): self.UpdateSettings.set('version', self.releaseInfo['tag_name']) diff --git a/gui/utils/animUtils.py b/gui/utils/anim.py similarity index 85% rename from gui/utils/animUtils.py rename to gui/utils/anim.py index 4003e7760..0f3333dc1 100644 --- a/gui/utils/animUtils.py +++ b/gui/utils/anim.py @@ -1,6 +1,6 @@ # noinspection PyPackageRequirements import wx -import gui.utils.colorUtils as colorUtils +import gui.utils.color as colorUtils class LoadAnimation(wx.Window): @@ -24,6 +24,8 @@ class LoadAnimation(wx.Window): self.animTimer.Start(self.animTimerPeriod) + self.SetBackgroundStyle(wx.BG_STYLE_PAINT) + def Play(self): if self.animTimer.IsRunning(): self.animTimer.Stop() @@ -52,13 +54,13 @@ class LoadAnimation(wx.Window): def OnPaint(self, event): rect = self.GetClientRect() - dc = wx.BufferedPaintDC(self) - windowColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + dc = wx.AutoBufferedPaintDC(self) + windowColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) dc.SetBackground(wx.Brush(windowColor)) dc.Clear() - barColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) - shadeColor = colorUtils.GetSuitableColor(barColor, 0.75) + barColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) + shadeColor = colorUtils.GetSuitable(barColor, 0.75) barWidth = rect.width / self.bars barHeight = rect.height - self.padding * 2 @@ -72,7 +74,7 @@ class LoadAnimation(wx.Window): bh = barHeight y = self.padding else: - barColor = colorUtils.GetSuitableColor(barColor, float(self.animCount / 2) / 10) + barColor = colorUtils.GetSuitable(barColor, float(self.animCount / 2) / 10) dc.SetPen(wx.Pen(barColor)) dc.SetBrush(wx.Brush(barColor)) bh = rect.height @@ -81,7 +83,7 @@ class LoadAnimation(wx.Window): dc.DrawRectangle(x, y, barWidth, bh) x += barWidth - textColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + textColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) dc.SetTextForeground(textColor) dc.DrawLabel(self.label, rect, wx.ALIGN_CENTER) diff --git a/gui/utils/animEffects.py b/gui/utils/anim_effects.py similarity index 100% rename from gui/utils/animEffects.py rename to gui/utils/anim_effects.py diff --git a/gui/utils/color.py b/gui/utils/color.py new file mode 100644 index 000000000..f89b56d7a --- /dev/null +++ b/gui/utils/color.py @@ -0,0 +1,73 @@ +# noinspection PyPackageRequirements +import wx + + +def Brighten(color, factor): + """ Brightens a Color using a factor between 0 and 1""" + r, g, b, a = color + + factor = min(max(factor, 0), 1) + + r += (255 - r) * factor + b += (255 - b) * factor + g += (255 - g) * factor + + return wx.Colour(r, g, b, a) + + +def Darken(color, factor): + """ Darkens a Color using a factor between 0 and 1""" + r, g, b, a = color + + factor = min(max(factor, 0), 1) + factor = 1 - factor + + r *= factor + g *= factor + b *= factor + + r = min(max(r, 0), 255) + b = min(max(b, 0), 255) + g = min(max(g, 0), 255) + + return wx.Colour(r, g, b, a) + + +def _getBrightness(color): + """ + Calculates brightness of color + http://stackoverflow.com/a/596243/788054 + """ + r, g, b, a = color + return 0.299 * r + 0.587 * g + 0.114 * b + + +def GetSuitable(color, factor: [0, 1]): + """ + Calculates a suitable color based on original color (wx.Colour), its + brightness, and a factor (darken/brighten by factor depending on + calculated brightness) + """ + + brightness = _getBrightness(color) + + if brightness > 129: + return Darken(color, factor) + else: + return Brighten(color, factor) + + +def CalculateTransition(s_color, e_color, delta): + """ + Calculates the color between a given start and end color using a delta + value between 0 and 1 + """ + + sR, sG, sB, sA = s_color + eR, eG, eB, eA = e_color + + tR = sR + (eR - sR) * delta + tG = sG + (eG - sG) * delta + tB = sB + (eB - sB) * delta + + return wx.Colour(tR, tG, tB, (sA + eA) / 2) diff --git a/gui/utils/colorUtils.py b/gui/utils/colorUtils.py deleted file mode 100644 index d2f10336b..000000000 --- a/gui/utils/colorUtils.py +++ /dev/null @@ -1,81 +0,0 @@ -# noinspection PyPackageRequirements -import wx -import math - - -def BrightenColor(color, factor): - # Brightens a color (wx.Colour), factor = [0,1] - - r, g, b = color - a = color.Alpha() - - factor = min(max(factor, 0), 1) - - r += (255 - r) * factor - b += (255 - b) * factor - g += (255 - g) * factor - - return wx.Colour(r, g, b, a) - - -def DarkenColor(color, factor): - # Darkens a color (wx.Colour), factor = [0, 1] - - bkR, bkG, bkB = color - - alpha = color.Alpha() - - factor = min(max(factor, 0), 1) - factor = 1 - factor - - r = float(bkR * factor) - g = float(bkG * factor) - b = float(bkB * factor) - - r = min(max(r, 0), 255) - b = min(max(b, 0), 255) - g = min(max(g, 0), 255) - - return wx.Colour(r, g, b, alpha) - - -def GetBrightnessO1(color): - # Calculates the brightness of a color, different options - - r, g, b = color - return 0.299 * r + 0.587 * g + 0.114 * b - - -def GetBrightnessO2(color): - r, g, b = color - return math.sqrt(0.241 * r * r + 0.691 * g * g + 0.068 * b * b) - - -def GetSuitableColor(color, factor): - # Calculates a suitable color based on original color (wx.Colour), its brightness, a factor=[0,1] (darken/brighten by factor depending on calculated brightness) - - brightness = GetBrightnessO1(color) - - if brightness > 129: - return DarkenColor(color, factor) - else: - return BrightenColor(color, factor) - - -def CalculateTransitionColor(startColor, endColor, delta): - """ - Calculates the color between a given start and end colors, delta = [0,1] - Colors are wx.Colour objects - """ - - sR, sG, sB = startColor - eR, eG, eB = endColor - - alphaS = startColor.Alpha() - alphaE = endColor.Alpha() - - tR = sR + (eR - sR) * delta - tG = sG + (eG - sG) * delta - tB = sB + (eB - sB) * delta - - return wx.Colour(tR, tG, tB, (alphaS + alphaE) / 2) diff --git a/gui/utils/draw.py b/gui/utils/draw.py new file mode 100644 index 000000000..992dbb9fb --- /dev/null +++ b/gui/utils/draw.py @@ -0,0 +1,90 @@ +# noinspection PyPackageRequirements +import wx +from . import color + + +def RenderGradientBar(windowColor, width, height, sFactor, eFactor, mFactor=None , fillRatio=2): + + if sFactor == 0 and eFactor == 0 and mFactor is None: + return DrawFilledBitmap(width, height, windowColor) + + gStart = color.GetSuitable(windowColor, sFactor) + + if mFactor: + gMid = color.GetSuitable(windowColor, mFactor) + else: + gMid = color.GetSuitable(windowColor, sFactor + (eFactor - sFactor) / 2) + + gEnd = color.GetSuitable(windowColor, eFactor) + + return DrawGradientBar(width, height, gStart, gEnd, gMid, fillRatio) + + +def DrawFilledBitmap(width, height, color): + canvas = wx.Bitmap(width, height) + + mdc = wx.MemoryDC() + mdc.SelectObject(canvas) + + mdc.SetBackground(wx.Brush(color)) + mdc.Clear() + + mdc.SelectObject(wx.NullBitmap) + + return canvas + + +def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4): + canvas = wx.Bitmap(width, height) + + mdc = wx.MemoryDC() + mdc.SelectObject(canvas) + + r = wx.Rect(0, 0, width, height) + r.SetHeight(height / fillRatio) + + if gMid is None: + gMid = gStart + + mdc.GradientFillLinear(r, gStart, gMid, wx.SOUTH) + r.SetTop(r.GetHeight()) + r.SetHeight(height * (fillRatio - 1) / fillRatio + (1 if height % fillRatio != 0 else 0)) + + mdc.GradientFillLinear(r, gMid, gEnd, wx.SOUTH) + + mdc.SelectObject(wx.NullBitmap) + + return canvas + + +def GetPartialText(dc, text , maxWidth, defEllipsis="..."): + ellipsis = defEllipsis + base_w, h = dc.GetTextExtent(ellipsis) + + lenText = len(text) + drawntext = text + w, dummy = dc.GetTextExtent(text) + + while lenText > 0: + + if w + base_w <= maxWidth: + break + + w_c, h_c = dc.GetTextExtent(drawntext[-1]) + drawntext = drawntext[0:-1] + lenText -= 1 + w -= w_c + + while len(ellipsis) > 0 and w + base_w > maxWidth: + ellipsis = ellipsis[0:-1] + base_w, h = dc.GetTextExtent(ellipsis) + if len(text) > lenText: + return drawntext + ellipsis + else: + return text + + +def CreateDropShadowBitmap(bitmap, opacity): + img = bitmap.ConvertToImage() + img = img.AdjustChannels(0, 0, 0, opacity) + return wx.Bitmap(img) diff --git a/gui/utils/drawUtils.py b/gui/utils/drawUtils.py deleted file mode 100644 index d7034ece7..000000000 --- a/gui/utils/drawUtils.py +++ /dev/null @@ -1,111 +0,0 @@ -# noinspection PyPackageRequirements -import wx -import gui.utils.colorUtils as colorUtils - - -def RenderGradientBar(windowColor, width, height, sFactor, eFactor, mFactor=None, fillRatio=2): - if sFactor == 0 and eFactor == 0 and mFactor is None: - return DrawFilledBitmap(width, height, windowColor) - - gStart = colorUtils.GetSuitableColor(windowColor, sFactor) - - if mFactor: - gMid = colorUtils.GetSuitableColor(windowColor, mFactor) - else: - gMid = colorUtils.GetSuitableColor(windowColor, sFactor + (eFactor - sFactor) / 2) - - gEnd = colorUtils.GetSuitableColor(windowColor, eFactor) - - return DrawGradientBar(width, height, gStart, gEnd, gMid, fillRatio) - - -def DrawFilledBitmap(width, height, color): - canvas = wx.EmptyBitmap(width, height) - - mdc = wx.MemoryDC() - mdc.SelectObject(canvas) - - mdc.SetBackground(wx.Brush(color)) - mdc.Clear() - - mdc.SelectObject(wx.NullBitmap) - - return canvas - - -# noinspection PyPropertyAccess -def DrawGradientBar(width, height, gStart, gEnd, gMid=None, fillRatio=4): - # we need to have dimensions to draw - # assert width > 0 and height > 0 - canvas = wx.EmptyBitmap(width, height) - - mdc = wx.MemoryDC() - mdc.SelectObject(canvas) - - r = wx.Rect(0, 0, width, height) - r.height = height / fillRatio - - if gMid is None: - gMid = gStart - - mdc.GradientFillLinear(r, gStart, gMid, wx.SOUTH) - r.top = r.height - r.height = height * (fillRatio - 1) / fillRatio + (1 if height % fillRatio != 0 else 0) - - mdc.GradientFillLinear(r, gMid, gEnd, wx.SOUTH) - - mdc.SelectObject(wx.NullBitmap) - - return canvas - - -def GetPartialText(dc, text, maxWidth, defEllipsis="..."): - ellipsis = defEllipsis - base_w, h = dc.GetTextExtent(ellipsis) - - lenText = len(text) - drawntext = text - w, dummy = dc.GetTextExtent(text) - - while lenText > 0: - - if w + base_w <= maxWidth: - break - - w_c, h_c = dc.GetTextExtent(drawntext[-1]) - drawntext = drawntext[0:-1] - lenText -= 1 - w -= w_c - - while len(ellipsis) > 0 and w + base_w > maxWidth: - ellipsis = ellipsis[0:-1] - base_w, h = dc.GetTextExtent(ellipsis) - if len(text) > lenText: - return drawntext + ellipsis - else: - return text - - -def GetRoundBitmap(w, h, r): - maskColor = wx.Color(0, 0, 0) - shownColor = wx.Color(5, 5, 5) - b = wx.EmptyBitmap(w, h) - dc = wx.MemoryDC(b) - dc.SetBrush(wx.Brush(maskColor)) - dc.DrawRectangle(0, 0, w, h) - dc.SetBrush(wx.Brush(shownColor)) - dc.SetPen(wx.Pen(shownColor)) - dc.DrawRoundedRectangle(0, 0, w, h, r) - dc.SelectObject(wx.NullBitmap) - b.SetMaskColour(maskColor) - return b - - -def GetRoundShape(w, h, r): - return wx.RegionFromBitmap(GetRoundBitmap(w, h, r)) - - -def CreateDropShadowBitmap(bitmap, opacity): - img = wx.ImageFromBitmap(bitmap) - img = img.AdjustChannels(0, 0, 0, opacity) - return wx.BitmapFromImage(img) diff --git a/gui/utils/exportHtml.py b/gui/utils/exportHtml.py index ba075a35f..612df367e 100644 --- a/gui/utils/exportHtml.py +++ b/gui/utils/exportHtml.py @@ -63,11 +63,13 @@ class exportHtmlThread(threading.Thread): HTML = self.generateFullHTML(sMkt, sFit, dnaUrl) try: - FILE = open(settings.getPath(), "w") - FILE.write(HTML.encode('utf-8')) + FILE = open(settings.getPath(), "w", encoding='utf-8') + FILE.write(HTML) FILE.close() - except IOError: - print("Failed to write to " + settings.getPath()) + except IOError as ex: + print(("Failed to write to " + settings.getPath())) + pass + except Exception as ex: pass if self.callback: @@ -84,6 +86,7 @@ class exportHtmlThread(threading.Thread): Pyfa Fittings +