diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 00000000..65215e63 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,3 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=v*[0-9]*)$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..00a7b00c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fdace1f7..bfb09df2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -60,7 +60,7 @@ jobs: PYTRAINER_ALCHEMYURL: ${{ matrix.database_url }} TZ: Europe/Kaliningrad LC_TIME: C - run: xvfb-run python3 -Wall setup.py test + run: xvfb-run python3 -Wall -m unittest test_tox: runs-on: ubuntu-latest diff --git a/INSTALL b/INSTALL deleted file mode 100644 index 37ff3bc4..00000000 --- a/INSTALL +++ /dev/null @@ -1,43 +0,0 @@ -pytrainer basic installation -=========================== - -These are generic installation instructions to use with *.tar.gz files - -1.- Dependency Overview - -Here are the dependencies for pytrainer. Of course you must have a working environment -with proper shell configuration and typical GNU tools to uncompress (gunzip) and untar -(tar) files. - -1.1.- Packages - -Required Python packages and their versions can be seen in pyproject.toml. Additionally -the Python bindings for gobject-introspection and Cairo are required, as are the gir -bindings for GTK. For map functionality the gir bindings for WebKit2 are also required. - -- Only needed if correspondent plugin or extension is enabled: -gpsbabel == 1.3.5 ("GoogleEarth" and "Garmin via GPSBabel 1.3.5" aka "garmin_hr") -garmintools >= 0.10 ("Import from Garmin GPS device (via garmintools)" aka "garmintools_full" plugin) -wordpresslib (already distributed within pytrainer tarball, wordpress extension) -httplib2 >= 0.6.0 (wordpress extension) -SOAPpy >= 0.11.6 (dotclear extension) -GDAL (Elevation correction, via "gdal-python" or "python-gdal") -perl (garmin-fit plugin, tested with perl v5.16.2 on Fedora 18, see ticket #5) - -2.- Installation process - -Copy tarball file to a location where you have write and execution rights (e.g. /tmp or -your $HOME directory). Make sure executables are under your $PATH. - -$ tar -xzf pytrainer-X.Y.Z.tar.gz -$ cd pytrainer-X.Y.Z -$ sudo python setup.py install -$ pytrainer -i - -For more information about the process, please check http://docs.python.org/distutils/setupscript.html - -3.- USB access - -pytrainer can use gpsbabel (http://www.gpsbabel.org) to retrieve information from Garmin -devices. There are some problems regarding driver to access usb ports, please take a look -at http://www.gpsbabel.org/os/Linux_Hotplug.html diff --git a/README.md b/README.md index 8f4ee3f8..316f5740 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,54 @@ -pytrainer - Free your sports -================================================== +# pytrainer - Free your sports pytrainer is a desktop application for logging and graphing sporting -activities such as running or cycling sessions. Data can be -imported from GPS devices, files or input manually. Currently -pytrainer supports GPX, TCX, and FIT files. - -Source Repository Structure ---------------------------- -* **extensions** addons to extend pytrainer basic functionality -* **glade** user interface design -* **imports** files to parse different source formats -* **locale** localization files -* **man** source manpage -* **plugins** files to retrieve data from different sources -* **pytrainer** core files -* **schemas** schemas to support correct xml parsing -* **utils** localization shell script - -Installation from source tarball ------------------ -Copy tarball file to a location where you have write and execution rights (e.g. `/tmp` or your `$HOME` directory). Make sure executables are under your `$PATH`. - -`$ tar -xzf pytrainer-X.Y.Z.tar.gz` - -`$ cd pytrainer-X.Y.Z` - -`$ sudo python setup.py install` - -`$ pytrainer -i` - -For more information about the process, please check [Distutils documentation] (http://docs.python.org/distutils/setupscript.html) - -Further Resources ------------------ +activities such as running or cycling sessions. Data can be imported from GPS +devices, files or input manually. Currently pytrainer supports GPX, TCX, and +FIT files. + +## Installation +Most popular Linux distributions (Debian, Ubuntu, Fedora and so on) already +contain a pytrainer package, use that if available. If a package is not +available for your system you can install from source, see below. + +### Installation from source tarball +Copy tarball file to a location where you have write and execution rights (e.g. your `$HOME` directory). + +```shell +tar -xzf pytrainer-X.Y.Z.tar.gz +cd pytrainer-X.Y.Z +pip install ."[gui]" +pytrainer +``` + +### Installation into a venv (for development) +This installation method is very similar to the basic source install above. +For development it makes more sense to start from a git clone instead of a +tarball, and also makes more sense to install into a virtual Python +environment. + +```shell +git clone https://github.com/pytrainer/pytrainer.git +cd pytrainer +python3 -m venv .venv +.venv/bin/pip install -e ".[gui]" +.venv/bin/pytrainer +``` + +### Additional packages +For map functionality the GIR bindings for WebKit2 need to be installed. + +Certain plugins and extensions also need additional software: + +| Package | Plugin or extension | +|---------------|-----------------------------------------------------------------------------------| +| GPSBabel | Garmin via GPSBabel and Google Earth plugins (aka garmin-hr) | +| garmintools | Import from Garmin GPS device (Garmin via garmintools plugin aka garmintools_full)| +| Perl | Garmin FIT plugin | +| wordpresslib | Wordpress extension, already distributed within pytrainer tarball | +| httplib2 | Wordpress extension | +| SOAPpy | Dotclear extension | +| GDAL | Elevation correction extension, via gdal-python or python-gdal | + +## Further Resources * FAQ [https://github.com/pytrainer/pytrainer/wiki/FAQ](https://github.com/pytrainer/pytrainer/wiki/FAQ) * Distribution list: pytrainer-devel@lists.sourceforge.net * Development guide: [https://github.com/pytrainer/pytrainer/wiki/Development-guide](https://github.com/pytrainer/pytrainer/wiki/Development-guide) diff --git a/glade/profile.ui b/glade/profile.ui index 4947c77d..62110258 100644 --- a/glade/profile.ui +++ b/glade/profile.ui @@ -1848,51 +1848,6 @@ Continue? 2 - - - True - 0 - 5 - 5 - New Graph - - - 5 - 6 - - - - - --newgraph - True - True - False - True - - - - 1 - 2 - 5 - 6 - 10 - - - - - True - 0 - 5 - <small>Want to use experimental new approach to graphing?</small> - True - - - 2 - 3 - 5 - 6 - - diff --git a/glade/pytrainer.ui b/glade/pytrainer.ui index 2bea5b02..fd930f9c 100644 --- a/glade/pytrainer.ui +++ b/glade/pytrainer.ui @@ -1534,100 +1534,6 @@ True - - - True - 5 - 8 - - - True - True - True - Show graph display options - - - - True - gtk-preferences - - - - - False - False - 1 - - - - - True - - model2 - - - - - - False - 2 - - - - - True - Versus - - - False - False - 3 - - - - - True - - model3 - - - - - - False - False - 4 - - - - - - - - True - True - True - - - - True - gtk-fullscreen - - - - - False - False - end - 0 - - - - - False - 0 - - True @@ -1720,699 +1626,10 @@ - + True - True - True - - True - - - True - <small>Graph Display Options</small> - True - - - False - 0 - - - - - True - 0 - - - True - 10 - - - True - 5 - 3 - - - True - 1 - <small>Limits</small> - True - - - 1 - 2 - - - - - - True - <small>Min</small> - True - - - 1 - 2 - - - - - - True - <small>Max</small> - True - - - 2 - 3 - - - - - - True - True - - 4 - adjustment1 - - - - 1 - 2 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - True - - 4 - adjustment2 - - - - 2 - 3 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - 1 - <small>Color</small> - True - - - 2 - 3 - - - - - - True - True - True - 0 - #000000000000 - - - - 1 - 2 - 2 - 3 - GTK_SHRINK - GTK_SHRINK - - - - - True - 1 - <small>Weight</small> - True - - - 3 - 4 - - - - - - True - True - 2 - - adjustment3 - - - - 1 - 2 - 3 - 4 - GTK_SHRINK - GTK_SHRINK - - - - - True - Y1 - - - - - - - - True - False - 1 - <small>Smoothing</small> - True - - - 4 - 5 - - - - - - True - False - True - 2 - - adjustment4 - - - 1 - 2 - 4 - 5 - GTK_SHRINK - GTK_SHRINK - - - - - - - - - - - - - - - - - - - - - False - 2 - - - - - True - False - 0 - - - True - 10 - - - True - 5 - 3 - - - True - 1 - <small>Limits</small> - True - - - 1 - 2 - - - - - - True - <small>Min</small> - True - - - 1 - 2 - - - - - - True - <small>Max</small> - True - - - 2 - 3 - - - - - - True - True - - 4 - adjustment5 - - - 1 - 2 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - True - - 4 - adjustment6 - - - 2 - 3 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - Y2 - - - - - - - - True - 1 - <small>Color</small> - True - - - 2 - 3 - - - - - - True - 1 - <small>Weight</small> - True - - - 3 - 4 - - - - - - True - True - True - 0 - #000000000000 - - - 1 - 2 - 2 - 3 - GTK_SHRINK - GTK_SHRINK - - - - - True - True - 2 - - adjustment7 - - - 1 - 2 - 3 - 4 - GTK_SHRINK - GTK_SHRINK - - - - - True - False - 1 - <small>Smoothing</small> - True - - - 4 - 5 - - - - - - True - False - True - 2 - - adjustment8 - - - 1 - 2 - 4 - 5 - GTK_SHRINK - GTK_SHRINK - - - - - - - - - - - - - - - - - - - - - False - 3 - - - - - True - False - 0 - - - True - 12 - - - True - 4 - 3 - - - True - X - - - - - - - - True - 1 - <small>Limits</small> - True - - - 1 - 2 - - - - - - True - True - - 4 - adjustment9 - - - 1 - 2 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - True - - 4 - adjustment10 - - - 2 - 3 - 1 - 2 - GTK_SHRINK - GTK_SHRINK - - - - - True - <small>Min</small> - True - - - 1 - 2 - - - - - - True - <small>Max</small> - True - - - 2 - 3 - - - - - - True - 1 - <small>Distance</small> - True - - - 2 - 3 - - - - - - True - True - False - True - True - - - 1 - 2 - 2 - 3 - GTK_EXPAND - - - - - True - 1 - <small>Time</small> - True - - - 3 - 4 - - - - - - True - True - False - True - radiobuttonDistance - - - 1 - 2 - 3 - 4 - GTK_EXPAND - - - - - - - - - - - - - - - - - - False - False - 4 - - - - - True - - - True - - - 0 - - - - - Show Laps - True - True - False - True - - - - False - False - 5 - 1 - - - - - True - - - 2 - - - - - False - False - 5 - - - - - True - - - 6 - - - - - Reset Graph - True - True - True - True - 1 - - - - False - False - end - 1 - - - - - False - True - - - - - True - - - - - - True - True - + diff --git a/man/pytrainer.1 b/man/pytrainer.1 index 821a26f6..ff43bd56 100644 --- a/man/pytrainer.1 +++ b/man/pytrainer.1 @@ -21,10 +21,6 @@ enable logging at warning level enable logging at error level .IP --valid enable validation of files imported by plugins (details at info or debug logging level) - note plugin must support validation. -.IP --oldgraph -Turn off new graphing approach -.IP --newgraph -Deprecated option: turn on new graphing approach .IP --confdir=CONF_DIR Specify the directory where application configuration will be stored. .IP --logtype=TYPE diff --git a/plugins/garmin-tcxv2/garmin-tcxv2.py b/plugins/garmin-tcxv2/garmin-tcxv2.py index e50bae4b..af5e8bf8 100644 --- a/plugins/garmin-tcxv2/garmin-tcxv2.py +++ b/plugins/garmin-tcxv2/garmin-tcxv2.py @@ -21,107 +21,107 @@ import os from lxml import etree from pytrainer.lib.xmlUtils import XMLParser -from pytrainer.gui.dialogs import fileChooserDialog, guiFlush from pytrainer.core.activity import Activity from sqlalchemy.orm import exc class garminTCXv2(): - def __init__(self, parent = None, validate=False): - self.parent = parent - self.pytrainer_main = parent.pytrainer_main - self.tmpdir = self.pytrainer_main.profile.tmpdir - self.data_path = os.path.dirname(__file__) - self.validate = validate - self.sport = self.getConfValue("Force_sport_to") + def __init__(self, parent = None, validate=False): + self.parent = parent + self.pytrainer_main = parent.pytrainer_main + self.tmpdir = self.pytrainer_main.profile.tmpdir + self.data_path = os.path.dirname(__file__) + self.validate = validate + self.sport = self.getConfValue("Force_sport_to") - def getConfValue(self, confVar): - info = XMLParser(self.data_path+"/conf.xml") - code = info.getValue("pytrainer-plugin","plugincode") - plugindir = self.pytrainer_main.profile.plugindir - if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): - value = None - else: - info = XMLParser(plugindir+"/"+code+"/conf.xml") - value = info.getValue("pytrainer-plugin",confVar) - return value + def getConfValue(self, confVar): + info = XMLParser(self.data_path+"/conf.xml") + code = info.getValue("pytrainer-plugin","plugincode") + plugindir = self.pytrainer_main.profile.plugindir + if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): + value = None + else: + info = XMLParser(plugindir+"/"+code+"/conf.xml") + value = info.getValue("pytrainer-plugin",confVar) + return value - def run(self): - logging.debug(">>") - # able to select multiple files.... - selectedFiles = fileChooserDialog(title="Choose a TCX file (or files) to import", multiple=True).getFiles() - guiFlush() - importfiles = [] - if not selectedFiles: #Nothing selected - return importfiles - for filename in selectedFiles: #Multiple files - if self.valid_input_file(filename): #TODO could consolidate tree generation here - tree = etree.ElementTree(file=filename) - #Possibly multiple entries in file - activities = self.getActivities(tree) - for activity in activities: - if not self.inDatabase(activity): - sport = self.getSport(activity) - gpxfile = "%s/garmin-tcxv2-%d.gpx" % (self.tmpdir, len(importfiles)) - self.createGPXfile(gpxfile, activity) - importfiles.append((gpxfile, sport)) - else: - logging.debug("File:%s activity %d already in database. Skipping import." % (filename, activities.index(activity)) ) - else: - logging.info("File %s failed validation" % (filename)) - logging.debug("<<") - return importfiles + def run(self): + logging.debug(">>") + # able to select multiple files.... + from pytrainer.gui.dialogs import fileChooserDialog, guiFlush + selectedFiles = fileChooserDialog(title="Choose a TCX file (or files) to import", multiple=True).getFiles() + guiFlush() + importfiles = [] + if not selectedFiles: #Nothing selected + return importfiles + for filename in selectedFiles: #Multiple files + if self.valid_input_file(filename): #TODO could consolidate tree generation here + tree = etree.ElementTree(file=filename) + #Possibly multiple entries in file + activities = self.getActivities(tree) + for activity in activities: + if not self.inDatabase(activity): + sport = self.getSport(activity) + gpxfile = "%s/garmin-tcxv2-%d.gpx" % (self.tmpdir, len(importfiles)) + self.createGPXfile(gpxfile, activity) + importfiles.append((gpxfile, sport)) + else: + logging.debug("File:%s activity %d already in database. Skipping import." % (filename, activities.index(activity)) ) + else: + logging.info("File %s failed validation" % (filename)) + logging.debug("<<") + return importfiles - def valid_input_file(self, filename): - """ Function to validate input file if requested""" - if not self.validate: #not asked to validate - logging.debug("Not validating %s" % (filename) ) - return True - else: - xslfile = os.path.realpath(self.pytrainer_main.data_path)+ "/schemas/GarminTrainingCenterDatabase_v2.xsd" - from pytrainer.lib.xmlValidation import xmlValidator - validator = xmlValidator() - return validator.validateXSL(filename, xslfile) + def valid_input_file(self, filename): + """ Function to validate input file if requested""" + if not self.validate: #not asked to validate + logging.debug("Not validating %s" % (filename) ) + return True + else: + xslfile = os.path.realpath(self.pytrainer_main.data_path)+ "/schemas/GarminTrainingCenterDatabase_v2.xsd" + from pytrainer.lib.xmlValidation import xmlValidator + validator = xmlValidator() + return validator.validateXSL(filename, xslfile) - def getActivities(self, tree): - '''Function to return all activities in Garmin training center version 2 file - ''' - root = tree.getroot() - activities = root.findall(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Activity") - return activities + def getActivities(self, tree): + '''Function to return all activities in Garmin training center version 2 file + ''' + root = tree.getroot() + activities = root.findall(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Activity") + return activities - def inDatabase(self, activity): - #comparing date and start time (sport may have been changed in DB after import) - time = self.detailsFromTCX(activity) - try: - self.pytrainer_main.ddbb.session.query(Activity).filter(Activity.date_time_utc == time).one() - return True - except exc.NoResultFound: - return False + def inDatabase(self, activity): + #comparing date and start time (sport may have been changed in DB after import) + time = self.detailsFromTCX(activity) + try: + self.pytrainer_main.ddbb.session.query(Activity).filter(Activity.date_time_utc == time).one() + return True + except exc.NoResultFound: + return False - def getSport(self, activity): - #return sport from file or overide if present - if self.sport: - return self.sport - #sportElement = activity.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Activity") - try: - sport = activity.get("Sport") - except: - sport = "import" - return sport + def getSport(self, activity): + #return sport from file or overide if present + if self.sport: + return self.sport + #sportElement = activity.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Activity") + try: + sport = activity.get("Sport") + except: + sport = "import" + return sport - def detailsFromTCX(self, activity): - timeElement = activity.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Id") - if timeElement is None: - return None - else: - return timeElement.text + def detailsFromTCX(self, activity): + timeElement = activity.find(".//{http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2}Id") + if timeElement is None: + return None + else: + return timeElement.text - def createGPXfile(self, gpxfile, activity): - """ Function to transform a Garmin Training Center v2 Track to a valid GPX+ file - """ - xslt_doc = etree.parse(self.data_path+"/translate.xsl") - transform = etree.XSLT(xslt_doc) - #xml_doc = etree.parse(filename) - xml_doc = activity - result_tree = transform(xml_doc) - result_tree.write(gpxfile, xml_declaration=True, encoding='UTF-8') + def createGPXfile(self, gpxfile, activity): + """ Function to transform a Garmin Training Center v2 Track to a valid GPX+ file + """ + xslt_doc = etree.parse(self.data_path+"/translate.xsl") + transform = etree.XSLT(xslt_doc) + #xml_doc = etree.parse(filename) + xml_doc = activity + result_tree = transform(xml_doc) + result_tree.write(gpxfile, xml_declaration=True, encoding='UTF-8') diff --git a/pyproject.toml b/pyproject.toml index 79d835f8..8dae10da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ repository = "https://github.com/pytrainer" [project.optional-dependencies] mysql = ["mysqlclient"] postgresql = ["psycopg2"] +gui = ["pycairo", "pygobject"] [build-system] requires = ["setuptools>=45", "setuptools_scm>=8"] diff --git a/pytrainer/__main__.py b/pytrainer/__main__.py index af263e52..94dc17d2 100644 --- a/pytrainer/__main__.py +++ b/pytrainer/__main__.py @@ -17,7 +17,6 @@ def get_options(): log_level=logging.WARNING, validate=False, equip=False, - newgraph=True, conf_dir=None, log_type="file", ) @@ -58,18 +57,6 @@ def get_options(): dest="validate", help="enable validation of files imported by plugins (details at info or debug logging level) - note plugin must support validation.", ) - parser.add_option( - "--oldgraph", - action="store_false", - dest="newgraph", - help="Turn off new graphing approach", - ) - parser.add_option( - "--newgraph", - action="store_true", - dest="newgraph", - help="Deprecated Option: Turn on new graphing approach", - ) parser.add_option( "--confdir", dest="conf_dir", diff --git a/pytrainer/extensions/mapviewer.py b/pytrainer/extensions/mapviewer.py index fcec7c71..72801115 100644 --- a/pytrainer/extensions/mapviewer.py +++ b/pytrainer/extensions/mapviewer.py @@ -20,7 +20,7 @@ import gi try: - gi.require_version('WebKit2', '4.0') + gi.require_version('WebKit2', '4.1') except ValueError: pass diff --git a/pytrainer/gui/popupmenu.py b/pytrainer/gui/popupmenu.py index d080119a..2daa7be7 100644 --- a/pytrainer/gui/popupmenu.py +++ b/pytrainer/gui/popupmenu.py @@ -19,24 +19,31 @@ from gi.repository import Gtk + class PopupMenu(Gtk.Menu): def __init__(self, data_path = None, parent = None): super(PopupMenu, self).__init__() self.windowmain = parent - edit_record = Gtk.ImageMenuItem(Gtk.STOCK_EDIT) - edit_record.set_label(_("Edit Record")) + edit_record = Gtk.ImageMenuItem( + image=Gtk.Image.new_from_stock(Gtk.STOCK_EDIT, Gtk.IconSize.MENU), + label=_("Edit Record"), + ) edit_record.connect("activate", self.on_editrecord_activate) self.attach(edit_record, 0, 1, 0, 1) - show_graph = Gtk.ImageMenuItem(Gtk.STOCK_FIND) - show_graph.set_label(_("Show graph in classic view")) + show_graph = Gtk.ImageMenuItem( + image=Gtk.Image.new_from_stock(Gtk.STOCK_FIND, Gtk.IconSize.MENU), + label=_("Show graph in classic view"), + ) show_graph.connect("activate", self.on_showclassic_activate) self.attach(show_graph, 0, 1, 1, 2) self.attach(Gtk.SeparatorMenuItem(), 0, 1, 2, 3) - remove_record = Gtk.ImageMenuItem(Gtk.STOCK_DELETE) - remove_record.set_label(_("Delete")) + remove_record = Gtk.ImageMenuItem( + image=Gtk.Image.new_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.MENU), + label=_("Delete"), + ) remove_record.connect("activate", self.on_remove_activate) self.attach(remove_record, 0, 1, 3, 4) - + def show(self,id_record,event_button, time, date=None): self.id_record = id_record self.date = date diff --git a/pytrainer/gui/windowmain.py b/pytrainer/gui/windowmain.py index 938e4952..f995dee1 100644 --- a/pytrainer/gui/windowmain.py +++ b/pytrainer/gui/windowmain.py @@ -257,7 +257,7 @@ def runExtension(self,widget,widget2,extension): def createGraphs(self): logging.debug(">>") - self.drawarearecord = RecordGraph(self.record_graph_vbox, self.window1, self.record_combovalue, self.record_combovalue2, self.btnShowLaps, self.tableConfigY1, pytrainer_main=self.pytrainer_main) + self.drawarearecord = RecordGraph(self.record_graph_vbox, self.window1, pytrainer_main=self.pytrainer_main) self.drawareaheartrate = HeartRateGraph([self.heartrate_vbox, self.heartrate_vbox2, self.heartrate_vbox3], self.window1, pytrainer_main=self.pytrainer_main) self.day_vbox.hide() sports = self._sport_service.get_all_sports() @@ -550,175 +550,159 @@ def actualize_recordgraph(self,activity): self.record_list = activity.tracks self.laps = activity.laps if activity.gpx_file is not None: - if not self.pytrainer_main.startup_options.newgraph: - logging.debug("Using the original graphing") - logging.debug("Activity has GPX data") - #Show drop down boxes - self.hbox30.show() - #Hide new graph details - self.graph_data_hbox.hide() - self.hboxGraphOptions.hide() - #Enable graph - self.record_vbox.set_sensitive(1) - self.drawarearecord.drawgraph(self.record_list,self.laps) + self.graph_data_hbox.hide() + #Enable graph + self.record_vbox.set_sensitive(1) + #Create a frame showing data available for graphing + #Remove existing frames + for child in self.graph_data_hbox.get_children(): + if isinstance(child, Gtk.Frame): + self.graph_data_hbox.remove(child) + #Build frames and vboxs to hold checkbuttons + xFrame = Gtk.Frame(label=_("Show on X Axis")) + y1Frame = Gtk.Frame(label=_("Show on Y1 Axis")) + y2Frame = Gtk.Frame(label=_("Show on Y2 Axis")) + limitsFrame = Gtk.Frame(label=_("Axis Limits")) + xvbox = Gtk.VBox() + y1box = Gtk.Table() + y2box = Gtk.Table() + limitsbox = Gtk.Table() + #Populate X axis data + #Create x axis items + xdistancebutton = Gtk.RadioButton(label=_("Distance")) + xtimebutton = Gtk.RadioButton(group=xdistancebutton, label=_("Time")) + xlapsbutton = Gtk.CheckButton(label=_("Laps")) + y1gridbutton = Gtk.CheckButton(label=_("Left Axis Grid")) + y2gridbutton = Gtk.CheckButton(label=_("Right Axis Grid")) + xgridbutton = Gtk.CheckButton(label=_("X Axis Grid")) + #Set state of buttons + if activity.x_axis == "distance": + xdistancebutton.set_active(True) + elif activity.x_axis == "time": + xtimebutton.set_active(True) + xlapsbutton.set_active(activity.show_laps) + y1gridbutton.set_active(activity.y1_grid) + y2gridbutton.set_active(activity.y2_grid) + xgridbutton.set_active(activity.x_grid) + #Connect handlers to buttons + xdistancebutton.connect("toggled", self.on_xaxischange, "distance", activity) + xtimebutton.connect("toggled", self.on_xaxischange, "time", activity) + xlapsbutton.connect("toggled", self.on_xlapschange, activity) + y1gridbutton.connect("toggled", self.on_gridchange, "y1", activity) + y2gridbutton.connect("toggled", self.on_gridchange, "y2", activity) + xgridbutton.connect("toggled", self.on_gridchange, "x", activity) + #Add buttons to frame + xvbox.pack_start(xdistancebutton, False, True, 0) + xvbox.pack_start(xtimebutton, False, True, 0) + xvbox.pack_start(xlapsbutton, False, True, 0) + xvbox.pack_start(y1gridbutton, False, True, 0) + xvbox.pack_start(y2gridbutton, False, True, 0) + xvbox.pack_start(xgridbutton, False, True, 0) + xFrame.add(xvbox) + + #Populate axis limits frame + #TODO Need to change these to editable objects and redraw graphs if changed.... + #Create labels etc + minlabel = Gtk.Label(label="Min") + minlabel.set_use_markup(True) + maxlabel = Gtk.Label(label="Max") + maxlabel.set_use_markup(True) + xlimlabel = Gtk.Label(label="X") + limits = {} + xminlabel = Gtk.Entry(max_length=10) + xmaxlabel = Gtk.Entry(max_length=10) + limits['xminlabel'] = xminlabel + limits['xmaxlabel'] = xmaxlabel + xminlabel.set_width_chars(5) + xminlabel.set_alignment(1.0) + xmaxlabel.set_width_chars(5) + xmaxlabel.set_alignment(1.0) + y1limlabel = Gtk.Label(label="Y1") + y1minlabel = Gtk.Entry(max_length=10) + y1maxlabel = Gtk.Entry(max_length=10) + limits['y1minlabel'] = y1minlabel + limits['y1maxlabel'] = y1maxlabel + y1minlabel.set_width_chars(5) + y1minlabel.set_alignment(1.0) + y1maxlabel.set_width_chars(5) + y1maxlabel.set_alignment(1.0) + y2limlabel = Gtk.Label(label="Y2") + y2minlabel = Gtk.Entry(max_length=10) + y2maxlabel = Gtk.Entry(max_length=10) + limits['y2minlabel'] = y2minlabel + limits['y2maxlabel'] = y2maxlabel + y2minlabel.set_width_chars(5) + y2minlabel.set_alignment(1.0) + y2maxlabel.set_width_chars(5) + y2maxlabel.set_alignment(1.0) + resetbutton = Gtk.Button(_('Reset Limits')) + resetbutton.connect("clicked", self.on_setlimits, activity, True, None) + setbutton = Gtk.Button(_('Set Limits')) + setbutton.connect("clicked", self.on_setlimits, activity, False, limits) + #Add labels etc to table + limitsbox.attach(minlabel, 1, 2, 0, 1, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(maxlabel, 2, 3, 0, 1, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(xlimlabel, 0, 1, 1, 2, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(xminlabel, 1, 2, 1, 2, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(xmaxlabel, 2, 3, 1, 2, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(y1limlabel, 0, 1, 2, 3, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(y1minlabel, 1, 2, 2, 3, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(y1maxlabel, 2, 3, 2, 3, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(y2limlabel, 0, 1, 3, 4, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(y2minlabel, 1, 2, 3, 4, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(y2maxlabel, 2, 3, 3, 4, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) + limitsbox.attach(setbutton, 0, 3, 4, 5, yoptions=Gtk.AttachOptions.SHRINK) + limitsbox.attach(resetbutton, 0, 3, 5, 6, yoptions=Gtk.AttachOptions.SHRINK) + limitsFrame.add(limitsbox) + + row = 0 + if activity.x_axis == "distance": + data = activity.distance_data + elif activity.x_axis == "time": + data = activity.time_data else: - #Still just test code.... - logging.debug("Using the new TEST graphing approach") - #Hide current drop down boxes - self.hbox30.hide() - self.graph_data_hbox.hide() - #Enable graph - self.record_vbox.set_sensitive(1) - #Create a frame showing data available for graphing - #Remove existing frames - for child in self.graph_data_hbox.get_children(): - if isinstance(child, Gtk.Frame): - self.graph_data_hbox.remove(child) - #Build frames and vboxs to hold checkbuttons - xFrame = Gtk.Frame(label=_("Show on X Axis")) - y1Frame = Gtk.Frame(label=_("Show on Y1 Axis")) - y2Frame = Gtk.Frame(label=_("Show on Y2 Axis")) - limitsFrame = Gtk.Frame(label=_("Axis Limits")) - xvbox = Gtk.VBox() - y1box = Gtk.Table() - y2box = Gtk.Table() - limitsbox = Gtk.Table() - #Populate X axis data - #Create x axis items - xdistancebutton = Gtk.RadioButton(label=_("Distance")) - xtimebutton = Gtk.RadioButton(group=xdistancebutton, label=_("Time")) - xlapsbutton = Gtk.CheckButton(label=_("Laps")) - y1gridbutton = Gtk.CheckButton(label=_("Left Axis Grid")) - y2gridbutton = Gtk.CheckButton(label=_("Right Axis Grid")) - xgridbutton = Gtk.CheckButton(label=_("X Axis Grid")) - #Set state of buttons - if activity.x_axis == "distance": - xdistancebutton.set_active(True) - elif activity.x_axis == "time": - xtimebutton.set_active(True) - xlapsbutton.set_active(activity.show_laps) - y1gridbutton.set_active(activity.y1_grid) - y2gridbutton.set_active(activity.y2_grid) - xgridbutton.set_active(activity.x_grid) - #Connect handlers to buttons - xdistancebutton.connect("toggled", self.on_xaxischange, "distance", activity) - xtimebutton.connect("toggled", self.on_xaxischange, "time", activity) - xlapsbutton.connect("toggled", self.on_xlapschange, activity) - y1gridbutton.connect("toggled", self.on_gridchange, "y1", activity) - y2gridbutton.connect("toggled", self.on_gridchange, "y2", activity) - xgridbutton.connect("toggled", self.on_gridchange, "x", activity) - #Add buttons to frame - xvbox.pack_start(xdistancebutton, False, True, 0) - xvbox.pack_start(xtimebutton, False, True, 0) - xvbox.pack_start(xlapsbutton, False, True, 0) - xvbox.pack_start(y1gridbutton, False, True, 0) - xvbox.pack_start(y2gridbutton, False, True, 0) - xvbox.pack_start(xgridbutton, False, True, 0) - xFrame.add(xvbox) - - #Populate axis limits frame - #TODO Need to change these to editable objects and redraw graphs if changed.... - #Create labels etc - minlabel = Gtk.Label(label="Min") - minlabel.set_use_markup(True) - maxlabel = Gtk.Label(label="Max") - maxlabel.set_use_markup(True) - xlimlabel = Gtk.Label(label="X") - limits = {} - xminlabel = Gtk.Entry(max_length=10) - xmaxlabel = Gtk.Entry(max_length=10) - limits['xminlabel'] = xminlabel - limits['xmaxlabel'] = xmaxlabel - xminlabel.set_width_chars(5) - xminlabel.set_alignment(1.0) - xmaxlabel.set_width_chars(5) - xmaxlabel.set_alignment(1.0) - y1limlabel = Gtk.Label(label="Y1") - y1minlabel = Gtk.Entry(max_length=10) - y1maxlabel = Gtk.Entry(max_length=10) - limits['y1minlabel'] = y1minlabel - limits['y1maxlabel'] = y1maxlabel - y1minlabel.set_width_chars(5) - y1minlabel.set_alignment(1.0) - y1maxlabel.set_width_chars(5) - y1maxlabel.set_alignment(1.0) - y2limlabel = Gtk.Label(label="Y2") - y2minlabel = Gtk.Entry(max_length=10) - y2maxlabel = Gtk.Entry(max_length=10) - limits['y2minlabel'] = y2minlabel - limits['y2maxlabel'] = y2maxlabel - y2minlabel.set_width_chars(5) - y2minlabel.set_alignment(1.0) - y2maxlabel.set_width_chars(5) - y2maxlabel.set_alignment(1.0) - resetbutton = Gtk.Button(_('Reset Limits')) - resetbutton.connect("clicked", self.on_setlimits, activity, True, None) - setbutton = Gtk.Button(_('Set Limits')) - setbutton.connect("clicked", self.on_setlimits, activity, False, limits) - #Add labels etc to table - limitsbox.attach(minlabel, 1, 2, 0, 1, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(maxlabel, 2, 3, 0, 1, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(xlimlabel, 0, 1, 1, 2, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(xminlabel, 1, 2, 1, 2, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(xmaxlabel, 2, 3, 1, 2, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(y1limlabel, 0, 1, 2, 3, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(y1minlabel, 1, 2, 2, 3, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(y1maxlabel, 2, 3, 2, 3, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(y2limlabel, 0, 1, 3, 4, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(y2minlabel, 1, 2, 3, 4, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(y2maxlabel, 2, 3, 3, 4, yoptions=Gtk.AttachOptions.SHRINK, xpadding=5) - limitsbox.attach(setbutton, 0, 3, 4, 5, yoptions=Gtk.AttachOptions.SHRINK) - limitsbox.attach(resetbutton, 0, 3, 5, 6, yoptions=Gtk.AttachOptions.SHRINK) - limitsFrame.add(limitsbox) - - row = 0 - if activity.x_axis == "distance": - data = activity.distance_data - elif activity.x_axis == "time": - data = activity.time_data + logging.error("x axis is unknown") + #Populate Y axis data + for graphdata in sorted(data.keys()): + #First Y axis... + #Create button + y1button = Gtk.CheckButton(label=data[graphdata].title) + #Make button active if this data is to be displayed... + y1button.set_active(data[graphdata].show_on_y1) + #Connect handler for toggle state changes + y1button.connect("toggled", self.on_y1change, y1box, graphdata, activity) + #Attach button to container + y1box.attach(y1button, 0, 1, row, row+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) + if data[graphdata].linecolor is not None: + #Create a color choser + y1color = Gtk.ColorButton() + #Set color to current activity color + _color = Gdk.color_parse(data[graphdata].linecolor) + y1color.set_color(_color) + #Connect handler for color state changes + y1color.connect("color-set", self.on_y1colorchange, y1box, graphdata, activity) + #Attach to container + y1box.attach(y1color, 1, 2, row, row+1) else: - logging.error("x axis is unknown") - #Populate Y axis data - for graphdata in sorted(data.keys()): - #First Y axis... - #Create button - y1button = Gtk.CheckButton(label=data[graphdata].title) - #Make button active if this data is to be displayed... - y1button.set_active(data[graphdata].show_on_y1) - #Connect handler for toggle state changes - y1button.connect("toggled", self.on_y1change, y1box, graphdata, activity) - #Attach button to container - y1box.attach(y1button, 0, 1, row, row+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) - if data[graphdata].linecolor is not None: - #Create a color choser - y1color = Gtk.ColorButton() - #Set color to current activity color - _color = Gdk.color_parse(data[graphdata].linecolor) - y1color.set_color(_color) - #Connect handler for color state changes - y1color.connect("color-set", self.on_y1colorchange, y1box, graphdata, activity) - #Attach to container - y1box.attach(y1color, 1, 2, row, row+1) - else: - blanklabel = Gtk.Label(label="") - y1box.attach(blanklabel, 1, 2, row, row+1) - - #Second Y axis - y2button = Gtk.CheckButton(label=data[graphdata].title) - y2button.set_active(data[graphdata].show_on_y2) - y2button.connect("toggled", self.on_y2change, y2box, graphdata, activity) - y2box.attach(y2button, 0, 1, row, row+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) - if data[graphdata].y2linecolor is not None: - y2color = Gtk.ColorButton() - _color = Gdk.color_parse(data[graphdata].y2linecolor) - y2color.set_color(_color) - y2color.connect("color-set", self.on_y2colorchange, y2box, graphdata, activity) - #Attach to container - y2box.attach(y2color, 1, 2, row, row+1) - else: - blanklabel = Gtk.Label(label="") - y2box.attach(blanklabel, 1, 2, row, row+1) - row += 1 + blanklabel = Gtk.Label(label="") + y1box.attach(blanklabel, 1, 2, row, row+1) + + #Second Y axis + y2button = Gtk.CheckButton(label=data[graphdata].title) + y2button.set_active(data[graphdata].show_on_y2) + y2button.connect("toggled", self.on_y2change, y2box, graphdata, activity) + y2box.attach(y2button, 0, 1, row, row+1, xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL) + if data[graphdata].y2linecolor is not None: + y2color = Gtk.ColorButton() + _color = Gdk.color_parse(data[graphdata].y2linecolor) + y2color.set_color(_color) + y2color.connect("color-set", self.on_y2colorchange, y2box, graphdata, activity) + #Attach to container + y2box.attach(y2color, 1, 2, row, row+1) + else: + blanklabel = Gtk.Label(label="") + y2box.attach(blanklabel, 1, 2, row, row+1) + row += 1 y1Frame.add(y1box) y2Frame.add(y2box) @@ -759,8 +743,6 @@ def actualize_recordgraph(self,activity): self.buttonGraphHideOptions.show() else: logging.debug("Activity has no GPX data") - #Show drop down boxes - self.hbox30.show() #Hide new graph details self.graph_data_hbox.hide() self.hboxGraphOptions.hide() @@ -1389,7 +1371,7 @@ def create_menulist(self,columns): if 'visible' in column_dict and not column_dict['visible']: pass else: - item = Gtk.CheckMenuItem(column_dict['name']) + item = Gtk.CheckMenuItem(label=column_dict['name']) #self.lsa_searchoption.append_text(name) item.connect("button_press_event", self.on_menulistview_activate, i) self.menulistviewOptions.append(item) @@ -1586,18 +1568,6 @@ def on_window1_configure_event(self, widget, event): #print event # resize event self.size = self.window1.get_size() - def on_buttonShowOptions_clicked(self, widget): - position_set = self.hpaned1.get_property('position-set') - if position_set: - #Currently not showing options - show them - self.hpaned1.set_property('position-set', False) - self.buttonShowOptions.set_tooltip_text(_('Hide graph display options') ) - else: - #Hide options - self.hpaned1.set_position(0) - self.buttonShowOptions.set_tooltip_text(_('Show graph display options') ) - logging.debug('Position set: %s', self.hpaned1.get_property('position-set')) - def on_buttonGraphHideOptions_clicked(self, widget): logging.debug('on_buttonGraphHideOptions_clicked') self.buttonGraphHideOptions.hide() @@ -1632,39 +1602,6 @@ def on_comboMapLineType_changed(self, widget): logging.debug('on_comboMapLineType_changed %s = %s', widget.get_name(), widget.get_active()) self.parent.refreshMapView() - def on_hpaned1_move_handle(self, widget): - logging.debug("Handler %s", widget) - - def on_spinbuttonY1_value_changed(self, widget): - y1min = self.spinbuttonY1Min.get_value() - y1max = self.spinbuttonY1Max.get_value() - #Check to see if the min and max have the same... - if y1min == y1max: - if widget.get_name() == "spinbuttonY1Min": #User was changing the min spinbutton, so move max up - y1max += 1 - else: #Move min down - y1min -= 1 - self.y1_limits=(y1min, y1max) - self.zoom_graph(y1limits=self.y1_limits, y1color=self.y1_color, y1_linewidth=self.y1_linewidth) - - def on_buttonResetGraph_clicked(self, widget): - #self.zoom_graph() - #Reset stored values - self.y1_limits = None - self.y1_color = None - self.y1_linewidth = 1 - self.zoom_graph() - - def on_colorbuttonY1LineColor_color_set(self, widget): - y1color = widget.get_color() - cs = y1color.to_string() - self.y1_color = cs[0:3] + cs[5:7] + cs[9:11] - self.drawarearecord.drawgraph(self.record_list,self.laps, y1limits=self.y1_limits, y1color=self.y1_color, y1_linewidth=self.y1_linewidth) - - def on_spinbuttonY1LineWeight_value_changed(self, widget): - self.y1_linewidth = self.spinbuttonY1LineWeight.get_value_as_int() - self.drawarearecord.drawgraph(self.record_list,self.laps, y1limits=self.y1_limits, y1color=self.y1_color, y1_linewidth=self.y1_linewidth) - def on_edit_clicked(self,widget): selected,iter = self.recordTreeView.get_selection().get_selected() id_record = selected.get_value(iter,0) @@ -1742,10 +1679,6 @@ def on_hidemap_clicked(self,widget): self.maparea.hide() self.infoarea.show() - def on_btnShowLaps_toggled(self,widget): - logging.debug("--") - self.parent.refreshGraphView(self.selected_view) - def on_day_combovalue_changed(self,widget): logging.debug("--") self.parent.refreshGraphView(self.selected_view) diff --git a/pytrainer/gui/windowprofile.py b/pytrainer/gui/windowprofile.py index 67c06285..30a4ff28 100644 --- a/pytrainer/gui/windowprofile.py +++ b/pytrainer/gui/windowprofile.py @@ -224,13 +224,7 @@ def init_params_tab(self): self.checkbuttonValidate.set_active(True) else: self.checkbuttonValidate.set_active(False) - - #Show if new graph activated - if self.pytrainer_main.startup_options.newgraph: - self.checkbuttonNewGraph.set_active(True) - else: - self.checkbuttonNewGraph.set_active(False) - + def on_comboboxLogLevel_changed(self, widget): active = self.comboboxLogLevel.get_active() if active == 1: @@ -254,15 +248,7 @@ def on_checkbuttonValidate_toggled(self, widget): else: logging.debug("Validate deactivated") self.pytrainer_main.startup_options.validate = False - - def on_checkbuttonNewGraph_toggled(self, widget): - if self.checkbuttonNewGraph.get_active(): - logging.debug("NewGraph activated") - self.pytrainer_main.startup_options.newgraph = True - else: - logging.debug("NewGraph deactivated") - self.pytrainer_main.startup_options.newgraph = False - + def on_accept_clicked(self,widget): self.saveOptions() self.close_window() diff --git a/pytrainer/platform.py b/pytrainer/platform.py index 2fd4637b..cc2e57c6 100644 --- a/pytrainer/platform.py +++ b/pytrainer/platform.py @@ -58,7 +58,7 @@ def _get_data_path(self): def _checkpath(fname): return os.path.exists(os.path.join(base_path, fname)) - if all(map(_checkpath, ("INSTALL", "setup.py", "pytrainer/main.py", "locale"))): + if all(map(_checkpath, ("pyproject.toml", "pytrainer/main.py", "locale"))): return base_path + "/" else: return os.path.join(sys.prefix, "share/pytrainer/") diff --git a/pytrainer/plugins.py b/pytrainer/plugins.py index b42c5ace..5dfdc2f4 100644 --- a/pytrainer/plugins.py +++ b/pytrainer/plugins.py @@ -22,111 +22,111 @@ from .lib.xmlUtils import XMLParser -from .gui.windowplugins import WindowPlugins class Plugins: - def __init__(self, data_path = None, parent = None): - self.data_path=data_path - self.pytrainer_main = parent - - def getActivePlugins(self): - retorno = [] - for plugin in self.getPluginsList(): - if self.getPluginInfo(plugin[0])[2] == "1": - retorno.append(plugin[0]) - return retorno - - def loadPlugin(self,plugin): - logging.debug('>>') - info = XMLParser(plugin+"/conf.xml") - button = info.getValue("pytrainer-plugin","pluginbutton") - name = info.getValue("pytrainer-plugin","name") - logging.info('Loading plugin '+name) - logging.debug('<<') - return button,plugin - - def importClass(self, pathPlugin): - logging.debug('>>') - info = XMLParser(pathPlugin+"/conf.xml") - #import plugin - plugin_dir = os.path.realpath(pathPlugin) - plugin_filename = info.getValue("pytrainer-plugin","executable") - plugin_classname = info.getValue("pytrainer-plugin","plugincode") - logging.debug("Plugin Filename: " + plugin_filename ) - logging.debug("Plugin Classname: " + plugin_classname) - sys.path.insert(0, plugin_dir) - module = __import__(plugin_filename) - pluginMain = getattr(module, plugin_classname) - logging.debug('<<') - #Only validate files if enabled at startup - if self.pytrainer_main.startup_options.validate: - validate_inputfiles=True - logging.info("validating plugin input files enabled") - else: - validate_inputfiles=False - return pluginMain(self, validate_inputfiles) - - def managePlugins(self): - pluginsList = self.getPluginsList() - windowplugins = WindowPlugins(self.data_path, self) - windowplugins.setList(pluginsList) - windowplugins.run() - - def getPluginsList(self): - pluginsdir = self.data_path+"/plugins" - pluginsList = [] - for plugin in os.listdir(pluginsdir): - pluginxmlfile = pluginsdir+"/"+plugin+"/conf.xml" - if os.path.isfile(pluginxmlfile): - plugininfo = XMLParser(pluginxmlfile) - name = plugininfo.getValue("pytrainer-plugin","name") - description = plugininfo.getValue("pytrainer-plugin","description") - pluginsList.append((pluginsdir+"/"+plugin,name,description)) - pluginsList.sort() - return pluginsList - - def getPluginInfo(self,pathPlugin): - info = XMLParser(pathPlugin+"/conf.xml") - name = info.getValue("pytrainer-plugin","name") - description = info.getValue("pytrainer-plugin","description") - code = info.getValue("pytrainer-plugin","plugincode") - plugindir = self.pytrainer_main.profile.plugindir - if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): - status = 0 - else: - info = XMLParser(plugindir+"/"+code+"/conf.xml") - status = info.getValue("pytrainer-plugin","status") - return name,description,status - - def getPluginConfParams(self,pathPlugin): - info = XMLParser(pathPlugin+"/conf.xml") - code = info.getValue("pytrainer-plugin","plugincode") - plugindir = self.pytrainer_main.profile.plugindir - if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): - params = info.getAllValues("conf-values") - params.append(("status","0")) - else: - prefs = info.getAllValues("conf-values") - prefs.append(("status","0")) - info = XMLParser(plugindir+"/"+code+"/conf.xml") - params = [] - for pref in prefs: - params.append((pref[0],info.getValue("pytrainer-plugin",pref[0]))) - return params - - def setPluginConfParams(self,pathPlugin,savedOptions): - info = XMLParser(pathPlugin+"/conf.xml") - code = info.getValue("pytrainer-plugin","plugincode") - plugindir = self.pytrainer_main.profile.plugindir+"/"+code - if not os.path.isdir(plugindir): - os.mkdir(plugindir) - if not os.path.isfile(plugindir+"/conf.xml"): - if ("status", "1") not in savedOptions: - savedOptions.append(("status","0")) - info = XMLParser(plugindir+"/conf.xml") - info.createXMLFile("pytrainer-plugin",savedOptions) - - def getCodeConfValue(self,code,value): - plugindir = self.pytrainer_main.profile.plugindir - info = XMLParser(plugindir+"/"+code+"/conf.xml") - return info.getValue("pytrainer-plugin",value) + def __init__(self, data_path = None, parent = None): + self.data_path=data_path + self.pytrainer_main = parent + + def getActivePlugins(self): + retorno = [] + for plugin in self.getPluginsList(): + if self.getPluginInfo(plugin[0])[2] == "1": + retorno.append(plugin[0]) + return retorno + + def loadPlugin(self,plugin): + logging.debug('>>') + info = XMLParser(plugin+"/conf.xml") + button = info.getValue("pytrainer-plugin","pluginbutton") + name = info.getValue("pytrainer-plugin","name") + logging.info('Loading plugin '+name) + logging.debug('<<') + return button,plugin + + def importClass(self, pathPlugin): + logging.debug('>>') + info = XMLParser(pathPlugin+"/conf.xml") + #import plugin + plugin_dir = os.path.realpath(pathPlugin) + plugin_filename = info.getValue("pytrainer-plugin","executable") + plugin_classname = info.getValue("pytrainer-plugin","plugincode") + logging.debug("Plugin Filename: " + plugin_filename ) + logging.debug("Plugin Classname: " + plugin_classname) + sys.path.insert(0, plugin_dir) + module = __import__(plugin_filename) + pluginMain = getattr(module, plugin_classname) + logging.debug('<<') + #Only validate files if enabled at startup + if self.pytrainer_main.startup_options.validate: + validate_inputfiles=True + logging.info("validating plugin input files enabled") + else: + validate_inputfiles=False + return pluginMain(self, validate_inputfiles) + + def managePlugins(self): + pluginsList = self.getPluginsList() + from .gui.windowplugins import WindowPlugins + windowplugins = WindowPlugins(self.data_path, self) + windowplugins.setList(pluginsList) + windowplugins.run() + + def getPluginsList(self): + pluginsdir = self.data_path+"/plugins" + pluginsList = [] + for plugin in os.listdir(pluginsdir): + pluginxmlfile = pluginsdir+"/"+plugin+"/conf.xml" + if os.path.isfile(pluginxmlfile): + plugininfo = XMLParser(pluginxmlfile) + name = plugininfo.getValue("pytrainer-plugin","name") + description = plugininfo.getValue("pytrainer-plugin","description") + pluginsList.append((pluginsdir+"/"+plugin,name,description)) + pluginsList.sort() + return pluginsList + + def getPluginInfo(self,pathPlugin): + info = XMLParser(pathPlugin+"/conf.xml") + name = info.getValue("pytrainer-plugin","name") + description = info.getValue("pytrainer-plugin","description") + code = info.getValue("pytrainer-plugin","plugincode") + plugindir = self.pytrainer_main.profile.plugindir + if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): + status = 0 + else: + info = XMLParser(plugindir+"/"+code+"/conf.xml") + status = info.getValue("pytrainer-plugin","status") + return name,description,status + + def getPluginConfParams(self,pathPlugin): + info = XMLParser(pathPlugin+"/conf.xml") + code = info.getValue("pytrainer-plugin","plugincode") + plugindir = self.pytrainer_main.profile.plugindir + if not os.path.isfile(plugindir+"/"+code+"/conf.xml"): + params = info.getAllValues("conf-values") + params.append(("status","0")) + else: + prefs = info.getAllValues("conf-values") + prefs.append(("status","0")) + info = XMLParser(plugindir+"/"+code+"/conf.xml") + params = [] + for pref in prefs: + params.append((pref[0],info.getValue("pytrainer-plugin",pref[0]))) + return params + + def setPluginConfParams(self,pathPlugin,savedOptions): + info = XMLParser(pathPlugin+"/conf.xml") + code = info.getValue("pytrainer-plugin","plugincode") + plugindir = self.pytrainer_main.profile.plugindir+"/"+code + if not os.path.isdir(plugindir): + os.mkdir(plugindir) + if not os.path.isfile(plugindir+"/conf.xml"): + if ("status", "1") not in savedOptions: + savedOptions.append(("status","0")) + info = XMLParser(plugindir+"/conf.xml") + info.createXMLFile("pytrainer-plugin",savedOptions) + + def getCodeConfValue(self,code,value): + plugindir = self.pytrainer_main.profile.plugindir + info = XMLParser(plugindir+"/"+code+"/conf.xml") + return info.getValue("pytrainer-plugin",value) diff --git a/pytrainer/recordgraph.py b/pytrainer/recordgraph.py index f69826e9..3fb9e646 100644 --- a/pytrainer/recordgraph.py +++ b/pytrainer/recordgraph.py @@ -19,31 +19,15 @@ import logging from .gui.drawArea import DrawArea -from gi.repository import Gtk - class RecordGraph: - def __init__(self, vbox = None, window = None, combovalue = None, combovalue2 = None, btnShowLaps = None, tableConfig = None, pytrainer_main=None): + def __init__(self, vbox = None, window = None, pytrainer_main=None): logging.debug(">>") self.pytrainer_main = pytrainer_main self.drawarea = DrawArea(vbox, window) - self.combovalue = combovalue - self.combovalue2 = combovalue2 - self.showLaps = btnShowLaps - self.config_table = tableConfig logging.debug("<<") def drawgraph(self,values,laps=None, y1limits=None, y1color=None, y1_linewidth=1): logging.debug(">>") - #Get the config options - for child in self.config_table.get_children(): - if child.get_name() == "spinbuttonY1Max": - spinbuttonY1Max = child - elif child.get_name() == "spinbuttonY1Min": - spinbuttonY1Min = child - elif child.get_name() == "colorbuttonY1LineColor": - colorbuttonY1LineColor = child - elif child.get_name() == "spinbuttonY1LineWeight": - spinbuttonY1LineWeight = child xval = [] yval = [] @@ -51,39 +35,17 @@ def drawgraph(self,values,laps=None, y1limits=None, y1color=None, y1_linewidth=1 ylab = [] tit = [] col = [] - value_selected = self.combovalue.get_active() - logging.debug("Value selected 1: %s", value_selected) - value_selected2 = self.combovalue2.get_active() - logging.debug("Value selected 2: %s", value_selected2) - showLaps = self.showLaps.get_active() - logging.debug("Show laps: %s", showLaps) - #Determine left and right lap boundaries - if laps is not None and showLaps: - lapValues = [] - lastPoint = 0.0 - for lap in laps: - thisPoint = float(lap['distance'])/1000.0 + lastPoint - lapValues.append((lastPoint, thisPoint)) - lastPoint = thisPoint - else: - lapValues = None + lapValues = None - if value_selected < 0: - self.combovalue.set_active(0) - value_selected = 0 + value_selected = 0 - if value_selected2 < 0: - self.combovalue2.set_active(0) - value_selected2 = 0 + value_selected2 = 0 xvalues, yvalues = self.get_values(values,value_selected) max_yvalue = max(yvalues) min_yvalue = min(yvalues) xlabel,ylabel,title,color = self.get_value_params(value_selected) if y1color is not None: - _color = Gdk.Color(y1color) color = y1color - else: - _color = Gdk.Color(color) xval.append(xvalues) yval.append(yvalues) @@ -95,15 +57,10 @@ def drawgraph(self,values,laps=None, y1limits=None, y1color=None, y1_linewidth=1 tit.append(title) col.append(color) - #_color = Gdk.Color(color) - colorbuttonY1LineColor.set_color(_color) - if value_selected2 > 0: value_selected2 = value_selected2-1 xlabel,ylabel,title,color = self.get_value_params(value_selected2) xvalues,yvalues = self.get_values(values,value_selected2) - max_yvalue=max(max(yvalues), max_yvalue) - min_yvalue=min(min(yvalues), min_yvalue) xval.append(xvalues) yval.append(yvalues) xlab.append(xlabel) @@ -111,21 +68,7 @@ def drawgraph(self,values,laps=None, y1limits=None, y1color=None, y1_linewidth=1 tit.append("") col.append(color) logging.info("To show: tit: %s | col: %s | xlab: %s | ylab: %s", tit, col, xlab, ylab) - #self.drawPlot(xvalues,yvalues,xlabel,ylabel,title,color,zones) - plot_stats = self.drawarea.drawPlot(xval,yval,xlab,ylab,tit,col,None,lapValues, ylimits=y1limits, y1_linewidth=y1_linewidth) - ymin = plot_stats['y1_min'] - ymax = plot_stats['y1_max'] - y1_linewidth = plot_stats['y1_linewidth'] - - max_yvalue = max(max_yvalue, ymax) - min_yvalue = min(min_yvalue, ymin) - adjY1Min = Gtk.Adjustment(value=ymin, lower=min_yvalue,upper=max_yvalue, step_incr=1, page_incr=10) - adjY1Max = Gtk.Adjustment(value=ymax, lower=min_yvalue,upper=max_yvalue, step_incr=1, page_incr=10) - spinbuttonY1Min.set_adjustment(adjY1Min) - spinbuttonY1Max.set_adjustment(adjY1Max) - spinbuttonY1Min.set_value(ymin) - spinbuttonY1Max.set_value(ymax) - spinbuttonY1LineWeight.set_value(y1_linewidth) + self.drawarea.drawPlot(xval,yval,xlab,ylab,tit,col,None,lapValues, ylimits=y1limits, y1_linewidth=y1_linewidth) logging.debug("<<") diff --git a/pytrainer/test/plugins/test_garmin-tcxv2.py b/pytrainer/test/plugins/test_garmintcxv2.py similarity index 100% rename from pytrainer/test/plugins/test_garmin-tcxv2.py rename to pytrainer/test/plugins/test_garmintcxv2.py