From 0caed19bb32fdd5b950ca7da803231ac72cc13be Mon Sep 17 00:00:00 2001 From: GliderGeek Date: Fri, 31 May 2019 19:58:32 +0200 Subject: [PATCH 1/4] use wx instead of tkinter for gui --- .gitignore | 6 +- PySoar/analysis.py | 18 ++- PySoar/main_pysoar.py | 252 +++++++++++++++++++++++-------------- PySoar/standalone/build.py | 131 ------------------- readme.md | 30 +++++ requirements.txt | 1 + 6 files changed, 198 insertions(+), 240 deletions(-) delete mode 100644 PySoar/standalone/build.py diff --git a/.gitignore b/.gitignore index ae74cc2..86750b2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,9 @@ hidden PySoar/standalone/*.zip PySoar/standalone/windows PySoar-manual.dvi -PySoar/*.pyc - +*.pyc +PySoar/bin env venv +.idea +*.DS_Store \ No newline at end of file diff --git a/PySoar/analysis.py b/PySoar/analysis.py index 6857858..5d5fcd2 100644 --- a/PySoar/analysis.py +++ b/PySoar/analysis.py @@ -2,9 +2,9 @@ from opensoar.competition.strepla import StreplaDaily from opensoar.competition.soaringspot import SoaringSpotDaily -from PySoar.exportClass import ExcelExport -from PySoar.performanceClass import Performance -from PySoar.settingsClass import Settings +from exportClass import ExcelExport +from performanceClass import Performance +from settingsClass import Settings settings = Settings() @@ -37,7 +37,7 @@ def download_progress(downloads, total_number_of_flights): return download_progress -def run(url, source, url_status=None, download_progress_label=None, analysis_progress_label=None): +def run(url, source, url_status=None, download_progress=None, analysis_progress=None): target_directory = os.path.join(settings.current_dir, 'bin') if source == 'cuc': daily_result_page = SoaringSpotDaily(url) @@ -46,15 +46,11 @@ def run(url, source, url_status=None, download_progress_label=None, analysis_pro else: raise ValueError('This source is not supported: %s' % source) - analysis_progress = get_analysis_progress_function(analysis_progress_label) - download_progress = get_download_progress_function(download_progress_label) - competition_day = daily_result_page.generate_competition_day(target_directory, download_progress) if url_status is not None and competition_day.task.multistart: - url_status.configure(text="Multiple starting points not implemented!", foreground='red') - url_status.update() - return + url_status(False, "Multiple starting points not implemented!") + return False classification_method = 'pysoar' failed_comp_ids = competition_day.analyse_flights(classification_method, analysis_progress, @@ -84,3 +80,5 @@ def run(url, source, url_status=None, download_progress_label=None, analysis_pro excel_sheet = ExcelExport(settings, competition_day.task.no_legs) excel_sheet.write_file(competition_day, settings, daily_result_page.igc_directory) + + return True diff --git a/PySoar/main_pysoar.py b/PySoar/main_pysoar.py index 7e5a884..fb33f9a 100644 --- a/PySoar/main_pysoar.py +++ b/PySoar/main_pysoar.py @@ -1,30 +1,31 @@ -from tkinter import Label, Tk, Button, Entry, W, E - import subprocess - import os - -from PySoar.analysis import run -from functools import partial -from PySoar.settingsClass import Settings import sys +import wx + +from analysis import run +from settingsClass import Settings + settings = Settings() -def url_format_correct(url_string): +def url_format_correct(url_string, status_handle): if 'soaringspot.com' in url_string: daily_results = _is_daily_soaringspot_url(url_string) elif 'strepla.de' in url_string: daily_results = _is_daily_strepla_url(url_string) else: - return 'Use SoaringSpot or Strepla URL' + status_handle('Wrong URL: Use SoaringSpot or Strepla URL') + return False if not daily_results: - return 'URL does not give daily results' + status_handle('Wrong URL: no daily results') + return False else: - return 'URL correct' + status_handle('URL correct') + return True def _is_daily_strepla_url(strepla_url): @@ -39,108 +40,165 @@ def _is_daily_soaringspot_url(soaringspot_url): return results == 'daily' -def go_bugform(url_entry, event): - import webbrowser +def get_url_source(url): + if 'soaringspot.com' in url: + return 'cuc' + elif 'strepla.de' in url: + return 'scs' + else: + raise ValueError('Unknown source') - form_url = settings.debug_form_url - versionID = settings.pysoar_version_formID - urlID = settings.competition_url_formID - pysoar_version = settings.version - comp_url = url_entry.get() +class MyFrame(wx.Frame): + def __init__(self): + super().__init__(parent=None, title='PySoar (%s)' % settings.version) + panel = wx.Panel(self) - complete_url = '%s?entry.%s=%s&entry.%s=%s' % (form_url, versionID, pysoar_version, urlID, comp_url) - webbrowser.open(complete_url) + complete_sizer = wx.BoxSizer(wx.VERTICAL) + my_sizer = wx.BoxSizer(wx.VERTICAL) -def open_analysis_file(): + text = wx.StaticText(panel, label="Fill in URL:") + my_sizer.Add(text, 0, wx.ALL | wx.CENTER, 5) + self.url_input = wx.TextCtrl(panel) + my_sizer.Add(self.url_input, 0, wx.ALL | wx.EXPAND, 5) - if sys.platform.startswith("darwin"): - subprocess.call(["open", settings.file_name]) - elif sys.platform.startswith('linux'): - subprocess.call(["xdg-open", settings.file_name]) - elif sys.platform.startswith('win32'): - os.startfile(settings.file_name) + self.status = wx.StaticText(panel, label="") + my_sizer.Add(self.status) + self.download_status = wx.StaticText(panel, label="") + my_sizer.Add(self.download_status) -def get_url_source(url): - if 'soaringspot.com' in url: - return 'cuc' - elif 'strepla.de' in url: - return 'scs' - else: - raise ValueError('Unknown source') + self.analyse_status = wx.StaticText(panel, label="") + my_sizer.Add(self.analyse_status) + + buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) + + my_btn = wx.Button(panel, label='Start analysis') + my_btn.Bind(wx.EVT_BUTTON, self.on_press) + buttons_sizer.Add(my_btn, 0, wx.EXPAND) + + self.open_spreadsheet = wx.Button(panel, label="Open") + self.open_spreadsheet.Disable() + self.open_spreadsheet.Bind(wx.EVT_BUTTON, self.open_analysis_file) + buttons_sizer.Add(self.open_spreadsheet) + + bug_report = wx.Button(panel, label='Report problem') + bug_report.Bind(wx.EVT_BUTTON, self.go_bugform) + buttons_sizer.Add(bug_report) + + complete_sizer.Add(my_sizer, 0, wx.ALL | wx.EXPAND, 5) + complete_sizer.Add(buttons_sizer, 0, wx.ALL | wx.CENTER, 5) + + panel.SetSizer(complete_sizer) + self.Show() + + def go_bugform(self, event): + import webbrowser + + form_url = settings.debug_form_url + versionID = settings.pysoar_version_formID + urlID = settings.competition_url_formID + pysoar_version = settings.version + + comp_url = self.url_input.GetValue() + + complete_url = '%s?entry.%s=%s&entry.%s=%s' % (form_url, versionID, pysoar_version, urlID, comp_url) + webbrowser.open(complete_url) + + def open_analysis_file(self, event): + if sys.platform.startswith("darwin"): + subprocess.call(["open", settings.file_name]) + elif sys.platform.startswith('linux'): + subprocess.call(["xdg-open", settings.file_name]) + elif sys.platform.startswith('win32'): + os.startfile(settings.file_name) + + def set_download_status(self, new, total=None): + if total is not None: + download_str = 'Downloaded: %s/%s' % (new, total) + else: + download_str = 'Downloaded: %s' % new + + self.download_status.SetLabel(download_str) + wx.Yield() + + def set_analyse_status(self, new, total=None): + if total is not None: + analysis_str = 'Analyzed: %s/%s' % (new, total) + else: + analysis_str = 'Analyzed: %s' % new + + self.analyse_status.SetLabel(analysis_str) + wx.Yield() + + def update_status(self, message): + self.status.SetLabel(message) + wx.Yield() + + def on_press(self, event): + self.open_spreadsheet.Disable() + self.download_status.SetLabel('') + self.analyse_status.SetLabel('') + + url = self.url_input.GetValue() + success = url_format_correct(url, self.update_status) + + if success: + success = run(url, get_url_source(url), self.update_status, self.set_download_status, self.set_analyse_status) + if success: + self.update_status("Analysis is complete.") + self.open_spreadsheet.Enable() def start_gui(): + app = wx.App() + frame = MyFrame() + app.MainLoop() + + +def run_commandline_program(sys_argv): + + def print_help(): + print('There are two options for running PySoar from the commandline:\n' + '1. `python main_python` for GUI\n' + '2. `python main_pysoar [url]` - where [url] is the daily competition url') + + def status_handle(message): + print(message) - root = Tk() - root.resizable(0, 0) + def download_handle(new, total=None): + if total is not None: + analysis_str = 'Downloaded: %s/%s' % (new, total) + else: + analysis_str = 'Downloaded: %s' % new + print(analysis_str) + + def analysis_handle(new, total=None): + if total is not None: + analysis_str = 'Analyzed: %s/%s' % (new, total) + else: + analysis_str = 'Analyzed: %s' % new + print(analysis_str) - def url_check(event): - checked_url = url_format_correct(url_entry.get()) - if checked_url == 'URL correct': - url_status.configure(text=checked_url, foreground='green') - url_status.update() - start_analysis() + if len(sys.argv) == 2: + if sys.argv[1] == '--help': + print_help() else: - url_status.configure(text=checked_url, foreground='red') - url_status.update() - - def start_analysis(): - - url = url_entry.get() - source = get_url_source(url) - run(url, source, url_status, download_progress_label, analysis_progress_label) - - analysis_done = Button(root, text='Excel produced', command=open_analysis_file) - analysis_done.grid(row=6, column=0, pady=5) - print("Analysis complete, excel produced") - - title = Label(root, text=' PySoar', font=("Helvetica", 30)) - url_accompanying_text = Label(root, text='Give Soaringspot/scoringStrepla URL:') - url_entry = Entry(root, width=60) - url_confirmation = Button(root, text='ok') - url_confirmation.bind('', url_check) - url_status = Label(root, text='', foreground='red') - download_progress_label = Label(root, text='Downloaded: ') - analysis_progress_label = Label(root, text='Analyzed: ') - report_problem = Button(root, text='Report problem') - report_problem.bind('', partial(go_bugform, url_entry)) - root.bind('', url_check) - version = Label(root, text='v %s' % settings.version) - - title.grid(row=0, column=0) - url_accompanying_text.grid(row=1, column=0, sticky=W) - url_entry.grid(row=2, column=0) - url_confirmation.grid(row=2, column=1) - url_status.grid(row=3, column=0) - download_progress_label.grid(row=4, column=0, pady=5) - analysis_progress_label.grid(row=5, column=0, pady=5) - report_problem.grid(row=7, column=0, sticky=W) - version.grid(row=7, column=1, sticky=E) - - root.mainloop() - - -def print_help(): - print('There are two options for running PySoar from the commandline:\n' - '1. `python main_python` for GUI\n' - '2. `python main_pysoar [url]` - where [url] is the daily competition url') - - -if len(sys.argv) == 1: - start_gui() -elif len(sys.argv) == 2: - if sys.argv[1] == '--help': + url = sys.argv[1] + correct = url_format_correct(url, status_handle) + if correct: + source = get_url_source(url) + run(url, source, status_handle, download_handle, analysis_handle) + else: print_help() + + +if __name__ == '__main__': + if len(sys.argv) == 1: + start_gui() else: - url = sys.argv[1] - if url_format_correct(url) == 'URL correct': - source = get_url_source(url) - run(url, source) -else: - print_help() + run_commandline_program(sys.argv) ############################# LICENSE ##################################### diff --git a/PySoar/standalone/build.py b/PySoar/standalone/build.py deleted file mode 100644 index 5cc4a0e..0000000 --- a/PySoar/standalone/build.py +++ /dev/null @@ -1,131 +0,0 @@ -import os -import platform -import shutil - -from PySoar.settingsClass import Settings -from tkinter import Label, Tk, Button, W, E -import subprocess -from contextlib import contextmanager - -settings = Settings() -version = settings.version - -manual_location = os.path.join("..", "..", "docs", "manual", "EN") -tex_filename = "PySoar-manual.tex" -pdf_filename = "PySoar-manual.pdf" -main_file = "main_pysoar" - -root = Tk() - - -@contextmanager -def cd(newdir): - prevdir = os.getcwd() - os.chdir(os.path.expanduser(newdir)) - try: - yield - finally: - os.chdir(prevdir) - - -def correct_version(): - print('Build process continues with version %s' % version) - - # # create pdf of manual - # with cd(manual_location): - # subprocess.call(["pdflatex", tex_filename]) - - if platform.system() == 'Darwin': - platform_name = "mac" - source_executable = main_file - executable = "PySoar_mac.app" - - subprocess.call(["pyinstaller", "--onefile", "--windowed", os.path.join("..", main_file + ".py")]) - - elif platform.system() == 'Windows': - platform_name = "windows" - source_executable = main_file + ".exe" - executable = "PySoar_windows.exe" - - subprocess.call(["pyinstaller", "-F", "--noconsole", os.path.join("..", main_file + ".py")]) - - elif platform.system() == "Linux": - platform_name = 'linux' - source_executable = main_file - executable = "PySoar_linux" - - subprocess.call(["pyinstaller", "-F", os.path.join("..", main_file + ".py")]) - - # create platform folder. if already exists, remove executable - if not os.path.exists(platform_name): - os.makedirs(platform_name) - else: - if platform.system() == "Darwin": - if os.path.exists(os.path.join(platform_name, executable + ".app")): - shutil.rmtree(os.path.join(platform_name, executable + ".app")) - else: - if os.path.exists(os.path.join(platform_name, executable)): - os.remove(os.path.join(platform_name, executable)) - - foldername = "%s_v%s" % (platform_name, version) - os.makedirs(foldername) - - # delete zip file if it exists - if os.path.exists("%s.zip" % foldername): - os.remove("%s.zip" % foldername) - - # copy executable to zip folder and platform_folder - if platform.system() == 'Darwin': - shutil.copytree(os.path.join("dist", source_executable + ".app"), os.path.join(foldername, executable)) - shutil.move(os.path.join("dist", source_executable + ".app"), os.path.join(platform_name, executable)) - else: # linux and windows - shutil.copy(os.path.join("dist", source_executable), os.path.join(foldername, executable)) - shutil.move(os.path.join("dist", source_executable), os.path.join(platform_name, executable)) - - # move pdf to zip folder and create zip file - # shutil.move(os.path.join(manual_location, pdf_filename), os.path.join(foldername, pdf_filename)) - shutil.make_archive(foldername, "zip", foldername) - - # remove unnecessary folders and files - shutil.rmtree(foldername) - shutil.rmtree('build') - shutil.rmtree('dist') - os.remove("main_pysoar.spec") - - root.quit() - - -def incorrect_version(): - print('Build process is cancelled because of incorrect version number') - root.quit() - - -version_title = Label(root, text='PySoar %s' % version, font=("Helvetica", 30)) -question = Label(root, text="Is this the correct version number?", font=("Helvetica", 12)) -stop = Button(root, command=incorrect_version, text='no') -go_on = Button(root, command=correct_version, text='yes') - -version_title.grid(row=0, column=0, columnspan=2) -question.grid(row=1, column=0, columnspan=2) -stop.grid(row=2, column=0, sticky=E) -go_on.grid(row=2, column=1, sticky=W) - -root.mainloop() - -############################# LICENSE ##################################### - -# PySoar - Automating gliding competition analysis -# Copyright (C) 2016 Matthijs Beekman -# -# This program 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. -# -# This program 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 this program. If not, see diff --git a/readme.md b/readme.md index ea77135..63abf60 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,36 @@ For development, the following steps need to be taken: pip install -r requirements.txt ``` +## Building an executable +This chapter explains how to create a pysoar executable + +Note: it is important to use the system python3.6 +- running inside virtualenv causes PyInstaller issues +- running python3.7 causes PyInstaller issues + +### Patch pygeodesy + +There is currently a problem with the pygeodesy library and pyinstaller for which pygeodesy needs to be cloned locally, patched and installed instead of taking the version from pypi. + +To patch `pygeodesy`: +- clone repo locally +- change `_ismodule` callable in `pygeodesy/__init__.py`. Replace its content with a single `pass` statement. + +For more info: https://github.com/mrJean1/PyGeodesy/issues/31 + +### Mac OS + +- inside pygeodesy patched repo: `pip3.6 install .` +- inside PySoar folder: `pip3.6 install -r requirements.txt` +- `PYGEODESY_PATH=$(python3.6 -c "import pygeodesy; print(pygeodesy.__path__[0])")` +- `pyinstaller --windowed --paths=$PYGEODESY_PATH main_pysoar.py` + +### Windows +- inside pygeodesy patched repo: `pip install .` +- inside PySoar folder: `pip install -r requirements.txt` +- `python3.6 -c "import pygeodesy; print(pygeodesy.__path__[0])"` +- `pyinstaller --windowed --onefile --paths=[INSERT_RESULT_PREVIOUS_LINE_HERE] main_pysoar.py` + ## License PySoar - Automating gliding competition analysis Copyright (C) 2016 Matthijs Beekman diff --git a/requirements.txt b/requirements.txt index cbceb0d..cf44e3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ numpy==1.15.4 pyinstaller==3.3.1 aerofiles==0.4.1 opensoar==0.1.3 +wxpython==4.0.6 From 387292b0687282e1d862680869b7c443e9750c37 Mon Sep 17 00:00:00 2001 From: GliderGeek Date: Fri, 31 May 2019 20:01:18 +0200 Subject: [PATCH 2/4] remove travis --- .travis.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bbd0ffb..0000000 --- a/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -dist: trusty -language: python - -addons: - apt: - packages: - - texlive - - texlive-latex-extra - - texlive-lang-english - - latex-xcolor - - texlive-science - -install: - - pip install --upgrade pip setuptools wheel - - pip install --only-binary=numpy,scipy numpy scipy - - pip install -r requirements.txt - -script: - - pyinstaller -F PySoar/main_pysoar.py - - cd docs/manual/EN/ && pdflatex PySoar-manual.tex && cd ../../../ From 63e0305ccf9de5532b26d2f4d02b5f4b667bda81 Mon Sep 17 00:00:00 2001 From: GliderGeek Date: Fri, 31 May 2019 20:03:55 +0200 Subject: [PATCH 3/4] small commit to trigger ci --- PySoar/analysis.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PySoar/analysis.py b/PySoar/analysis.py index 5d5fcd2..3702575 100644 --- a/PySoar/analysis.py +++ b/PySoar/analysis.py @@ -2,6 +2,7 @@ from opensoar.competition.strepla import StreplaDaily from opensoar.competition.soaringspot import SoaringSpotDaily + from exportClass import ExcelExport from performanceClass import Performance from settingsClass import Settings From 42034997c7003dc396fb0b2aaa3e8d9428ee057d Mon Sep 17 00:00:00 2001 From: GliderGeek Date: Fri, 31 May 2019 20:09:00 +0200 Subject: [PATCH 4/4] update changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e57b74c..9ae0e8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ Master +[unreleased] +- use wxpython instead of tkinter for GUI + v0.57.2 - try, except around performance creation - bump opensoar to v0.1.3