cyclograph-1.9.1/0000755000175000017500000000000013215023054015232 5ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/0000755000175000017500000000000013215023054017365 5ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/gtk3/0000755000175000017500000000000013215023054020235 5ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/gtk3/__init__.py0000644000175000017500000000000012065361217022344 0ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/gtk3/glal.py0000644000175000017500000003266613144674235021561 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # gtk3.glal.py """This module provides a graphic library abstraction layer for Cyclograph""" # Copyright (C) 2008-2016 Federico Brega, Pierluigi Villani # 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. import math import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject, Gdk, GLib import cairo from . import gui GObject.threads_init() def initapp(): return Gtk.main() def bind(cgfunc, menuopt): """Connect gui menu option with cg function""" return menuopt.connect("activate", cgfunc) def bind_close(cg_exit, gui): """Connect gui close event with cg exit function""" gui.cg_exit = cg_exit bind(gui.exit, gui.menu_item_exit) def ToolbarBind(fun, button): """Connect gui toolbar button with cg function""" return button.connect("clicked", fun) def OptionCheck(guiopt, option): """Set a gui menu option to selected/unselected """ guiopt.set_active(option) def enable_saving(gui, bool_val): """Enable or disable saving options according to bool_val""" gui.menu_item_save.set_sensitive(bool_val) gui.menu_item_save_as.set_sensitive(bool_val) gui.action_add.set_sensitive(bool_val) gui.action_edit.set_sensitive(bool_val) gui.action_delete.set_sensitive(bool_val) gui.action_plot.set_sensitive(bool_val) gui.action_map.set_sensitive(bool_val) gui.action_properties.set_sensitive(bool_val) def addstatusbartext(maingui, text): """Add text to main gui status bar""" maingui.addstatusbartext(text) maingui.sbtimer.start() def signalbug(event=None): """Open webpage to signal bugs""" bugUrl = 'http://sourceforge.net/project/memberlist.php?group_id=227295' # Looks like gtk hasn't any function to launch an external browser, # the best we can do is use python webbrowser import webbrowser webbrowser.open(bugUrl, new=True, autoraise=True) class Message(): """ Gtk3 message""" def __init__(self): class Messenger(GObject.GObject): __gsignals__ = { str("UPDATE_TAB"): (GObject.SignalFlags.ACTION, None, (int, int, )), str("SLOPE_ADD"): (GObject.SignalFlags.ACTION, None, (int, int, )), str("SLOPE_DEL"): (GObject.SignalFlags.ACTION, None, (int, int, )), } self.gobj = Messenger() def send(self, message, slope_number, row_num): """ Send message""" self.gobj.emit(message, slope_number, row_num) def subscribe(self, function, emitter, message): """ Subscribe message""" self.handler = function emitter.gobj.connect(message, self._reformat_handler_param) def _reformat_handler_param(self, emitter, arg1, arg2): self.handler(arg1, arg2) class Notebook: """Gtk3 notebook""" def __init__(self, notebook, fun_close): self.notebook = notebook self.fun_close = fun_close self.notebook.set_scrollable(True) def Page(self): """Creates page""" return gui.Page(self.notebook) def set_page_label(self, page_num, text): """Set page label text""" lab = self.notebook.get_tab_label(self.get_page(page_num)) lab.label.set_text(text) def add_page(self, page, title): """Add page to the notebook""" box = gui.tab_label(page, title) page.button_close = box.closebtn page.button_close.connect("clicked", self.fun_close, page) self.notebook.append_page(page, box) self.notebook.show_all() nbpages = self.notebook.get_n_pages() self.notebook.set_current_page(nbpages-1) def remove_page(self, page_num): """Remove page from the notebook""" self.notebook.remove_page(page_num) def getselpagnum(self): """Return current selected page""" return self.notebook.get_current_page() def setselpagnum(self, page_num): """Set current selected page""" self.notebook.set_current_page(page_num) def get_page(self, page_num): """Return page at page_num""" return self.notebook.get_nth_page(page_num) def get_pagenum(self, argument): """Return page_num from page passed as argument""" return self.notebook.page_num(argument[1]) class DeviceContext(): """Device Context Gtk3 class wrapper""" def _convcolor(self, color): """ Convert color""" col = color if isinstance(color, str): if color.startswith("rgb"): col = [(int(component) << 8)/65535.0 for component in color[4:-1].split(',')] elif color == "white": col = [1, 1, 1] else: col = [0, 0, 0] return col def init_draw_surf(self, widget, width, height): """Initialize device context""" self.size_x = width self.size_y = height self.devc = widget self.FONT_TYP = { "light": cairo.FONT_WEIGHT_NORMAL, "normal": cairo.FONT_WEIGHT_NORMAL, "bold": cairo.FONT_WEIGHT_BOLD, } # pen color self.pcolor = [0, 0, 0] # brush color self.bcolor = [0, 0, 0] def shear(self, shear): """ device shear""" matrix = cairo.Matrix(yx = shear) self.devc.set_matrix(matrix) def getsize(self): """Return device context dimensions""" return (self.size_x, self.size_y) def gradfill(self, rect, startcolor, endcolor): """Fill context with gradient""" (startx, starty) = (rect[0], rect[1]) scolor = self._convcolor(startcolor) ecolor = self._convcolor(endcolor) lg1 = cairo.LinearGradient(0, 160.0, 0, self.size_y) lg1.add_color_stop_rgb(0, *scolor) lg1.add_color_stop_rgb(1, *ecolor) self.devc.rectangle(startx, starty, self.size_x, self.size_y) self.devc.set_source(lg1) self.devc.fill() def setpen(self, color, size): """Set pen color and size""" self.devc.set_line_width(size) self.pcolor = self._convcolor(color) def setfont(self, font): """Set font""" try: self.devc.select_font_face(font['des'], cairo.FONT_SLANT_NORMAL, self.FONT_TYP[font['typ']]) self.devc.set_font_size(font['dim']) except Exception: pass def drawtext(self, text, pos_x, pos_y): """Draw text""" self.devc.set_source_rgb(*self.pcolor) self.devc.move_to(pos_x, pos_y+12) self.devc.show_text(text) def gettextwidth(self, text): """ Return text length""" return int(self.devc.text_extents(text)[2]) def gettextheight(self, text): """ Return text height""" return int(self.devc.text_extents(text)[3]) def drawline(self, pos_x0, pos_y0, pos_x1, pos_y1): """Draw line""" self.devc.set_source_rgb(*self.pcolor) self.devc.move_to(pos_x0, pos_y0) self.devc.line_to(pos_x1, pos_y1) self.devc.stroke() def setlineargradientbrush(self, colorlist, startp, endp): """ Get a linear gradient from startp to endp, using colors in colorlist. The elments of colorlist are tuple in the format (color, realtive position).""" grad = cairo.LinearGradient(startp[0], startp[1], endp[0], endp[1]) for color in colorlist: grad.add_color_stop_rgb(color[1], *self._convcolor(color[0])) self.pattern = grad def setbrush(self, color): """Set brush color""" self.pattern = cairo.SolidPattern(*self._convcolor(color)) def drawrectangle(self, pos_x0, pos_y0, width, height): """Draw rectangle""" self.devc.set_source_rgb(0, 0, 0) self.devc.rectangle(pos_x0, pos_y0, width, height) self.devc.stroke_preserve() self.devc.set_source(self.pattern) self.devc.fill() def drawrotatedtext(self, text, pos_x, pos_y, angle): """Draw rotated text""" self.devc.save() self.devc.translate(pos_x, pos_y) self.devc.rotate(-math.radians(angle)) self.drawtext(text, 0, 0) self.devc.restore() def drawpolygon(self, sequence): """Draw polygon""" self.devc.set_source_rgb(0, 0, 0) self.devc.move_to(*sequence[0]) for point in sequence: self.devc.line_to(*point) self.devc.close_path() self.devc.stroke_preserve() self.devc.set_source(self.pattern) self.devc.fill() def startpath(self, point): """ Start a path in the specified point,""" (pointx, pointy) = point self.devc.set_source_rgb(0, 0, 0) self.devc.move_to(pointx, pointy) return None def drawpathlineto(self, path, point): """ Draw a straight line from the last point to the given point.""" (pointx, pointy) = point self.devc.line_to(pointx, pointy) def drawpathcubicto(self, path, controlpoints): """ Draw a cubic BeziƩr from the last point using the given list of three control points.""" points = [] for pnt in controlpoints: points.append(pnt[0]) points.append(pnt[1]) self.devc.curve_to(*points) def endpath(self, path): """ Show the path.""" self.devc.set_source_rgb(0, 0, 0) self.devc.stroke_preserve() self.devc.set_source(self.pattern) self.devc.fill() def end_draw(self): """End drawing not used for gtk gui""" pass class Image(): """Gtk3 image class wrapper""" def __init__(self, size_x, size_y, plotfnct): (self.size_x, self.size_y) = (size_x, size_y) self.plotfnct = plotfnct self.image = cairo.ImageSurface(cairo.FORMAT_RGB24, self.size_x, self.size_y) def plot(self, settings): """Plot image""" dcwrpp = DeviceContext() dcwrpp.init_draw_surf(cairo.Context(self.image), self.size_x, self.size_y) self.plotfnct(settings, dcwrpp) def savetofile(self, path, format): """Save slope to image""" if format.lower() == 'png': self.image.write_to_png(path) if format.lower() == 'bmp': width, height = self.image.get_width(), self.image.get_height() pixbuf = Gdk.pixbuf_get_from_surface(self.image, 0, 0, width, height) pixbuf.savev(path, "bmp", [], []) if format.lower() == 'jpg': width, height = self.image.get_width(), self.image.get_height() pixbuf = Gdk.pixbuf_get_from_surface(self.image, 0, 0, width, height) pixbuf.savev(path, "jpeg", ["quality"], ["100"]) class Pdf(): """Gtk3 pdf class wrapper""" def __init__(self, filepath): width, height = 793, 1122 self.dy = 90 self.dx = 300 self.y_incr = 30 self.surface = cairo.PDFSurface(filepath, width, height) self.context = cairo.Context(self.surface) # white background self.context.set_source_rgb(1, 1, 1) self.context.rectangle(0, 0, width, height) self.context.fill() self.context.select_font_face("Arial") self.context.set_font_size(18) def plot_image(self, settings, size_x, size_y, plotfnct): """Plot image""" image = cairo.SVGSurface(None, size_x, size_y) dcwrpp = DeviceContext() dcwrpp.init_draw_surf(cairo.Context(image), size_x, size_y) plotfnct(settings, dcwrpp) self.context.set_source_surface(image, 0, 0) self.context.paint() self.dy += size_y def addtext(self, text): self.dy += self.y_incr self.context.set_source_rgb(0, 0, 0) self.context.move_to(self.dx, self.dy) self.context.show_text(text) def addtitle(self, text): self.dy += self.y_incr self.context.set_source_rgb(0, 0, 0.4) self.context.move_to(self.dx-50, self.dy) self.context.show_text(text+":") def save(self): """Save slope to pdf""" self.context.show_page() self.surface.finish() class ProgressDialog(): """Gtk3 Progress dialog""" def __init__(self): self.pdialog = gui.ProgressDialog(_("Downloading altitudes"), _("Please wait, Cyclograph is downloading altitudes."), 1000.0) def update(self, value): """Update the progress shown and return if user want to abort.""" return self.pdialog.update(value) def destroy(self): """Destroy dialog""" self.pdialog.destroy() class Timer(): """Gtk3 timer""" def __init__(self, period, callback): self.timer = 0 self.callback = callback self.period = period def start(self): """Start timer""" self.timer = GLib.timeout_add(self.period, self.callback) def stop(self): """Stop timer""" GObject.source_remove(self.timer) self.timer = 0 # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/gtk3/gui.py0000644000175000017500000021562513152573342021420 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """gtk3.gui.py""" # Copyright (C) 2011-2017 Pierluigi Villani # 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. import os import io import gi gi.require_version("WebKit2", "4.0") from gi.repository import Gtk, GObject, Gdk, GdkPixbuf, Pango from gi.repository import WebKit2 import glal import cairo from themes import ThemeManager from version import VERSION, VERSION_CHECK_ENABLED pixmap_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'pixmaps') ICON_FILENAME = os.path.join(pixmap_dir, 'cyclograph.svg') if not os.path.isfile(ICON_FILENAME): pixmap_dir = '/usr/share/cyclograph/icons/' ICON_FILENAME = '/usr/share/icons/hicolor/scalable/apps/cyclograph.svg' class Gui(Gtk.Window): """Main window""" def __init__(self, parent, title, size): super(Gui, self).__init__() self.cg_exit = None # check on exit callback self.set_title(title) self.maximized = False self.set_default_size(size[0], size[1]) if size[2]: self.maximize() self.maximized = True try: self.set_icon_from_file(ICON_FILENAME) except Exception: pass # if librsvg2-common is not installed self.set_position(Gtk.WindowPosition.CENTER) self.notebook = Gtk.Notebook() self.notebook.set_property('show-tabs', True) agr = Gtk.AccelGroup() self.add_accel_group(agr) # Statusbar self.statusbar = Gtk.Statusbar() self.context_id = self.statusbar.get_context_id("Statusbar") self.sbtimer = glal.Timer(5000, self.clearstatusbartext) # 5000ms timeout # Load icons icons = [('icon_add', os.path.join(pixmap_dir, 'icon_add.svg')), ('icon_ckml', os.path.join(pixmap_dir, 'icon_ckml.svg')), ('icon_delete', os.path.join(pixmap_dir, 'icon_delete.svg')), ('icon_modify', os.path.join(pixmap_dir, 'icon_modify.svg')), ('icon_plot', os.path.join(pixmap_dir, 'icon_plot.svg')), ('icon_map', os.path.join(pixmap_dir, 'icon_map.svg')), ('icon_info', os.path.join(pixmap_dir, 'icon_info.svg'))] factory = Gtk.IconFactory() for (icon_id, icon_file) in icons: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_file) except Exception: continue # if librsvg2-common is not installed iconset = Gtk.IconSet.new_from_pixbuf(pixbuf) factory.add(icon_id, iconset) factory.add_default() # Action group self.menu_item_new = Gtk.Action(name="New", label=_(u"New"), tooltip=_(u"New Slope"), stock_id=Gtk.STOCK_NEW) self.menu_item_open = Gtk.Action(name="Open", label=_(u"Open"), tooltip=_(u"Open Slope"), stock_id=Gtk.STOCK_OPEN) self.menu_item_createkml = Gtk.Action(name="CreateKML", label=_(u"Create"), tooltip=_(u"Create KML"), stock_id='icon_ckml') self.menu_item_save = Gtk.Action(name="Save", label=_(u"Save"), tooltip=_(u"Save Slope"), stock_id=Gtk.STOCK_SAVE) actiongroup = Gtk.ActionGroup(name='Actions') actiongroup.add_action(self.menu_item_new) actiongroup.add_action(self.menu_item_open) actiongroup.add_action(self.menu_item_createkml) actiongroup.add_action(self.menu_item_save) # Toolbar toolbar = Gtk.Toolbar() context = toolbar.get_style_context() context.add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) self.action_add = Gtk.ToolButton.new_from_stock('icon_add') self.action_add.set_label_widget(Gtk.Label(label=_(u"Add"))) self.action_add.set_homogeneous(False) self.action_add.set_tooltip_text(_(u"Add a check-point")) key, mod = Gtk.accelerator_parse('plus') self.action_add.add_accelerator("clicked", agr, key, mod, Gtk.AccelFlags.VISIBLE) self.action_edit = Gtk.ToolButton.new_from_stock('icon_modify') self.action_edit.set_label_widget(Gtk.Label(label=_(u"Edit"))) self.action_edit.set_homogeneous(False) self.action_edit.set_tooltip_text(_(u"Edit a check-point")) self.action_delete = Gtk.ToolButton.new_from_stock('icon_delete') self.action_delete.set_label_widget(Gtk.Label(label=_(u"Delete"))) self.action_delete.set_homogeneous(False) self.action_delete.set_tooltip_text(_(u"Remove a check-point")) key, mod = Gtk.accelerator_parse('minus') self.action_delete.add_accelerator("clicked", agr, key, mod, Gtk.AccelFlags.VISIBLE) self.action_plot = Gtk.ToolButton.new_from_stock('icon_plot') self.action_plot.set_homogeneous(False) self.action_plot.set_label_widget(Gtk.Label(label=_(u"Plot"))) self.action_plot.set_tooltip_text(_(u"Plot your slope")) self.action_map = Gtk.ToolButton.new_from_stock('icon_map') self.action_map.set_homogeneous(False) self.action_map.set_label_widget(Gtk.Label(label=_(u"Map"))) self.action_map.set_tooltip_text(_(u"Show the map of your slope")) self.action_properties = Gtk.ToolButton.new_from_stock('icon_info') self.action_properties.set_homogeneous(False) self.action_properties.set_label_widget(Gtk.Label(label=_(u"Info"))) self.action_properties.set_tooltip_text(_(u"Informations about the slope")) self.menu_item_save.set_sensitive(False) self.action_add.set_sensitive(False) self.action_edit.set_sensitive(False) self.action_delete.set_sensitive(False) self.action_plot.set_sensitive(False) self.action_map.set_sensitive(False) self.action_properties.set_sensitive(False) toolbar.insert(self.menu_item_new.create_tool_item(), 0) toolbar.insert(self.menu_item_open.create_tool_item(), 1) toolbar.insert(self.menu_item_createkml.create_tool_item(), 2) toolbar.insert(self.menu_item_save.create_tool_item(), 3) toolbar.insert(Gtk.SeparatorToolItem(), 4) toolbar.insert(self.action_add, 5) toolbar.insert(self.action_edit, 6) toolbar.insert(self.action_delete, 7) toolbar.insert(Gtk.SeparatorToolItem(), 8) toolbar.insert(self.action_plot, 9) toolbar.insert(self.action_map, 10) toolbar.insert(self.action_properties, 11) # Creating the menubar. menubar = Gtk.MenuBar() filemenu = Gtk.Menu() self.smenu = Gtk.MenuItem.new_with_mnemonic(_(u"_File")) self.smenu.set_submenu(filemenu) item_new = self.menu_item_new.create_menu_item() key, mod = Gtk.accelerator_parse("N") item_new.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE) filemenu.append(item_new) # sep = Gtk.SeparatorMenuItem() # filemenu.append(sep) item_open = self.menu_item_open.create_menu_item() key, mod = Gtk.accelerator_parse("O") item_open.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE) filemenu.append(item_open) item_createkml = self.menu_item_createkml.create_menu_item() key, mod = Gtk.accelerator_parse("G") item_createkml.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE) filemenu.append(item_createkml) item_save = self.menu_item_save.create_menu_item() key, mod = Gtk.accelerator_parse("S") item_save.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE) filemenu.append(item_save) self.menu_item_save_as = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_SAVE_AS, agr) self.menu_item_save_as.set_sensitive(False) key, mod = Gtk.accelerator_parse("S") self.menu_item_save_as.add_accelerator("activate", agr, key, mod, Gtk.AccelFlags.VISIBLE) filemenu.append(self.menu_item_save_as) # sep = Gtk.SeparatorMenuItem() # filemenu.append(sep) self.menu_item_exit = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, agr) filemenu.append(self.menu_item_exit) menubar.append(self.smenu) filemenu1 = Gtk.Menu() self.omenu = Gtk.MenuItem.new_with_mnemonic(_(u"_Options")) self.omenu.set_submenu(filemenu1) kmenu = Gtk.Menu() kmenum = Gtk.MenuItem.new_with_mnemonic("_Kml server") kmenum.set_submenu(kmenu) self.menu_item_s1 = Gtk.RadioMenuItem.new_with_label( [], (u"geonames.org")) self.menu_item_s2 = Gtk.RadioMenuItem.new_with_label_from_widget( self.menu_item_s1, (u"earthtools.org")) self.menu_item_s3 = Gtk.RadioMenuItem.new_with_label_from_widget( self.menu_item_s1, (u"usgs.net")) kmenu.append(self.menu_item_s1) kmenu.append(self.menu_item_s2) kmenu.append(self.menu_item_s3) filemenu1.append(kmenum) # sep = Gtk.SeparatorMenuItem() # filemenu1.append(sep) self.preferences = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_PREFERENCES, agr) filemenu1.append(self.preferences) menubar.append(self.omenu) filemenu2 = Gtk.Menu() self.amenu = Gtk.MenuItem.new_with_label((u"?")) self.amenu.set_submenu(filemenu2) self.menu_item_about = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ABOUT, agr) self.menu_item_about.connect("activate", onabout) filemenu2.append(self.menu_item_about) self.menu_item_bug = Gtk.MenuItem.new_with_label(_(u"Signal a bug")) filemenu2.append(self.menu_item_bug) menubar.append(self.amenu) vbox = Gtk.VBox() vbox.pack_start(menubar, False, False, 0) vbox.pack_start(toolbar, False, False, 0) vbox.pack_start(self.notebook, True, True, 0) vbox.pack_start(self.statusbar, False, False, 0) self.add(vbox) self.connect("delete-event", self.exit) self.connect('window-state-event', self.on_window_state_event) self.show_all() def exit(self, event=None, param=None): """ Exit from CycloGraph, check if there are unsaved slopes""" if self.cg_exit(): Gtk.main_quit() return return True # needed by gtk def getdimensions(self): """Return window width and height""" return self.get_size() def ismaximized(self): """Return true if window is maximized""" return self.maximized def addstatusbartext(self, text): """Add text to the statusbar""" self.statusbar.push(self.context_id, text) def clearstatusbartext(self): """Remove text from statusbar""" self.statusbar.pop(self.context_id) self.sbtimer.stop() def on_window_state_event(self, widget, event): "Update maximized variable to the current window state" if event.new_window_state & Gdk.WindowState.MAXIMIZED: self.maximized = True else: self.maximized = False def onabout(event): """ About dialog""" about = Gtk.AboutDialog() about.set_program_name("CycloGraph") about.set_version(VERSION) about.set_copyright("Copyright (C) 2008-2017 Federico Brega, Pierluigi Villani") about.set_authors(("Federico Brega", "Pierluigi Villani")) about.set_license(""" CycloGraph 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. CycloGraph 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 http://www.gnu.org/licenses/. """) about.set_website("http://cyclograph.sourceforge.net") about.set_logo(GdkPixbuf.Pixbuf.new_from_file(ICON_FILENAME)) about.set_icon_from_file(ICON_FILENAME) about.run() about.destroy() class Page(Gtk.ScrolledWindow): """Notebook page""" def __init__(self, parent): GObject.GObject.__init__(self) self.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.store = Gtk.ListStore(int, str, str, str) self.mylistctrl = Gtk.TreeView.new_with_model(self.store) self.mylistctrl.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) # can select multiple cp by dragging the mouse self.mylistctrl.set_rubber_banding(True) self.add(self.mylistctrl) self.savedfilepath = None self.modified = False self.slopecounternew = 0 self.closebox = None rendererText = Gtk.CellRendererText() column = Gtk.TreeViewColumn(u"#", rendererText, text=0) column.set_sort_column_id(0) self.mylistctrl.append_column(column) rendererText = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_(u"Distance [km]"), rendererText, text=1) column.set_sort_column_id(1) self.mylistctrl.append_column(column) rendererText = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_(u"Altitude [m]"), rendererText, text=2) column.set_sort_column_id(2) self.mylistctrl.append_column(column) rendererText = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_(u"Name"), rendererText, text=3) column.set_sort_column_id(3) self.mylistctrl.append_column(column) self.mylistctrl.set_headers_clickable(False) # disable column sort def insert_row(self, row_num, chkpnt): """Insert item to this page at position row_num""" self.store.insert(row_num, [row_num, str(chkpnt[0]), str(chkpnt[1]), chkpnt[2]]) treeiter = self.store.get_iter(path = (row_num,)) while treeiter is not None: self.store[treeiter][0] = self.store[treeiter][0] + 1 treeiter = self.store.iter_next(treeiter) def delete_row(self, row_num): """Removes row at position row_num in this page.""" itera = self.store.get_iter(path = (row_num,)) treeiter = self.store.iter_next(itera) while treeiter is not None: self.store[treeiter][0] = self.store[treeiter][0] - 1 treeiter = self.store.iter_next(treeiter) self.store.remove(itera) def get_sel_row(self): """Gives the row number that is selected.""" selection = self.mylistctrl.get_selection() model, selected = selection.get_selected_rows() editcp = -1 if len(selected) > 0: editcp = selected[0].get_indices()[0] return editcp def get_sel_multi_row(self): """Gives a list with the row number of selected items.""" selection = self.mylistctrl.get_selection() model, selected = selection.get_selected_rows() rm_cps = [path.get_indices()[0] for path in selected] return rm_cps class Managecp(Gtk.Dialog): """Add and edit dialog""" def __init__(self, parent, title, dist="", alt="", name=""): GObject.GObject.__init__(self) self.set_title(title) self.set_transient_for(parent) self.set_modal(True) self.set_destroy_with_parent(True) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) self.set_size_request(220, 150) box = Gtk.VBox(spacing=10) box.set_border_width(10) vbox = self.get_content_area() vbox.pack_start(box, True, True, 0) box.show() labelbox = Gtk.VBox(spacing=10) labelbox.set_border_width(10) labelbox.pack_start(Gtk.Label(_(u"Distance:")), True, True, 0) labelbox.pack_start(Gtk.Label(_(u"Altitude:")), True, True, 0) labelbox.pack_start(Gtk.Label(_(u"Name:")), True, True, 0) entrybox = Gtk.VBox() self.distance = Gtk.Entry() self.distance.set_text(dist) self.distance.set_activates_default(True) entrybox.pack_start(self.distance, True, True, 0) self.altitude = Gtk.Entry() self.altitude.set_text(alt) self.altitude.set_activates_default(True) entrybox.pack_start(self.altitude, True, True, 0) self.cpname = Gtk.Entry() self.cpname.set_text(name) self.cpname.set_activates_default(True) entrybox.pack_start(self.cpname, True, True, 0) obox = Gtk.HBox() obox.pack_start(labelbox, False, False, 0) obox.pack_start(entrybox, True, True, 0) box.pack_start(obox, True, True, 0) box.show_all() def show_modal(self): """Shows the dialog and returns True if the value has to be changed.""" return self.run() == Gtk.ResponseType.ACCEPT def destroy(self): """Destroy current managecp""" Gtk.Dialog.destroy(self) def getcp(self): """Gives the modified values.""" dist = self.distance.get_text() alti = self.altitude.get_text() name = self.cpname.get_text() return (dist, alti, name) class FormSlopeInfo(Gtk.Dialog): """info dialog about the slope""" def __init__(self, parent, slopedata): GObject.GObject.__init__(self) self.set_title(_(u"Slope Info")) self.set_transient_for(parent) self.set_modal(True) self.set_destroy_with_parent(True) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) self.set_size_request(300, 420) box = Gtk.VBox(spacing=10) box.set_border_width(10) vbox = self.get_content_area() vbox.pack_start(box, True, True, 0) box.show() grid = Gtk.Grid() box.pack_start(grid, False, True, 0) grid.show() grid.add(Gtk.Label(label=_(u"Name:"))) self.s_name = Gtk.Entry() self.s_name.set_text(slopedata['name']) grid.attach(self.s_name, 1, 0, 1, 1) grid.attach(Gtk.Label(label=_(u"State:")), 0, 1, 1, 1) self.s_state = Gtk.Entry() self.s_state.set_text(slopedata['state']) grid.attach(self.s_state, 1, 1, 1, 1) grid.attach(Gtk.Label(label=_(u"Author:")), 0, 2, 1, 1) self.s_author = Gtk.Entry() self.s_author.set_text(slopedata['author']) grid.attach(self.s_author, 1, 2, 1, 1) grid.attach(Gtk.Label(label=_(u"E-mail:")), 0, 3, 1, 1) self.s_email = Gtk.Entry() self.s_email.set_text(slopedata['email']) grid.attach(self.s_email, 1, 3, 1, 1) grid.attach(Gtk.Label(label=_(u"Average gradient:")), 0, 4, 1, 1) grid.attach(Gtk.Label(label=slopedata['average_grad']), 1, 4, 1, 1) grid.attach(Gtk.Label(label=_(u"Max gradient:")), 0, 5, 1, 1) grid.attach(Gtk.Label(label=slopedata['max_grad']), 1, 5, 1, 1) grid.attach(Gtk.Label(label=_(u"Height difference:")), 0, 6, 1, 1) grid.attach(Gtk.Label(label=slopedata['height_difference']), 1, 6, 1, 1) grid.attach(Gtk.Label(label=_(u"Height gain:")), 0, 7, 1, 1) grid.attach(Gtk.Label(label=slopedata['height_gain']), 1, 7, 1, 1) grid.attach(Gtk.Label(_(u"Note")+":"), 0, 8, 1, 1) grid.set_row_spacing(7) grid.set_column_spacing(5) self.s_comment = Gtk.TextBuffer() self.s_comment.set_text(slopedata['comment']) textview = Gtk.TextView() textview.set_buffer(self.s_comment) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_placement(Gtk.CornerType.TOP_LEFT) sw.add(textview) sw.show_all() box.pack_start(sw, True, True, 0) box.show_all() def getslopeinfo(self): """Returns the slope infos: name, state, comment""" startnote, endnote = self.s_comment.get_bounds() return {'name': self.s_name.get_text(), 'state': self.s_state.get_text(), 'author': self.s_author.get_text(), 'email': self.s_email.get_text(), 'comment': self.s_comment.get_text(startnote, endnote, True)} def show_modal(self): """Shows the dialog and returns True if the value has to be chenged.""" return self.run() == Gtk.ResponseType.ACCEPT def destroy(self): """Destroy current FormSlopeInfo""" Gtk.Dialog.destroy(self) class tab_label(Gtk.HBox): """Notebook tab custom label with close button""" def __init__(self, page, title): GObject.GObject.__init__(self) self.label = Gtk.Label(label=title) self.closebtn = Gtk.Button() image = Gtk.Image() image.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU) self.closebtn.set_image(image) self.closebtn.set_relief(Gtk.ReliefStyle.NONE) self.pack_start(self.label, True, True, 0) self.pack_end(self.closebtn, False, False, 0) self.show_all() class Plot(Gtk.Window): """plot main class""" def __init__(self, parent, slope, plot_settings, exportfun, exportfun_pdf, exportfun_html): super(Plot, self).__init__() title = "CycloGraph" if slope.name: title += " - " + slope.name self.set_title(title) self.settings = plot_settings self.set_default_size(self.settings['width'], self.settings['height']) # set minimum height and width that plot can have geometry = Gdk.Geometry() geometry.min_width = 300 geometry.min_height = 300 self.set_geometry_hints(self, geometry, 0) self.set_icon_from_file(ICON_FILENAME) menubar = Gtk.MenuBar() filemenu = Gtk.Menu() self.menu = Gtk.MenuItem.new_with_mnemonic(_(u"_Options")) self.menu.set_submenu(filemenu) agr = Gtk.AccelGroup() self.add_accel_group(agr) self.menu_item_save = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_SAVE, agr) filemenu.append(self.menu_item_save) self.menu_item_savepdf = Gtk.MenuItem.new_with_mnemonic(_(u"Save _PDF")) filemenu.append(self.menu_item_savepdf) self.menu_item_savehtml = Gtk.MenuItem.new_with_mnemonic(_(u"Save _html")) filemenu.append(self.menu_item_savehtml) self.menu_item_olines = Gtk.CheckMenuItem.new_with_label(_(u"Orizzontal lines")) self.menu_item_olines.set_active(self.settings['olines']) filemenu.append(self.menu_item_olines) self.menu_item_3d = Gtk.CheckMenuItem.new_with_label(_(u"3D effect")) self.menu_item_3d.set_active(self.settings['3d']) filemenu.append(self.menu_item_3d) self.menu_item_legend = Gtk.CheckMenuItem.new_with_label(_(u"Show legend")) self.menu_item_legend.set_active(self.settings['legend']) filemenu.append(self.menu_item_legend) tmenu = Gtk.Menu() tmenum = Gtk.MenuItem.new_with_mnemonic(_(u"_Theme")) tmenum.set_submenu(tmenu) self.themeitems = [] self.radioitem = {} self.thememanager = ThemeManager() for theme in self.thememanager.getthemeslist(): if self.settings['theme'] == theme: self.theme1 = Gtk.RadioMenuItem.new_with_label([], theme) self.themeitems.append(theme) self.theme1.connect("activate", self.changetheme) for theme in self.themeitems: if self.settings['theme'] != theme: rmi = Gtk.RadioMenuItem.new_with_label_from_widget(self.theme1, theme) rmi.connect("activate", self.changetheme) tmenu.append(rmi) self.radioitem[theme] = rmi else: self.radioitem[theme] = self.theme1 tmenu.append(self.theme1) self.theme1.set_active(True) filemenu.append(tmenum) rmenu = Gtk.Menu() rmenum = Gtk.MenuItem.new_with_mnemonic(_(u"Saving _resolution")) rmenum.set_submenu(rmenu) menu_item_r1 = Gtk.RadioMenuItem.new_with_label([], (u"800x600")) menu_item_r2 = Gtk.RadioMenuItem.new_with_label_from_widget(menu_item_r1, (u"1024x768")) menu_item_r3 = Gtk.RadioMenuItem.new_with_label_from_widget(menu_item_r1, (u"1280x800")) menu_item_r4 = Gtk.RadioMenuItem.new_with_label_from_widget(menu_item_r1, (u"1600x1200")) menu_item_r1.set_active(self.settings['width'] == 800) menu_item_r2.set_active(self.settings['width'] == 1024) menu_item_r3.set_active(self.settings['width'] == 1280) menu_item_r4.set_active(self.settings['width'] == 1600) menu_item_r1.connect("activate", self.res1) menu_item_r2.connect("activate", self.res2) menu_item_r3.connect("activate", self.res3) menu_item_r4.connect("activate", self.res4) rmenu.append(menu_item_r1) rmenu.append(menu_item_r2) rmenu.append(menu_item_r3) rmenu.append(menu_item_r4) filemenu.append(rmenum) menubar.append(self.menu) self.menu_item_save.connect("activate", self.saveimage) self.menu_item_savepdf.connect("activate", self.savepdf) self.menu_item_savehtml.connect("activate", self.savehtml) self.menu_item_olines.connect("activate", self.olines) self.menu_item_3d.connect("activate", self.change3d) self.menu_item_legend.connect("activate", self.legend) darea = Gtk.DrawingArea() darea.connect("draw", self.on_paint) self.myslope = slope self.devc = glal.DeviceContext() self.myslope.calc() self.exportfun = exportfun self.exportfun_pdf = exportfun_pdf self.exportfun_html = exportfun_html vbox = Gtk.VBox() vbox.pack_start(menubar, False, False, 0) vbox.pack_start(darea, True, True, 0) self.add(vbox) self.show_all() def on_paint(self, widget, event): """Draw graph""" window = widget.get_window() devc = window.cairo_create() self.devc.init_draw_surf(devc, widget.get_allocation().width, widget.get_allocation().height) self.myslope.paint(self.settings, self.devc) def showplot(self): """ Show plot""" pass def saveimage(self, event): """ Save plot image""" self.exportfun(self.myslope, self.settings) def savepdf(self, event): """ Save pdf report""" self.exportfun_pdf(self.myslope, self.settings) def savehtml(self, event): """ Save html report""" self.exportfun_html(self.myslope, self.settings) def change3d(self, event): """ If active switch 3d effect off, else turn it on.""" self.settings['3d'] = not self.settings['3d'] self.queue_draw() def olines(self, event): """ If active draws orizzontal lines""" self.settings['olines'] = not self.settings['olines'] self.queue_draw() def legend(self, event): """ If active draws legend""" self.settings['legend'] = not self.settings['legend'] self.queue_draw() def changetheme(self, event): """Change the actual slope theme.""" ritemt = self.radioitem.items() for theme in ritemt: if theme[1] == event: self.settings["theme"] = theme[0] self.queue_draw() break def res1(self, event=None): """Set 800x600""" self.settings['width'] = 800 self.settings['height'] = 600 def res2(self, event=None): """Set 1024x768""" self.settings['width'] = 1024 self.settings['height'] = 768 def res3(self, event=None): """Set 1280x800""" self.settings['width'] = 1280 self.settings['height'] = 800 def res4(self, event=None): """Set 1600x1200""" self.settings['width'] = 1600 self.settings['height'] = 1200 FONT_TYP = { "300": "light", "400": "normal", "700": "bold", } class Preferences(Gtk.Dialog): """Set program preferences""" def __init__(self, parent, pref, default_pref): GObject.GObject.__init__(self) self.set_title(_(u"Preferences")) self.set_transient_for(parent) self.set_modal(True) self.set_destroy_with_parent(True) self.colorlist = [] for color in pref['colors']: components = [int(component) << 8 for component in color[4:-1].split(',')] self.colorlist.append(Gdk.Color(*components)) self.default = default_pref self.set_size_request(340, 400) notebook = Gtk.Notebook() notebook.set_tab_pos(Gtk.PositionType.TOP) frame = Gtk.Frame() vbox = Gtk.VBox() vbox.set_border_width(5) hbox = Gtk.HBox() hbox.pack_start(vbox, False, False, 10) frame.add(hbox) frame.set_shadow_type(Gtk.ShadowType.NONE) label = Gtk.Label(label=""+_(u"Visualization")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.olines = Gtk.CheckButton(label=_(u"Orizzontal lines")) vbox.pack_start(self.olines, False, False, 0) self.olines.set_active(pref['olines']) self.effect3d = Gtk.CheckButton(label=_(u"3D effect")) vbox.pack_start(self.effect3d, False, False, 0) self.effect3d.set_active(pref['3d']) self.showinfo = Gtk.CheckButton(label=_(u"Show info")) vbox.pack_start(self.showinfo, False, False, 0) self.showinfo.set_active(pref['sinfo']) self.legend = Gtk.CheckButton(label=_(u"Show legend")) vbox.pack_start(self.legend, False, False, 0) self.legend.set_active(pref['legend']) label = Gtk.Label(label=""+_(u"Resolution")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.res = Gtk.ComboBoxText.new() self.res.append_text('800 x 600') self.res.append_text('1024 x 768') self.res.append_text('1280 x 800') self.res.append_text('1600 x 1200') self.res.set_active(0) if pref['width'] == 1600: self.res.set_active(3) elif pref['width'] == 1280: self.res.set_active(2) elif pref['width'] == 1024: self.res.set_active(1) vbox.pack_start(self.res, False, False, 0) label = Gtk.Label(label=""+_(u"Theme")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.theme = Gtk.ComboBoxText.new() current = 0 for theme in pref['themelist']: self.theme.append_text(theme) if theme == pref['theme']: self.theme.set_active(current) current += 1 vbox.pack_start(self.theme, False, False, 0) label = Gtk.Label(label=""+_(u"Kml server")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.serv = Gtk.ComboBoxText.new() self.serv.append_text('geonames.org') self.serv.append_text('earthtools.org') self.serv.append_text('usgs.net') self.serv.set_active(0) if pref['serv'] == 'usgs.net': self.serv.set_active(2) elif pref['serv'] == 'earthtools.org': self.serv.set_active(1) vbox.pack_start(self.serv, False, False, 0) vchecklabel = Gtk.Label(label=""+_(u"Latest version")+"") vchecklabel.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(vchecklabel, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.vcheck = Gtk.CheckButton(label=_(u"Check online")) vbox.pack_start(self.vcheck, False, False, 0) self.vcheck.set_active(pref['vcheck']) notebook.append_page(frame, Gtk.Label(label="General")) frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.NONE) vbox = Gtk.VBox() vbox.set_border_width(5) hbox = Gtk.HBox() hbox.pack_start(vbox, False, False, 10) frame.add(hbox) label = Gtk.Label(label=""+_(u"Description")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) desc = pref['fdesc'] self.fdesc = Gtk.Button(label=desc['des']+' | '+str(desc["dim"])) self.fdesc.desc = desc self.fdesc.connect("clicked", self.font_button_clicked) vbox.pack_start(self.fdesc, False, False, 0) label = Gtk.Label(label=""+_(u"Gradient")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) desc = pref['fgrad'] self.fgrad = Gtk.Button(label=desc['des']+' | '+str(desc["dim"])) self.fgrad.desc = desc self.fgrad.connect("clicked", self.font_button_clicked) vbox.pack_start(self.fgrad, False, False, 0) label = Gtk.Label(label=""+_(u"Title")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) desc = pref['ftitle'] self.ftitle = Gtk.Button(label=desc['des']+' | '+str(desc["dim"])) self.ftitle.desc = desc self.ftitle.connect("clicked", self.font_button_clicked) vbox.pack_start(self.ftitle, False, False, 0) vbox.show_all() notebook.append_page(frame, Gtk.Label(label="Fonts")) frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.NONE) vbox = Gtk.VBox() vbox.set_border_width(5) frame.add(vbox) self.grid = Gtk.Grid() vbox.pack_start(self.grid, True, True, 0) self.grid.show() self.tick = 0.1 self.levlist = [] self.colblist = [] for i in range(len(pref['levels'])): self.levlist.append(Gtk.SpinButton()) self.levlist[i].set_range(0.0, 90.0) self.levlist[i].set_value(pref['levels'][i]) self.levlist[i].set_increments(self.tick, 1) self.levlist[i].set_numeric(True) self.levlist[i].set_digits(1) self.levlist[i].connect("value_changed", self.changed_level) self.colblist.append( Gtk.ColorButton.new_with_color(self.colorlist[i])) hbx = Gtk.HBox() hbx.pack_start(Gtk.Label(label=">"), False, False, 5) hbx.pack_start(self.levlist[i], False, False, 5) hbx.pack_start(Gtk.Label(label="%"), False, False, 5) self.grid.attach(hbx, 0, i, 1, 1) self.grid.attach(self.colblist[i], 1, i, 1, 1) self.grid.set_row_spacing(10) self.grid.set_column_spacing(10) vbox.show_all() notebook.append_page(frame, Gtk.Label(label="Gradient colors")) frame = Gtk.Frame() frame.set_shadow_type(Gtk.ShadowType.NONE) vbox = Gtk.VBox() vbox.set_border_width(5) hbox = Gtk.HBox() hbox.pack_start(vbox, False, False, 10) frame.add(hbox) label = Gtk.Label(label=""+_(u"Name")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.aname = Gtk.Entry() self.aname.set_text(pref['aname']) vbox.pack_start(self.aname, False, False, 0) label = Gtk.Label(label=""+_(u"E-mail")+"") label.set_use_markup(True) hhbox = Gtk.HBox() hhbox.pack_start(label, False, False, 0) vbox.pack_start(hhbox, False, False, 5) self.amail = Gtk.Entry() self.amail.set_text(pref['amail']) vbox.pack_start(self.amail, False, False, 0) vbox.show_all() notebook.append_page(frame, Gtk.Label(label="Author")) notebook.show_all() bbox = Gtk.HBox() button_k = Gtk.Button(stock=Gtk.STOCK_OK) button_c = Gtk.Button(stock=Gtk.STOCK_CANCEL) button_d = Gtk.Button(label=_(u"Restore Default")) bbox.pack_end(button_k, False, False, 5) bbox.pack_end(button_c, False, False, 5) bbox.pack_start(button_d, False, False, 5) button_k.connect("clicked", self.ok_clicked) button_d.connect("clicked", self.restore_default) button_c.connect("clicked", self.destroy) box = Gtk.VBox() box.pack_start(notebook, True, True, 5) box.pack_start(bbox, False, False, 5) carea = self.get_content_area() carea.pack_start(box, True, True, 0) carea.show_all() if not VERSION_CHECK_ENABLED: #Check at last because any show_all of a parent widget made after # this block would invalidate the effect, showing again the widgets vchecklabel.hide() self.vcheck.hide() def changed_level(self, event): "Changed one of the gradient levels" self.levlist[0].set_range(0.0, self.levlist[1].get_value() - self.tick) for i in range(1, len(self.levlist)-1): self.levlist[i].set_range(self.levlist[i-1].get_value() + self.tick, self.levlist[i+1].get_value() - self.tick) self.levlist[len(self.levlist)-1].set_range( self.levlist[len(self.levlist)-2].get_value() + self.tick, 90.0) def ok_clicked(self, event): "Close preferences dialog and save info" self.response(True) def restore_default(self, event): """Restore preferences to default value""" self.olines.set_active(bool(self.default['orizzontal lines'])) self.legend.set_active(bool(self.default['legend'])) self.effect3d.set_active(bool(self.default['effect-3d'])) self.showinfo.set_active(bool(self.default['slopeinfo'])) self.vcheck.set_active(bool(self.default['latestVersionCheck'])) self.res.set_active(0) if self.default['width'] == 1600: self.res.set_active(3) elif self.default['width'] == 1280: self.res.set_active(2) elif self.default['width'] == 1024: self.res.set_active(1) current = 0 model = self.theme.get_model() for text in model: if text[0] == self.default['theme']: self.theme.set_active(current) current += 1 self.serv.set_active(0) if self.default['server'] == 'usgs.net': self.serv.set_active(2) elif self.default['server'] == 'earthtools.org': self.serv.set_active(1) self.fdesc.set_label(self.default['font-des 1']+' | '+self.default['font-dim 1']) self.fdesc.desc = { "des": self.default['font-des 1'], "typ": self.default['font-typ 1'], "dim": self.default['font-dim 1'] } self.fgrad.set_label(self.default['font-des g']+' | '+self.default['font-dim g']) self.fgrad.desc = { "des": self.default['font-des g'], "typ": self.default['font-typ g'], "dim": self.default['font-dim g'] } self.ftitle.set_label(self.default['font-des t']+' | '+self.default['font-dim t']) self.ftitle.desc = { "des": self.default['font-des t'], "typ": self.default['font-typ t'], "dim": self.default['font-dim t'] } self.aname.set_text(self.default['authorname']) self.amail.set_text(self.default['authormail']) colorlist = [self.default["color "+str(i)] for i in range(1, 8)] self.colorlist = [] for color in colorlist: components = [int(component) << 8 for component in color[4:-1].split(',')] self.colorlist.append(Gdk.Color(*components)) for i in range(len(colorlist)): self.colblist[i].set_color(self.colorlist[i]) levellist = [self.default["level "+str(i)] for i in range(1, 8)] for k in range(len(self.levlist)): i = len(self.levlist) - k-1 self.levlist[i].set_value(float(levellist[i])) def font_button_clicked(self, button): """If a font button is clicked show a font dialog to change font""" dlg = Gtk.FontSelectionDialog(_(u"Select Font")) dlg.set_font_name(button.desc["des"]+' '+button.desc["typ"].capitalize()+' '+str(button.desc["dim"])) dlg.set_icon_from_file(ICON_FILENAME) result = dlg.run() if result == Gtk.ResponseType.OK: font = Pango.FontDescription(dlg.get_font_name()) res = str(int(font.get_weight())) dlg.destroy() if res in FONT_TYP: weight = FONT_TYP[res] else: weight = "normal" button.desc = { "des": font.get_family(), "typ": weight, "dim": str(font.get_size()/Pango.SCALE) } button.set_label(font.get_family()+' | '+str(font.get_size()/Pango.SCALE)) dlg.destroy() def show_modal(self): """Shows the dialog and returns True if the values have to be chenged.""" return self.run() == 1 def getconfig(self): """Returns selected config options""" res = self.res.get_active_text().split(' ') levels = [] for i in range(len(self.levlist)): levels.append(self.levlist[i].get_value()) self.colorlist[i] = self.colblist[i].get_color() settings = { 'olines': self.olines.get_active(), 'theme': self.theme.get_active_text(), 'sinfo': self.showinfo.get_active(), 'legend': self.legend.get_active(), 'vcheck': self.vcheck.get_active(), '3d': self.effect3d.get_active(), 'aname': self.aname.get_text(), 'amail': self.amail.get_text(), 'fdesc': self.fdesc.desc, 'ftitle': self.ftitle.desc, 'fgrad': self.fgrad.desc, 'width': res[0], 'height': res[2], 'serv': self.serv.get_active_text(), 'colors': ['rgb(%d,%d,%d)' % (color.red >> 8, color.green >> 8, color.blue >> 8) for color in self.colorlist], 'levels': levels } return settings def destroy(self, event=None): """Destroy current Preferences""" Gtk.Dialog.destroy(self) class Map(Gtk.Dialog): """Show map of slopes""" def __init__(self, parent, slopename, stringc, savefnct): Gtk.Window.__init__(self) self.set_size_request(800, 600) # FIXME:take webpage dimension self.set_title(_(u"Map") + " - " + slopename) self.set_icon_from_file(ICON_FILENAME) self.set_resizable(True) self.set_type_hint(Gdk.WindowTypeHint.NORMAL) self.savefnct = savefnct ctx = WebKit2.WebContext.get_default() self.view = WebKit2.WebView.new_with_context(ctx) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.view) swframe = Gtk.Frame() swframe.add(sw) obox = Gtk.HBox() obox.pack_start(swframe, True, True, 0) bbox = Gtk.HBox() button_s = Gtk.Button(stock=Gtk.STOCK_SAVE) button_c = Gtk.Button(stock=Gtk.STOCK_CANCEL) bbox.pack_end(button_s, False, False, 5) bbox.pack_end(button_c, False, False, 5) button_s.connect("clicked", self.onsave) button_c.connect("clicked", self.cancel) box = Gtk.VBox() box.pack_start(obox, True, True, 0) box.pack_start(bbox, False, False, 5) vbox = self.get_content_area() vbox.pack_start(box, True, True, 0) self.show_all() MAP_SCRIPT = "" try: fid = open(os.path.join(os.path.dirname(__file__), "../map.html"), 'r') for line in fid: MAP_SCRIPT += line fid.close() except IOError: print("map.html not loaded\n") return HTML_MAP = MAP_SCRIPT % (stringc,) self.view.load_html(HTML_MAP, "file:///") def onsave(self, event): """ Save call""" self.savefnct(self) def cancel(self, event): """ Cancel Map dialog""" self.destroy() def on_snapshot_created(self, obj, result, filepath): surface = obj.get_snapshot_finish(result) pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height()) fformat = filepath.rsplit('.', 1)[1] if fformat.lower() == 'png': surface.write_to_png(filepath) if fformat.lower() == 'bmp': pixbuf.savev(filepath, "bmp", [], []) if fformat.lower() == 'jpg': pixbuf.savev(filepath, "jpeg", ["quality"], ["100"]) def saveimage(self, filepath): """ Save map image""" self.view.get_snapshot(WebKit2.SnapshotRegion.VISIBLE, WebKit2.SnapshotOptions.NONE, None, self.on_snapshot_created, filepath) class Create_kml(Gtk.Dialog): """Create kml routes""" MODE = {#_(u'Open Route Service'): 'ORS', #_(u'OSRM'): 'OSRM', _(u'brouter'): 'BROU', _(u'yournavigation'): 'YOURS', _(u"Draw on the map"): 'DRAW'} def __init__(self, parent, downloadfunc, savefunc): Gtk.Window.__init__(self) self.set_size_request(640, 480) # FIXME:take webpage dimension self.set_title(_(u"Create Kml")) self.set_icon_from_file(ICON_FILENAME) self.set_resizable(True) self.set_type_hint(Gdk.WindowTypeHint.NORMAL) self.downloadfunc = downloadfunc self.savefunc = savefunc self.content = None self.mode = 'BROU' ctx = WebKit2.WebContext.get_default() self.view = WebKit2.WebView.new_with_context(ctx) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_placement(Gtk.CornerType.TOP_LEFT) sw.add(self.view) store = Gtk.ListStore(str) #store.append([_(u'Open Route Service')]) #store.append([_(u'OSRM')]) store.append([_(u'brouter')]) store.append([_(u'yournavigation')]) store.append([_(u'Draw on the map')]) treeview = Gtk.TreeView.new_with_model(store) treeview.connect("cursor-changed", self.mode_changed) renderertext = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Views", renderertext, text=0) column.set_sort_column_id(0) treeview.append_column(column) frame = Gtk.Frame() frame.set_label(_(u'Modes')) frame.add(treeview) swframe = Gtk.Frame() swframe.add(sw) treeview.set_cursor(0) treeview.set_headers_visible(False) obox = Gtk.HBox() obox.pack_start(frame, False, True, 5) obox.pack_start(swframe, True, True, 5) bbox = Gtk.HBox() button_k = Gtk.Button(stock=Gtk.STOCK_OK) button_s = Gtk.Button(stock=Gtk.STOCK_SAVE) button_c = Gtk.Button(stock=Gtk.STOCK_CANCEL) bbox.pack_end(button_k, False, False, 5) bbox.pack_end(button_s, False, False, 5) bbox.pack_end(button_c, False, False, 5) button_k.connect("clicked", self.import_kml) button_s.connect("clicked", self.save_kml) button_c.connect("clicked", self.cancel) box = Gtk.VBox() box.pack_start(obox, True, True, 5) box.pack_start(bbox, False, False, 5) vbox = self.get_content_area() vbox.pack_start(box, True, True, 0) self.show_all() self.webview_init() def webview_init(self): """ Check mode and set webview accordingly""" if self.mode == 'ORS': self.view.load_uri("http://openrouteservice.org/") elif self.mode == 'OSRM': self.view.load_uri("http://map.project-osrm.org/") elif self.mode == 'BROU': self.view.load_uri('http://brouter.de/brouter-web/') elif self.mode == 'YOURS': self.view.load_uri('http://yournavigation.org/') elif self.mode == 'DRAW': HTML_DRAW = "" try: fid = open(os.path.join(os.path.dirname(__file__), "../openlayers.html"), 'r') for line in fid: HTML_DRAW += line fid.close() except IOError: print("openlayers.html not loaded\n") exit(1) self.view.load_html(HTML_DRAW.replace("file://./draw_on_map.js", "file://"+os.path.join(os.path.dirname(__file__), "../draw_on_map.js")), "file:///") def mode_changed(self, widget): """ Change current mode""" selection = widget.get_selection() if selection is None: return model, treeiter = selection.get_selected() if treeiter is None: return newmode = self.MODE[model.get_value(treeiter, 0)] if newmode != self.mode: self.mode = newmode self.webview_init() def import_kml(self, event): """ Import route in CycloGraph""" self.download('IMPORT') def ors_js_finished(self, obj, result, typ): jsresult = self.view.get_title() gpxcontent = str(jsresult) self.view.run_javascript('document.title=oldtitle;', None, None) from xml.sax.saxutils import unescape self.content = io.StringIO(unescape(gpxcontent)).read() if typ == 'SAVE': if not self.content: return self.savefunc(self.content, self.mode) else: self.response(True) return def get_ors(self, typ): """ Returns gpx content from ORS service""" self.view.run_javascript("oldtitle=document.title;document.title=document.getElementById('gpx').innerHTML", None, self.ors_js_finished, typ) return def osrm_js_finished(self, obj, result, typ): jsresult = self.view.get_title() gpxcontent = str(jsresult) url = gpxcontent.split('\'') self.view.run_javascript('document.title=oldtitle;', None, None) self.content = self.downloadfunc(url[1]).read().decode('utf-8') if typ == 'SAVE': if not self.content: return self.savefunc(self.content, self.mode) else: self.response(True) return def get_osrm(self, typ): """ Returns gpx content from ORSM service""" self.view.run_javascript("oldtitle=document.title;document.title=document.getElementsByClassName('text-link')[1].getAttribute('onclick')", None, self.osrm_js_finished, typ) return def brou_js_finished(self, obj, result, typ): jsresult = self.view.get_title() elemtext = str(jsresult) self.view.run_javascript('document.title=oldtitle;', None, None) self.content = self.downloadfunc(elemtext).read().decode('utf-8') if typ == 'SAVE': if not self.content: return self.savefunc(self.content, self.mode) else: self.response(True) return def get_brou(self, typ): """ Returns kml content from BROU service""" self.view.run_javascript("oldtitle=document.title;document.title=$(\".value\").find('a:contains(\"GPX\")')[0].href", None, self.brou_js_finished, typ) return def yours_js_finished(self, obj, result, typ): jsresult = self.view.get_title() elemtext = str(jsresult) text = elemtext.split('href="http://yournavigation.org/?') turl = "http://www.yournavigation.org/api/1.0/gosmore.php?format=kml&" if len(text) > 1: turl += text[1].split("\">")[0] turl = turl.replace("&", "&") self.view.run_javascript('document.title=oldtitle;', None, None) self.content = self.downloadfunc(turl).read().decode('utf-8') if typ == 'SAVE': if not self.content: return self.savefunc(self.content, self.mode) else: self.response(True) return def get_yours(self, typ): """ Returns kml content from YOURS service""" self.view.run_javascript("oldtitle=document.title;document.title=document.getElementById('feature_info').innerHTML", None, self.yours_js_finished, typ) return def draw_js_finished(self, obj, result, typ): kmlstring = self.view.get_title() self.view.run_javascript('document.title=oldtitle;', None, None) self.content = io.StringIO(kmlstring).read() if typ == 'SAVE': if not self.content: return self.savefunc(self.content, self.mode) else: self.response(True) return def get_draw(self, typ): """ Returns kml content from DRAW mode""" self.view.run_javascript("oldtitle=document.title;document.title=createKml()", None, self.draw_js_finished, typ) return def download(self, typ): """ Download selected route""" #Reset the content, it will be filled asynchronously self.content = None if self.mode == 'ORS': self.get_ors(typ) elif self.mode == 'OSRM': self.get_osrm(typ) elif self.mode == 'BROU': self.get_brou(typ) elif self.mode == 'YOURS': self.get_yours(typ) elif self.mode == 'DRAW': self.get_draw(typ) def save_kml(self, event): """Save selected route to file""" self.download('SAVE') def cancel(self, event): """ Cancel create kml dialog""" self.destroy() def show_modal(self): """Shows the dialog and returns True if the value has to be changed.""" return self.run() == 1 FONT_TYP = { "300": "light", "400": "normal", "700": "bold", } def preferences(parent, cgpref, defaultpref): """ Show Preference dialog""" pref = Preferences(parent, cgpref, defaultpref) pref_conf = {} if pref.show_modal(): pref_conf = pref.getconfig() pref.destroy() return pref_conf def formslopeinfo(parent, slopedata): """ Show slope's info dialog""" dlg = FormSlopeInfo(parent, slopedata) if dlg.show_modal(): data = dlg.getslopeinfo() dlg.destroy() return data else: dlg.destroy() return {} def FileDialog(parent, title, dirname, filename, other, file_types, rw, fd_open, fd_save): """ Show file dialog for open or save slopes""" if rw == fd_open: dlg = Gtk.FileChooserDialog(title, parent, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) elif rw == fd_save: dlg = Gtk.FileChooserDialog(title, parent, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) dlg.set_do_overwrite_confirmation(True) if filename: dlg.set_current_name(filename) for elem in file_types: ffilter = Gtk.FileFilter() ffilter.set_name(elem[0]) for pattern in elem[1].split(' '): ffilter.add_pattern(pattern) ffilter.add_pattern(pattern.upper()) # print(pattern) # print("----") dlg.add_filter(ffilter) head, tail = os.path.split(dirname) dlg.set_current_folder(head) if tail: dlg.set_current_name(tail) response = dlg.run() if response == Gtk.ResponseType.OK: filename = dlg.get_filename() dlg.destroy() return str(filename) else: dlg.destroy() return None class ProgressDialog(Gtk.Dialog): """Progress dialog for import Kml function""" def __init__(self, title, label, limit): GObject.GObject.__init__(self) self.set_title(title) self.set_modal(True) self.set_destroy_with_parent(True) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) self.set_size_request(330, 130) box = Gtk.VBox() self.limit = limit self.label = Gtk.Label(label=label) self.pbar = Gtk.ProgressBar() align = Gtk.Alignment.new(0.5, 0.5, 0.5, 0.5) align.show() align.add(self.pbar) self.pbar.show() self.pbar.set_fraction(0.0) self.pbar.set_text("0"+" %") box.pack_start(self.label, False, False, 10) box.pack_start(align, False, False, 5) self.wanttoabort = False self.connect("response", self.abort) carea = self.get_content_area() carea.pack_start(box, True, True, 0) self.show_all() def update(self, value): """Update the progress shown and return if user want to abort.""" self.pbar.set_fraction(value/self.limit) self.pbar.set_text(str(value/10)+" %") return self.wanttoabort def abort(self, dialog, response): """When the abort button is pressed this method is called and it updates the status of this object to take track of user action""" self.wanttoabort = (response in (Gtk.ResponseType.REJECT, Gtk.ResponseType.DELETE_EVENT)) def destroy(self): """Destroy current ProgressDialog""" Gtk.Dialog.destroy(self) class NumcpsDialog(Gtk.Dialog): """ask cps number you want to import""" def __init__(self, max_num_cps, start_dist, max_dist, file_ext): GObject.GObject.__init__(self) self.set_title(_(u"number of checkpoints")) self.set_modal(True) self.set_destroy_with_parent(True) # self.add_button(Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) self.connect("delete-event", self.destroy) self.set_size_request(330, 150) box = Gtk.VBox() self.notebook = Gtk.Notebook() self.notebook.set_show_tabs(False) frame = Gtk.Frame() vbox_1 = Gtk.VBox() label = Gtk.Label(label=_(u"Choose the type of import you want")) vbox_1.pack_start(label, False, False, 15) hboxr = Gtk.HBox() vboxr = Gtk.VBox() self.button1 = Gtk.RadioButton.new_with_label_from_widget(None, _(u"Automatic")) self.button1.set_active(True) vboxr.pack_start(self.button1, False, False, 0) self.button2 = Gtk.RadioButton.new_from_widget(self.button1) self.button2.set_label(_(u"Select the number of checkpoints")) vboxr.pack_start(self.button2, False, False, 0) self.button3 = Gtk.RadioButton.new_from_widget(self.button1) self.button3.set_label(_(u"Select the distance rate")) vboxr.pack_start(self.button3, False, False, 0) self.button4 = Gtk.RadioButton.new_from_widget(self.button1) self.button4.set_label(_(u"Use every point (discouraged)")) vboxr.pack_start(self.button4, False, False, 0) hboxr.pack_start(vboxr, False, False, 30) hbx = Gtk.HBox() self.tick = 0.1 self.startdist = start_dist self.maxdist = max_dist self.bstartdist = Gtk.SpinButton() self.bstartdist.set_range(start_dist, max_dist) self.bstartdist.set_value(start_dist) self.bstartdist.set_increments(self.tick, 1) self.bstartdist.set_numeric(True) self.bstartdist.set_digits(3) self.bstartdist.connect("value_changed", self.changed_dist) self.benddist = Gtk.SpinButton() self.benddist.set_range(start_dist, max_dist) self.benddist.set_value(max_dist) self.benddist.set_increments(self.tick, 1) self.benddist.set_numeric(True) self.benddist.set_digits(3) self.benddist.connect("value_changed", self.changed_dist) hbx.pack_start(Gtk.Label(label=_(u"Start")), False, False, 10) hbx.pack_start(self.bstartdist, False, False, 10) hbx.pack_start(Gtk.Label(label=_(u"End")), False, False, 10) hbx.pack_start(self.benddist, False, False, 10) bbox = Gtk.HBox() button_c = Gtk.Button(label=_(u"Cancel")) button_n = Gtk.Button(label=_(u"Next")) bbox.pack_end(button_n, False, False, 5) bbox.pack_end(button_c, False, False, 5) button_n.connect("clicked", self.next_clicked) button_c.connect("clicked", self.destroy) vbox_1.pack_end(bbox, False, False, 10) vbox_1.pack_end(hbx, False, False, 5) vbox_1.pack_end(hboxr, False, False, 5) frame.add(vbox_1) frame.set_shadow_type(Gtk.ShadowType.NONE) self.notebook.append_page(frame, Gtk.Label(label="1")) frame = Gtk.Frame() label = Gtk.Label(label=_(u"The %s file contains %d points.\nChoose how many will be imported.") % (file_ext, max_num_cps)) self.gpxnum = Gtk.HScale() self.gpxnum.set_range(0, max_num_cps) self.gpxnum.set_increments(1, 1) self.gpxnum.set_digits(0) self.gpxnum.set_size_request(160, 38) self.gpxnum.set_value(0) box.pack_start(label, False, False, 5) box.pack_start(self.gpxnum, False, False, 5) bbox = Gtk.HBox() button_n = Gtk.Button(label=_(u"Finish")) button_c = Gtk.Button(label=_(u"Back")) bbox.pack_end(button_n, False, False, 5) bbox.pack_end(button_c, False, False, 5) button_n.connect("clicked", self.next_clicked) button_c.connect("clicked", self.back_clicked) box.pack_end(bbox, False, False, 10) frame.add(box) frame.set_shadow_type(Gtk.ShadowType.NONE) self.notebook.append_page(frame, Gtk.Label(label="2")) frame = Gtk.Frame() boxdist = Gtk.VBox() hboxdist = Gtk.HBox() selrange = self.benddist.get_value() - self.bstartdist.get_value() label = Gtk.Label(label=_(u"The %s file contains a route of %.3f Km.\nChoose what range you want between points.") % (file_ext, selrange)) self.spingpxdist = Gtk.SpinButton() self.spingpxdist.set_range(0, selrange) self.spingpxdist.set_value(0) self.spingpxdist.set_increments(self.tick, 1) self.spingpxdist.set_numeric(True) self.spingpxdist.set_digits(3) self.spingpxdist.connect("value_changed", self.changed_rangedist) self.gpxdist = Gtk.HScale() self.gpxdist.set_range(0, selrange) self.gpxdist.set_increments(self.tick, 1) self.gpxdist.set_digits(3) self.gpxdist.set_size_request(160, 38) self.gpxdist.set_value(0) self.gpxdist.set_draw_value(False) self.gpxdist.connect("value_changed", self.changed_rangescaledist) hboxdist.pack_start(self.spingpxdist, False, False, 10) hboxdist.pack_start(self.gpxdist, False, False, 5) boxdist.pack_start(label, False, False, 5) boxdist.pack_start(hboxdist, False, False, 15) bbox = Gtk.HBox() button_n = Gtk.Button(label=_(u"Finish")) button_c = Gtk.Button(label=_(u"Back")) bbox.pack_end(button_n, False, False, 5) bbox.pack_end(button_c, False, False, 5) button_n.connect("clicked", self.next_clicked) button_c.connect("clicked", self.back_clicked) boxdist.pack_end(bbox, False, False, 10) frame.add(boxdist) frame.set_shadow_type(Gtk.ShadowType.NONE) self.notebook.append_page(frame, Gtk.Label(label="3")) self.notebook.set_current_page(0) self.notebook.show_all() carea = self.get_content_area() carea.pack_start(self.notebook, True, True, 0) self.show_all() def changed_dist(self, event): "Changed dist" self.bstartdist.set_range(self.startdist, self.benddist.get_value() - self.tick) self.benddist.set_range(self.bstartdist.get_value() + self.tick, self.maxdist) def changed_rangedist(self, event): "Changed range dist" self.gpxdist.set_value(self.spingpxdist.get_value()) def changed_rangescaledist(self, event): "Changed range dist" self.spingpxdist.set_value(self.gpxdist.get_value()) def next_clicked(self, event): "Show next page" if self.notebook.get_current_page() == 0: if self.button1.get_active(): self.response(True) elif self.button2.get_active(): self.notebook.set_current_page(1) self.notebook.show_all() elif self.button3.get_active(): selrange = self.benddist.get_value() - self.bstartdist.get_value() self.spingpxdist.set_range(0, selrange) self.spingpxdist.set_value(0) self.gpxdist.set_range(0, selrange) self.gpxdist.set_value(0) self.notebook.set_current_page(2) self.notebook.show_all() elif self.button4.get_active(): self.response(True) else: self.response(True) def back_clicked(self, event): "Show initial page" self.notebook.set_current_page(0) self.notebook.show_all() def destroy(self, event=None, param=None): """Destroy current dialog""" self.response(False) Gtk.Dialog.destroy(self) def geterrnoroute(parent): """Get an error message explaining that you should have selected one route""" mdlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, _(u'No route')) mdlg.format_secondary_text(_(u"Specify one route first.")) mdlg.run() mdlg.destroy() def managecperr(parent): """Get an error message explaining that you should have inserted altitude and distance""" mdlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, _("Form incomplete")) mdlg.format_secondary_text(_("Distance and altitude are required.")) mdlg.run() mdlg.destroy() def mapcoorderr(parent): """Get an error message explaining that there are no coords in the slope""" mdlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.OK, _("No coordinates")) mdlg.format_secondary_text(_("there aren't coordinates associated with this slope.")) mdlg.run() mdlg.destroy() def geterrnocp(parent): """Get an error message explaining that you should have at least 2 cps to plot""" mdlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, _(u'Not enough check points')) mdlg.format_secondary_text(_(u"""There aren't enougth check points to plot. At least two points are required to plot.""")) mdlg.run() mdlg.destroy() def numcpsdlg(max_num_cps, start_dist, max_dist, file_ext): """ask what number of cps in a gpx or tcx files have to be imported""" dlg = NumcpsDialog(max_num_cps, start_dist, max_dist, file_ext) response = dlg.run() if response is False: return (-1, 0, start_dist, max_dist) if dlg.button1.get_active(): valstart = dlg.bstartdist.get_value() valend = dlg.benddist.get_value() dlg.destroy() return (0, -1, valstart, valend) elif dlg.button2.get_active(): val = dlg.gpxnum.get_value() valstart = dlg.bstartdist.get_value() valend = dlg.benddist.get_value() dlg.destroy() return (0, int(val), valstart, valend) elif dlg.button3.get_active(): val = dlg.gpxdist.get_value() valstart = dlg.bstartdist.get_value() valend = dlg.benddist.get_value() dlg.destroy() return (1, val, valstart, valend) elif dlg.button4.get_active(): valstart = dlg.bstartdist.get_value() valend = dlg.benddist.get_value() dlg.destroy() return (0, max_num_cps, valstart, valend) def assistant_end(parent, value): parent.Close() return (0, 20) def save_changes(parent, filename): """Show a message if you close a modified and not saved slope""" response = None text = ""+(_(u"Save changes to %s before closing?") % filename)+"" secondary_text = _(u"If you don't save, all your changes will be " "permanently lost.") mdlg = Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE, text) mdlg.set_title(_(u'Save changes')) mdlg.set_markup(text) mdlg.format_secondary_text(secondary_text) mdlg.add_buttons(_(u'Close without saving'), Gtk.ResponseType.REJECT, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT) mdlg.set_default_response(Gtk.ResponseType.ACCEPT) response = mdlg.run() mdlg.destroy() if response == Gtk.ResponseType.ACCEPT: return 'SAVE' if response == Gtk.ResponseType.REJECT: return 'DISCARD' if response == Gtk.ResponseType.CANCEL: return 'CANCEL' return response # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/qt/0000755000175000017500000000000013215023054020011 5ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/qt/__init__.py0000644000175000017500000000000012374740515022125 0ustar federicofederico00000000000000cyclograph-1.9.1/cyclograph/qt/glal.py0000644000175000017500000003074313152470300021311 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- # qt.glal.py """This module provides a graphic library abstraction layer for Cyclograph""" # Copyright (C) 2008-2014 Federico Brega, Pierluigi Villani # 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. from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtPrintSupport import QPrinter from PyQt5 import QtCore from version import VERSION from . import gui def initapp(*args): """Create initial app for gui""" app = QApplication(*args) # Tell to the graphic library witch language use translations_path = QtCore.QLibraryInfo.location(QtCore.QLibraryInfo.TranslationsPath) translation_file = "qt_" + str(QtCore.QLocale.system().name()) translator = QtCore.QTranslator(app) translator.load(translation_file, translations_path) app.installTranslator(translator) #Enable support for High resolution disaplays app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) return app def bind(cgfunc, menuopt): """Connect gui menu option with cg function""" return menuopt.triggered.connect(cgfunc) def bind_close(cg_exit, gui): """Connect gui close event with cg exit function""" gui.cg_exit = cg_exit return gui.menu_item_exit.triggered.connect(gui.quit) def ToolbarBind(fun, button): """Connect gui toolbar button with cg function""" return button.triggered.connect(fun) def OptionCheck(guiopt, option): """Set a gui menu option to selected/unselected """ guiopt.setChecked(option) def enable_saving(gui, bool_val): """Enable or disable saving options according to bool_val""" gui.menu_item_save.setEnabled(bool_val) gui.menu_item_save_as.setEnabled(bool_val) gui.action_add.setEnabled(bool_val) gui.action_edit.setEnabled(bool_val) gui.action_delete.setEnabled(bool_val) gui.action_plot.setEnabled(bool_val) gui.action_map.setEnabled(bool_val) gui.action_properties.setEnabled(bool_val) def addstatusbartext(maingui, text): """Add text to main gui status bar""" maingui.addstatusbartext(text) def signalbug(event=None): """Open webpage to signal bugs""" bugUrl = 'http://sourceforge.net/project/memberlist.php?group_id=227295' QDesktopServices.openUrl(QtCore.QUrl(bugUrl)) class Message(QtCore.QObject): """ Qt message""" sent = QtCore.pyqtSignal(str, int, int) #message name, slope_number, row_num def __init__(self): QtCore.QObject.__init__(self) def send(self, message, slope_number, row_num): """ Send message""" self.sent.emit(message, slope_number, row_num) @staticmethod def subscribe(function, emitter, message): """ Subscribe message""" def callback_check(message_sent, slope_number, row_num): if message_sent == message: function(slope_number, row_num) emitter.sent.connect(callback_check) class Notebook: """ Qt Notebook""" def __init__(self, notebook, fun_close): self.notebook = notebook self.fun_close = fun_close self.notebook.setTabsClosable(True) self.notebook.tabCloseRequested[int].connect(fun_close) def Page(self): """ Return qt Page""" return gui.Page(self.notebook) def set_page_label(self, page_num, text): """ Set page label text""" self.notebook.setTabText(page_num, text) def add_page(self, page, title): """ Add page""" self.notebook.addTab(page, title) page.button_close.setVisible(False) self.notebook.setCurrentWidget(page) def remove_page(self, page_num): """ Remove page""" self.notebook.removeTab(page_num) def getselpagnum(self): """ Return number from selected page""" return self.notebook.currentIndex() def setselpagnum(self, page_num): """Set current selected page""" self.notebook.setCurrentWidget(self.get_page(page_num)) def get_page(self, page_num): """ Return page""" page = self.notebook.widget(page_num) return page def get_pagenum(self, argument): """Return page_num from page passed as argument""" return argument[0] class DeviceContext(): """Device Context Qt class wrapper""" def _convqcolor(self, color): """ Convert color""" if isinstance(color, str): if color.startswith("rgb"): col = list(map(int, color[4:-1].split(','))) return QColor(*col) else: return QColor(color) else: return QColor(*color) def init_draw_surf(self, panel): """ Initialize drawing surface""" (self.size_x, self.size_y) = (panel.width(), panel.height()) self.paint = QPainter() self.paint.begin(panel) self.paint.setRenderHint(QPainter.SmoothPixmapTransform) self.paint.setRenderHint(QPainter.Antialiasing) def shear(self, shear): """ device shear""" self.paint.shear(0.0, shear) def getsize(self): """ Return size""" return (self.size_x, self.size_y) def gradfill(self, rect, startcolor, endcolor): """ Fill gradient""" (sx, sy, ex, ey) = (rect[0], rect[1], rect[0], rect[1]+rect[3]) gradient = QLinearGradient(sx, sy, ex, ey) sqcol = self._convqcolor(startcolor) gradient.setColorAt(0, sqcol) eqcol = self._convqcolor(endcolor) gradient.setColorAt(1, eqcol) brush = QBrush(gradient) self.paint.fillRect(QtCore.QRect(*rect), brush) def setpen(self, color, size): """ Set pen's color and size""" qcol = self._convqcolor(color) pen = QPen(qcol) pen.setWidth(size) self.paint.setPen(pen) def setfont(self, fontdict): """ Set font to use""" font = QFont() self.fontmetrics = QFontMetrics(font) try: font.setFamily(fontdict["des"]) font.setPointSize(fontdict["dim"]) font.setWeight(eval('QFont.'+fontdict["typ"].capitalize())) self.paint.setFont(font) except Exception: pass def drawtext(self, text, pos_x, pos_y): """ Draw text at position x,y""" self.paint.drawText(pos_x, pos_y+10, text) def gettextwidth(self, text): """ Return text length""" return self.fontmetrics.width(text) def gettextheight(self, text): """ Return text height""" return self.fontmetrics.height() def drawline(self, pos_x0, pos_y0, pos_x1, pos_y1): """ Draw line""" self.paint.drawLine(pos_x0, pos_y0, pos_x1, pos_y1) def setlineargradientbrush(self, colorlist, startp, endp): """ Get a linear gradient from startp to endp, using colors in colorlist. The elments of colorlist are tuple in the format (color, realtive position).""" grad = QLinearGradient(startp[0], startp[1], endp[0], endp[1]) for color in colorlist: grad.setColorAt(color[1], self._convqcolor(color[0])) brush = QBrush(grad) self.paint.setBrush(brush) def setbrush(self, color): """ Set brush's color""" qcol = self._convqcolor(color) brush = QBrush(qcol) self.paint.setBrush(brush) def drawrectangle(self, pos_x0, pos_y0, width, height): """ Draw rectangle""" self.paint.drawRect(pos_x0, pos_y0, width, height) def drawrotatedtext(self, text, pos_x, pos_y, angle): """ Draw rotated text at position x,y by angle""" self.paint.save() self.paint.rotate(-angle) transform = QTransform() transform.rotate(angle) (newx, newy) = transform.map(pos_x+10, pos_y-5) self.paint.drawText(newx, newy, text) self.paint.restore() def drawpolygon(self, sequence): """ Draw polygon""" points = [QtCore.QPoint(*p) for p in sequence] self.paint.drawPolygon(*points) def startpath(self, point): """ Start a path in the specified point,""" point = QtCore.QPointF(*point) path = QPainterPath() path.moveTo(point) return path def drawpathlineto(self, path, point): """ Draw a straight line from the last point to the given point.""" point = QtCore.QPointF(*point) path.lineTo(point) def drawpathcubicto(self, path, controlpoints): """ Draw a cubic BeziƩr frome the last point using the given list of three control points.""" points = [QtCore.QPointF(pnt[0], pnt[1]) for pnt in controlpoints] path.cubicTo(*points) def endpath(self, path): """ Show the path.""" self.paint.drawPath(path) def end_draw(self): """ Finish drawing""" self.paint.end() del self.paint class Image(): """Image Qt class wrapper""" def __init__(self, size_x, size_y, plotfnct): (self.size_x, self.size_y) = (size_x, size_y) self.plotfnct = plotfnct self.image = QPixmap(size_x, size_y) self.image.fill() # white backgound def plot(self, settings): """ Draw slope to the device context""" dcwrpp = DeviceContext() (dcwrpp.size_x, dcwrpp.size_y) = (self.size_x, self.size_y) dcwrpp.paint = QPainter() dcwrpp.paint.begin(self.image) dcwrpp.paint.setRenderHint(QPainter.SmoothPixmapTransform) dcwrpp.paint.setRenderHint(QPainter.Antialiasing) self.plotfnct(settings, dcwrpp) dcwrpp.end_draw() def savetofile(self, path, format): """ Save image to file""" self.image.save(path, format) class Pdf(): """Qt pdf class wrapper""" def __init__(self, filepath): self.dy = 90 self.dx = 300 self.y_incr = 30 self.printer = QPrinter(QPrinter.ScreenResolution) self.printer.setOutputFormat(QPrinter.PdfFormat) self.printer.setOutputFileName(filepath) self.printer.setFullPage(True) # paperSize = QtCore.QSizeF(793, 1122) # self.printer.setPaperSize(paperSize, QPrinter.Millimeter) self.printer.setCreator('Cyclograph {0}'.format(VERSION)) self.painter = QPainter() self.painter.begin(self.printer) self.painter.setRenderHint(QPainter.SmoothPixmapTransform) self.painter.setRenderHint(QPainter.Antialiasing) self.painter.setFont(QFont('Decorative', 18)) def plot_image(self, settings, size_x, size_y, plotfnct): """ Draw slope to pdf""" image = QPicture() dcwrpp = DeviceContext() (dcwrpp.size_x, dcwrpp.size_y) = (size_x, size_y) dcwrpp.paint = QPainter() dcwrpp.paint.begin(image) dcwrpp.paint.setRenderHint(QPainter.SmoothPixmapTransform) dcwrpp.paint.setRenderHint(QPainter.Antialiasing) plotfnct(settings, dcwrpp) dcwrpp.end_draw() self.painter.drawPicture(0, 0, image) self.dy += size_y def addtext(self, text): self.dy += self.y_incr self.painter.setPen(QColor(0, 0, 0)) self.painter.drawText(self.dx, self.dy, text) def addtitle(self, text): self.dy += self.y_incr self.painter.setPen(QColor(0, 0, 102)) self.painter.drawText(self.dx-50, self.dy, text+":") def save(self): """Save pdf""" self.painter.end() class ProgressDialog(): """ Progress dialog for importkml""" def __init__(self): self.pd = QProgressDialog(_("Downloading altitudes"), _("Cancel"), 0, 1000) def update(self, value): """Update the progress shown and return if user want to abort.""" self.pd.setValue(value) return self.pd.wasCanceled() def destroy(self): """ Destroy progress dialog""" self.pd = None class Timer(): """ Timer for Qt gui""" def __init__(self, period, callback): self.timer = self.timer = QtCore.QTimer() self.period = period self.timer.timeout.connect(callback) def start(self): """ Start timer count""" self.timer.start(self.period) def stop(self): """ Stop timer count""" self.timer.stop() # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/qt/gui.py0000644000175000017500000011347013152470300021155 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- """qt.gui.py""" # Copyright (C) 2009-2017 Federico Brega, Pierluigi Villani # 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. from PyQt5 import QtCore from PyQt5.QtGui import * from PyQt5.QtWidgets import * from . import ui_qt import glal import os.path from themes import ThemeManager from version import VERSION, VERSION_CHECK_ENABLED import io from functools import reduce class Gui(QMainWindow, ui_qt.Ui_MainWindow): """Main frame""" def __init__(self, parent, title, size): QMainWindow.__init__(self, parent) self.setupUi(self) self.setWindowTitle(title) self.notebook.removeTab(0) self.cg_exit = None # check on exit callback self.setContextMenuPolicy(QtCore.Qt.PreventContextMenu) self.resize(QtCore.QSize(size[0], size[1])) # center Gui screen = QDesktopWidget().screenGeometry() dimension = self.geometry() self.move((screen.width() - dimension.width()) / 2, (screen.height() - dimension.height()) / 2) if size[2]: self.showMaximized() # Set standard icons icon1 = QIcon(QPixmap(":/qt-icons/pixmaps/open.png")) icon1 = QIcon.fromTheme("document-open", icon1) self.menu_item_open.setIcon(icon1) icon2 = QIcon(QPixmap(":/qt-icons/pixmaps/save_as.png")) icon2 = QIcon.fromTheme("document-save-as", icon2) self.menu_item_save_as.setIcon(icon2) icon0 = QIcon(QPixmap(":/qt-icons/pixmaps/save.png")) icon0 = QIcon.fromTheme("document-save", icon0) self.menu_item_save.setIcon(icon0) icon3 = QIcon(QPixmap(":/qt-icons/pixmaps/new.png")) icon3 = QIcon.fromTheme("document-new", icon3) self.menu_item_new.setIcon(icon3) icon_about = QIcon.fromTheme("help-about") self.menu_item_about.setIcon(icon_about) icon_bug = QIcon.fromTheme("tools-report-bug") self.menu_item_bug.setIcon(icon_bug) icon_exit = QIcon.fromTheme("application-exit") self.menu_item_exit.setIcon(icon_exit) icon_server = QIcon.fromTheme("network-server") self.menu_Kml_server.setIcon(icon_server) icon_pref = QIcon.fromTheme("preferences-desktop") self.preferences.setIcon(icon_pref) # Group the altitude services to have radio button. serv_Action_group = QActionGroup(self) serv_Action_group.addAction(self.menu_item_s1) serv_Action_group.addAction(self.menu_item_s2) serv_Action_group.addAction(self.menu_item_s3) self.menu_item_about.triggered.connect(self.onabout) def closeEvent(self, event): """If a close event is generated call the controller""" if self.cg_exit(): event.accept() else: event.ignore() def quit(self): """ Exit from CycloGraph""" self.close() def getdimensions(self): """Return window width and height""" return (self.size().width(), self.size().height()) def ismaximized(self): """Return true if window is maximized""" return self.isMaximized() def addstatusbartext(self, text): """Show text in the statusbar for 5000 ms""" self.statusbar.showMessage(text, 5000) def onabout(self): """About dialog""" description = """CycloGraph""" licence = """ CycloGraph 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. CycloGraph 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 http://www.gnu.org/licenses/. """ text = 'Copyright (C) 2008-2017 Federico Brega, Pierluigi Villani\n' + \ 'http://cyclograph.sourceforge.net\n' + licence QMessageBox.about(self, description +" "+ VERSION, text) class Page(QFrame, ui_qt.Ui_Frame_Page): """Page showed in a tab""" def __init__(self, parent): QFrame.__init__(self, parent) self.savedfilepath = None self.slopecounternew = 0 self.modified = False self.setupUi(self) def insert_row(self, row_num, cp): """Insert item to this page at position row_num""" self.tableWidget.insertRow(row_num) for col in range(len(cp)): item = QTableWidgetItem(str(cp[col])) self.tableWidget.setItem(row_num, col, item) def delete_row(self, row_num): """Removes row at position row_num in this page.""" self.tableWidget.removeRow(row_num) def get_sel_row(self): """Gives the row number that is selected.""" row = self.tableWidget.currentRow() return row def get_sel_multi_row(self): """Gives a list with the row number of selected items.""" rows = [self.tableWidget.row(item) \ for item in self.tableWidget.selectedItems()] rows = list(set(rows)) rows.sort() return rows class Managecp(QDialog, ui_qt.Ui_manag_dlg): """Add and edit dialog""" def __init__(self, parent, title, dist="", alt="", name=""): QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(title) self.distance.setText(dist) self.altitude.setText(alt) self.name.setText(name) def show_modal(self): """Shows the dialog and returns True if the value has to be chenged.""" val = self.exec_() return val == QDialog.Accepted def destroy(self): """Destroy current managecp""" pass def getcp(self): """Gives the modified values.""" dist = self.distance.text() alt = self.altitude.text() name = self.name.text() return (dist, alt, name) class FormSlopeInfo(QDialog, ui_qt.Ui_Slope_Dialog): """info dialog about the slope""" def __init__(self, parent, slopedata): QDialog.__init__(self, parent) self.setupUi(self) self.s_name.setText(slopedata['name']) self.s_state.setText(slopedata['state']) self.s_author.setText(slopedata['author']) self.s_email.setText(slopedata['email']) self.average_grad.setText(slopedata['average_grad']) self.max_grad.setText(slopedata['max_grad']) self.height_difference.setText(slopedata['height_difference']) self.height_gain.setText(slopedata['height_gain']) self.s_comment.setText(slopedata['comment']) def getslopeinfo(self): """Returns the slope infos: name, state, comment""" return {'name': self.s_name.text(), 'state': self.s_state.text(), 'author': self.s_author.text(), 'email': self.s_email.text(), 'comment': self.s_comment.toPlainText()} FONT_TYP = { QFont.Light: "light", QFont.Normal: "normal", QFont.Bold: "bold", QFont.DemiBold: "bold", QFont.Black: "bold", } class Preferences(QDialog, ui_qt.Ui_Pref_Dialog): """Set gradient's color""" def __init__(self, parent, pref, default_pref): QDialog.__init__(self, parent) self.setupUi(self) self.default = default_pref # General self.olines.setChecked(pref['olines']) self.effect3d.setChecked(pref['3d']) self.legend.setChecked(pref['legend']) self.showinfo.setChecked(pref['sinfo']) self.vcheck.setChecked(pref['vcheck']) if not VERSION_CHECK_ENABLED: self.label.hide() self.vcheck.hide() res = 0 if pref['width'] == 1600: res = 3 if pref['width'] == 1280: res = 2 elif pref['width'] == 1024: res = 1 self.res.setCurrentIndex(res) current = 0 for theme in pref['themelist']: self.theme.addItem("") self.theme.setItemText(current, theme) if theme == pref['theme']: self.theme.setCurrentIndex(current) current += 1 self.serv.setCurrentIndex(0) if pref['serv'] == 'usgs.net': self.serv.setCurrentIndex(2) elif pref['serv'] == 'earthtools.org': self.serv.setCurrentIndex(1) # Fonts self.fontbuttonlist = [self.fdesc, self.fgrad, self.ftitle] self.fontbuttongroup = QButtonGroup(parent) for id_, button in enumerate(self.fontbuttonlist): self.fontbuttongroup.addButton(button, id_) desc = pref['fdesc'] self.fdesc.setText(desc['des']+' | '+str(desc["dim"])) self.fdesc.desc = desc desc = pref['fgrad'] self.fgrad.setText(desc['des']+' | '+str(desc["dim"])) self.fgrad.desc = desc desc = pref['ftitle'] self.ftitle.setText(desc['des']+' | '+str(desc["dim"])) self.ftitle.desc = desc self.fontbuttongroup.buttonClicked[int].connect(self.font_button_clicked) # Color group buttons self.buttonlist = [self.button1, self.button2, self.button3, self.button4, self.button5, self.button6, self.button7] self.buttongroup = QButtonGroup(parent) for id_, button in enumerate(self.buttonlist): self.buttongroup.addButton(button, id_) self.colorlist = [[int(x) for x in color[4:-1].split(',')] \ for color in pref['colors']] for index in range(len(self.buttonlist)): self.setcolor(index, self.colorlist[index]) self.buttonBox.clicked['QAbstractButton *'].connect(self.restore_default) self.bindsgc() # Author info self.aname.setText(pref['aname']) self.amail.setText(pref['amail']) # Level group SpinButtons self.levlist = [self.lev_1, self.lev_2, self.lev_3, self.lev_4, self.lev_5, self.lev_6, self.lev_7] for i in range(len(pref['levels'])): self.levlist[i].setValue(float(pref['levels'][i])) self.levlist[i].editingFinished.connect(self.updatelevelconstraints) self.updatelevelconstraints() def restore_default(self, button): button_id = self.buttonBox.standardButton(button) if button_id == QDialogButtonBox.RestoreDefaults: self.olines.setChecked(bool(self.default['orizzontal lines'])) self.showinfo.setChecked(bool(self.default['slopeinfo'])) self.effect3d.setChecked(bool(self.default['effect-3d'])) self.vcheck.setChecked(bool(self.default['latestVersionCheck'])) res = 0 if self.default['width'] == 1600: res = 3 if self.default['width'] == 1280: res = 2 elif self.default['width'] == 1024: res = 1 self.res.setCurrentIndex(res) index = self.theme.findText(self.default['theme']) if index: self.theme.setCurrentIndex(index) self.serv.setCurrentIndex(0) if self.default['server'] == 'usgs.net': self.serv.setCurrentIndex(2) elif self.default['server'] == 'earthtools.org': self.serv.setCurrentIndex(1) self.fdesc.setText(self.default['font-des 1']+' | '+self.default['font-dim 1']) self.fdesc.desc = { "des": self.default['font-des 1'], "typ": self.default['font-typ 1'], "dim": self.default['font-dim 1'] } self.fgrad.setText(self.default['font-des g']+' | '+self.default['font-dim g']) self.fgrad.desc = { "des": self.default['font-des g'], "typ": self.default['font-typ g'], "dim": self.default['font-dim g'] } self.ftitle.setText(self.default['font-des t']+' | '+self.default['font-dim t']) self.ftitle.desc = { "des": self.default['font-des t'], "typ": self.default['font-typ t'], "dim": self.default['font-dim t'] } self.aname.setText(self.default['authorname']) self.amail.setText(self.default['authormail']) colorlist = [self.default["color "+str(i)] for i in range(1, 8)] self.colorlist = [list(map(int, color[4:-1].split(','))) \ for color in colorlist] for index in range(len(self.buttonlist)): self.setcolor(index, self.colorlist[index]) levellist = [self.default["level "+str(i)] for i in range(1, 8)] for i in range(len(self.levlist)): # disable range fot this widget self.levlist[i].setMinimum(0.0) self.levlist[i].setMaximum(90.0) # set the range for this widget self.levlist[i].setValue(float(levellist[i])) self.updatelevelconstraints() def bindsgc(self): """Bind Setgcolor buttons """ self.buttongroup.buttonClicked[int].connect(self.changecolor) def setcolor(self, index, color): """Set color in the colorlist""" qcolor = QColor(*color) palette = self.buttonlist[index].palette() palette.setColor(QPalette.Button, qcolor) self.colorlist[index] = (color[0], color[1], color[2]) self.buttonlist[index].setStyleSheet("QWidget { background-color: %s }" % qcolor.name()) def updatelevelconstraints(self): for i in range(1, len(self.levlist)): self.levlist[i-1].setMaximum(self.levlist[i].value()-0.1) for i in range(len(self.levlist)-1): self.levlist[i+1].setMinimum(self.levlist[i].value()+0.1) def changecolor(self, index): """Change the color used by plot to fill different classes of gradients """ button = self.buttonlist[index] palette = button.palette() qcolor = palette.color(QPalette.Button) newcolor = QColorDialog.getColor(qcolor, self) if newcolor.isValid(): self.setcolor(index, (newcolor.red(), newcolor.green(), newcolor.blue())) def font_button_clicked(self, index): button = self.fontbuttonlist[index] qfont = QFont() qfont.setFamily(button.desc["des"]) qfont.setPointSize(int(button.desc["dim"])) try: res = eval('QFont.'+button.desc["typ"].capitalize()) except AttributeError: res = QFont.Normal qfont.setWeight(res) (qfont, ok) = QFontDialog.getFont(qfont, self) if ok: button.desc = { "des": str(qfont.family()), "typ": FONT_TYP[qfont.weight()], "dim": str(qfont.pointSize()) } button.setText(str(qfont.family())+' | '+str(qfont.pointSize())) def getconfig(self): """Returns selected config options""" res = str(self.res.currentText()).split(' ') settings = { 'olines': self.olines.isChecked(), 'theme': str(self.theme.currentText()), 'sinfo': self.showinfo.isChecked(), 'legend': self.legend.isChecked(), 'vcheck': self.vcheck.isChecked(), '3d': self.effect3d.isChecked(), 'aname': self.aname.text(), 'amail': self.amail.text(), 'fdesc': self.fdesc.desc, 'ftitle': self.ftitle.desc, 'fgrad': self.fgrad.desc, 'width': res[0], 'height': res[2], 'serv': str(self.serv.currentText()), 'colors': ['rgb(%d,%d,%d)' % (clr[0], clr[1], clr[2]) \ for clr in self.colorlist], 'levels': ['%f' % (lev.value()) for lev in self.levlist] } if not VERSION_CHECK_ENABLED: del settings['vcheck'] return settings class Plot(QMainWindow): """ Plot window""" def __init__(self, parent, slope, plot_settings, exportfunc, exportfunc_pdf, exportfunc_html): QMainWindow.__init__(self, parent) self.panel = AnimatedPanel(slope) self.setCentralWidget(self.panel) title = "CycloGraph" if slope.name: title += " - " + slope.name self.setWindowTitle(title) self.myslope = slope self.menu_item_save = QAction(_('Save'), self) icon_save = QIcon(QPixmap(":/qt-icons/pixmaps/save.png")) icon_save = QIcon.fromTheme("document-save-as", icon_save) self.menu_item_save.setIcon(icon_save) self.menu_item_savepdf = QAction(_('Save PDF'), self) self.menu_item_savehtml = QAction(_('Save html'), self) self.menu_item_olines = QAction(self) self.menu_item_olines.setCheckable(True) self.menu_item_olines.setChecked(plot_settings['olines']) self.menu_item_olines.setText(_("&Orizzontal lines")) self.menu_item_3d = QAction(self) self.menu_item_3d.setCheckable(True) self.menu_item_3d.setChecked(plot_settings['3d']) self.menu_item_3d.setText(_("3D &effect")) self.menu_item_legend = QAction(self) self.menu_item_legend.setCheckable(True) self.menu_item_legend.setChecked(plot_settings['legend']) self.menu_item_legend.setText(_("Show &legend")) self.themeitems = {} self.theme_action_group = QActionGroup(self) self.thememanager = ThemeManager() for theme in self.thememanager.getthemeslist(): themeitem = QAction(self) themeitem.setCheckable(True) if plot_settings['theme'] == theme: themeitem.setChecked(True) themeitem.setText(theme) self.theme_action_group.addAction(themeitem) self.themeitems[themeitem] = theme menubar = self.menuBar() options_menu = menubar.addMenu(_('&Options')) options_menu.addAction(self.menu_item_save) options_menu.addAction(self.menu_item_savepdf) options_menu.addAction(self.menu_item_savehtml) options_menu.addAction(self.menu_item_olines) options_menu.addAction(self.menu_item_3d) options_menu.addAction(self.menu_item_legend) themes_menu = QMenu(options_menu) themes_menu.setTitle(_("&Theme")) for action in list(self.themeitems.keys()): themes_menu.addAction(action) options_menu.addAction(themes_menu.menuAction()) res_menu = QMenu(options_menu) res_menu.setTitle(_("&Saving resolution")) self.resitems = {} res_action_group = QActionGroup(self) rlist = [('800', '600'), ('1024', '768'), ('1280', '800'), ('1600', '1200')] for res in rlist: resitem = QAction(self) resitem.setCheckable(True) restext = res[0]+'x'+res[1] resitem.setText(restext) if plot_settings['width'] == int(res[0]): resitem.setChecked(True) res_action_group.addAction(resitem) self.resitems[resitem] = restext res_menu.addAction(resitem) options_menu.addAction(res_menu.menuAction()) self.exportfunc = exportfunc self.exportfunc_pdf = exportfunc_pdf self.exportfunc_html = exportfunc_html self.myslope.calc() self.settings = plot_settings self.panel.settings = self.settings self.panel.devc = glal.DeviceContext() self.resize(plot_settings['width'], plot_settings['height']) self.settings['colors'] = plot_settings['colors'] self.animation3d = QtCore.QPropertyAnimation(self.panel, b"depth") self.animation3d.setDuration(1000) self.animation3d.setStartValue(100) self.animation3d.setEndValue(0) self.animationTh = QtCore.QPropertyAnimation(self.panel, b"opacity") self.animationTh.setDuration(1000) self.animationTh.setStartValue(1.0) self.animationTh.setEndValue(0.0) self.menu_item_save.triggered.connect(self.saveimage) self.menu_item_savepdf.triggered.connect(self.savepdf) self.menu_item_savehtml.triggered.connect(self.savehtml) self.menu_item_olines.triggered.connect(self.olines) self.menu_item_3d.triggered.connect(self.change3d) self.menu_item_legend.triggered.connect(self.changelegend) self.theme_action_group.triggered['QAction *'].connect(self.changetheme) res_action_group.triggered['QAction *'].connect(self.changeres) def showplot(self): """ Show plot""" self.show() def saveimage(self): """ Save image""" self.exportfunc(self.myslope, self.settings) def savepdf(self): """ Save pdf""" self.exportfunc_pdf(self.myslope, self.settings) def savehtml(self): """ Save html report""" self.exportfunc_html(self.myslope, self.settings) def change3d(self): """ If active switch 3d effect off, else turn it on.""" if self.settings['3d']: self.animation3d.setDirection(self.animation3d.Forward) else: self.animation3d.setDirection(self.animation3d.Backward) self.animation3d.start() def changelegend(self): """ If active draws legend""" self.panel.shot() self.settings['legend'] = not self.settings['legend'] self.animationTh.start() def olines(self): """ If active draws orizzontal lines""" self.panel.shot() self.settings['olines'] = not self.settings['olines'] self.animationTh.start() def changetheme(self, action): """Change the actual slope theme.""" self.panel.shot() self.settings["theme"] = self.themeitems[action] self.animationTh.start() def changeres(self, action): """Change saving resolution.""" text = self.resitems[action] width, height = text.split('x') self.settings['width'] = int(width) self.settings['height'] = int(height) class AnimatedPanel(QWidget): """This class implements a personalized widget that draws a slope. It also implements the fade between different images and 2D to 3D. Using this class prevents from drawing on the menu.""" def __init__(self, slope, parent=None): QWidget.__init__(self, parent) self.__old_opacity = 0.0 self.__depth = 100 self.myslope = slope def shot(self): """Create an internal pixmap representing the current state of the widget""" self.__old_pixmap = QPixmap(self.size()) self.render(self.__old_pixmap) def oldOpacity(self): """Get the opacity of the pixamp of the old slope""" return self.__old_opacity def setOldOpacity(self, opacity): """Set the opacity of the pixamp of the old slope""" self.__old_opacity = opacity self.repaint() def changeres(self, action): """Change saving resolution.""" text = self.resitems[action] width, height = text.split('x') self.settings['width'] = int(width) self.settings['height'] = int(height) opacity = QtCore.pyqtProperty("double", oldOpacity, setOldOpacity) def depth(self): """Get the depth of the current slope (0 means 2D)""" return self.__depth def setDepth(self, depth): """Set the depth of the current slope""" self.__depth = depth self.settings['3d'] = (depth != 0) self.repaint() depth = QtCore.pyqtProperty("int", depth, setDepth) def paintEvent(self, event): """Actually draws the content.""" self.myslope.depth = self.__depth self.devc.init_draw_surf(self) self.myslope.paint(self.settings, self.devc) if self.__old_opacity > 0.0: self.devc.paint.resetTransform() self.devc.paint.setOpacity(self.__old_opacity) self.devc.paint.drawPixmap(0, 0, self.__old_pixmap) self.devc.end_draw() class Map(QDialog, ui_qt.Ui_map): """Show map of slopes""" def __init__(self, parent, slopename, stringc, savefnct): QDialog.__init__(self, parent) self.setupUi(self) self.savefnct = savefnct self.setWindowFlags(QtCore.Qt.Window) self.setWindowTitle(_("Map") + " - " + slopename) self.buttonBox.clicked['QAbstractButton *'].connect(self.button_clicked) mapfile = QtCore.QFile(":/openlayers/cyclograph/map.html") if mapfile.open(QtCore.QFile.ReadOnly): mapdata = bytes(mapfile.readAll()).decode() htmlMap = mapdata % (stringc,) mapfile.close() self.webView.setHtml(htmlMap, QtCore.QUrl('file://')) self.show() def saveimage(self, filepath): """ Save map image""" page = self.webView.page() img = QImage(page.contentsSize().toSize(), QImage.Format_ARGB32) painter = QPainter(img) self.webView.render(painter) painter.end() img.save(filepath) def button_clicked(self, button): button_id = self.buttonBox.standardButton(button) if button_id == QDialogButtonBox.Save: self.savefnct(self) class Create_kml(QDialog, ui_qt.Ui_Create_KML_Dialog): """Create kml dialog""" MODE = {_("Open Route Service"): 'ORS', _(u'brouter'): 'BROU', _('yournavigation'): 'YOURS', _("Draw on the map"): 'DRAW'} def __init__(self, parent, downloadfunc, savefunc): QDialog.__init__(self, parent) self.setupUi(self) self.setWindowFlags(QtCore.Qt.Window) self.downloadfunc = downloadfunc # used to downlaod from url self.savefunc = savefunc self.content = None self.mode = None self.mode_changed(self.listWidget.currentItem().text()) self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 3) self.buttonBox.clicked['QAbstractButton *'].connect(self.button_clicked) self.listWidget.currentTextChanged['const QString&'].connect(self.mode_changed) def webview_init(self): """ Check mode and set webview accordingly""" if self.mode == 'ORS': self.webView.setUrl(QtCore.QUrl("http://openrouteservice.org/")) elif self.mode == 'BROU': self.webView.setUrl(QtCore.QUrl("http://brouter.de/brouter-web/")) elif self.mode == 'YOURS': self.webView.setUrl(QtCore.QUrl("http://yournavigation.org/")) elif self.mode == 'DRAW': url = QtCore.QUrl("qrc:/openlayers/cyclograph/openlayers.html") # .fromLocalFile(os.path.join(os.path.dirname(__file__),"openlayers.html")) self.webView.setUrl(url) def button_clicked(self, button): button_id = self.buttonBox.standardButton(button) if button_id == QDialogButtonBox.Ok: self.import_kml() elif button_id == QDialogButtonBox.Save: self.save_kml() elif button_id == QDialogButtonBox.Cancel: self.content = None #Close the dialog immediately QDialog.reject(self) def mode_changed(self, sel_string): """ Change current mode""" newmode = self.MODE[str(sel_string)] if newmode != self.mode: self.mode = newmode self.webview_init() def get_ors(self, callback): page = self.webView.page() #jsresult = frame.evaluateJavaScript("document.getElementById('gpx').innerHTML") def jscallback(jsresult): if jsresult is None: self.content = None return gpxcontent = str(jsresult) from xml.sax.saxutils import unescape self.content = unescape(gpxcontent) callback() page.runJavaScript("document.getElementById('gpx').innerHTML", jscallback) #FIXME: this is not working: getElementById returns null def get_brou(self, callback): """ Returns kml content from BROU service""" page = self.webView.page() def jscallback(jsresult): if jsresult is None: self.content = None return url = str(jsresult) handler = self.downloadfunc(url) self.content = handler.read().decode('utf-8') callback() page.runJavaScript("$(\".value\").find('a:contains(\"GPX\")')[0].href", jscallback) def get_yours(self, callback): """ Returns kml content from YOURS service""" page = self.webView.page() def jscallback(jsresult): if jsresult is None: self.content = None return elemtext = str(jsresult) text = elemtext.split('href="http://yournavigation.org/?') turl = "http://www.yournavigation.org/api/1.0/gosmore.php?format=kml&" if len(text) > 1: turl += text[1].split("\">")[0] turl = turl.replace("&", "&") handler = self.downloadfunc(turl) self.content = handler.read().decode('utf-8') callback() page.runJavaScript("document.getElementById('feature_info').innerHTML", jscallback) def get_draw(self, callback): page = self.webView.page() def jscallback(jsresult): self.content = jsresult callback() page.runJavaScript("createKml()", jscallback) def download(self, callback): if self.mode == 'ORS': self.get_ors(callback) elif self.mode == 'BROU': self.get_brou(callback) elif self.mode == 'YOURS': self.get_yours(callback) elif self.mode == 'DRAW': self.get_draw(callback) def import_kml(self): def import_callback(): QDialog.accept(self) self.download(import_callback) def save_kml(self): def download_callback(): if not self.content: return self.savefunc(self.content, self.mode) # Do not accept the dialog: it must stay open if the user wants to # import without opening the file saved or if he hasn't saved and # wants to go back. self.download(download_callback) def show_modal(self): """Shows the dialog and returns True if the value has to be chenged.""" val = self.exec_() return val == QDialog.Accepted class NumcpsDialog(QDialog, ui_qt.Ui_numcps_dlg): """ask cps number you want to import""" def __init__(self, max_num_cps, start_dist, finish_dist, file_ext): QDialog.__init__(self, None) self.setupUi(self) self.slider.setMaximum(max_num_cps) self.spinBox.setMaximum(max_num_cps) self.start.setRange(start_dist, finish_dist) self.start.setValue(start_dist) self.end.setRange(start_dist, finish_dist) self.end.setValue(finish_dist) selrange = self.end.value() - self.start.value() # the value of the slider_2 is multiplied by 1000 to have a resolution of 0.001 self.slider_2.setMaximum(selrange*1000) self.doubleSpinBox_2.setMaximum(selrange) text = _("The %s file contains %d points.\nChoose how many will be imported.") % (file_ext, max_num_cps) self.label.setText(text) self.file_ext = file_ext self.label_2.setText(_("The %s file contains a route of %.3f Km.\nChoose what range you want between points.") % (file_ext, finish_dist)) self.label_3.setText(_("Choose the type of import you want")) self.proceedButton.clicked.connect(self.onProceedClicked) self.start.valueChanged.connect(self.updateRange) self.end.valueChanged.connect(self.updateRange) self.slider_2.valueChanged.connect(self.updateSlider_2) self.doubleSpinBox_2.valueChanged.connect(self.updateRange_2) def onProceedClicked(self): btn = self.buttonGroup.checkedButton() if btn == self.all_ or btn == self.automatic: self.accept() elif btn == self.number: self.stackedWidget.setCurrentIndex(1) elif btn == self.distance_2: self.stackedWidget.setCurrentIndex(2) def updateRange(self): "The range changed" self.start.setMaximum(self.end.value() - self.end.singleStep()) self.end.setMinimum(self.start.value() + self.start.singleStep()) self.slider_2.setMaximum((self.end.value() - self.start.value())*1000) self.doubleSpinBox_2.setMaximum(self.end.value() - self.start.value()) def updateRange_2(self): "The range_2 changed" spinval = self.doubleSpinBox_2.value() self.slider_2.setValue(int(spinval*1000)) def updateSlider_2(self): "The Slider_2 value changed" sliderval = self.slider_2.value()/1000.0 self.doubleSpinBox_2.setValue(sliderval) def preferences(parent, cgpref, defaultpref): """ set Preference dialog""" dlg = Preferences(parent, cgpref, defaultpref) if dlg.exec_() == QDialog.Rejected: return {} else: return dlg.getconfig() def formslopeinfo(parent, slopedata): """ Show slope's info""" dlg = FormSlopeInfo(parent, slopedata) if dlg.exec_() == QDialog.Rejected: return {} else: return dlg.getslopeinfo() def FileDialog(parent, title, dirname, filename, other, file_types, rw, FD_OPEN, FD_SAVE): """ Show File dialog""" filters = reduce(lambda x, y: x + ';;' + y, [("%s (%s)" % elem) for elem in file_types]) # print filters if rw == FD_OPEN: result = QFileDialog.getOpenFileName(parent, title, dirname, filters) elif rw == FD_SAVE: result = QFileDialog.getSaveFileName(parent, title, dirname, filters) if len(result) == 2: #result is a tuple (filename, selected_file_option) return str(result[0]) else: return None def numcpsdlg(max_num_cps, start_dist, finish_dist, file_ext): """ask how many cps in a gpx or tcx files shall be imported""" dlg = NumcpsDialog(max_num_cps, start_dist, finish_dist, file_ext) if dlg.exec_() == QDialog.Rejected: return (-1, 0, start_dist, finish_dist) elif dlg.buttonGroup.checkedButton() == dlg.automatic: return (0, -1, dlg.start.value(), dlg.end.value()) elif dlg.buttonGroup.checkedButton() == dlg.all_: return (0, max_num_cps, dlg.start.value(), dlg.end.value()) elif dlg.stackedWidget.currentIndex() == 1: return (0, dlg.slider.value(), dlg.start.value(), dlg.end.value()) else: value = dlg.doubleSpinBox_2.value() return (1, value, dlg.start.value(), dlg.end.value()) def geterrnoroute(parent): """Get an error message explaining that you should have selected one route""" return QMessageBox.warning(parent, _('No route'), _("Specify one route first."), QMessageBox.Ok) def managecperr(parent): """Get an error message explaining that you should have inserted altitude and distance""" return QMessageBox.critical(parent, _("Form incomplete"), _("Distance and altitude are required."), QMessageBox.Ok) def mapcoorderr(parent): """Get an error message explaining that there are no coords in the slope""" return QMessageBox.critical(parent, _("No coordinates"), _("there aren't coordinates associated with this slope."), QMessageBox.Ok) def geterrnocp(parent): """Get an error message explaining that you should have at least two cps to plot """ return QMessageBox.warning(parent, _('Not enough check points'), _( """There aren't enougth check points to plot. At least two points are required to plot."""), QMessageBox.Ok) def save_changes(parent, filename): """Show a message if you close a modified and not saved slope""" response = None SAVE = _('Save') DISCARD = _('Close without saving') CANCEL = _('Cancel') message = QMessageBox(parent) message.setText(_("Save changes to %s before closing?") % filename) message.setWindowTitle(_('Save changes')) message.setIcon(QMessageBox.Warning) save = message.addButton(SAVE, QMessageBox.AcceptRole) discard = message.addButton(DISCARD, QMessageBox.DestructiveRole) cancel = message.addButton(CANCEL, QMessageBox.RejectRole) message.exec_() response = message.clickedButton() if response == save: return 'SAVE' if response == discard: return 'DISCARD' if response == cancel: return 'CANCEL' return response # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/__init__.py0000644000175000017500000000004312327255440021504 0ustar federicofederico00000000000000# vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/altitude_downloader.py0000644000175000017500000001505513144673265024016 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """altitude_downloader.py""" # Copyright (C) 2008-2014 Federico Brega, Pierluigi Villani # 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. import logging import math import threading from urllib.request import urlopen from urllib.error import URLError import re ALTITUDE_CACHE = {} SERVICES = frozenset(("geonames.org", "earthtools.org", "usgs.net")) _ACTIVE_SERVICE = "geonames.org" def geonames_org(lat, lng): """ Use geonames.org STRM3 to download data""" url = 'http://api.geonames.org/srtm3?lat='+lat+'&lng='+lng+'&username=cyclograph' line = '' tries = 5 # Sometimes geonames retuns an xml header, which is wrong, so try again. # the first character might be a minus sign, we keep it simple while not line.isdigit() and tries > 0: han = urlopen(url) line = han.readline().strip() tries -= 1 han.close() alt = float(line) return alt def earthtools_org(lat, lng): """ Use earthtools.org to download data""" # http://www.earthtools.org/height// url = 'http://www.earthtools.org/height/'+lat+'/'+lng han = urlopen(url) alt = re.search(r"(-?\d+)<\/meters>", han.read()).group(1) alt = float(alt) han.close() return alt def usgs_net(lat, lng): """ Use usgs.net to download data""" url = 'http://gisdata.usgs.net/XMLWebServices/' + \ 'TNM_Elevation_Service.asmx/getElevation?X_Value=' + \ lng + '&Y_Value=' + lat + \ '&Elevation_Units=METERS&Source_Layer=-1&Elevation_Only=TRUE' han = urlopen(url) match = re.search(r"<.+>(-?\d+[\.\d]*)<\/.+>", han.read()).group(1) alt = float(match) return alt SERVER_DOWNLOADER = {"geonames.org": geonames_org, "earthtools.org": earthtools_org, "usgs.net": usgs_net } # WARNING! this module is not thread safe, maybe you can use a lock to use # more threads. def choose_service(newserv): """ Choose the service to use for retreiving altitude data""" global _ACTIVE_SERVICE global ALTITUDE_CACHE if newserv not in SERVICES: return -1 if newserv != _ACTIVE_SERVICE: # Because different services may give different datas # we clear the cache if we change service. ALTITUDE_CACHE = {} _ACTIVE_SERVICE = newserv return 0 class ImporterThread(threading.Thread): """ Download altitude""" def __init__(self, outqueue, slopefile, num, start_dist, end_dist, seltype): threading.Thread.__init__(self) self.outq = outqueue self._want_abort = False self.slopefile = slopefile self.service = _ACTIVE_SERVICE self.status = 'OK' self.num_cps = num self.seltype = seltype self.start_dist = start_dist self.end_dist = end_dist def run(self): """ Task to execute while running""" if self.service not in SERVER_DOWNLOADER: self.status = "Error: service unknown" return getalt = SERVER_DOWNLOADER[self.service] self.slopefile.setcpToLoad(self.num_cps, self.start_dist, self.end_dist, self.seltype) cps = self.slopefile.cps usedcp = self.slopefile.cpToLoad for i in range(len(usedcp)): logging.debug("cpi:"+str(i)+"\tcp:"+str(cps[usedcp[i]])) (dist, alt, name, lat, lng) = cps[usedcp[i]] if (lat, lng) in ALTITUDE_CACHE: # altitude has already been downloaded alt = ALTITUDE_CACHE[(lat, lng)] else: # try to download the altitude try: alt = getalt(lat, lng) except URLError: self.status = 'Error: No network' return ALTITUDE_CACHE[(lat, lng)] = alt progr = 1000 * i / (len(usedcp) - 1) self.outq.put((progr, (dist, alt, name))) if self._want_abort: self.status = 'Aborted' return def abort(self): """abort worker thread. Method for use by main thread to signal an abort. """ self._want_abort = True def point_conversion(lng, lat): """ return radians from coordinates""" return (math.radians(float(lng)), math.radians(float(lat))) def distance(coords): """Calculates the distance from a list of coordinates.""" (lng, lat) = coords[0] (lng_old, lat_old) = point_conversion(lng, lat) dist = 0.0 cps = [] for pnt in coords: if len(pnt) != 2: continue (lng, lat) = pnt (lng_new, lat_new) = point_conversion(lng, lat) dist += lambert_formulae(lat_old, lat_new, lng_old, lng_new) cps.append((dist, lng.strip('\n\t'), lat.strip('\n\t'))) (lng_old, lat_old) = (lng_new, lat_new) return cps R_EARTH = 6367.45 # km def haversine_angle(lat_old, lat_new, lng_old, lng_new): """Haversine formula""" alpha = (math.sin((lat_old-lat_new)/2))**2 \ + math.cos(lat_old) * math.cos(lat_new) * (math.sin((lng_old-lng_new)/2)**2) return 2 * math.atan2(math.sqrt(alpha), math.sqrt(1-alpha)) def haversine(lat_old, lat_new, lng_old, lng_new): return R_EARTH * haversine_angle(lat_old, lat_new, lng_old, lng_new) R_EARTH_EQUATOR = 6378.137 # km WGS 84 r = 298.257223563 # inverse of eccentricity WGS 84 def lambert_formulae(lat_old, lat_new, lng_old, lng_new): red_lat_old = math.atan((r - 1) / r * math.tan(lat_old)) red_lat_new = math.atan((r - 1) / r * math.tan(lat_new)) sigma = haversine_angle(red_lat_old, red_lat_new, lng_old, lng_new) if sigma == 0.0: return 0.0 P = (red_lat_old + red_lat_new) / 2 Q = (red_lat_new - red_lat_old) / 2 X = (sigma - math.sin(sigma)) * math.sin(P)**2 * math.cos(Q)**2 / (math.cos(sigma/2)**2) Y = (sigma + math.sin(sigma)) * math.cos(P)**2 * math.sin(Q)**2 / (math.sin(sigma/2)**2) distance = R_EARTH_EQUATOR * (sigma - (X + Y) / (2*r)) return distance # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/cyclograph0000755000175000017500000000022713144673265021467 0ustar federicofederico00000000000000#!/usr/bin/env python3 try: from launcher import * except ImportError: from cyclograph.launcher import * main() #TODO: use execfile instead? cyclograph-1.9.1/cyclograph/cyclograph.py0000644000175000017500000012075613144674235022123 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """cyclograph.py """ # Copyright (C) 2008-2017 Federico Brega, Pierluigi Villani # 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. import slope import iofile import glal import altitude_downloader import report_html from themes import ThemeManager import os import gettext import configparser import sys import queue import argparse from version import VERSION, LastVersionQuery # font-typ light, normal, bold DEFAULT_CONF = {"version": "1.2", "orizzontal lines": "True", "legend": "True", "slopeinfo": "True", "effect-3d": "True", "theme": "Classic", "font-des 1": "Sans", "font-typ 1": "normal", "font-dim 1": "10", "font-des g": "Sans", "font-typ g": "normal", "font-dim g": "10", "font-des t": "Sans", "font-typ t": "bold", "font-dim t": "20", "width": "800", "height": "600", "color 1": "rgb(0,0,255)", "color 2": "rgb(0,255,0)", "color 3": "rgb(255,255,0)", "color 4": "rgb(255,0,0)", "color 5": "rgb(200,0,0)", "color 6": "rgb(150,0,0)", "color 7": "rgb(100,0,0)", "level 1": "0", "level 2": "3.0", "level 3": "6.0", "level 4": "9.0", "level 5": "12.0", "level 6": "15.0", "level 7": "18.0", "server": "geonames.org", "numcps": "20", "widthsize": "540", "heightsize": "400", "maximized": "False", "latestVersionCheck": "True", "authorname": "", "authormail": "", } SECTIONS = ["global", "font", "resolution", "colors", "levels", "kml", "main window"] class Cyclograph(): """Controller according to MCV""" def __init__(self): self.slopelist = slope.SlopeList() self.init_configparser() size = (self.config.getint("main window", "widthsize"), self.config.getint("main window", "heightsize"), self.config.getboolean("main window", "maximized")) self.mygui = glal.gui.Gui(None, "CycloGraph", size) self.notebook = glal.Notebook(self.mygui.notebook, self.close) self.slopecounter = 1 self.setguiopt() glal.bind(self.new_slope, self.mygui.menu_item_new) glal.bind(self.open_slope, self.mygui.menu_item_open) glal.bind(self.create_kml, self.mygui.menu_item_createkml) glal.bind(self.save_slope, self.mygui.menu_item_save) glal.bind(self.save_slope_as, self.mygui.menu_item_save_as) glal.bind(self.preferences, self.mygui.preferences) glal.bind(self.ser1, self.mygui.menu_item_s1) glal.bind(self.ser2, self.mygui.menu_item_s2) glal.bind(self.ser3, self.mygui.menu_item_s3) glal.bind(glal.signalbug, self.mygui.menu_item_bug) glal.bind_close(self.exit, self.mygui) # Toolbar glal.ToolbarBind(self.add_cp, self.mygui.action_add) glal.ToolbarBind(self.edit_cp, self.mygui.action_edit) glal.ToolbarBind(self.delete_cp, self.mygui.action_delete) glal.ToolbarBind(self.plot_graph, self.mygui.action_plot) glal.ToolbarBind(self.map_slope, self.mygui.action_map) glal.ToolbarBind(self.info, self.mygui.action_properties) glal.Message().subscribe(self.slope_add, self.slopelist.message, "SLOPE_ADD") glal.Message().subscribe(self.slope_del, self.slopelist.message, "SLOPE_DEL") self.mygui.show() # Check if this is the latest version if self.config.getboolean("main window", "latestVersionCheck"): def notify(): self.versionchk.stop() if self.lvq.islast(): return glal.addstatusbartext(self.mygui, _("A new version of CycloGraph is out!")+"(%s)" % self.lvq.lastversion) del self.versionchk del self.lvq self.lvq = LastVersionQuery() self.lvq.start() self.versionchk = glal.Timer(10000, notify) # wait 1s before notify self.versionchk.start() def init_configparser(self): """Create a new configparser, and inizializes with default values, if the user saved his preferencese, open configuration file and use this values """ self.config = configparser.ConfigParser(DEFAULT_CONF) self.config.read(get_config_file_path()) for sec in SECTIONS: if not self.config.has_section(sec): self.config.add_section(sec) if self.config.get("global", "version") > VERSION: # The configuration file has been saved by a future version of # CycloGraph thus it is not guaranteeted to be backward compatible. # It is better to leave the file as it is and use default settings. self.defaultpref() return if self.config.get("global", "version") < '1.1': glal.addstatusbartext(self.mygui, _("old cofiguration issue.")) def setguiopt(self): """Set gui options to the value from preferences""" serv = self.config.get("kml", "server") glal.OptionCheck(self.mygui.menu_item_s1, True) glal.OptionCheck(self.mygui.menu_item_s2, serv == "earthtools.org") glal.OptionCheck(self.mygui.menu_item_s3, serv == "usgs.net") altitude_downloader.choose_service(serv) # Functions responding to menu events ### def new_slope(self, event=None): """ Create new slope""" pagen = self.slopelist.new_slope() self.slopelist.set_author(pagen, self.config.get("global", "authorname")) self.slopelist.set_email(pagen, self.config.get("global", "authormail")) pag = self.notebook.Page() pag.slopecounternew = self.slopecounter self.notebook.add_page(pag, _('slope')+" "+str(self.slopecounter)) self.slopecounter = self.slopecounter + 1 glal.enable_saving(self.mygui, True) def open_slope(self, event=None, filepath=None): """ Open a file""" # statusok = True types = [('Supported files', '*.cgx *.csv *.xml *.gpx *.kml *.fitlog *.tcx *.crp *.sal *.txt'), ('CycloGraphXml (*.cgx)', '*.cgx'), ('CycloGraph (*.csv)', '*.csv'), ('Ciclomaniac (*.xml)', '*.xml'), ('GPS eXchange Format (*.gpx)', '*.gpx'), ('Keyhole Markup Language (*.kml)', '*.kml'), ('SportTracks Fitlog files (*.fitlog)', '*.fitlog'), ('Training Center xml (*.tcx)', '*.tcx'), ('Ciclotour (*.crp)', '*.crp'), ('Salitaker (*.sal)', '*.sal'), ('Ciclomaniac (*.txt)', '*.txt')] if not filepath: filepath = glal.FileDialog(self.mygui, _("Choose a file"), '', "", types, glal.FD_OPEN) if not filepath: return for nump in range(len(self.slopelist)): # Check if the file has already been opened. page = self.notebook.get_page(nump) if page.savedfilepath == filepath: # Select the page of the selected file. self.notebook.setselpagnum(nump) return slope_num = self.slopelist.new_slope() pag = self.notebook.Page() pag.savedfilepath = filepath self.notebook.add_page(pag, "") slopefile = None filetype = "None" if filepath.lower().endswith(".gpx"): slopefile = iofile.load_gpxfile(filepath) filetype = "gpx" elif filepath.lower().endswith(".kml"): slopefile = iofile.load_kmlfile(filepath) filetype = "kml" elif filepath.lower().endswith(".fitlog"): slopefile = iofile.load_fitlogfile(filepath) filetype = "fitlog" elif filepath.lower().endswith(".tcx"): slopefile = iofile.load_tcxfile(filepath) filetype = "tcx" if filetype == "None": statusok = iofile.open_file(filepath, self.slopelist, slope_num) else: if slopefile is not None: max_num_cps = len(slopefile) (seltype, num, start_dist, end_dist) = glal.gui.numcpsdlg(max_num_cps, slopefile.min_dist(), slopefile.max_dist(), filetype) if (seltype == -1): nbook = None self.slopelist.del_slope(slope_num) self.notebook.remove_page(slope_num) return if not slopefile.hasAltitudes: self.import_altitudes(num, seltype, slopefile, start_dist, end_dist, slope_num) return statusok = slopefile.newSlope(self.slopelist, slope_num, num, start_dist, end_dist, seltype) # listcoords = self.slopelist.get_coords(slope_num) # print(listcoords) else: statusok = False if not statusok: nbook = None self.slopelist.del_slope(slope_num) self.notebook.remove_page(slope_num) glal.addstatusbartext(self.mygui, _("Unable to open file")) return self.tabupdate(slope_num) glal.enable_saving(self.mygui, True) if statusok == 2: glal.addstatusbartext(self.mygui, _("Old file format: please save it again.")) def create_kml(self, event=None): """ Create kml file""" # TODO: check for internet connection def downloadfunc(url): """Download current create_kml page""" from urllib.request import urlopen if url: response = urlopen(url) return response def savefunc(content, mode): """Save as kml current create_kml selected route""" if mode != 'DRAW' and mode != 'YOURS': content = iofile.gpxtokml(content) filepath = glal.FileDialog(None, _("kml name"), "", "", [("Keyhole Markup Language (*.kml)", "*.kml")], glal.FD_SAVE) if not filepath: return if not filepath.endswith('.kml'): filepath = filepath+".kml" with open(filepath, "w") as outfile: print(content, file=outfile) create = glal.gui.Create_kml(self.mygui, downloadfunc, savefunc) accepted = create.show_modal() if not accepted: create.destroy() return if not create.content: glal.addstatusbartext(self.mygui, _("Network error : unable to create an handler to download kml route.")) return if create.mode == 'DRAW': # If the user drawned the path cg must use every point, # and not waste user effort. numcps = 99999 # Hack: unlikely any user will draw more points. kmlcontent = create.content else: numcps = self.config.getint("kml", "numcps") if create.mode == 'ORS': gpxcontent = create.content kmlcontent = iofile.gpxtokml(gpxcontent) elif create.mode == 'BROU': gpxcontent = create.content kmlcontent = iofile.gpxtokml(gpxcontent) elif create.mode == 'YOURS': kmlcontent = create.content kmlfile = iofile.load_kmlfile(kmlcontent) if kmlfile is None: glal.addstatusbartext(self.mygui, _("Error on loading slope")) create.destroy() return slope_num = self.slopelist.new_slope() nbook = self.notebook.Page() nbook.slopecounternew = self.slopecounter self.notebook.add_page(nbook, _('slope %d') % self.slopecounter) self.slopecounter = self.slopecounter + 1 self.tabupdate(slope_num, '*') self.import_altitudes(numcps, 0, kmlfile, kmlfile.min_dist(), kmlfile.max_dist(), slope_num) create.destroy() def import_altitudes(self, num, seltype, slopefile, start_dist, end_dist, slope_num): """ Start the import kml process in CycloGraph""" # TODO: check for internet connection self.slopenumber_kml = slope_num slopefile.getCoords(self.slopelist, slope_num, start_dist, end_dist) self.pdlg = glal.ProgressDialog() refresh_time = 30 # FIXME: is the measure unit milliseconds? self.timer = glal.Timer(refresh_time, self.on_altitude_importing) self.queue = queue.Queue() self.prsrthrd = altitude_downloader.ImporterThread(self.queue, slopefile, num, start_dist, end_dist, seltype) self.prsrthrd.daemon = True # start thread and timer self.prsrthrd.start() self.timer.start() def on_altitude_importing(self, widget=None): """ Called by a timer: check if thread is still executing (if not stop timer and, according to status, handle the produced slope) reads the progress of importer-thread updates the progress dialog check if operation is cancelled and tells to thread """ try: (progress, chkp) = self.queue.get(block=False) except queue.Empty: # print("is alive "+str(self.prsrthrd.isAlive())) if not self.prsrthrd.isAlive(): self.timer.stop() self.pdlg.destroy() if self.prsrthrd.status != 'OK': slope_num = self.slopenumber_kml self.slopelist.del_slope(slope_num) self.notebook.remove_page(slope_num) glal.addstatusbartext(self.mygui, _("Error while loading data")) else: self.tabupdate(self.slopenumber_kml, '*') glal.enable_saving(self.mygui, True) del self.prsrthrd # there is nothing to update but gtk wants True return True self.slopelist.add_cp(self.slopenumber_kml, *chkp) # There is a new progress value => update the gui want2abort = self.pdlg.update(progress) if want2abort: self.prsrthrd.abort() # need to return True to keep timer_gtk running return True def save_slope_as(self, event=None, pagenumber=None, filename=None): """ Save_as selected slope """ if pagenumber is None: pagenumber = self.notebook.getselpagnum() myslope = self.slopelist.get_slope_copy(pagenumber) filepath = glal.FileDialog(self.mygui, _("Save as"), '', "", [("CycloGraphXml(*.cgx)", "*.cgx")], glal.FD_SAVE, filename) if not filepath: return False if not filepath.endswith('.cgx'): filepath = filepath + '.cgx' iofile.save_file(filepath, myslope) page = self.notebook.get_page(pagenumber) page.savedfilepath = filepath self.tabupdate(pagenumber) return True def save_slope(self, event=None): """ Save selected slope """ pagenumber = self.notebook.getselpagnum() page = self.notebook.get_page(pagenumber) myslope = self.slopelist.get_slope_copy(pagenumber) filepath = page.savedfilepath if filepath is None or not filepath.endswith('.cgx'): filepath = glal.FileDialog(self.mygui, _("Choose a file"), '', "", [("CycloGraphXml(*.cgx)", "*.cgx")], glal.FD_SAVE) if not filepath: return if not filepath.endswith('.cgx'): filepath = filepath + '.cgx' iofile.save_file(filepath, myslope) page.savedfilepath = filepath self.tabupdate(pagenumber) glal.addstatusbartext(self.mygui, _("File saved")) def exit(self): """ Check if there are unsaved slopes, and cg is ready to exit This funciotn is called by the gui""" if not len(self.slopelist): if not self.mygui.ismaximized(): dim = self.mygui.getdimensions() self.config.set("main window", "widthsize", str(dim[0])) self.config.set("main window", "heightsize", str(dim[1])) self.config.set("main window", "maximized", "%s" % self.mygui.ismaximized()) self.savepref() return True for pagenum in reversed(range(len(self.slopelist))): self.check_unsaved(pagenum) if not len(self.slopelist): if not self.mygui.ismaximized(): dim = self.mygui.getdimensions() self.config.set("main window", "widthsize", str(dim[0])) self.config.set("main window", "heightsize", str(dim[1])) self.config.set("main window", "maximized", "%s" % self.mygui.ismaximized()) self.savepref() return True else: return False def preferences(self, event=None): config = configparser.ConfigParser(DEFAULT_CONF) config.read(get_config_file_path()) for sec in SECTIONS: if not config.has_section(sec): config.add_section(sec) if config.get("global", "version") > VERSION: self.defaultpref() settings = getconfig(config) pref = glal.gui.preferences(self.mygui, settings, DEFAULT_CONF) if not pref: return self.config.set("global", "orizzontal lines", str(pref['olines'])) self.config.set("global", "legend", str(pref['legend'])) self.config.set("global", "theme", pref['theme']) self.config.set("global", "slopeinfo", str(pref['sinfo'])) self.config.set("global", "effect-3d", str(pref['3d'])) self.config.set("global", "latestVersionCheck", str(pref['vcheck'])) self.config.set("global", "authorname", str(pref['aname'])) self.config.set("global", "authormail", str(pref['amail'])) font = pref['fdesc'] self.config.set("font", "font-des 1", font["des"]) self.config.set("font", "font-typ 1", font["typ"]) self.config.set("font", "font-dim 1", str(font["dim"])) font = pref['ftitle'] self.config.set("font", "font-des t", font["des"]) self.config.set("font", "font-typ t", font["typ"]) self.config.set("font", "font-dim t", str(font["dim"])) font = pref['fgrad'] self.config.set("font", "font-des g", font["des"]) self.config.set("font", "font-typ g", font["typ"]) self.config.set("font", "font-dim g", str(font["dim"])) self.config.set("resolution", "width", pref['width']) self.config.set("resolution", "height", pref['height']) self.config.set("kml", "server", pref['serv']) colorlist = pref['colors'] levellist = pref['levels'] for i in range(0, 7): self.config.set("colors", "color "+str(i+1), str(colorlist[i])) self.config.set("levels", "level "+str(i+1), str(levellist[i])) self.savepref() self.setguiopt() # Functions related to kml server ### def ser1(self, event=None): """Set kml server1""" self.config.set("kml", "server", "geonames.org") altitude_downloader.choose_service("geonames.org") def ser2(self, event=None): """Set kml server2""" self.config.set("kml", "server", "earthtools.org") altitude_downloader.choose_service("earthtools.org") def ser3(self, event=None): """Set kml server3""" self.config.set("kml", "server", "usgs.net") altitude_downloader.choose_service("usgs.net") def savepref(self, event=None): """Save preferences. Configuration file is save in the user's home as standardized byXDG.""" with open(get_config_file_path(), "w") as fid: self.config.write(fid) def defaultpref(self, event=None): """Load default preferences: delete current config parser and recreate a new configparser with default values""" del self.config self.config = configparser.ConfigParser(DEFAULT_CONF) for sec in SECTIONS: if not self.config.has_section(sec): self.config.add_section(sec) self.setguiopt() # Functions related to a single Page ### def close(self, *argument): # only qt < 4.5 doesn't pass any argument """Close a single page of notebook.""" if not len(self.slopelist): return if not argument: pagenum = self.notebook.getselpagnum() else: pagenum = self.notebook.get_pagenum(argument) self.check_unsaved(pagenum) def tabupdate(self, num, tag=''): """Update page.modified and title of the page tab""" page = self.notebook.get_page(num) name = self.slopelist.get_name(num) if tag == '': page.modified = False else: page.modified = True if name == '' or name is None: if page.savedfilepath is not None: self.notebook.set_page_label(num, tag+os.path.split(page.savedfilepath)[-1]) else: self.notebook.set_page_label(num, tag+_('slope')+" "+str(page.slopecounternew)) return self.notebook.set_page_label(num, tag+name) def check_unsaved(self, pagenum): """ Check if passed page is not saved and ask to save it""" page = self.notebook.get_page(pagenum) if page.modified: if page.savedfilepath is None: response = glal.gui.save_changes(self.mygui, _('slope') + " " + str(page.slopecounternew)) if (response == 'SAVE' and not self.save_slope_as(None, pagenum)) \ or response != 'DISCARD': return else: filename = os.path.split(page.savedfilepath)[-1] response = glal.gui.save_changes(self.mygui, filename) if response == 'SAVE': if not filename.endswith('.cgx'): self.save_slope_as(None, pagenum, filename.rsplit('.', 1)[0]+'.cgx') return filepath = page.savedfilepath myslope = self.slopelist.get_slope_copy(pagenum) iofile.save_file(filepath, myslope) page.savedfilepath = filepath page.modified = False elif not response == 'DISCARD': return self.notebook.remove_page(pagenum) self.slopelist.del_slope(pagenum) if not len(self.slopelist): glal.enable_saving(self.mygui, False) def plot_graph(self, event=None): """Open the Plot windows showing a graphical image of current slope""" # slope_num == pagenumber == num num = self.notebook.getselpagnum() myslope = self.slopelist.get_slope_copy(num) if len(myslope) < 2: glal.gui.geterrnocp(self.mygui) return plotdata = getconfig(self.config) myplot = glal.gui.Plot(self.mygui, myslope, plotdata, export_as_image, save_pdf, save_html) myplot.showplot() def map_slope(self, event=None): """Open a Map window""" num = self.notebook.getselpagnum() slope = self.slopelist.get_slope_copy(num) if len(slope.coords) != 0: (lat, lng) = slope.coords[0] stringc = "%s %s" % (lng, lat) for i in range(1, len(slope.coords)): (lat, lng) = slope.coords[i] stringc += ",%s %s" % (lng, lat) glal.gui.Map(self.mygui, slope.name, stringc, save_map_image) else: glal.gui.mapcoorderr(self.mygui) def add_cp(self, event=None): """Add a check point to current slope.""" num = self.notebook.getselpagnum() mng = glal.gui.Managecp(self.mygui, _("Add"), "", "", "") if mng.show_modal(): (dist, alt, name) = mng.getcp() if (dist != "") and (alt != ""): self.slopelist.add_cp(num, float(dist), float(alt), name) self.tabupdate(num, '*') else: glal.gui.managecperr(self.mygui) mng.destroy() def edit_cp(self, event=None): """Edit a check point to current slope.""" num = self.notebook.getselpagnum() page = self.notebook.get_page(num) editcp = page.get_sel_row() myslope = self.slopelist.get_slope_copy(num) if editcp < 0: return (dist, alt, name) = (str(myslope.cps[editcp][0]), str(myslope.cps[editcp][1]), str(myslope.cps[editcp][2])) mng = glal.gui.Managecp(self.mygui, _("Edit"), dist, alt, name) if mng.show_modal(): (dist, alt, name) = mng.getcp() if (dist != "") and (alt != ""): self.slopelist.remove_cp(num, editcp) self.slopelist.add_cp(num, float(dist), float(alt), name) page = self.notebook.get_page(num) self.tabupdate(num, '*') else: glal.gui.managecperr(self.mygui) mng.destroy() def delete_cp(self, event=None): """Delete a check point to current slope.""" num = self.notebook.getselpagnum() page = self.notebook.get_page(num) rm_cps = page.get_sel_multi_row() # remove selected cps starting from the end # to not affect row number of non removed items rm_cps.reverse() for i in rm_cps: self.slopelist.remove_cp(num, i) self.tabupdate(num, '*') def info(self, event=None): """Show informations on current slope""" num = self.notebook.getselpagnum() # need to call calc to have the correct value of average and max gradient myslope = self.slopelist.get_slope_copy(num) myslope.calc() slopedata = {'name': self.slopelist.get_name(num), 'state': self.slopelist.get_state(num), 'author': self.slopelist.get_author(num), 'email': self.slopelist.get_email(num), 'comment': self.slopelist.get_comment(num), 'average_grad': "%.1f" % self.slopelist.get_average_grad(num)+' %', 'max_grad': "%.1f" % self.slopelist.get_max_grad(num)+' %', 'height_difference': str(self.slopelist.get_height_difference(num)) + ' m', 'height_gain': str(self.slopelist.get_height_gain(num)) + ' m', } dlg = glal.gui.formslopeinfo(self.mygui, slopedata) if not dlg: return self.slopelist.set_name(num, dlg['name']) self.slopelist.set_country(num, dlg['state']) self.slopelist.set_author(num, dlg['author']) self.slopelist.set_email(num, dlg['email']) self.slopelist.set_comment(num, dlg['comment']) self.tabupdate(num, '*') # Functions activated in response to Messages by the GUI ### def slope_add(self, pagenumber, row_num): """Callback from a SLOPE ADD message""" page = self.notebook.get_page(pagenumber) if page is None: return myslope = self.slopelist.get_slope_copy(pagenumber) page.insert_row(row_num, myslope.cps[row_num]) def slope_del(self, pagenumber, row_num): """Callback from a SLOPE DEL message""" page = self.notebook.get_page(pagenumber) if page is None: return page.delete_row(row_num) # Functions related to Map ### def save_map_image(mapwindow): """ Save map to an image""" formats = [("Portable Network Graphics (*.png)", "*.png"), ("Bitmap (*.bmp)", "*.bmp"), ("Joint Photographic Experts Group (*.jpg)", "*.jpg")] filepath = '' # Control if the right extension is given by user while not filepath.lower().endswith(('.png', '.bmp', '.jpg')): if filepath: filepath += '.png' filepath = glal.FileDialog(None, _("Save map"), filepath, "", formats, glal.FD_SAVE) if not filepath: return mapwindow.saveimage(filepath) # Functions related to Plot ### def export_as_image(imgslope, settings): """ Save a plot to an image""" formats = [("Portable Network Graphics (*.png)", "*.png"), ("Bitmap (*.bmp)", "*.bmp"), ("Joint Photographic Experts Group (*.jpg)", "*.jpg"), ("Scalable Vector Graphics (*.svg)", "*.svg")] filepath = '' # Control if the right extension is given by user while not filepath.lower().endswith(('.png', '.bmp', '.jpg', '.svg')): if filepath: filepath += '.png' filepath = glal.FileDialog(None, _("Save plot"), filepath, "", formats, glal.FD_SAVE) if not filepath: return format = filepath.rsplit('.', 1)[1] if format.lower() == 'svg': image = glal.Image_svg(settings['width'], settings['height'], imgslope.paint) else: image = glal.Image(settings['width'], settings['height'], imgslope.paint) image.plot(settings) image.savetofile(filepath, format) def save_pdf(imgslope, settings): """ Save a pdf from current plot""" formats = [("Portable Document Format (*.pdf)", "*.pdf")] filepath = '' # Check if the right extension is given by user while not filepath.lower().endswith('.pdf'): if filepath: filepath += '.pdf' filepath = glal.FileDialog(None, _("Save plot to pdf"), filepath, "", formats, glal.FD_SAVE) if not filepath: return pdf = glal.Pdf(filepath) pdfconfig = settings.copy() pdfconfig['sinfo'] = False pdf.plot_image(pdfconfig, 793, 600, # settings['width'], settings['height'],) imgslope.paint) pdf.addtitle(_("Slope")) pdf.addtext(_("Name") + ": " + imgslope.name) pdf.addtext(_("Country") + ": " + imgslope.country) pdf.addtext(_("Average gradient") + ": " + "%.1f" % imgslope.average_grad+" %") pdf.addtext(_("Max gradient") + ": " + "%.1f" % imgslope.max_grad+" %") pdf.addtext(_("Height difference") + ": " + str(imgslope.height_difference)+" m") pdf.addtext(_("Height gain") + ": " + str(imgslope.height_gain)+" m") pdf.addtitle(_("Author")) pdf.addtext(_("Name") + ": " + imgslope.author) pdf.addtext(_("E-mail") + ": " + imgslope.email) # TODO:use correct indentation to include notes on the pdf output # pdf.addtitle(_("Note")) # pdf.addtext(imgslope.comment) pdf.save() def save_html(imgslope, settings): """ Save an html from current plot""" formats = [("HyperText Markup Language (*.html)", "*.html")] filepath = '' # Check if the right extension is given by user while not filepath.lower().endswith('.html'): if filepath: filepath += '.html' filepath = glal.FileDialog(None, _("Save plot to html"), filepath, "", formats, glal.FD_SAVE) if not filepath: return image = glal.Image_svg(settings['width'], settings['height'], imgslope.paint) image.plot(settings) reportpage = report_html.Report_html(imgslope.name) reportpage.add_image(image.svg) if len(imgslope.coords) != 0: reportpage.add_map(imgslope, settings['width'], settings['height']) reportpage.addtitle(_("Slope")) reportpage.addtext(_("Name")+": ", imgslope.name) reportpage.addtext(_("Country")+": ", imgslope.country) reportpage.addtext(_("Average gradient")+": ", "%.1f" % imgslope.average_grad+" %") reportpage.addtext(_("Max gradient")+": ", "%.1f" % imgslope.max_grad+" %") reportpage.addtext(_("Height difference")+": ", str(imgslope.height_difference)+" m") reportpage.addtext(_("Height gain")+": ", str(imgslope.height_gain)+" m") reportpage.addtitle(_("Author")) reportpage.addtext(_("Name")+": ", imgslope.author) reportpage.addtext(_("E-mail")+": ", imgslope.email) if imgslope.comment != '': reportpage.addtitle(_("Note")+": ") reportpage.addnote(imgslope.comment) reportpage.save(filepath) def get_config_file_path(): config_path = os.path.expanduser(os.path.normpath('~/.config/CycloGraph')) if 'XDG_CONFIG_HOME' in os.environ and os.path.isabs(os.environ['XDG_CONFIG_HOME']): config_path = os.environ['XDG_CONFIG_HOME'] + os.path.normpath('/CycloGraph') config_file = config_path + os.path.normpath('/CycloGraph.conf') # Create a directory if needed if not os.path.exists(config_path): os.makedirs(config_path) # migrate old configuration file if there is any old_file = os.path.expanduser(os.path.normpath('~/.cyclograph.cfg')) if os.path.exists(old_file): os.rename(old_file, config_file) return config_file def getconfig(config): """ Return current settings from config""" settings = { 'olines': config.getboolean("global", "orizzontal lines"), 'legend': config.getboolean("global", "legend"), 'theme': config.get("global", "theme"), 'sinfo': config.getboolean("global", "slopeinfo"), '3d': config.getboolean("global", "effect-3d"), 'vcheck': config.getboolean("global", "latestVersionCheck"), 'fdesc': { "des": config.get("font", "font-des 1"), "typ": config.get("font", "font-typ 1"), "dim": config.getint("font", "font-dim 1") }, 'ftitle': { "des": config.get("font", "font-des t"), "typ": config.get("font", "font-typ t"), "dim": config.getint("font", "font-dim t") }, 'fgrad': { "des": config.get("font", "font-des g"), "typ": config.get("font", "font-typ g"), "dim": config.getint("font", "font-dim g") }, 'width': config.getint("resolution", "width"), 'height': config.getint("resolution", "height"), 'themelist': ThemeManager().getthemeslist(), 'serv': config.get("kml", "server"), 'aname': str(config.get("global", "authorname")), 'amail': str(config.get("global", "authormail")), 'colors': [str(config.get("colors", "color "+str(i))) for i in range(1, 8)], 'levels': [config.getfloat("levels", "level "+str(i)) for i in range(1, 8)] } return settings ######################################################## def usage(): """ Print usage on standard error""" sys.stderr.write("usage: %s {-q|-G|-f}\n" % sys.argv[0]) sys.stderr.write("\t--qt --gtk3 or --file=*.cgx file for cli\n") def opt_parser(argv): """ Parse console input""" domain = gettext.textdomain(None) gettext.textdomain("help") parser = argparse.ArgumentParser(prog="CycloGraph", description="CycloGraph "+VERSION) parser.set_defaults(filename="") parser.set_defaults(gui="cli") parser.add_argument("-v", "--version", action="version", version="%(prog)s "+VERSION) parser.add_argument("-G", "--gtk3", help="gtk3 graphical user interface", action="store_const", dest="gui", const="gtk3") parser.add_argument("-q", "--qt", help="qt graphical user interface", action="store_const", dest="gui", const="qt") parser.add_argument("-f", "--file", dest="filename", help="open cgx, csv, gpx, tcx, sal, crp or cyclomaniac file", metavar="FILE") parser.add_argument("-s", "--size", dest="size", action="store", help="output size widthxheight") parser.add_argument("-l", "--list", dest="_list", action="store_true", help="list available themes and exit") parser.add_argument("-o", "--options", action='append', dest='optionslist', default=[], help="add option to optionlist", choices=['3d', 'legend', 'olines', 'sinfo']) parser.add_argument("-t", "--theme", action="store", dest="theme", help="use selected theme") parser.add_argument("-e", "--extension", dest="extension", default="", help="choose default file format extension", metavar="EXT") options = parser.parse_args() if options._list: # print("available themes are:") # User asked for the available themes, so telling him is quite useless # also is less machine readable: silence is golden. for theme in ThemeManager().getthemeslist(): print(theme) sys.exit(0) if options.theme and \ options.theme not in ThemeManager().getthemeslist(): parser.error('there is no theme named "%s"' % options.theme) gettext.textdomain(domain) return options ######################################################## def main_qt(): """Main application for qt interface""" app = glal.initapp(sys.argv) arg = [str(s) for s in app.arguments()] options = opt_parser(arg) # WARNING: The return value of CycloGraph MUST be stored in a variable # otherwise the application could segfault. controller = Cyclograph() if options.filename: controller.open_slope(filepath=options.filename) sys.exit(app.exec_()) def main_gtk3(): """Main application for gtk3 interface""" controller = Cyclograph() options = opt_parser(sys.argv) if options.filename: controller.open_slope(filepath=options.filename) glal.initapp() def main_cli(): """Main application for cli interface""" slopelist = slope.SlopeList() slope_num = slopelist.new_slope() opt = opt_parser(sys.argv) filein = opt.filename if filein == "-": filein = sys.stdin success = iofile.open_file(filein, slopelist, slope_num, opt.extension) if not success: print(('Unable to open "%s"' % filein), file=sys.stderr) return False devc = glal.DeviceContext() myslope = slopelist.get_slope_copy(slope_num) myslope.calc() config = configparser.ConfigParser(DEFAULT_CONF) config.read(get_config_file_path()) for sec in SECTIONS: if not config.has_section(sec): config.add_section(sec) settings = getconfig(config) if opt.theme: settings['theme'] = opt.theme # FIXME: this should work also with GUIs size_w = settings['width'] size_h = settings['height'] if opt.optionslist: if "3d" in opt.optionslist: settings['3d'] = True else: settings['3d'] = False if "legend" in opt.optionslist: settings['legend'] = True else: settings['legend'] = False if "olines" in opt.optionslist: settings['olines'] = True else: settings['olines'] = False if "sinfo" in opt.optionslist: settings['sinfo'] = True else: settings['sinfo'] = False if opt.size: size_el = opt.size.split('x') if len(size_el) == 2: try: el_w = int(size_el[0]) el_h = int(size_el[1]) if (el_w > 0) and (el_h > 0): size_w = el_w size_h = el_h except ValueError: print(('ValueError reading size "%s"' % opt.size), file=sys.stderr) devc.init_draw_surf((size_w, size_h), settings['3d']) myslope.paint(settings, devc) devc.end_draw() # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/draw_on_map.js0000644000175000017500000000625012065361217022224 0ustar federicofederico00000000000000/* * Copyright (C) 2011, 2012 Federico Brega * * 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. * */ var map; var featureLatLon; function init() { map = new OpenLayers.Map('map', {displayProjection: new OpenLayers.Projection("EPSG:4326")}); map.addControl(new OpenLayers.Control.LayerSwitcher()); var osm = new OpenLayers.Layer.OSM(); var cycle = new OpenLayers.Layer.OSM( "OpenCycleMap", ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"], { type: 'png', displayOutsideMaxExtent: true, transitionEffect: 'resize'} ); var gphy = new OpenLayers.Layer.Google( "Google Physical", {type: google.maps.MapTypeId.TERRAIN} ); var gmap = new OpenLayers.Layer.Google( "Google Streets", {numZoomLevels: 20} ); var ghyb = new OpenLayers.Layer.Google( "Google Hybrid", {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} ); var gsat = new OpenLayers.Layer.Google( "Google Satellite", {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} ); var renderer = OpenLayers.Util.getParameters(window.location.href).renderer; renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers; vectors = new OpenLayers.Layer.Vector( "Drawn", {renderers: renderer} ); map.addLayers([cycle, osm, gphy, gmap, ghyb, gsat, vectors]); map.addControl(new OpenLayers.Control.LayerSwitcher()); map.addControl(new OpenLayers.Control.MousePosition()); convertCallback = function(feature) { featureLatLon = feature.clone(); var geom = featureLatLon.geometry; geom.transform(map.getProjectionObject(), new OpenLayers.Projection("EPSG:4326")); }; controls = { point: new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.Point), line: new OpenLayers.Control.DrawFeature(vectors, OpenLayers.Handler.Path, {'featureAdded': convertCallback}), }; for(var key in controls) { map.addControl(controls[key]); } controls["line"].activate(); //TODO: activate through a proper function // Google.v3 uses EPSG:900913 map.setCenter(new OpenLayers.LonLat(10.2, 48.9).transform( new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject() ), 5); for (var i = 0; i < map.layers.length; i++) { map.layers[i].animationEnabled = true; } } kmlFormat = new OpenLayers.Format.KML(); function createKml() { var kml = kmlFormat.write(featureLatLon); return kml }cyclograph-1.9.1/cyclograph/glal.py0000644000175000017500000002252113144673266020701 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- # glal.py """This module provides a graphic library abstraction layer for Cyclograph""" # Copyright (C) 2008-2015 Federico Brega, Pierluigi Villani # 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. from __future__ import print_function, unicode_literals import sys import glal_selected LIBRARY = glal_selected.library (FD_OPEN, FD_SAVE) = range(2) def Message(*args): """This funcion return the class that wraps the using messaging library""" if LIBRARY == 'cli': return Message_cli(*args) def DeviceContext(*args): if LIBRARY == 'cli': return DeviceContext_cli(*args) def Image(*args): if LIBRARY == 'cli': return Image_cli(*args) def addstatusbartext(maingui, text): """Add text to main gui status bar""" if LIBRARY == 'cli': sys.stderr.write(text) def FileDialog(parent, title, dirname, other, file_types, rw, filename=None): """Return File dialog """ return gui.FileDialog(parent, title, dirname, filename, other, file_types, rw, FD_OPEN, FD_SAVE) # Begin of cli classes ### class Message_cli(): """CLI interfaces doesn't need to update their view because they haven't got any """ def send(self, *args): """ send method not used for cli""" pass def subscribe(self, *args): """ subscribe method not used for cli""" pass def DeviceContext_cli(*args): """ Get a device contex for CLI default is an svg generator.""" return DeviceContext_svg(*args) class Image_cli(): """ Image cli not used""" pass # End of cli classes ### # Begin of SVG ### class DeviceContext_svg(): """Device Context class to draw sgv files""" colors = {'white': '#ffffff', 'black': '#000000'} def setcolor(self, color): """ Set color converting to svg compatible format.""" if type(color) == type(str()): if color.startswith('#'): return color elif color.startswith('rgb'): tuple_ = [int(x) for x in color[4:-1].split(',')] return tupletocss(tuple_) else: return self.colors[color] else: sys.stderr.write("Color type unknown: "+color+" "+str(type(color))+"\n") return False def init_draw_surf(self, size, p3d): """ Init draw surface""" self.size_x = size[0] self.size_y = size[1] self.pencolor = None self.lineargcount = 0 self.pensize = 0 self.pshear = p3d self.svg = ''' \n ''' % size if p3d: self.brushcolor = '#ffffff' # white background self.svg += ''' ''' % (self.size_x, self.size_y, self.brushcolor) def shear(self, shear): if shear != 0: self.pshear = True self.svg += '' % (shear*50,) else: self.pshear = False def getsize(self): """ Return size""" return (self.size_x, self.size_y) def gradfill(self, rect, startcolor, endcolor): """ Fill gradient""" self.svg += ''' ''' % (self.setcolor(startcolor), self.setcolor(endcolor), rect[2] - rect[0], rect[3] - rect[1]) def setpen(self, color, size): """ Set pen's size and color""" self.pencolor = self.setcolor(color) self.pensize = str(size) def setfont(self, fontdict): """ Set font""" self.dim = fontdict['dim'] self.font = "font-family:%s;font-size:%spx" % (fontdict['des'], self.dim) def drawtext(self, text, pos_x, pos_y): """ Draw text at position pos_x,pos_y """ self.drawrotatedtext(text, pos_x, pos_y, 0) def gettextwidth(self, text): """ Return text length""" return self.dim*len(text)-30 # TODO: check if there is a more accurate method def gettextheight(self, text): """ Return text height""" return self.dim # TODO: check if there is a more accurate method def drawline(self, pos_x0, pos_y0, pos_x1, pos_y1): """ draw line""" self.svg += ''' ''' % (pos_x0, pos_y0, pos_x1, pos_y1, self.pencolor, self.pensize) def setlineargradientbrush(self, colorlist, startp, endp): """ Get a linear gradient from startp to endp, using colors in colorlist. The elments of colorlist are tuple in the format (color, realtive position).""" self.svg += ''' ''' % (self.lineargcount, startp[0], startp[1], endp[0], endp[1]) for color in colorlist: self.svg += ''' ''' % (color[1]*100, self.setcolor(color[0])) self.svg += ''' ''' self.brushcolor = "url(#linearg%s)" % (self.lineargcount) self.lineargcount = self.lineargcount + 1 def setbrush(self, color): """ Set brush color""" self.brushcolor = self.setcolor(color) def drawrectangle(self, pos_x0, pos_y0, width, height): """ Draw rectangle""" self.svg += ''' ''' % (pos_x0, pos_y0, width, height, self.brushcolor, self.pencolor, self.pensize) def drawrotatedtext(self, text, pos_x, pos_y, angle): """Draw the text given, rotated by the angle.""" dy = self.dim self.svg += '%s ''' % (text) def drawpolygon(self, sequence): """ Draw polygon""" if len(sequence) < 2: return self.svg += '\n' \ % (self.brushcolor, self.pencolor, self.pensize) def startpath(self, point): """ Start a path in the specified point,""" self.svg += ''' next_cp or i == len(cps)-1: indices.append(i) next_cp = dist + max_dist/len(cps) # Use waypoints to determine the name of a cp. waypoints = {} for waypoint in fid.getElementsByTagName('wpt'): # find the nearest (filtered) point to this waypont (wpt_lon, wpt_lat) = point_conversion(waypoint.attributes["lon"].value, waypoint.attributes["lat"].value) (min_i, min_distance) = (-1, float(3)) # use minimum accetable distance 3 km. # print cps for i in indices: # calculate distance to point i (lon, lat) = point_conversion(cps[i][1], cps[i][2]) dist = haversine(lat, wpt_lat, lon, wpt_lon) # print (lon, lat, dist) if dist < min_distance: (min_i, min_distance) = (i, dist) if min_i >= 0: # is found a point near enought to the waypoint: use it. waypoints[min_i] = waypoint.getElementsByTagName("name")[0].firstChild.data returncp = [] is_with_alt = True alt_count = 0 for i in indices: # get elevation (dist, lng, lat) = cps[i] sample = ptls[i] elelist = sample.getElementsByTagName('ele') if len(elelist) > 0: ele = elelist[0] node = ele.childNodes alt = float(node[0].data) else: is_with_alt = False alt = 0 if (alt == 0): alt_count += 1 if i in waypoints: name = waypoints[i] else: name = u"" returncp.append((dist, alt, name, lat, lng)) fid.unlink() retfile = SlopeFile(returncp) if len(indices) == alt_count: is_with_alt = False retfile.hasAltitudes = is_with_alt return retfile except Exception as err: logging.exception(err) return None def load_fitlogfile(xmlfile): """Load a fitlog file""" try: if isinstance(xmlfile, str) and xmlfile.strip().startswith('<'): fid = xml.dom.minidom.parseString(xmlfile) else: fid = xml.dom.minidom.parse(xmlfile) ptls = fid.getElementsByTagName('pt') coords = [(el.attributes["lon"].value, el.attributes["lat"].value) for el in ptls] cps = distance(coords) next_cp = 0 max_dist = cps[-1][0] indices = [] for i in range(len(cps)): dist = cps[i][0] if i == 0 or dist > next_cp or i == len(cps)-1: indices.append(i) next_cp = dist + max_dist/len(cps) returncp = [] is_with_alt = True alt_count = 0 name = u"" for i in indices: # get elevation (dist, lng, lat) = cps[i] sample = ptls[i] if sample.getAttribute('ele'): elev = sample.attributes["ele"].value if len(elev) > 0: alt = float(elev) else: is_with_alt = False alt = 0 else: is_with_alt = False alt = 0 if (alt == 0): alt_count += 1 returncp.append((dist, alt, name, lat, lng)) fid.unlink() retfile = SlopeFile(returncp) if len(indices) == alt_count: is_with_alt = False retfile.hasAltitudes = is_with_alt return retfile except Exception as err: logging.exception(err) return None def load_kmlfile(xmlfile): """Load a kml file""" try: if isinstance(xmlfile, bytes) and xmlfile.strip().startswith(b'<'): fid = xml.dom.minidom.parseString(xmlfile) elif isinstance(xmlfile, str) and xmlfile.strip().startswith('<'): fid = xml.dom.minidom.parseString(xmlfile) else: fid = xml.dom.minidom.parse(xmlfile) coordlist = [] altlist = [] for linstr in fid.getElementsByTagName("LineString"): coords = linstr.getElementsByTagName("coordinates")[0] node = coords.childNodes[0] data = node.data.strip(' \n') data = data.replace('\n', ' ') coordlist += [el.split(',')[:2] for el in data.split(' ')] # if LineString is not present try gx:coord (KML 2.2 ext) if len(coordlist) == 0: for gxcoord in fid.getElementsByTagName("gx:coord"): node = gxcoord.childNodes[0] temp = node.data.split(' ') coordlist += [temp[:2]] altlist += [float(temp[2])] if len(coordlist) == 0: return SlopeFile([]) cps = distance(coordlist) next_cp = 0 max_dist = cps[-1][0] is_with_alt = len(altlist) >= len(cps) returncp = [] for i in range(len(cps)): (dist, lng, lat) = cps[i] # create self.num_cps check points # print i if i == 0 or dist > next_cp or i == len(cps)-1: if is_with_alt: # altitude is already in the kml file alt = altlist[i] else: # altitude has to be downloaded alt = 0 returncp.append((dist, alt, "", lat, lng)) next_cp = dist + max_dist/len(cps) fid.unlink() retfile = SlopeFile(returncp) retfile.hasAltitudes = is_with_alt return retfile except Exception as err: logging.exception(err) return None def load_tcxfile(xmlfile): """Load a tcx file""" try: fid = xml.dom.minidom.parse(xmlfile) ptls = fid.getElementsByTagName('Trackpoint') coords = [] is_with_alt = False for sample in ptls: for ent in sample.childNodes: if ent.localName == "Position": # Iterate on child nodes to get latitude and longitude for pos_node in ent.childNodes: if pos_node.localName == "LatitudeDegrees": lat = pos_node.childNodes[0].data elif pos_node.localName == "LongitudeDegrees": lng = pos_node.childNodes[0].data elif ent.localName == "AltitudeMeters": alt = float(ent.childNodes[0].data) if (alt != 0): is_with_alt = True coords.append((lng, lat, alt)) dist = 0.0 (lon_old, lat_old) = point_conversion(coords[0][0], coords[0][1]) returncp = [] for i in range(len(coords)): (lon_new, lat_new) = point_conversion(coords[i][0], coords[i][1]) dist += haversine(lat_old, lat_new, lon_old, lon_new) lon_old = lon_new lat_old = lat_new returncp.append((dist, coords[i][2], u"", coords[i][1], coords[i][0])) retfile = SlopeFile(returncp) logging.info("is with alt " + str(is_with_alt)) retfile.hasAltitudes = is_with_alt return retfile except Exception as err: logging.exception(err) return None (UNSUPPORTED, CPS, AUTHOR, DATA) = range(4) def __open_txt(file_, slopelist, slopenum): """Open a Cyclomaniac TXT file""" if isinstance(file_, str): fid = open(file_, encoding='utf-8', mode='r', errors='replace') else: fid = file_ # with open(filename, 'rb') as fid: mode = CPS for line in fid: line = line.strip() # Find if switching to a different mode. if line.startswith('['): modename = line.strip('[]').lower() # print modename if modename == 'author': mode = AUTHOR elif modename == 'data': mode = DATA else: mode = UNSUPPORTED continue # mode cps import check point to slope. if mode == CPS: # format is dist, alt, name, other. (dist, alt, name) = line.split(',')[:3] (dist, alt) = (float(dist), float(alt)) name = name.strip('\"') slopelist.add_cp(slopenum, dist, alt, name) # mode data imports extra information about the slope elif mode == DATA: try: (lhs, rhs) = line.split('=')[:2] except: continue # print lhs +" <- " + rhs if lhs == 'IDPercorso': slopelist.set_name(slopenum, rhs) elif lhs == 'Stato': slopelist.set_country(slopenum, rhs) elif lhs == 'Url': slopelist.set_url(slopenum, rhs) fid.close() def __open_sal(filepathname, slopelist, slopenum): """Open a Salitaker SAL file""" if isinstance(filepathname, str): filename = os.path.split(filepathname)[-1] name = filename.split('.')[0] slopelist.set_name(slopenum, name) fid = open(filepathname, encoding='utf-8', mode='r') else: fid = filepathname # with open(filepathname, 'rb') as fid: ncp = fid.readline().strip().split(' ')[4] for _ in range(int(ncp)): name = fid.readline().strip() (dist, alt) = fid.readline().strip().split(' ') slopelist.add_cp(slopenum, float(dist), float(alt), name) fid.close() def __open_crp(filepathname, slopelist, slopenum, num_cps=-1): """Open Ciclotour crp The parameter num_cps is the number ok cp to import (at most). Particular values are: 0: import every cp -1: import the default number of cp""" data = b"" if isinstance(filepathname, str): fid = open(filepathname, mode='rb') # needed byte access not unicode string else: fid = filepathname # with open(filepathname, 'rb') as fid: byte = b' ' while byte: byte = fid.read(1) data += byte fid.close() if not data: # File not accesible or empty return -1 if data[:2] == b'\x78\xda': plaincsv = zlib.decompress(data).decode() elif data[:2] == b'HR': plaincsv = data.decode() else: # Invalid file format return -1 # print(plaincsv) points = plaincsv.split('***') line = points[0].split('\n') # plaincsv.split('\n') # print(line[0]) next_cp = 0 max_dist = float(line[-2].split('\t')[2])/100 lastline = len(line)-3 if num_cps < 0: N = 20 # default value: often looks good. elif num_cps == 0: N = lastline else: N = num_cps for i in range(lastline): fields = line[i+2].split('\t') # print fields dist = float(fields[2])/100 # create N check points if dist > next_cp or i == lastline or fields[8] != '': alt = float(fields[3]) slopelist.add_cp(slopenum, dist, alt, fields[8]) next_cp = dist + max_dist/N return True # Function that return kml string from gpx def gpxtokml(xmlfile): """Load a gpx file into kml""" if isinstance(xmlfile, str) and xmlfile.strip().startswith('<'): fid = xml.dom.minidom.parseString(xmlfile) else: fid = xml.dom.minidom.parse(xmlfile) ptls = fid.getElementsByTagName('trkpt') coords = [(el.attributes["lon"].value, el.attributes["lat"].value) for el in ptls] kmlstring = "\n" kmlstring += "\n" kmlstring += "\n" kmlstring += "" for coord in coords: (lon, lat) = coord kmlstring += lon + "," + lat + " " kmlstring += "\n" kmlstring += "\n" return kmlstring # class for various formats class SlopeFile: def __init__(self, cps): """Load a track file""" self.cps = cps self.cpToLoad = [] self.hasAltitudes = False def min_dist(self): """ Minimum distance of the track """ return self.cps[0][0] def max_dist(self): """ Maximum distance of the track """ return self.cps[-1][0] def __len__(self): """ Number of points of the track """ return len(self.cps) def newSlope(self, slopelist, slopenum, num, start_dist, end_dist, seltype=0): """ Create a new slope in the slopelist""" self.setcpToLoad(num, start_dist, end_dist, seltype) self.getCoords(slopelist, slopenum, start_dist, end_dist) for icp in self.cpToLoad: (dist, alt, name, lat, lng) = self.cps[icp] normadist = dist - float(start_dist) slopelist.add_cp(slopenum, normadist, alt, name) return True def setcpToLoad(self, num, start_dist, end_dist, seltype): """ Set cp index to load""" self.cpToLoad = [] startindex = 0 endindex = len(self.cps) lencps = len(self.cps) for i in range(lencps): if self.cps[i][0] >= start_dist: startindex = i break for i in range(lencps): if self.cps[lencps-i-1][0] <= end_dist: endindex = lencps - i break # print(startindex) # print(endindex) if seltype == 0: # using the given number of check points in num max_dist = self.max_dist() next_cp = 0.0 if num == -1: N = 30 elif num == 0: N = endindex - startindex else: N = num for i in range(startindex, endindex): dist = self.cps[i][0] # create approximately N check points if i == startindex or dist > next_cp or i == endindex-1: self.cpToLoad.append(i) next_cp = dist + max_dist/N elif seltype == 1: # using the passed minimum distance in metres in num next_dist = num for i in range(startindex, endindex): dist = self.cps[i][0] if i == startindex or dist > next_dist or i == endindex-1: self.cpToLoad.append(i) next_dist = dist + num return True def getCoords(self, slopelist, slopenum, start_dist, end_dist): """ Get coords from cps to the passed slope""" startindex = 0 endindex = len(self.cps) lencps = len(self.cps) for i in range(lencps): if self.cps[i][0] >= start_dist: startindex = i break for i in range(lencps): if self.cps[lencps-i-1][0] <= end_dist: endindex = lencps - i break # print(startindex) # print(endindex) for i in range(startindex, endindex): (dist, alt, name, lat, lng) = self.cps[i] slopelist.add_coord(slopenum, float(lat), float(lng)) # vim:sw=4:softtabstop=4:expandtab ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyclograph-1.9.1/cyclograph/launcher.py�������������������������������������������������������������0000755�0001750�0001750�00000006465�13144673265�021576� 0����������������������������������������������������������������������������������������������������ustar �federico������������������������federico������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python3 # -*- coding: utf-8 -*- """launcher.py """ # Copyright (C) 2008-2014 Federico Brega, Pierluigi Villani # 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. import sys import locale import gettext import os import glal_selected def setup_gettext(): """Application translation system setup""" # Setup translation. langs = [] mylocale = locale.getdefaultlocale()[0] if mylocale: langs = [mylocale] # Other languages in the system. language = os.environ.get('LANGUAGE', None) if language: langs += language.split(':') # If there isn't any better use this. langs += ["en_CA", "en_US"] po_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'po') if not os.path.isdir(po_path): po_path = "/usr/share/locale" gettext.bindtextdomain("cyclograph", po_path) gettext.textdomain("cyclograph") lang = gettext.translation("cyclograph", po_path, languages=langs, fallback=True) lang.install() def main(): setup_gettext() if len(sys.argv) < 2: # If there has been an error with the commandline specified frontend # try others in the order (PyQt gets first because is the supported # frontend on Mac Os and Windows where users might want a graphical # interface. if glal_selected.use('qt'): import cyclograph cyclograph.main_qt() elif glal_selected.use('gtk3'): import cyclograph cyclograph.main_gtk3() elif glal_selected.use('cli'): import cyclograph cyclograph.main_cli() else: sys.exit(2) else: # also file=* is an argument even if it is not space separed. arguments = set([token.partition('=')[0] for token in sys.argv]) if arguments & set(('-q', '--qt')): if glal_selected.use('qt') != 'qt': sys.stderr.write('Qt backend is not installed correctely\n') sys.exit(3) import cyclograph cyclograph.main_qt() elif arguments & set(('-G', '--gtk3')): if glal_selected.use('gtk3') != 'gtk3': sys.stderr.write('GTK+ 3 backend is not installed correctely\n') sys.exit(3) import cyclograph cyclograph.main_gtk3() elif arguments & set(('-f', '--file', '-e', '--extension')): if glal_selected.use('cli') != 'cli': sys.stderr.write('Fatal error, cli backend cannot be started\n') sys.exit(3) import cyclograph cyclograph.main_cli() else: glal_selected.use('cli') import cyclograph cyclograph.main_cli() sys.exit(1) if __name__ == '__main__': main() # vim:sw=4:softtabstop=4:expandtab �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������cyclograph-1.9.1/cyclograph/map.html����������������������������������������������������������������0000644�0001750�0001750�00000003760�12430765005�021045� 0����������������������������������������������������������������������������������������������������ustar �federico������������������������federico������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������
cyclograph-1.9.1/cyclograph/openlayers.html0000644000175000017500000000237112430765005022446 0ustar federicofederico00000000000000 CycloGraph: draw on map
cyclograph-1.9.1/cyclograph/report_html.py0000644000175000017500000001000513144673265022312 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- # report_html.py """report_html.py""" # Copyright (C) 2012-2014 Pierluigi Villani, Federico Brega # 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. import codecs TEMPLATE_CSS = """ """ MAP_SCRIPT = """ """ class Report_html(): """html report""" def __init__(self, slopename): self.reportdata = '\n\n\n' if slopename != '': self.reportdata += '' + slopename + '\n' self.reportdata += TEMPLATE_CSS self.reportdata += '\n\n\n' def add_image(self, svgstring): """Add image""" self.reportdata += '
\n' + svgstring def add_map(self, slope, width, height): """Add map with route""" mapstring = '
\n' self.reportdata += mapstring % (str(width), str(height)) (lat, lng) = slope.coords[0] stringc = "%s %s" % (lng, lat) for i in range(1, len(slope.coords)): (lat, lng) = slope.coords[i] stringc += ",%s %s" % (lng, lat) # print(stringc) self.reportdata += MAP_SCRIPT % (stringc,) def addtext(self, label, value): """Add label+value""" self.reportdata += '\n

' + label + '' + value + '

' def addtitle(self, text): """Add group title""" self.reportdata += '\n

' + text + '

' def addnote(self, text): """Add comments""" self.reportdata += '\n

' + text + '

' def save(self, filepath): """Save html report to file""" self.reportdata += '\n
\n\n\n' with codecs.open(filepath, encoding='utf-8', mode='w') as fid: fid.write(self.reportdata) # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/slope.py0000644000175000017500000004554113144674235021110 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- # slope.py """This module provides a model for Cyclograph""" # Copyright (C) 2008-2016 Federico Brega, Pierluigi Villani # 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. from __future__ import unicode_literals import math import glal from themes import ThemeManager class Slope: """Model of a slope""" def __init__(self): """Create a slope model.""" self.cps = [] self.coords = [] self.grad = [] self.dercp = [] self.name = '' self.country = '' self.author = '' self.email = '' self.comment = '' self.url = '' self.reset_calculated() def __len__(self): return len(self.cps) def reset_calculated(self): self.average_grad = 0 self.max_grad = 0 self.height_difference = 0 self.height_gain = 0 def add_cp(self, distance, altitude, name): """ Adds a check-point to a Slope. A check point must have an altitude and a distance from start, it can have also a name or description. """ rdistance = float('%.3f'%(distance)) new_cp = (rdistance, altitude, name) # check if there is already a cp with the same distance # and in this case remove it # WARNING: when loading from a file this cause complexity to be O(|#cps|^2) # If slowliness occurs consider using a binary search instead. for i in range(len(self.cps)): if self.cps[i][0] == rdistance: del self.cps[i] break self.cps.append(new_cp) self.cps.sort() self.grad = [] self.reset_calculated() return self.cps.index(new_cp) def add_coord(self, latitude, longitude): """ Adds a coordinate to a Slope. A coordinate must have latitude and longitude. """ new_coord = (latitude, longitude) self.coords.append(new_coord) def remove_cp(self, num): """ Removes check-point num from current slope and clears data.""" del self.cps[num] self.grad = [] self.reset_calculated() def shift(self, shift_amount): for i in range(len(self.cps)): self.cps[i][0] += shift_amount def calc(self): """ Update gradient and altitude bounds""" if len(self.cps) > 1: cps = self.cps[:] # Multithread safe? derivate = lambda p0, p1: (p1[1] - p0[1])/(p1[0] - p0[0]) self.grad = [derivate(cps[i], cps[i+1])/10 for i in range(len(cps)-1)] self.average_grad = 0 self.max_grad = self.grad[0] for i in range(len(self.grad)): if self.grad[i] > self.max_grad: self.max_grad = self.grad[i] self.height_gain = 0 for i in range(len(cps)-1): self.height_gain += max(cps[i+1][1]-cps[i][1], 0) # find max & min altitude # float("inf") doesn't work on Windows self.max_h = -float("1e1000") self.min_h = +float("1e1000") for cpi in self.cps: if cpi[1] > self.max_h: self.max_h = cpi[1] if cpi[1] < self.min_h: self.min_h = cpi[1] self.height_difference = self.max_h - self.min_h self.max_h += 100 self.min_h = int(math.floor(self.min_h/100)) * 100 # min distance is always in the first item self.min_d = self.cps[0][0] # max distance is always in the last item self.max_d = self.cps[-1][0] if self.max_d != 0: self.average_grad = (self.cps[-1][1] - self.cps[0][1]) / (self.max_d * 10) self.dercp = self.smooth() def smooth(self): # References: # Subroutine PCHIM, F. N. Fritsch, Lawrence Livermore National Laboratory. # F. N. Fritsch and J. Butland, "A method for constructing local monotone # piecewise cubic interpolants", SIAM J. Sci. Stat. Comput., vol. 5, # pp. 300-304, June 1984. if not self.grad: return [] if len(self.cps) < 3: # If less than 3 points draw a rect return [self.grad[0] * 10] * 2 der = [0] * len(self.cps) # Inspired by Octave code in dpchim.f grad1 = self.grad[0] * 10 grad2 = self.grad[1] * 10 (h1, h2) = (self.cps[1][0] - self.cps[0][0], self.cps[2][0] - self.cps[1][0]) w1 = (2 * h1 + h2) / (h1 + h2) w2 = -h1 / (h1 + h2) der[0] = w1 * grad1 + w2 * grad2 if der[0] * grad1 <= 0: der[0] = 0 elif grad1 * grad2 < 0: dmax = 3 * grad1 if abs(der[0]) > abs(dmax): der[0] = dmax # Using brodlie modification of butland's formula for i in range(len(self.cps)-2): (h1, h2) = (float(self.cps[i][0] - self.cps[i-1][0]), float(self.cps[i+1][0] - self.cps[i][0])) grad1 = self.grad[i] * 10 grad2 = self.grad[i+1] * 10 if grad1 * grad2 <= 0: der[i] = 0 continue dmax = max(abs(grad1), abs(grad2)) dmin = min(abs(grad1), abs(grad2)) w1 = (2 * h1 + h2) / (3 * (h1 + h2)) w2 = (2 * h2 + h1) / (3 * (h1 + h2)) der[i+1] = dmin / (w1 * grad1 / dmax + w2 * grad2 / dmax) grad1 = self.grad[-2] * 10 grad2 = self.grad[-1] * 10 (h1, h2) = (self.cps[1][0] - self.cps[0][0], self.cps[2][0] - self.cps[1][0]) w1 = - h2 / (h1 + h2) w2 = (2 * h2 + h1) / (h1 + h2) der[-1] = w1 * grad1 + w2 * grad2 if der[-1] * grad2 <= 0: der[-1] = 0 elif grad1 * grad2 < 0: dmax = 3.0 * grad2 if abs(der[-1]) > abs(dmax): der[-1] = dmax return der depth = 100 def paint(self, settings, devc): """ Paint devc from plot""" # upper, lower, right and left margin of area where draw the slope theme = ThemeManager().gettheme(settings['theme']) updownmar = (180, 30) leftrightmar = (50, 10) margins = (updownmar, leftrightmar) (upp_mar, low_mar) = updownmar (lef_mar, rig_mar) = leftrightmar theme.paintbackground(devc, devc.size_x, devc.size_y) (max_x, max_y) = devc.getsize() if settings['3d']: devc.shear(theme.shear*self.depth/100) min_y = max_x*self.depth/100/10 rig_mar = 10 + 20*self.depth/100 else: min_y = 0 theme.gradback(devc, max_x, max_y, settings['fdesc']) # draw altitude bar metersize = (max_y - upp_mar - low_mar - min_y) \ / (self.max_h - self.min_h) self.h_incr = 100 # draw a line every 100m if settings['olines']: theme.olines(devc, self, margins, max_x, max_y, metersize) theme.alttext(devc, self, margins, max_y, metersize) theme.yaxis(devc, self, margins, max_y, metersize) if settings['3d']: (dx, dy) = (20*self.depth/100, 10*self.depth/100) else: (dx, dy) = (0, 0) # draw km bar devc.setpen('black', 1) increments = [1, 2, 5, 10, 20, 50, 100] # km bar resolutions for d_incr in increments: # draw less than 30 bars if (self.max_d - self.min_d) <= 30 * d_incr: break # this must be float otherwise there are problems with long slopes kmsize = (max_x - lef_mar - rig_mar) / (self.max_d - int(self.min_d)) theme.xaxis(devc, self, margins, max_y, d_incr, kmsize, dx, dy) # draw slope's name s_info = (self.name, _("Average gradient:")+" "+"%.1f" % self.average_grad+" %", _("Max gradient:")+" "+"%.1f" % self.max_grad+" %", _("Height difference:")+" "+str(self.height_difference)+" m", _("Height gain")+": "+str(self.height_gain)+" m") theme.drawslopeinfo(devc, s_info, settings, lef_mar+20, min_y+upp_mar-140) # draw slope's legend if settings['legend']: theme.drawlegend(devc, settings, max_x-rig_mar-410, min_y+upp_mar-110, _("LEGEND")) # draw first info text font = settings['fdesc'] theme.desctext(devc, "%.0f %s" % (self.cps[0][1], self.cps[0][2]), lef_mar + int(self.cps[0][0] * kmsize) + 3, max_y-low_mar-10-int((self.cps[0][1] - self.min_h) * metersize), font) # plot the slope # plot orizzontal polygon in reverse order to prevent bad visualization in 3d mode if (dx != 0) and (dy != 0): linkpoints = [] spath_back = [] colorlisth = [] for i in range(len(self.cps)-1): # i = len(self.cps)-1 - k v_a = (int((self.cps[i][0] - int(self.min_d)) * kmsize), int((self.cps[i][1] - self.min_h) * metersize)) v_b = (int((self.cps[i+1][0] - int(self.min_d)) * kmsize), int((self.cps[i+1][1] - self.min_h) * metersize)) points = [(lef_mar + v_a[0], max_y - low_mar - v_a[1]), (lef_mar + v_b[0], max_y - low_mar - v_b[1]), (lef_mar + v_b[0] + dx, max_y - low_mar - v_b[1] + dy), (lef_mar + v_a[0] + dx, max_y - low_mar - v_a[1] + dy)] linkpoints.append(points) spath_back.append(polytoBezier(points[0], self.dercp[i] * (-metersize / kmsize), points[1], self.dercp[i+1] * (-metersize / kmsize))) # theme.fillhslopecontour(devc, spath_back, dx, dy) for k in range(len(self.cps)-1): i = len(self.cps)-1 - k-1 color = (theme.getcolor(settings['colors'], settings['levels'], self.grad[i])) colorlisth.append(color) theme.fillhpoly(devc, linkpoints[i], color) theme.fillhslopecontour(devc, spath_back, dx, dy, colorlisth) # draw the first polygon v_a = (int((self.cps[0][0]-int(self.min_d)) * kmsize), int((self.cps[0][1]-self.min_h) * metersize)) points = ((lef_mar + v_a[0], max_y - low_mar), (lef_mar + v_a[0], max_y - low_mar - v_a[1]), (lef_mar + v_a[0] + dx, max_y - low_mar - v_a[1] + dy), (lef_mar + v_a[0] + dx, max_y - low_mar + dy)) color = (theme.getcolor(settings['colors'], settings['levels'], self.grad[0])) theme.fillfirsthpoly(devc, points, color) vpolygons = [] spath_pnts = [] colorlistv = [] for i in range(len(self.cps)-1): v_a = (int((self.cps[i][0] - int(self.min_d)) * kmsize), int((self.cps[i][1] - self.min_h) * metersize)) v_b = (int((self.cps[i+1][0] - int(self.min_d)) * kmsize), int((self.cps[i+1][1] - self.min_h) * metersize)) # points that delimitate the area to color points = [(lef_mar + v_a[0], max_y - low_mar), (lef_mar + v_b[0], max_y - low_mar), (lef_mar + v_b[0], max_y - low_mar - v_b[1]), (lef_mar + v_a[0], max_y - low_mar - v_a[1])] points = [(p[0] + dx, p[1] + dy) for p in points] vpolygons.append(points) spath_pnts.append(polytoBezier(points[3], self.dercp[i] * (-metersize / kmsize), points[2], self.dercp[i+1] * (-metersize / kmsize))) color = (theme.getcolor(settings['colors'], settings['levels'], self.grad[i])) colorlistv.append(color) # add also the two lower points (those near km bar) spath_pnts = [vpolygons[0][0]] + spath_pnts + [vpolygons[-1][1]] theme.fillvslopecontour(devc, spath_pnts, max_y - low_mar + self.min_h*metersize, max_y - low_mar - (1000 - self.min_h)*metersize, vpolygons, colorlistv) infotext_x = [] for i in range(len(self.cps)-1): points = vpolygons[i] color = (theme.getcolor(settings['colors'], settings['levels'], self.grad[i])) theme.fillvpoly(devc, points, color) # draw gradient text font = settings['fgrad'] if (points[1][0] - points[0][0] > devc.gettextwidth("%.1f%%" % self.grad[i])): theme.gradtext(devc, "%.1f%%" % self.grad[i], points[0][0] + 3, points[0][1] - 20, font) infotext_x.append(points[2][0] - dx - 4) infotext_x.append(infotext_x[len(self.cps)-2]+50) for i in range(len(self.cps)-1): # another cycle to prevent text to be hidden by polygons points = vpolygons[i] # draw info text font = settings['fdesc'] infotext = "%.0f %s" % (self.cps[i + 1][1], self.cps[i + 1][2]) diffx = infotext_x[i+1] - infotext_x[i] diffx = diffx - devc.gettextheight(infotext) - 2 if diffx < 0: diffx = diffx/2 infotext_x[i+1] -= diffx theme.desctext(devc, infotext, infotext_x[i] + diffx, points[2][1] - dy - 10, font) else: theme.desctext(devc, infotext, infotext_x[i], points[2][1] - dy - 10, font) class SlopeList: """Wrapper for a list of slopes, according to MCV""" def __init__(self): """ Wraps a list of slopes, according to MCV""" self._lst = [] self.message = glal.Message() def __len__(self): """"Gives how many slopes are in the list""" return len(self._lst) def new_slope(self): """ Add a new slope to the list. """ self._lst.append(Slope()) return (len(self._lst) - 1) def del_slope(self, slope_number): """ Remove a slope from the list """ del self._lst[slope_number] # It doesen't send a SLOPE CHANGED message because # other slopes are not been modified. def get_slope_copy(self, slope_number): """ Get a copy of a slope in the list. """ return self._lst[slope_number] def set_name(self, slope_number, name): """ Set the name of a slope in the list. """ self._lst[slope_number].name = name # This updates the title in the tab. self.message.send("UPDATE_TAB", slope_number, 0) def set_country(self, slope_number, country): """ Set the country of a slope in the list.""" self._lst[slope_number].country = country def set_author(self, slope_number, author): """ Set the author of a slope in the list.""" self._lst[slope_number].author = author def set_email(self, slope_number, email): """ Set the email of the author of a slope.""" self._lst[slope_number].email = email def set_comment(self, slope_number, comment): """ Ser a comment to a slope in the list. """ self._lst[slope_number].comment = comment def set_url(self, slope_number, url): """ Add a URL referring to a slope in the list """ self._lst[slope_number].url = url def get_name(self, slope_number): """ Get name """ return self._lst[slope_number].name def get_state(self, slope_number): """ Get country """ return self._lst[slope_number].country def get_author(self, slope_number): """ Get author """ return self._lst[slope_number].author def get_email(self, slope_number): """ Get email """ return self._lst[slope_number].email def get_comment(self, slope_number): """ Get comment """ return self._lst[slope_number].comment def get_average_grad(self, slope_number): """ Get average gradient """ return self._lst[slope_number].average_grad def get_max_grad(self, slope_number): """ Get max gradient """ return self._lst[slope_number].max_grad def get_height_difference(self, slope_number): """ Get height difference""" return self._lst[slope_number].height_difference def get_height_gain(self, slopenumber): """ Get height gain""" return self._lst[slopenumber].height_gain def get_url(self, slope_number): """ Get URL """ return self._lst[slope_number].url def get_coords(self, slope_number): """ Get coords """ return self._lst[slope_number].coords def add_coord(self, slope_number, latitude, longitude): """ Add a coordinate to a slope in the list.""" sel_lst = self._lst[slope_number] sel_lst.add_coord(latitude, longitude) def add_cp(self, slope_number, distance, altitude, name=""): """ Add a check point to a slope in the list.""" sel_lst = self._lst[slope_number] orig_len = len(sel_lst) row_num = sel_lst.add_cp(distance, altitude, name) if len(sel_lst) == orig_len: # if the slope isn't grown then a cp has been modified. self.message.send("SLOPE_DEL", slope_number, row_num) self.message.send("SLOPE_ADD", slope_number, row_num) def remove_cp(self, slope_number, cp_num): """ Remove a check point from a slope in the list.""" self._lst[slope_number].remove_cp(cp_num) self.message.send("SLOPE_DEL", slope_number, cp_num) def shift_slope(self, slope_number, shift_amount): sel_lst = self._lst[slope_number] sel_lst.shift(shift_amount) for row_num in range(len(sel_lst)): self.message.send("SLOPE_DEL", slope_number, row_num) self.message.send("SLOPE_ADD", slope_number, row_num) # Below this line fuctions are part of the view according MCV pattern ### def polytoBezier(p0, m1, p3, m2): """Covert from polynomial function to BeziĆ©r curve p0 is the start point (as tuple of dimension 2) of the BeziĆ©r curve p1 is the end point of the BeziĆ©r curve m1 is the value of the derivate in p0 m2 is the value of the derivate in p3 returns the four control points of a cubic BeziĆ©r curve (p1, p2, p3, p4) """ (x0, y0) = p0 (x3, y3) = p3 h = x3 - x0 x1 = x0 + h/3 y1 = m1*(x1 - x0) + y0 p1 = (x1, y1) x2 = x0 + 2*h/3 y2 = m2*(x2 - x3) + y3 p2 = (x2, y2) return (p0, p1, p2, p3) # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/themes.py0000644000175000017500000007716313144673267021264 0ustar federicofederico00000000000000# -*- coding: utf-8 -*- # themes.py """This module provides a graphic theme for plotting""" # Copyright (C) 2010-2015 Federico Brega, Pierluigi Villani # 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. class ThemeManager: def getthemeslist(self): """Return names associated with classes subclassing Theme.""" return sorted(Theme.themes.keys()) def gettheme(self, name): """Return reference to the given class name.""" if name in Theme.themes: return Theme.themes[name] return None class ThemeMount(type): # from http://martyalchin.com/2008/jan/10/simple-plugin-framework/ def __init__(cls, name, bases, attrs): if not hasattr(cls, 'themes'): # This branch only executes when processing the mount point itself. # So, since this is a new plugin type, not an implementation, this # class shouldn't be registered as a plugin. Instead, it sets up a # list where plugins can be registered later. cls.themes = {} else: # This must be a plugin implementation, which should be registered. # Simply appending it to the list is all that's needed to keep # track of it later. # if cls.themename in cls.themes: # TODO: if user can create/install themes warn if a name is # used by more than one theme # return cls.themes[cls.themename] = cls class Theme(metaclass=ThemeMount): """Model of a theme: every theme must subclass this class.""" themename = "" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" pass @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" pass @staticmethod def drawlegend(devc, settings, startx, starty, title): """Draw slope's legend""" pass @staticmethod def paintbackground(devc, width, height): """Draw background""" pass @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(64, 185, 255)", endcol='white'): """Draw a gradient background""" pass @staticmethod def olines(devc, slope, margins, width, height, metersize): """Draw altitude line""" pass @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" pass @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" pass @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" pass @staticmethod def fillhslopecontour(devc, spath_back, dx, dy, colorlist): """ Draw horizontal contour.""" pass @staticmethod def fillhpoly(devc, points, color): """fill horizontal polygons of the 3d slope""" pass @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" @staticmethod def fillvslopecontour(devc, path_points, pixel_0m, pixel_1000m, vpolygons, colorlist): """Draw a close smooth path thant contais the whole slope.""" pass @staticmethod def fillvpoly(devc, points, color): """fill vertical polygons (2d and 3d) of the slope""" pass @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" pass @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" pass class ThemeClassic(Theme): """Classic CycloGraph theme""" themename = "Classic" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" gradient = abs(grad) for i in range(len(levels)): if gradient < levels[i]: return colors[i-1] return colors[i] @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" name, avp, maxp, eleg, heightg = info devc.setfont(settings['ftitle']) devc.drawtext(name, startx, starty) if settings['sinfo']: devc.setfont(settings['fdesc']) devc.drawtext(avp, startx+10, starty+20) devc.drawtext(maxp, startx+10, starty+40) devc.drawtext(eleg, startx+10, starty+60) devc.drawtext(heightg, startx+10, starty+80) @staticmethod def drawlegend(devc, settings, startx, starty, title): """Draw slope's legend""" devc.setbrush("rgb(236, 236, 236)") rec = (startx, starty, 310, 52) devc.drawrectangle(*rec) devc.setfont(settings['fdesc']) devc.setpen('black', 2) devc.drawtext(title, startx+130, starty+2) incr = 43 devc.setpen('black', 1) for i in range(len(settings['colors'])): devc.setbrush(settings['colors'][i]) rec = (startx+8+i*incr, starty+20, 36, 10) devc.drawrectangle(*rec) alti_t = (">%d%%" % (settings['levels'][i])) devc.drawtext(alti_t, startx+9+i*incr, starty+34) @staticmethod def paintbackground(devc, width, height): """Draw backgroundr""" devc.setbrush("rgb(255, 255, 255)") rec = (0, 0, width, height) devc.drawrectangle(*rec) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(64, 185, 255)", endcol='white'): """Draw a gradient background""" area = (0, 0, width, height) devc.gradfill(area, startcol, endcol) devc.setpen('black', 1) devc.setfont(setting_f) @staticmethod def olines(devc, slope, margins, width, height, metersize): """Draw altitude line""" devc.setpen("rgb(150, 150, 150)", 1) (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar x0_alt_bar = lef_mar - 3 x1_alt_bar = width - rig_mar - 3 y_alt_base_bar = height - low_mar for i in range(int(slope.min_h / slope.h_incr), int((slope.max_h / slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) devc.drawline(x0_alt_bar, y_alt_base_bar - yincr, x1_alt_bar, y_alt_base_bar - yincr) @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar devc.setpen('black', 2) x_alt_txt = lef_mar - 40 y_alt_base_txt = max_y - low_mar - 8 for i in range(int(slope.min_h/slope.h_incr), int((slope.max_h/slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) alti_t = ("%4d" % (i*slope.h_incr)) devc.drawtext(alti_t, x_alt_txt, y_alt_base_txt - yincr) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar linesnum = int(slope.max_h / slope.h_incr) devc.drawline(lef_mar, max_y - low_mar + 4, lef_mar, max_y - low_mar - 8 \ - int(linesnum * slope.h_incr - slope.min_h) * metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar for i in range(int(int(slope.max_d) / d_incr - int(slope.min_d) / d_incr + 1)): if i % 2 == 0: devc.setbrush("rgb(0, 0, 0)") else: devc.setbrush("rgb(255, 255, 255)") rec = (i * d_incr * kmsize + lef_mar + dx, max_y - low_mar + dy, d_incr * kmsize + 1, 5) devc.drawrectangle(*rec) d_label = ("%d" % (i * d_incr + int(slope.min_d))) devc.drawtext(d_label, rec[0], rec[1] + 5) @staticmethod def fillhpoly(devc, points, color): """fill horizontal polygons of the 3d slope""" devc.setbrush(color) devc.drawpolygon(points) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" devc.setbrush(color) devc.drawpolygon(points) @staticmethod def fillvpoly(devc, points, color): """fill vertical polygons (2d and 3d) of the slope""" devc.setbrush(color) devc.drawpolygon(points) @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" devc.setfont(font) devc.drawrotatedtext(text, pos_x, pos_y, 90) @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" devc.setfont(font) devc.drawtext(text, pos_x, pos_y) class ThemeBclassic(Theme): """Bezier classic CycloGraph theme""" themename = "Smooth classic" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" gradient = abs(grad) for i in range(len(levels)): if gradient < levels[i]: return colors[i-1] return colors[i] @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" ThemeClassic.drawslopeinfo(devc, info, settings, startx, starty) @staticmethod def drawlegend(devc, settings, startx, starty, title): """Draw slope's legend""" ThemeClassic.drawlegend(devc, settings, startx, starty, title) @staticmethod def paintbackground(devc, width, height): """Draw backgroundr""" ThemeClassic.paintbackground(devc, width, height) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(64, 185, 255)", endcol='white'): """Draw a gradient background""" ThemeClassic.gradback(devc, width, height, setting_f, startcol, endcol) @staticmethod def olines(devc, slope, margins, width, height, metersize): """Draw altitude line""" ThemeClassic.olines(devc, slope, margins, width, height, metersize) @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" ThemeClassic.alttext(devc, slope, margins, max_y, metersize) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" ThemeClassic.yaxis(devc, slope, margins, max_y, metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" ThemeClassic.xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy) @staticmethod def fillhslopecontour(devc, spath_back, dx, dy, colorlist): """ Draw horizontal contour.""" spath_back.reverse() index = 0 for pnts in spath_back: devc.setbrush(colorlist[index]) index = index + 1 path = devc.startpath(pnts[0]) devc.drawpathcubicto(path, pnts[1:]) point = (pnts[3][0]+dx, pnts[3][1]+dy) points = [(pnts[2][0]+dx, pnts[2][1]+dy), (pnts[1][0]+dx, pnts[1][1]+dy), (pnts[0][0]+dx, pnts[0][1]+dy)] devc.drawpathlineto(path, point) pnts = list(pnts) pnts.reverse() devc.drawpathcubicto(path, points) devc.endpath(path) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" ThemeClassic.fillfirsthpoly(devc, points, color) @staticmethod def fillvslopecontour(devc, path_points, pixel_0m, pixel_1000m, vpolygons, colorlist): """Draw a close smooth path thant contais the whole slope.""" index = 0 start_pnt = path_points.pop(0) point = path_points[0] start_pnt = point[0] for pnt in path_points[:-1]: devc.setbrush(colorlist[index]) path = devc.startpath(start_pnt) devc.drawpathcubicto(path, pnt[1:]) devc.drawpathlineto(path, vpolygons[index][1]) devc.drawpathlineto(path, vpolygons[index][0]) devc.drawpathlineto(path, vpolygons[index][3]) devc.endpath(path) start_pnt = pnt[3] index = index + 1 @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" ThemeClassic.desctext(devc, text, pos_x, pos_y, font) @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" ThemeClassic.gradtext(devc, text, pos_x, pos_y, font) class ThemeBwhite(Theme): """bezier white theme""" themename = "giro" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" ThemeClassic.getcolor(colors, levels, grad) @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" ThemeClassic.drawslopeinfo(devc, info, settings, startx, starty) @staticmethod def paintbackground(devc, width, height): """Draw background""" ThemeClassic.paintbackground(devc, width, height) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(0, 0, 0)", endcol="rgb(70, 80, 70)"): """Draw a gradient background""" devc.setfont(setting_f) # FIXME: move setfont in a new method @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" ThemeClassic.alttext(devc, slope, margins, max_y, metersize) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" ThemeClassic.yaxis(devc, slope, margins, max_y, metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" ThemeClassic.xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy) @staticmethod def fillhslopecontour(devc, spath_back, dx, dy, colorlist): """ Draw horizontal contour.""" devc.setbrush("rgb(207, 208, 212)") spath_back.reverse() for pnts in spath_back: path = devc.startpath(pnts[0]) devc.drawpathcubicto(path, pnts[1:]) point = (pnts[3][0]+dx, pnts[3][1]+dy) points = [(pnts[2][0]+dx, pnts[2][1]+dy), (pnts[1][0]+dx, pnts[1][1]+dy), (pnts[0][0]+dx, pnts[0][1]+dy)] devc.drawpathlineto(path, point) pnts = list(pnts) pnts.reverse() devc.drawpathcubicto(path, points) devc.endpath(path) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" devc.setbrush("rgb(207, 208, 212)") devc.drawpolygon(points) @staticmethod def fillvslopecontour(devc, path_points, pixel_0m, pixel_1000m, vpolygons, colorlist): """Draw a close smooth path thant contais the whole slope.""" devc.setlineargradientbrush([("rgb(255, 253, 241)", 0), ("rgb(255, 253, 241)", 0.25), ("rgb(222, 234, 190)", 0.50), ("rgb(240, 236, 189)", 0.75), ("rgb(233, 198, 153)", 1)], (0, pixel_0m), (0, pixel_0m + (pixel_1000m - pixel_0m)*1.8)) start_pnt = path_points.pop(0) path = devc.startpath(start_pnt) ctrl_pnts = path_points.pop(0) devc.drawpathlineto(path, ctrl_pnts[0]) devc.drawpathcubicto(path, ctrl_pnts[1:]) for pnt in path_points[:-1]: devc.drawpathcubicto(path, pnt[1:]) devc.drawpathlineto(path, path_points[-1]) devc.endpath(path) @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" ThemeClassic.desctext(devc, text, pos_x, pos_y, font) class ThemeBlue(Theme): """Blue theme""" themename = "Blue" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" gradient = abs(grad) for i in range(len(levels)): if gradient < levels[i]: return colors[i-1] return colors[i] @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" name, avp, maxp, eleg, heightg = info devc.setfont(settings['ftitle']) devc.setpen('black', 1) devc.drawtext(name, startx, starty) if settings['sinfo']: devc.setfont(settings['fdesc']) devc.drawtext(avp, startx+10, starty+20) devc.drawtext(maxp, startx+10, starty+40) devc.drawtext(eleg, startx+10, starty+60) devc.drawtext(heightg, startx+10, starty+80) @staticmethod def paintbackground(devc, width, height): """Draw background""" devc.setbrush("rgb(255, 255, 255)") rec = (0, 0, width, height) devc.drawrectangle(*rec) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(255, 255, 255)", endcol="rgb(250, 250, 250)"): """Draw a gradient background""" area = (0, 0, width, height) devc.gradfill(area, startcol, endcol) devc.setpen('black', 1) devc.setfont(setting_f) @staticmethod def olines(devc, slope, margins, width, height, metersize): """Draw altitude line""" devc.setpen("rgb(200, 200, 250)", 1) (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar x0_alt_bar = lef_mar - 3 x1_alt_bar = width - rig_mar - 3 y_alt_base_bar = height - low_mar for i in range(int(slope.min_h / slope.h_incr), int((slope.max_h / slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) devc.drawline(x0_alt_bar, y_alt_base_bar - yincr, x1_alt_bar, y_alt_base_bar - yincr) @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar devc.setpen('black', 2) x_alt_txt = lef_mar - 40 y_alt_base_txt = max_y - low_mar - 8 for i in range(int(slope.min_h/slope.h_incr), int((slope.max_h/slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) alti_t = ("%4d" % (i*slope.h_incr)) devc.drawtext(alti_t, x_alt_txt, y_alt_base_txt - yincr) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" ThemeClassic.yaxis(devc, slope, margins, max_y, metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" devc.setpen('black', 1) ThemeClassic.xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy) @staticmethod def fillhpoly(devc, points, color): """fill horizontal polygons of the 3d slope""" devc.setpen('black', 1) devc.setbrush("rgb(220, 220, 250)") devc.drawpolygon(points) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" devc.setpen('black', 1) # devc.setbrush(color) devc.setlineargradientbrush([("rgb(250, 250, 250)", 0), ("rgb(200, 200, 250)", 1)], (0, points[3][1]), (0, devc.size_y/2)) devc.drawpolygon(points) @staticmethod def fillvpoly(devc, points, color): """fill vertical polygons (2d and 3d) of the slope""" devc.setpen('black', 1) # devc.setbrush(color) devc.setlineargradientbrush([("rgb(250, 250, 250)", 0), ("rgb(200, 200, 250)", 1)], (0, points[1][1]), (0, devc.size_y/2)) # max(points[3][1], points[2][1]))) devc.drawpolygon(points) @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" devc.setpen('black', 1) devc.setfont(font) devc.drawrotatedtext(text, pos_x, pos_y, 90) @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" devc.setpen('black', 1) devc.setfont(font) devc.drawtext(text, pos_x, pos_y) class ThemeBlack(Theme): """Black theme""" themename = "Black" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" gradient = abs(grad) for i in range(len(levels)): if gradient < levels[i]: return colors[i-1] return colors[i] @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" name, avp, maxp, eleg, heightg = info devc.setfont(settings['ftitle']) devc.setpen('white', 1) devc.drawtext(name, startx, starty) if settings['sinfo']: devc.setfont(settings['fdesc']) devc.drawtext(avp, startx+10, starty+20) devc.drawtext(maxp, startx+10, starty+40) devc.drawtext(eleg, startx+10, starty+60) devc.drawtext(heightg, startx+10, starty+80) @staticmethod def drawlegend(devc, settings, startx, starty, title): """Draw slope's legend""" devc.setbrush("rgb(90, 100, 90)") rec = (startx, starty, 310, 52) devc.setpen('black', 1) devc.drawrectangle(*rec) devc.setfont(settings['fdesc']) devc.setpen('white', 2) devc.drawtext(title, startx+130, starty+2) incr = 43 for i in range(len(settings['colors'])): devc.setbrush(settings['colors'][i]) devc.setpen('black', 1) rec = (startx+8+i*incr, starty+20, 36, 10) devc.drawrectangle(*rec) devc.setpen('white', 1) alti_t = (">%d%%" % (settings['levels'][i])) devc.drawtext(alti_t, startx+9+i*incr, starty+34) @staticmethod def paintbackground(devc, width, height): """Draw background""" devc.setbrush("rgb(70, 80, 70)") rec = (0, 0, width, height) devc.drawrectangle(*rec) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(0, 0, 0)", endcol="rgb(70, 80, 70)"): """Draw a gradient background""" area = (0, 0, width, height) devc.gradfill(area, startcol, endcol) devc.setpen('black', 1) devc.setfont(setting_f) @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar devc.setpen('white', 2) x_alt_txt = lef_mar - 40 y_alt_base_txt = max_y - low_mar - 8 for i in range(int(slope.min_h/slope.h_incr), int((slope.max_h/slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) alti_t = ("%4d" % (i*slope.h_incr)) devc.drawtext(alti_t, x_alt_txt, y_alt_base_txt - yincr) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" ThemeClassic.yaxis(devc, slope, margins, max_y, metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar for i in range(int(int(slope.max_d) / d_incr - int(slope.min_d) / d_incr + 1)): if i % 2 == 0: devc.setbrush("rgb(0, 0, 0)") else: devc.setbrush("rgb(255, 255, 255)") rec = (i * d_incr * kmsize + lef_mar + dx, max_y - low_mar + dy, d_incr * kmsize + 1, 5) devc.setpen('black', 1) devc.drawrectangle(*rec) devc.setpen('white', 1) d_label = ("%d" % (i * d_incr + int(slope.min_d))) devc.drawtext(d_label, rec[0], rec[1] + 5) @staticmethod def fillhpoly(devc, points, color): """fill horizontal polygons of the 3d slope""" devc.setpen('black', 1) devc.setbrush(color) devc.drawpolygon(points) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" devc.setpen('black', 1) devc.setbrush(color) devc.setlineargradientbrush([("rgb(0, 0, 0)", 0), (color, 1)], (0, points[3][1]), (0, points[1][1])) devc.drawpolygon(points) @staticmethod def fillvpoly(devc, points, color): """fill vertical polygons (2d and 3d) of the slope""" devc.setpen('black', 1) devc.setbrush(color) devc.setlineargradientbrush([("rgb(0, 0, 0)", 0), (color, 1)], (0, points[1][1]), (0, min(points[3][1], points[2][1]))) devc.drawpolygon(points) @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" devc.setpen('white', 1) devc.setfont(font) devc.drawrotatedtext(text, pos_x, pos_y, 90) @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" devc.setpen('white', 1) devc.setfont(font) devc.drawtext(text, pos_x, pos_y) class ThemeGray(Theme): """Gray theme""" themename = "Gray" shear = -0.15 @staticmethod def getcolor(colors, levels, grad): """Return gradient relative color""" gradient = abs(grad) graycolors = ["rgb(175,175,175)", "rgb(150,150,150)", "rgb(125,125,125)", "rgb(100,100,100)", "rgb(75,75,75)", "rgb(50,50,50)", "rgb(25,25,25)"] for i in range(len(levels)): if gradient < levels[i]: return graycolors[i-1] return graycolors[i] @staticmethod def drawslopeinfo(devc, info, settings, startx, starty): """Draw slope's name and other info background""" ThemeClassic.drawslopeinfo(devc, info, settings, startx, starty) @staticmethod def drawlegend(devc, settings, startx, starty, title): """Draw slope's legend""" graycolors = ["rgb(175,175,175)", "rgb(150,150,150)", "rgb(125,125,125)", "rgb(100,100,100)", "rgb(75,75,75)", "rgb(50,50,50)", "rgb(25,25,25)"] devc.setbrush("rgb(236, 236, 236)") rec = (startx, starty, 310, 52) devc.drawrectangle(*rec) devc.setfont(settings['fdesc']) devc.setpen('black', 2) devc.drawtext(title, startx+130, starty+2) incr = 43 devc.setpen('black', 1) for i in range(len(graycolors)): devc.setbrush(graycolors[i]) rec = (startx+8+i*incr, starty+20, 36, 10) devc.drawrectangle(*rec) alti_t = (">%d%%" % (settings['levels'][i])) devc.drawtext(alti_t, startx+9+i*incr, starty+34) @staticmethod def paintbackground(devc, width, height): """Draw background""" devc.setbrush('white') rec = (0, 0, width, height) devc.drawrectangle(*rec) @staticmethod def gradback(devc, width, height, setting_f, startcol="rgb(0, 0, 0)", endcol="rgb(70, 80, 70)"): """Draw a gradient background""" devc.setpen('black', 1) devc.setfont(setting_f) @staticmethod def olines(devc, slope, margins, width, height, metersize): """Draw altitude line""" ThemeClassic.olines(devc, slope, margins, width, height, metersize) @staticmethod def alttext(devc, slope, margins, max_y, metersize): """draw altitude text aside""" (updownmar, leftrightmar) = margins (lef_mar, rig_mar) = leftrightmar (upp_mar, low_mar) = updownmar devc.setpen('black', 2) x_alt_txt = lef_mar - 40 y_alt_base_txt = max_y - low_mar - 8 for i in range(int(slope.min_h/slope.h_incr), int((slope.max_h/slope.h_incr) + 1)): yincr = int((i * slope.h_incr - slope.min_h) * metersize) alti_t = ("%4d" % (i*slope.h_incr)) devc.drawtext(alti_t, x_alt_txt, y_alt_base_txt - yincr) @staticmethod def yaxis(devc, slope, margins, max_y, metersize): """Draw y axis""" ThemeClassic.yaxis(devc, slope, margins, max_y, metersize) @staticmethod def xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy): """Draw km bar""" devc.setpen('black', 1) ThemeClassic.xaxis(devc, slope, margins, max_y, d_incr, kmsize, dx, dy) @staticmethod def fillhpoly(devc, points, color): """fill horizontal polygons of the 3d slope""" devc.setpen('black', 1) devc.setbrush(color) devc.drawpolygon(points) @staticmethod def fillfirsthpoly(devc, points, color): """fill first horizontal polygon of the 3d slope""" devc.setpen('black', 1) devc.setbrush(color) devc.setlineargradientbrush([("rgb(0, 0, 0)", 0), (color, 1)], (0, points[3][1]), (0, points[1][1])) devc.drawpolygon(points) @staticmethod def fillvpoly(devc, points, color): """fill vertical polygons (2d and 3d) of the slope""" devc.setpen('black', 1) devc.setbrush("rgb(100, 100, 100)") devc.setbrush(color) devc.setlineargradientbrush([("rgb(0, 0, 0)", 0), (color, 1)], (0, points[1][1]), (0, min(points[3][1], points[2][1]))) devc.drawpolygon(points) @staticmethod def desctext(devc, text, pos_x, pos_y, font): """Draw description text for every cp""" devc.setfont(font) devc.setpen('white', 1) devc.drawrotatedtext(text, pos_x + 1, pos_y + 1, 90) devc.setpen('black', 1) devc.drawrotatedtext(text, pos_x, pos_y, 90) @staticmethod def gradtext(devc, text, pos_x, pos_y, font): """Draw gradient text for every cp""" devc.setfont(font) devc.setpen('black', 1) devc.drawtext(text, pos_x + 1, pos_y + 1) devc.setpen('white', 1) devc.drawtext(text, pos_x, pos_y) # vim:sw=4:softtabstop=4:expandtab cyclograph-1.9.1/cyclograph/version.py0000755000175000017500000000340713152470300021433 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- """version.py """ # Copyright (C) 2009-2017 Federico Brega, Pierluigi Villani # 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. from urllib.request import urlopen from urllib.error import URLError import threading # Version of this software VERSION = '1.9.1' # This url is used to fetch the latest version number directely from # the website LAST_VERSION_URL = 'https://cyclograph.sourceforge.io/latestcg' # This parameter enables or disables the check if the user is running # the latest version. # It is intended for environments where the installable software is controlled # as are distribution. VERSION_CHECK_ENABLED = True class LastVersionQuery(threading.Thread): lastversion = '' def query(self): if not VERSION_CHECK_ENABLED: return try: page = urlopen(LAST_VERSION_URL) self.lastversion = page.read().decode().strip() page.close() except URLError: self.lastversion = '' def islast(self): if not self.lastversion: return True return version_list(VERSION) >= version_list(self.lastversion) def run(self): self.query() def version_list(version_string): return [int(x) for x in version_string.split('.')] if __name__ == "__main__": print(VERSION) cyclograph-1.9.1/doc/0000755000175000017500000000000013215023054015777 5ustar federicofederico00000000000000cyclograph-1.9.1/doc/manual.txt0000644000175000017500000001641413215023006020020 0ustar federicofederico00000000000000CycloGraph User Manual ====================== Federico Brega; Pierluigi Villani Introduction ------------ CycloGraph is an application born to let cyclists visualize their routes and estimate how difficult they are. It is important to stress that plotting the elevation of an altitude may be of interest also in other fields such as running, hiking or even skiing. The goal of the application is to show the altitude and the slope along the route and to do it as elegantly as possible. Many other programs can show a simple plot of the altitude, and some of them provide other functionality, but the graphics are essential. CycloGraph aims to be great at producing graphically appealing images to document the stages of a race or to be shown on web pages and journals. Features ~~~~~~~~ .Manual insertion The purpose of the application is to draw a graph; to do this a source of information is needed. The application can be fed manually, which is very straightforward but soon becomes tiring because you need to insert every single piece of information you wrote down while you were doing the run. .GPS tracks GPS devices are very common nowadays, their problem is the very high rate of recording. CycloGraph lets you import a track recorded and stored in gpx (GPs eXchange format). If your GPS device produces files in another format you can use GpsBabel (http://www.gpsbabel.org) to convert it to GPX. Note that GPS tracking data is usually not enough to produce a high-quality plot because although it contains a lot of points, only a few really add any information to the plot. CycloGraph tries to import a reasonable number of points, but the best results can be achieved by importing more points than needed and then manually delete the ones that are not relevant. You may also want to edit some points to add labels. .Files produced by other applications Interoperability is important and CycloGraph permits you to open files saved by other applications. Supported formats are: * GPS eXchange format (.gpx) * Keyhole Markup Language format (.kml) * Salitaker format (.sal) * Training Center XML format (.tcx) * Ciclotour format (.crp) * Ciclomaniac format (.xml and .txt) * SportTracks Fitlog files (.fitlog) .KML format and drawing on maps A unique feature allow to import a slope from a KML file. This is a complex process because a KML file doesn’t contain the altitudes, so they are downloaded from an Internet server. You can choose any of many services, which offer different resolution levels and download speeds. Google Earth (https://earth.google.com) lets you create a route and save it in KML. CycloGraph includes a tool to create a KML file by tracing a polyline on a map (Open Street Map or Google Maps). The same tool can also interface to directions given by Open Route Service, OSRM and YOURS. This tool permits you to visualize how hard a slope is even if you have never been there. This is very useful in planning. Please note that using an automatic tool may lead to misleading results: if two consecutive points are too far apart you can miss a peak between them. On the other hand, if they are too close together the slope may be very inaccurate due to the resolution of the altitude data. Basic usage ----------- Start the application ~~~~~~~~~~~~~~~~~~~~~ CycloGraph has several interfaces, either graphical or command-line. If you are a Windows user you’re probably interested in the Qt 5 interface, because it is the only one which we support in the package for your platform. Note that all graphical interfaces have the same functionalities; the only difference is some minor aesthetic details. This is only relevant if you are a Linux user since only Qt interface is officially supported on Windows. The same Qt 5 interface can look different when used on different platforms so don’t be confused if the screenshots look a bit different from what you see on your screen. .Qt 5 The Qt interface uses the Qt5 framework to provide the graphic user interface. While for Windows packages Qt is the only interface available, for GNU/Linux there is also the GTK+ interface. .GTK+ The GTK+ interface uses GTK+ 3.0 or newer. .Command Line This interface allows you to run the application in a console, without the need of a graphical server. This is intended for experts, not because it is hard to use but because usually only ā€œexpertsā€ use the command line. Editing a new file ~~~~~~~~~~~~~~~~~~ You can start creating a new slope by selecting the first element of the toolbar or alternatively selecting the element _ā€œNewā€_ on the _File_ menu. You can add check-points by selecting _ā€œadd check-pointā€_ on the toolbar or by pressing ā€œ+ā€ from the keyboard. To modify a point, select it from the list and then _ā€œmodify check-pointā€_ on the toolbar. You can also delete one or more points by selecting them on the list and then pressing _ā€œdelete check-pointā€_ or ā€œ-ā€. Show the plot ~~~~~~~~~~~~~ To show the plot after you have created or loaded one, you have to press the _ā€œplot your slopeā€_ button on the toolbar. A new plot window will pop up showing the desired graph, drawn according to the settings on the Options>Preferences menu. If you want to change these settings you can use the menu of the plot window, these changes only affects the current plot window while the preferences settings are applied at the opening of every plot window. .Save the plot as Image You can save the image as seen on the new window to a file. Many file formats are supported but the most common are: * SVG: a vectorial format, which means that can be zoomed with no artifacts. * BMP: a raster format which means that changing the zoom level of the file will affect the quality of the image. No compression is used so the format is simple but the files are large. * PNG: a raster format with loose-less compression, which means that the quality is not affected by the compression level. * JPEG: a raster format that degrades the quality with the level of compression, but can achieve very small files. Expert Usage ------------ Command Line ~~~~~~~~~~~~ The command line interface exposes a subset of the features of the graphical interface. Many of them (such as drawing a path on a map) are not accessible because the console is not suitable for this use case. Editing is not possible but more appropriate tools can be used instead, see <> . When CycloGraph is invoked without any option the application will guess a suitable graphical interface. A specific graphical interface can be selected by means of an option. The complete and updated list of supported flags can be shown using the ā€œ--helpā€ option. If no graphical interface is selected but a file is opened (using ā€œ--file=filenameā€) the application will run from command-line. This means that the slope will be plotted and printed in SVG format to the standard output. If you want to save it you can simply use the output redirection operator of your shell (usually ā€œ>ā€). [[editing-csv]] Editing the CycloGraph CSV file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CycloGraph CSV files use the delimiter ā€œ;ā€ for fields in the same line. # line 1: 6 fields 1. CSV version 2. slope name 3. slope state 4. author 5. author’s e-mail 6. comments # line 2+: check points, 3 fields 1. distance 2. altitude 3. check point name cyclograph-1.9.1/man/0000755000175000017500000000000013215023054016005 5ustar federicofederico00000000000000cyclograph-1.9.1/man/cyclograph.10000644000175000017500000000212313215023053020217 0ustar federicofederico00000000000000.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.5. .TH CYCLOGRAPH "1" "December 2017" "CycloGraph 1.9.1" "User Commands" .SH NAME CycloGraph \- route altimetry plotting application .SH DESCRIPTION usage: CycloGraph [\-h] [\-v] [\-G] [\-q] [\-f FILE] [\-s SIZE] [\-l] .IP [\-o {3d,legend,olines,sinfo}] [\-t THEME] [\-e EXT] .PP CycloGraph 1.9.1 .SS "optional arguments:" .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP \fB\-v\fR, \fB\-\-version\fR show program's version number and exit .TP \fB\-G\fR, \fB\-\-gtk3\fR gtk3 graphical user interface .TP \fB\-q\fR, \fB\-\-qt\fR qt graphical user interface .TP \fB\-f\fR FILE, \fB\-\-file\fR FILE open cgx, csv, gpx, tcx, sal, crp or cyclomaniac file .TP \fB\-s\fR SIZE, \fB\-\-size\fR SIZE output size widthxheight .TP \fB\-l\fR, \fB\-\-list\fR list available themes and exit .TP \fB\-o\fR {3d,legend,olines,sinfo}, \fB\-\-options\fR {3d,legend,olines,sinfo} add option to optionlist .TP \fB\-t\fR THEME, \fB\-\-theme\fR THEME use selected theme .TP \fB\-e\fR EXT, \fB\-\-extension\fR EXT choose default file format extension cyclograph-1.9.1/pixmaps/0000755000175000017500000000000013215023054016713 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/16x16/0000755000175000017500000000000013215023054017500 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/16x16/cyclograph.png0000644000175000017500000000143113152476643022360 0ustar federicofederico00000000000000‰PNG  IHDRó’asBIT|dˆ pHYsbb8z™ŪtEXtSoftwarewww.inkscape.org›ī<–IDAT8•‘KhQ†æ;¹“ŒI|TØV“ЦF©[ą ‚ Hµˆ t#®|n”ŗ¢uį¦n| J]ˆ]| ÅI„¦Vi̫ј&ĪLf®‹‰­5n¼pąĀżĪ’óŃqžū©¾~{—£”Ÿ’8RÓ¬5ĶņŖhŚžŠ —žķߢe±Ī‰‹9RY·ā½©Aɜ@e³¦Į™C3čŠ÷Ī¢ė^”‚ÉYH‰•>)ŲyrŒ„õ:7™7ßĒXĪÅ“Õ$d€D‡æ~AüŹt)øūģ'ݧg³¦ŃϦŽ }ļ¬IP@Yą:½w±‘ķ§2¤ó.’%ļKšā“UĮJSGˆĢ•nńś£Ķ–µ÷ĒQ(öl ’Ķ»„ Ųg›ž~³,ąŸ:Āč‡%Ē“l]nŠŃ䊊sņ”9ŗ9Ģóüæ m™A¤ŚGŃRŌĢōq©wœ :ᐠ]t)I@zI›Č©Ź ±¦Z£ŗJćĀ“q‚~ĮĮ !:Ž#€–:µ5>F³RƒÖʝ]0®ņDŖ$±Œ=‘E…¦ĆóÆ^ā/“»W™¦ŁČŽ«˜ūļpūė» xoŅ6օx•²q4Ķ•¤L× ©˜ źöą~é§žņGā/Ё§F”bį§āĘŪo쌌R“©—q¬±œØūȶÕ1V5øu¾½öw®f é’Óˆ¶12'ĶõĘ,³ā‹č^?@×µé,‰'6”±Ķ‰»#]!ŻBŹ”ö=ĢL°ūód›=(ŸWd2`–ūĘ\‡į²@B¹æk‰Ö[£āĆ#G•šÜéNA N³½_¬’Č»³4€*™BȀ {Q4zöuĄ_`z~øIEND®B`‚cyclograph-1.9.1/pixmaps/22x22/0000755000175000017500000000000013215023054017472 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/22x22/cyclograph.png0000644000175000017500000000212413152476643022352 0ustar federicofederico00000000000000‰PNG  IHDRÄ“l;sBIT|dˆ pHYs & &Q©©3tEXtSoftwarewww.inkscape.org›ī<ŃIDAT8­“[lTU†æs™9әĪōBŚ¢%`o‘ R”՚ V- 7 FcŌōÅZM“Q!@” ˆC >Mˆ©“!M Ä’j)’&- Mm „Wčmڹϙ™3LJցCĖ4&®d?ģ½×’ļ½¾äN57¶tØ»=žh:’CŁ­ā賅ʹīL`ēŃ&ń|G¢@fŗDWo]Ūš54®ķ»Āé˜ ŽŹĶ–é>Į•ći|æ79n/&čˆ¤Ė*ŗŽ9žo‹Ÿ0‘bŲšŒ…łśUt]Fšy)N5^ ²©%Ȳ,™uŽyū‘@Žš€ŹĖ”łā5ēü¼uPÅŌ))2óö‹6Zƒœø˜-2żkü€Ń$j?JfU®™ O)T·_'#EdÜ„¾50·ÖqQlߚHA¶‰W8žˆ²„ŲĀ f>9ęįČÆ‚!}nm<Ļ-SŲWīąÆ”MēŹ@˜¶›!8 ¢k­<š)# ·'5¾nņįWõŲÅŠūV;.Ü‡$w„ā¬YHÕf˜į`E]£¶×¹ł°ÖÅŁ*‡«’QlĀ“ŽRZɎwĒ|Ń4¤é@Q&5¾lņQ{)Ą„7JŁć<ŖNߤF‚"p¼#ėńDŃt(Č2qm8BZ²8f`¼j‰‰Ź+S~EDųō”‡}g¼|°>‘v‘„‹dž ĻĀ×1¦ŖŌFm{ĄČxqŖDåóVŖźŻ„µiV‹R$½‘Äė‡'iļ !‰7F#¬Ī1ó[_Č`\œgęŚķp,}1Ęå«­ģ=ļ%,é1΃>¶žE9f6}ėdż7ü~'LN†Lér%Ö÷d®‰­+%.łŽĀžŚć©vq5:+÷#ž()¢įlŪi¤K|õrūK\Üß‹¹Ü\MŁT·EĖ­[ ,Ō_5&i]¾ĀĪ&·©§łē“ŒõvóRŪ9vųFgī<Ń 1y'’²æĢŻ*šSŠCØ,²qö¦Ź„ĻIvBŁžņÓzX³“›ĶeSü°n%å¾QĆGÜcņQżc'y‰,± ¦čoļ¤ĄÖĻĒÜ’Eį½7§„»&CN׬`łµą½‘®‚œVČwŅ“­Ń“+ź—Óųźõ˜ąDƒ Æģö»&Ī€:ĖXō{;Fąµ1ćHׯ†}$¢‰Üē"0»ÄirB‚x‹Ė)qØŠp ±»ęéŲžź€‹ĻŁ0[åŲY·Åƶ¬…]Ÿc ±WxæĶ¹āā ö™ŖEĻńž.‹$ōÆyX®žĻrodNĒęIEND®B`‚cyclograph-1.9.1/pixmaps/24x24/0000755000175000017500000000000013215023054017476 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/24x24/cyclograph.png0000644000175000017500000000232413152476643022360 0ustar federicofederico00000000000000‰PNG  IHDRąw=ųsBIT|dˆ pHYs  šœtEXtSoftwarewww.inkscape.org›ī<QIDATH‰­”{lSuĒ?÷Ž_×­]éč\·Ń±ad¼ę$DĢnjÄ>@”8!&Ä(BŒŒI$J"1&$ÄĄ?¢Č³„@1KŽMĘcte}¬µ½½/’(”Ŗ[ ßä—Üœßłžļł{Ī‘N5§ź~oQ7öö–…ÄC€$aUQzŸž¦¬’–}öoŻ~¤d”€nŒĢ÷ƒ7 É­źŲ`øS9V”ķē’(ēŪOÜĆścƒ³—µJ9’4¬‘87¾ģ`ņ£Y‚„óTŽU†åD’†%#†Ž N¶«™Ņ\ģѹ1‡ē ’€$Ak·Fķ’Ę Nµ§Š$‹’™JÆBK§–›8Rƒ<ĢžžĻéŽæ6%‘…Ä‚ś|ԐP-J_ļū’óźņ™9ĶNGÆNY±Āŗ·]EĀ0,Ž“ŖlŁ’w¾’žČ…·Ģwļ¹¹vŪ䙏ƒ„ć&ÓĒŪxöq;ū›“tōźä;$’)ėĮŹ0/8ŗ®_±Bk·Ę’Ł4'9Ó£q¦GC‘aÅ+N¦VŁLXx\2—®ė|½7†ŖY™(„õ+ß ÄĢR”“āŠć“ĖT•(L(Ʋ~į(:nč|¹?†æ]å—3I\™å/:9pNJ=r@F!g‹9œ›Ä˜“*@õŹ›żqʗ |^o‘Ģž¶dēął$&š˜O¤m ˆŒąČ“ųt®‹—L\µšŽ’ł­3Å÷'ālšņį'^·LµOpīŗ–³“-½Ó«mt…t°AfФ<ŲÖčfw[‚wv†Y¾;ĀĀķ!P`õÜBā¦Å• ĮčB‰ī°Īä ‘óåOTŚčCķĪŌj;né“ÜĢĪl[Sœ}K=ä;$f|Ä‚1“” S윸¬f|ėĒēįqɜoi¢ĢHĮøz2’`B¹B[Ÿ–3«Ž€Ī” AæjTÓ+bµ?JĆč`źĪrKFš†1±°…ڲKTDłØ© 徤āF|Č$ŪĄŽ÷GXŪąĀe—č›”Ūā?}”Ż'NÓXĆ©^ᩲf^š5ĄčŃé@›>+{ 8@B iÓx; #lļgÅO!¤‚„c€Uī(ėē_ĆēK“Öo)bŃ«aĐRõü„‘ īŠĢŲī–ØęGX“+Sc ŠnĮ#~w&8Ą`Ä$ēž-™0Ńr d„dŅ=šąĀė‡šm3ĖdVŽ–2 $S* ų¦²¦!Ū!šąp.o,ʘzŗśYņ•‘w/›«ĮŪ¼5©E’³č7½UŒsʤMgCU{{Ķ/BŖ^sG÷aĄ,Ī—/Ģ£¬žž€›½^G3IEND®B`‚cyclograph-1.9.1/pixmaps/256x256/0000755000175000017500000000000013215023054017654 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/256x256/cyclograph.png0000644000175000017500000004174013152476643022543 0ustar federicofederico00000000000000‰PNG  IHDR\rØfsBIT|dˆ pHYsvv§ĀxźtEXtSoftwarewww.inkscape.org›ī< IDATxœķgxÕՀߙٮ.Yī½÷nŪ€MǦÓB/”„@B! %4‡^ę31L0ĶąŠmlĄą^dK–-«·Õ֙ł~ŒdĖŅVm_Ķū<÷YYŽ½Ś{ī¹§ DUU³€iĄ1Ąp`ŠČlŃxNJ`ʁķĄ`9°\„†H/.tō…ŖŖZ3K€ćC¤“ŃŃŃ /š5šš‘ ŽŽ\$l Ŗj6pšk K«ß ²‚¤(ˆŠ‚ØŖŖŠŠq£££ƒ ‚€*Ø¢ˆ"I‚, Ŗ"‚ŚźYĄæĆÕ ĀZžŖŖž ü(ŌžąöØ&·“Ē«ù–ŽŽNĒ1ÉˆŪd܂@‹0(īžŪF@ų%$ Ŗj!š4šŖ¢ ŚŖĶéT­ŠŠŲņ<—[å—m¶ļ‘Ł[&ÓäTi°+aüY:::­ÉʱYzw—ŅObĢP#&Ó”e+ ( Ž ‹Š$ŠBĖbūøE„Ŗ`×*TU<fŌ£±IĶØoTrE[ų v•O¾qņŃ'ß’āĮé Iščččt‹Y`Ņh#gĪ“pś ™6m ‹"Jv¦X—iģĶO=\'Āś@× (TU=˜XeY•Ź«•B§ +@eĀ‹ ķ¼õ‰»C_ō::ń&Ć*péמo#?GSÄ-fĮѵ@¬DdĄÜ$Ā7ž®įWØŖzšaĮąt©ęŅr„—,«U…ł;xņFźõ…Æ£“h²3næ"“‹N³" Ix{u•J-fĮ…ę-ųµ ßśz­O Ŗźxą%Ąbwض’żr_YAŖ©WųĆæźY¾Ž»æFGG§CLŸhāo·e“—-"‰Č}zHÅV” pW ‚šSŪדŖŖę €B‡ĖöbĻEA*= óėæÖQ¼_Žż_¢££Ó!zu“xö¾śōEAŌWŚ‘a@pa[Ćąa@UUx ˜āöØĘM;呯jŚ[&sŻżµTÖč}d§0_äÅæäҳ«„Ń€{Ō`ćF£/°ųmk”Ųęµ§Gž­»åžMNÅTQ-sĖCuśā×ŃI*Ŗn~P[³MNÕ“u··?ą&'¶~īA @UÕ ąM ÆdæŅcW©wØ¢ØÜżxk~ÖĻü::©ĘŌq&ž~[‚ 0Ø·øµwwCŚQą2Ašąp `éņĄĪ½ŽŹū_9õÅÆ£“¢|÷“›¾rāńØģ,Qø<*@60»å9"€ŖŖ&ą Ą½}·§W“S1–×(¼ņ~“¦#čCśHÉńņūMTŌ(ŲŠiĒoOĄ Ģi^ó5€£€ ·WU÷ģ—ūy¼°p±ࣣ“ź46©¼ó…vļWś¹½ŖŠ–¦? €i€gW‰·›Ć„ėÆt%\zéCśˆ|üo¹‹śF‡S1•Č]Ń ‚ĒˆĶyżĆOŃ>¹Ē Ė×»qč1ż::iĆ„²āG·¦ģ“{£ €‘ŖŖZD`( ø=ŠZY­z¼*+Ō ::éÄŹŻx¼*5JWŒ(Ą0šģާtu{UŃćUŁ^"kźƒŽŽNZ°u‡KÅ !ķŁēĶÜǰl@Kóõ”U)¹ÆŹ®½2n®žw2¬Ż Dr³4sŖĀ*…ŠWæŅ·G[ŪzI”U©9ƒū°(4y€§¦NÉņxa_…¾ū§3{Kœr“…&›7Ģ@ßī’ĻēyeŲRäåūM¾ųĪÅ«\Ō5źŃ ©Ģ¾ ™ŽŻ$Ŗj•l4;@°žz»jóxU*kõ/9ݐD8ļD+ןgćčq&„¼A‚Ńƒ ŒląŠ9V.•÷¾r2oõ›=±Ÿ“NŌ©¬Õ“ŗ»bCfš+Šćp«š\Ŗ®¤³gXųĒ­Ł īć{§«Yą’Ó¬\|Ŗ•O—»øóńzvīõFi–:ń É©¢­qň&¤–8Ė„JƊKw¤]rEŽy4Ÿwɋxń·FąōifÖ/(äŽĖ2CŅ&t’§<^— M`hłĮ#£zdPT]Hu& 7ņŽ£łō,l›ģ=,&xč–,¦aā²?ÕŠ`× †ÉŽ¢Ŗxdm­Ó¼īÅę<Ŗz¼*ŠnHi¦aā‹g bŗų[sź1f>¦€.¹ńy?Ž£(šąńØ-ĄsHPP=^-:@×R“I£Œ¼÷X>Y¶ų~G 7ņĮ泜rsMŗ&¬(€Ē«­uZinĄķö(ŖŪ£k©JažČĀĘń·0i¤‘’˜›÷Ö EŃāÜE„yŻ:xUMPt žŠ¼|_.=ŗ$V ?ē WĪŃ{Į&+Š¢y<^G·Õ#«ś 9o¦•“2'züķ¦,>^ęŌćI’šČ*nąĆčÕl²^ō7„Dø’ś¬DOć 9"·_’™čičų@–›mރ6Ļ! «š ’šüe}„>fϰ0$Š~žhpķŁ6²3…„6ś8|(Ŗ¦xdõ0 “Ā\vZņ¹³3ęL·$z:!`h÷›‰”“ōXĶ'NI޳[ΜaįĶωž†Nk|¬k]Ha&4b6&z¾9jlhIG:‰åŠöĢ “ōŒ˜¤«(ĢéV RV„{’ė[×R˜~~rł“…¾ŻŪŸ0u’‹Ćæ!]H)lÖ䞢r³ż^J&|¬m]Љ.·Ušģč^€¦².¹Ļ× =µ<©Š½éÅŽņäŪÜYšÜóÓŃ@J³~kņÖę+> ėEDSŻ ˜Āl,ņPUÆP|rü«ļ]ś}”lčnĄōĀ+Ć¢®DOĆ',u&z :! k)ĪK7qł©ÖDOć0J+¾X«kI‡®¤«7¹YöSr•r~d~#²~üO |‡jé’;„øėŁz–?Ū1 ¾·ķ{e^YŌ¤ßC)‚®¤ė¶z˜÷®=ŃÓ@QįĘGkqé½%S†C€ˆnHažųbG61ixā„žņjĖ6øõū'YiYŪ­¶}]H\•sī­¦hb‚oŽXģąŸóņŽ:GN#Źkfż¶Š’=ZĄŠŽńĖœ’…ƒė­EżŽIfōPąōgo…Ģq·TņåŗŲĒČ Ü’jW’³V·ś§(ŗHCŖźfß]ĶŻĻ7hŻžc@Ń~™“īØāļo6¢ź6æ”EJS_ŲČūĖÜwE`=Ō :Ŗź_hēéwķ8Żz¶_Jįc}ė%[ҜŻe2Wż£–’ÓĄµgd0w–•łįK‚µ[<Ģ’ĀĮ7awź[~ŗ Wź$ģŚ/ó‡ė¹ē„zŽjdŚX3“†ŅŪ@‘ĀM(8ŻPÓØP“ßĖ–b/ßmņ°äÅZyōū#5ń±¶u  “”ŖšżVß'q*±Nü8ÜŠņØKxōChóˆīŠŃéŌč6”aā0#—̲1i˜U…õŪ=¼øČĪ/EŽDO-5Šm:©†Ł(pŽ 7ĪÉh—ē0y„‘ėΰńä»vī}¹^Fźķć@גA€žŻ$Fō3āńŖŌŁU6ģņhžų4£w”Äu§ŪøźT…¹žOŖ¢æ=?ƒī"W>\«%CH= ²En='ƒKgŁč]xųbšxaŁĻn>Xīä£UNöW„v%ŽćLÜ8'ƒŁGY0„‘Ī0÷+?īššD¤E§ŗHr.?ÉĘĆ×e“—uHl @ĖFg4Ą Lœ0ĮÄ7e³f‹‡V:ł`…“ūRćlœiød¦fŪŁæć·ä}—e±š['„•©-扞 ˜ÄüķŖlī¼ QŚUüQŃüśŠŠŖ"Š0u¤‘©#ÕsĀAo – 2­/ü&ч–QĀŽPUBv’EB(k[A@Ą‡łØ*¼µÄĮĶĻŌ”€~’śĆGk0ż æ.‡=ü;ü=²ŠAĀźųė•ć£ū !J&QQõ™ œnx{©ƒg?±óƒnųėķk¶żY'źĢžbįņYÖ ĻóŹ*¢ ö^9öŖGDP¢œœ·æZįåÅM<»ČNe}óyIægCGJ…9"ĻܔņóYÕ“7D”½0Ьšń[žį¬5AŠŽ‘šU–lpóĢ'v>]ėŌ«’D Ż gž¾1‡®*ÜųCT”ę…$ š0@9NŖ비+:*Ÿ*o/ušĢ";÷“ @Ņļњѯ€‰åŹmœ}“%*×jŁõÆy²Ž‡Ā™S-œ6ŃBnfģW†ŖŖD=· [J¼<ūió—4ŃąHĘĆMz kq¢7‰‡ÆŹŽź5?Xåä%MŚĻß9‘D˜:ÜĹG[8ū(+= bćåUTĶļ*”._YEk<³Ø‰o~vŅōū1:č@bxń–\²mŃ»“÷W+ÜųLŻaæ“X±É͊Mn~÷r=S†™8sŖ…³¦ZÅ|UÕFØJ@°ĮŚF•łß4ńäGvö”ėå¼ā‰Ž(Ü~v&ÓGG/^^Uįś×RŻč?éEVmu³j«›»_Ægl#gNµ0g²…±"—ū²ŖbA X·ĆóŸŚYøĀyز±~ļÅUæu ƌģcąOE7ŃēłĻšX¼ŽÖk6ģö°a·‡4ŠÆ«ÄģÉNŸhaśhS‡ź( ØBp- m`’Ėļ®tšģ§M¬Łę’u¢Šnˆ!f£ĄnĻĆÅd¹]e2÷¾QŃw“§BfŽ";óŁéž'2g²…3§X˜1ŚV:Æ·9Xɟ•CÖ’²…7æqšļEvöU7«łś}_t@|¹nc"(sŻÆ æz¢–Ę(–ķ*«Qxaq/,n"7C䓉fΜbᤠflęĄ+T¼ŠŠAl/EŪż—ntó̧v>^ćÄ«ļ“]Ĉ£‡›ų͜ö‰>‘šwcŖ6׌ŽśÖĮ[ß:°™f3sęTķØēĒ½ØŖZŲ²Ų¤°æZęķå^ł¢‰Å©Ń› ³¢c@†Eą„[s–ō—vyųĒ»qūnšÜ*­uņŃZ'FI`Ś(gMьˆ=ņŪ’av§Źß9xšæģŲÆģ$%ŗ0>üėŖEŃķętĆUOÕā‰sÄ_ Yåė .¾Žąā¶—źÕ×ȑƒtÉqŗU6•xY»Ż­ģ¤ zUą(sĘD WĢ žčx£žM{½Iń½(ĄĻÅ~.ö‘}—óÓ €õ­‰"]²Ež¹!ōDŸPXņ³›g?Ӌ\čĆönĄÖ:ańōu9tė@¢?źšT®}¦V „Õæh »cĆÆŽ·qĪŌC‰>šA\ŌgĀŻśb%z§[¢÷ˆż %½"AÄ¶%³“(ĶńóŠš@ųp“+św"¢} %†ö40¼—AŻ ägŠdŪ²­“öAŗ<ąp«ø½*Å2»ĖeöTČģ:ąå—bo\ś$& ½/@ōxżÖ<ņ³Ä€U{ÄfĮ*!4 ’„“Ėjn|¾®żEtb`ā 3F™˜1ŹĢԔƈ{:ŻšC‘‡5ŪŻ,ßģęĖ .š\i,нó狲˜>Ņv'ߖRŚ hĀ@9¤\’\-U}:+¢ĒŽ0qń4+gO±’›ŻČb‚£†9j˜‘ßœ‘A“KåóŸ\|øĘÉĒß;SßĶéc}ė@LjāēeE\C@”Š+e†ō0°µŌK‘ž @A–ȍ'gpÅńVśt‰]kń¶ŲĢgM¶pÖd •7—:xę3;Ūö„Ot£.:ˆQųäžü°ŖöCU”{®Č£æŹęŃ_e³©ÄĖ¢õN­s±z›»Ó5¼čÓEā¶32øņx[ÜZ‹ū#Ė*pćÉ6n8Éʗ\üåæ ¬Ż‘ś•ˆõ#@yé×9dE7Œ¢m ż‘} Œģ“ɝgfRÕ šĶF7Ÿ®×ĀsėS] @–UąŽs³øé” LI¶E œ8Ī̬±fŽ_ćä¾ lߟ"~ˆC{ødŗ-Ŗ×l:ė‹‚,‘s§Z8wŖ§¾Żčā“ušvPZGA€K¦YłŪÅŁtRų]>ŁV!š€.:Čā .^üŖ ¬ āQ› }Y»Ś/‰FIĄ(Aä`ŖrːDĶm2“xźŹŒQ0p¶å–“3˜{L`ƒßĪ23īÆdõŽ$ö„·BUį6rį58ƒDīc`ž-yjĀK‰VQRlÜņzó—;¬āńŖŗGÖ¢Ļ4_•_Œ­6AØī5IŌTō”=$Μd‰źētĢpĶ Üõē/3žZÉ®ŠäSłƒÖ;9÷ńjīĄ_ņIcĶüᬬ„Ļ·5ŗ^®|®–ĖŸ©es©ęś'J}mŁÉC\÷‡!QąÜÉŃ t*Č™sĘ;ßĪ2§’³ŠŠśśDcĢ—?»8ū_5ø‚hwĻÉdJœ ®”pøPoē`Ā=’`-²³5AÉ!Ś9?²k3̵Ļ改Łō׿æVᓇ«8PÆ$ü{Œt,Łäā†Wj ƒÆŽK¦UHģ|›Ń5€(”Ŗ°r››{Ž®gģ]Œ¼³‚ßæUĻŅ-īøµÄ2DéŪ,ĢŽĪ…¦ 7ń«>.žWĆī$– ‡·V8ųĒGŸ3؛ÄΌnĆŲŽ¢'ňå^ž\ģåÉÅvņ2DNeāōńNŸ`!7ĀL4_ˆQ,L µ…E€Q˜wEN@ğ6°r»;ķī·æ¾ßĄ1ĆLLī_Õæł¤ ^\bÆšóń9ė@ر+¼»ĘÉU/ŌŅūęœņĻję}n§(Š_~4א$ ägFvk\z¬•į`¾Śčę‰ĻŠ¢ĀÕ/ŌR×ä’,`1ĀC6ŒĘŻēįQT–lvqĒ[õ 泜įw–sĒüz¾Žäʛ$60UUµčµžÜ5;Óļõøå?u‡ś¤į(®–¹ūķś€Ÿó9“,ŒļoLĢ›Ń5€ST!3ļ ;§>\E’Ūpż+u|øĪ‰=Ģj“Ńt:(* ķŽńšÕ¹GYPčßģ’Ļ’'>>†¼¶“‰ ’®n=9ŗ¤ĆEH¢QŃ šŚ²&.˜WCĻ[pź#ÕüūK;{«ƒ«Ńr;*ĶAM‘hæ9Ł’ī_^ÆšÄb{Ā?ėx ųż‚ĄZĄł“­Zõ£xĪ­ŗ¤8=*_orqūüzŻq€ ¬ąŽf£™Æµī«æ@øØŖL4¬GĒ4€ żŒŒéć’µO,¶Ó$`&X²ÉŲ­ž#Møī„Äi‡my|>W'lŚēeÓ¾Fż_#½ņ$NoęŒńf 7ciĪLV‰ģ«“[…*ėačŠÅ.;ÖæŪÆĘ®ņĀ{§»æžžĀĪ“až=L±ņ×b?”Ķ#ŗ’”Öȼ°¤‰9WÓóÖ2.˜WĆė˵µņʇ') (”0Ā[© .œā_¼½ŚAƒ³óģž-|ņ£“Żz<é&1ŗwbR†uāŲ]*®wrŻ+µ¼ŗ4xrR[TUKTj›Ńfa`ך2W&0Ń%@©ō7V4…u½tAVą’V9>ēģ#C«ŒmŚ[õ‘rck™÷`…a|(IQ÷ØĶ­ČZžćOX„k ų²­LęūŻž„F‰¬\;ą¤1ęŲσVĶč@Ń6”%ŁŪœ©čöŹPō†”8,LWąń#üēśžKšāéĢÅž€Ē€q}a¹¢nL#¶–E×·>4 C (ĄŌAžė$~³%żB~ĆeéV7ż»ųVõĶ×ĻȚ]1¬…ąC HŽbe1 K–ČȞ†voŻ äŚDrlŁV‘ ³öÉ8Ü*.ÆŠĆ­²ÆV¦ØBfO„LQ„ĢśŻn-g= Ł])ćōpŠ+)įh}ņ%l&ß+\Qay27̈k‹Ü\ (ʤ1>Hė¾]2E¦31c˜™ĆMŒŃ·×*Yg¤xöŠ…ļvzX±ŻĶ'?9Łž$Qm °£ÜĖč^ёėĆzÄŠ Šƒ‹Ņ™š&½ŻłŚŻ‹ éŽ1×kČųXŪi§ŲLs&X˜;ÅŹ¬Q樄ȶ¦0Kdöx3³Ē›łĒłYl,õņĮNŽ^툺.[Ė¢'²-Żs$ö××x†vó’ž;ō6ēl roōŹ‹æI.mś ,”ųķI™Ģb%+Ī­¤Gõ20ŖW&÷œ‘ÉכŻ<óµO78ŅĪ;ŚhXūė‚/ąŽłž]†ŪxSņžŠ6v·Jm“ź7¼w^K9…JĖŚn%gR^×Ēȝ§frĪ–Ø6ģģ0s„‰™#LUŹ<ųqo}爫 ¶Ė„Ėšī¾Łā ś¼ū‰/*“$Ķ1 (­•ɵł^v}ŃX‘²A 2Eī==“Ļ е0¢Ę€./_™Ė§fņ׏xw]|Ü`[£lŚ=“]ÉźĒh±’Ių%‚:‡aŲ%KÄfb—+įć;H9 Ą ĀĶ33ø÷Œ,²#Tõ=2”ÕÉŌ9Tź ®ęµ“i0 g®Dבm”0¼»·®Ėćӟ]ÜįŌN IDATÜōfūB8OGĀÖ2oÄ9­ ÕąĻtŖäŸ`ˆJ$ @~†H“;~6“”ŗHüēš<&ßĻå–aõ.7ßlqós©‡Ķū¼UŹx‚ōē³™św‘ŻĖČԁF¦ 41¾Æ1lćāicĢüšēB~氞×WĘ.$ÖīR)­Qč%ƒRØ PéÆdnšo JGœ[ „Œš¢ÉVž¾8'¬]ßīVłčG' Ö8XŗĶķ{' r¹&Ź¦ż^6ķ÷ņßļµxīü ‘3ĘY8ē 3G˜1…xt˵ ¼š«NcęŚ×ki ³čGØl-óŅ;/:„§{ēKdZ„ s­iņÆŚęeˆIyO%‚@¶Šī٘}V©č”Dxģ‚n<Īņk¶”yyü ;ļ|ļˆÉ"«¶+üge’YŁD×,‘k§gpŻ ŻC¬¦{ΆvėĀłĻUĒ$Øhk™—™#¢#`H7?öa:ŪĘ¢jŖ’@Č qo‚ھ&`ėŸ<¬&×ē…¼ų(ńpĮó5LųkÆ­l¢±EšĘp”7*üķÓ†Ü[ĪĶoÕiõķC`t/+ī.äŲ!Ń«Įß2¢mÖ=xRPM€˜]²ā\ń&I‡Õ$PĄ¦TeqoŚüL'åŁD>»­€9ć‚w©©²+ÜüVG’½’LŒ’ŻķUyqY#ī+ēOƒöŠČĻųč–|Ž݆œ1AdÜA}Įtb`”Šc•ˆˆŅöķĮ“@RfZ>¼9Ÿ©ƒūŽžŽÉ˜ū+xqyJĢßīVyšÓ¦ü½‚5AB?2LܔĻÉ££—š `c€nH#z¤čüm©<†ˆ–Ų^‡¾ˆ“z$ 5‹Qąņ™ÄŅow«\÷F—æR£©NIʖ2/Ē=ZÉ‹ƒj$ü÷ś|Žsū¾Z͵-BѶ–yńųQ¬FĮ]u-`Jļw{y"5Hø„DxżŹ\ŽP? “šėĒ<\Éėß5%|΁†¬ĀƒŸ6pĮ 541FZ °š†<JQyļmQ¼™†t ¾ƒ»d5ą |ü°8¼Hņ1chąūśū=q*˜ŅФŅ~wb&g|ęßPźeś£UlNPĪŽšń'Ó©¤,ˆ°0Säƒ_ē“k‹ük‰fN€ŁżBS]¹Ó*ėÉ£¢kēH5ņl"ćūų×j^ų®(ž)ÓIÓhĘ0™øaāšŻf=QÅžśŌė!æ©Ģˬ'Ŗ(­ ,†u30onNÄļm;Ą ®Įķ‹7łĻ˜1Ō¤… 'Įw‘ˆ1g¼Iš’ł®ŚåĘé½×źąh&)4€.™"o^™šŚøßĖ™ĻVō7';Ū˽Ģz¢’ ’†ó°pÉäȊDF[˜B(Wµd› Q¬&óŽīŃIW. ņ}~BĀU,H š÷³²ĘÜ×Ȝ>ƚź$4ö…Ė®J™óžÆ ņłųł9ō ;l[”Ą¦żĮ= N5ą1ąźcŪ+QōĖ—8v°’óæ ,\øjp¬hæźā¬Mjⲩž„£Ė s_®IIµßßX³Ē͵oÖģē—cxųœģæĒĪ*ł`rS¤,Łę¦Ø*“Ļ’?«żē95ŠČŲŽ j†™Ąń›™3V—ķżóx“!”€Q˜waŽÆyä®÷ė5ėhš±p½ƒ—ƒŌÉ?{¼…ivŽ@ø½*ßl‹\­¬wŖÜ¶°.äēæūƒ“źQ:Ķߥt¤[¶ČUGŽd}subvH°šāÉV†ŽX¼ŁÅ³ĖŅ·‘ä]Ō³3@©h€GĪĶFl[­)Äq’¢†ˆZŽ×9UĪ{±š--}BNÆŹü5žŪģ±&%Ŗ%vĘļOŹÄjü~ÕM*ļžčˆ’ܚI˜`ᮓüļŹožśĪ“Š4ŗTnx«6ąs&ō62{LnjgėK<\śZ ĪVy8ś‘J¾ķ@5ßg–Śńų<šĶ'Pśpŗ0¦§‘ė§¶{<¹¤1fY””°@  'ZŌÅæ‘ėļŸ7RT>ē~céN7n\-čÖć3:|ż÷r2ńŸ|øĮTPTX²ŻĶ…ÆŌpü“•ģØ }ēo=vUÉZ–¦ 2i # >’X Q„§/ĢĮ`‹­uØ<·,Įl­HX|ęM3üKƊF…§æ±Ēq6‰åž8m“ÅļĶrģ Gö5².HJ®?¶—{¹ąåŗf‰œ8ÜĢŲ^FzęH%-¤ŗ¬^a}±›»ÜAƒ•Bå”ō\2ÉźWż}pvŸntR\žƒož‘ĮQAĀŁ’õu#µ vk·ļ Ōöē0Ŗ‡#ūś’pžXb§ÉÓyźČķØōņö:G@_ńÓ3øf~ąćB0Źęļ`ž÷ŒNQśĢKėdžYÖÄ3} ś,‹Ąü+ņ8q^UÜsącĶQL½rļ«o}ļ`ÉvWĒŽ§©6¼œŁ|›‰»0¦§1`鬳kēM4 Öé!?61=ä#aé7ωwxpv·° „V£Ą{×ä1¾wąs’Ī ™Ūß«ļāŖ %Ėą³ėįõI°ģĻPŗ ܍ĢX#īEAglŁ]%³jOēķ"ūŃ/Nž:/Góūū`ę0SJ~6w}TĻ}ŒLéļq<|–vf~*ČQ(É·‰,¼*cŚrøU.z­†Zgˆ}«·Ā¦°å=pT¶’’ƽP0<ō‰śXŪq׎ā?-ō«m®N]Bś@ƒĀŖŻž}ņ¤ &.ÆŹ…ÆÖ°æĪ’Y@ą‘³²yņܜ˜ōsŒżņ%¾¹µ čāøå:6ģ āÉq7Ą/oĀĀ3`ž šĆ ¾?@Cif|8q’اؾݔ·^¤=ōä~Ń©oö×Ė\ųjMŠ&!7kćĆėņ)ĢL~)pźH3+~Ū%`4k .nä@Ēۚšķ½šźDXr”żąóią\ąNkØ/éŲÄ[×d ‚L‘.¾Ų„;Ż 7ä$z¬ āėŸŌ7uĆhW»9ļ•š Sg 3³ž÷…œ>Ś’š9ūf£Ą£ggóž5” Ŗ/³óĄā†ö×B½Ėą“_Į›ĒĮ†×üžė»w;€w«(AciųóoC\wń’vµMŖ–ń×É &ų SÆ¶¹8’Õj޹*s€?„k–Č{WēńĘZ\Tµ„H9e„™ĒĪŹapah©ŚÆÆqpĒmŒ~īŲō6üų2Ō|ż‘ĄuĄå@ė€š-²+*@\ۃķ …t„ŽB`ƒL“[õŪBŖWNŒ[HǁϷjBą­Ėó6Źøl’•³ĒZxlI#O~k×:ē$€1=ŒüåŌ,N£“Ł#_7ņ§O““oØŲ?½ [߯’šop šk`¼Ÿēl–]ŠP޽в¶Õ¼{–°³2ujüŚ}uŠß¦wsŖ°x‹‹ćēUńŽUyōÉ ¼›fšž|J7OĖą„UM<³Ā7ą˜&ī8†@ ¾hÕM'ō7„u²_Š#[B’“›,ÕŁ°ßĆ1OUņĪłLī¼DA†Č]³2łķq™|²ÉÉ»?9łßfgŌµ‚~ys°2÷+ĆC0𵦼AįŹ’«åĖĶ °ķ#ųžØŚź÷ł0mįŸ„r°Ų 8Uš*Av€!Äų”Ķ#qÖlņ¢›’ ,2Yh pC$č™-Qć6ćńā@ƒĀĢgŖųćI™üīųL¤“ĪkᜱšÜ*KvøYYäfÕn7ėJČžd›‰k 5@kdG'Jž F°Ec(I£R nEå¾Ļų߯\”ĖĄ‚Šk!ŚL§4sśHķl.+°·V¦ØZfwµL„]Į#«…jŽE Ē"’gX 1“ŠVĒi_48Unyq ’÷ņ“°ł=ż»r‡£-śĖ€ŽÖFśń°7/ü€µW P‡œ@5Ó:4% Ø/=UYµŪĶ‘’Ŗą·Ó3¹ćų 2lžD-8'”>‘¢(*|īę=ł8®ßč|p;p‘ļs‡ €ś½]«½Šś1ŹŹ}ε ŗŠL°æ1–=äL“Gåo_5šŹŚ&ī?9‹Ė'Z“nsp»Ż<šō|ę=ł(Mū6ū}ž 8ø˜Å÷?\( ßЊøjµśÕåXŅĆŗ ²‚|įžqS‘żõ2×/¬å_5pĶŌ ®b#ǚXIPQ^ĪŻĶćķןĆÓą'<Č®nzFy%Ąaļ\Y,@|€ÓæŠ-@_€Ī„(@ß\’ź«Ż­ķ3˜NUĖÜūi=.iäŖ)6.ž`etųC}³śīżŪc¬ū|Ŗ×„åĮĄmĄ@¬riū‹Ęż]/®nĄā–ė]i«Ö†CŸ< KŲö ļ”Ą’NDSį±oyģŪF†8w¬…³F[ÓĆ“#Ā–ž{1 ^z‚źMKt¾Ÿü˜Cģ“kŚ%V«rč÷B¢Ż€æ”łsķ+’k^#-Ń - ü•ģØJ÷_$l­šņŠW<ōU#9‘©żŒÕĻĔ¾&HōĪ•ĀŹ(,­SŲZīek…—•;źYüĮ’Q·ņEØÜā÷5Fą|“…?1Ņ?( Ś}ūBdĘĪĆ5€»4*TŚŗdųžvĘö4°4H6\ŗsT’ĄŁ~Ū*ōéÖŌ¹os±øU£$Š'W¤wŽD–YÄb€,³ˆAŅÜ͵•:§BSewµW;R5–Į†łšĆkż÷Łh*ž@ß’m¾h'D1<  ĶśŽ{fÉĘ2/3üäNĻbīōąø yåkJ:÷ē YeW•Ģ® Ś’ {–Ɔ7aūg ųG€fŲ»ȍā\Ć%vÄ\XZäö+Nję¾Ļb÷ęIN¦I`R’Ą#ĆņŻ·bRŌp7ĄęaŻĖPµ-ąSD ܹ˜ÖŠoE{ Œä°D,ŽęäO³|Ē@ļi¤g¶Ä¾NšŻżj/ÉĘøF®Ÿ8rü©•4¤q`Pœu°{™¶ĖļZ|>-·a5-ķSE“ÜėŠ2ņR³˜z{RŽ ŲBSį±eüķä,Ÿ’? _ā/³²ųݧa¶QJ!DžšøF­CåŁļ:”ś_¹v|©-üāUĻó-4••0¹Ø\Eb"õbMģŽ­%CœvšgVŪ¹åč ŗūÉ¼éØ ŽŪčdeqzFæŻ>-“©ڤ<¶¼‘ZWˆ­¤R™¦J(Z ;—@Ń·ŠTŅĖśõƒ9s“1ū+‘5}ćH"ŅZH“°…&Ź_¾jąŁ³|·ˆ˜aSŸ­ą@czł§0ń—Y¾µŸ¶VxyreźõŹ J懢eŚ.ąPCūŽGބٳįŒ3ą˜c“ų‘ŅRpÖÖR°¢nŖ»Pą8ōšÅ«?4qŃ8+3ų>„õĢY07“_­Ę-§‡= W¶Ä›äTżUnžø—œ&gÅ ū~„ŻĖµ_ŗīń.ŁŁ0kœ|2œvōīŻž9ŸØ*õ<é@»æ.œõšč¾¾PUøžƒZÖŻTč·ÖŃ}M¼xvW½W›ņB]l"Ÿž*ŸnAZJ½žƒƒ„…&=Ŗ[`ĻJmѧ¹ģB@`üxmĮŸr }4ƒT ’üsķ±&Āi';ķ@4Ż€ Њjeīł¢'O÷h¬· ×X›²ńŁf.Ėgxœ’-^īų_]jķüŖåĶ ¾x•¶ąµĮ_×L·npĀ pŅIŚ¢ļŽ=ō·VųņKķćJwŠn’ĀØ„™L^€¶<·ĘĪäŽF.ēæÉĮå“’ūõĒuxRģ8Š=SäĆKóß#šVÖčV¹šķš€½’‚|f&LŸ®©ö3g˜1tØ?ĄśõPY©ŻŪž3łÓƒö@d»DܓqÓ'u /4pdO’‹äņ VśåI\ōv Õ)R=hD”.ͧoN`ƒŖĀ Õ±%Yū$ÖģьvEĖ`÷ p„¾ßJ’¦ÖϚ„iÓĄz›½€,^ÜjŠŃ¹dŅāS¤R2P •óŌ°ģš.ōŹö¶™ŃßIJkŗpÉ;5üø?4CR¢ødœ•'OĖ!+HL€»æØgį/Iń§*P¾ö¬‚Ż+”duX;¼ĮG©ķņ3fhYf[sJg8“× £„Ä1(„ 2'½^ŗWŠ#@„ąĮĖ®éĀß4šŲŹäĖ̱ˆ L#`’^hĶ7:X^ģęÅ99ĢžyhÄŠW6W„Ų× ST+SaWpĖ*vöńfšr-"ŁfAy†y8ŠØšū/źYŖ›ĻY6k‹żĄ&-č¦|kČį“-œu<śØŹ ęņ>JŒ“Ś‹æµŠ€V4#„nģ0ˆ­ŠGļ°dc_£ĢéoUsś œ”Ķ  BH"ōɑč$8'R¼ <æĪĪ_¾i¤ĪWZÆŪŪµÅ]¹lŃ{}ǚ>J ¦Ł/»LSé[ćėļK(¤­½*P tIŌdbLD6HŽPąpY“ŻÉ»\Ü8ŃĘ]ĒfR`M®-LVį½ĶN\Ś EöÉØŚ„-öŠ­P± Ź·A厐Ó`}Ń£‡pÓ2Ž=ņņü?_õqźI'Ś1 Ó€Øµ<&±Š·¢ņä;Ļ­kāü‘ī<:“į]+ÓÜ2¼““ˆĒŽ_ĶŽm?CŁ/P¶ ŖŠ"ZčYY0z“¦®·Œ 4k}8“],‚½Üd! Q N%/@(ød•7všÖ/NlįұVNdĘfŒ½$Ū[RĀŚµkųxÉj–®^OéöŸQ*:|=ƒ†ÕųŲ±‡}’ž÷€ö \{ĮPŚČÕt6ĘĪ „qᢋv8Y“ĆI†Qą“!ĪfaZ_Żüt$‡ĆĮŠåĖX»f «×®å»ÕkØ©š] 2 ¤-š#“Ē‘#µó»)†,Ū )¶&øąė®D„ Hŗi¾°{Tnr°p“fu”gąØŽF&÷21$ß@’f ±•\p:”SRRLII ÅÅ{(ŽSĢ®Ż{(ŚSĢČigšÅO†=—¾}µLø–Ż|Ä><1ŖwŪŻ2@:Ś“¦”ĄŃfg—•NŽ\Z ÕÅP[ŒX[‚„¾µ¦oM)žŗöĮ3­9ął<ą’ŚĀ?žš‘ŸĶæ$2:ƒHg Ś$EE ØŅX5%PS|ųØ.Ö\k­ŹK+@8·ū®ƒ?›LŚŽŽŚ?fLņŌtftäЊŌÓ¼n­Łcu±–¾Śś±Ŗ(ģ@™pPź]Ģ›§e¾Ū³z¬Š€Nk’Ļ ({ ¶ōŠĪ]»÷Š^»WŪįcMw“®żZ.ĄÅŠ«—¶Ū§*At.)ęTØ/k^ą{[©éĶj{}YDžņ(4ĶżŃzĄWšė?^~Y ÆMUŅQxŪäyé@čDß x””\ŪÅėJ›v«…^[r=ųcA[Č-‹{@›Ÿ}÷!ńOšˆöćņåQšc‚HGąn“õŻ©@Bm å°āØŪÆ-öŚRķw!ōq‹č‰ļ>čAt1sPƬ­ÕjŠ ÅėǑΠBÆk¤Y PŁFXöl ¦¢ķāZ–÷āimśš¾ņ üćq|’(’Ž ­ ĘŪOņ‘°@ Ē®ąĻńG6ķxėEžŃĢ¢K›-„:…­LmĻĖé t:ŽoŖDqY@ÖŚ.šTéąØŠ. ;1ł‰BiF„6»5'\¶Č{½ŠÜg½ ߊ–¬¤™>éicÕ@ē&2PS’#µ#ƒįĆa‘N@:dźtń°ŸŚƒ²b(ļ„掮č$;į®ŪV p‘åČÖVĄ¦ˆ®üų©¬ČmZ&¤r-@Č9ōõ‡+ET/4ŌvJŹ“u¦²0Ó!üuŪĘhŲqł__®…ģnīšRƒ4³č蓦ć}Zjѧ»H³#€Nn2P+"К€~ŠŃIYįœ!Z@б²x©ą«“¶ŽNBˆąü‘hu­V}:k1N\ŌŃI$÷“īG—Ī@?č$; ńt @žT„ć^€śV@:{ōó¾N2N2PT½uDčč¤1'8[Uß-C/Ć¢£“‚“w¶<ŠÓxˆmļutt¢L8†æØ¹uttRžŽÕŌCaut’ƒp€>܀2€$ nƒ®čč¤%-k[’WóƼ“X7É ā0H‚®čč¤!®G£¤=Ń( -;ÆølF± Ć Ænt$ĆØ­ķ–µ8E  ß*ģG€l³V2ŽŽN #ü·em˜ÕżĶÆn8(śg‰{ņ-"’¾ČutŅ ƒyfĶŠ?G*nžu½TĢźgŪ&Š‚G Š&źZ€ŽNŖĀīßÕ&! ‚ē„^ęķĶÆ,7  5׈;Ļ$l­rŖ£ūe(krūx§\H|ŪuŋśöæŚ»¾ü2žS‰?žxųæ÷ļ‡ž31s‰„m*S×$d&±ē‡¾®o–Vž9ß*lʵHn“v7A<ŖŖ–=F—/-uī›)±^š(adĀ,ėąĢR?ÖF:PRwߝčYD'°0ѓH"Œ¢@ŸLMŒŹ3®lžu™ ŽÆĄ9ƒĶk ”Ń(Į‘gĄ¤[utR³$0‘ xa×ŗC’.ģ9Ż6Øą²CÉ/‡žŻw<˜ŅŃ åƒŽ£jäć ˜ FślI'[EIDAT+GŚ>kžõfAdhõRUUĄlĄņĢĻMĒ­(s] šM©›}v½,ŽŽNŖŃ;SbzOÓ{Ÿ»~dę2“¼ŻAšB«l@A<Ąz€ėĒŲ¾ķb~8Ŗ‡‘,SĮAśŠ‡>’fd™¦v×”Müįš‘™ĖŃXß²ų”M:° »}Øæ“ńœYŖĶ¢Ąń½ĢXõa”ĄjŠÖ¬I³$TŻ>.ė9I+n·O„=­ŸŪnU«ŖjNlĖö¹{æ“„éO^EĶ“{U¾Žė¢Į£ÉÓŃIV2ŚāĻ2 ”éŹa¶Žėm*š€’ ĀĮL@ĄGAę',¼Ózšö^8ŲņØAĞa8©™žz?id¤w¦Ä)}-Śā…Ę‹†XžŁ¼ų½Ą²¶‹|h-ØŖŚ˜ˆĖʼ½_ŻŅų{—¬lÆóņS„w8B:::1Į, Œ+028Gۜ͢Puå0ŪĆÓzšö¢Eü}+Ā~_Æ x°oĒ†ŻJÖ?5ÜPįTĘød•Ķ5^¶×ÉįE źččD£(04Wbx®ssĢN”Uüįö±YĻõĶ9“óū\üDØŖŚMŲd^ŚŌxģŖ2ĻÅ•l%2{d*œ2^%šõttt:ŽA„B‹Dæ,‰>™F±å÷Bćųćū·ŽĶXÜlškB[üU®’iæŁ0x4Š`oƒœńŸķM§l­“Oņ*jfĖó Ę„PļV±{U<ŠŠG::Ę(j;}†A Ū$g3ÜD”qX®“ųŹa–Ļz،MͿެņuęoKX¾=UU{“@µG6æ³Ć5icµē˜j—:Bт‰tttbˆ(ž|‹°iTžqåyƒĶkóRĖBw? ‚PźµĀvī7G †––ß×:eÓ×ū]CŠÕ~UN„G“GÉó*X¼Ŗjń{1€Įiqf„ź|e³ »ļaÜќŅŪ‚­?×öÖA>”ŠįčUU% 70莮c@G'ž(h=¹Š€½-±żį•š>UU @W Č²Š“¾źč脊·y8“5å@Eø»½/žL‰ł&×q±IEND®B`‚cyclograph-1.9.1/pixmaps/32x32/0000755000175000017500000000000013215023054017474 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/32x32/cyclograph.png0000644000175000017500000000331413152476643022356 0ustar federicofederico00000000000000‰PNG  IHDR szzōsBIT|dˆ pHYsÄÄ•+tEXtSoftwarewww.inkscape.org›ī<IIDATX…½–}lUõĒ?æsĪ}mļ½-mj¤”±«ˆ¼8u"T ‰›NĒ`TiLĮøY²ǺKŗN0c$a`Ōa`eČéŲ‚a“B±t”BŪA×·Ū—Ū{īķ}ė¹ēœż±r[zo[nb|’ß?Ļy~ßē{æĻĖļ Ó4­5cŪ[ŒÅjŠtń-X–[‹fJ'ļ»ĒöKåų梛׽”®knÓåo#ł ›] Ļ}‹Ē/ēmÜü×SŃB$H÷,(¶šČ6"ų‚FZw}ASŹĖ–t„„CĖ@I’T¾ąbó ™Äuxž ?ū«#ia4w虒1udHēXl‚å™ E†k2ÓĀ@5bź 2¤«€.™Ø!ƒ¼,)įėi㠃„4L cH°öm?=~€+ķ:/’AM ”„$iX^–Ä©KC”zÉńHōA·S°r©ƒ£51ŗśõIH»Æ=›É›kŻÄ†LŸ‰ņ—ÓQŽÖĘȟ"Qńde9p9 ³~—śĶ(˜*ó«ŅL„«E°|±å‹ķč:Ų,‚øaŠ«œhˆMŽ+ƒ‚Ä-•@°{CN›ą««CT~ä‰EvžŗßĪķ¹BĄ…V‡Cų2JL3'Ē•@I4Ä$¶i„‹%w[‰išüN? mqŖėc¼¼Ge~”…øõדD¼Õ.p9jŲ >^+(7˜„@I±7Ė\­Ž8%ól„ā&’éŃ1ó×µąĆEV*WŗČuIų n›"s¾UcĖž­Ž1Lnu ņsž?Ód¶•»ŁVOē·łčd€µ%N*–9©ųP„nX !็«Ģ”lēµ-#*Żņųōl„¢W{xm€³-† Š$˜5]į27(0ėv…—žČ`Łļś©k×wMöS±Geļϳ±9ÄM{@š(±Ķ.˜ž#awšzć¼sl%oõ1cƒ—ć—bč˜Ā֔8Ųy#ƒ”  @¶[b{©‡Ēwłų¢uč&©ž~%Ɗ}¼Wźaj¶D}wœKŻqεi“uŽ6F){Š1n93œ‚'ļ±sģrtğ 0|V,“sšė]ƒzŹēóroœĻź":‚:‡¢ä{d*–f$ݳŪ{ʳųä\˜ö«˜sź×‰o7ķŁÓjŪ“ e¼Š©±äNkRŒ ¬Śē濟d±ü^;UõQśB³ódV͵šī¶Ż4ü˜ķżW™gs±4ńŽŚ„Qƒ\÷Äuœź‘šĒĢ”1øI٧ĢĶ·šč,+wāćė][i:w’WśZxȈš¹-sŌū·«1vüŠĆµ!L39$`õ'ÆQĒļöp?5‡©żsԟdo°“Ü1!= æbŒõ½ ½[ąfSuc Y‚ķß÷ŠŲ«qŖ}hD”Aœ]'ø#RMē:ߝÓΚ—ŗłĒŗEü,Ų™’£ĻŌA6†3†Ŗ¼õ˜‹/_ĢåĄ„(׿:3³ež¹ŪĮ‰ę/ī®”@ŗČ‚ā²·ž[ «Vv³p”»ćlē>’µÓć®«ß‹ÅšĢĪ ö#2²@J^ fæaŸ’ĻĒC?Ö~1?ū9a𦓄&PZćÓJC1sśx$¾IĖ“Ź]÷ēYöoY”łŁ’b>x]§“•IEND®B`‚cyclograph-1.9.1/pixmaps/32x32/icon_add.png0000644000175000017500000000237313152476643021767 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ +(ćWŹģ{IDATXĆå—mLSWǽ„õR^:XGLjČ?čŅ8ŗe É–ųA¾ŠS¶·i&Ł–`Ä0\ 1f!‹ Ó°—-†Eć]ĀH¦{•LŠ1F­ ¶¶øNźķ ķ=ūPŽ.å„T³eŁ“ÜäęwrĻżŸēyĪsž’wÓĶ“““_IKK{I§ÓE&Y8VGGGóĢóēŒłNćń Hš Z,–***Źl6Ū’VÓŌŌ¤ŗ\®1—ĖU¢(JoĀ ĆŲęĶ›YŖ€¾¾>IQsWWW·Ūķ>īp8ŽÄBßH3žF£‘””JKK“ *V®\9ä-Ł3­ææÆ×«a”P£ŃØaĮ`””!<²,3>>®ĖŹŹ²ś|¾[Š¢ņł|u yąŌ©S^œ~š››c˜ŻngēĪŌÖ~ČīŻUģŪWKQQN§ÓX^^žžÕj=·TU5L¾ēęęRXXØĻÉɉaŁŁŁ1¬§§Y–iiiÉloo·ÅķH$"?īżn0q{`ٲeS>vā­g>ÓŒß¾įą’³'†u÷_аįĮvķŖ$”$LMM„å2¦Ÿ4cęū\^qIĆäį”f5>½ØgęMB½^’”b‰Ł­ʈ‚Ķ“NĆĒ–-e688He„vwœļ ž·żč7‚€ €—Ϭ'R –`VjBĢf3™™™³jDš†mz攎mŻčM BPmŖĮiąü­ŸéL9T ÷s]«łš·šņy<„8)ŽÖ®ē»:@”D{£½%5Ōoj`e-€0śŽaąÅE=0ó,čģģÄįphĘÆ\¹Ā „B!>Dg^fžŪ”ƒ©Ą³üĀķé’Ī×Mńāāb¶mŪŖÆ®ŽK]Ż~jķC—ų1މNžŖa5*" D¢9!e”Wo“ ŒÄu,dß\o3¬“žēģö¦xzz:ĶļįąŲ”é^s{&™hXP@ovZ–Ū‰“S¦Ō|0eJŻn`¦iās'08˜śjßéÜ/bŗŽå6†u‘©įX‚”]šfF؞"’)85”ķ—->ļ÷ĶĶõožĻ††’×īś(ū|żŠĶK€Qą­wĒHõ(%å2X–Ä4­—yÜ÷łÄæżż ³4{ļš5 ļŖ®6öŚłś9B­æšFß+[½Ą¶t ŖYāĄ¶=Yn\ŅBÓü‘H½o D"M¤6ńܑĶĄ]wts˜ČāõŲ¹oóę÷TJiŸ9J©čÓoōõĪXŁŁqhßit7LóÜ©$·[“}±ŠUž<l%q•dĘ­¾yI (%’ū½G Žōƒļ]»VÓ4ūœPJéŁtąÄ²•³Hs Ś‘²§’¬½©Š› ŲlyLmDŪFP£vŖ"×Fš°€īų9É®zäŃ-Ļ2Oh[“z×Õ«vy µ9&”oĄć@æ`šõ²¬ću¼’ŽUÓšŗLęµTćœ(JØil£g’;³c½£‘-ÆnŲśoS°iĖžŪO–Œē¤¬!Š *Ł‚!  „Ą­uU ēDׅŚkŠÜ:ƆOęWEX5-Œėƒ€ŒļD. cƒ§ų`?‡_ėå”ūÖ^±`Įģ=“(„’±'Æ1= 5  ”F(TłĒu}`,ÉsKWŻD£¾×¶O ¤€¬ ¹±ńžćdGäĘ>ŹäO÷ĢÖ4MMhą™ēŽ»G©ĘؔQ¤4n!4„Ø€zȀD»2†äŗ®:z8€Aå;ž÷’œ¹±,…t’|*I!• /sxÖ¦MŪož$ĀĮ“…Ÿ–­(B”O7oVžĪĪFōZ(;”¦Śß?ž÷¢ļ}NA:ž&›ˆ“"Į±Ėx®Ėō¹óŁŗõ½{&(„j„Öxža„āyś$ĻĒ Č„ĮTąĪ5µĄ7ę7”ĪĻūą ;ęPH%É$b¤cĆx®;”µ ¦häj„T$šč£ŪV;Ö4 „¦éhšęė¢bŗzG½Ö£(¾€ŹĄ üŠ÷ö§°-įŗ$‡ÉÄcŲ–yV­É%;vģ›ˆ%Š×ē³­D" ]ŽƒK  bÕMuTO¾“B0}E33ƒP ¼“a×-'Ҳ-`Tč(‰’J ¼bœ–6ƒėļüցźó˼õVϲ €b¾eé>ø6 \J(•5NķAÄj‘B㣧9¦Źœæą"¾tūt~łĆ ō2”Ņį€p”pz€ä޵­X&żÓŲÖĄŠPbFĄ*X.3į¹”WL…<ę|7Čø|y;xµo„ÄĖķH‡ŽĻ²Of&ŖŠ¦Į¤Z'A ĒO ;Büą"Š£G 4竃UӵúH)+‹ŠųōƫפּĮ/©Ņ·eķlJģ„4VĘvR‡ Ÿ}Å%A .H×ij+–RQ@#FU> ir”Ŗr‡e ¤”©įŗ:óW—ˆPKł p øĄ²«WŌžōH ĮÆJT€•,lšŹag)„#d1¤$įŗ2µµĶ}:€ęČ'[ęX˜¦ƒiJĢ’FŁńčŗø•²Æōņ×ĶæriIzdx¢ĄHრ¾<;‹p  ąčŪu„ė2€‡ŸĀ 7\µUųĶOn~bęŠ:LÓ¤Tņ(¶“‘gt“¢š2eȎ¹dā1²£qĘNŸ%'„ē•+Ž «ˆg„šģ ±2­Ä,"”ŃĮNģķE–8õ Ń©ŻhZ„6${’а3 ~-ūļ߆<špӇ&īM§óėt}J½ć„‘u ”ŌPf Q…a˜d³‡“ĒGÓSf¬~‰£W`„z[Äēö:!?õŌ+m;wœgŃˆć” +W^vč¶Ū¾<¦išÕ_WŠg„[š:×Y’ˆčOžņ+ĢIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/icon_delete.png0000644000175000017500000000216013152476643022473 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ + 67‹šIDATXĆå—mHdUĒ3Ό#ŽĪ"2j¶":Ÿ\DK©>$Į.„Ō‡E7J…ucA1ÖÜŲØ”nī0k[B¢a…Am„ķŚ*¦4-•ĆĢ:łĮµ&œ½Ī›sś0cėmļĢ`/Ї{łs.’ó<Ļyιš7Õ^0++ėќœœz•Jµ½ĆĀįpdccßą;æķśŽJV€f/˜ŸŸŗµµµ¹ŖŖ*„Õō÷÷G<ϦĒćyB’¤ł“hµŚĶ¦¦&R`·ŪՒ$gff¾^[[{Ļįp¼ˆżęØ2ž:Žģģl5ÕÕÕ­eee«@iŹŲmN§Æ×+cĮ`N'c@€••Ö××Ńėõ„B!•Éd*šł|?J’ōŗĻē;Ÿ–ĘĘĘb^¼×ā˜Ķf£½½ŽŽ—éźźäģŁ^jkkq»Żŗ––– F“ö@$Ńī¼—””PSS#ė/..ŽcEEEqlvv½^ÕjĶ›œœ¬JŚŪŪŪśƒŽļZ­ÖŸ“233’ š§CCĢY­²žo:oŻ’³„%:oܐ±„ÕUNžģH]­Łlžp¹\B!dķLaa;}čP9|XģXiié7)%aFFĘ?RŠÕü˦Xż~^«{Xƾæ³Ē–œ.^Ŗ«“1ĒĘϦ£Źl6OøŻn!„###Bˆˆ¬uwwDZžžž868ųVś9šŸ ĮĢĢMÖÖ<ņ°,.b±Xdlnn‹Å‚$IøķvT›>œ^/mmķh4šōTTįĉf[^^¦£C¾æķóóTõ½ŹÓ!łUĄ£Õb++#ėŅ`4ÉĖĖ“1ƒ!GĘŽæt‰Ń?Śs¾ xÅåāq8rŽząz2„X“l ?ē™3=Šć†ą3Ą¬čŻgĮōō4‡CÖæ°°ĄÅ‹}cęv4½UV«•Uń/›œœ0‹Å‚Ė—/add}}½øysMMMšz½ŠĪĪĪk•••3¢H„Rņ½¾V«…^ÆēĶWWW XUU•€---¢(˜Ķęņ¹¹¹FŃ $“Iź°æw¹\¾-ZāāāżĻĻĻĆįpšęWVVŠŪŪ—“¹\.ōō|™æ· Żn7!„“ÉDŅŪąą € ›žžŽļ×ÖÖžšWŹd²¤Kń/·œu`yyćć_óŲśśŗ€¹\. šMÓččč(,¼^/'Ž)žõ÷÷ ŲŠŠ€™L÷ ρ’LģöE~AXĘĘĘxĢįX‚Ńh„Ķö4]…H¤2Łs¢Ā•+ķ…‡Ąd2‘P(ȳ7® ˜N×I±?9żȑ#Ē6 Z­Fyy9Ļ”Ź2ŽųĢ™'p:īÆžOmmn–œB8¼ö>šŁ#±„øH¬Z==ßaqń īN‚ÖV 55ļįģŁōGŻ;|П3ø{ĶfMÓ¼y§Ó‰ŃQ#b±ĢęŪ‚wzš Ž <˜™į³8ėÄł;ĄWßHf}«śśśŸÄ|†Ć·iqēZ2m!€“ß ¢7g(•ŹœņĻĪfęķķæįī]NŸv¦)šĄkÕēe/ŲÜĢüwOÆWāźÕ:=ŗįöįoŲ¾R+ŗÄb ,–ūi,‹å>TŖˆDZb·DüĮŽ™W¢čunaĮˆ©©tJŅ2?` €‡3’ržPJńččy?³Ņ¾feŽs< ` Ą^5]šjü@‰åv»”V«s:qā„ JŽärų|oa³Å zøXĄ˜ę̬,ŁfĘØR©>’J„I1JŠō±Ļ éQ‰,W%LmOrž ;c’hžŠf˜vxbžµžĶ›kń‡@Ū,0¹ Ģ`<œšP“mÅ;HY`Ęč="IEND®B`‚cyclograph-1.9.1/pixmaps/32x32/icon_map.png0000644000175000017500000000331213152476643022006 0ustar federicofederico00000000000000‰PNG  IHDR szzōbKGD’’’ ½§“ pHYs × ×B(›xtIMEŻ )@v5WIDATXƵ–ih\׆Ÿ{ī™;‹fŃH#©®lɱk±l'8vœbÅ ME¹P!;Љć&Š6.ŗ„ø…B ‚†¦n]SH]ˆV6jėā-±ŻX²vśK3£‰fīÜ{NĢh4£5qėÜūœ÷ū8ówĪ=FgggąfśÖ±¤'Ł¢¼: 1…-\—¢aja˜Śųæq•Q3“’}3a>%{¦Æ½ńƦ?9‘A#H„QĮ°)ĪćšŖ öśuŪĻmż¹q†· xG$.h KÖ}oxæ9$k¼ÕMŅvœąfŃ €ßšaŹtYIā½āŹuĆrĀžČōŁ#…ÅĢ †œÅ­»|£]—ZēZ¢]@Ķ?ĒŻņšq—‡®dJųž® D6ÆU Ń •Īķ Ø“ūāģžØDéŲ8ŽĒŗŃµĆ„{š×Ļå/ä§R::ͳÆTѵĶĒ æ楱 Ī|æ†K¬Ü“Ņ ę““×63żėam9…oכˆš$ÖX#Cæ|·ąŻń'äšAN.Į $æ0 čü\ˆź)—£ß§»ÕĖĪ›ßmåŅś8Ø\Ŗ£ąÖĢč&nŪGh’K˜5óæÖ»ĆŚę®žc+©7_@Ż©e2 ^?„ńĢĮŅ䢼öT„Ē/¦i{7MēŽg[ŹŃŁx^2$‚žV«­~ū*»÷’žŁš2 „P0`ų‰˜a‚»{`wĻ"~Ói)YĄÆČś8|ä"ė&į‡uĻšõ?ž†@,ĶėŸĻé}Āōʤ“Ģö¤†Éö>‚®¾ŹXE7ƒ©Ņ½‰ 1YÉ żńų¾7ŽeSOŠöOļタ™Ū”“¼öśYŽŖˆqb›Mv7!8C[4œCēßKMÅ]ń_=\Åϲ¹<µ‹²/¾ÄQo9éh”īū…©”ÖŚÕčtĆA»ķ.įźĮŻT˜Ūē;øu¦ kēqŹy„‹VAN=PA*“W£]FŽÅFų6Īd-Ø’,võœÓWį*^C¦ė9Rƒ;Øzč4ĮżĻa' †[ŖŽ˜k”l>Aźl;ĪlZ±84+r•Œ’śÅO1ėĻQóāW©~ü8&W¬ ƒ"䣟o?LķłC?9BpĆe*[Īm| _ų)—aBʂ³½ˆO°™ńśīß9‚_4¬ŖĻ½KK&œDöj"ēāPć0ė›žJߕZ&žŁŽ~ū |OæHŲUV%³ Ünēyå5>¼ŽAü|”hćÅÕõ@S¶Ž¹ƒ(o&„1D³į¼{恹ńŁ3_A«y³•D172ų¾|ūÄAģįĶ«ė]Ö rĖģ‘°Ń®üHŠ Œcķż&Óoböę}«ė]‡.5¼ēRņĮ£KĪ/":@yĒaFæŒ;“ce= Š,j“š‰yūU¬/¼ žųŅķ[®­®ĘŒõR’ōaģßFMW/«Gƒ {Ār[Ø5w†›~Ź=a&ζSöčIŖ×E€H‡DØōĢ_‰GĆ8žÄīżkŪž°¤>č‘^Ļ&²W¦Ė UŽJĘz›°~…ėSN ļ_šXz’ØÉĻ2>uyI}kś>[ęn/ł ‰Š(G‚pŠ: łĻ“y£.<ŠWįźF+”÷—Ō/Ök=ēU|:9ązŠŽ¹“‹?bd㕨÷æ„hų˲#·Ü¢P`¬9‡źūĢ"^ņ¾wn63zü;ˆ]?kby½©µžæŖÜ³ŲžcÜ?w‚™ĀØė*šEwæ9īHt¼}ūAōŠ|hHjŪš÷ļ%Ÿź…u“ÖmžœŁč¬(3żD¬7fo‘™®aägÉLWSQßKĄÆ˜qRh׃3ÄIŃé0n:„|•×)ūD?å.PY=UØS<Šė„®Y£r:Č¾76s©'Hµ?F_bø „lˆųų§pŻuŒ©Iš%1ŖĘĮš"rłdÄ¢¦ nøž(®Sōļš”ņģ†Œ”Jċ[²ØÕfY×EŲc,ŸX˜µ‚@lńÖče¶l7’¢šŠÓ±t¹s7fū_xĢŽ:åvč“\k®ł–Ž£īˆd›)D™GxĢØćĖÆ\šRx,K…mÆcŗ†,p!…%-#ģz3ÅzÆöXį1#®•ŃĀ(ŲŠ2„°Lƈf|ɊLø;dūż«OĶ…ĪéĘIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/icon_modify.png0000644000175000017500000000272213152476643022524 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ /%łŠsURIDATXĆå—LSWĒæ}ż‰PŖ„iéĀ( jĄ 8ėŲ¢Q³9‡ŪĘį]4b”Eňć2!2DzM2uN”‰‚’-8ėŠ ¢Vp*†“F¤Ć¤-“By”–öī ä …RĶöĒNr“×Ͻ}ļ¼ļ9ē¾s’»±Fƒ~~~IB”PÅb±\ƒl``ĄŻŁŁi÷p˰ūUpzėg4(‘H²222Ņbcc'ō6………n£ŃŲc4Ń4}Ógø\nOjj*&ź@cc#EÓ“H§Ó]7™LĒõzżĒČX’”^d‚ÕÕÕA  øøX\YYėµ.—Kš¢ėĖåŚ½V€ĻēX{ź{ÜŠ3ęšõŲ²Ŗ–ÉšZ°åįUk¹’™™įSęEó9­š1—C£`a ƒ}Ņ5_=Ć4”ć*ć1 Łlöæ²SųmÜ} ž¾ū¬Æ0ŲGmŲWĖd-ĘVlƞĖ`zC'>ōÅ«ČČČ ƒĮ@!¤““”āfŒģģģ,''g;xš[2hįįį·|RĄ[£i{s”»xN{7(`¶ŅŠ7^ĆGė¶śīL,õõČĖĖśŻt»-·Ļ#/ŁģtĪ°Ģśża)äž Bw+ōLȁ3fbłņ4kkkĆʍ×wї{ī¾ Ķī~ō؁āšIhč¢×A!jŠ«ēŚQ²Ü„K³ Ģ8 ]›Ižx]"‘b±˜1„‹ÅØæżŗJQš®§o±ńžÓ {ėv«ėpądž„nĄö‹IČ?7ócĄś9 Ó£d(÷f+ö*7öķĪB~²žą`’%“—š¶””” Btt4ŹĻ\ n3ņ+)Wl(ؕÆbv ?޳%S(U555‹ 233<4wBsÄnÅį!qjŽ­±ƒ½ˆœˆŽ.V®^ ¹\hnn†F£LŸŹ·]ŽįąP€*—[:0ßć›)•ŹjOexį¼–,‰ŸDƒ#OĒé,6I{gpŽZ†2©¤õ»U ÉY “Ž›ƒß‚gmį¢7 y YåAC¬¶•§g%8^¦õø…Sln’Ē° ž ś>} "cāŖZ‡‚*š²[…y SFtIĆ;(—Ėő‹ § ŽĮ^qÜdė룔Vz†õA‡ņ ‘Ų©¹‰mŪ’įt:”V«™½ŗÅ:“^ȱ½—į“>€vąĮ˜U*•ÕV«•Œe)Ė–åK$õu)Y+"ßę{\{N{†|0‡r’# ūÓ` "å¹hoo'Kę†ēa÷ ›Ž"Ū6Æ%F£qhM__łś‹|²L%%½% śĻAbäø2īÉ(,,LWQQ”‰Dc:Z}ž,~9¶š5ąsm%×%čš‹ĶŪeCŚ, V©ģxdļǟw;< ĮØH„ŅĻēPå/Gt·TŹ7ÅŲąā‡KF_sź‹dŸdYŚĢnżø ų`1į†°*H_–€²õ7ī£÷ų5Xŗlųé;ōzu8}‹˜ā‡„S„˜ŁļDgG7j].TštØÅ_ŻבZZųÆIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/icon_plot.png0000644000175000017500000000154613152476643022216 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŚ 2(-!nęIDATXĆŖKHTQĒ÷ŽćcĘĘ©”ČÄ ƒsŠ@’ *Zō ¢MÖ"Šj%mz- BZ•$“(h× eō‚p=ØD,#ƒˆBSLĶÉLjĪ83zēŽ¹-fFšL½6ćõgsļįū¾ó’ž’ļÉ0 éõ{õŅ^}ļ°ßp`ęJ•łJżöŅĢJńäeøāTU ¢Ė« ,ĊeĀ]sĮįo[Ō=]šĄŅōŠ5 ‰·ŸŌŻ¢µGĻBa^ŠŚ­g ĶźÓ'࣠d˜/AF„µĖį» Ę ( &~X%ōõm•ģyOõ‚U H“@²Ŗõ"G}ÆØļ£ Ø–i®ŠĆäµ_fĒ@5£m,N¢#–WLh ]0tr‚ĻYm«£ŌżŸŸū© ż£Ä5.hA–÷ždsÉOŹt°c»Š$AŁć)¦»sAzˆFXūī(y_øw{Ō¤"cKNŁVŠŽ»¹œ»CĶŲ³åYX"”]°¦ń4uŽäŃY””]PŲtOO=EQb'Aš# >^įVēC6źć’=†’“ƒ„gøöż;#”wK²<²ł¤p|§Čō·pH š›EŃ)z —‘Iµ»k9į:@ćżœ.ó=3 #E Œ(ī=KÕÉ7ģßLßĜQa“]§D{Ī‹GŻ8fzDŠs ؿ Š:`Ū/¶¶8Ģ%ŸmQI ¾„Č X× •}ßŌ“¤!Ł“ (@čÜ|Ī9H42ÄwÄÜaŖŲ6;‘āż°9vz¤ ŠĢ ÄHS „0(Œ1įŁf3†¦eĄä ¢Y“eȓ7«ńQlĻ–|¢x‘š¼Éß²/t¼]Ał+@ @]FOå&}ĖńRX’üԊjŌPˆĀ²d+Es‡(,q% ŽįČĖ-‘=Ą¹Ęįƒ_‡£‡GU}™X©xŻ® ĻÕMŽæD\å10Š`ąIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/new.png0000644000175000017500000000156513152476643021022 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ ).8 [õIDATXĆķ—MOAĒ’Żm‚©»t„AčvŪRĆ©ÄD.xOĄ”\½HųŠ -߀౉œN įh‚h@R±Kh „˜Ęģ¶Š ŪŽtŪQ”ÆŒ>É\~3;óŸēefų×Mw·s÷¶½½=Vi‚¹ąģģģv>Ÿ_’eŁÓذ‡ććć RfĒĒĒD–åKŪņņ2ééé):ĪόÕ,¤æ€³ ưå€ćøŠ“9ø\.(ŠwWVV¾&‰§±Xlī²o˜ •éõśŗbŖÓĮl6cddäVww·_’¤÷Zjõxž/@ @4”śĀį]X­"ZZJóĘćq$“ID"ēc !0 ¬Ńhį[2™š©Ś<ņx< !„Ģͽ$„©ęõz‰¢Č›ŸM¶··)–L*dqq‘Äb1b6›\Õ!h¦uvvĀåre°×" ž*Š,‰`rņ9ÅÖ×׏Ēa2™4 …`0`³Ł4–Ėe!I¶ĘŲķvøŻnŠł|ÓuC’$-,, «Ė‰žž>„R),-½A]excBĻŸB–eŠ„ÓiȲžē©Ż*ŠBM„RPUµ1‡‡‡˜™™”X €ŖŖhm- ?‚ć88ve³98Īŗhõj³IæåĆLclģAؘ««ļ~$dŽ­%X†aō͌µŖŖŗšBšó.ŲßßĒÄÄÕ·µõ”B”A@GĒœä5 …B­wQ«*ĆŁŁ~Bå!@nBę®[ĄiĶe~ĮŌ”—bkkkČd2Ō#egg°ZŲČ”··Æ±sąOečóÕ^†ļQ¬Ŗ*E”X&“†¢( ¤”ŲGGGˆDöĄó\sā””ĒŲÜÜ¢Ų½{÷ļ‚aJlk3£X,boÆō|#„`pp°1‹‹åŹBĄTŗ ®+XæßÆgY6Ū¬…‚Į ©–_3+€W°įWųoæŲwƊ·’ÜIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/open.png0000644000175000017500000000313513152476643021165 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ ) še|)ŻIDATXĆķ—_h[×Ē?Wŗ²¤ųOÕI¶k叫üiė–¦Äɓ­°•A”Šڦ{HÉ ĶŪhXņö0Ę`ŻĖź±Rˆa¶5&$ƒn/ۚ…­$4)¦uBāŌžc+’lY¾’®®ī=÷œ=)ścJ›n/=šć]]ļē÷ēüī|õ1üŸĘžX,ęlß¾żöęĶ› žOÕS©Ōł™™%„PēĪćććK©Tź¦i~|Óś}{öģ¹£:ĘĀĀ‚:qāDqēĪ·“Éä/€”oD=‘H¼599YS÷Bužüy?N/¦R©‹įpų¹•T*u£Z­*„”šššRŽć܏E-..Ŗ“'OwģŲ1—L& t®·¾Šw§ˆÅéė#£~mqą‡Gž{’½ßE®\¹L.—#›Ķāy‚±±1FFF6„–R211!O:e///ĒæńŁńlmdĖī½ōÄĮŒ“ ^’€Ÿ €Ė—ÆpģŲ›(„øxń"“““ pōčQŗ»»ļż.Édétŗūڵkš}ic€-žbš±½Ä‡”§Bįf\›Į°ĻČČår™ŽŽžf ƒńńq¦¦¦xżõÜ=ū•ŠĶčč>öīŻ‡”’L&ĆĮƒb±ų|>Ÿ?`YÖĒė"ŃMDū ś63|O?zūļüųĶpęĢffføyó&Ē§ÆÆß÷‰ĒÄćq>\ŅeŽ9¶mۈÅb$ :dęr¹æNOOšĶu †“‚Ś M_ų'?xõg!xē_±²²ĀéÓ§łā‹9,«ČŪo’“m©ŃŃQ¶n݊mŪd2,Ė"‰N§ūlŪ>;;;ū‚^}xwŠPō5ŗ6=J’®£ō$ ¼ u>+Ē3Į%^~įyņł<•J™G‡‡čOÄ ƒLLü–䊾ļóģ³ß£·§—«Ÿ\%šÄć»ČeļņńæžM6³D²’aęo]ÓÓÓcC#ĆģJϱo?Äś!Ö^+“IÖϤҦHŁžœßjTץ^įźyaĒkיŸłŌ1 …^䙧aĖ"÷NQ# @Ė=QÖ„§EKY°6éyWĢk·fH$%“h½}ŗćĄli­5d# ².ģAjåzÄ ¤¾ †ś)T²1¹qé;æ Oi‚ 2 ŲŃ®Œ˜@+HK[¹—‚ø68% QŹCy<Ŗ8EŖ„bu~~ž×ŗŹ–ēĄõĄóĮT0Śū¤źčōŚÆ‡^BB­µ8–†p,mnU_Ėˬę–V«Ą^^Āv’8>U³E-rÆżŽœūB Ś«M«®éTVu\Vž®ż~³łņeū*J/h¢Ó`tŌ‚źöĻÆ^µ)\^JA”—ėk(Pģ"ŁlöŻ&€š>¢R~§]R?ģ5y €h‰€pĮwõÕµux=ģ¢.ēėį/ŃVŃR‚[Xj„£æēεߐHAW/ÕL¼’Śüś–’>ˆšž G »¶¾ēUė+:äJ¶‹+R`ųŽÕö2šžó*=qMģŅ­XłšÖ÷“ēč{ŽöŌ÷“°ēŌ£Q’ģXZØs(„į}…ń·õoĆĀ‚ĪW(¢ßžÓn®­=t«ŚŪFAyĪ—<Ź(픨4>ZąU?Ē.>I(¢+ÆVŅŪ©!ź”ōüK vxßhŁ•KX ÜKŲ…'1ĆśsĄµu3ł*Āė@ō P°6ˆ€’!Ėso \M[]ÓįzPCI]µJŪķ&@F\ ž¹>‚)Ł<Š_óĻRĶķ'wic>sY C|X‹»¾¾xkžEMגWļ”ó·©¶ģ³š" |-nŻ_LÜ@ČżäoĶ2ōŌC POEĖA)}VT-ĶJŹfߐ¢åZ·ZEwFėīJŁ |;ZĘ=ā&Š-,IEND®B`‚cyclograph-1.9.1/pixmaps/32x32/save.png0000644000175000017500000000104713152476643021162 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŚ49ą^A>§IDATXĆķ—?KĆPÅ6­AqØ*ģ *ŌŃ”ƒÖv²B ź¬ušāfõ(®‚K'…B»UÅE°Ę -ü—Ō8Øm,¢ĘWtHrņrß9y÷¾›œŽ¶ś©{Üaė`$2هzDö÷Š™L®ōF ؃¾<øė·JŻą·{½^"‘©/6·¶H§Ó5#9?_懇G]ąŖGø‚`ĄXm0š;”ĖeĪNOk\Q”DI*Ævž»ZžŻ€pŖŖJ4­ńį‘‘ßö9RĀ:č÷«£BON2\^^5\­nĄćÜ·+ĻŸ“ĻŸ·ŠŠ”» 'š0 ••µõž9ätšļG<ž0›J„bB‡iūUė‡}ąYÓ0ļļķ?ŗ$!ł|āč6›ć±T²Æļń  ŠčN;ü]p³½q}m’[Ļķa`1Ł„ÄbOAqg½peęv%¹ n ovĘń)ŲA/ģļYfĄņ ‚ŁæZź°÷Ī4M󭯧zEīīŚą9 č Z-šƒö»ńKIEND®B`‚cyclograph-1.9.1/pixmaps/32x32/save_as.png0000644000175000017500000000221713152476643021645 0ustar federicofederico00000000000000‰PNG  IHDR szzōsRGB®ĪébKGD’’’ ½§“ pHYs × ×B(›xtIMEŪ į¬IDATXĆķ—mL[UĒ·„/L€ņ–0CŒĪįR·e€Œ‰ Ū@\X\†Ū:“ Öśi²,Ę2nf²"ƒĶDL #q2V Ša0†M^J«¼IZޤ]Ł„"żā?¹É}ī=Ļż’ó<’sī9šļBJH ›’ Z-Ÿ˜LLfeńŖ[у×~Eą—ź™–¶',55e“/¤õõ ÖĪĪī±­[į óŃŃČbcłypt ĻļĮPyČvz&’–¶wE‚O«ŖhkksĒĻnŪ†^§sĒ--_DE‰-bcA£‘1@5°ĒĻײŽÓß×ēŽ###—6]š¢øxŽŻ»™™f³£€C¶‘=Ÿ˜°²‡ ōz‰¦&·,+87LĄģģ ##g((˜ęźU‰ @±h;ÅM`ŅUŠuĒĀĀg8zō{L&9Z-8.r M¹Ęnˆ€®®óœ:u‹‰ ‰Ųķ.rå#”>›0..Žōōtw5ANN+J„ 9y36ŪØ•ü‘üōööSVö±W8æ Õv6F揣ģI®šö•üÅZÆą`µØ«“„ˆŒ I‹—üoĘ«ŒėźµZpś“ƒłł2é&_4ŻņšY€$Į±csčt‚ņr £Q,N7Ł¢éŲX‡ٹxŃI{;čtątz:^ņ𿬠v’’’¼bņōt#ÅÅŻX,99ŲķÓØÕj23_!.ī)ŽyĪ;æ1KMxüų›b%46~(l6„˜Eh4‘ …B”””,799)ą±eMčuX°X33Kžµ™æä™ēŠ ™#ć„'0›‡‘$‰“¹¹¼“•…óŽ=Ė‘ĒĬ½.ŲītcsĒ?Zzy|G9±OžAnV-7¬!Čܵ }Ś‹ µßžĖ’ ‘ŃѾ ؜¾Ļ}?<<€²ß@āŽ)Īž „łĘ,‡“¤¤$¾ø~’lāUĻ‚»wāƒĀ—Q)ø|9£q©©ß‰§¶¶vM䫪Ąhu5Ž‘ŽoŖ§šÄ[t4÷PņŁWX­£DQ”˜ˆØ©ačįõĮOAÄ{śuhA^Bž¶Hʎøö™Ńi;e••ģĖöm“»Ŗ|k2±=<̇o Ę`0ķūŪk¬5WhnøFž‘רk½ÅšČ(…ćć  Ė'*UDźßõ]@ȑ×Qšæcš×ߨ¹ŻAĪ'8łQłŗm^¼ hm½IWĻ(CĀ(­ØB©T²žš* ēóZ ¶?Ļ™¶ŅŅÕ’%U*"<ĪkšvEå†×<8€:Wd±Xb.]2†śJp’ž¬ w€ł‡øž£'°«p)IEND®B`‚cyclograph-1.9.1/pixmaps/48x48/0000755000175000017500000000000013215023054017512 5ustar federicofederico00000000000000cyclograph-1.9.1/pixmaps/48x48/cyclograph.png0000644000175000017500000000534313152476643022400 0ustar federicofederico00000000000000‰PNG  IHDR00Wł‡sBIT|dˆ pHYs%%IR$štEXtSoftwarewww.inkscape.org›ī< `IDAThĶ™ipTUĒ÷½×{’ĪĀ@ ‚ Č¢ "‚ øė`‰ą ŠĮ µF‘QF-Eu\°€P .ˆ²FŁeI „t:Iwŗ_æm>44!!é“„§ź~čwļ=’sīYļm`Y–³Ģg>®F˜ Ŗ–K3,‹ß Łd!rŲYŌ*Sš+„ ˲œÅśGóŽ yoEČ„F~“²ĒČéL¾Å]ūą$ϦN(7ŠŸŽ3žx¹jŽ’¼ū×®94ńzWķ33½)ūk7ü{]ŲüĖ„yÕĮä[uŁŚ°ūޱCEš+bX ‘“!)0s¼‡‚5­)ߊߦ6¼2Ē‹7MJFİ8\¬;•ć>SEJīé<<)…grScæ=.Aīmn:w¹ž~¦™œcFDņĶ2$k8\‚9“RĪ 8b ƒA}ģIĆņ͈‚€dZĄåøœ¢Ńł Ɣ<< $3€żµ&»iō¹ŲÖ`.¤Zl;IžRĢISēUįœ™yt˜_MY•™<¬_Ā» 5zÜ]Ęä1nŗuPų¹Ü`Ł×aöhÉŒ ©1 KŠ»“¢2ƒņ*“ē—ž"E†ė9¹yˆ‹÷>­åėjócH‚N»`’;­Či#c˜°~—ŹŖ-a>ĪS\nÄÖµōJL¾ÖĶԱڷŒéi§ć„ŅęƒĘ,³¾~Ÿ—ŽŁQFŠ Wõ²sU/;/ꦱć°ĘŚļT.ͱqMŽzńāšŻBŠ$¹Š5żÜ9܅aFŻØn”ś_lc`W²$0L0ĢӒ~’£ĘӋk°DT ę+pžAœī‘xcV:BD+8nšäā®éē`Ģ')Q©N®Ńae~˜k‚|s 8’ÄÆM÷Ņ.+ŹĄ4įŽł~6ī‹°ts›,øŖ‡/w2¦æ·CbéęZž_ Ōoʄ8g:_ üq˜›qC\X'÷ŲX§PiX¬Ū«²nÆŹ¬wźm®ƒ)KŠŹ+#KPQcJōNr>čy”wf¦#K_Ą¤°Ü eŗDyubŻŚØ>¦ņ0¬§ēÉĄÖ ųö`„wæŖeŁ–ŗŃqYčoćSO „T—`ĮT/ÆOń²õĘźļĀäķóC‰Ž`ošKšīŒ F÷qšĮęć^ņqųxTŅĪmdnądĮŌtrGy˜0æ’#åh!@L»rõ‚µµc›«Ąķƒ\,łSŅÉą5ėY]!`Ć~•«’Zūī² >{,‹V^‰Ū^ņ±ļē† tÉVXv_§ą÷©ąheC%¦tēs/“lkˆn³ĖxāĆj¶żØ‘Ć%I ČQߎĆANk9¶ļ©?¤Ņ©µĢØæW°ļØŽ(’ƒ„:£ŸÆ@‘ īõ6Ś I± >‡qØ\gīźƒŸ=AĪ„ĢXXEŽNŻ8­iAū2Čå•˜1ŅĆÓ+k(Ŗ4āņ/ ˜<ŗ“šŃ½ člkø¦¹ŻhšGŠ„­Ā-dl6qĘÜń“w6ÕrĖ|ßō£Vltm§€£{;°,Xŗ-”0ęŖļĆų‚7_ę:·nŌmäó0žr=Ū+±ļAÕā«^]`ćĮČ{öÕΈ‰.Ł ČŠ9[įp™N@³N{K4ŗ·SFūv°ńanvE°ąė Ó©ū R};ؘ8ČÅŚŁY¼·„–ū—VÖ¢RTD °Ÿģšµ€"ƒnZĶNŪB€M9‹œ”˜?Õ£žķl|ń`kP™²ČOP=}¤eµ&?śt–ļ q]/' '„Ó"UfÜ[>L+zjå:—“‰Z¬k›čéś :¶P°Łš‘X±’\’­°ņūpC9åS:Õó-EÅ÷¤“_aāĀJ‚ZćĻ.«÷†¹żķJĘöt0mØ'ö}G±Ć)¬0@‚O÷«¤:c{9ށŻ“L‘ČŪn8O#.t[³®[ąĆ į|}ZwPeÉösF„šÆü ŗ ³–W±ī J(b±zÆ «1X“-Äs7„±žpØéŠā¼pk;Š5¾8偩ƒt2K£7ōvņÕ!•āźų©īŌx{-m¼WtŠ>›Ōh‹æ ńŸ=aTˊ­{|u5X•›Iėt©Q~-Ņ$VNˤ­Węžü˜žƒ“X›K•7ÅO£ZŹģ>Ŗ5«°ķ<u™īm•&ם™\ūf.;|’hK•B·lE‰ŗīÅ­™Ā®9-¹(KbȬ%œxe$CWŽććCŸ0£^=KĖ’ˆŗN3z$I‰öż’,āī;Re0ōõ ¦\įfꕞ}Å;u!Ś_xœ)Ӟf÷šøC 0; Õɽ Mć4¢ĪTĻĒ }:—“Vš•īŗeGYVź ķ ›óóƒĢĻrQ–LĒL…pÉŽ­˜‡Tų_ę„}¬2tźæ.ł,ć žgµ@Ž’T^ŗ.i'‚‰µĘćū¹ØQ-6üŌ̇«@)%[·£ny™ArŽUŠ£‰å'L=¾–ķ ńŲ°ęMćīåžø2 logņ7Ļ­Æ!ÆPé*oÅ{ģRüŪéš` 3ƒĒʐ |–^Ļg¹„L‹{WłÉ›˜E•jńšēՍžĮģ,»=ƒm%^ü&ŲšōuJv^śž[ČtV2fDq׊·o“ŹžłīŒ„„ز  r >ˉm*Žpė2ļߜΰ‹ģ¼śm/~T9Zcą± śeŪ™ŲŪÅ=]|Y r× ?ŖiF©.äāĢR}ߢķ^Ćš+k¹qJ5‚ĆŃėHQM‚āCŠ2ĄTĮęn܅NŃŚB•¾o–óČąž½:•^ēĶ…ĀaVlÜňw7Sx`ézdG4 –»ī,£Gw[&d°bCe\”~>^›ø¦FŃÄķ…”Qz¬„‡¶1;xTķ¦’ZČOŸnu*呑Uōžm‘“Óp{M•Ži‚'+UV† ®Š, ŠcÅģ“ō0¬Ÿž#®)­N:\V‹ÕY„:褥Uoµć…éMx(hR] ééM éųTąŒĀKŻ ®)†¹yŠȈƤļÅŠ՚ųżń0M‹fżėtŹkb½T•B ń…(€²ā³_Ęė’aZD"q—5ŸźXA:mr؟ ƒć 6õ`ó S]   ŠĀąOšu5I’2žÕ=} CÉōČGe ųĪ‚8ž ĄVØń›|łeÓĖ"a“ü|(*jziX¬2€.×±KW†[*ŸiCĪÆĢŪ³kw*×$ĄŠT<”82²šV “#ćB•&י%?!eę$m‰Čš©ōl›^żāļRĘ˲ä×öÖ<šÖžš“?Tj©śÆé "Į„¶ź©ŻŻĻĢčįyYX–%}^¼¹T»Ó¶Śj¦Ńō‘żJd“d=ĆNÉŠ6¶Å#;ø¾B˜’Ŗoč/äIEND®B`‚cyclograph-1.9.1/pixmaps/cyclograph.svg0000644000175000017500000014733113152476643021620 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_add.svg0000644000175000017500000005460312065361217021214 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_ckml.svg0000644000175000017500000021275112065361217021412 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_delete.svg0000644000175000017500000006117712065361217021732 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_info.svg0000644000175000017500000006567212065361217021427 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_map.svg0000644000175000017500000011261312430765005021234 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_modify.svg0000644000175000017500000007035512065361217021755 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/icon_plot.svg0000644000175000017500000002145112065361217021435 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/new.svg0000644000175000017500000002402112065361217020234 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/open.svg0000644000175000017500000006572612065361217020425 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/save.svg0000644000175000017500000001745312065361217020414 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/pixmaps/save_as.svg0000644000175000017500000002472212065361217021074 0ustar federicofederico00000000000000 image/svg+xml cyclograph-1.9.1/po/0000755000175000017500000000000013215023054015650 5ustar federicofederico00000000000000cyclograph-1.9.1/po/en/0000755000175000017500000000000013215023054016252 5ustar federicofederico00000000000000cyclograph-1.9.1/po/en/LC_MESSAGES/0000755000175000017500000000000013215023054020037 5ustar federicofederico00000000000000cyclograph-1.9.1/po/en/LC_MESSAGES/cyclograph.po0000644000175000017500000003211713144673266022557 0ustar federicofederico00000000000000# Cyclograph. # Copyright (C) 2010 Pierluigi Villani # This file is distributed under the same license as the Cyclograph package. # Pierluigi Villani , 2010, 2011, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: Cyclograph\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-02-16 23:46+0100\n" "PO-Revision-Date: 2015-02-16 23:47+0100\n" "Last-Translator: agron85 \n" "Language-Team: english \n" "Language: en\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" #: qt_ui/ui_qt_ts.py:294 msgid " Create Kml" msgstr "" #: qt_ui/ui_qt_ts.py:681 qt_ui/ui_qt_ts.py:683 qt_ui/ui_qt_ts.py:686 msgid " km" msgstr "" #: qt_ui/ui_qt_ts.py:292 msgid "&Create Kml" msgstr "" #: qt_ui/ui_qt_ts.py:275 msgid "&Kml server" msgstr "" #: qt_ui/ui_qt_ts.py:280 msgid "&New" msgstr "" #: qt_ui/ui_qt_ts.py:276 msgid "&Open" msgstr "" #: cyclograph/qt/gui.py:471 msgid "&Options" msgstr "" #: cyclograph/qt/gui.py:448 msgid "&Orizzontal lines" msgstr "" #: qt_ui/ui_qt_ts.py:297 msgid "&Preferences" msgstr "" #: qt_ui/ui_qt_ts.py:278 msgid "&Quit" msgstr "" #: qt_ui/ui_qt_ts.py:277 msgid "&Save" msgstr "" #: cyclograph/qt/gui.py:484 msgid "&Saving resolution" msgstr "" #: cyclograph/qt/gui.py:479 msgid "&Theme" msgstr "" #: cyclograph/qt/gui.py:452 msgid "3D &effect" msgstr "" #: cyclograph/gtk3/gui.py:587 cyclograph/gtk3/gui.py:778 qt_ui/ui_qt_ts.py:973 msgid "3D effect" msgstr "" #: cyclograph/cyclograph.py:120 msgid "A new version of CycloGraph is out!" msgstr "" #: qt_ui/ui_qt_ts.py:279 msgid "About" msgstr "" #: cyclograph/cyclograph.py:599 cyclograph/gtk3/gui.py:105 #: qt_ui/ui_qt_ts.py:281 #, fuzzy msgid "Add" msgstr "Add" #: cyclograph/gtk3/gui.py:107 msgid "Add a check-point" msgstr "" #: cyclograph/gtk3/gui.py:348 qt_ui/ui_qt_ts.py:735 msgid "Altitude [m]" msgstr "" #: cyclograph/gtk3/gui.py:414 qt_ui/ui_qt_ts.py:469 msgid "Altitude:" msgstr "" #: cyclograph/cyclograph.py:760 cyclograph/cyclograph.py:796 #: qt_ui/ui_qt_ts.py:992 msgid "Author" msgstr "" #: cyclograph/gtk3/gui.py:484 qt_ui/ui_qt_ts.py:399 msgid "Author:" msgstr "" #: cyclograph/gtk3/gui.py:1508 qt_ui/ui_qt_ts.py:676 msgid "Automatic" msgstr "" #: cyclograph/cyclograph.py:756 cyclograph/cyclograph.py:792 msgid "Average gradient" msgstr "" #: cyclograph/gtk3/gui.py:492 cyclograph/slope.py:230 qt_ui/ui_qt_ts.py:401 msgid "Average gradient:" msgstr "" #: cyclograph/gtk3/gui.py:1581 cyclograph/gtk3/gui.py:1620 msgid "Back" msgstr "" #: cyclograph/qt/glal.py:367 cyclograph/qt/gui.py:917 #: cyclograph/gtk3/gui.py:1553 qt_ui/ui_qt_ts.py:684 msgid "Cancel" msgstr "" #: cyclograph/gtk3/gui.py:842 qt_ui/ui_qt_ts.py:979 msgid "Check online" msgstr "" #: cyclograph/cyclograph.py:186 cyclograph/cyclograph.py:394 msgid "Choose a file" msgstr "" #: cyclograph/qt/gui.py:793 cyclograph/gtk3/gui.py:1503 msgid "Choose the type of import you want" msgstr "" #: qt_ui/ui_qt_ts.py:731 msgid "Close" msgstr "" #: cyclograph/qt/gui.py:916 cyclograph/gtk3/gui.py:1772 msgid "Close without saving" msgstr "" #: cyclograph/cyclograph.py:755 cyclograph/cyclograph.py:791 msgid "Country" msgstr "" #: cyclograph/gtk3/gui.py:88 qt_ui/ui_qt_ts.py:293 msgid "Create" msgstr "" #: cyclograph/gtk3/gui.py:89 msgid "Create KML" msgstr "" #: cyclograph/gtk3/gui.py:1207 qt_ui/ui_qt_ts.py:75 msgid "Create Kml" msgstr "" #: cyclograph/gtk3/gui.py:116 qt_ui/ui_qt_ts.py:285 msgid "Delete" msgstr "" #: cyclograph/gtk3/gui.py:856 qt_ui/ui_qt_ts.py:982 msgid "Description" msgstr "" #: cyclograph/gtk3/gui.py:343 qt_ui/ui_qt_ts.py:733 msgid "Distance [km]" msgstr "" #: cyclograph/qt/gui.py:891 cyclograph/gtk3/gui.py:1699 msgid "Distance and altitude are required." msgstr "" #: cyclograph/gtk3/gui.py:413 qt_ui/ui_qt_ts.py:467 msgid "Distance:" msgstr "" #: cyclograph/qt/glal.py:366 cyclograph/gtk3/glal.py:386 msgid "Downloading altitudes" msgstr "" #: cyclograph/qt/gui.py:668 cyclograph/gtk3/gui.py:1202 #: cyclograph/gtk3/gui.py:1226 qt_ui/ui_qt_ts.py:86 msgid "Draw on the map" msgstr "" #: cyclograph/cyclograph.py:762 cyclograph/cyclograph.py:798 #: cyclograph/gtk3/gui.py:951 msgid "E-mail" msgstr "" #: cyclograph/gtk3/gui.py:488 qt_ui/ui_qt_ts.py:400 qt_ui/ui_qt_ts.py:991 msgid "E-mail:" msgstr "" #: cyclograph/cyclograph.py:620 cyclograph/gtk3/gui.py:112 #: qt_ui/ui_qt_ts.py:283 msgid "Edit" msgstr "" #: cyclograph/gtk3/gui.py:114 qt_ui/ui_qt_ts.py:284 msgid "Edit a check-point" msgstr "" #: cyclograph/gtk3/gui.py:1549 qt_ui/ui_qt_ts.py:682 msgid "End" msgstr "" #: cyclograph/cyclograph.py:304 msgid "Error on loading slope" msgstr "" #: cyclograph/cyclograph.py:355 msgid "Error while loading data" msgstr "" #: qt_ui/ui_qt_ts.py:273 msgid "File" msgstr "" #: cyclograph/cyclograph.py:403 msgid "File saved" msgstr "" #: cyclograph/gtk3/gui.py:1580 cyclograph/gtk3/gui.py:1619 msgid "Finish" msgstr "" #: qt_ui/ui_qt_ts.py:988 msgid "Fonts" msgstr "" #: cyclograph/qt/gui.py:890 cyclograph/gtk3/gui.py:1698 msgid "Form incomplete" msgstr "" #: qt_ui/ui_qt_ts.py:981 msgid "General" msgstr "" #: cyclograph/gtk3/gui.py:868 qt_ui/ui_qt_ts.py:984 msgid "Gradient" msgstr "" #: qt_ui/ui_qt_ts.py:989 msgid "Gradient colors" msgstr "" #: cyclograph/cyclograph.py:758 cyclograph/cyclograph.py:794 msgid "Height difference" msgstr "" #: cyclograph/gtk3/gui.py:496 cyclograph/slope.py:232 qt_ui/ui_qt_ts.py:403 msgid "Height difference:" msgstr "" #: cyclograph/cyclograph.py:759 cyclograph/cyclograph.py:795 #: cyclograph/slope.py:233 msgid "Height gain" msgstr "" #: cyclograph/gtk3/gui.py:498 qt_ui/ui_qt_ts.py:404 msgid "Height gain:" msgstr "" #: cyclograph/gtk3/gui.py:1765 msgid "If you don't save, all your changes will be permanently lost." msgstr "" #: cyclograph/gtk3/gui.py:132 qt_ui/ui_qt_ts.py:289 msgid "Info" msgstr "" #: cyclograph/gtk3/gui.py:133 qt_ui/ui_qt_ts.py:290 msgid "Informations about the slope" msgstr "" #: cyclograph/gtk3/gui.py:821 qt_ui/ui_qt_ts.py:976 msgid "Kml server" msgstr "" #: cyclograph/slope.py:238 msgid "LEGEND" msgstr "" #: cyclograph/gtk3/gui.py:837 qt_ui/ui_qt_ts.py:978 msgid "Latest version" msgstr "" #: cyclograph/qt/gui.py:639 cyclograph/gtk3/gui.py:128 #: cyclograph/gtk3/gui.py:1119 qt_ui/ui_qt_ts.py:298 qt_ui/ui_qt_ts.py:299 msgid "Map" msgstr "" #: cyclograph/cyclograph.py:757 cyclograph/cyclograph.py:793 msgid "Max gradient" msgstr "" #: cyclograph/gtk3/gui.py:494 cyclograph/slope.py:231 qt_ui/ui_qt_ts.py:402 msgid "Max gradient:" msgstr "" #: qt_ui/ui_qt_ts.py:76 msgid "Mode" msgstr "" #: cyclograph/gtk3/gui.py:1237 msgid "Modes" msgstr "" #: cyclograph/cyclograph.py:754 cyclograph/cyclograph.py:761 #: cyclograph/cyclograph.py:790 cyclograph/cyclograph.py:797 #: cyclograph/gtk3/gui.py:353 cyclograph/gtk3/gui.py:941 qt_ui/ui_qt_ts.py:737 msgid "Name" msgstr "" #: cyclograph/gtk3/gui.py:415 cyclograph/gtk3/gui.py:476 qt_ui/ui_qt_ts.py:396 #: qt_ui/ui_qt_ts.py:468 qt_ui/ui_qt_ts.py:990 msgid "Name:" msgstr "" #: cyclograph/cyclograph.py:283 msgid "Network error : unable to create an handler to download kml route." msgstr "" #: cyclograph/gtk3/gui.py:84 msgid "New" msgstr "" #: cyclograph/gtk3/gui.py:85 msgid "New Slope" msgstr "" #: cyclograph/gtk3/gui.py:1554 qt_ui/ui_qt_ts.py:685 msgid "Next" msgstr "" #: cyclograph/qt/gui.py:897 cyclograph/gtk3/gui.py:1708 msgid "No coordinates" msgstr "" #: cyclograph/qt/gui.py:883 cyclograph/gtk3/gui.py:1688 msgid "No route" msgstr "" #: cyclograph/qt/gui.py:906 cyclograph/gtk3/gui.py:1718 msgid "Not enough check points" msgstr "" #: cyclograph/cyclograph.py:800 cyclograph/gtk3/gui.py:501 msgid "Note" msgstr "" #: qt_ui/ui_qt_ts.py:398 msgid "Note:" msgstr "" #: cyclograph/qt/gui.py:666 cyclograph/gtk3/gui.py:1200 #: cyclograph/gtk3/gui.py:1224 qt_ui/ui_qt_ts.py:82 msgid "OSRM" msgstr "" #: cyclograph/cyclograph.py:251 msgid "Old file format: please save it again." msgstr "" #: cyclograph/gtk3/gui.py:86 msgid "Open" msgstr "" #: cyclograph/qt/gui.py:665 cyclograph/gtk3/gui.py:1199 #: cyclograph/gtk3/gui.py:1223 qt_ui/ui_qt_ts.py:80 msgid "Open Route Service" msgstr "" #: cyclograph/gtk3/gui.py:87 msgid "Open Slope" msgstr "" #: qt_ui/ui_qt_ts.py:274 msgid "Options" msgstr "" #: cyclograph/gtk3/gui.py:584 cyclograph/gtk3/gui.py:775 qt_ui/ui_qt_ts.py:972 msgid "Orizzontal lines" msgstr "" #: cyclograph/gtk3/glal.py:387 msgid "Please wait, Cyclograph is downloading altitudes." msgstr "" #: cyclograph/gtk3/gui.py:124 qt_ui/ui_qt_ts.py:287 msgid "Plot" msgstr "" #: cyclograph/gtk3/gui.py:125 qt_ui/ui_qt_ts.py:288 msgid "Plot your slope" msgstr "" #: cyclograph/gtk3/gui.py:747 qt_ui/ui_qt_ts.py:970 msgid "Preferences" msgstr "" #: qt_ui/ui_qt_ts.py:282 msgid "Press + on the keyboard to add a check-point" msgstr "" #: qt_ui/ui_qt_ts.py:286 msgid "Press - on the keyboard to remove the selected check-point" msgstr "" #: cyclograph/gtk3/gui.py:118 msgid "Remove a check-point" msgstr "" #: cyclograph/gtk3/gui.py:788 qt_ui/ui_qt_ts.py:974 msgid "Resolution" msgstr "" #: cyclograph/gtk3/gui.py:968 msgid "Restore Default" msgstr "" #: cyclograph/qt/gui.py:439 cyclograph/qt/gui.py:915 cyclograph/gtk3/gui.py:90 msgid "Save" msgstr "" #: qt_ui/ui_qt_ts.py:295 msgid "Save &As" msgstr "" #: cyclograph/qt/gui.py:443 msgid "Save PDF" msgstr "" #: cyclograph/gtk3/gui.py:91 msgid "Save Slope" msgstr "" #: cyclograph/gtk3/gui.py:580 msgid "Save _PDF" msgstr "" #: cyclograph/gtk3/gui.py:582 msgid "Save _html" msgstr "" #: cyclograph/cyclograph.py:375 qt_ui/ui_qt_ts.py:296 msgid "Save as" msgstr "" #: cyclograph/qt/gui.py:920 cyclograph/gtk3/gui.py:1769 msgid "Save changes" msgstr "" #: cyclograph/qt/gui.py:919 cyclograph/gtk3/gui.py:1764 #, python-format msgid "Save changes to %s before closing?" msgstr "" #: cyclograph/qt/gui.py:444 msgid "Save html" msgstr "" #: cyclograph/cyclograph.py:699 msgid "Save map" msgstr "" #: cyclograph/cyclograph.py:718 msgid "Save plot" msgstr "" #: cyclograph/cyclograph.py:777 msgid "Save plot to html" msgstr "" #: cyclograph/cyclograph.py:742 msgid "Save plot to pdf" msgstr "" #: cyclograph/gtk3/gui.py:620 msgid "Saving _resolution" msgstr "" #: cyclograph/gtk3/gui.py:1056 msgid "Select Font" msgstr "" #: cyclograph/gtk3/gui.py:1517 qt_ui/ui_qt_ts.py:677 msgid "Select the distance rate" msgstr "" #: cyclograph/gtk3/gui.py:1513 qt_ui/ui_qt_ts.py:678 msgid "Select the number of checkpoints" msgstr "" #: cyclograph/qt/gui.py:456 msgid "Show &legend" msgstr "" #: cyclograph/gtk3/gui.py:781 qt_ui/ui_qt_ts.py:977 msgid "Show info" msgstr "" #: cyclograph/gtk3/gui.py:590 cyclograph/gtk3/gui.py:784 qt_ui/ui_qt_ts.py:980 msgid "Show legend" msgstr "" #: cyclograph/gtk3/gui.py:129 qt_ui/ui_qt_ts.py:300 msgid "Show the map of your slope" msgstr "" #: cyclograph/gtk3/gui.py:242 qt_ui/ui_qt_ts.py:291 msgid "Signal a bug" msgstr "" #: cyclograph/cyclograph.py:753 cyclograph/cyclograph.py:789 msgid "Slope" msgstr "" #: cyclograph/gtk3/gui.py:458 qt_ui/ui_qt_ts.py:395 msgid "Slope Info" msgstr "" #: cyclograph/qt/gui.py:883 cyclograph/gtk3/gui.py:1689 msgid "Specify one route first." msgstr "" #: cyclograph/gtk3/gui.py:1547 qt_ui/ui_qt_ts.py:680 msgid "Start" msgstr "" #: cyclograph/gtk3/gui.py:480 qt_ui/ui_qt_ts.py:397 msgid "State:" msgstr "" #: cyclograph/qt/gui.py:789 cyclograph/gtk3/gui.py:1568 #, python-format msgid "" "The %s file contains %d points.\n" "Choose how many will be imported." msgstr "" #: cyclograph/qt/gui.py:792 cyclograph/gtk3/gui.py:1596 #, python-format msgid "" "The %s file contains a route of %.3f Km.\n" "Choose what range you want between points." msgstr "" #: cyclograph/gtk3/gui.py:807 qt_ui/ui_qt_ts.py:975 msgid "Theme" msgstr "" #: cyclograph/qt/gui.py:907 cyclograph/gtk3/gui.py:1719 msgid "" "There aren't enougth check points to plot.\n" " At least two points are required to plot." msgstr "" #: cyclograph/gtk3/gui.py:880 qt_ui/ui_qt_ts.py:986 msgid "Title" msgstr "" #: cyclograph/cyclograph.py:245 msgid "Unable to open file" msgstr "" #: cyclograph/gtk3/gui.py:1521 qt_ui/ui_qt_ts.py:679 msgid "Use every point (discouraged)" msgstr "" #: cyclograph/gtk3/gui.py:770 qt_ui/ui_qt_ts.py:971 msgid "Visualization" msgstr "" #: cyclograph/gtk3/gui.py:159 msgid "_File" msgstr "" #: cyclograph/gtk3/gui.py:206 cyclograph/gtk3/gui.py:572 msgid "_Options" msgstr "" #: cyclograph/gtk3/gui.py:595 msgid "_Theme" msgstr "" #: qt_ui/ui_qt_ts.py:983 qt_ui/ui_qt_ts.py:985 qt_ui/ui_qt_ts.py:987 msgid "font | dim" msgstr "" #: cyclograph/cyclograph.py:267 msgid "kml name" msgstr "" #: qt_ui/ui_qt_ts.py:510 msgid "map" msgstr "" #: cyclograph/gtk3/gui.py:1489 qt_ui/ui_qt_ts.py:675 msgid "number of checkpoints" msgstr "" #: cyclograph/cyclograph.py:148 msgid "old cofiguration issue." msgstr "" #: cyclograph/cyclograph.py:167 cyclograph/cyclograph.py:534 #: cyclograph/cyclograph.py:543 msgid "slope" msgstr "" #: cyclograph/cyclograph.py:310 #, python-format msgid "slope %d" msgstr "" #: cyclograph/qt/gui.py:898 cyclograph/gtk3/gui.py:1709 msgid "there aren't coordinates associated with this slope." msgstr "" #: cyclograph/qt/gui.py:667 cyclograph/gtk3/gui.py:1201 #: cyclograph/gtk3/gui.py:1225 qt_ui/ui_qt_ts.py:84 msgid "yournavigation" msgstr "" #~ msgid "Add cp" #~ msgstr "Add" cyclograph-1.9.1/po/it/0000755000175000017500000000000013215023054016264 5ustar federicofederico00000000000000cyclograph-1.9.1/po/it/LC_MESSAGES/0000755000175000017500000000000013215023054020051 5ustar federicofederico00000000000000cyclograph-1.9.1/po/it/LC_MESSAGES/cyclograph.po0000644000175000017500000005057713144673266022603 0ustar federicofederico00000000000000# Cyclograph. # Copyright (C) 2010 Pierluigi Villani # This file is distributed under the same license as the Cyclograph package. # # Pierluigi Villani , 2010, 2011, 2012, 2013. # Federico Brega , 2010, 2011. msgid "" msgstr "" "Project-Id-Version: Cyclograph\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-02-16 23:46+0100\n" "PO-Revision-Date: 2015-02-16 23:49+0100\n" "Last-Translator: agron85 \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: qt_ui/ui_qt_ts.py:294 msgid " Create Kml" msgstr " Crea Kml" #: qt_ui/ui_qt_ts.py:681 qt_ui/ui_qt_ts.py:683 qt_ui/ui_qt_ts.py:686 msgid " km" msgstr "km" #: qt_ui/ui_qt_ts.py:292 msgid "&Create Kml" msgstr "&Crea Kml" #: qt_ui/ui_qt_ts.py:275 msgid "&Kml server" msgstr "&Server KML" #: qt_ui/ui_qt_ts.py:280 msgid "&New" msgstr "&Nuovo" #: qt_ui/ui_qt_ts.py:276 msgid "&Open" msgstr "&Apri" #: cyclograph/qt/gui.py:471 msgid "&Options" msgstr "&Opzioni" #: cyclograph/qt/gui.py:448 msgid "&Orizzontal lines" msgstr "&Linee orizzontali" #: qt_ui/ui_qt_ts.py:297 msgid "&Preferences" msgstr "&Preferenze" #: qt_ui/ui_qt_ts.py:278 msgid "&Quit" msgstr "&Esci" #: qt_ui/ui_qt_ts.py:277 msgid "&Save" msgstr "&Salva" #: cyclograph/qt/gui.py:484 msgid "&Saving resolution" msgstr "&Risoluzione del salvataggio" #: cyclograph/qt/gui.py:479 msgid "&Theme" msgstr "&Temi" #: cyclograph/qt/gui.py:452 msgid "3D &effect" msgstr "&Effetto 3d" #: cyclograph/gtk3/gui.py:587 cyclograph/gtk3/gui.py:778 qt_ui/ui_qt_ts.py:973 msgid "3D effect" msgstr "Effetto 3D" #: cyclograph/cyclograph.py:120 msgid "A new version of CycloGraph is out!" msgstr "Ć© uscita una nuova versione di CycloGraph!" #: qt_ui/ui_qt_ts.py:279 msgid "About" msgstr "&Informazioni" #: cyclograph/cyclograph.py:599 cyclograph/gtk3/gui.py:105 #: qt_ui/ui_qt_ts.py:281 msgid "Add" msgstr "Aggiungi" #: cyclograph/gtk3/gui.py:107 msgid "Add a check-point" msgstr "Aggiungi un punto" #: cyclograph/gtk3/gui.py:348 qt_ui/ui_qt_ts.py:735 msgid "Altitude [m]" msgstr "Altezza [m]" #: cyclograph/gtk3/gui.py:414 qt_ui/ui_qt_ts.py:469 msgid "Altitude:" msgstr "Altezza:" #: cyclograph/cyclograph.py:760 cyclograph/cyclograph.py:796 #: qt_ui/ui_qt_ts.py:992 msgid "Author" msgstr "Autore" #: cyclograph/gtk3/gui.py:484 qt_ui/ui_qt_ts.py:399 msgid "Author:" msgstr "Autore:" #: cyclograph/gtk3/gui.py:1508 qt_ui/ui_qt_ts.py:676 msgid "Automatic" msgstr "Automatica" #: cyclograph/cyclograph.py:756 cyclograph/cyclograph.py:792 msgid "Average gradient" msgstr "Pendenza media" #: cyclograph/gtk3/gui.py:492 cyclograph/slope.py:230 qt_ui/ui_qt_ts.py:401 msgid "Average gradient:" msgstr "Pendenza media:" #: cyclograph/gtk3/gui.py:1581 cyclograph/gtk3/gui.py:1620 msgid "Back" msgstr "Indietro" #: cyclograph/qt/glal.py:367 cyclograph/qt/gui.py:917 #: cyclograph/gtk3/gui.py:1553 qt_ui/ui_qt_ts.py:684 msgid "Cancel" msgstr "Annulla" #: cyclograph/gtk3/gui.py:842 qt_ui/ui_qt_ts.py:979 msgid "Check online" msgstr "Controlla online" #: cyclograph/cyclograph.py:186 cyclograph/cyclograph.py:394 msgid "Choose a file" msgstr "Scegli un file" #: cyclograph/qt/gui.py:793 cyclograph/gtk3/gui.py:1503 msgid "Choose the type of import you want" msgstr "Seleziona il tipo di importazione" #: qt_ui/ui_qt_ts.py:731 msgid "Close" msgstr "Chiudi" #: cyclograph/qt/gui.py:916 cyclograph/gtk3/gui.py:1772 msgid "Close without saving" msgstr "Chiudi senza salvare" #: cyclograph/cyclograph.py:755 cyclograph/cyclograph.py:791 msgid "Country" msgstr "Nazione" #: cyclograph/gtk3/gui.py:88 qt_ui/ui_qt_ts.py:293 msgid "Create" msgstr "Crea" #: cyclograph/gtk3/gui.py:89 msgid "Create KML" msgstr "Crea un KML" #: cyclograph/gtk3/gui.py:1207 qt_ui/ui_qt_ts.py:75 msgid "Create Kml" msgstr "Crea Kml" #: cyclograph/gtk3/gui.py:116 qt_ui/ui_qt_ts.py:285 msgid "Delete" msgstr "Cancella" #: cyclograph/gtk3/gui.py:856 qt_ui/ui_qt_ts.py:982 msgid "Description" msgstr "Descrizione" #: cyclograph/gtk3/gui.py:343 qt_ui/ui_qt_ts.py:733 msgid "Distance [km]" msgstr "Distanza [Km]" #: cyclograph/qt/gui.py:891 cyclograph/gtk3/gui.py:1699 msgid "Distance and altitude are required." msgstr "Distanza e altezza sono richieste." #: cyclograph/gtk3/gui.py:413 qt_ui/ui_qt_ts.py:467 msgid "Distance:" msgstr "Distanza:" #: cyclograph/qt/glal.py:366 cyclograph/gtk3/glal.py:386 msgid "Downloading altitudes" msgstr "Scaricamento altezze" #: cyclograph/qt/gui.py:668 cyclograph/gtk3/gui.py:1202 #: cyclograph/gtk3/gui.py:1226 qt_ui/ui_qt_ts.py:86 msgid "Draw on the map" msgstr "Disegna sulla mappa" #: cyclograph/cyclograph.py:762 cyclograph/cyclograph.py:798 #: cyclograph/gtk3/gui.py:951 msgid "E-mail" msgstr "E-mail" #: cyclograph/gtk3/gui.py:488 qt_ui/ui_qt_ts.py:400 qt_ui/ui_qt_ts.py:991 msgid "E-mail:" msgstr "E-mail:" #: cyclograph/cyclograph.py:620 cyclograph/gtk3/gui.py:112 #: qt_ui/ui_qt_ts.py:283 msgid "Edit" msgstr "Modifica" #: cyclograph/gtk3/gui.py:114 qt_ui/ui_qt_ts.py:284 msgid "Edit a check-point" msgstr "Modifica un punto" #: cyclograph/gtk3/gui.py:1549 qt_ui/ui_qt_ts.py:682 msgid "End" msgstr "Fine" #: cyclograph/cyclograph.py:304 msgid "Error on loading slope" msgstr "Errore durante il caricamento della salita" #: cyclograph/cyclograph.py:355 msgid "Error while loading data" msgstr "Errore durante il caricamento dei dati" #: qt_ui/ui_qt_ts.py:273 msgid "File" msgstr "File" #: cyclograph/cyclograph.py:403 msgid "File saved" msgstr "File salvato" #: cyclograph/gtk3/gui.py:1580 cyclograph/gtk3/gui.py:1619 msgid "Finish" msgstr "Fine" #: qt_ui/ui_qt_ts.py:988 msgid "Fonts" msgstr "Caratteri" #: cyclograph/qt/gui.py:890 cyclograph/gtk3/gui.py:1698 msgid "Form incomplete" msgstr "Form incompleto" #: qt_ui/ui_qt_ts.py:981 msgid "General" msgstr "Generale" #: cyclograph/gtk3/gui.py:868 qt_ui/ui_qt_ts.py:984 msgid "Gradient" msgstr "Pendenza" #: qt_ui/ui_qt_ts.py:989 msgid "Gradient colors" msgstr "Colori salita" #: cyclograph/cyclograph.py:758 cyclograph/cyclograph.py:794 msgid "Height difference" msgstr "Dislivello" #: cyclograph/gtk3/gui.py:496 cyclograph/slope.py:232 qt_ui/ui_qt_ts.py:403 msgid "Height difference:" msgstr "Dislivello:" #: cyclograph/cyclograph.py:759 cyclograph/cyclograph.py:795 #: cyclograph/slope.py:233 msgid "Height gain" msgstr "Aumento in altezza" #: cyclograph/gtk3/gui.py:498 qt_ui/ui_qt_ts.py:404 msgid "Height gain:" msgstr "Aumento in altezza:" #: cyclograph/gtk3/gui.py:1765 msgid "If you don't save, all your changes will be permanently lost." msgstr "Se non salvi, tutte le modifiche andranno perse." #: cyclograph/gtk3/gui.py:132 qt_ui/ui_qt_ts.py:289 msgid "Info" msgstr "Info" #: cyclograph/gtk3/gui.py:133 qt_ui/ui_qt_ts.py:290 msgid "Informations about the slope" msgstr " Informazioni sulla salita" #: cyclograph/gtk3/gui.py:821 qt_ui/ui_qt_ts.py:976 msgid "Kml server" msgstr "kml server" #: cyclograph/slope.py:238 msgid "LEGEND" msgstr "LEGENDA" #: cyclograph/gtk3/gui.py:837 qt_ui/ui_qt_ts.py:978 msgid "Latest version" msgstr "Ultima versione" #: cyclograph/qt/gui.py:639 cyclograph/gtk3/gui.py:128 #: cyclograph/gtk3/gui.py:1119 qt_ui/ui_qt_ts.py:298 qt_ui/ui_qt_ts.py:299 msgid "Map" msgstr "Mappa" #: cyclograph/cyclograph.py:757 cyclograph/cyclograph.py:793 msgid "Max gradient" msgstr "Pendenza massima" #: cyclograph/gtk3/gui.py:494 cyclograph/slope.py:231 qt_ui/ui_qt_ts.py:402 msgid "Max gradient:" msgstr "Pendenza massima:" #: qt_ui/ui_qt_ts.py:76 msgid "Mode" msgstr "ModalitĆ " #: cyclograph/gtk3/gui.py:1237 msgid "Modes" msgstr "ModalitĆ " #: cyclograph/cyclograph.py:754 cyclograph/cyclograph.py:761 #: cyclograph/cyclograph.py:790 cyclograph/cyclograph.py:797 #: cyclograph/gtk3/gui.py:353 cyclograph/gtk3/gui.py:941 qt_ui/ui_qt_ts.py:737 msgid "Name" msgstr "Nome" #: cyclograph/gtk3/gui.py:415 cyclograph/gtk3/gui.py:476 qt_ui/ui_qt_ts.py:396 #: qt_ui/ui_qt_ts.py:468 qt_ui/ui_qt_ts.py:990 msgid "Name:" msgstr "Nome:" #: cyclograph/cyclograph.py:283 msgid "Network error : unable to create an handler to download kml route." msgstr "" "Errore di rete: impossibile creare un gestore per scaricare il percorso del " "kml." #: cyclograph/gtk3/gui.py:84 msgid "New" msgstr "Nuovo" #: cyclograph/gtk3/gui.py:85 msgid "New Slope" msgstr " Nuova Salita" #: cyclograph/gtk3/gui.py:1554 qt_ui/ui_qt_ts.py:685 msgid "Next" msgstr "Avanti" #: cyclograph/qt/gui.py:897 cyclograph/gtk3/gui.py:1708 msgid "No coordinates" msgstr "Nessuna coordinata" #: cyclograph/qt/gui.py:883 cyclograph/gtk3/gui.py:1688 msgid "No route" msgstr "Nessun percorso" #: cyclograph/qt/gui.py:906 cyclograph/gtk3/gui.py:1718 msgid "Not enough check points" msgstr "Non ci sono abbastanza check point" #: cyclograph/cyclograph.py:800 cyclograph/gtk3/gui.py:501 msgid "Note" msgstr "Note" #: qt_ui/ui_qt_ts.py:398 msgid "Note:" msgstr "Note:" #: cyclograph/qt/gui.py:666 cyclograph/gtk3/gui.py:1200 #: cyclograph/gtk3/gui.py:1224 qt_ui/ui_qt_ts.py:82 msgid "OSRM" msgstr "OSRM" #: cyclograph/cyclograph.py:251 msgid "Old file format: please save it again." msgstr "Vecchio formato di file: salvare di nuovo" #: cyclograph/gtk3/gui.py:86 msgid "Open" msgstr "Apri" #: cyclograph/qt/gui.py:665 cyclograph/gtk3/gui.py:1199 #: cyclograph/gtk3/gui.py:1223 qt_ui/ui_qt_ts.py:80 msgid "Open Route Service" msgstr "Open Route Service" #: cyclograph/gtk3/gui.py:87 msgid "Open Slope" msgstr " Apri Salita" #: qt_ui/ui_qt_ts.py:274 msgid "Options" msgstr "&Opzioni" #: cyclograph/gtk3/gui.py:584 cyclograph/gtk3/gui.py:775 qt_ui/ui_qt_ts.py:972 msgid "Orizzontal lines" msgstr "Linee orizzontali" #: cyclograph/gtk3/glal.py:387 msgid "Please wait, Cyclograph is downloading altitudes." msgstr "attendere, Cyclograph sta scaricando le altimetrie" #: cyclograph/gtk3/gui.py:124 qt_ui/ui_qt_ts.py:287 msgid "Plot" msgstr "Disegna" #: cyclograph/gtk3/gui.py:125 qt_ui/ui_qt_ts.py:288 msgid "Plot your slope" msgstr "Disegna la salita" #: cyclograph/gtk3/gui.py:747 qt_ui/ui_qt_ts.py:970 msgid "Preferences" msgstr "Preferenze" #: qt_ui/ui_qt_ts.py:282 msgid "Press + on the keyboard to add a check-point" msgstr "Premere + sulla tastiera per aggiungere un check-point" #: qt_ui/ui_qt_ts.py:286 msgid "Press - on the keyboard to remove the selected check-point" msgstr "Premere - sulla tastiera per rimuovere il check-point selezionato" #: cyclograph/gtk3/gui.py:118 msgid "Remove a check-point" msgstr "Rimuove check-point" #: cyclograph/gtk3/gui.py:788 qt_ui/ui_qt_ts.py:974 msgid "Resolution" msgstr "Risoluzione" #: cyclograph/gtk3/gui.py:968 msgid "Restore Default" msgstr "Ripristina default" #: cyclograph/qt/gui.py:439 cyclograph/qt/gui.py:915 cyclograph/gtk3/gui.py:90 msgid "Save" msgstr "Salva" #: qt_ui/ui_qt_ts.py:295 msgid "Save &As" msgstr "Salva &come" #: cyclograph/qt/gui.py:443 msgid "Save PDF" msgstr "Salva PDF" #: cyclograph/gtk3/gui.py:91 msgid "Save Slope" msgstr " Salva Salita" #: cyclograph/gtk3/gui.py:580 msgid "Save _PDF" msgstr "Salva _PDF" #: cyclograph/gtk3/gui.py:582 msgid "Save _html" msgstr "Salva _html" #: cyclograph/cyclograph.py:375 qt_ui/ui_qt_ts.py:296 msgid "Save as" msgstr "Salva come" #: cyclograph/qt/gui.py:920 cyclograph/gtk3/gui.py:1769 msgid "Save changes" msgstr "Salvare le modifiche a" #: cyclograph/qt/gui.py:919 cyclograph/gtk3/gui.py:1764 #, python-format msgid "Save changes to %s before closing?" msgstr "Salvare i cambiamenti in %s prima di chiudere?" #: cyclograph/qt/gui.py:444 msgid "Save html" msgstr "Salva html" #: cyclograph/cyclograph.py:699 msgid "Save map" msgstr "Salva mappa" #: cyclograph/cyclograph.py:718 msgid "Save plot" msgstr "Salva kml" #: cyclograph/cyclograph.py:777 msgid "Save plot to html" msgstr "Salva grafico in html" #: cyclograph/cyclograph.py:742 msgid "Save plot to pdf" msgstr "Salva grafico in PDF" #: cyclograph/gtk3/gui.py:620 msgid "Saving _resolution" msgstr "_Risoluzione del salvataggio" #: cyclograph/gtk3/gui.py:1056 msgid "Select Font" msgstr "Seleziona lo stile" #: cyclograph/gtk3/gui.py:1517 qt_ui/ui_qt_ts.py:677 msgid "Select the distance rate" msgstr "Seleziona l'intervallo di distanza" #: cyclograph/gtk3/gui.py:1513 qt_ui/ui_qt_ts.py:678 msgid "Select the number of checkpoints" msgstr "Seleziona il numero di checkpoint" #: cyclograph/qt/gui.py:456 msgid "Show &legend" msgstr "Mostra &legenda" #: cyclograph/gtk3/gui.py:781 qt_ui/ui_qt_ts.py:977 msgid "Show info" msgstr "Mostra informazioni" #: cyclograph/gtk3/gui.py:590 cyclograph/gtk3/gui.py:784 qt_ui/ui_qt_ts.py:980 msgid "Show legend" msgstr "Mostra legenda" #: cyclograph/gtk3/gui.py:129 qt_ui/ui_qt_ts.py:300 msgid "Show the map of your slope" msgstr "Mostra la mappa della salita" #: cyclograph/gtk3/gui.py:242 qt_ui/ui_qt_ts.py:291 msgid "Signal a bug" msgstr "Segnala un bug" #: cyclograph/cyclograph.py:753 cyclograph/cyclograph.py:789 msgid "Slope" msgstr "Salita" #: cyclograph/gtk3/gui.py:458 qt_ui/ui_qt_ts.py:395 msgid "Slope Info" msgstr "Info Salita" #: cyclograph/qt/gui.py:883 cyclograph/gtk3/gui.py:1689 msgid "Specify one route first." msgstr "Specificare prima un percorso." #: cyclograph/gtk3/gui.py:1547 qt_ui/ui_qt_ts.py:680 msgid "Start" msgstr "Inizio" #: cyclograph/gtk3/gui.py:480 qt_ui/ui_qt_ts.py:397 msgid "State:" msgstr "Stato:" #: cyclograph/qt/gui.py:789 cyclograph/gtk3/gui.py:1568 #, python-format msgid "" "The %s file contains %d points.\n" "Choose how many will be imported." msgstr "" "il file %s contiene %d punti.\n" "Scegli quanti importarne." #: cyclograph/qt/gui.py:792 cyclograph/gtk3/gui.py:1596 #, python-format msgid "" "The %s file contains a route of %.3f Km.\n" "Choose what range you want between points." msgstr "" "il file %s contiene un percorso di %3f Km.\n" "Scegli quale distanza vuoi tra i punti." #: cyclograph/gtk3/gui.py:807 qt_ui/ui_qt_ts.py:975 msgid "Theme" msgstr "Tema" #: cyclograph/qt/gui.py:907 cyclograph/gtk3/gui.py:1719 msgid "" "There aren't enougth check points to plot.\n" " At least two points are required to plot." msgstr "" "Non ci sono abbastanza punti da disegnare.\n" " Per il disegno servono almeno 2 punti." #: cyclograph/gtk3/gui.py:880 qt_ui/ui_qt_ts.py:986 msgid "Title" msgstr "Titolo" #: cyclograph/cyclograph.py:245 msgid "Unable to open file" msgstr "Impossibile aprire il file" #: cyclograph/gtk3/gui.py:1521 qt_ui/ui_qt_ts.py:679 msgid "Use every point (discouraged)" msgstr "Utilizza tutti i punti (sconsigliato)" #: cyclograph/gtk3/gui.py:770 qt_ui/ui_qt_ts.py:971 msgid "Visualization" msgstr "Visualizzazione" #: cyclograph/gtk3/gui.py:159 msgid "_File" msgstr "_File" #: cyclograph/gtk3/gui.py:206 cyclograph/gtk3/gui.py:572 msgid "_Options" msgstr "_Opzioni" #: cyclograph/gtk3/gui.py:595 msgid "_Theme" msgstr "_Tema" #: qt_ui/ui_qt_ts.py:983 qt_ui/ui_qt_ts.py:985 qt_ui/ui_qt_ts.py:987 msgid "font | dim" msgstr "font | dim" #: cyclograph/cyclograph.py:267 msgid "kml name" msgstr "nome del kml" #: qt_ui/ui_qt_ts.py:510 msgid "map" msgstr "Mappa" #: cyclograph/gtk3/gui.py:1489 qt_ui/ui_qt_ts.py:675 msgid "number of checkpoints" msgstr "numero di checkpoint" #: cyclograph/cyclograph.py:148 msgid "old cofiguration issue." msgstr "problema con una vecchia configurazione" #: cyclograph/cyclograph.py:167 cyclograph/cyclograph.py:534 #: cyclograph/cyclograph.py:543 msgid "slope" msgstr "Salita" #: cyclograph/cyclograph.py:310 #, python-format msgid "slope %d" msgstr "Salita %d" #: cyclograph/qt/gui.py:898 cyclograph/gtk3/gui.py:1709 msgid "there aren't coordinates associated with this slope." msgstr "non ci sono coordinate associate alla salita" #: cyclograph/qt/gui.py:667 cyclograph/gtk3/gui.py:1201 #: cyclograph/gtk3/gui.py:1225 qt_ui/ui_qt_ts.py:84 msgid "yournavigation" msgstr "yournavigation" #~ msgid "3D _effect" #~ msgstr "_Effetto 3d" #~ msgid "Select color" #~ msgstr "Colori salita" #~ msgid "_Orizzontal lines" #~ msgstr "_Linee orizzontali" #~ msgid "&Delete configuration file" #~ msgstr "&Cancella i file di configurazione" #~ msgid "&Description" #~ msgstr "&Descrizione" #~ msgid "&Gradient" #~ msgstr "&Pendenza" #~ msgid "&Import KML" #~ msgstr "&Importa KML" #~ msgid "&Save preferences" #~ msgstr "&Salva preferenze" #~ msgid "&Slope colors..." #~ msgstr "&Colori salita" #~ msgid "&Title" #~ msgstr "&Titolo" #~ msgid "&Use default preferences" #~ msgstr "&Usa le preferenze di default" #~ msgid "Cyclograph" #~ msgstr "CycloGraph" #~ msgid "Frame" #~ msgstr "Riquadro" #~ msgid "&Effect 3d" #~ msgstr "&Effetto 3d" #~ msgid "3D effect " #~ msgstr "Effetto 3d" #~ msgid "Import" #~ msgstr "Importa" #~ msgid "Import KML" #~ msgstr "Importa KML" #~ msgid "Save kml" #~ msgstr "Salva kml" #~ msgid "Error" #~ msgstr "Errore:" #~ msgid "_Create Kml" #~ msgstr "_Crea Kml" #~ msgid "_Import KML" #~ msgstr "_Importa KML" #~ msgid "Library not available" #~ msgstr "Libreria non disponibile" #~ msgid "PyQt4 is not installed on the system" #~ msgstr "PyQt4 non ĆØ installato sul sistema." #~ msgid "The gtk python module is not installed on the system" #~ msgstr "Il modulo python di gtk non ĆØ installato sul sistema." #~ msgid "" #~ "You should have it on your machine if you want to use the GTK+ front-end" #~ msgstr "" #~ "Dovresti averlo installato sul tuo sistema se vuoi usare l'interfaccia GTK" #~ "+." #~ msgid "" #~ "You should have it on your machine if you want to use the qt front-end" #~ msgstr "" #~ "Dovresti averlo installato sul tuo sistema se vuoi usare l'interfaccia qt." #~ msgid "_Saving resolution" #~ msgstr "_Risoluzione del salvataggio" #~ msgid "pygtk not installed" #~ msgstr "le librerie pygtk non sono installate" #~ msgid "pyqt not installed" #~ msgstr "le librerie pyqt non sono installate" #~ msgid "" #~ "There aren't enougth check points to plot.\n" #~ " To plot are required at least 2 points." #~ msgstr "" #~ "Non ci sono abbastanza punti da disegnare.\n" #~ " Per il disegno servono almeno 2 punti." #~ msgid "Save changes to " #~ msgstr "Salvare le modifiche a " #~ msgid "Are you sure you want to reset to initial configuration preferences?" #~ msgstr "" #~ "Sei sicuro di voler azzerare la configurazione delle preferenze al valore " #~ "iniziale?" #~ msgid "Default preferences" #~ msgstr "Azzera preferenze" #~ msgid "Des_cription" #~ msgstr "Des_crizione" #~ msgid "Grad_ient" #~ msgstr "_Pendenza" #~ msgid "_Delete configuration file" #~ msgstr "_Cancella i file di configurazione" #~ msgid "_Font" #~ msgstr "_Caratteri" #~ msgid "_Gradient colors" #~ msgstr "C_olori salita" #~ msgid "_Resolution" #~ msgstr "_Risoluzione" #~ msgid "_Save preferences" #~ msgstr "_Salva preferenze" #~ msgid "_Title" #~ msgstr "_Titolo" #~ msgid "_Use default preferences" #~ msgstr "_Usa le preferenze di default" #~ msgid "Color type unknown" #~ msgstr "tipo di colore sconosciuto" #~ msgid "Specify one route." #~ msgstr "Specificare un percorso." #~ msgid "" #~ "The gtk python module is not installed on the system.\n" #~ "You should have it on your machine if you want to use the GTK+ front-end." #~ msgstr "" #~ "Il modulo gtk per python non ĆØ installato sul sistema.\n" #~ "devi installarlosul tuo sistema se vuoi usare il front-end GTK+." #, fuzzy #~ msgid "&save as" #~ msgstr "&Salva" #~ msgid "&Reset preferences" #~ msgstr "&Ripristina preferenze" #~ msgid "Graph" #~ msgstr "Grafico" #~ msgid "average gradient:" #~ msgstr "pendenza media:" #~ msgid "max gradient:" #~ msgstr "pendenza massima:" #~ msgid "Properties" #~ msgstr "ProprietĆ " #~ msgid "Altitude" #~ msgstr "Altezza" #~ msgid "Distance" #~ msgstr "Distanza" #~ msgid "Old file format" #~ msgstr "Vecchio formato di file" #~ msgid " Default preferences" #~ msgstr "Azzera preferenze" #~ msgid " Information about this program" #~ msgstr " Informazioni su questo programma" #~ msgid " Load Slope" #~ msgstr " Carica Salita" #~ msgid " Save preferences" #~ msgstr "Salva preferenze" #~ msgid " Terminate the program" #~ msgstr " Termina il programma" #~ msgid "&About" #~ msgstr "&Info" #~ msgid "&Exit\tCtrl+Q" #~ msgstr "&Esci\tCtrl+Q" #~ msgid "&File" #~ msgstr "&File" #~ msgid "&Gradient colors" #~ msgstr "&Colori salita" #~ msgid "&Import KML\tCtrl+I" #~ msgstr "&Importa KML\tCtrl+I" #~ msgid "&New\tCtrl+N" #~ msgstr "&Nuova\tCtrl+N" #~ msgid "&Open\tCtrl+O" #~ msgstr "&Apri\tCtrl+O" #~ msgid "&Save\tCtrl+S" #~ msgstr "&Salva\tCtrl+S" #~ msgid "&orizzontal lines" #~ msgstr "&linee orizzontali" #~ msgid "&save preferences" #~ msgstr "&Salva preferenze" #~ msgid "Add cp" #~ msgstr "Aggiungi" #~ msgid "OK" #~ msgstr "OK" #~ msgid "State" #~ msgstr "Stato" #~ msgid "&Font..." #~ msgstr "&Carattere" cyclograph-1.9.1/qt_ui/0000755000175000017500000000000013215023054016353 5ustar federicofederico00000000000000cyclograph-1.9.1/qt_ui/build.py0000755000175000017500000000213113152470301020025 0ustar federicofederico00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os def findandreplace(text): #PyQt5 is bad again :'( but we have a new trick: the header file text = text.replace( "QtCore.QCoreApplication.translate", "cg_translate") return text ui_files = [ x for x in os.listdir('.') if x.endswith('.ui')] ui_files.sort() commands = [] python_files = [] for uif in ui_files: python_file = 'ui_' + uif.rsplit('.')[0] +'.py' python_files += [python_file] commands += ['pyuic5 -o ' + python_file + ' ' + uif] for command in commands: print(command) os.system(command) print("Building ui_qt module: ../cyclograph/qt/ui_qt.py") outfile = open('../cyclograph/qt/ui_qt.py','w') with open('imports.py', 'r') as imports: outfile.write(imports.read()) for i in range(len(python_files)): filename = python_files[i] fid = open(filename, 'r') for line in fid: if line.startswith("from PyQt5 import") or line.startswith("import cyclograph"): continue res = findandreplace(line) outfile.write(res) fid.close() outfile.close() cyclograph-1.9.1/qt_ui/create_kml.ui0000644000175000017500000000620513152470301021024 0ustar federicofederico00000000000000 Create_KML_Dialog 0 0 800 550 484 0 Create Kml :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png Qt::Horizontal false 0 0 Mode QAbstractItemView::NoEditTriggers QListView::ListMode false true 0 brouter yournavigation Draw on the map 400 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Save QWebEngineView QWidget
PyQt5.QtWebEngineWidgets
1
cyclograph-1.9.1/qt_ui/cyclograph.qrc0000644000175000017500000000237413152500125021222 0ustar federicofederico00000000000000 ../pixmaps/32x32/icon_add.png ../pixmaps/32x32/icon_plot.png ../pixmaps/32x32/icon_map.png ../pixmaps/32x32/icon_info.png ../pixmaps/32x32/cyclograph.png ../pixmaps/32x32/icon_delete.png ../pixmaps/32x32/icon_modify.png ../pixmaps/32x32/icon_ckml.png ../pixmaps/cyclograph.svg ../pixmaps/32x32/new.png ../pixmaps/32x32/open.png ../pixmaps/32x32/save_as.png ../pixmaps/32x32/save.png ../cyclograph/map.html ../cyclograph/openlayers.html ../cyclograph/google.css ../cyclograph/draw_on_map.js cyclograph-1.9.1/qt_ui/cyclograph_main_window.ui0000644000175000017500000002403012670627222023452 0ustar federicofederico00000000000000 Federico Brega MainWindow 0 0 580 436 Cyclograph :/icon/pixmaps/cyclograph.svg:/icon/pixmaps/cyclograph.svg QTabWidget::Rounded 0 true Tab 1 0 0 580 27 File ? Options &Kml server toolbar true 32 32 Qt::ToolButtonTextUnderIcon TopToolBarArea false :/qt-icons/pixmaps/open.png:/qt-icons/pixmaps/open.png &Open Ctrl+O false :/qt-icons/pixmaps/save.png:/qt-icons/pixmaps/save.png &Save Ctrl+S &Quit Ctrl+Q About true true geonames.org true earthtools.org true usgs.net :/qt-icons/pixmaps/new.png:/qt-icons/pixmaps/new.png &New Ctrl+N false :/icon/pixmaps/icon_add.png:/icon/pixmaps/icon_add.png Add Press + on the keyboard to add a check-point + false :/icon/pixmaps/icon_modify.png:/icon/pixmaps/icon_modify.png Edit Edit a check-point false :/icon/pixmaps/icon_delete.png:/icon/pixmaps/icon_delete.png Delete Press - on the keyboard to remove the selected check-point - false :/icon/pixmaps/icon_plot.png:/icon/pixmaps/icon_plot.png Plot Plot your slope false :/icon/pixmaps/icon_info.png:/icon/pixmaps/icon_info.png Info Informations about the slope Signal a bug :/icon/pixmaps/icon_ckml.png:/icon/pixmaps/icon_ckml.png &Create Kml Create Create Kml false :/qt-icons/pixmaps/save_as.png:/qt-icons/pixmaps/save_as.png Save &As Save as Ctrl+Alt+S &Preferences false :/icon/pixmaps/icon_map.png:/icon/pixmaps/icon_map.png Map Map Show the map of your slope cyclograph-1.9.1/qt_ui/form_slope_info.ui0000644000175000017500000001100512670627222022102 0ustar federicofederico00000000000000 charon66 Slope_Dialog 0 0 313 336 Slope Info :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png Name: s_name State: s_state Note: s_comment Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Author: E-mail: Average gradient: Max gradient: Height difference: Height gain: buttonBox accepted() Slope_Dialog accept() 276 199 157 224 buttonBox rejected() Slope_Dialog reject() 337 205 286 224 cyclograph-1.9.1/qt_ui/imports.py0000644000175000017500000000033413144674235020440 0ustar federicofederico00000000000000## imports are included from imports.py ## from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets from . import cyclograph_rc def cg_translate(context, text, **kwargs): return _(text) ## end of imports ## cyclograph-1.9.1/qt_ui/manage_cp.ui0000644000175000017500000000746312670627222020651 0ustar federicofederico00000000000000 manag_dlg 0 0 317 159 :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png QFormLayout::ExpandingFieldsGrow Distance: distance Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok Name: name Altitude: altitude distance altitude name buttonBox buttonBox accepted() manag_dlg accept() 293 116 157 147 buttonBox rejected() manag_dlg reject() 307 122 286 147 name returnPressed() manag_dlg accept() 182 85 158 73 distance returnPressed() altitude selectAll() 218 32 224 61 altitude returnPressed() name selectAll() 137 61 186 78 cyclograph-1.9.1/qt_ui/map.ui0000644000175000017500000000272413144674235017512 0ustar federicofederico00000000000000 map 0 0 800 550 484 0 map :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png Qt::Horizontal false QDialogButtonBox::Save QWebEngineView QWidget
PyQt5.QtWebEngineWidgets
1
cyclograph-1.9.1/qt_ui/numcps.ui0000644000175000017500000002552512670627222020243 0ustar federicofederico00000000000000 numcps_dlg 0 0 354 265 number of checkpoints :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png 0 TextLabel Automatic true buttonGroup Select the distance rate buttonGroup Select the number of checkpoints buttonGroup Use every point (discouraged) buttonGroup Start km 3 0.100000000000000 End km 3 0.100000000000000 Qt::Horizontal 40 20 Cancel Next message 0 0 0 0 true Qt::Horizontal QSlider::TicksAbove 10 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok TextLabel Qt::ImhFormattedNumbersOnly km 3 99.000000000000000 0.100000000000000 0 0 0 0 true Qt::Horizontal 10 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() numcps_dlg accept() 99 29 157 147 buttonBox rejected() numcps_dlg reject() 99 29 286 147 slider valueChanged(int) spinBox setValue(int) 98 20 54 20 spinBox valueChanged(int) slider setValue(int) 51 20 98 20 buttonBox_2 accepted() numcps_dlg accept() 99 29 176 115 buttonBox_2 rejected() numcps_dlg reject() 99 29 176 115 pushButton clicked() numcps_dlg reject() 246 201 307 229 cyclograph-1.9.1/qt_ui/page.ui0000644000175000017500000000307212670627222017643 0ustar federicofederico00000000000000 Frame_Page 0 0 558 399 Frame QFrame::StyledPanel QFrame::Raised Close QAbstractItemView::NoEditTriggers true QAbstractItemView::SelectRows Distance [km] Altitude [m] Name cyclograph-1.9.1/qt_ui/preference.ui0000644000175000017500000003471112670627222021051 0ustar federicofederico00000000000000 Pref_Dialog 0 0 409 372 Preferences :/icon/pixmaps/cyclograph.png:/icon/pixmaps/cyclograph.png Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults false 0 true General QFormLayout::ExpandingFieldsGrow 40 5 40 40 Visualization Orizzontal lines true 3D effect true Resolution 800 x 600 1024 x 768 1280 x 800 1600 x 1200 Theme Kml server geonames.org earthtools.org usgs.net Show info true Latest version Check online Show legend true Fonts QFormLayout::AllNonFixedFieldsGrow 40 5 40 40 Description 0 0 font | dim Gradient font | dim Title font | dim Gradient colors QFormLayout::AllNonFixedFieldsGrow 40 5 40 40 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 1 90.000000000000000 0.100000000000000 Author 40 20 59 17 Name: 40 70 59 17 E-mail: 100 20 161 27 100 70 161 27 buttonBox accepted() Pref_Dialog accept() 248 254 157 274 buttonBox rejected() Pref_Dialog reject() 316 260 286 274 cyclograph-1.9.1/CHANGELOG.txt0000644000175000017500000001343213152573343017300 0ustar federicofederico00000000000000Version 1.9.1: * Switched Qt interface to use webengine * Fix in .desktop file to run the interpreter specified in the script header * Build all the generated file from setup.py Version 1.9.0: * Moved gtk interface to webkit2 * Added BRouter service * Removed Open Route Service and OSRM because the web service is broken Version 1.8.1: * Improvements and bug fixes * Distance is always saved in meters * Fixed bug: check message box check which button is clicked * Fixed bug: made property animation working with PyQt > 5.5 Version 1.8.0: * Added legend * Ported to Qt5 * Ported to Python 3 * Added support for retina displays * improved cli options selection * Miscellaneous and bug fixes * Fixed svg generation without 3d effect Version 1.7.1: * Miscellaneous and bug fixes * Fixed bug: OpenLayers path still pointing to the old URL (html report) * Fixed bug: import with a given number of points broken in Qt interface * Corrected spelling error in the .desktop files Version 1.7.0: * Added support for YOURS service * It is now possible to import only a part of a path (range of distances) * Geonames service is working again (new API) * Changed OpenLayers version (fixes missing mouse clicks on the map) * Dropped gtk2 interface Version 1.6.1: * Cyclograph can import fitlog files with gps coordinates * Can now save map to image * Miscellaneous and bug fixes * Set map dimension equal to plot on html report * Added configuration option to disable check for the latest version. * Fixed bug: Fixed km bar drawing color for black theme in Qt gui * Fixed bug: Fixed Qt translation file not loaded * Fixed bug: Fixed average grad calc * Fixed bug: Fixed import of TCX files: no hardcoded indices * Fixed bug: Fixed slope import from osrm site * Fixed bug: mark created slopes as not saved Version 1.6.0: * Removed separation from import and open * New cgx format saves imported coordinates * Added map window * Added map within the html report * Auto-import gpx if no altitude is present * Miscellaneous and bug fixes * Case insensitive open routines Version 1.5.2: * Added blue theme * Added filter dialog to select the step between consecutive cps * Removed Google maps service * Added Open Route Service * Added OSRM * Miscellaneous and bug fixes * First release with a user manual Version 1.5.1: * Initial support for html report * Added height gain * Support for kml 2.2 ext (i.e. my tracks) * Miscellaneous and bug fixes * Resizable splitter in qt4 "create" window * New submodule structure * Fixed bug: Fixed haversine formula (for distant points) * Fixed bug: Fixed save as image for qt4 interface Version 1.5.0: * New grayscale theme. * Added pdf report about a slope. * Initial implementation of the gtk3 interface. Version 1.4.2: * Miscellaneous and bug fixes * Added some information within plot window. Version 1.4.1: * More granular distnace bar. * Support for slopes startig from distance grater than zero. * Support for drawing on Open Cycle Map and Open Street Map. * Miscellaneous and bug fixes * Changed accessing mode to Google Maps. * Fixed bug: Window size and maximization status correctely saved for the next session. Version 1.4.0: * Names are imported from gpx wayposints. * Added new theme. * All supported files can be read from standard input. * Added support to utf-8. * Removed google elevation. * Miscellaneous and bug fixes * Plot animated. * Icons little update. * Moved icons to a better location. * Workaround to geonames unstable service. * Main window remember its position and size. * Improvements in text layout in plots. Version 1.3.1: * Added preferences dialog. * Number of points shown in gpx is customizable. * User can select the resolution of the images saved. * Miscellaneous and bug fixes * Kml import is now more robust (i.e. it works much better with kml produced by saliteweb). Version 1.3.0: * Added new themes. * Now it is possible to create draw a route within CycloGraph. * A route found by Google Maps direction can be drawn by CycloGraph. * New version notifier. * Miscellaneous and bug fixes * Better handling of slopes and user-friendly behaviour when quitting. * Possible to easily report bugs. Version 1.2.1: * Miscellaneous and bug fixes * Migration from 1.x wx version is now smooth. * Fixed bug: Fixed a paint issue in Qt under X11 with composing window managers. Version 1.2.0: * A new 3D visualization mode is available in addition to the usual 2D. * New format support for gpx and crp (binary and textual) files. * New toolbar with icons to give a more user-friendly experience. * Added an help file, to guide te user on basic usage of CycloGraph. * Added the ability to signal bugs (link to sourceforge page). * A new GTK+ interfece has replaced the wx (Qt became default on Win and MacOs). * Miscellaneous and bug fixes * Command line usage is now simpler (see Help file). * Launching Cyclograph without the appropriate graphic library is told to the user in a understandable way. Version 1.1.1: * New icons. * Cyclograph can load Cyclomainac's xml. * Miscellaneous and bug fixes * Fixed bug: .sal file can't be opened. Version 1.1.0: * Added a Qt interface. * Added minimal command line interface. * Miscellaneous and bug fixes * Added more error dialog to make usage clearer. * Fixed bug: Cancelling a file opening and creating a new file. * Fixed bug: Export as image under some platforms. * Fixed bug: Segmentation fault when importing KML. Version 1.0.0: * Miscellaneous and bug fixes * New Release, many changes, almost everything is changed : Version 0.4.1: * Miscellaneous and bug fixes * Fixed bug: Fixed issue with icon in plot window. Version 0.4: * Initial release cyclograph-1.9.1/README0000644000175000017500000000461613144674235016137 0ustar federicofederico00000000000000Cyclograph ========== CycloGraph is an application for plotting the elevation profile of routes. Its main value is in the graphical visualization of the difficulty of a road, in term of slope, difference in height, etc. Plots like these are often used in cycling competitions, but are also useful in other sports, such as hiking or running. website: http://cyclograph.sourceforge.net System Requirements -------------------- * Python 3 If you want a graphical interface: * PyQt 5.x (pyqt5) or * gir-1.2-gtk-3.0 (we actively support only X11) and gir-1.2-webkit N.B.: The Widows installer already ships everything you need. Usage ----- * GUI - Graphical User Inteface `cyclograph`:: Tries any available graphical interface. `cyclograph --qt [-f FILE, --file=FILE]`:: Executes CycloGraph with qt5 graphic libraries. It is possible to open at startup any supported file. `cyclograph --gtk3 [-f FILE, --file=FILE]`:: Executes Cyclograph with gtk3 graphic libraries. It is possible to open at startup any supported file. * CLI - Command Line Interface `cyclograph -f FILE, --file=FILE`:: Executes Cyclograph with command line interface. This require a ciclograph supported file and prints on the standard output the svg image rapresenting the slope. Installation ------------ There are several ways to install CycloGraph, depending on witch package you have downloaded. cyclograph*.tar.gz ~~~~~~~~~~~~~~~~~~ You can just run cyclograph without installing it with python cyclograph/cyclograph [OPTIONS] If you really want to install it do the followings: 1) Ensure you have the required libraries installed (PyQt5 or gir gtk). 2) Extract the archive. 3) Go into the newly created dirctory and launch the installation process with: python setup.py install This will build CycloGraph for your machine and then install it. N.B.: This may require superuser priviledges. cyclograph*.deb ~~~~~~~~~~~~~~~ Use this if you are using Debian or a derived distribution like Ubuntu. For the installation procedure follow the documentation provided by your distibution. cyclograph*.exe ~~~~~~~~~~~~~~~ If you are a windows user you may prefer this executable to install a standalone version of cyclograph. cyclograp*.app ~~~~~~~~~~~~~~ Sadly we cannot provide a packaged app anymore. CycloGraph shoud still work on Mac OS X, if someone is interested in this please email us or the mailing-list. cyclograph-1.9.1/cyclograph-gtk3.desktop0000644000175000017500000000054213152470413021634 0ustar federicofederico00000000000000[Desktop Entry] Version=1.0 Icon=cyclograph Name=Cyclograph GTK3 GenericName=Plot your ride (GTK 3 interface) GenericName[it]=Disegna il tuo giro (interfaccia GTK 3) Comment=Plots slopes of bicycle routes. Comment[it]=Disegna altimetrie di percorsi. Exec=/usr/bin/cyclograph --gtk3 Terminal=false Type=Application Categories=Utility; StartupNotify=false cyclograph-1.9.1/cyclograph-qt.desktop0000644000175000017500000000053013152470401021402 0ustar federicofederico00000000000000[Desktop Entry] Version=1.0 Icon=cyclograph Name=Cyclograph Qt GenericName=Plot your ride (Qt interface) GenericName[it]=Disegna il tuo giro (interfaccia Qt) Comment=Plots slopes of bicycle routes. Comment[it]=Disegna altimetrie di percorsi. Exec=/usr/bin/cyclograph --qt Terminal=false Type=Application Categories=Utility; StartupNotify=false cyclograph-1.9.1/setup.py0000755000175000017500000001244713164776421016777 0ustar federicofederico00000000000000#!/usr/bin/env python3 from distutils.core import setup from distutils.command.build_py import build_py from distutils.command.clean import clean from distutils.cmd import Command import distutils.log from cyclograph.version import VERSION import glob import os import subprocess files = ["openlayers.html", "map.html", "google.css", "draw_on_map.js"] icons = glob.glob('pixmaps/32x32/*.png') + glob.glob('pixmaps/*svg') class pyrcc(Command): description = "Run pyrcc to generate pyqt resource file" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): command = ['/usr/bin/pyrcc5', '-o', 'cyclograph/qt/cyclograph_rc.py', 'qt_ui/cyclograph.qrc'] self.announce("Running: %s" % " ".join(command), level=distutils.log.INFO) #We set the seed to 0 as environmental variable to obtain #a reproducible build subprocess.check_call(command, env={'QT_HASH_SEED':'0'}) class genhtml(Command): description = "Generate html user manual from asciidoc file" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): command = ['asciidoc', '-b', 'html5', '--section-numbers', '-a', 'footer-style=none', 'doc/manual.txt'] subprocess.check_call(command) class pyuic(Command): description = "Generate python from designer ui files" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): command = ['./build.py'] #The script is designed to be run from the qt_ui directory subprocess.check_call(command, cwd='qt_ui') class msgfmt(Command): description = "Run gettext to generate .mo files" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): for lang in ['en', 'it']: filename = 'po/%s/LC_MESSAGES/cyclograph' % lang fnamein = filename + '.po' fnameout = filename + '.mo' subprocess.check_call(['msgfmt', fnamein, '-o', fnameout]) class generate_all(Command): description = "Run all the file generators" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): self.run_command("pyrcc") self.run_command("pyuic") self.run_command("html") self.run_command("msgfmt") class my_build_py(build_py): def run(self): nogeneratepath = os.path.join(os.path.dirname(__file__), 'no-generate.stamp') if not os.path.exists(nogeneratepath): self.run_command('generate') build_py.run(self) class my_clean(clean): def run(self): files = ['cyclograph/qt/cyclograph_rc.py', 'cyclograph/qt/ui_qt.py', 'doc/manual.html'] files += glob.glob('qt_ui/ui_*.py') files += ['po/en/LC_MESSAGES/cyclograph.mo', 'po/it/LC_MESSAGES/cyclograph.mo'] for f in files: try: os.remove(f) except OSError as e: pass #print(e) #run standard clean clean.run(self) setup(name = "cyclograph", version = VERSION, description = "route altimetry plotting application", author = "Federico Brega, Pierluigi Villani", author_email = "charon66@users.sourceforge.net", url = "http://cyclograph.sourceforge.net", license = "GPL v3 or later", packages = ['cyclograph', 'cyclograph.qt', 'cyclograph.gtk3'], cmdclass = {'build_py': my_build_py, 'clean': my_clean, 'generate': generate_all, 'pyrcc': pyrcc, 'pyuic': pyuic, 'msgfmt': msgfmt, 'html': genhtml}, package_data = {'cyclograph' : files }, scripts = ["cyclograph/cyclograph"], data_files = [('share/icons/hicolor/scalable/apps', ['pixmaps/cyclograph.svg']), ('share/icons/hicolor/16x16/apps', ['pixmaps/16x16/cyclograph.png']), ('share/icons/hicolor/22x22/apps', ['pixmaps/22x22/cyclograph.png']), ('share/icons/hicolor/24x24/apps', ['pixmaps/24x24/cyclograph.png']), ('share/icons/hicolor/32x32/apps', ['pixmaps/32x32/cyclograph.png']), ('share/icons/hicolor/48x48/apps', ['pixmaps/48x48/cyclograph.png']), ('share/icons/hicolor/256x256/apps', ['pixmaps/256x256/cyclograph.png']), ('share/cyclograph/icons', icons), ('share/locale/en/LC_MESSAGES', ['po/en/LC_MESSAGES/cyclograph.mo']), ('share/locale/it/LC_MESSAGES', ['po/it/LC_MESSAGES/cyclograph.mo']), ('share/applications', ['cyclograph-qt.desktop', 'cyclograph-gtk3.desktop']), ], long_description = \ """CycloGraph is an application for plotting the elevation profile of routes. Its main value is in the graphical visualization of the difficulty of a road, in term of slope, difference in height, etc. Plots like these are often used in cycling competitions, but are also useful in other sports, such as hiking or running.""" ) cyclograph-1.9.1/PKG-INFO0000644000175000017500000000117713215023054016335 0ustar federicofederico00000000000000Metadata-Version: 1.0 Name: cyclograph Version: 1.9.1 Summary: route altimetry plotting application Home-page: http://cyclograph.sourceforge.net Author: Federico Brega, Pierluigi Villani Author-email: charon66@users.sourceforge.net License: GPL v3 or later Description: CycloGraph is an application for plotting the elevation profile of routes. Its main value is in the graphical visualization of the difficulty of a road, in term of slope, difference in height, etc. Plots like these are often used in cycling competitions, but are also useful in other sports, such as hiking or running. Platform: UNKNOWN