veusz-1.15/0000755002344000001440000000000011734662466012611 5ustar jssusers00000000000000veusz-1.15/qtall.py0000644002344000001440000000240211734662204014264 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A convenience module to import both the used Qt symbols from.""" import sys import os # disable KDE specific dialog boxes as they are currently broken if sys.platform != 'win32' and sys.platform != 'darwin': os.environ['QT_PLATFORM_PLUGIN'] = 'none' from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.QtSvg import * from PyQt4.uic import loadUi veusz-1.15/embed.py0000644002344000001440000004521411734662204014233 0ustar jssusers00000000000000# A module for embedding Veusz within another python program # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """This module allows veusz to be embedded within other Python programs. For example: import time import numpy import veusz.embed as veusz g = veusz.Embedded('new win') g.To( g.Add('page') ) g.To( g.Add('graph') ) g.SetData('x', numpy.arange(20)) g.SetData('y', numpy.arange(20)**2) g.Add('xy') g.Zoom(0.5) time.sleep(60) g.Close() More than one embedded window can be opened at once """ import atexit import sys import os import os.path import struct import new import cPickle import socket import subprocess import time import uuid # check remote process has this API version API_VERSION = 1 def Bind1st(function, arg): """Bind the first argument of a given function to the given parameter.""" def runner(*args, **args2): return function( arg, *args, **args2 ) return runner def findOnPath(cmd): """Find a command on the system path, or None if does not exist.""" path = os.getenv('PATH', os.path.defpath) pathparts = path.split(os.path.pathsep) for dirname in pathparts: cmdtry = os.path.join(dirname, cmd) if os.path.isfile(cmdtry): return cmdtry return None class Embedded(object): """An embedded instance of Veusz. This embedded instance supports all the normal veusz functions """ remote = None def __init__(self, name = 'Veusz', copyof = None): """Initialse the embedded veusz window. name is the name of the window to show. This method creates a new thread to run Qt if necessary """ if not Embedded.remote: Embedded.startRemote() if not copyof: retval = self.sendCommand( (-1, '_NewWindow', (name,), {}) ) else: retval = self.sendCommand( (-1, '_NewWindowCopy', (name, copyof.winno), {}) ) self.winno, cmds = retval # add methods corresponding to Veusz commands for name, doc in cmds: func = Bind1st(self.runCommand, name) func.__doc__ = doc # set docstring func.__name__ = name # make name match what it calls method = new.instancemethod(func, Embedded) setattr(self, name, method) # assign to self # check API version is same try: remotever = self._apiVersion() except AttributeError: remotever = 0 if remotever != API_VERSION: raise RuntimeError("Remote Veusz instance reports version %i of" " API. This embed.py supports version %i." % (remotever, API_VERSION)) # define root object self.Root = WidgetNode(self, 'widget', '/') def StartSecondView(self, name = 'Veusz'): """Provides a second view onto the document of this window. Returns an Embedded instance """ return Embedded(name=name, copyof=self) def WaitForClose(self): """Wait for the window to close.""" # this is messy, polling for closure, but cleaner than doing # it in the remote client while not self.IsClosed(): time.sleep(0.1) @classmethod def makeSockets(cls): """Make socket(s) to communicate with remote process. Returns string to send to remote process """ if ( hasattr(socket, 'AF_UNIX') and hasattr(socket, 'socketpair') ): # convenient interface cls.sockfamily = socket.AF_UNIX sock, socket2 = socket.socketpair(cls.sockfamily, socket.SOCK_STREAM) sendtext = 'unix %i\n' % socket2.fileno() cls.socket2 = socket2 waitaccept = False else: # otherwise mess around with internet sockets # * This is required for windows, which doesn't have AF_UNIX # * It is required where socketpair is not supported cls.sockfamily = socket.AF_INET sock = socket.socket(cls.sockfamily, socket.SOCK_STREAM) sock.bind( ('localhost', 0) ) interface, port = sock.getsockname() sock.listen(1) sendtext = 'internet %s %i\n' % (interface, port) waitaccept = True return (sock, sendtext, waitaccept) @classmethod def makeRemoteProcess(cls): """Try to find veusz process for remote program.""" # here's where to look for embed_remote.py thisdir = os.path.dirname(os.path.abspath(__file__)) # build up a list of possible command lines to start the remote veusz if sys.platform == 'win32': # windows is a special case # we need to run embed_remote.py under pythonw.exe, not python.exe # look for the python windows interpreter on path findpython = findOnPath('pythonw.exe') if not findpython: # if it wasn't on the path, use sys.prefix instead findpython = os.path.join(sys.prefix, 'pythonw.exe') # look for veusz executable on path findexe = findOnPath('veusz.exe') if not findexe: try: # add the usual place as a guess :-( findexe = os.path.join(os.environ['ProgramFiles'], 'Veusz', 'veusz.exe') except KeyError: pass # here is the list of commands to try possiblecommands = [ [findpython, os.path.join(thisdir, 'veusz_main.py')], [findexe] ] else: # try embed_remote.py in this directory, veusz in this directory # or veusz on the path in order possiblecommands = [ [sys.executable, os.path.join(thisdir, 'veusz_main.py')], [os.path.join(thisdir, 'veusz')], [findOnPath('veusz')] ] # cheat and look for Veusz app for MacOS under the standard application # directory. I don't know how else to find it :-( if sys.platform == 'darwin': findbundle = findOnPath('Veusz.app') if findbundle: possiblecommands += [ [findbundle+'/Contents/MacOS/Veusz'] ] else: possiblecommands += [[ '/Applications/Veusz.app/Contents/MacOS/Veusz' ]] for cmd in possiblecommands: # only try to run commands that exist as error handling # does not work well when interfacing with OS (especially Windows) if ( None not in cmd and False not in [os.path.isfile(c) for c in cmd] ): try: # we don't use stdout below, but works around windows bug # http://bugs.python.org/issue1124861 cls.remote = subprocess.Popen(cmd + ['--embed-remote'], shell=False, bufsize=0, close_fds=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE) return except OSError: pass raise RuntimeError('Unable to find a veusz executable on system path') @classmethod def startRemote(cls): """Start remote process.""" cls.serv_socket, sendtext, waitaccept = cls.makeSockets() cls.makeRemoteProcess() stdin = cls.remote.stdin # send socket number over pipe stdin.write( sendtext ) # accept connection if necessary if waitaccept: cls.serv_socket, address = cls.serv_socket.accept() # Send a secret to the remote program by secure route and # check it comes back. This is to check that no program has # secretly connected on our port, which isn't really useful # for AF_UNIX sockets. secret = str(uuid.uuid4()) + '\n' stdin.write(secret) secretback = cls.readLenFromSocket(cls.serv_socket, len(secret)) if secret != secretback: raise RuntimeError, "Security between client and server broken" # packet length for command bytes cls.cmdlen = struct.calcsize('" % (self.__class__.__name__, self._path, self._type) def fromPath(self, path): """Return a new Node for the path given.""" wtype = self._ci.NodeType(path) if wtype == 'widget': return WidgetNode(self._ci, wtype, path) elif wtype == 'setting': return SettingNode(self._ci, wtype, path) else: return SettingGroupNode(self._ci, wtype, path) @property def path(self): """Veusz full path to node""" return self._path @property def type(self): """Type of node: 'widget', 'settinggroup', or 'setting'""" return self._type def _joinPath(self, child): """Return new path of child.""" if self._path == '/': return '/' + child else: return self._path + '/' + child def __getitem__(self, key): """Return a child widget, settinggroup or setting.""" if self._type != 'setting': try: return self.fromPath(self._joinPath(key)) except ValueError: pass raise KeyError, "%s does not have key or child '%s'" % ( self.__class__.__name__, key) def __getattr__(self, attr): """Return a child widget, settinggroup or setting.""" if self._type == 'setting': pass elif attr[:2] != '__': try: return self.fromPath(self._joinPath(attr)) except ValueError: pass raise AttributeError, "%s does not have attribute or child '%s'" % ( self.__class__.__name__, attr) # boring ways to get children of nodes @property def children(self): """Generator to get children as Nodes.""" for c in self._ci.NodeChildren(self._path): yield self.fromPath(self._joinPath(c)) @property def children_widgets(self): """Generator to get child widgets as Nodes.""" for c in self._ci.NodeChildren(self._path, types='widget'): yield self.fromPath(self._joinPath(c)) @property def children_settings(self): """Generator to get child settings as Nodes.""" for c in self._ci.NodeChildren(self._path, types='setting'): yield self.fromPath(self._joinPath(c)) @property def children_settinggroups(self): """Generator to get child settingsgroups as Nodes.""" for c in self._ci.NodeChildren(self._path, types='settinggroup'): yield self.fromPath(self._joinPath(c)) @property def childnames(self): """Get names of children.""" return self._ci.NodeChildren(self._path) @property def childnames_widgets(self): """Get names of children widgets.""" return self._ci.NodeChildren(self._path, types='widget') @property def childnames_settings(self): """Get names of child settings.""" return self._ci.NodeChildren(self._path, types='setting') @property def childnames_settinggroups(self): """Get names of child setting groups""" return self._ci.NodeChildren(self._path, types='settinggroup') @property def parent(self): """Return parent of node.""" if self._path == '/': raise TypeError, "Cannot get parent node of root node""" p = self._path.split('/')[:-1] if p == ['']: newpath = '/' else: newpath = '/'.join(p) return self.fromPath(newpath) @property def name(self): """Get name of node.""" if self._path == '/': return self._path else: return self._path.split('/')[-1] class SettingNode(Node): """A node which is a setting.""" def _getVal(self): """The value of a setting.""" if self._type == 'setting': return self._ci.Get(self._path) raise TypeError, "Cannot get value unless is a setting""" def _setVal(self, val): if self._type == 'setting': self._ci.Set(self._path, val) else: raise TypeError, "Cannot set value unless is a setting.""" val = property(_getVal, _setVal) @property def isreference(self): """Is this setting set to a reference to another setting?.""" ref = self._ci.ResolveReference(self._path) return bool(ref) def resolveReference(self): """If this is set to a reference to a setting, return a new SettingNode to the original setting. If there are a chain of references, follow them to the target. Returns None if this setting is not set to a reference. """ real = self._ci.ResolveReference(self._path) if not real: return None return self.fromPath(real) def setToReference(self, othernode): """Make this setting point to another setting, by creating a reference. References can be chained. Note that the absolute path is used to specify a reference, so moving affected widgets around will destroy the link.""" if not isinstance(othernode, SettingNode): raise ValueError, "othernode is not a SettingNode" self._ci.SetToReference(self._path, othernode._path) @property def settingtype(self): """Get the type of setting, which is a string.""" return self._ci.SettingType(self._path) class SettingGroupNode(Node): """A node containing a group of settings.""" pass class WidgetNode(Node): """A node pointing to a widget.""" @property def widgettype(self): """Get Veusz type of widget.""" return self._ci.WidgetType(self.path) def WalkWidgets(self, widgettype=None): """Generator to walk widget tree and get this widget and the widgets below this WidgetNode of type given. widgettype is a Veusz widget type name or None to get all widgets.""" if widgettype is None or self._ci.WidgetType(self._path) == widgettype: yield self for child in self.children_widgets: for w in child.WalkWidgets(widgettype=widgettype): yield w def Add(self, widgettype, *args, **args_opt): """Add a widget of the type given, returning the Node instance. """ args_opt['widget'] = self._path name = self._ci.Add(widgettype, *args, **args_opt) return WidgetNode( self._ci, 'widget', self._joinPath(name) ) def Rename(self, newname): """Renames widget to name given.""" if self._path == '/': raise RuntimeError, "Cannot rename root widget" self._ci.Rename(self._path, newname) self._path = '/'.join( self._path.split('/')[:-1] + [newname] ) def Action(self, action): """Applies action on widget.""" self._ci.Action(action, widget=self._path) def Remove(self): """Removes a widget and its children.""" self._ci.Remove(self._path) def Clone(self, newparent, newname=None): """Clone widget, placing at newparent. Uses newname if given. Returns new node.""" path = self._ci.CloneWidget(self._path, newparent._path, newname=newname) return WidgetNode( self._ci, 'widget', path ) veusz-1.15/plugins/0000755002344000001440000000000011734662466014272 5ustar jssusers00000000000000veusz-1.15/plugins/datasetplugin.py0000644002344000001440000014602711734662204017510 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Plugins for creating datasets.""" import numpy as N from itertools import izip import field import veusz.utils as utils try: import veusz.helpers.qtloops as qtloops except ImportError: pass # add an instance of your class to this list to be registered datasetpluginregistry = [] class DatasetPluginException(RuntimeError): """Raise this to report an error. """ pass def numpyCopyOrNone(data): """If data is None return None Otherwise return a numpy array corresponding to data.""" if data is None: return None return N.array(data, dtype=N.float64) # these classes are returned from dataset plugins class Dataset1D(object): """1D dataset for ImportPlugin or DatasetPlugin.""" def __init__(self, name, data=[], serr=None, perr=None, nerr=None): """1D dataset name: name of dataset data: data in dataset: list of floats or numpy 1D array serr: (optional) symmetric errors on data: list or numpy array perr: (optional) positive errors on data: list or numpy array nerr: (optional) negative errors on data: list or numpy array If errors are returned for data give serr or nerr and perr. nerr should be negative values if used. perr should be positive values if used. """ self.name = name self.update(data=data, serr=serr, perr=perr, nerr=nerr) def update(self, data=[], serr=None, perr=None, nerr=None): """Update values to those given.""" self.data = numpyCopyOrNone(data) self.serr = numpyCopyOrNone(serr) self.perr = numpyCopyOrNone(perr) self.nerr = numpyCopyOrNone(nerr) def _null(self): """Empty data contents.""" self.data = N.array([]) self.serr = self.perr = self.nerr = None def _makeVeuszDataset(self, manager): """Make a Veusz dataset from the plugin dataset.""" # need to do the import here as otherwise we get a loop import veusz.document as document return document.Dataset1DPlugin(manager, self) class Dataset2D(object): """2D dataset for ImportPlugin or DatasetPlugin.""" def __init__(self, name, data=[[]], rangex=None, rangey=None): """2D dataset. name: name of dataset data: 2D numpy array of values or list of lists of floats rangex: optional tuple with X range of data (min, max) rangey: optional tuple with Y range of data (min, max) """ self.name = name self.update(data=data, rangex=rangex, rangey=rangey) def update(self, data=[[]], rangex=None, rangey=None): self.data = N.array(data, dtype=N.float64) self.rangex = rangex self.rangey = rangey def _null(self): """Empty data contents.""" self.data = N.array([[]]) self.rangex = self.rangey = (0, 1) def _makeVeuszDataset(self, manager): """Make a Veusz dataset from the plugin dataset.""" import veusz.document as document return document.Dataset2DPlugin(manager, self) class DatasetDateTime(object): """Date-time dataset for ImportPlugin or DatasetPlugin.""" def __init__(self, name, data=[]): """A date dataset name: name of dataset data: list of datetime objects """ self.name = name self.update(data=data) def update(self, data=[]): self.data = N.array(data) @staticmethod def datetimeToFloat(datetimeval): """Return a python datetime object to the required float type.""" return utils.datetimeToFloat(datetimeval) @staticmethod def dateStringToFloat(text): """Try to convert an iso or local date time to the float type.""" return utils.dateStringToDate(text) @staticmethod def floatToDateTime(val): """Convert float format datetime to Python datetime.""" return utils.floatToDateTime(val) def _null(self): """Empty data contents.""" self.data = N.array([]) def _makeVeuszDataset(self, manager): """Make a Veusz dataset from the plugin dataset.""" import veusz.document as document return document.DatasetDatePlugin(manager, self) class DatasetText(object): """Text dataset for ImportPlugin or DatasetPlugin.""" def __init__(self, name, data=[]): """A text dataset name: name of dataset data: data in dataset: list of strings """ self.name = name self.update(data=data) def update(self, data=[]): self.data = list(data) def _null(self): """Empty data contents.""" self.data = [] def _makeVeuszDataset(self, manager): """Make a Veusz dataset from the plugin dataset.""" import veusz.document as document return document.DatasetTextPlugin(manager, self) class Constant(object): """Dataset to return to set a Veusz constant after import. This is only useful in an ImportPlugin, not a DatasetPlugin """ def __init__(self, name, val): """Map string value val to name. Convert float vals to strings first!""" self.name = name self.val = val class Function(object): """Dataset to return to set a Veusz function after import.""" def __init__(self, name, val): """Map string value val to name. name is "funcname(param,...)", val is a text expression of param. This is only useful in an ImportPlugin, not a DatasetPlugin """ self.name = name self.val = val # class to pass to plugin to give parameters class DatasetPluginHelper(object): """Helpers to get existing datasets for plugins.""" def __init__(self, doc): """Construct helper object to pass to DatasetPlugins.""" self._doc = doc @property def datasets1d(self): """Return list of existing 1D numeric datasets""" return [name for name, ds in self._doc.data.iteritems() if (ds.dimensions == 1 and ds.datatype == 'numeric')] @property def datasets2d(self): """Return list of existing 2D numeric datasets""" return [name for name, ds in self._doc.data.iteritems() if (ds.dimensions == 2 and ds.datatype == 'numeric')] @property def datasetstext(self): """Return list of existing 1D text datasets""" return [name for name, ds in self._doc.data.iteritems() if (ds.dimensions == 1 and ds.datatype == 'text')] @property def datasetsdatetime(self): """Return list of existing date-time datesets""" import veusz.document as document return [name for name, ds in self._doc.data.iteritems() if isinstance(ds, document.DatasetDateTime)] def evaluateExpression(self, expr, part='data'): """Return results of evaluating a 1D dataset expression. part is 'data', 'serr', 'perr' or 'nerr' - these are the dataset parts which are evaluated by the expression """ return self._doc.evalDatasetExpression(expr, part=part) def getDataset(self, name, dimensions=1): """Return numerical dataset object for name given. Please make sure that dataset data are not modified. name: name of dataset dimensions: number of dimensions dataset requires name not found: raise a DatasetPluginException dimensions not right: raise a DatasetPluginException """ import veusz.document as document try: ds = self._doc.data[name] except KeyError: raise DatasetPluginException("Unknown dataset '%s'" % name) if ds.dimensions != dimensions: raise DatasetPluginException( "Dataset '%s' does not have %i dimensions" % (name, dimensions)) if ds.datatype != 'numeric': raise DatasetPluginException( "Dataset '%s' is not a numerical dataset" % name) if isinstance(ds, document.DatasetDateTime): return DatasetDateTime(name, data=ds.data) elif ds.dimensions == 1: return Dataset1D(name, data=ds.data, serr=ds.serr, perr=ds.perr, nerr=ds.nerr) elif ds.dimensions == 2: return Dataset2D(name, ds.data, rangex=ds.xrange, rangey=ds.yrange) else: raise RuntimeError("Invalid number of dimensions in dataset") def getDatasets(self, names, dimensions=1): """Get a list of numerical datasets (of the dimension given).""" return [ self.getDataset(n, dimensions=dimensions) for n in names ] def getTextDataset(self, name): """Return a text dataset with name given. Do not modify this dataset. name not found: raise a DatasetPluginException """ try: ds = self._doc.data[name] except KeyError: raise DatasetPluginException("Unknown dataset '%s'" % name) if ds.datatype == 'text': return DatasetText(name, ds.data) raise DatasetPluginException("Dataset '%s' is not a text datset" % name) # internal object to synchronise datasets created by a plugin class DatasetPluginManager(object): """Manage datasets generated by plugin.""" def __init__(self, plugin, doc, fields): """Construct manager object. plugin - instance of plugin class doc - document instance fields - fields to pass to plugin """ self.plugin = plugin self.document = doc self.helper = DatasetPluginHelper(doc) self.fields = fields self.changeset = -1 self.setupDatasets() def setupDatasets(self): """Do initial construction of datasets.""" self.datasetnames = [] self.datasets = [] self.veuszdatasets = [] self.datasets = self.plugin.getDatasets(self.fields) for ds in self.datasets: self.datasetnames.append(ds.name) veuszds = ds._makeVeuszDataset(self) veuszds.document = self.document self.veuszdatasets.append(veuszds) def nullDatasets(self): """Clear out contents of datasets.""" for ds in self.datasets: ds._null() def saveToFile(self, fileobj): """Save command to load in plugin and parameters.""" args = [ repr(self.plugin.name), repr(self.fields) ] # look for renamed or deleted datasets names = {} for ds, dsname in izip( self.veuszdatasets, self.datasetnames ): try: currentname = self.document.datasetName(ds) except ValueError: # deleted currentname = None if currentname != dsname: names[dsname] = currentname if names: args.append( "datasetnames="+repr(names) ) fileobj.write( 'DatasetPlugin(%s)\n' % (', '.join(args)) ) def update(self, raiseerrors=False): """Update created datasets. if raiseerrors is True, raise an exception if there is an exeception when updating the dataset """ if self.document.changeset == self.changeset: return self.changeset = self.document.changeset # run the plugin with its parameters try: self.plugin.updateDatasets(self.fields, self.helper) except DatasetPluginException, ex: # this is for immediate notification if raiseerrors: raise # otherwise if there's an error, then log and null outputs self.document.log( unicode(ex) ) self.nullDatasets() class DatasetPlugin(object): """Base class for defining dataset plugins.""" # the plugin will get inserted into the menu in a hierarchy based on # the elements of this tuple menu = ('Base plugin',) name = 'Base plugin' author = '' description_short = '' description_full = '' # if the plugin takes no parameters, set this to False has_parameters = True def __init__(self): """Override this to declare a list of input fields if required.""" self.fields = [] def getDatasets(self, fields): """Override this to return a list of (empty) Dataset1D, Dataset2D and DatasetText objects to provide the initial names and type of datasets. These should be saved for updating in updateDatasets. fields: dict of results to the field objects given in self.fields raise a DatasetPluginException if there is a problem with fields """ return [] def updateDatasets(self, fields, helper): """Override this to update the dataset objects provided by this plugin. fields: dict of field results (also provided to setup) helper: DatasetPluginHelper object, to get other datasets in document raise a DatasetPluginException if there is a problem """ class _OneOutputDatasetPlugin(DatasetPlugin): """Simplify plugins which create one output with field ds_out.""" def getDatasets(self, fields): """Returns single output dataset (self.dsout).""" if fields['ds_out'] == '': raise DatasetPluginException('Invalid output dataset name') self.dsout = Dataset1D(fields['ds_out']) return [self.dsout] def errorBarType(ds): """Return type of error bars in list of datasets. 'none', 'symmetric', 'asymmetric' """ symerr = False for d in ds: if d.serr is not None: symerr = True elif d.perr is not None or d.nerr is not None: return 'asymmetric' if symerr: return 'symmetric' return 'none' def combineAddedErrors(inds, length): """Combine error bars from list of input dataset, adding errors squared (suitable for adding/subtracting).""" errortype = errorBarType(inds) serr = perr = nerr = None if errortype == 'symmetric': serr = N.zeros(length, dtype=N.float64) elif errortype == 'asymmetric': perr = N.zeros(length, dtype=N.float64) nerr = N.zeros(length, dtype=N.float64) for d in inds: f = N.isfinite(d.data) if errortype == 'symmetric' and d.serr is not None: serr[f] += d.serr[f]**2 elif errortype == 'asymmetric': if d.serr is not None: v = (d.serr[f])**2 perr[f] += v nerr[f] += v if d.perr is not None: perr[f] += (d.perr[f])**2 if d.nerr is not None: nerr[f] += (d.nerr[f])**2 if serr is not None: serr = N.sqrt(serr) if perr is not None: perr = N.sqrt(perr) if nerr is not None: nerr = -N.sqrt(nerr) return serr, perr, nerr def combineMultipliedErrors(inds, length, data): """Combine error bars from list of input dataset, adding fractional errors squared (suitable for multipling/dividing).""" errortype = errorBarType(inds) serr = perr = nerr = None if errortype == 'symmetric': serr = N.zeros(length, dtype=N.float64) elif errortype == 'asymmetric': perr = N.zeros(length, dtype=N.float64) nerr = N.zeros(length, dtype=N.float64) for d in inds: f = N.isfinite(d.data) if len(f) > length: f = f[:length] if errortype == 'symmetric' and d.serr is not None: serr[f] += (d.serr[f]/d.data[f])**2 elif errortype == 'asymmetric': if d.serr is not None: v = (d.serr[f]/d.data[f])**2 perr[f] += v nerr[f] += v if d.perr is not None: perr[f] += (d.perr[f]/d.data[f])**2 if d.nerr is not None: nerr[f] += (d.nerr[f]/d.data[f])**2 if serr is not None: serr = N.abs(N.sqrt(serr) * data) if perr is not None: perr = N.abs(N.sqrt(perr) * data) if nerr is not None: nerr = -N.abs(N.sqrt(nerr) * data) return serr, perr, nerr ########################################################################### ## Real plugins are below class MultiplyDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to scale a dataset.""" menu = ('Multiply', 'By constant',) name = 'Multiply' description_short = 'Multiply dataset by a constant' description_full = ('Multiply a dataset by a factor. ' 'Error bars are also scaled.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldFloat('factor', 'Factor', default=1.), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do scaling of dataset.""" ds_in = helper.getDataset(fields['ds_in']) f = fields['factor'] data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr data = data * f if serr is not None: serr = serr * f if perr is not None: perr = perr * f if nerr is not None: nerr = nerr * f self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class AddDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to add a constant to a dataset.""" menu = ('Add', 'Constant',) name = 'Add' description_short = 'Add a constant to a dataset' description_full = ('Add a dataset by adding a value. ' 'Error bars remain the same.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldFloat('value', 'Add value', default=0.), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do shifting of dataset.""" ds_in = helper.getDataset(fields['ds_in']) self.dsout.update(data = ds_in.data + fields['value'], serr=ds_in.serr, perr=ds_in.perr, nerr=ds_in.nerr) class ConcatenateDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to concatenate datasets.""" menu = ('Join', 'Concatenate',) name = 'Concatenate' description_short = 'Concatenate datasets' description_full = ('Concatenate datasets into single dataset.\n' 'Error bars are merged.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do concatenation of dataset.""" dsin = helper.getDatasets(fields['ds_in']) if len(dsin) == 0: raise DatasetPluginException("Requires one or more input datasets") # concatenate main data dstack = N.hstack([d.data for d in dsin]) sstack = pstack = nstack = None # what sort of error bars do we need? errortype = errorBarType(dsin) if errortype == 'symmetric': # symmetric and not asymmetric error bars sstack = [] for d in dsin: if d.serr is not None: sstack.append(d.serr) else: sstack.append(N.zeros(d.data.shape, dtype=N.float64)) sstack = N.hstack(sstack) elif errortype == 'asymmetric': # asymmetric error bars pstack = [] nstack = [] for d in dsin: p = n = N.zeros(d.data.shape, dtype=N.float64) if d.serr is not None: p, n = d.serr, -d.serr else: if d.perr is not None: p = d.perr if d.nerr is not None: n = d.nerr pstack.append(p) nstack.append(n) pstack = N.hstack(pstack) nstack = N.hstack(nstack) self.dsout.update(data=dstack, serr=sstack, perr=pstack, nerr=nstack) class InterleaveDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to interleave datasets.""" menu = ('Join', 'Element by element',) name = 'Interleave' description_short = 'Join datasets, interleaving element by element' description_full = ('Join datasets, interleaving element by element.\n' 'Error bars are merged.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do concatenation of dataset.""" dsin = helper.getDatasets(fields['ds_in']) if len(dsin) == 0: raise DatasetPluginException("Requires one or more input datasets") maxlength = max( [len(d.data) for d in dsin] ) def interleave(datasets): """This is complex to account for different length datasets.""" # stick in columns ds = [ N.hstack( (d, N.zeros(maxlength-len(d))) ) for d in datasets ] # which elements are valid good = [ N.hstack( (N.ones(len(d), dtype=N.bool), N.zeros(maxlength-len(d), dtype=N.bool)) ) for d in datasets ] intl = N.column_stack(ds).reshape(maxlength*len(datasets)) goodintl = N.column_stack(good).reshape(maxlength*len(datasets)) return intl[goodintl] # do interleaving data = interleave([d.data for d in dsin]) # interleave error bars errortype = errorBarType(dsin) serr = perr = nerr = None if errortype == 'symmetric': slist = [] for ds in dsin: if ds.serr is None: slist.append(N.zeros_like(ds.data)) else: slist.append(ds.serr) serr = interleave(slist) elif errortype == 'asymmetric': plist = [] nlist = [] for ds in dsin: if ds.serr is not None: plist.append(ds.serr) nlist.append(-ds.serr) else: if ds.perr is not None: plist.append(ds.perr) else: plist.append(N.zeros_like(ds.data)) if ds.nerr is not None: nlist.append(ds.nerr) else: nlist.append(N.zeros_like(ds.data)) perr = interleave(plist) nerr = interleave(nlist) # finally update self.dsout.update(data=data, serr=serr, nerr=nerr, perr=perr) class ChopDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to chop datasets.""" menu = ('Split', 'Chop',) name = 'Chop' description_short = 'Chop dataset part into new dataset' description_full = ('Chop out a section of a dataset. Give starting ' 'index of data and number of datapoints to take.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldInt('start', 'Starting index (from 1)', default=1), field.FieldInt('num', 'Maximum number of datapoints', default=1), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do chopping of dataset.""" ds_in = helper.getDataset(fields['ds_in']) start = fields['start'] num = fields['num'] data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr # chop the data data = data[start-1:start-1+num] if serr is not None: serr = serr[start-1:start-1+num] if perr is not None: perr = perr[start-1:start-1+num] if nerr is not None: nerr = nerr[start-1:start-1+num] self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class PartsDatasetPlugin(DatasetPlugin): """Dataset plugin to split datasets into parts.""" menu = ('Split', 'Parts',) name = 'Parts' description_short = 'Split dataset into equal-size parts' description_full = ('Split dataset into equal-size parts. ' 'The parts will differ in size if the dataset ' 'cannot be split equally.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldDatasetMulti('ds_out', 'Output datasets'), ] def getDatasets(self, fields): """Get output datasets.""" self.dsout = [] for d in fields['ds_out']: if d.strip() != '': self.dsout.append( Dataset1D(d.strip()) ) if len(self.dsout) == 0: raise DatasetPluginException("Needs at least one output dataset") return self.dsout def updateDatasets(self, fields, helper): """Do chopping of dataset.""" ds_in = helper.getDataset(fields['ds_in']) data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr plen = float(len(data)) / len(self.dsout) for i, ds in enumerate(self.dsout): minv, maxv = int(plen*i), int(plen*(i+1)) pserr = pperr = pnerr = None pdata = data[minv:maxv] if serr is not None: pserr = serr[minv:maxv] if perr is not None: pperr = perr[minv:maxv] if nerr is not None: pnerr = nerr[minv:maxv] ds.update(data=pdata, serr=pserr, perr=pperr, nerr=pnerr) class ThinDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to thin datasets.""" menu = ('Split', 'Thin',) name = 'Thin' description_short = 'Select data points at intervals from dataset' description_full = ('Select data points at intervals from dataset ' 'to create new dataset') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldInt('start', 'Starting index (from 1)', default=1), field.FieldInt('interval', 'Interval between data points', default=1), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do thinning of dataset.""" ds_in = helper.getDataset(fields['ds_in']) start = fields['start'] interval = fields['interval'] data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr data = data[start-1::interval] if serr is not None: serr = serr[start-1::interval] if perr is not None: perr = perr[start-1::interval] if nerr is not None: nerr = nerr[start-1::interval] self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class MeanDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to mean datasets together.""" menu = ('Compute', 'Mean of datasets',) name = 'Mean' description_short = 'Compute mean of datasets' description_full = ('Compute mean of multiple datasets to create ' 'a single dataset.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Compute means of dataset.""" inds = helper.getDatasets(fields['ds_in']) if len(inds) == 0: raise DatasetPluginException("Requires one or more input datasets") maxlength = max( [len(d.data) for d in inds] ) # mean data (only use finite values) tot = N.zeros(maxlength, dtype=N.float64) num = N.zeros(maxlength, dtype=N.int) for d in inds: f = N.isfinite(d.data) tot[f] += d.data[f] num[f] += 1 data = tot / num def averageError(errtype, fallback=None): """Get average for an error value.""" tot = N.zeros(maxlength, dtype=N.float64) num = N.zeros(maxlength, dtype=N.int) for d in inds: vals = getattr(d, errtype) if vals is None and fallback: vals = getattr(d, fallback) # add values if not missing if vals is not None: f = N.isfinite(vals) tot[f] += (vals[f]) ** 2 num[f] += 1 else: # treat as zero errors if missing errors num[:len(d.data)] += 1 return N.sqrt(tot) / num # do error bar handling serr = perr = nerr = None errortype = errorBarType(inds) if errortype == 'symmetric': serr = averageError('serr') elif errortype == 'asymmetric': perr = averageError('perr', fallback='serr') nerr = -averageError('nerr', fallback='serr') self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class AddDatasetsPlugin(_OneOutputDatasetPlugin): """Dataset plugin to mean datasets together.""" menu = ('Add', 'Datasets',) name = 'Add Datasets' description_short = 'Add two or more datasets together' description_full = ('Add datasets together to make a single dataset. ' 'Error bars are combined.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Compute means of dataset.""" inds = helper.getDatasets(fields['ds_in']) if len(inds) == 0: raise DatasetPluginException("Requires one or more input datasets") maxlength = max( [len(d.data) for d in inds] ) # add data where finite data = N.zeros(maxlength, dtype=N.float64) anyfinite = N.zeros(maxlength, dtype=N.bool) for d in inds: f = N.isfinite(d.data) data[f] += d.data[f] anyfinite[f] = True data[N.logical_not(anyfinite)] = N.nan # handle error bars serr, perr, nerr = combineAddedErrors(inds, maxlength) # update output dataset self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class SubtractDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to subtract two datasets.""" menu = ('Subtract', 'Datasets',) name = 'Subtract Datasets' description_short = 'Subtract two datasets' description_full = ('Subtract two datasets. ' 'Combined error bars are also calculated.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in1', 'Input dataset 1'), field.FieldDataset('ds_in2', 'Input dataset 2'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do scaling of dataset.""" dsin1 = helper.getDataset(fields['ds_in1']) dsin2 = helper.getDataset(fields['ds_in2']) minlength = min( len(dsin1.data), len(dsin2.data) ) data = dsin1.data[:minlength] - dsin2.data[:minlength] # computing error bars is non trivial! serr = perr = nerr = None errortype = errorBarType([dsin1, dsin2]) if errortype == 'symmetric': serr1 = serr2 = 0 if dsin1.serr is not None: serr1 = dsin1.serr[:minlength] if dsin2.serr is not None: serr2 = dsin2.serr[:minlength] serr = N.sqrt(serr1**2 + serr2**2) elif errortype == 'asymmetric': perr1 = perr2 = nerr1 = nerr2 = 0 if dsin1.serr is not None: perr1 = nerr1 = dsin1.serr[:minlength] else: if dsin1.perr is not None: perr1 = dsin1.perr[:minlength] if dsin1.nerr is not None: nerr1 = dsin1.nerr[:minlength] if dsin2.serr is not None: perr2 = nerr2 = dsin2.serr[:minlength] else: if dsin2.perr is not None: perr2 = dsin2.perr[:minlength] if dsin2.nerr is not None: nerr2 = dsin2.nerr[:minlength] perr = N.sqrt(perr1**2 + nerr2**2) nerr = -N.sqrt(nerr1**2 + perr2**2) self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class SubtractMeanDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to subtract mean from dataset.""" menu = ('Subtract', 'Mean',) name = 'Subtract Mean' description_short = 'Subtract mean from dataset' description_full = ('Subtract mean from dataset,' ' optionally dividing by standard deviation.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset 1'), field.FieldBool('divstddev', 'Divide by standard deviation'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do scaling of dataset.""" dsin = helper.getDataset(fields['ds_in']) vals = dsin.data mean = vals[N.isfinite(vals)].mean() vals = vals - mean if fields['divstddev']: vals /= vals[N.isfinite(vals)].std() self.dsout.update( data=vals, serr=dsin.serr, perr=dsin.perr, nerr=dsin.nerr) class SubtractMinimumDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to subtract minimum from dataset.""" menu = ('Subtract', 'Minimum',) name = 'Subtract Minimum' description_short = 'Subtract minimum from dataset' description_full = 'Subtract the minimum value from a dataset' def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset 1'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Do scaling of dataset.""" dsin = helper.getDataset(fields['ds_in']) vals = dsin.data minval = vals[N.isfinite(vals)].min() vals = vals - minval self.dsout.update( data=vals, serr=dsin.serr, perr=dsin.perr, nerr=dsin.nerr) class MultiplyDatasetsPlugin(_OneOutputDatasetPlugin): """Dataset plugin to multiply two or more datasets.""" menu = ('Multiply', 'Datasets',) name = 'Multiply Datasets' description_short = 'Multiply two or more datasets' description_full = ('Multiply two or more datasets. ' 'Combined error bars are also calculated.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Multiply the datasets.""" names = fields['ds_in'] inds = [ helper.getDataset(d) for d in names ] maxlength = max( [d.data.shape[0] for d in inds] ) # output data and where data is finite data = N.ones(maxlength, dtype=N.float64) anyfinite = N.zeros(maxlength, dtype=N.bool) for d in inds: f = N.isfinite(d.data) anyfinite[f] = True data[f] *= d.data[f] # where always NaN, make NaN data[N.logical_not(anyfinite)] = N.nan # get error bars serr, perr, nerr = combineMultipliedErrors(inds, maxlength, data) self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class DivideDatasetsPlugin(_OneOutputDatasetPlugin): """Dataset plugin to divide two datasets.""" menu = ('Divide', 'Datasets',) name = 'Divide Datasets' description_short = ('Compute ratio or fractional difference' ' between two datasets') description_full = ('Divide or compute fractional difference' ' between two datasets') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in1', 'Input dataset 1'), field.FieldDataset('ds_in2', 'Input dataset 2'), field.FieldBool('frac', 'Compute fractional difference', default=False), field.FieldDataset('ds_out', 'Output dataset name'), ] def updateDatasets(self, fields, helper): """Compute ratio.""" inds1 = helper.getDataset( fields['ds_in1'] ) inds2 = helper.getDataset( fields['ds_in2'] ) length = min( len(inds1.data), len(inds2.data) ) # compute ratio data = inds1.data[:length] / inds2.data[:length] # get error bars serr, perr, nerr = combineMultipliedErrors([inds1, inds2], length, data) # convert to fractional difference (if reqd) if fields['frac']: data -= 1 self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class ExtremesDatasetPlugin(DatasetPlugin): """Dataset plugin to get extremes of dataset.""" menu = ('Compute', 'Dataset extremes',) name = 'Extremes' description_short = 'Compute extreme values of input datasets' description_full = ('Compute extreme values of input datasets. Creates ' 'minimum and maximum datasets.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDatasetMulti('ds_in', 'Input datasets'), field.FieldBool('errorbars', 'Include error bars'), field.FieldDataset('ds_min', 'Output minimum dataset (optional)'), field.FieldDataset('ds_max', 'Output maximum dataset (optional)'), field.FieldDataset('ds_errorbar', 'Output range as error bars ' 'in dataset (optional)'), ] def getDatasets(self, fields): """Returns output dataset.""" dsout = [] self.dsmin = self.dsmax = self.dserror = None if fields['ds_min'] != '': self.dsmin = Dataset1D(fields['ds_min']) dsout.append(self.dsmin) if fields['ds_max'] != '': self.dsmax = Dataset1D(fields['ds_max']) dsout.append(self.dsmax) if fields['ds_errorbar'] != '': self.dserror = Dataset1D(fields['ds_errorbar']) dsout.append(self.dserror) if not dsout: raise DatasetPluginException('Provide at least one output dataset') return dsout def updateDatasets(self, fields, helper): """Compute extremes of datasets.""" names = fields['ds_in'] inds = [ helper.getDataset(d) for d in names ] maxlength = max( [d.data.shape[0] for d in inds] ) minvals = N.zeros(maxlength, dtype=N.float64) + 1e100 maxvals = N.zeros(maxlength, dtype=N.float64) - 1e100 anyfinite = N.zeros(maxlength, dtype=N.bool) for d in inds: f = N.isfinite(d.data) anyfinite[f] = True v = d.data if fields['errorbars']: if d.serr is not None: v = v - d.serr elif d.nerr is not None: v = v + d.nerr minvals[f] = N.min( (minvals[f], v[f]), axis=0 ) v = d.data if fields['errorbars']: if d.serr is not None: v = v + d.serr elif d.perr is not None: v = v + d.perr maxvals[f] = N.max( (maxvals[f], v[f]), axis=0 ) minvals[N.logical_not(anyfinite)] = N.nan maxvals[N.logical_not(anyfinite)] = N.nan if self.dsmin is not None: self.dsmin.update(data=minvals) if self.dsmax is not None: self.dsmax.update(data=maxvals) if self.dserror is not None: # compute mean and look at differences from it tot = N.zeros(maxlength, dtype=N.float64) num = N.zeros(maxlength, dtype=N.int) for d in inds: f = N.isfinite(d.data) tot[f] += d.data[f] num[f] += 1 mean = tot / num self.dserror.update(data=mean, nerr=minvals-mean, perr=maxvals-mean) class CumulativePlugin(_OneOutputDatasetPlugin): """Compute cumulative values.""" menu = ('Compute', 'Cumulative value',) name = 'Cumulative' description_short = 'Compute the cumulative value of a dataset' description_full = ('Compute the cumulative value of a dataset. ' ' Error bars are combined.\n' 'Default behaviour is to accumulate from start.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldBool('fromend', 'Compute cumulative value from end'), field.FieldDataset('ds_out', 'Output dataset'), ] def updateDatasets(self, fields, helper): """Do accumulation.""" ds_in = helper.getDataset(fields['ds_in']) fromend = fields['fromend'] def cumsum(v): """Compute cumulative, handing nans and reverse.""" v = N.array(v) if fromend: v = v[::-1] v[ N.logical_not(N.isfinite(v)) ] = 0. c = N.cumsum(v) if fromend: c = c[::-1] return c # compute cumulative values data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr data = cumsum(data) if serr is not None: serr = N.sqrt( cumsum(serr**2) ) if perr is not None: perr = N.sqrt( cumsum(perr**2) ) if nerr is not None: nerr = -N.sqrt( cumsum(nerr**2) ) self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class DemultiplexPlugin(DatasetPlugin): """Dataset plugin to split a dataset into multiple datasets, element-by-element.""" menu = ('Split', 'Element by element',) name = 'Demultiplex' description_short = 'Split dataset into multiple datasets element-by-element' description_full = ('Split dataset into multiple datasets on an ' 'element-by-element basis.\n' 'e.g. 1, 2, 3, 4, 5, 6 could be converted to ' '1, 3, 5 and 2, 4, 6.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldDatasetMulti('ds_out', 'Output datasets'), ] def getDatasets(self, fields): """Returns demuxed output datasets.""" names = [n.strip() for n in fields['ds_out'] if n.strip() != ''] if len(names) == 0: raise DatasetPluginException('Requires at least one output dataset') self.ds_out = [ Dataset1D(n) for n in names ] return self.ds_out def updateDatasets(self, fields, helper): """Compute means of dataset.""" ds_in = helper.getDataset( fields['ds_in'] ) num = len(self.ds_out) for i, ds in enumerate(self.ds_out): data = ds_in.data[i::num] serr = nerr = perr = None if ds_in.serr is not None: serr = ds_in.serr[i::num] if ds_in.perr is not None: perr = ds_in.perr[i::num] if ds_in.nerr is not None: nerr = ds_in.nerr[i::num] ds.update(data=data, serr=serr, perr=perr, nerr=nerr) class PolarToCartesianPlugin(DatasetPlugin): """Convert from r,theta to x,y coordinates.""" menu = ('Convert', 'Polar to Cartesian',) name = 'PolarToCartesian' description_short = u'Convert r,θ coordinates to x,y coordinates' description_full = (u'Convert r,θ coordinates to x,y coordinates.\n' u'Error bars are ignored.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('r_in', 'Input dataset (r)'), field.FieldDataset('theta_in', u'Input dataset (θ)'), field.FieldCombo('units', 'Angular units', items=('radians', 'degrees'), editable=False), field.FieldDataset('x_out', 'Output dataset (x)'), field.FieldDataset('y_out', 'Output dataset (y)'), ] def getDatasets(self, fields): """Returns x and y output datasets.""" if fields['x_out'] == '': raise DatasetPluginException('Invalid output x dataset name') if fields['y_out'] == '': raise DatasetPluginException('Invalid output y dataset name') self.x_out = Dataset1D(fields['x_out']) self.y_out = Dataset1D(fields['y_out']) return [self.x_out, self.y_out] def updateDatasets(self, fields, helper): """Compute means of dataset.""" ds_r = helper.getDataset( fields['r_in'] ).data ds_theta = helper.getDataset( fields['theta_in'] ).data if fields['units'] == 'degrees': # convert to radians ds_theta = ds_theta * (N.pi / 180.) x = ds_r * N.cos(ds_theta) y = ds_r * N.sin(ds_theta) self.x_out.update(data=x) self.y_out.update(data=y) class FilterDatasetPlugin(_OneOutputDatasetPlugin): """Dataset plugin to filter a dataset using an expression.""" menu = ('Filter', 'Expression',) name = 'FilterExpression' description_short = 'Filter a dataset using an expression' description_full = ('Filter a dataset using an expression, ' 'e.g. "x>10" or "(x>1) & (y<2)"') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldText('filter', 'Filter expression'), field.FieldBool('replacenan', 'Replace excluded points by NaN\n' '(indicate missing points)', default=False), field.FieldDataset('ds_out', 'Output dataset'), ] def updateDatasets(self, fields, helper): """Do shifting of dataset.""" ds_in = helper.getDataset(fields['ds_in']) filt = helper.evaluateExpression(fields['filter']) data, serr, perr, nerr = ds_in.data, ds_in.serr, ds_in.perr, ds_in.nerr try: if fields['replacenan']: # replace bad points with nan data = data.copy() data[N.logical_not(filt)] = N.nan else: # just select good points data = data[filt] if serr is not None: serr = serr[filt] if perr is not None: perr = perr[filt] if nerr is not None: nerr = nerr[filt] except: raise DatasetPluginException("Error filtering dataset") self.dsout.update(data=data, serr=serr, perr=perr, nerr=nerr) class MovingAveragePlugin(_OneOutputDatasetPlugin): """Compute moving average for dataset.""" menu = ('Filtering', 'Moving Average',) name = 'MovingAverage' description_short = 'Compute moving average for regularly spaced data' description_full = ('Compute moving average for regularly spaced data.' 'Average is computed either\nside of each data point ' 'by number of points given.') def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_in', 'Input dataset'), field.FieldInt('width', 'Points either side of point to average', default=1, minval=0), field.FieldBool('weighterrors', 'Weight by error bars', default=True), field.FieldDataset('ds_out', 'Output dataset'), ] def updateDatasets(self, fields, helper): """Do shifting of dataset.""" ds_in = helper.getDataset(fields['ds_in']) weights = None if fields['weighterrors']: if ds_in.serr is not None: weights = 1. / ds_in.serr**2 elif ds_in.perr is not None and ds_in.nerr is not None: weights = 1. / ( (ds_in.perr**2+ds_in.nerr**2)/2. ) width = fields['width'] data = qtloops.rollingAverage(ds_in.data, weights, width) self.dsout.update(data=data) class LinearInterpolatePlugin(_OneOutputDatasetPlugin): """Do linear interpolation of data.""" menu = ('Filtering', 'Linear interpolation',) name = 'LinearInterpolation' description_short = 'Linear interpolation of x,y data' description_full = ("Compute linear interpolation of x,y data.\n" "Given datasets for y = f(x), compute y' = f(x'), " "using linear interpolation.\n" "Assumes x dataset increases in value.") def __init__(self): """Define fields.""" self.fields = [ field.FieldDataset('ds_x', 'Input dataset x'), field.FieldDataset('ds_y', 'Input dataset y'), field.FieldDataset('ds_xprime', "Input dataset x'"), field.FieldBool('edgenan', 'Use nan for values outside x range'), field.FieldDataset('ds_out', "Output dataset y'"), ] def updateDatasets(self, fields, helper): """Do shifting of dataset.""" ds_x = helper.getDataset(fields['ds_x']).data ds_y = helper.getDataset(fields['ds_y']).data ds_xprime = helper.getDataset(fields['ds_xprime']).data minlenin = min( len(ds_x), len(ds_y) ) pad = None if fields['edgenan']: pad = N.nan interpol = N.interp(ds_xprime, ds_x[:minlenin], ds_y[:minlenin], left=pad, right=pad) self.dsout.update(data=interpol) datasetpluginregistry += [ AddDatasetPlugin, AddDatasetsPlugin, SubtractDatasetPlugin, SubtractMeanDatasetPlugin, SubtractMinimumDatasetPlugin, MultiplyDatasetPlugin, MultiplyDatasetsPlugin, DivideDatasetsPlugin, MeanDatasetPlugin, ExtremesDatasetPlugin, CumulativePlugin, ConcatenateDatasetPlugin, InterleaveDatasetPlugin, ChopDatasetPlugin, PartsDatasetPlugin, DemultiplexPlugin, ThinDatasetPlugin, PolarToCartesianPlugin, FilterDatasetPlugin, MovingAveragePlugin, LinearInterpolatePlugin, ] veusz-1.15/plugins/field.py0000644002344000001440000003356711734662204015733 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Data entry fields for plugins.""" import veusz.qtall as qt4 import veusz.utils as utils import veusz.setting as setting class Field(object): """A class to represent an input field on the dialog or command line.""" def __init__(self, name, descr=None, default=None): """name: name of field descr: description to show to user default: default value.""" self.name = name if descr: self.descr = descr else: self.descr = name self.default = default def makeControl(self, doc, currentwidget): """Create a set of controls for field.""" return None def setControlVal(self, controls, val): """Update control's value to val.""" pass def getControlResults(self, cntrls): """Get result from created contrls.""" return None class FieldText(Field): """Text entry on the dialog.""" def makeControl(self, doc, currentwidget): l = qt4.QLabel(self.descr) e = qt4.QLineEdit() if self.default: e.setText(self.default) return (l, e) def setControlVal(self, controls, val): controls[1].setText(val) def getControlResults(self, cntrls): return unicode( cntrls[1].text() ) class FieldCombo(Field): """Drop-down combobox on dialog.""" def __init__(self, name, descr=None, default=None, items=(), editable=True): """name: name of field descr: description to show to user default: default value items: items in drop-down box editable: whether user can enter their own value.""" Field.__init__(self, name, descr=descr, default=default) self.items = items self.editable = editable def makeControl(self, doc, currentwidget): l = qt4.QLabel(self.descr) c = qt4.QComboBox() c.addItems(self.items) c.setEditable(bool(self.editable)) if self.default: self.setControlVal((l, c), self.default) return (l, c) def setControlVal(self, controls, val): """Update value to val.""" if self.editable: controls[1].setEditText(val) else: controls[1].setCurrentIndex(controls[1].findText(val)) def getControlResults(self, cntrls): return unicode( cntrls[1].currentText() ) class _WidgetCombo(qt4.QComboBox): """Combo box for selecting widgets.""" def __init__(self, doc, widgettypes, default): """doc: Veusz document widgettypes: set of allowed widgettypes or empty for all default: default path.""" qt4.QComboBox.__init__(self) self.doc = doc self.widgettypes = widgettypes self.default = default self.updateWidgets() self.connect(doc, qt4.SIGNAL("sigModified"), self.updateWidgets) def _iterateWidgets(self, comboitems, paths, widget, level): """Walk widget tree recursively. Adds name onto a list of strings (comboitems) Adds path to widget onto list of paths (paths) """ if not self.widgettypes or widget.typename in self.widgettypes: comboitems.append(' '*level + widget.name) paths.append(widget.path) for w in widget.children: self._iterateWidgets(comboitems, paths, w, level+1) def updateWidgets(self): """Update combo with new widgets.""" self.paths = [] # veusz widget paths of items comboitems = [] # names of items (with tree spacing) self._iterateWidgets(comboitems, self.paths, self.doc.basewidget, 0) if self.count() == 0: # first time around add default to get it selected, yuck :-( try: idx = self.paths.index(self.default) self.addItem( comboitems[idx] ) except ValueError: pass utils.populateCombo(self, comboitems) def getWidgetPath(self): """Get path of selected widget.""" return self.paths[self.currentIndex()] class FieldWidget(Field): """Drop-down combobox for selecting widgets.""" def __init__(self, name, descr=None, default='/', widgettypes=set()): """name: name of field descr: description to show to user default: default value - set to '' to get current widget.""" Field.__init__(self, name, descr=descr, default=default) self.widgettypes = widgettypes def makeControl(self, doc, currentwidget): default = self.default if default == '': default = currentwidget l = qt4.QLabel(self.descr) c = _WidgetCombo(doc, self.widgettypes, default) return (l, c) def setControlVal(self, cntrls, val): controls[1].setCurrentIndex(c.findText(val)) def getControlResults(self, cntrls): return cntrls[1].getWidgetPath() class _FieldSetting(Field): """Field using a setting internally to avoid code duplication. Designed to be subclassed.""" def __init__(self, settingkls, name, descr=None, default='', setnparams = {}): Field.__init__(self, name, descr=descr, default=default) self.default = default self.setn = settingkls(name, default, **setnparams) def makeControl(self, doc, currentwidget): """Use setting makeControl method to make control.""" self.setn.parent = self # setting looks to parent for document self.setn.set(self.default) self.document = doc l = qt4.QLabel(self.descr) c = self.setn.makeControl(None) def updateval(cntrl, setn, val): setn.set(val) # if control changes setting, update setting c.connect(c, qt4.SIGNAL('settingChanged'), updateval) return (l, c) def setControlVal(self, cntrls, val): self.setn.set(val) def getDocument(self): """This is used by settings to get their document.""" return self.document def getControlResults(self, cntrls): """Get result from setting.""" return self.setn.get() class FieldBool(_FieldSetting): """A true/false value using a check box.""" def __init__(self, name, descr=None, default=False): _FieldSetting.__init__(self, setting.Bool, name, descr=descr, default=default) class FieldInt(_FieldSetting): """An integer number field.""" def __init__(self, name, descr=None, default=0, minval=-9999999, maxval=9999999): """name: name of field descr: description to show to user default: default value. minval and maxval: minimum and maximum integers """ _FieldSetting.__init__(self, setting.Int, name, descr=descr, default=default, setnparams={'minval': minval, 'maxval': maxval}) class FieldFloat(_FieldSetting): """A floating point number field.""" def __init__(self, name, descr=None, default=None, minval=-1e99, maxval=1e99): """name: name of field descr: description to show to user default: default value. minval and maxval: minimum and maximum values """ _FieldSetting.__init__(self, setting.Float, name, descr=descr, default=default, setnparams={'minval': minval, 'maxval': maxval}) class FieldColor(_FieldSetting): """Field for selecting a color - returns #rrggbb string.""" def __init__(self, name, descr=None, default='black'): _FieldSetting.__init__(self, setting.Color, name, descr=descr, default=default) class FieldFillStyle(_FieldSetting): """Field for selecting fill styles - returns a string.""" def __init__(self, name, descr=None, default='solid'): _FieldSetting.__init__(self, setting.FillStyle, name, descr=descr, default=default) class FieldLineStyle(_FieldSetting): """Field for selecting line styles - returns a string.""" def __init__(self, name, descr=None, default='solid'): _FieldSetting.__init__(self, setting.LineStyle, name, descr=descr, default=default) class FieldMarker(_FieldSetting): """Field for selecting a marker type. Returns a string """ def __init__(self, name, descr=None, default='circle'): _FieldSetting.__init__(self, setting.Marker, name, descr=descr, default=default) class FieldArrow(_FieldSetting): """Field for selecting an arrow type. Returns a string """ def __init__(self, name, descr=None, default='none'): _FieldSetting.__init__(self, setting.Arrow, name, descr=descr, default=default) class FieldErrorStyle(_FieldSetting): """Field for selecting an error bar style Returns a string """ def __init__(self, name, descr=None, default='bar'): _FieldSetting.__init__(self, setting.ErrorStyle, name, descr=descr, default=default) class FieldDistance(_FieldSetting): """Field for selecting a veusz-style distance, e.g. '1pt'. Returns a string """ def __init__(self, name, descr=None, default='1pt'): _FieldSetting.__init__(self, setting.Distance, name, descr=descr, default=default) class FieldFloatList(_FieldSetting): """Field for entering multiple numbers, separated by commas or spaces Returns a list/tuple of floats """ def __init__(self, name, descr=None, default=()): _FieldSetting.__init__(self, setting.FloatList, name, descr=descr, default=default) class FieldDataset(_FieldSetting): """Field for selecting a datset. Returns a string. Note that the validity of dataset names is not checked Note that a blank string may result """ def __init__(self, name, descr=None, default='', dims=1, datatype='numeric'): """name: name of field descr: description to show to user default: default value (ignored currently) dims: dimensions of dataset to show datatype: type of data: numeric or text """ _FieldSetting.__init__(self, setting.Dataset, name, descr=descr, default=default, setnparams={'dimensions': dims, 'datatype': datatype}) class FieldTextMulti(_FieldSetting): """Field for entering multiple lines of text. Returns a tuple/list of strings. """ def __init__(self, name, descr=None, default=('')): _FieldSetting.__init__(self, setting.Strings, name, descr=descr, default=default) class FieldDatasetMulti(_FieldSetting): """Field for entering multiple datasets. Returns a tuple/list of strings. """ def __init__(self, name, descr=None, default=(''), dims=1, datatype='numeric'): """dims is number of dimensions of datasets to show in drop-down list. datatype is 'numeric' or 'text' """ _FieldSetting.__init__(self, setting.Datasets, name, descr=descr, default=default, setnparams={'dimensions': dims, 'datatype': datatype}) class FieldLineMulti(_FieldSetting): """A field for holding a set of lines. Consists of tuples [('dotted', '1pt', 'color', , False), ...] These are style, width, color, and hide or style, widget, color, transparency, hide This is compatible with the contour widget line style """ def __init__(self, name, descr=None, default=(('solid', '1pt', 'black', False),) ): _FieldSetting.__init__(self, setting.LineSet, name, descr=descr, default=default) class FieldFillMulti(_FieldSetting): """A field for holding a set of fills. Consists of tuples [('solid', 'color', , False), ...] These are color, fill style, and hide or color, fill style, transparency and hide This is compatible with the contour widget line style """ def __init__(self, name, descr=None, default=()): _FieldSetting.__init__(self, setting.FillSet, name, descr=descr, default=default) class FieldFontFamily(_FieldSetting): """A field for holding a font family. Returns a string. """ def __init__(self, name, descr=None, default=None): """Default None selects the default font.""" if default is None: default = setting.Text.defaultfamily _FieldSetting.__init__(self, setting.FontFamily, name, descr=descr, default=default) class FieldFilename(_FieldSetting): """Select a filename with a browse button.""" def __init__(self, name, descr=None, default=''): _FieldSetting.__init__(self, setting.Filename, name, descr=descr, default=default) veusz-1.15/plugins/toolsplugin.py0000644002344000001440000004574111734662204017224 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Plugins for general operations.""" import random import re import fnmatch import veusz.qtall as qt4 import veusz.setting as setting import field # add an instance of your class to this list to be registered toolspluginregistry = [] class ToolsPluginException(RuntimeError): """Raise this to report an error doing what was requested. """ pass class ToolsPlugin(object): # the plugin will get inserted into the menu in a hierarchy based on # the elements of this tuple menu = ('Base plugin',) name = 'Base plugin' author = '' description_short = '' description_full = '' # if the plugin takes no parameters, set this to False has_parameters = True def __init__(self): """Override this to declare a list of input fields if required.""" self.fields = [] def apply(self, commandinterface, fieldresults): """Override this option to do the work of the plugin. * commandinterface is an instance of the embedding interface, which also contains the Root widget node object * fieldresults is a dict containing the values of the fields plus 'currentwidget' which is the path to the current widget * Raise an ToolsPluginException(str) to report a problem to the user """ ######################################################################### class ColorsRandomize(ToolsPlugin): """Randomize the colors used in plotting.""" menu = ('Colors', 'Randomize') name = 'Randomize colors' description_short = 'Randomize the colors used in plotting' description_full = 'Randomize the colors used in plotting markers, lines or error bars. Random colors in hue, saturation and luminosity (HSV) are chosen between the two colors given.' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("randxy", descr="Randomize xy plotters", default=True), field.FieldBool("randfunc", descr="Randomize function plotters", default=True), field.FieldColor('color1', descr="Start of color range", default='#404040'), field.FieldColor('color2', descr="End of color range", default='#ff0004'), ] def getRandomColor(self, col1, col2): """Return RGB name for a random color.""" H1, H2 = col1.hue(), col2.hue() S1, S2 = col1.saturation(), col2.saturation() V1, V2 = col1.value(), col2.value() def rand(a, b): if a > b: return random.randint(b, a) return random.randint(a, b) col = qt4.QColor.fromHsv(rand(H1, H2), rand(S1, S2), rand(V1, V2)) return str(col.name()) def apply(self, ifc, fields): """Do the randomizing.""" fromwidget = ifc.Root.fromPath(fields['widget']) col1 = qt4.QColor(fields['color1']) col2 = qt4.QColor(fields['color2']) if fields['randxy']: for node in fromwidget.WalkWidgets(widgettype='xy'): col = self.getRandomColor(col1, col2) node.PlotLine.color.val = col node.MarkerFill.color.val = col node.ErrorBarLine.color.val = col if fields['randfunc']: for node in fromwidget.WalkWidgets(widgettype='function'): node.Line.color.val = self.getRandomColor(col1, col2) class ColorsSequence(ToolsPlugin): """Color plotters in sequence.""" menu = ('Colors', 'Sequence') name = 'Create color sequence' description_short = 'Make widgets use sequence of colors' description_full = 'Give new colors to each widget in a sequence between the two colors given.' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("randxy", descr="Color xy plotters", default=True), field.FieldBool("randfunc", descr="Color function plotters", default=True), field.FieldColor('color1', descr="Start of color range", default='#ff0000'), field.FieldColor('color2', descr="End of color range", default='#4000ff'), ] def apply(self, ifc, fields): """Do the randomizing.""" fromwidget = ifc.Root.fromPath(fields['widget']) col1 = qt4.QColor(fields['color1']) col2 = qt4.QColor(fields['color2']) H1, H2 = col1.hue(), col2.hue() S1, S2 = col1.saturation(), col2.saturation() V1, V2 = col1.value(), col2.value() # add up total number of widgets numwidgets = ( len( list(fromwidget.WalkWidgets(widgettype='xy')) ) + len( list(fromwidget.WalkWidgets(widgettype='function')) ) ) def colatidx(i): """Get color in range 0...numwidgets-1.""" H = i * (H2-H1) / float(numwidgets-1) + H1 S = i * (S2-S1) / float(numwidgets-1) + S1 V = i * (V2-V1) / float(numwidgets-1) + V1 return str(qt4.QColor.fromHsv(H, S, V).name()) idx = 0 for node in fromwidget.WalkWidgets(): t = node.widgettype if fields['randxy'] and t == 'xy': col = colatidx(idx) idx += 1 node.PlotLine.color.val = col node.MarkerFill.color.val = col node.ErrorBarLine.color.val = col if fields['randfunc'] and t == 'function': node.Line.color.val = colatidx(idx) idx += 1 class ColorsReplace(ToolsPlugin): """Replace one color by another.""" menu = ('Colors', 'Replace') name = 'Replace colors' description_short = 'Search and replace colors' description_full = 'Searches for a color and replaces it with a different color' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("follow", descr="Change references and defaults", default=True), field.FieldColor('color1', descr="Color to change", default='black'), field.FieldColor('color2', descr="Replacement color", default='red'), ] def apply(self, ifc, fields): """Do the color search and replace.""" fromcol = qt4.QColor(fields['color1']) def walkNodes(node): """Walk nodes, changing values.""" if node.type == 'setting' and node.settingtype == 'color': # only follow references if requested if node.isreference: if fields['follow']: node = node.resolveReference() else: return # evaluate into qcolor to make sure is a true match if qt4.QColor(node.val) == fromcol: node.val = fields['color2'] else: for c in node.children: walkNodes(c) fromwidget = ifc.Root.fromPath(fields['widget']) walkNodes(fromwidget) class ColorsSwap(ToolsPlugin): """Swap colors used in plotting.""" menu = ('Colors', 'Swap') name = 'Swap colors' description_short = 'Swap two colors' description_full = 'Swaps two colors in the plot' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("follow", descr="Change references and defaults", default=True), field.FieldColor('color1', descr="First color", default='black'), field.FieldColor('color2', descr="Second color", default='red'), ] def apply(self, ifc, fields): """Do the color search and replace.""" col1 = qt4.QColor(fields['color1']) col2 = qt4.QColor(fields['color2']) def walkNodes(node): """Walk nodes, changing values.""" if node.type == 'setting' and node.settingtype == 'color': # only follow references if requested if node.isreference: if fields['follow']: node = node.resolveReference() else: return # evaluate into qcolor to make sure is a true match if qt4.QColor(node.val) == col1: node.val = fields['color2'] elif qt4.QColor(node.val) == col2: node.val = fields['color1'] else: for c in node.children: walkNodes(c) fromwidget = ifc.Root.fromPath(fields['widget']) walkNodes(fromwidget) class TextReplace(ToolsPlugin): """Randomize the colors used in plotting.""" menu = ('General', 'Replace text') name = 'Replace text' description_short = 'Search and replace text in settings' description_full = 'Searches for text in a setting and replaces it' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("follow", descr="Change references and defaults", default=True), field.FieldBool("onlystr", descr="Change only textual data", default=False), field.FieldText('text1', descr="Text to change", default=''), field.FieldText('text2', descr="Replacement text", default=''), ] def apply(self, ifc, fields): """Do the search and replace.""" def walkNodes(node): """Walk nodes, changing values.""" if node.type == 'setting': # only follow references if requested if node.isreference: if fields['follow']: node = node.resolveReference() else: return val = node.val # try to change if a string, and not only strings or type is string if isinstance(val, basestring) and (not fields['onlystr'] or node.settingtype == 'str'): # update text if it changes val2 = val.replace(fields['text1'], fields['text2']) if val != val2: try: node.val = val2 except setting.InvalidType: pass else: for c in node.children: walkNodes(c) fromwidget = ifc.Root.fromPath(fields['widget']) walkNodes(fromwidget) class WidgetsClone(ToolsPlugin): """Take a widget and children and clone them.""" menu = ('Widgets', 'Clone for datasets') name = 'Clone widgets for datasets' description_short = 'Clones a widget and its children for datasets' description_full = 'Take a widget and its children and clone it, plotting different sets of data in each clone.\nHint: Use a "*" in the name of a replacement dataset to match multiple datasets, e.g. x_*' def __init__(self): """Construct plugin.""" self.fields = [ field.FieldWidget("widget", descr="Clone widget", default=""), field.FieldDataset('ds1', descr="Dataset 1 to change", default=''), field.FieldDatasetMulti('ds1repl', descr="Replacement(s) for dataset 1"), field.FieldDataset('ds2', descr="Dataset 2 to change (optional)", default=''), field.FieldDatasetMulti('ds2repl', descr="Replacement(s) for dataset 2"), field.FieldBool("names", descr="Build new names from datasets", default=True), ] def apply(self, ifc, fields): """Do the cloning.""" def expanddatasets(dslist): """Expand * and ? in dataset names.""" datasets = [] for ds in dslist: if ds.find('*') == -1 and ds.find('?') == -1: datasets.append(ds) else: dlist = fnmatch.filter(ifc.GetDatasets(), ds) dlist.sort() datasets += dlist return datasets def chainpairs(dslist1, dslist2): """Return pairs of datasets, repeating if necessary.""" if not dslist1: dslist1 = [''] if not dslist2: dslist2 = [''] end1 = end2 = False idx1 = idx2 = 0 while True: if idx1 >= len(ds1repl): idx1 = 0 end1 = True if idx2 >= len(ds2repl): idx2 = 0 end2 = True if end1 and end2: break yield dslist1[idx1], dslist2[idx2] idx1 += 1 idx2 += 1 def walkNodes(node, dsname, dsrepl): """Walk nodes, changing datasets.""" if node.type == 'setting': if node.settingtype in ( 'dataset', 'dataset-or-floatlist', 'dataset-or-str'): # handle single datasets if node.val == dsname: node.val = dsrepl elif node.settingtype == 'dataset-multi': # settings with multiple datasets out = list(node.val) for i, v in enumerate(out): if v == dsname: out[i] = dsrepl if tuple(out) != node.val: node.val = out else: for c in node.children: walkNodes(c, dsname, dsrepl) # get names of replacement datasets ds1repl = expanddatasets(fields['ds1repl']) ds2repl = expanddatasets(fields['ds2repl']) # make copies of widget and children for each pair of datasets widget = ifc.Root.fromPath(fields['widget']) for ds1r, ds2r in chainpairs(ds1repl, ds2repl): # construct a name newname = None if fields['names']: newname = widget.name if ds1r: newname += ' ' + ds1r if ds2r: newname += ' ' + ds2r # make the new widget (and children) newwidget = widget.Clone(widget.parent, newname=newname) # do replacement of datasets if fields['ds1']: walkNodes(newwidget, fields['ds1'], ds1r) if fields['ds2']: walkNodes(newwidget, fields['ds2'], ds2r) class FontSize(ToolsPlugin): """Increase or decrease the font size.""" def __init__(self, dirn): """Construct plugin. dirn == 1: increase sizes dirn == -1: decrease sizes """ self.dirn = dirn self.fields = [ field.FieldWidget("widget", descr="Start from widget", default="/"), field.FieldBool("follow", descr="Change references and defaults", default=True), field.FieldFloat("delta", descr="Change by value", default=2), ] def apply(self, ifc, fields): """Do the search and replace.""" pt_re = re.compile(r'^([\d.]+)[ ]*pt$') delta = fields['delta'] changed = set() def walkNodes(node): """Walk nodes, changing values.""" if node.type == 'setting': if node.name == 'size': # find size setting with sibling font (improve this) if not hasattr(node.parent, 'font'): return # only follow references if requested if node.isreference: if fields['follow']: node = node.resolveReference() else: return # avoid doing things more than once p = node.path if p in changed: return changed.add(p) # change point size if requested m = pt_re.match(node.val) if m: pt = float(m.group(1)) + delta*self.dirn if pt < 0: pt = 0.1 node.val = '%gpt' % pt else: for c in node.children: walkNodes(c) fromwidget = ifc.Root.fromPath(fields['widget']) walkNodes(fromwidget) class FontSizeIncrease(FontSize): menu = ('General', 'Increase font sizes') name = 'Increase font sizes' description_short = 'Increase font sizes' description_full = 'Increase font sizes by number of points given' def __init__(self): FontSize.__init__(self, 1) class FontSizeDecrease(FontSize): menu = ('General', 'Decrease font sizes') name = 'Decrease font sizes' description_short = 'Decrease font sizes' description_full = 'Decrease font sizes by number of points given' def __init__(self): FontSize.__init__(self, -1) toolspluginregistry += [ ColorsRandomize, ColorsSequence, ColorsReplace, ColorsSwap, TextReplace, WidgetsClone, FontSizeIncrease, FontSizeDecrease, ] veusz-1.15/plugins/importplugin.py0000644002344000001440000005416511734662204017376 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Import plugin base class and helpers.""" import os.path import numpy as N import veusz.utils as utils import field import datasetplugin # add an instance of your class to this list to get it registered importpluginregistry = [] class ImportPluginParams(object): """Parameters to plugin are passed in this object.""" def __init__(self, filename, encoding, field_results): self.filename = filename self.encoding = encoding self.field_results = field_results def openFileWithEncoding(self): """Helper to open filename but respecting encoding.""" return utils.openEncoding(self.filename, self.encoding) class ImportPluginException(RuntimeError): """An exception to return errors about importing or previewing data.""" class ImportPlugin(object): """Define a plugin to read data in a particular format. Override doImport and optionally getPreview to define a new plugin. Register the class by adding it to the importpluginregistry list. Of promote_tab is set to some text, put the plugin on its own tab in the import dialog using that text as the tab name. """ name = 'Import plugin' author = '' description = '' # if set to some text, use this plugin on its own tab promote_tab = None # set these to get focus if a file is selected with these extensions # include the dot in the extension names file_extensions = set() def __init__(self): """Override this to declare a list of input fields if required.""" # a list of Field objects to display self.fields = [] def getPreview(self, params): """Get data to show in a text box to show a preview. params is a ImportPluginParams object. Returns (text, okaytoimport) """ f = params.openFileWithEncoding() return f.read(4096), True def doImport(self, params): """Actually import data params is a ImportPluginParams object. Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects """ return [] ################################################################# class ImportPluginExample(ImportPlugin): """An example plugin for reading a set of unformatted numbers from a file.""" name = "Example plugin" author = "Jeremy Sanders" description = "Reads a list of numbers in a text file" def __init__(self): self.fields = [ field.FieldText("name", descr="Dataset name", default="name"), field.FieldBool("invert", descr="invert values"), field.FieldFloat("mult", descr="Multiplication factor", default=1), field.FieldInt("skip", descr="Skip N lines", default=0, minval=0), field.FieldCombo("subtract", items=("0", "1", "2"), editable=False, default="0") ] def doImport(self, params): """Actually import data params is a ImportPluginParams object. Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects """ try: f = params.openFileWithEncoding() data = [] mult = params.field_results["mult"] sub = float(params.field_results["subtract"]) if params.field_results["invert"]: mult *= -1 for i in xrange(params.field_results["skip"]): f.readline() for line in f: data += [float(x)*mult-sub for x in line.split()] return [datasetplugin.Dataset1D(params.field_results["name"], data), datasetplugin.Constant("testconst", "42"), datasetplugin.Function("testfunc(x)", "testconst*x**2")] except Exception, e: raise ImportPluginException(unicode(e)) class ImportPluginDateTime(ImportPlugin): """An example plugin for reading a set of iso date-times from a file.""" name = "Example plugin for date/times" author = "Jeremy Sanders" description = "Reads a list of ISO date times in a text file" def __init__(self): self.fields = [ field.FieldText("name", descr="Dataset name", default="name"), ] def doImport(self, params): """Actually import data params is a ImportPluginParams object. Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects """ f = params.openFileWithEncoding() data = [] for line in f: data.append( datasetplugin.DatasetDateTime. dateStringToFloat(line.strip()) ) return [ datasetplugin.DatasetDateTime(params.field_results["name"], data) ] #importpluginregistry.append( ImportPluginDateTime ) class QdpFile(object): """Handle reading of a Qdp file.""" def __init__(self, colnames): self.colmodes = {} self.skipmode = 'none' self.retndata = [] # store read in data here self.data = [] # index of max vector self.dataindex = 1 self.colnames = colnames # list of data groups for 2d objects self.datagroup2d = [] # axis ranges for 2d objects self.axis2d = [None, None] def handleRead(self, p): """Handle read command.""" try: mode = {'t': 'terr', 's': 'serr'}[p[1][:1]] except (IndexError, KeyError): raise ImportPluginException("read command takes terr/serr") try: cols = [int(x) for x in p[2:]] except ValueError: raise ImportPluginException("read command takes list of columns separated by spaces") for c in cols: self.colmodes[c] = mode def handleSkip(self, p): """Handle skip command.""" try: self.skipmode = {'o': 'off', 's': 'single', 'd': 'double'}[p[1][:1]] except (IndexError, KeyError): raise ImportPluginException("skip command takes single/double/off") def handleNO(self, p, lastp): """Handle no command, meaning no data.""" if self.skipmode == 'none': self.addNans( len(p) ) elif self.skipmode == 'single': self.pushData() del self.data[:] self.dataindex += 1 elif self.skipmode == 'double': if lastp[0] == 'no': self.pushData() del self.data[:] self.dataindex += 1 else: self.addNans( len(p) ) def addNans(self, num): """Add a blank set of data to output.""" col = 0 ds = 0 while col < num or ds < len(self.data): if ds >= len(self.data): self.data.append([]) m = self.colmodes.get(ds+1) if m == 'serr': self.data[ds].append( (N.nan, N.nan) ) col += 2 elif m == 'terr': self.data[ds].append( (N.nan, N.nan, N.nan) ) col += 3 else: self.data[ds].append( N.nan ) col += 1 ds += 1 def pushData2D(self): """Handle 2D data groups.""" for num, r1, c1, r2, c2 in self.datagroup2d: arr = [] for c in xrange(c1-1,c2-1+1): arr.append( self.data[c][r1-1:r2-1+1] ) # make data as "used" self.data[c] = None arr = N.array(arr) if num-1 < len(self.colnames): name = self.colnames[num-1] else: name = 'vec2d%i' % num rangex = rangey = None if self.axis2d[0] is not None: minval, pixsize = self.axis2d[0] rangex = (minval - pixsize*0.5, minval+(arr.shape[1]-0.5)*pixsize ) if self.axis2d[1] is not None: minval, pixsize = self.axis2d[1] rangey = (minval - pixsize*0.5, minval+(arr.shape[0]-0.5)*pixsize ) ds = datasetplugin.Dataset2D(name, data=arr, rangex=rangex, rangey=rangey) self.retndata.append(ds) def pushData(self): """Add data to output array. """ for i in xrange(len(self.data)): if self.data[i] is None: continue # get dataset name if i < len(self.colnames): name = self.colnames[i] else: name = 'vec%i' % (i+1) if self.skipmode == 'single' or self.skipmode == 'double': name = name + '_' + str(self.dataindex) # convert data a = N.array(self.data[i]) if len(a.shape) == 1: # no error bars ds = datasetplugin.Dataset1D(name, data=a) elif a.shape[1] == 2: # serr ds = datasetplugin.Dataset1D(name, data=a[:,0], serr=a[:,1]) elif a.shape[1] == 3: # perr/nerr p = N.where(a[:,1] < a[:,2], a[:,2], a[:,1]) n = N.where(a[:,1] < a[:,2], a[:,1], a[:,2]) ds = datasetplugin.Dataset1D(name, data=a[:,0], perr=p, nerr=n) else: raise RuntimeError self.retndata.append(ds) def handleDataGroup(self, p): """Handle data groups.""" if len(p) == 3: # we don't support the renaming thing pass elif len(p) == 6: # 2d data try: pint = [int(x) for x in p[1:]] except ValueError: raise ImportPluginException("invalid 2d datagroup command") self.datagroup2d.append(pint) def handleAxis(self, p): """Axis command gives range of axes (used for 2d).""" try: minval, maxval = float(p[2]), float(p[3]) except ValueError: raise ImportPluginException("invalid axis range") self.axis2d[ p[0][0] == 'y' ] = (minval, maxval) def handleNum(self, p): """Handle set of numbers.""" try: nums = [float(x) for x in p] except ValueError: raise ImportPluginException("Cannot convert '%s' to numbers" % (' '.join(p))) col = 0 ds = 0 while col < len(nums): if ds >= len(self.data): self.data.append([]) m = self.colmodes.get(ds+1) if m == 'serr': self.data[ds].append( (nums[col], nums[col+1]) ) col += 2 elif m == 'terr': self.data[ds].append( (nums[col], nums[col+1], nums[col+2]) ) col += 3 else: self.data[ds].append( nums[col] ) col += 1 ds += 1 def importFile(self, fileobj, dirname): """Read data from file object. dirname is the directory in which the file is located """ contline = None lastp = [] for line in fileobj: # strip comments if line.find("!") >= 0: line = line[:line.find("!")] if line[:1] == '@': # read another file fname = os.path.join(dirname, line[1:].strip()) try: newf = open(fname) self.importFile(newf, dirname) except EnvironmentError: pass continue p = [x.lower() for x in line.split()] if contline: # add on previous continuation if existed p = contline + p contline = None if len(p) > 0 and p[-1][-1] == '-': # continuation p[-1] = p[-1][:-1] contline = p continue if len(p) == 0: # nothing continue v0 = p[0] if v0[0] in '0123456789-.': self.handleNum(p) elif v0 == 'no': self.handleNO(p, lastp) elif v0 == 'read': self.handleRead(p) elif v0[:2] == 'sk': self.handleSkip(p) elif v0[:2] == 'dg': self.handleDataGroup(p) elif v0[:1] == 'x' or v0[:2] == 'ya': self.handleAxis(p) else: # skip everything else (for now) pass lastp = p class ImportPluginQdp(ImportPlugin): """An example plugin for reading data from QDP files.""" name = "QDP import" author = "Jeremy Sanders" description = "Reads datasets from QDP files" file_extensions = set(['.qdp']) def __init__(self): self.fields = [ field.FieldTextMulti("names", descr="Vector name list ", default=['']), ] def doImport(self, params): """Actually import data params is a ImportPluginParams object. Return a list of datasetplugin.Dataset1D, datasetplugin.Dataset2D objects """ names = [x.strip() for x in params.field_results["names"] if x.strip()] f = params.openFileWithEncoding() rqdp = QdpFile(names) rqdp.importFile(f, os.path.dirname(params.filename)) rqdp.pushData2D() rqdp.pushData() f.close() return rqdp.retndata def cnvtImportNumpyArray(name, val, errorsin2d=True): """Convert a numpy array to plugin returns.""" try: val.shape except AttributeError: raise ImportPluginException("Not the correct format file") try: val + 0. val = val.astype(N.float64) except TypeError: raise ImportPluginException("Unsupported array type") if val.ndim == 1: return datasetplugin.Dataset1D(name, val) elif val.ndim == 2: if errorsin2d and val.shape[1] in (2, 3): # return 1d array if val.shape[1] == 2: # use as symmetric errors return datasetplugin.Dataset1D(name, val[:,0], serr=val[:,1]) else: # asymmetric errors # unclear on ordering here... return datasetplugin.Dataset1D(name, val[:,0], perr=val[:,1], nerr=val[:,2]) else: return datasetplugin.Dataset2D(name, val) else: raise ImportPluginException("Unsupported dataset shape") class ImportPluginNpy(ImportPlugin): """For reading single datasets from NPY numpy saved files.""" name = "Numpy NPY import" author = "Jeremy Sanders" description = "Reads a 1D/2D numeric dataset from a Numpy NPY file" file_extensions = set(['.npy']) def __init__(self): self.fields = [ field.FieldText("name", descr="Dataset name", default=''), field.FieldBool("errorsin2d", descr="Treat 2 and 3 column 2D arrays as\n" "data with error bars", default=True), ] def getPreview(self, params): """Get data to show in a text box to show a preview. params is a ImportPluginParams object. Returns (text, okaytoimport) """ try: retn = N.load(params.filename) except Exception, e: return "Cannot read file", False try: text = 'Array shape: %s\n' % str(retn.shape) text += 'Array datatype: %s (%s)\n' % (retn.dtype.str, str(retn.dtype)) text += str(retn) return text, True except AttributeError: return "Not an NPY file", False def doImport(self, params): """Actually import data. """ name = params.field_results["name"].strip() if not name: raise ImportPluginException("Please provide a name for the dataset") try: retn = N.load(params.filename) except Exception, e: raise ImportPluginException("Error while reading file: %s" % unicode(e)) return [ cnvtImportNumpyArray( name, retn, errorsin2d=params.field_results["errorsin2d"]) ] class ImportPluginNpz(ImportPlugin): """For reading single datasets from NPY numpy saved files.""" name = "Numpy NPZ import" author = "Jeremy Sanders" description = "Reads datasets from a Numpy NPZ file." file_extensions = set(['.npz']) def __init__(self): self.fields = [ field.FieldBool("errorsin2d", descr="Treat 2 and 3 column 2D arrays as\n" "data with error bars", default=True), ] def getPreview(self, params): """Get data to show in a text box to show a preview. params is a ImportPluginParams object. Returns (text, okaytoimport) """ try: retn = N.load(params.filename) except Exception, e: return "Cannot read file", False # npz files should define this attribute try: retn.files except AttributeError: return "Not an NPZ file", False text = [] for f in sorted(retn.files): a = retn[f] text.append('Name: %s' % f) text.append(' Shape: %s' % str(a.shape)) text.append(' Datatype: %s (%s)' % (a.dtype.str, str(a.dtype))) text.append('') return '\n'.join(text), True def doImport(self, params): """Actually import data. """ try: retn = N.load(params.filename) except Exception, e: raise ImportPluginException("Error while reading file: %s" % unicode(e)) try: retn.files except AttributeError: raise ImportPluginException("File is not in NPZ format") # convert each of the imported arrays out = [] for f in sorted(retn.files): out.append( cnvtImportNumpyArray( f, retn[f], errorsin2d=params.field_results["errorsin2d"]) ) return out class ImportPluginBinary(ImportPlugin): name = "Binary import" author = "Jeremy Sanders" description = "Reads numerical binary files." file_extensions = set(['.bin']) def __init__(self): self.fields = [ field.FieldText("name", descr="Dataset name", default=""), field.FieldCombo("datatype", descr="Data type", items = ("float32", "float64", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64"), default="float64", editable=False), field.FieldCombo("endian", descr="Endian (byte order)", items = ("little", "big"), editable=False), field.FieldInt("offset", descr="Offset (bytes)", default=0, minval=0), field.FieldInt("length", descr="Length (values)", default=-1) ] def getNumpyDataType(self, params): """Convert params to numpy datatype.""" t = N.dtype(str(params.field_results["datatype"])) return t.newbyteorder( {"little": "<", "big": ">"} [ params.field_results["endian"]] ) def getPreview(self, params): """Preview of data files.""" try: f = open(params.filename, "rb") data = f.read() f.close() except EnvironmentError, e: return "Cannot read file (%s)" % e.strerror, False text = ['File length: %i bytes' % len(data)] def filtchr(c): """Filtered character to ascii range.""" if ord(c) <= 32 or ord(c) > 127: return '.' else: return c # do a hex dump (like in CP/M) for i in xrange(0, min(65536, len(data)), 16): hdr = '%04X ' % i subset = data[i:i+16] hexdata = ('%02X '*len(subset)) % tuple([ord(x) for x in subset]) chrdata = ''.join([filtchr(c) for c in subset]) text.append(hdr+hexdata + ' ' + chrdata) return '\n'.join(text), True def doImport(self, params): """Import the data.""" name = params.field_results["name"].strip() if not name: raise ImportPluginException("Please provide a name for the dataset") try: f = open(params.filename, "rb") f.seek( params.field_results["offset"] ) retn = f.read() f.close() except EnvironmentError, e: raise ImportPluginException("Error while reading file '%s'\n\n%s" % (params.filename, e.strerror)) data = N.fromstring(retn, dtype=self.getNumpyDataType(params), count=params.field_results["length"]) data = data.astype(N.float64) return [ datasetplugin.Dataset1D(name, data) ] importpluginregistry += [ ImportPluginNpy, ImportPluginNpz, ImportPluginQdp, ImportPluginBinary, ImportPluginExample, ] veusz-1.15/plugins/__init__.py0000644002344000001440000000241511734662204016373 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## from field import * from datasetplugin import * from importplugin import * from toolsplugin import * # backward compatibility ImportDataset1D = Dataset1D ImportDataset2D = Dataset2D ImportDatasetText = DatasetText ImportField = Field ImportFieldCheck = FieldBool ImportFieldText = FieldText ImportFieldFloat = FieldFloat ImportFieldInt = FieldInt ImportFieldCombo = FieldCombo veusz-1.15/examples/0000755002344000001440000000000011734662466014427 5ustar jssusers00000000000000veusz-1.15/examples/datebar.dat0000644002344000001440000000024411734662204016511 0ustar jssusers00000000000000descriptor d(date) value(numeric),+- 2009-03-10 1 0.1 2009-03-11 2 0.2 2009-03-12 1.3 0.12 2009-03-13 1.5 0.1 2009-03-14 4 0.2 2009-03-15 3 0.15 2009-03-16 1.8 0.2 veusz-1.15/examples/contour.vsz0000644002344000001440000000146411734662204016657 0ustar jssusers00000000000000# Veusz saved document (version 0.9) # User: jss # Date: Wed, 18 Jan 2006 21:50:11 +0000 SetData2D('vals', fromfunction(lambda x, y: sin(x*0.05)+cos(x*0.1+y*0.1), (100, 100))) SetData2D('vals2', fromfunction(lambda x, y: sin(x*0.2+y*0.1), (100, 100))) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('contour', name='contour1', autoadd=False) To('contour1') Set('data', u'vals') Set('numLevels', 10) Set('lines', [('solid', '1pt', u'#5500ff', False), ('dotted', '1pt', u'#aa557f', False)]) To('..') Add('image', name='image1', autoadd=False) To('image1') Set('data', u'vals2') Set('colorMap', u'bluegreen') To('..') To('..') To('..') veusz-1.15/examples/bar_labels.dat0000644002344000001440000000020511734662204017172 0ustar jssusers00000000000000descriptor name spring,+,- summer,+- "Red" 2 0.2 -0.2 4 0.3 "Green" 3 0.1 -0.1 2.5 0.1 "Blue" 5 0.3 -0.2 3 0.2 veusz-1.15/examples/coloredpoints.vsz0000644002344000001440000001252111734662204020046 0ustar jssusers00000000000000# Veusz saved document (version 1.14) # Saved at 2012-01-21T17:19:49.964627 AddImportPath(u'/home/jss/code/veusz-git/veusz/examples') ImportString('c(numeric)',''' 1.459606e+02 1.702896e+02 7.934817e+01 1.227687e+01 5.599601e+03 1.539531e+01 1.828166e+04 5.054989e+03 3.191051e+02 7.949710e+00 2.216535e+03 5.143240e+03 3.148290e+01 4.965444e+01 7.777005e+02 3.185130e+02 1.588387e+04 2.813152e+02 1.339360e+02 4.215790e+03 6.843036e+03 8.415999e+01 1.362926e+02 6.243027e+03 6.179793e+02 1.897636e+03 2.796312e+02 1.026779e+01 4.765936e+03 1.570359e+04 1.467323e+02 1.164478e+03 1.902282e+02 1.520207e+04 2.973087e+00 2.337408e+00 4.134565e+02 7.166886e+03 4.217587e+03 9.953222e+02 8.267064e+01 5.035324e+00 9.556994e+00 8.546561e+01 1.500701e+01 1.351431e+01 4.917555e+03 2.496430e+00 2.103088e+01 1.077053e+03 1.563147e+02 3.163246e+03 1.626294e+04 4.627788e+03 8.483016e+03 5.805220e+03 1.600690e+01 2.339657e+02 1.526946e+02 4.282832e+02 2.799937e+02 3.737549e+02 2.842098e+01 1.275867e+03 4.526153e+04 2.403380e+04 4.756101e+01 2.977493e+04 5.519853e+01 2.365612e+02 4.460124e+03 2.131700e+03 6.597176e+03 8.787685e+02 3.755261e+04 5.750869e+02 7.043527e+02 8.845572e+03 1.043050e+02 5.741879e+00 2.603017e+02 1.178276e+01 9.850848e+00 9.117799e+02 1.102115e+02 9.833058e+01 2.374284e+02 1.129614e+04 2.973567e+03 2.594940e+02 3.478364e+01 2.132164e+00 7.268064e+03 1.879249e+01 1.321168e+02 1.057914e+03 7.070878e+01 4.039144e+00 7.826971e+01 1.123204e+04 ''') SetDataExpression(u'sizef', u'abs(x-0.5)+0.5', linked=True) ImportString('x(numeric)',''' 3.259640e-01 2.936538e-01 3.162201e-01 2.638131e-01 8.803033e-01 2.577980e-01 9.479319e-01 5.931048e-01 5.222732e-01 1.018496e-01 7.321032e-01 7.753362e-01 2.971560e-01 3.875175e-01 4.714681e-01 5.461489e-01 8.912572e-01 3.138063e-01 3.838737e-01 7.173045e-01 9.487288e-01 3.029883e-01 4.863407e-01 8.589109e-01 5.710536e-01 7.576283e-01 5.049896e-01 2.308246e-01 8.570804e-01 8.612760e-01 4.759630e-01 5.248471e-01 4.719874e-01 7.990987e-01 9.636075e-02 6.375893e-02 6.057062e-01 9.102558e-01 7.864667e-01 6.260576e-01 4.087506e-01 1.741066e-01 1.165042e-01 3.446296e-01 2.170302e-01 9.573020e-02 8.219256e-01 9.182756e-02 2.018576e-01 6.840850e-01 3.888053e-01 8.064366e-01 9.381433e-01 8.748492e-01 8.716737e-01 8.351529e-01 1.047454e-01 4.707636e-01 4.919583e-01 5.672392e-01 4.050533e-01 4.783146e-01 3.282565e-01 6.755866e-01 9.435113e-01 9.404685e-01 2.656279e-01 9.456492e-01 3.985884e-01 5.263482e-01 6.925591e-01 6.756365e-01 9.412488e-01 6.768923e-01 8.147229e-01 5.731504e-01 5.591458e-01 8.806983e-01 4.030426e-01 1.638092e-01 4.160340e-01 2.343386e-01 1.942402e-01 6.532868e-01 3.740195e-01 3.338149e-01 3.336983e-01 8.617688e-01 7.426878e-01 4.504087e-01 2.690837e-01 5.143151e-02 9.142272e-01 1.996254e-01 5.223122e-01 7.060125e-01 3.945721e-01 1.191278e-01 3.037703e-01 8.536895e-01 ''') ImportString('y(numeric)',''' 1.720759e+00 -2.113146e+00 -1.269313e+00 6.767068e-02 -4.538878e-01 -3.123929e-01 -9.405761e-01 -2.662602e+00 -8.296818e-01 -9.859058e-01 -8.345237e-01 1.219784e+00 6.189014e-01 2.917761e-01 -2.009880e+00 -6.370632e-01 1.271855e+00 -2.387936e+00 -1.182805e+00 1.511322e+00 8.066724e-02 -1.426305e+00 -3.782192e-01 -7.195032e-01 -1.013519e+00 -4.953992e-01 -8.532545e-01 -1.763571e-01 4.996532e-01 -1.501790e+00 -5.253474e-01 -1.933486e+00 7.826508e-01 1.971016e+00 1.755292e-01 2.273975e-01 3.872100e-01 -4.286146e-01 9.583945e-01 -9.874666e-01 5.646978e-01 1.120199e-02 1.028609e+00 -1.106546e+00 6.163468e-01 -1.495746e+00 -8.080937e-01 6.001841e-02 -1.030854e+00 -5.917945e-01 -1.277557e+00 -5.487731e-01 -9.172515e-01 3.319534e-01 -8.837110e-01 8.464141e-01 1.570651e+00 -9.721957e-01 -4.319809e-01 7.255485e-01 1.653870e+00 -1.318657e+00 2.812261e-01 -8.069181e-01 -1.763368e+00 -1.237897e+00 -1.229479e+00 -1.382508e+00 2.951479e-01 -5.371013e-01 -1.758221e+00 -1.252360e+00 -1.087257e-01 -4.726106e-01 -2.631497e+00 -9.342638e-01 1.222414e+00 -8.478654e-01 8.122695e-01 2.076344e-01 -1.502682e+00 2.677853e-01 4.330256e-01 -6.934856e-01 1.092298e+00 1.314858e+00 -2.081479e+00 -1.211710e+00 1.005053e+00 -1.224985e+00 -9.300805e-01 2.461890e-01 -4.090199e-01 9.509655e-01 -6.341827e-02 4.008008e-01 5.423699e-01 2.595563e-01 1.357025e+00 -1.271401e+00 ''') Set('StyleSheet/Font/font', u'Arial') Set('StyleSheet/axis/Line/width', u'1pt') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('Background/color', u'#e5e9ff') Set('Border/width', u'1pt') Add('colorbar', name='colorbar1', autoadd=False) To('colorbar1') Set('widgetName', u'xy1') Set('label', u'Power (W)') Set('autoRange', u'exact') Set('lowerPosition', 0.0) Set('upperPosition', 1.0) Set('otherPosition', 0.0) Set('TickLabels/format', u'%VE') Set('horzPosn', u'centre') Set('vertPosn', u'top') Set('width', u'8cm') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('marker', u'circle') Set('markerSize', u'5pt') Set('scalePoints', u'sizef') Set('Color/points', u'c') Set('Color/min', 2.0) Set('Color/max', 4500.0) Set('Color/scaling', u'log') Set('PlotLine/hide', True) Set('MarkerFill/colorMap', u'complement') To('..') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Time (yr)') Set('GridLines/hide', False) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Offset (m)') Set('min', -3.0) Set('max', 3.0) Set('direction', 'vertical') Set('GridLines/hide', False) To('..') To('..') To('..') veusz-1.15/examples/spectrum.vsz0000644002344000001440000074234111734662204017036 0ustar jssusers00000000000000# Veusz saved document (version 1.8) # User: jss # Date: Thu, 17 Jun 2010 17:00:56 +0000 ImportString(u'flux(numeric),+-',''' 2.861010e+03 4.547170e+03 -7.409870e+01 6.536110e+01 -2.857510e+00 4.228670e+00 -1.980870e-01 2.777620e-01 1.785610e-01 7.082090e-02 -1.368460e-02 1.465760e-02 3.897360e-04 5.402620e-03 3.944980e-03 2.587620e-03 4.341220e-05 1.533630e-03 1.702510e-03 8.312080e-04 -1.016690e-03 6.990930e-04 7.531280e-04 5.704690e-04 5.805790e-04 4.560370e-04 3.678060e-04 4.197650e-04 5.169160e-05 2.739360e-04 4.095020e-04 1.523600e-04 1.338490e-04 8.217050e-05 3.564070e-05 6.596020e-05 -2.585000e-05 4.778170e-05 1.126610e-04 5.349490e-05 8.400350e-05 7.116170e-05 2.121370e-04 9.766310e-05 3.038190e-04 1.869580e-04 7.236700e-04 3.161350e-04 1.028230e-03 4.622540e-04 2.146910e-03 6.097640e-04 2.904600e-03 7.987310e-04 2.291840e-03 8.011260e-04 2.960800e-03 8.085590e-04 4.408690e-03 7.376950e-04 3.567680e-03 5.860230e-04 2.422900e-03 4.616410e-04 2.563590e-03 3.805690e-04 1.620900e-03 3.021680e-04 2.021810e-03 2.428460e-04 1.716790e-03 1.923180e-04 1.838320e-03 1.890360e-04 1.445380e-03 1.340410e-04 1.510370e-03 1.192620e-04 1.544470e-03 1.198910e-04 1.521150e-03 1.129190e-04 1.362110e-03 1.108320e-04 1.581290e-03 1.038470e-04 1.804590e-03 1.084650e-04 1.929750e-03 1.038210e-04 1.834490e-03 9.696040e-05 1.666100e-03 9.004330e-05 1.223830e-03 8.225680e-05 1.124530e-03 7.873860e-05 1.206090e-03 7.675500e-05 1.264110e-03 7.400770e-05 1.371810e-03 7.158190e-05 1.338920e-03 6.836650e-05 1.342980e-03 6.895620e-05 1.219650e-03 6.543910e-05 1.163210e-03 6.335340e-05 1.127750e-03 6.091640e-05 1.026730e-03 5.334660e-05 9.756010e-04 4.979970e-05 9.780990e-04 4.899700e-05 8.115800e-04 4.475730e-05 8.957100e-04 4.645520e-05 8.947580e-04 4.497950e-05 9.635150e-04 5.058840e-05 1.069390e-03 5.872320e-05 1.006650e-03 4.948750e-05 9.243670e-04 4.591560e-05 9.125620e-04 4.679670e-05 8.713890e-04 4.256130e-05 8.910070e-04 4.384970e-05 7.762210e-04 4.241900e-05 8.683510e-04 4.119610e-05 8.455850e-04 4.172860e-05 6.999520e-04 4.040850e-05 8.657580e-04 4.669020e-05 8.503220e-04 4.050280e-05 1.009740e-03 4.270630e-05 9.939050e-04 4.107680e-05 9.318460e-04 4.149810e-05 9.162650e-04 3.974780e-05 9.679990e-04 3.982370e-05 1.037850e-03 4.140730e-05 1.048320e-03 4.221370e-05 9.859770e-04 4.018910e-05 9.611630e-04 4.050540e-05 9.496320e-04 4.197060e-05 1.154220e-03 4.397390e-05 1.195480e-03 4.304880e-05 1.316100e-03 4.479180e-05 1.322080e-03 4.447060e-05 1.462290e-03 4.833050e-05 1.409960e-03 4.638030e-05 1.324160e-03 5.276210e-05 1.161440e-03 4.374760e-05 1.134130e-03 4.164150e-05 1.137740e-03 4.225890e-05 1.060220e-03 4.030560e-05 1.057720e-03 4.105860e-05 1.014710e-03 4.044670e-05 9.644090e-04 3.719090e-05 1.029160e-03 3.826920e-05 8.932350e-04 3.508860e-05 9.523760e-04 3.626080e-05 1.031550e-03 3.898160e-05 1.037600e-03 3.908580e-05 9.752100e-04 3.662610e-05 9.999040e-04 3.697830e-05 9.950660e-04 3.565950e-05 1.087500e-03 3.751450e-05 9.485900e-04 3.502090e-05 1.017960e-03 3.724980e-05 9.560120e-04 3.503670e-05 1.016640e-03 3.755340e-05 9.564330e-04 3.484880e-05 9.770200e-04 3.417970e-05 9.268900e-04 3.296080e-05 9.034370e-04 3.352500e-05 8.671910e-04 3.244000e-05 9.829280e-04 3.439780e-05 9.941800e-04 3.471960e-05 9.448990e-04 3.263870e-05 9.526980e-04 3.644530e-05 9.415620e-04 4.245090e-05 1.085490e-03 4.495640e-05 1.059780e-03 3.875400e-05 1.085230e-03 3.916330e-05 1.059810e-03 3.777580e-05 9.934540e-04 3.729870e-05 1.007470e-03 3.746450e-05 1.063620e-03 3.789320e-05 1.180250e-03 4.206580e-05 1.191950e-03 4.368860e-05 1.173540e-03 3.911450e-05 1.285680e-03 4.113160e-05 1.420680e-03 5.629710e-05 1.467120e-03 5.625290e-05 1.711210e-03 6.086900e-05 1.813310e-03 6.263700e-05 1.988670e-03 6.582080e-05 1.909580e-03 6.424930e-05 1.793240e-03 6.205990e-05 1.794340e-03 6.273630e-05 1.541830e-03 6.133070e-05 1.700930e-03 5.972990e-05 1.743430e-03 6.117250e-05 1.848860e-03 6.230040e-05 2.076060e-03 6.570080e-05 2.480300e-03 7.184680e-05 2.433870e-03 7.028620e-05 2.312970e-03 6.872160e-05 2.465270e-03 7.242480e-05 2.511480e-03 7.368900e-05 2.488910e-03 7.248220e-05 2.312320e-03 7.219380e-05 2.106310e-03 6.584510e-05 2.057450e-03 8.573220e-05 2.014860e-03 6.764160e-05 1.886570e-03 6.261900e-05 1.722480e-03 5.960430e-05 1.726690e-03 6.164920e-05 1.714720e-03 6.004540e-05 2.058800e-03 6.711510e-05 2.270420e-03 6.894370e-05 2.345830e-03 6.963730e-05 2.179790e-03 6.633480e-05 1.947280e-03 6.339100e-05 1.770830e-03 5.906180e-05 1.657760e-03 5.116800e-05 1.617140e-03 5.004290e-05 1.737280e-03 5.254400e-05 1.955750e-03 5.517160e-05 2.327110e-03 6.034000e-05 2.034150e-03 5.579600e-05 1.793670e-03 5.603270e-05 1.786760e-03 5.929700e-05 1.780900e-03 5.311330e-05 1.467690e-03 4.967820e-05 1.357520e-03 4.717840e-05 1.283180e-03 4.533820e-05 1.288750e-03 4.563510e-05 1.146980e-03 4.253180e-05 1.069530e-03 4.100800e-05 1.051730e-03 4.122660e-05 1.000000e-03 4.035170e-05 9.702090e-04 3.974090e-05 1.115700e-03 4.289800e-05 1.215350e-03 4.596170e-05 1.203690e-03 4.515030e-05 1.108780e-03 7.097760e-05 9.385710e-04 5.090720e-05 9.011720e-04 3.890710e-05 9.431780e-04 4.058840e-05 9.163000e-04 3.924170e-05 8.846180e-04 3.863360e-05 8.958540e-04 3.996850e-05 8.987240e-04 3.969160e-05 8.841800e-04 3.896110e-05 9.454700e-04 4.043850e-05 1.025550e-03 4.212830e-05 1.029330e-03 4.169800e-05 9.236210e-04 3.961090e-05 8.037460e-04 3.282890e-05 8.121830e-04 3.028440e-05 7.751830e-04 2.974850e-05 8.192630e-04 3.020080e-05 7.800070e-04 2.923660e-05 7.512600e-04 2.929310e-05 6.918980e-04 2.881160e-05 7.952500e-04 3.150440e-05 8.502090e-04 3.100650e-05 8.064780e-04 3.000180e-05 7.608890e-04 2.975180e-05 8.237940e-04 3.093400e-05 7.862610e-04 3.022490e-05 7.936110e-04 3.077030e-05 7.620850e-04 3.192690e-05 7.787270e-04 2.987040e-05 7.523590e-04 2.989110e-05 7.232450e-04 2.886770e-05 7.036520e-04 3.019410e-05 7.214750e-04 3.501420e-05 7.126900e-04 3.082310e-05 6.976470e-04 2.898830e-05 6.728590e-04 2.812690e-05 6.741780e-04 2.942630e-05 6.763330e-04 3.290670e-05 6.360100e-04 2.906300e-05 6.806230e-04 2.869370e-05 6.806820e-04 2.833820e-05 6.989240e-04 2.892490e-05 7.976530e-04 3.131240e-05 7.567250e-04 3.226120e-05 6.778250e-04 3.071740e-05 6.994180e-04 3.214040e-05 6.748710e-04 2.880460e-05 6.132930e-04 2.986610e-05 5.866450e-04 2.728230e-05 6.131070e-04 2.865200e-05 5.416110e-04 2.645350e-05 5.455280e-04 2.658340e-05 5.776050e-04 2.756840e-05 6.203470e-04 2.968180e-05 5.671440e-04 2.706110e-05 5.816770e-04 2.802630e-05 5.971860e-04 2.834120e-05 6.460490e-04 2.922830e-05 6.181770e-04 2.886470e-05 6.820700e-04 2.996520e-05 7.247100e-04 3.084360e-05 7.759860e-04 3.652260e-05 7.327970e-04 3.183040e-05 7.047140e-04 3.098690e-05 6.528700e-04 3.085540e-05 5.392520e-04 3.063810e-05 6.161510e-04 4.034180e-05 5.611310e-04 3.802640e-05 6.344880e-04 3.763000e-05 5.429920e-04 2.921130e-05 5.444720e-04 2.844010e-05 4.276200e-04 2.516010e-05 4.554120e-04 2.597290e-05 4.493410e-04 2.652790e-05 4.447980e-04 2.652980e-05 5.224540e-04 2.924500e-05 5.318340e-04 2.872930e-05 6.182950e-04 3.086820e-05 5.544040e-04 2.884330e-05 4.816930e-04 2.983900e-05 5.126870e-04 3.680080e-05 5.147660e-04 3.098070e-05 6.374550e-04 3.066170e-05 6.554620e-04 3.136400e-05 6.236090e-04 3.019030e-05 5.034590e-04 2.781270e-05 4.734420e-04 2.767240e-05 5.042050e-04 2.785790e-05 4.581950e-04 2.732260e-05 4.970930e-04 3.444130e-05 4.646210e-04 2.758950e-05 4.833420e-04 2.700570e-05 4.999550e-04 2.775630e-05 4.680400e-04 2.724540e-05 4.401610e-04 2.749640e-05 5.400870e-04 2.968690e-05 5.278720e-04 2.916140e-05 4.798640e-04 2.823670e-05 4.239470e-04 2.670990e-05 4.641170e-04 2.732610e-05 4.850710e-04 2.773380e-05 4.509850e-04 2.650590e-05 4.252190e-04 2.605830e-05 4.668860e-04 3.246880e-05 4.577890e-04 3.003070e-05 4.335870e-04 2.667710e-05 4.171130e-04 2.808100e-05 4.619760e-04 2.926000e-05 4.085890e-04 2.576520e-05 3.883100e-04 2.555600e-05 4.259280e-04 2.714870e-05 4.594990e-04 2.723480e-05 4.989350e-04 3.017650e-05 5.038000e-04 3.461010e-05 5.285230e-04 3.049320e-05 5.226110e-04 2.981470e-05 5.656870e-04 3.313260e-05 5.680540e-04 3.609410e-05 6.690510e-04 3.405850e-05 7.017520e-04 3.572780e-05 8.580000e-04 4.240700e-05 9.937860e-04 4.238600e-05 1.048150e-03 4.339060e-05 9.393550e-04 4.021780e-05 7.923770e-04 3.745260e-05 6.740910e-04 3.510610e-05 6.476020e-04 3.581350e-05 5.807330e-04 3.405770e-05 5.137690e-04 3.195450e-05 4.677410e-04 3.071010e-05 4.623170e-04 3.117640e-05 4.200170e-04 2.926980e-05 3.953640e-04 2.823510e-05 3.898140e-04 2.965010e-05 3.740230e-04 3.056350e-05 3.763400e-04 2.849310e-05 4.416930e-04 3.097710e-05 4.142190e-04 2.882360e-05 4.310560e-04 2.963170e-05 3.800950e-04 2.828830e-05 4.202660e-04 2.935760e-05 3.788250e-04 3.088230e-05 4.304580e-04 4.246700e-05 3.979740e-04 4.076640e-05 3.582540e-04 3.844520e-05 3.870910e-04 4.226250e-05 4.417540e-04 4.599760e-05 3.657650e-04 4.018400e-05 3.324060e-04 3.857810e-05 3.685220e-04 4.021680e-05 2.958760e-04 3.619180e-05 3.168040e-04 3.671770e-05 3.264960e-04 3.814460e-05 3.706650e-04 3.982650e-05 3.484040e-04 4.097060e-05 3.222550e-04 4.147550e-05 3.072360e-04 3.938360e-05 3.244510e-04 3.965230e-05 3.133700e-04 4.005980e-05 3.413290e-04 4.850170e-05 3.563580e-04 4.142410e-05 3.395470e-04 4.031830e-05 3.357620e-04 3.971920e-05 4.155700e-04 4.493060e-05 3.353600e-04 4.012680e-05 3.162100e-04 3.943580e-05 3.269470e-04 3.878660e-05 3.888780e-04 4.258220e-05 3.507230e-04 4.199000e-05 3.211280e-04 3.943140e-05 3.272560e-04 4.097850e-05 2.917890e-04 3.825580e-05 2.723260e-04 3.741270e-05 3.548400e-04 4.157350e-05 2.730940e-04 3.785460e-05 3.780600e-04 4.267290e-05 2.990880e-04 3.955590e-05 3.062080e-04 4.016820e-05 3.336380e-04 3.965360e-05 2.642930e-04 3.878470e-05 2.767200e-04 3.812250e-05 2.944730e-04 3.944330e-05 3.438840e-04 4.257430e-05 2.755910e-04 6.728730e-05 3.045650e-04 3.816990e-05 2.774550e-04 3.762880e-05 3.436050e-04 4.336140e-05 2.118240e-04 3.459580e-05 2.653250e-04 3.753270e-05 2.926040e-04 4.036290e-05 2.403630e-04 3.873070e-05 3.572570e-04 4.374210e-05 2.985860e-04 4.154380e-05 3.177100e-04 4.278440e-05 2.885790e-04 3.900820e-05 2.492620e-04 3.700790e-05 3.035410e-04 4.212120e-05 3.292170e-04 4.389020e-05 3.551910e-04 4.452550e-05 2.611890e-04 3.790380e-05 2.890790e-04 4.058870e-05 3.176660e-04 4.286220e-05 2.719400e-04 3.918250e-05 2.556290e-04 3.803010e-05 3.186170e-04 4.457240e-05 3.304370e-04 6.434260e-05 3.212150e-04 4.455150e-05 2.859880e-04 4.279710e-05 3.069420e-04 4.647420e-05 3.184630e-04 4.894080e-05 3.789710e-04 5.346840e-05 3.858650e-04 5.440110e-05 2.805850e-04 4.412170e-05 2.946850e-04 4.333800e-05 3.029370e-04 4.227280e-05 3.182710e-04 4.382730e-05 3.405070e-04 4.429830e-05 3.244850e-04 4.601550e-05 3.121400e-04 8.020120e-05 3.751010e-04 4.571470e-05 3.188810e-04 4.155220e-05 3.491500e-04 4.283890e-05 2.519610e-04 3.849190e-05 3.522580e-04 4.229360e-05 3.775990e-04 4.528940e-05 3.907480e-04 4.509510e-05 3.546410e-04 4.369960e-05 3.278480e-04 4.043990e-05 3.157380e-04 4.303600e-05 3.345870e-04 4.330240e-05 3.374060e-04 4.137300e-05 2.884940e-04 3.937530e-05 3.116010e-04 4.017600e-05 3.484180e-04 4.031820e-05 3.256430e-04 2.829330e-05 3.258680e-04 2.885560e-05 2.949130e-04 2.771760e-05 3.520740e-04 3.010880e-05 3.327740e-04 2.990660e-05 3.242210e-04 3.011480e-05 2.933610e-04 2.847450e-05 3.446060e-04 3.029480e-05 3.207560e-04 2.846260e-05 3.447900e-04 3.067590e-05 3.465690e-04 3.022480e-05 2.913870e-04 2.846990e-05 3.056820e-04 2.878110e-05 2.595220e-04 3.227520e-05 2.769480e-04 3.713070e-05 3.254210e-04 3.678700e-05 3.152450e-04 2.971290e-05 3.121030e-04 2.913680e-05 3.762530e-04 3.729750e-05 3.914630e-04 3.212950e-05 4.720770e-04 3.463980e-05 4.094890e-04 3.266230e-05 3.831580e-04 3.128650e-05 3.500760e-04 3.038000e-05 3.054450e-04 2.996120e-05 3.434850e-04 3.144790e-05 3.455080e-04 3.011890e-05 3.043760e-04 2.913640e-05 2.635850e-04 2.667480e-05 2.900000e-04 2.729600e-05 3.308770e-04 3.078570e-05 3.168560e-04 2.933630e-05 3.279870e-04 2.990530e-05 3.315650e-04 3.018430e-05 2.438170e-04 2.668190e-05 2.905260e-04 2.980360e-05 2.968570e-04 3.043200e-05 2.998830e-04 2.790780e-05 2.966130e-04 3.444750e-05 3.049130e-04 3.590900e-05 3.308690e-04 3.540300e-05 3.030670e-04 2.981530e-05 3.023410e-04 3.029530e-05 2.487670e-04 3.332600e-05 3.099690e-04 3.468300e-05 2.482250e-04 2.732090e-05 2.760110e-04 2.960090e-05 2.710220e-04 2.951250e-05 2.711740e-04 2.982640e-05 2.601570e-04 2.935910e-05 2.419090e-04 2.893340e-05 2.790100e-04 3.076040e-05 2.360450e-04 2.779860e-05 2.734820e-04 3.073880e-05 2.590530e-04 3.113010e-05 2.129930e-04 2.727480e-05 2.205300e-04 3.201980e-05 2.920970e-04 3.130020e-05 1.791730e-04 2.749190e-05 2.271190e-04 3.173030e-05 2.193500e-04 3.048110e-05 2.414980e-04 3.030750e-05 2.347810e-04 2.909560e-05 2.000460e-04 1.988060e-05 2.377080e-04 2.044350e-05 2.549620e-04 2.156820e-05 2.382640e-04 2.163620e-05 2.202990e-04 2.223030e-05 2.206130e-04 2.228180e-05 2.198130e-04 2.244550e-05 2.245690e-04 2.494680e-05 2.158890e-04 2.345740e-05 2.295680e-04 2.277040e-05 2.039340e-04 2.183360e-05 2.170470e-04 2.211360e-05 2.280540e-04 2.778760e-05 2.290840e-04 3.208900e-05 2.295780e-04 2.533740e-05 2.088020e-04 2.506950e-05 2.270230e-04 2.646760e-05 2.548750e-04 2.625970e-05 2.542780e-04 2.638950e-05 2.192710e-04 2.658520e-05 2.233170e-04 2.834870e-05 2.269950e-04 3.832980e-05 1.741830e-04 2.731850e-05 2.077740e-04 2.775460e-05 2.168850e-04 2.813770e-05 2.494080e-04 2.995190e-05 1.847190e-04 2.777430e-05 2.131180e-04 2.858370e-05 2.326600e-04 2.837060e-05 2.375710e-04 2.972310e-05 1.919940e-04 2.826280e-05 2.118940e-04 2.896070e-05 1.799910e-04 2.697110e-05 2.084800e-04 2.756780e-05 2.048710e-04 2.893760e-05 2.039400e-04 2.849890e-05 1.479110e-04 2.763830e-05 1.746140e-04 3.125820e-05 1.938630e-04 2.993970e-05 2.273160e-04 3.199730e-05 1.893220e-04 2.967660e-05 2.544100e-04 3.275580e-05 2.620560e-04 3.528150e-05 2.452550e-04 3.286810e-05 2.370690e-04 3.245880e-05 2.067780e-04 3.114670e-05 2.138080e-04 3.655750e-05 2.745210e-04 4.825130e-05 2.388920e-04 3.646220e-05 2.930640e-04 3.490560e-05 1.833240e-04 3.570090e-05 1.920090e-04 3.617750e-05 2.599530e-04 3.908610e-05 1.867340e-04 3.514540e-05 1.738400e-04 3.658140e-05 2.480720e-04 3.667500e-05 1.996910e-04 3.531350e-05 2.162660e-04 3.692390e-05 2.663410e-04 3.805280e-05 1.941180e-04 3.663090e-05 1.642050e-04 3.523890e-05 2.689110e-04 3.838150e-05 2.409360e-04 4.965310e-05 1.847900e-04 5.041100e-05 1.780040e-04 3.219900e-05 2.234630e-04 3.296830e-05 1.370700e-04 3.059550e-05 2.075870e-04 3.348930e-05 1.341410e-04 3.284120e-05 1.832390e-04 3.434580e-05 1.508510e-04 3.167450e-05 1.333470e-04 4.119730e-05 1.524640e-04 3.975300e-05 1.713540e-04 3.992650e-05 1.498740e-04 3.718810e-05 1.630350e-04 3.503340e-05 1.212000e-04 3.026400e-05 4.009120e-05 3.562720e-05 1.973950e-04 3.471480e-05 1.461300e-04 2.871250e-05 1.476770e-04 2.882960e-05 1.175550e-04 2.928400e-05 1.072990e-04 2.749720e-05 1.482710e-04 3.188290e-05 1.242280e-04 2.794560e-05 1.039130e-04 2.777680e-05 2.152580e-04 3.162130e-05 1.210340e-04 2.838330e-05 5.914980e-05 2.777230e-05 9.871170e-05 2.978000e-05 1.401770e-04 3.355030e-05 6.826660e-05 2.925510e-05 1.677930e-04 3.064590e-05 6.853110e-05 2.514600e-05 1.431750e-04 3.020160e-05 1.100100e-04 2.916770e-05 1.275130e-04 2.784630e-05 8.670860e-05 2.643850e-05 1.328280e-04 3.301160e-05 1.012330e-04 2.817270e-05 1.299050e-04 2.832730e-05 1.291000e-04 2.945080e-05 6.946560e-05 2.886690e-05 9.621770e-05 2.719220e-05 1.074810e-04 2.955330e-05 1.353190e-04 3.123540e-05 1.534920e-04 2.976960e-05 1.261350e-04 2.864630e-05 9.923770e-05 3.167100e-05 1.104340e-04 3.648610e-05 9.240890e-05 2.888430e-05 -5.476390e-06 2.996170e-05 1.150100e-04 3.324890e-05 1.000750e-04 3.427050e-05 6.576840e-05 3.566030e-05 -2.233520e-04 9.337150e-05 1.557920e-04 4.651850e-05 8.327750e-05 3.995890e-05 1.121130e-04 4.034960e-05 7.820600e-05 3.808170e-05 7.453110e-05 5.616330e-05 1.553410e-04 5.948510e-05 8.860320e-05 4.228480e-05 8.186370e-05 6.762710e-05 4.912510e-05 6.950520e-05 ''') ImportString(u'flux_090(numeric),+-',''' 2.189310e+03 3.957380e+03 -3.848260e+01 2.944600e+01 -2.235260e-01 2.569630e+00 1.148560e-01 1.804310e-01 1.187360e-02 3.671730e-02 -5.200170e-03 7.960460e-03 -1.414540e-03 2.831620e-03 2.513630e-04 1.136470e-03 4.521350e-04 9.816890e-04 3.368390e-04 4.359300e-04 -4.878070e-04 3.649430e-04 1.040890e-04 3.122470e-04 1.988550e-04 2.699880e-04 1.019220e-04 2.450900e-04 -1.046140e-04 1.368130e-04 1.014340e-04 7.702500e-05 1.627390e-05 4.172710e-05 3.418940e-05 3.641600e-05 -7.669300e-06 2.265170e-05 2.431390e-05 2.461040e-05 1.886670e-05 3.256710e-05 9.342400e-05 4.966980e-05 8.700760e-05 9.238170e-05 3.042900e-04 1.879750e-04 5.940840e-04 2.743870e-04 1.003320e-03 3.625380e-04 1.336580e-03 4.839930e-04 3.792900e-04 4.150900e-04 2.378790e-04 3.557360e-04 1.609710e-03 4.530660e-04 1.343500e-03 3.489400e-04 1.238390e-03 2.893310e-04 8.969520e-04 2.250110e-04 4.661140e-04 1.607770e-04 8.105390e-04 1.487010e-04 7.264460e-04 1.226090e-04 7.527700e-04 1.148020e-04 4.630340e-04 7.487960e-05 5.272710e-04 7.094880e-05 4.811790e-04 6.802030e-05 4.768310e-04 6.411700e-05 4.895900e-04 6.535510e-05 6.122410e-04 6.522840e-05 7.670080e-04 7.055660e-05 7.904070e-04 6.722970e-05 7.670350e-04 6.367560e-05 6.694930e-04 5.760900e-05 4.834540e-04 5.092890e-05 4.135160e-04 4.699490e-05 4.042320e-04 4.437900e-05 4.783140e-04 4.665430e-05 5.667440e-04 4.634570e-05 6.032080e-04 4.720030e-05 5.421420e-04 4.467770e-05 6.051960e-04 4.692450e-05 5.844670e-04 4.596860e-05 5.009010e-04 4.121890e-05 4.163550e-04 3.437820e-05 4.029660e-04 3.248040e-05 3.760420e-04 3.112840e-05 2.921990e-04 2.707350e-05 4.426360e-04 3.330880e-05 3.394490e-04 2.853650e-05 3.824900e-04 3.266230e-05 4.405500e-04 3.842730e-05 3.974210e-04 3.204660e-05 3.742320e-04 3.002110e-05 3.485960e-04 2.948280e-05 3.402540e-04 2.646820e-05 3.316290e-04 2.685680e-05 2.728030e-04 2.464370e-05 3.453200e-04 2.671720e-05 3.008220e-04 2.507950e-05 3.167260e-04 2.842040e-05 3.018550e-04 2.785470e-05 3.418200e-04 2.629130e-05 4.013920e-04 2.817840e-05 3.879520e-04 2.680130e-05 3.766470e-04 2.726420e-05 3.904920e-04 2.731480e-05 3.864770e-04 2.630150e-05 4.165190e-04 2.741630e-05 4.073560e-04 2.729510e-05 3.717290e-04 2.527240e-05 3.854870e-04 2.663850e-05 3.890700e-04 2.767200e-05 4.633250e-04 2.888230e-05 4.832750e-04 2.909130e-05 5.989080e-04 3.145740e-05 5.622960e-04 3.018420e-05 6.573950e-04 3.396660e-05 6.209600e-04 3.208950e-05 5.730690e-04 3.570650e-05 4.638390e-04 2.819540e-05 4.755720e-04 2.808470e-05 4.856120e-04 2.868640e-05 4.598610e-04 2.728380e-05 4.053160e-04 2.613350e-05 4.278810e-04 2.729730e-05 4.120400e-04 2.552130e-05 4.318480e-04 2.593390e-05 3.666020e-04 2.306130e-05 3.703610e-04 2.338260e-05 4.494530e-04 2.667210e-05 4.441930e-04 2.690620e-05 3.794860e-04 2.332380e-05 4.032390e-04 2.439100e-05 4.400150e-04 2.454610e-05 4.482450e-04 2.531100e-05 4.082950e-04 2.393610e-05 4.453790e-04 2.591680e-05 3.885490e-04 2.303500e-05 4.058890e-04 2.492030e-05 4.364210e-04 2.476390e-05 4.208970e-04 2.308230e-05 3.966090e-04 2.264030e-05 4.038420e-04 2.327710e-05 3.766320e-04 2.243100e-05 4.029770e-04 2.313100e-05 4.271150e-04 2.387070e-05 3.986970e-04 2.241500e-05 3.759110e-04 2.349600e-05 3.541990e-04 2.689230e-05 4.950470e-04 3.213940e-05 4.682630e-04 2.717000e-05 4.237930e-04 2.555710e-05 4.268360e-04 2.544000e-05 4.308350e-04 2.560270e-05 4.162720e-04 2.549180e-05 4.407000e-04 2.541610e-05 5.012660e-04 2.869820e-05 4.968080e-04 2.977780e-05 4.860740e-04 2.664370e-05 4.918510e-04 2.691120e-05 5.597910e-04 3.824190e-05 6.136890e-04 3.902550e-05 6.752500e-04 4.012790e-05 7.669390e-04 4.342700e-05 8.135920e-04 4.481290e-05 7.947810e-04 4.405070e-05 7.082690e-04 4.112800e-05 6.424060e-04 3.989990e-05 6.482680e-04 4.232460e-05 6.848350e-04 4.067380e-05 7.432990e-04 4.187700e-05 7.561130e-04 4.200300e-05 8.847240e-04 4.594600e-05 1.051090e-03 4.990120e-05 1.072130e-03 4.944570e-05 9.847120e-04 4.749810e-05 9.632770e-04 4.773560e-05 1.166510e-03 5.315660e-05 1.102740e-03 5.123650e-05 1.005590e-03 5.068740e-05 8.306470e-04 4.337450e-05 9.148520e-04 6.053090e-05 9.024780e-04 4.797280e-05 8.131140e-04 4.335410e-05 7.865150e-04 4.287560e-05 7.344510e-04 4.189260e-05 7.761440e-04 4.291760e-05 8.759570e-04 4.627650e-05 1.124620e-03 5.085710e-05 1.163600e-03 5.160870e-05 1.125660e-03 5.017850e-05 9.262800e-04 4.618390e-05 8.268290e-04 4.205060e-05 8.068440e-04 3.770970e-05 7.715470e-04 3.639450e-05 8.273580e-04 3.791650e-05 9.249840e-04 4.000860e-05 1.231950e-03 4.618910e-05 1.029860e-03 4.198100e-05 9.347270e-04 4.229940e-05 9.118660e-04 4.401100e-05 9.238550e-04 3.939530e-05 7.560730e-04 3.691880e-05 6.870150e-04 3.534350e-05 6.963400e-04 3.504840e-05 6.522050e-04 3.382380e-05 5.132750e-04 2.945600e-05 5.119080e-04 2.960120e-05 4.563810e-04 2.838800e-05 4.477500e-04 2.806660e-05 4.245150e-04 2.717070e-05 5.459650e-04 3.160100e-05 6.898200e-04 3.691700e-05 6.735890e-04 3.457740e-05 5.433300e-04 5.174020e-05 4.444810e-04 3.680070e-05 4.379060e-04 2.837160e-05 4.768070e-04 3.008840e-05 4.259950e-04 2.795140e-05 4.641260e-04 2.881110e-05 4.342080e-04 2.875370e-05 4.511970e-04 2.947330e-05 3.970060e-04 2.677690e-05 4.756860e-04 2.949110e-05 5.503190e-04 3.244230e-05 6.075410e-04 3.344380e-05 4.510560e-04 2.874830e-05 3.846050e-04 2.365440e-05 3.849010e-04 2.148730e-05 4.165880e-04 2.250840e-05 3.656070e-04 2.059810e-05 4.180400e-04 2.185650e-05 3.786980e-04 2.147910e-05 3.356630e-04 2.044950e-05 3.666890e-04 2.255640e-05 4.225550e-04 2.254930e-05 4.186750e-04 2.240740e-05 3.535560e-04 2.054840e-05 4.114510e-04 2.272900e-05 4.533170e-04 2.359970e-05 4.470720e-04 2.364200e-05 3.550670e-04 2.217940e-05 3.780510e-04 2.103430e-05 3.766290e-04 2.168190e-05 3.476090e-04 2.109610e-05 3.331580e-04 2.128430e-05 3.804490e-04 2.584950e-05 3.198560e-04 2.130740e-05 3.043750e-04 1.969080e-05 2.907190e-04 1.867410e-05 3.212390e-04 2.083660e-05 2.907430e-04 2.208830e-05 2.729850e-04 1.901210e-05 3.008320e-04 1.948910e-05 3.215500e-04 2.038920e-05 3.509110e-04 2.108900e-05 4.497720e-04 2.466890e-05 3.881780e-04 2.325600e-05 3.362740e-04 2.228140e-05 3.404450e-04 2.348080e-05 3.221980e-04 1.993270e-05 2.899130e-04 2.117170e-05 2.172050e-04 1.685090e-05 2.633540e-04 1.970370e-05 2.228880e-04 1.721260e-05 2.611010e-04 1.922450e-05 2.433760e-04 1.838170e-05 2.553230e-04 1.973050e-05 2.269310e-04 1.741710e-05 2.463680e-04 1.871310e-05 2.689320e-04 1.961470e-05 2.710120e-04 1.943940e-05 2.743060e-04 1.972980e-05 3.165980e-04 2.142750e-05 2.964300e-04 2.031540e-05 3.814600e-04 2.643690e-05 3.934200e-04 2.383800e-05 3.762790e-04 2.309830e-05 3.223700e-04 2.241240e-05 2.508420e-04 2.127430e-05 2.272260e-04 2.458500e-05 2.433310e-04 2.555020e-05 2.466050e-04 2.426480e-05 2.157370e-04 1.834870e-05 2.337090e-04 1.896410e-05 1.730880e-04 1.651370e-05 1.834370e-04 1.712470e-05 1.767030e-04 1.707730e-05 1.850980e-04 1.773440e-05 2.317430e-04 1.983130e-05 2.363810e-04 1.936410e-05 3.163970e-04 2.251870e-05 2.686570e-04 2.016570e-05 1.817340e-04 1.797690e-05 1.769360e-04 2.172880e-05 2.315540e-04 2.106780e-05 3.607100e-04 2.410770e-05 4.228070e-04 2.547590e-05 3.230370e-04 2.151710e-05 2.113980e-04 1.834630e-05 1.593280e-04 1.620070e-05 1.826030e-04 1.696200e-05 1.621360e-04 1.680160e-05 2.026230e-04 2.225970e-05 1.882860e-04 1.820230e-05 1.853480e-04 1.683110e-05 1.679850e-04 1.660570e-05 1.901490e-04 1.807590e-05 1.984030e-04 1.883860e-05 2.170980e-04 1.896430e-05 2.077690e-04 1.843080e-05 1.890880e-04 1.847100e-05 1.593040e-04 1.696240e-05 1.694350e-04 1.633860e-05 1.741820e-04 1.702330e-05 1.712220e-04 1.702000e-05 1.569070e-04 1.648740e-05 1.710360e-04 1.932610e-05 1.480450e-04 1.741920e-05 1.770950e-04 1.771470e-05 1.654610e-04 1.788410e-05 1.622280e-04 1.816770e-05 1.518910e-04 1.634970e-05 1.301070e-04 1.531500e-05 1.722310e-04 1.723240e-05 1.585080e-04 1.634870e-05 2.195240e-04 2.012820e-05 1.717000e-04 2.101260e-05 2.155510e-04 2.014470e-05 1.950330e-04 1.838060e-05 2.110570e-04 1.978050e-05 1.967610e-04 2.181210e-05 2.929480e-04 2.323980e-05 2.730930e-04 2.315850e-05 3.643080e-04 2.769350e-05 5.202760e-04 3.200170e-05 5.679730e-04 3.347840e-05 4.861390e-04 2.957460e-05 3.499850e-04 2.538360e-05 2.786060e-04 2.355510e-05 2.604830e-04 2.296670e-05 2.333050e-04 2.220100e-05 2.039960e-04 2.062450e-05 1.939900e-04 2.078750e-05 1.905580e-04 2.020830e-05 1.616390e-04 1.825540e-05 1.370730e-04 1.679840e-05 1.508030e-04 1.873990e-05 1.408870e-04 1.855940e-05 1.320390e-04 1.718750e-05 1.693530e-04 1.944680e-05 1.538960e-04 1.776660e-05 1.902140e-04 2.082780e-05 1.537900e-04 1.824720e-05 1.369430e-04 1.662840e-05 1.639220e-04 1.982100e-05 1.368490e-04 2.442950e-05 1.462460e-04 2.591340e-05 1.347690e-04 2.453050e-05 1.356830e-04 2.491550e-05 1.754900e-04 2.952930e-05 8.863090e-05 2.008080e-05 1.278080e-04 2.381000e-05 1.359020e-04 2.598260e-05 1.148680e-04 2.250130e-05 1.161110e-04 2.188610e-05 8.540910e-05 1.888680e-05 1.108980e-04 2.134620e-05 1.616390e-04 2.835570e-05 1.320740e-04 2.631360e-05 1.156300e-04 2.426370e-05 1.150670e-04 2.228250e-05 1.178350e-04 2.395820e-05 8.479670e-05 2.409060e-05 1.525950e-04 2.806050e-05 1.202580e-04 2.390140e-05 1.388730e-04 2.666730e-05 1.451350e-04 2.664400e-05 1.180570e-04 2.390850e-05 1.401550e-04 2.688890e-05 1.543920e-04 2.761250e-05 1.672210e-04 2.718470e-05 1.580430e-04 2.773210e-05 1.177980e-04 2.366220e-05 1.113850e-04 2.432650e-05 1.201190e-04 2.552750e-05 1.140480e-04 2.401440e-05 1.556630e-04 2.691640e-05 7.708020e-05 1.971010e-05 1.410430e-04 2.650430e-05 1.013070e-04 2.143620e-05 1.206690e-04 2.545310e-05 1.209640e-04 2.480310e-05 1.093820e-04 2.455250e-05 8.053830e-05 2.107410e-05 8.219740e-05 2.049970e-05 1.577030e-04 2.881110e-05 1.100950e-04 3.925630e-05 1.254610e-04 2.337630e-05 9.541680e-05 2.222230e-05 1.030650e-04 2.384190e-05 8.753700e-05 2.322180e-05 1.151360e-04 2.409380e-05 1.171970e-04 2.588510e-05 7.645200e-05 2.172200e-05 1.191450e-04 2.548730e-05 9.405710e-05 2.381870e-05 1.179810e-04 2.619000e-05 9.514210e-05 2.277540e-05 6.651240e-05 2.067810e-05 1.309440e-04 2.745420e-05 1.288110e-04 2.672590e-05 1.084650e-04 2.509860e-05 8.753660e-05 2.231390e-05 9.551640e-05 2.399950e-05 1.097720e-04 2.594050e-05 7.027060e-05 2.077640e-05 1.226920e-04 2.560470e-05 1.090420e-04 2.560110e-05 1.300320e-04 4.195140e-05 1.175640e-04 2.584890e-05 1.450130e-04 3.035600e-05 1.194760e-04 2.998020e-05 1.020770e-04 2.873730e-05 1.229290e-04 2.996850e-05 1.041010e-04 2.852600e-05 1.090500e-04 2.931620e-05 8.305970e-05 2.324780e-05 8.550330e-05 2.280670e-05 1.053820e-04 2.492270e-05 9.525930e-05 2.348280e-05 1.217570e-04 2.733860e-05 1.378520e-04 4.244890e-05 1.053260e-04 2.329600e-05 9.978090e-05 2.363680e-05 1.112200e-04 2.503330e-05 7.541960e-05 2.100170e-05 1.104820e-04 2.495930e-05 1.176530e-04 2.519410e-05 1.482200e-04 2.842620e-05 1.353610e-04 2.814750e-05 1.046080e-04 2.223810e-05 1.043620e-04 2.484270e-05 1.716590e-04 3.029100e-05 1.292850e-04 2.537210e-05 1.152540e-04 2.478310e-05 1.363750e-04 2.692090e-05 1.323550e-04 2.566820e-05 1.142670e-04 1.680600e-05 1.178140e-04 1.716560e-05 1.202570e-04 1.795190e-05 1.261820e-04 1.803730e-05 1.442150e-04 1.993030e-05 1.320060e-04 1.872940e-05 1.089160e-04 1.729060e-05 1.230990e-04 1.804120e-05 1.242430e-04 1.836550e-05 1.507060e-04 2.067550e-05 1.346240e-04 1.853340e-05 1.276210e-04 1.843930e-05 8.129750e-05 1.493320e-05 1.234800e-04 2.254670e-05 1.298080e-04 2.496220e-05 1.177570e-04 2.190980e-05 1.208600e-04 1.827070e-05 1.198560e-04 1.842010e-05 1.378740e-04 2.286000e-05 1.862510e-04 2.280360e-05 2.288730e-04 2.461690e-05 1.847750e-04 2.248560e-05 1.264400e-04 1.829690e-05 1.361440e-04 1.935220e-05 1.421310e-04 2.107330e-05 1.487440e-04 2.140700e-05 1.345510e-04 1.912050e-05 1.106680e-04 1.720580e-05 7.928700e-05 1.499060e-05 9.544570e-05 1.605820e-05 1.208860e-04 1.916950e-05 9.626840e-05 1.676630e-05 1.300420e-04 1.939780e-05 1.628070e-04 2.185710e-05 9.024460e-05 1.610480e-05 1.071200e-04 1.813280e-05 1.108670e-04 1.898680e-05 1.206780e-04 1.844400e-05 1.546930e-04 2.460740e-05 1.427230e-04 2.503730e-05 1.321720e-04 2.251250e-05 1.047630e-04 1.782200e-05 1.345880e-04 2.007590e-05 8.450010e-05 1.873870e-05 1.125290e-04 2.086700e-05 1.086640e-04 1.788820e-05 1.009180e-04 1.738930e-05 1.077490e-04 1.869770e-05 1.314380e-04 2.102130e-05 1.028240e-04 1.841490e-05 1.005090e-04 1.797710e-05 9.947960e-05 1.806900e-05 6.297460e-05 1.557010e-05 1.019190e-04 1.838910e-05 6.407510e-05 1.559560e-05 8.464310e-05 1.655980e-05 6.804990e-05 1.764170e-05 1.141680e-04 2.024150e-05 6.302310e-05 1.570300e-05 9.441250e-05 2.035660e-05 9.153530e-05 1.928880e-05 9.232240e-05 1.831240e-05 1.118250e-04 2.019520e-05 8.523570e-05 1.278950e-05 8.677740e-05 1.229410e-05 8.181380e-05 1.221410e-05 8.308390e-05 1.274030e-05 8.219700e-05 1.367640e-05 8.920160e-05 1.375670e-05 7.258440e-05 1.276070e-05 9.015920e-05 1.528490e-05 1.160040e-04 1.677330e-05 9.919070e-05 1.470680e-05 9.141510e-05 1.373840e-05 8.809160e-05 1.316330e-05 8.480170e-05 1.737330e-05 9.242640e-05 2.024270e-05 9.734510e-05 1.487870e-05 7.828420e-05 1.450600e-05 8.811210e-05 1.535970e-05 8.860830e-05 1.534490e-05 1.206280e-04 1.732040e-05 6.816770e-05 1.467670e-05 1.118430e-04 1.857100e-05 7.659590e-05 2.120910e-05 8.801850e-05 1.723730e-05 9.765050e-05 1.728740e-05 9.480540e-05 1.697640e-05 5.330690e-05 1.520880e-05 5.542910e-05 1.482640e-05 9.426510e-05 1.716280e-05 7.990070e-05 1.609890e-05 9.082360e-05 1.742080e-05 6.125430e-05 1.493280e-05 8.153730e-05 1.723310e-05 8.734000e-05 1.680450e-05 6.825010e-05 1.563060e-05 6.526390e-05 1.633400e-05 4.182360e-05 1.379650e-05 4.842100e-05 1.478030e-05 8.598900e-05 1.910990e-05 6.012170e-05 1.662580e-05 7.657060e-05 1.778200e-05 5.923120e-05 1.617660e-05 1.363290e-04 2.160890e-05 9.253890e-05 2.026420e-05 9.464420e-05 1.940600e-05 8.594680e-05 1.891840e-05 7.388890e-05 1.809060e-05 1.260230e-04 2.500290e-05 9.598130e-05 2.823840e-05 1.145890e-04 2.260720e-05 1.214390e-04 2.085150e-05 7.017010e-05 2.061090e-05 9.425200e-05 2.210500e-05 9.207530e-05 2.226070e-05 9.367040e-05 2.099240e-05 1.041640e-04 2.307210e-05 8.767790e-05 2.108680e-05 6.132360e-05 1.953140e-05 3.383470e-05 1.734240e-05 1.274730e-04 2.345420e-05 4.972170e-05 1.891460e-05 8.214450e-05 2.149860e-05 1.013380e-04 2.212960e-05 6.709950e-05 2.603140e-05 9.392430e-05 3.063280e-05 8.199390e-05 1.927460e-05 9.488300e-05 1.994580e-05 5.338870e-05 1.652970e-05 8.752200e-05 1.947110e-05 6.659600e-05 1.961090e-05 9.018510e-05 2.120990e-05 7.002800e-05 1.857560e-05 5.944480e-05 2.328890e-05 8.569420e-05 2.432770e-05 5.097920e-05 2.151620e-05 5.314880e-05 2.009670e-05 7.485610e-05 2.078880e-05 4.125950e-05 1.585860e-05 4.223670e-05 1.982160e-05 1.005500e-04 2.249630e-05 6.217580e-05 1.673370e-05 6.807440e-05 1.769030e-05 2.896580e-05 1.485400e-05 5.081910e-05 1.643620e-05 6.275850e-05 2.032010e-05 2.932820e-05 1.374720e-05 2.485330e-05 1.426590e-05 7.546890e-05 1.835550e-05 6.925820e-05 1.804420e-05 2.421510e-05 1.524950e-05 4.176270e-05 1.684660e-05 3.131620e-05 1.762550e-05 2.991900e-05 1.661930e-05 2.584090e-05 1.387280e-05 4.416010e-05 1.544960e-05 5.502680e-05 1.743100e-05 6.226740e-05 1.879430e-05 4.787250e-05 1.606620e-05 2.452870e-05 1.336580e-05 2.902590e-05 1.606590e-05 5.583530e-05 1.716640e-05 9.065510e-05 1.993000e-05 6.089810e-05 1.789940e-05 3.587730e-05 1.730450e-05 4.004220e-05 1.596700e-05 1.560880e-05 1.376360e-05 1.024310e-04 2.249720e-05 4.332750e-05 1.574300e-05 3.633940e-05 1.579770e-05 2.198550e-05 1.575820e-05 4.997950e-05 2.082940e-05 4.277200e-05 1.741470e-05 -3.043260e-06 1.529930e-05 5.554110e-05 2.039130e-05 3.265840e-05 1.832360e-05 5.363930e-06 1.629740e-05 -5.950040e-05 3.664910e-05 5.674340e-05 2.668630e-05 -5.359900e-06 1.441850e-05 4.501340e-05 2.293660e-05 5.012770e-06 1.789040e-05 2.662400e-05 2.937400e-05 1.719700e-05 2.522640e-05 2.278130e-05 2.083440e-05 2.830410e-05 3.645150e-05 7.612740e-05 4.944380e-05 ''') ImportString(u'flux_model_0_5(numeric)',''' 2.638349e-03 3.500185e-03 4.195564e-03 5.033681e-03 6.662102e-03 9.691017e-03 1.383798e-02 1.730952e-02 1.782465e-02 1.491672e-02 1.052511e-02 6.985221e-03 5.120528e-03 4.457958e-03 4.315053e-03 4.329839e-03 4.390443e-03 4.461482e-03 4.517663e-03 4.541635e-03 4.527193e-03 4.484314e-03 4.441404e-03 4.425652e-03 4.452141e-03 4.523108e-03 4.630249e-03 4.762861e-03 4.916740e-03 5.085519e-03 5.226392e-03 5.294072e-03 5.323343e-03 5.443004e-03 5.770480e-03 6.351340e-03 7.204733e-03 8.298443e-03 9.389546e-03 1.001495e-02 9.825170e-03 8.917985e-03 7.738226e-03 6.692107e-03 5.924180e-03 5.398249e-03 5.062076e-03 4.911711e-03 4.935686e-03 5.067608e-03 5.194373e-03 5.222608e-03 5.144365e-03 5.037660e-03 4.988319e-03 5.019739e-03 5.093912e-03 5.152969e-03 5.163832e-03 5.131372e-03 5.085257e-03 5.060005e-03 5.083899e-03 5.199510e-03 5.465772e-03 5.893727e-03 6.344101e-03 6.561472e-03 6.398052e-03 5.966652e-03 5.515189e-03 5.208256e-03 5.069245e-03 5.086064e-03 5.346950e-03 6.141645e-03 7.876758e-03 1.062103e-02 1.359337e-02 1.543759e-02 1.537860e-02 1.385804e-02 1.182906e-02 9.895545e-03 8.278267e-03 7.071151e-03 6.280646e-03 5.812481e-03 5.550927e-03 5.417207e-03 5.359876e-03 5.344851e-03 5.357774e-03 5.402117e-03 5.497721e-03 5.658911e-03 5.885693e-03 6.181025e-03 6.561367e-03 7.014364e-03 7.409760e-03 7.541663e-03 7.306973e-03 6.819452e-03 6.311415e-03 5.948962e-03 5.776437e-03 5.773050e-03 5.914581e-03 6.169031e-03 6.463208e-03 6.718062e-03 6.973356e-03 7.450235e-03 8.391633e-03 9.788708e-03 1.132921e-02 1.265226e-02 1.357322e-02 1.391399e-02 1.336779e-02 1.182625e-02 9.745662e-03 7.877703e-03 6.689730e-03 6.161845e-03 6.036965e-03 6.076394e-03 6.142457e-03 6.193032e-03 6.255145e-03 6.414870e-03 6.805693e-03 7.540205e-03 8.561670e-03 9.511556e-03 9.884710e-03 9.445193e-03 8.462913e-03 7.455984e-03 6.764556e-03 6.416781e-03 6.279466e-03 6.238789e-03 6.236223e-03 6.247693e-03 6.264526e-03 6.287873e-03 6.319859e-03 6.355495e-03 6.377765e-03 6.374531e-03 6.355951e-03 6.354557e-03 6.411426e-03 6.557892e-03 6.792885e-03 7.072779e-03 7.351488e-03 7.635578e-03 7.955848e-03 8.261680e-03 8.407689e-03 8.284276e-03 7.936441e-03 7.512128e-03 7.135670e-03 6.858952e-03 6.687729e-03 6.604043e-03 6.582204e-03 6.605375e-03 6.680189e-03 6.821240e-03 7.023224e-03 7.230743e-03 7.361530e-03 7.372550e-03 7.298509e-03 7.233538e-03 7.254974e-03 7.367637e-03 7.511028e-03 7.623095e-03 7.735142e-03 7.997609e-03 8.572497e-03 9.436619e-03 1.027271e-02 1.063284e-02 1.029918e-02 9.494261e-03 8.706038e-03 8.313137e-03 8.354012e-03 8.587754e-03 8.702097e-03 8.541980e-03 8.170747e-03 7.770373e-03 7.473888e-03 7.304975e-03 7.224075e-03 7.198561e-03 7.216503e-03 7.286777e-03 7.436890e-03 7.723366e-03 8.192892e-03 8.808263e-03 9.398174e-03 9.765725e-03 9.885055e-03 9.999391e-03 1.046360e-02 1.148160e-02 1.296156e-02 1.452561e-02 1.563003e-02 1.583690e-02 1.522935e-02 1.459465e-02 1.518075e-02 1.827562e-02 2.481496e-02 3.467502e-02 4.567079e-02 5.356495e-02 5.430129e-02 4.711052e-02 3.528816e-02 2.363950e-02 1.531288e-02 1.076014e-02 8.852233e-03 8.316687e-03 8.363831e-03 8.624770e-03 8.903523e-03 9.074247e-03 9.084827e-03 8.982937e-03 8.856234e-03 8.784448e-03 8.848024e-03 9.158531e-03 9.811356e-03 1.074434e-02 1.165930e-02 1.218393e-02 1.213135e-02 1.163627e-02 1.097359e-02 1.033227e-02 9.752006e-03 9.225862e-03 8.785322e-03 8.467982e-03 8.279487e-03 8.187440e-03 8.150270e-03 8.144439e-03 8.148754e-03 8.162733e-03 8.178647e-03 8.197497e-03 8.220041e-03 8.244955e-03 8.276387e-03 8.315169e-03 8.359424e-03 8.407638e-03 8.463450e-03 8.570347e-03 8.833080e-03 9.418809e-03 1.049098e-02 1.207705e-02 1.394416e-02 1.563976e-02 1.666920e-02 1.671853e-02 1.578877e-02 1.418209e-02 1.238207e-02 1.083961e-02 9.811778e-03 9.298561e-03 9.155245e-03 9.191974e-03 9.271901e-03 9.316526e-03 9.300929e-03 9.240680e-03 9.159161e-03 9.083294e-03 9.030324e-03 9.001008e-03 8.991310e-03 8.975531e-03 8.913346e-03 8.773651e-03 8.564204e-03 8.356713e-03 8.252824e-03 8.288762e-03 8.367720e-03 8.304888e-03 7.970950e-03 7.403291e-03 6.773978e-03 6.254741e-03 5.925057e-03 5.762625e-03 5.710123e-03 5.712811e-03 5.743801e-03 5.791515e-03 5.854992e-03 5.933499e-03 6.019581e-03 6.086817e-03 6.106109e-03 6.067309e-03 5.986337e-03 5.908203e-03 5.875052e-03 5.918834e-03 6.041366e-03 6.220534e-03 6.400033e-03 6.517583e-03 6.524953e-03 6.426368e-03 6.263959e-03 6.100332e-03 5.974766e-03 5.903430e-03 5.879032e-03 5.898911e-03 5.977836e-03 6.173112e-03 6.594283e-03 7.436107e-03 9.016654e-03 1.175857e-02 1.601445e-02 2.165502e-02 2.765495e-02 3.215739e-02 3.336180e-02 3.063611e-02 2.506963e-02 1.871558e-02 1.336728e-02 9.805501e-03 7.877279e-03 7.056336e-03 6.912922e-03 7.267903e-03 8.107245e-03 9.387062e-03 1.087789e-02 1.214594e-02 1.276774e-02 1.261191e-02 1.203416e-02 1.185113e-02 1.323157e-02 1.750119e-02 2.566630e-02 3.757828e-02 5.098214e-02 6.177396e-02 6.574720e-02 6.122219e-02 5.017444e-02 3.703641e-02 2.601842e-02 1.914604e-02 1.609990e-02 1.523513e-02 1.484655e-02 1.393847e-02 1.236248e-02 1.052907e-02 8.939297e-03 7.912371e-03 7.532069e-03 7.778645e-03 8.592328e-03 9.855602e-03 1.128326e-02 1.244546e-02 1.291175e-02 1.250033e-02 1.139416e-02 1.001516e-02 8.784835e-03 7.926752e-03 7.461348e-03 7.303685e-03 7.383199e-03 7.694687e-03 8.293150e-03 9.232636e-03 1.045986e-02 1.175012e-02 1.273095e-02 1.305434e-02 1.259108e-02 1.152228e-02 1.023135e-02 9.093793e-03 8.313852e-03 7.898860e-03 7.755840e-03 7.782374e-03 7.908337e-03 8.100160e-03 8.323000e-03 8.533517e-03 8.682410e-03 8.735127e-03 8.681234e-03 8.541031e-03 8.350113e-03 8.153214e-03 7.989999e-03 7.891449e-03 7.867648e-03 7.904958e-03 7.965628e-03 8.003064e-03 7.989327e-03 7.921439e-03 7.825663e-03 7.727321e-03 7.647310e-03 7.596543e-03 7.599218e-03 7.677603e-03 7.841384e-03 8.053457e-03 8.234243e-03 8.297769e-03 8.201583e-03 7.976229e-03 7.706096e-03 7.488867e-03 7.380434e-03 7.390142e-03 7.474693e-03 7.573418e-03 7.625229e-03 7.605841e-03 7.525440e-03 7.430649e-03 7.370103e-03 7.391247e-03 7.527406e-03 7.820009e-03 8.307527e-03 9.008409e-03 9.879768e-03 1.077957e-02 1.149549e-02 1.181835e-02 1.164388e-02 1.103751e-02 1.021328e-02 9.460298e-03 9.022510e-03 9.035720e-03 9.496499e-03 1.027977e-02 1.117551e-02 1.194092e-02 1.235750e-02 1.229592e-02 1.176092e-02 1.087313e-02 9.851906e-03 8.918431e-03 8.243966e-03 7.913210e-03 7.928296e-03 8.224633e-03 8.692377e-03 9.191621e-03 9.585596e-03 9.773852e-03 9.727390e-03 9.477054e-03 9.102014e-03 8.685155e-03 8.295125e-03 7.973884e-03 7.732108e-03 7.558865e-03 7.479201e-03 7.520754e-03 7.967748e-03 9.520626e-03 1.389450e-02 2.472043e-02 4.845768e-02 9.448146e-02 1.725475e-01 2.871600e-01 4.296480e-01 5.747074e-01 6.854643e-01 7.273508e-01 6.863860e-01 5.757480e-01 4.296219e-01 2.854793e-01 1.697547e-01 9.140206e-02 4.587189e-02 2.308636e-02 1.322964e-02 9.680926e-03 8.810654e-03 9.031567e-03 9.783368e-03 1.091781e-02 1.247661e-02 1.442600e-02 1.661938e-02 1.869979e-02 2.016831e-02 2.055129e-02 1.966201e-02 1.770881e-02 1.519817e-02 1.271533e-02 1.066971e-02 9.212579e-03 8.280414e-03 7.719573e-03 7.384676e-03 7.183567e-03 7.067337e-03 7.022019e-03 7.044664e-03 7.139591e-03 7.305093e-03 7.533553e-03 7.801603e-03 8.074812e-03 8.317829e-03 8.510754e-03 8.647176e-03 8.741611e-03 8.805587e-03 8.839296e-03 8.826273e-03 8.743930e-03 8.581818e-03 8.351759e-03 8.086007e-03 7.825758e-03 7.598645e-03 7.423246e-03 7.300393e-03 7.226048e-03 7.196899e-03 7.207924e-03 7.263809e-03 7.368215e-03 7.531663e-03 7.755765e-03 8.034494e-03 8.358277e-03 8.716793e-03 9.124364e-03 9.621641e-03 1.024680e-02 1.101464e-02 1.187623e-02 1.276852e-02 1.371017e-02 1.496196e-02 1.710768e-02 2.099020e-02 2.740384e-02 3.657487e-02 4.767098e-02 5.868538e-02 6.693014e-02 7.003922e-02 6.697556e-02 5.860499e-02 4.718079e-02 3.540182e-02 2.541912e-02 1.825895e-02 1.391639e-02 1.180377e-02 1.114952e-02 1.127039e-02 1.166577e-02 1.199422e-02 1.206024e-02 1.179497e-02 1.123414e-02 1.050128e-02 9.755202e-03 9.122123e-03 8.732170e-03 8.741889e-03 9.473883e-03 1.174996e-02 1.729896e-02 2.929594e-02 5.267659e-02 9.386796e-02 1.593664e-01 2.530190e-01 3.726299e-01 5.081310e-01 6.415411e-01 7.504432e-01 8.138642e-01 8.189348e-01 7.643213e-01 6.609489e-01 5.290110e-01 3.913017e-01 2.674466e-01 1.693763e-01 1.007277e-01 5.910326e-02 3.954862e-02 3.796503e-02 5.267582e-02 8.439147e-02 1.336887e-01 1.989496e-01 2.727730e-01 3.429033e-01 3.939203e-01 4.131436e-01 3.953264e-01 3.451843e-01 2.751369e-01 2.007517e-01 1.345754e-01 8.366989e-02 4.926526e-02 2.854155e-02 1.737755e-02 1.195803e-02 9.558667e-03 8.578883e-03 8.200795e-03 8.062436e-03 8.027728e-03 8.039646e-03 8.115903e-03 8.284763e-03 8.624448e-03 9.266158e-03 1.038986e-02 1.220045e-02 1.486349e-02 1.842647e-02 2.274805e-02 2.744008e-02 3.194724e-02 3.565375e-02 3.810749e-02 3.914260e-02 3.900992e-02 3.830663e-02 3.783181e-02 3.839888e-02 4.064266e-02 4.492343e-02 5.130484e-02 5.969504e-02 7.006256e-02 8.251904e-02 9.739518e-02 1.149415e-01 1.351648e-01 1.575816e-01 1.813980e-01 2.054499e-01 2.282755e-01 2.477685e-01 2.610628e-01 2.650966e-01 2.574116e-01 2.376814e-01 2.083251e-01 1.740047e-01 1.403001e-01 1.119055e-01 9.143031e-02 7.916461e-02 7.356905e-02 7.215858e-02 7.230131e-02 7.180182e-02 6.935077e-02 6.452194e-02 5.775045e-02 5.006570e-02 4.277694e-02 3.722509e-02 3.449823e-02 3.536098e-02 3.996757e-02 4.781812e-02 5.756850e-02 6.721530e-02 7.461750e-02 7.804290e-02 7.683382e-02 7.165094e-02 6.424576e-02 5.680025e-02 5.122445e-02 4.871994e-02 4.941294e-02 5.261513e-02 5.706711e-02 6.140390e-02 6.439516e-02 6.535518e-02 6.424193e-02 6.165434e-02 5.882503e-02 5.742332e-02 5.954136e-02 6.754334e-02 8.391391e-02 1.106450e-01 1.481907e-01 1.946876e-01 2.453059e-01 2.926229e-01 3.284052e-01 3.460627e-01 3.426641e-01 3.198676e-01 2.832318e-01 2.404668e-01 1.992538e-01 1.661250e-01 1.468392e-01 1.466236e-01 1.713802e-01 2.271228e-01 3.181776e-01 4.442966e-01 5.971096e-01 7.591323e-01 9.048728e-01 1.007239e+00 1.044810e+00 1.008758e+00 9.067758e-01 7.595049e-01 5.937905e-01 4.351478e-01 3.009174e-01 1.993869e-01 1.298028e-01 8.659863e-02 6.249543e-02 5.082114e-02 4.639960e-02 4.572211e-02 4.661896e-02 4.791437e-02 4.908005e-02 4.990833e-02 5.059090e-02 5.117670e-02 5.171545e-02 5.218405e-02 5.255086e-02 5.286922e-02 5.334352e-02 5.436102e-02 5.639414e-02 5.988001e-02 6.507341e-02 7.188922e-02 7.986892e-02 8.819487e-02 9.586426e-02 1.017880e-01 1.051853e-01 1.057029e-01 1.035529e-01 9.957407e-02 9.504312e-02 9.134782e-02 8.973418e-02 9.104694e-02 9.544356e-02 1.024400e-01 1.109466e-01 1.195670e-01 1.268357e-01 1.318688e-01 1.345993e-01 1.362082e-01 1.388552e-01 1.453705e-01 1.583458e-01 1.792234e-01 2.078033e-01 2.412799e-01 2.752272e-01 3.036751e-01 3.210806e-01 3.233637e-01 3.093626e-01 2.811786e-01 2.433937e-01 2.019809e-01 1.625546e-01 1.293085e-01 1.041221e-01 8.704689e-02 7.647927e-02 7.036209e-02 6.665612e-02 6.385951e-02 6.120854e-02 5.849326e-02 5.592061e-02 5.377958e-02 5.235954e-02 5.181576e-02 5.232011e-02 5.413318e-02 5.763409e-02 6.330797e-02 7.154430e-02 8.241149e-02 9.547129e-02 1.096732e-01 1.234733e-01 1.351841e-01 1.433816e-01 1.473067e-01 1.470847e-01 1.436253e-01 1.383880e-01 1.327625e-01 1.277958e-01 1.237546e-01 1.203331e-01 1.166890e-01 1.119667e-01 1.056409e-01 9.777334e-02 8.901619e-02 8.057575e-02 7.382485e-02 7.009993e-02 7.038209e-02 7.514348e-02 8.434866e-02 9.745461e-02 1.136247e-01 1.318253e-01 1.510347e-01 1.701826e-01 1.882948e-01 2.042028e-01 2.166449e-01 2.241429e-01 2.254958e-01 2.199047e-01 2.075176e-01 1.892840e-01 1.671504e-01 1.434139e-01 1.203324e-01 9.969115e-02 8.248828e-02 6.895737e-02 5.873585e-02 5.112160e-02 4.535965e-02 4.083057e-02 3.711594e-02 3.404782e-02 3.160733e-02 2.987048e-02 2.892143e-02 2.876273e-02 2.931211e-02 3.038354e-02 3.168973e-02 3.294695e-02 3.388460e-02 3.434770e-02 3.429913e-02 3.383702e-02 3.313122e-02 3.239895e-02 3.182370e-02 3.153666e-02 3.158685e-02 3.196087e-02 3.261534e-02 3.347092e-02 3.447293e-02 3.557301e-02 3.674406e-02 3.798774e-02 3.933380e-02 4.082875e-02 4.256457e-02 4.459205e-02 4.693062e-02 4.949188e-02 5.205773e-02 5.427919e-02 5.575475e-02 5.606927e-02 5.497336e-02 5.240401e-02 4.855488e-02 4.382132e-02 3.875063e-02 3.387529e-02 2.967808e-02 2.645281e-02 2.432960e-02 2.326413e-02 2.307578e-02 2.352270e-02 2.432186e-02 2.520607e-02 2.595749e-02 2.642561e-02 2.652886e-02 2.626673e-02 2.570507e-02 2.495895e-02 2.416488e-02 2.347292e-02 2.302354e-02 2.294590e-02 2.335289e-02 2.434823e-02 2.601423e-02 2.837873e-02 3.140305e-02 3.494032e-02 3.870603e-02 4.232711e-02 4.537388e-02 4.744958e-02 4.830851e-02 4.794395e-02 4.663095e-02 4.492792e-02 4.361794e-02 4.359085e-02 4.570501e-02 5.065759e-02 5.876291e-02 6.993286e-02 8.354696e-02 9.852751e-02 1.134592e-01 1.269359e-01 1.379156e-01 1.461330e-01 1.523122e-01 1.583177e-01 1.666957e-01 1.801465e-01 2.007979e-01 2.293932e-01 2.647364e-01 3.036382e-01 3.411888e-01 3.717816e-01 3.902693e-01 3.930625e-01 3.788314e-01 3.489024e-01 3.071269e-01 2.582935e-01 2.078015e-01 1.602484e-01 1.189369e-01 8.550802e-02 6.024265e-02 4.230240e-02 3.032477e-02 2.280726e-02 1.836381e-02 1.590226e-02 1.463268e-02 1.404961e-02 1.384926e-02 1.387409e-02 1.403950e-02 1.430554e-02 1.461664e-02 1.496651e-02 1.530011e-02 1.557845e-02 1.577500e-02 1.587397e-02 1.587393e-02 1.579509e-02 1.565550e-02 1.548538e-02 1.529199e-02 1.509846e-02 1.489909e-02 1.470036e-02 1.450694e-02 1.433823e-02 1.422143e-02 1.420098e-02 1.434165e-02 1.471554e-02 1.542653e-02 1.659263e-02 1.833684e-02 2.078942e-02 2.405400e-02 2.818321e-02 3.310191e-02 3.865159e-02 4.452503e-02 5.030255e-02 5.548391e-02 5.964105e-02 6.241574e-02 6.364170e-02 6.336790e-02 6.187263e-02 5.952355e-02 5.677003e-02 5.399612e-02 5.146874e-02 4.933130e-02 4.758076e-02 4.618588e-02 4.507313e-02 4.426687e-02 4.382282e-02 4.386159e-02 4.452045e-02 4.590153e-02 4.803662e-02 5.086217e-02 5.426025e-02 5.799365e-02 6.180144e-02 6.540419e-02 6.844754e-02 7.066695e-02 7.176594e-02 7.159640e-02 7.008035e-02 6.737152e-02 6.374633e-02 5.967122e-02 5.562399e-02 5.211744e-02 4.949958e-02 4.795275e-02 4.739699e-02 4.757820e-02 4.810593e-02 4.856107e-02 4.859089e-02 4.797609e-02 4.667964e-02 4.479880e-02 4.258006e-02 4.026378e-02 3.807516e-02 3.614673e-02 3.450849e-02 3.309107e-02 3.177366e-02 3.041578e-02 2.891623e-02 2.722954e-02 2.537291e-02 2.342805e-02 2.150257e-02 1.973460e-02 1.824065e-02 1.711811e-02 1.644959e-02 1.628067e-02 1.664571e-02 1.756117e-02 1.902115e-02 2.102814e-02 2.351150e-02 2.642153e-02 2.964258e-02 3.300985e-02 3.635193e-02 3.944889e-02 4.208095e-02 4.408540e-02 4.533329e-02 4.578921e-02 4.553366e-02 4.470013e-02 4.353885e-02 4.227551e-02 4.114458e-02 4.028976e-02 3.978297e-02 3.959569e-02 3.959918e-02 3.965691e-02 3.961235e-02 3.937503e-02 3.892206e-02 3.830651e-02 3.764031e-02 3.709797e-02 3.681472e-02 3.690688e-02 3.742620e-02 3.833306e-02 3.952827e-02 4.084288e-02 4.211354e-02 4.314433e-02 4.378544e-02 4.394560e-02 4.357168e-02 4.271093e-02 4.141686e-02 3.982539e-02 3.806920e-02 3.627692e-02 3.457224e-02 3.302431e-02 3.165900e-02 3.049761e-02 2.947465e-02 2.856253e-02 2.771864e-02 2.693199e-02 2.622434e-02 2.567737e-02 2.538521e-02 2.547181e-02 2.605723e-02 2.721981e-02 2.897656e-02 3.125219e-02 3.389325e-02 3.668338e-02 3.933952e-02 4.155437e-02 4.308696e-02 4.375868e-02 4.347071e-02 4.227866e-02 4.033370e-02 3.782945e-02 3.506845e-02 3.227886e-02 2.966729e-02 2.740748e-02 2.554401e-02 2.407567e-02 2.295741e-02 2.209264e-02 2.140014e-02 2.080927e-02 2.026089e-02 1.974611e-02 1.926425e-02 1.883507e-02 1.847976e-02 1.821231e-02 1.803388e-02 1.792849e-02 1.786335e-02 1.780168e-02 1.769990e-02 1.752105e-02 1.724133e-02 1.685517e-02 1.637444e-02 1.582658e-02 1.525036e-02 1.468875e-02 1.418456e-02 1.376329e-02 1.344957e-02 1.324027e-02 1.313056e-02 1.309439e-02 1.310936e-02 1.315087e-02 1.320275e-02 1.326127e-02 1.333897e-02 1.345635e-02 1.365533e-02 1.397447e-02 1.445892e-02 1.513258e-02 1.601526e-02 1.709214e-02 1.832762e-02 1.965429e-02 2.098380e-02 2.220943e-02 2.322753e-02 2.394287e-02 2.428674e-02 2.421407e-02 2.372807e-02 2.286914e-02 2.170193e-02 2.032072e-02 1.882877e-02 1.732172e-02 1.588930e-02 1.460006e-02 1.350113e-02 1.261872e-02 1.196262e-02 1.152414e-02 1.129208e-02 1.124444e-02 1.135728e-02 1.160919e-02 1.196668e-02 1.240124e-02 1.288021e-02 1.335682e-02 1.380515e-02 1.418290e-02 1.447086e-02 1.465572e-02 1.474356e-02 1.476641e-02 1.477162e-02 1.483754e-02 1.504779e-02 1.551388e-02 1.632500e-02 1.758486e-02 1.935606e-02 2.168384e-02 2.454337e-02 2.787941e-02 3.157571e-02 3.545602e-02 3.934967e-02 4.302173e-02 4.630700e-02 4.906765e-02 5.123317e-02 5.282579e-02 5.399374e-02 5.490644e-02 5.584837e-02 5.710117e-02 5.893781e-02 6.153298e-02 6.503397e-02 6.938579e-02 7.442034e-02 7.991625e-02 8.538711e-02 9.040926e-02 9.453254e-02 9.722890e-02 9.821959e-02 9.725589e-02 9.428483e-02 8.945446e-02 8.302819e-02 7.537649e-02 6.704493e-02 5.841343e-02 5.000979e-02 4.215549e-02 3.513132e-02 2.907826e-02 2.405951e-02 2.002849e-02 1.689931e-02 1.453568e-02 1.280307e-02 1.155725e-02 1.068270e-02 1.007643e-02 9.659143e-03 9.374692e-03 9.183250e-03 9.051901e-03 8.967238e-03 8.913040e-03 8.885162e-03 8.871778e-03 8.882857e-03 8.906255e-03 8.941452e-03 8.986862e-03 9.040207e-03 9.098881e-03 9.160437e-03 9.218813e-03 9.273719e-03 9.318804e-03 9.352072e-03 9.371513e-03 9.376320e-03 9.367999e-03 9.346261e-03 9.314484e-03 9.276175e-03 9.235414e-03 9.193471e-03 9.153292e-03 9.116177e-03 9.083540e-03 9.053201e-03 9.025086e-03 8.998054e-03 8.972420e-03 8.946167e-03 8.921467e-03 8.897748e-03 8.877953e-03 8.864176e-03 8.859962e-03 8.867055e-03 8.886534e-03 8.921131e-03 8.972697e-03 9.042797e-03 9.129112e-03 9.231208e-03 9.349464e-03 9.484662e-03 9.635434e-03 9.805445e-03 9.997042e-03 1.021887e-02 1.048111e-02 1.080183e-02 1.120056e-02 1.170683e-02 1.235152e-02 1.316813e-02 1.418812e-02 1.544437e-02 1.695284e-02 1.871013e-02 2.069847e-02 2.287522e-02 2.516653e-02 2.747615e-02 2.968562e-02 3.166974e-02 3.330914e-02 3.448523e-02 3.511149e-02 3.513787e-02 3.455374e-02 3.338686e-02 3.171256e-02 2.963505e-02 2.727793e-02 2.476913e-02 2.223936e-02 1.979735e-02 1.753071e-02 1.550065e-02 1.374792e-02 1.228110e-02 1.108893e-02 1.014744e-02 9.423874e-03 8.880975e-03 8.484643e-03 8.198850e-03 7.997288e-03 7.856138e-03 7.757690e-03 7.688685e-03 7.639956e-03 7.605058e-03 7.580275e-03 7.563166e-03 7.552290e-03 7.546707e-03 7.550621e-03 7.560409e-03 7.577932e-03 7.602875e-03 7.638169e-03 7.681763e-03 7.734643e-03 7.795600e-03 7.863609e-03 7.936549e-03 8.012085e-03 8.085007e-03 8.153823e-03 8.212381e-03 8.258990e-03 8.288606e-03 8.300312e-03 8.291860e-03 8.264585e-03 8.219114e-03 8.158323e-03 8.090279e-03 8.015811e-03 7.945888e-03 7.887148e-03 7.847767e-03 7.837943e-03 7.865272e-03 7.943645e-03 8.080632e-03 8.289542e-03 8.577652e-03 8.954437e-03 9.426774e-03 9.999397e-03 1.066581e-02 1.142005e-02 1.225144e-02 1.313384e-02 1.404050e-02 1.494247e-02 1.580281e-02 1.657990e-02 1.724642e-02 1.776798e-02 1.812316e-02 1.830252e-02 1.830034e-02 1.812877e-02 1.780071e-02 1.734398e-02 1.678766e-02 1.615835e-02 1.549427e-02 1.481173e-02 1.413755e-02 1.348670e-02 1.286342e-02 1.228088e-02 1.173174e-02 1.121922e-02 1.073802e-02 1.028599e-02 9.861251e-03 9.461015e-03 9.087383e-03 8.739196e-03 8.419509e-03 8.130627e-03 7.870640e-03 7.642432e-03 7.447290e-03 7.281275e-03 7.145165e-03 7.036773e-03 6.952893e-03 6.890526e-03 6.849853e-03 6.827150e-03 6.821097e-03 6.830801e-03 6.855831e-03 6.895635e-03 6.952364e-03 7.024466e-03 7.114236e-03 7.221397e-03 7.345633e-03 7.485582e-03 7.638624e-03 7.801395e-03 7.968460e-03 8.133336e-03 8.288811e-03 8.427146e-03 8.540767e-03 8.622836e-03 8.667905e-03 8.672392e-03 8.634987e-03 8.557046e-03 8.442277e-03 8.297291e-03 8.127714e-03 7.944903e-03 7.756228e-03 7.570719e-03 7.399185e-03 7.244757e-03 7.115132e-03 7.013617e-03 6.940790e-03 6.897121e-03 6.880559e-03 6.887561e-03 6.914087e-03 6.953882e-03 7.001883e-03 7.052859e-03 7.100278e-03 7.141502e-03 7.169474e-03 7.184171e-03 7.182305e-03 7.164305e-03 7.131800e-03 7.085876e-03 7.030878e-03 6.969559e-03 6.906633e-03 6.848049e-03 6.796605e-03 6.757377e-03 6.733291e-03 6.727885e-03 6.742743e-03 6.779601e-03 6.839526e-03 6.920415e-03 7.023579e-03 7.144612e-03 7.281519e-03 7.432118e-03 7.588707e-03 7.748181e-03 7.905746e-03 8.051397e-03 8.181236e-03 8.288217e-03 8.367534e-03 8.412886e-03 8.420967e-03 8.390971e-03 8.321675e-03 8.214463e-03 8.074391e-03 7.905345e-03 7.713270e-03 7.505975e-03 7.291161e-03 7.075286e-03 6.864079e-03 6.663804e-03 6.480105e-03 6.313652e-03 6.167606e-03 6.041391e-03 5.935911e-03 5.849035e-03 5.778483e-03 5.722936e-03 5.679493e-03 5.646421e-03 5.620203e-03 5.600877e-03 5.588255e-03 5.577736e-03 5.569988e-03 5.565634e-03 5.563863e-03 5.565796e-03 5.572741e-03 5.587682e-03 5.612521e-03 5.651387e-03 5.710060e-03 5.795211e-03 5.913449e-03 6.080307e-03 6.298789e-03 6.584401e-03 6.953921e-03 7.415485e-03 7.989698e-03 8.689798e-03 9.523786e-03 1.051211e-02 1.165300e-02 1.295330e-02 1.442386e-02 1.604155e-02 1.781374e-02 1.971530e-02 2.173810e-02 2.386487e-02 2.605632e-02 2.831540e-02 3.060111e-02 3.289827e-02 3.518417e-02 3.743684e-02 3.963682e-02 4.175098e-02 4.375294e-02 4.562883e-02 4.730240e-02 4.876744e-02 4.996438e-02 5.085127e-02 5.137542e-02 5.152012e-02 5.121696e-02 5.049440e-02 4.930513e-02 4.769605e-02 4.566825e-02 4.329624e-02 4.061590e-02 3.770883e-02 3.466233e-02 3.152380e-02 2.842455e-02 2.537463e-02 2.249338e-02 1.979249e-02 1.732462e-02 1.511666e-02 1.316143e-02 1.148534e-02 1.004682e-02 8.858242e-03 7.874160e-03 7.083517e-03 6.449248e-03 5.956428e-03 5.572488e-03 5.280823e-03 5.058397e-03 4.892586e-03 4.768420e-03 4.676393e-03 4.607568e-03 4.555750e-03 4.516646e-03 4.486604e-03 4.463510e-03 4.444591e-03 4.428985e-03 4.414631e-03 4.405474e-03 4.397450e-03 4.391353e-03 4.386404e-03 4.382994e-03 4.380961e-03 4.381061e-03 4.382855e-03 4.385829e-03 4.390928e-03 4.398308e-03 4.407142e-03 4.418648e-03 4.432066e-03 4.447742e-03 4.466152e-03 4.486805e-03 4.509978e-03 4.536948e-03 4.566298e-03 4.599927e-03 4.637376e-03 4.679364e-03 4.726703e-03 4.779200e-03 4.837668e-03 4.902700e-03 4.973756e-03 5.050999e-03 5.134274e-03 5.222184e-03 5.314091e-03 5.407699e-03 5.501437e-03 5.593168e-03 5.679897e-03 5.758334e-03 5.826017e-03 5.880856e-03 5.918420e-03 5.938502e-03 5.938773e-03 5.917946e-03 5.876645e-03 5.814843e-03 5.734962e-03 5.637266e-03 5.526572e-03 5.404097e-03 5.273930e-03 5.139766e-03 5.004484e-03 4.871504e-03 4.741901e-03 4.621025e-03 4.506769e-03 4.403403e-03 4.309687e-03 4.227107e-03 4.154173e-03 4.091449e-03 4.036526e-03 3.991012e-03 3.951338e-03 3.917466e-03 3.888335e-03 3.864072e-03 3.841539e-03 3.822243e-03 3.804785e-03 3.788214e-03 3.772735e-03 3.758597e-03 3.745252e-03 3.732125e-03 3.719699e-03 3.708538e-03 3.698255e-03 3.688577e-03 3.680091e-03 3.674097e-03 3.668491e-03 3.666191e-03 3.665461e-03 3.667222e-03 3.672532e-03 3.681333e-03 3.693135e-03 3.709894e-03 3.730801e-03 3.755893e-03 3.786145e-03 3.822221e-03 3.862096e-03 3.906501e-03 3.955491e-03 4.007366e-03 4.061602e-03 4.118127e-03 4.174478e-03 4.230238e-03 4.283223e-03 4.333597e-03 4.379446e-03 4.419715e-03 4.453434e-03 4.481107e-03 4.502275e-03 4.516671e-03 4.527106e-03 4.532146e-03 4.535846e-03 4.538127e-03 4.543382e-03 4.551327e-03 4.566181e-03 4.588503e-03 4.620214e-03 4.662624e-03 4.716004e-03 4.781436e-03 4.855120e-03 4.940313e-03 5.029117e-03 5.124064e-03 5.216646e-03 5.307801e-03 5.392458e-03 5.464574e-03 5.524390e-03 5.565697e-03 5.588395e-03 5.589273e-03 5.567571e-03 5.524592e-03 5.460197e-03 5.374582e-03 5.273169e-03 5.156769e-03 5.028174e-03 4.892947e-03 4.753880e-03 4.612705e-03 4.475305e-03 4.342746e-03 4.217169e-03 4.102355e-03 3.995995e-03 3.901991e-03 3.819203e-03 3.745981e-03 3.683340e-03 3.629418e-03 3.584208e-03 3.544403e-03 3.510816e-03 3.481371e-03 3.455680e-03 3.432212e-03 3.411545e-03 3.392261e-03 3.374840e-03 3.359202e-03 3.345325e-03 3.334221e-03 3.325130e-03 3.318844e-03 3.316535e-03 3.316875e-03 3.321186e-03 3.329320e-03 3.340562e-03 3.355175e-03 3.372550e-03 3.392473e-03 3.414195e-03 3.436053e-03 3.457704e-03 3.479450e-03 3.498063e-03 3.514023e-03 3.526964e-03 3.534634e-03 3.537397e-03 3.534730e-03 3.526534e-03 3.512039e-03 3.492296e-03 3.466590e-03 3.435964e-03 3.401382e-03 3.363075e-03 3.321658e-03 3.279458e-03 3.235549e-03 3.192392e-03 3.149727e-03 3.108457e-03 3.070451e-03 3.034149e-03 3.001840e-03 2.972155e-03 2.946574e-03 2.923901e-03 2.904076e-03 2.887001e-03 2.871828e-03 2.859690e-03 2.847789e-03 2.837868e-03 2.828863e-03 2.820081e-03 2.812003e-03 2.804105e-03 2.796941e-03 2.790734e-03 2.785791e-03 2.782240e-03 2.780687e-03 2.782696e-03 2.788354e-03 2.799341e-03 2.816835e-03 2.841675e-03 2.875651e-03 2.920392e-03 2.976797e-03 3.047024e-03 3.132120e-03 3.234657e-03 3.354471e-03 3.493628e-03 3.653697e-03 3.834097e-03 4.036427e-03 4.259773e-03 4.504780e-03 4.770263e-03 5.055184e-03 5.357638e-03 5.674262e-03 6.003961e-03 6.342714e-03 6.686941e-03 7.031950e-03 7.375701e-03 7.710483e-03 8.033918e-03 8.339347e-03 8.624727e-03 8.882888e-03 9.109266e-03 9.301693e-03 9.455527e-03 9.567361e-03 9.634796e-03 9.656575e-03 9.631566e-03 9.558519e-03 9.441144e-03 9.276438e-03 9.071078e-03 8.824799e-03 8.545139e-03 8.232559e-03 7.895027e-03 7.537460e-03 7.164015e-03 6.782109e-03 6.396457e-03 6.012612e-03 5.633474e-03 5.266165e-03 4.913589e-03 4.580073e-03 4.265802e-03 3.974963e-03 3.706911e-03 3.462721e-03 3.243220e-03 3.046754e-03 2.873544e-03 2.721871e-03 2.589482e-03 2.475830e-03 2.378059e-03 2.295153e-03 2.225146e-03 2.166375e-03 2.117377e-03 2.076008e-03 2.041560e-03 2.012841e-03 1.988944e-03 1.968940e-03 1.951496e-03 1.936801e-03 1.923706e-03 1.912164e-03 1.900983e-03 1.891125e-03 1.881528e-03 1.871682e-03 1.861989e-03 1.851783e-03 1.841096e-03 1.829296e-03 1.816996e-03 1.803498e-03 1.788756e-03 1.772265e-03 1.754189e-03 1.733962e-03 1.711560e-03 1.686997e-03 1.659762e-03 1.630046e-03 1.597348e-03 1.561778e-03 1.523427e-03 1.482097e-03 1.437636e-03 1.390258e-03 1.340285e-03 1.287592e-03 1.232648e-03 1.175462e-03 1.116603e-03 1.056219e-03 9.948738e-04 9.328031e-04 ''') ImportString(u'flux_model_0_5_reduce(numeric)',''' 1.319175e-03 1.750092e-03 2.097782e-03 2.516841e-03 3.331051e-03 4.845508e-03 6.918991e-03 8.654762e-03 8.912326e-03 7.458358e-03 5.262554e-03 3.492610e-03 2.560264e-03 2.228979e-03 2.157527e-03 2.164920e-03 2.195222e-03 2.230741e-03 2.258832e-03 2.270818e-03 2.263597e-03 2.242157e-03 2.220702e-03 2.212826e-03 2.226071e-03 2.261554e-03 2.315124e-03 2.381431e-03 2.458370e-03 2.542760e-03 2.613196e-03 2.647036e-03 2.661671e-03 2.721502e-03 2.885240e-03 3.175670e-03 3.602367e-03 4.149221e-03 4.694773e-03 5.007476e-03 4.912585e-03 4.458992e-03 3.869113e-03 3.346053e-03 2.962090e-03 2.699124e-03 2.531038e-03 2.455856e-03 2.467843e-03 2.533804e-03 2.597186e-03 2.611304e-03 2.572183e-03 2.518830e-03 2.494159e-03 2.509870e-03 2.546956e-03 2.576485e-03 2.581916e-03 2.565686e-03 2.542628e-03 2.530002e-03 2.541950e-03 2.599755e-03 2.732886e-03 2.946864e-03 3.172050e-03 3.280736e-03 3.199026e-03 2.983326e-03 2.757594e-03 2.604128e-03 2.534622e-03 2.543032e-03 2.673475e-03 3.070823e-03 3.938379e-03 5.310514e-03 6.796687e-03 7.718797e-03 7.689302e-03 6.929021e-03 5.914528e-03 4.947772e-03 4.139133e-03 3.535575e-03 3.140323e-03 2.906241e-03 2.775464e-03 2.708603e-03 2.679938e-03 2.672426e-03 2.678887e-03 2.701059e-03 2.748861e-03 2.829456e-03 2.942847e-03 3.090513e-03 3.280684e-03 3.507182e-03 3.704880e-03 3.770831e-03 3.653487e-03 3.409726e-03 3.155707e-03 2.974481e-03 2.888218e-03 2.886525e-03 2.957291e-03 3.084516e-03 3.231604e-03 3.359031e-03 3.486678e-03 3.725117e-03 4.195816e-03 4.894354e-03 5.664606e-03 6.326131e-03 6.786610e-03 6.956993e-03 6.683895e-03 5.913124e-03 4.872831e-03 3.938851e-03 3.344865e-03 3.080923e-03 3.018482e-03 3.038197e-03 3.071228e-03 3.096516e-03 3.127572e-03 3.207435e-03 3.402847e-03 3.770103e-03 4.280835e-03 4.755778e-03 4.942355e-03 4.722597e-03 4.231457e-03 3.727992e-03 3.382278e-03 3.208390e-03 3.139733e-03 3.119395e-03 3.118112e-03 3.123846e-03 3.132263e-03 3.143936e-03 3.159930e-03 3.177747e-03 3.188882e-03 3.187266e-03 3.177975e-03 3.177278e-03 3.205713e-03 3.278946e-03 3.396443e-03 3.536390e-03 3.675744e-03 3.817789e-03 3.977924e-03 4.130840e-03 4.203844e-03 4.142138e-03 3.968221e-03 3.756064e-03 3.567835e-03 3.429476e-03 3.343864e-03 3.302021e-03 3.291102e-03 3.302688e-03 3.340095e-03 3.410620e-03 3.511612e-03 3.615371e-03 3.680765e-03 3.686275e-03 3.649254e-03 3.616769e-03 3.627487e-03 3.683819e-03 3.755514e-03 3.811547e-03 3.867571e-03 3.998804e-03 4.286249e-03 4.718309e-03 5.136354e-03 5.316422e-03 5.149588e-03 4.747130e-03 4.353019e-03 4.156569e-03 4.177006e-03 4.293877e-03 4.351049e-03 4.270990e-03 4.085374e-03 3.885187e-03 3.736944e-03 3.652488e-03 3.612037e-03 3.599281e-03 3.608251e-03 3.643389e-03 3.718445e-03 3.861683e-03 4.096446e-03 4.404131e-03 4.699087e-03 4.882862e-03 4.942528e-03 4.999695e-03 5.231800e-03 5.740802e-03 6.480780e-03 7.262807e-03 7.815016e-03 7.918450e-03 7.614676e-03 7.297327e-03 7.590373e-03 9.137810e-03 1.240748e-02 1.733751e-02 2.283540e-02 2.678248e-02 2.715064e-02 2.355526e-02 1.764408e-02 1.181975e-02 7.656440e-03 5.380071e-03 4.426117e-03 4.158344e-03 4.181915e-03 4.312385e-03 4.451761e-03 4.537124e-03 4.542414e-03 4.491468e-03 4.428117e-03 4.392224e-03 4.424012e-03 4.579266e-03 4.905678e-03 5.372169e-03 5.829649e-03 6.091967e-03 6.065673e-03 5.818136e-03 5.486793e-03 5.166137e-03 4.876003e-03 4.612931e-03 4.392661e-03 4.233991e-03 4.139743e-03 4.093720e-03 4.075135e-03 4.072220e-03 4.074377e-03 4.081367e-03 4.089323e-03 4.098748e-03 4.110021e-03 4.122477e-03 4.138194e-03 4.157585e-03 4.179712e-03 4.203819e-03 4.231725e-03 4.285173e-03 4.416540e-03 4.709404e-03 5.245488e-03 6.038527e-03 6.972079e-03 7.819878e-03 8.334602e-03 8.359266e-03 7.894386e-03 7.091047e-03 6.191037e-03 5.419804e-03 4.905889e-03 4.649281e-03 4.577622e-03 4.595987e-03 4.635951e-03 4.658263e-03 4.650464e-03 4.620340e-03 4.579580e-03 4.541647e-03 4.515162e-03 4.500504e-03 4.495655e-03 4.487765e-03 4.456673e-03 4.386825e-03 4.282102e-03 4.178356e-03 4.126412e-03 4.144381e-03 4.183860e-03 4.152444e-03 3.985475e-03 3.701646e-03 3.386989e-03 3.127370e-03 2.962528e-03 2.881313e-03 2.855061e-03 2.856405e-03 2.871900e-03 2.895757e-03 2.927496e-03 2.966749e-03 3.009791e-03 3.043409e-03 3.053055e-03 3.033655e-03 2.993168e-03 2.954102e-03 2.937526e-03 2.959417e-03 3.020683e-03 3.110267e-03 3.200016e-03 3.258791e-03 3.262476e-03 3.213184e-03 3.131980e-03 3.050166e-03 2.987383e-03 2.951715e-03 2.939516e-03 2.949456e-03 2.988918e-03 3.086556e-03 3.297142e-03 3.718053e-03 4.508327e-03 5.879284e-03 8.007223e-03 1.082751e-02 1.382748e-02 1.607870e-02 1.668090e-02 1.531806e-02 1.253481e-02 9.357792e-03 6.683642e-03 4.902750e-03 3.938640e-03 3.528168e-03 3.456461e-03 3.633952e-03 4.053622e-03 4.693531e-03 5.438943e-03 6.072968e-03 6.383871e-03 6.305955e-03 6.017079e-03 5.925563e-03 6.615786e-03 8.750596e-03 1.283315e-02 1.878914e-02 2.549107e-02 3.088698e-02 3.287360e-02 3.061109e-02 2.508722e-02 1.851821e-02 1.300921e-02 9.573020e-03 8.049950e-03 7.617563e-03 7.423274e-03 6.969233e-03 6.181242e-03 5.264537e-03 4.469648e-03 3.956186e-03 3.766035e-03 3.889322e-03 4.296164e-03 4.927801e-03 5.641628e-03 6.222730e-03 6.455877e-03 6.250164e-03 5.697078e-03 5.007578e-03 4.392418e-03 3.963376e-03 3.730674e-03 3.651842e-03 3.691599e-03 3.847344e-03 4.146575e-03 4.616318e-03 5.229929e-03 5.875058e-03 6.365476e-03 6.527171e-03 6.295541e-03 5.761138e-03 5.115677e-03 4.546897e-03 4.156926e-03 3.949430e-03 3.877920e-03 3.891187e-03 3.954168e-03 4.050080e-03 4.161500e-03 4.266758e-03 4.341205e-03 4.367563e-03 4.340617e-03 4.270515e-03 4.175057e-03 4.076607e-03 3.994999e-03 3.945725e-03 3.933824e-03 3.952479e-03 3.982814e-03 4.001532e-03 3.994664e-03 3.960719e-03 3.912832e-03 3.863661e-03 3.823655e-03 3.798271e-03 3.799609e-03 3.838802e-03 3.920692e-03 4.026729e-03 4.117121e-03 4.148885e-03 4.100792e-03 3.988115e-03 3.853048e-03 3.744434e-03 3.690217e-03 3.695071e-03 3.737347e-03 3.786709e-03 3.812615e-03 3.802920e-03 3.762720e-03 3.715324e-03 3.685051e-03 3.695623e-03 3.763703e-03 3.910005e-03 4.153763e-03 4.504205e-03 4.939884e-03 5.389785e-03 5.747745e-03 5.909177e-03 5.821939e-03 5.518757e-03 5.106638e-03 4.730149e-03 4.511255e-03 4.517860e-03 4.748249e-03 5.139885e-03 5.587755e-03 5.970462e-03 6.178748e-03 6.147959e-03 5.880461e-03 5.436565e-03 4.925953e-03 4.459215e-03 4.121983e-03 3.956605e-03 3.964148e-03 4.112316e-03 4.346189e-03 4.595811e-03 4.792798e-03 4.886926e-03 4.863695e-03 4.738527e-03 4.551007e-03 4.342577e-03 4.147563e-03 3.986942e-03 3.866054e-03 3.779433e-03 3.739600e-03 3.760377e-03 3.983874e-03 4.760313e-03 6.947250e-03 1.236022e-02 2.422884e-02 4.724073e-02 8.627376e-02 1.435800e-01 2.148240e-01 2.873537e-01 3.427321e-01 3.636754e-01 3.431930e-01 2.878740e-01 2.148110e-01 1.427396e-01 8.487734e-02 4.570103e-02 2.293594e-02 1.154318e-02 6.614820e-03 4.840463e-03 4.405327e-03 4.515783e-03 4.891684e-03 5.458907e-03 6.238304e-03 7.213002e-03 8.309690e-03 9.349895e-03 1.008416e-02 1.027565e-02 9.831006e-03 8.854404e-03 7.599085e-03 6.357663e-03 5.334853e-03 4.606289e-03 4.140207e-03 3.859786e-03 3.692338e-03 3.591784e-03 3.533668e-03 3.511010e-03 3.522332e-03 3.569795e-03 3.652546e-03 3.766776e-03 3.900802e-03 4.037406e-03 4.158915e-03 4.255377e-03 4.323588e-03 4.370805e-03 4.402793e-03 4.419648e-03 4.413137e-03 4.371965e-03 4.290909e-03 4.175880e-03 4.043004e-03 3.912879e-03 3.799322e-03 3.711623e-03 3.650196e-03 3.613024e-03 3.598450e-03 3.603962e-03 3.631904e-03 3.684107e-03 3.765832e-03 3.877883e-03 4.017247e-03 4.179139e-03 4.358396e-03 4.562182e-03 4.810820e-03 5.123401e-03 5.507320e-03 5.938117e-03 6.384260e-03 6.855084e-03 7.480982e-03 8.553842e-03 1.049510e-02 1.370192e-02 1.828744e-02 2.383549e-02 2.934269e-02 3.346507e-02 3.501961e-02 3.348778e-02 2.930249e-02 2.359039e-02 1.770091e-02 1.270956e-02 9.129475e-03 6.958195e-03 5.901887e-03 5.574760e-03 5.635194e-03 5.832885e-03 5.997112e-03 6.030119e-03 5.897483e-03 5.617070e-03 5.250641e-03 4.877601e-03 4.561062e-03 4.366085e-03 4.370945e-03 4.736941e-03 5.874980e-03 8.649478e-03 1.464797e-02 2.633829e-02 4.693398e-02 7.968320e-02 1.265095e-01 1.863149e-01 2.540655e-01 3.207705e-01 3.752216e-01 4.069321e-01 4.094674e-01 3.821606e-01 3.304745e-01 2.645055e-01 1.956509e-01 1.337233e-01 8.468816e-02 5.036386e-02 2.955163e-02 1.977431e-02 1.898251e-02 2.633791e-02 4.219574e-02 6.684437e-02 9.947478e-02 1.363865e-01 1.714517e-01 1.969601e-01 2.065718e-01 1.976632e-01 1.725921e-01 1.375684e-01 1.003759e-01 6.728772e-02 4.183495e-02 2.463263e-02 1.427077e-02 8.688774e-03 5.979014e-03 4.779334e-03 4.289441e-03 4.100398e-03 4.031218e-03 4.013864e-03 4.019823e-03 4.057951e-03 4.142382e-03 4.312224e-03 4.633079e-03 5.194932e-03 6.100224e-03 7.431744e-03 9.213234e-03 1.137402e-02 1.372004e-02 1.597362e-02 1.782688e-02 1.905374e-02 1.957130e-02 1.950496e-02 1.915331e-02 1.891590e-02 1.919944e-02 2.032133e-02 2.246172e-02 2.565242e-02 2.984752e-02 3.503128e-02 4.125952e-02 4.869759e-02 5.747077e-02 6.758238e-02 7.879080e-02 9.069902e-02 1.027250e-01 1.141377e-01 1.238843e-01 1.305314e-01 1.325483e-01 1.287058e-01 1.188407e-01 1.041626e-01 8.700237e-02 7.015005e-02 5.595274e-02 4.571515e-02 3.958230e-02 3.678453e-02 3.607929e-02 3.615066e-02 3.590091e-02 3.467539e-02 3.226097e-02 2.887522e-02 2.503285e-02 2.138847e-02 1.861254e-02 1.724911e-02 1.768049e-02 1.998378e-02 2.390906e-02 2.878425e-02 3.360765e-02 3.730875e-02 3.902145e-02 3.841691e-02 3.582547e-02 3.212288e-02 2.840012e-02 2.561222e-02 2.435997e-02 2.470647e-02 2.630757e-02 2.853356e-02 3.070195e-02 3.219758e-02 3.267759e-02 3.212097e-02 3.082717e-02 2.941252e-02 2.871166e-02 2.977068e-02 3.377167e-02 4.195695e-02 5.532252e-02 7.409535e-02 9.734378e-02 1.226529e-01 1.463114e-01 1.642026e-01 1.730314e-01 1.713320e-01 1.599338e-01 1.416159e-01 1.202334e-01 9.962690e-02 8.306252e-02 7.341960e-02 7.331178e-02 8.569011e-02 1.135614e-01 1.590888e-01 2.221483e-01 2.985548e-01 3.795661e-01 4.524364e-01 5.036193e-01 5.224049e-01 5.043792e-01 4.533879e-01 3.797524e-01 2.968952e-01 2.175739e-01 1.504587e-01 9.969346e-02 6.490139e-02 4.329932e-02 3.124771e-02 2.541057e-02 2.319980e-02 2.286106e-02 2.330948e-02 2.395719e-02 2.454002e-02 2.495417e-02 2.529545e-02 2.558835e-02 2.585772e-02 2.609202e-02 2.627543e-02 2.643461e-02 2.667176e-02 2.718051e-02 2.819707e-02 2.994000e-02 3.253670e-02 3.594461e-02 3.993446e-02 4.409743e-02 4.793213e-02 5.089401e-02 5.259263e-02 5.285144e-02 5.177643e-02 4.978703e-02 4.752156e-02 4.567391e-02 4.486709e-02 4.552347e-02 4.772178e-02 5.122000e-02 5.547331e-02 5.978348e-02 6.341784e-02 6.593439e-02 6.729967e-02 6.810408e-02 6.942760e-02 7.268526e-02 7.917289e-02 8.961169e-02 1.039017e-01 1.206399e-01 1.376136e-01 1.518376e-01 1.605403e-01 1.616819e-01 1.546813e-01 1.405893e-01 1.216969e-01 1.009905e-01 8.127730e-02 6.465425e-02 5.206104e-02 4.352345e-02 3.823964e-02 3.518105e-02 3.332806e-02 3.192975e-02 3.060427e-02 2.924663e-02 2.796030e-02 2.688979e-02 2.617977e-02 2.590788e-02 2.616005e-02 2.706659e-02 2.881704e-02 3.165399e-02 3.577215e-02 4.120575e-02 4.773564e-02 5.483662e-02 6.173664e-02 6.759207e-02 7.169078e-02 7.365336e-02 7.354236e-02 7.181264e-02 6.919399e-02 6.638124e-02 6.389790e-02 6.187728e-02 6.016655e-02 5.834452e-02 5.598335e-02 5.282044e-02 4.888667e-02 4.450810e-02 4.028787e-02 3.691243e-02 3.504997e-02 3.519104e-02 3.757174e-02 4.217433e-02 4.872731e-02 5.681236e-02 6.591267e-02 7.551733e-02 8.509132e-02 9.414738e-02 1.021014e-01 1.083225e-01 1.120714e-01 1.127479e-01 1.099524e-01 1.037588e-01 9.464200e-02 8.357520e-02 7.170697e-02 6.016618e-02 4.984557e-02 4.124414e-02 3.447869e-02 2.936793e-02 2.556080e-02 2.267983e-02 2.041528e-02 1.855797e-02 1.702391e-02 1.580367e-02 1.493524e-02 1.446071e-02 1.438136e-02 1.465606e-02 1.519177e-02 1.584486e-02 1.647348e-02 1.694230e-02 1.717385e-02 1.714957e-02 1.691851e-02 1.656561e-02 1.619948e-02 1.591185e-02 1.576833e-02 1.579342e-02 1.598044e-02 1.630767e-02 1.673546e-02 1.723646e-02 1.778651e-02 1.837203e-02 1.899387e-02 1.966690e-02 2.041438e-02 2.128229e-02 2.229602e-02 2.346531e-02 2.474594e-02 2.602886e-02 2.713960e-02 2.787738e-02 2.803463e-02 2.748668e-02 2.620200e-02 2.427744e-02 2.191066e-02 1.937531e-02 1.693765e-02 1.483904e-02 1.322640e-02 1.216480e-02 1.163207e-02 1.153789e-02 1.176135e-02 1.216093e-02 1.260303e-02 1.297874e-02 1.321280e-02 1.326443e-02 1.313337e-02 1.285253e-02 1.247947e-02 1.208244e-02 1.173646e-02 1.151177e-02 1.147295e-02 1.167644e-02 1.217412e-02 1.300712e-02 1.418936e-02 1.570152e-02 1.747016e-02 1.935302e-02 2.116355e-02 2.268694e-02 2.372479e-02 2.415426e-02 2.397197e-02 2.331547e-02 2.246396e-02 2.180897e-02 2.179543e-02 2.285250e-02 2.532879e-02 2.938145e-02 3.496643e-02 4.177348e-02 4.926376e-02 5.672959e-02 6.346796e-02 6.895782e-02 7.306649e-02 7.615609e-02 7.915883e-02 8.334784e-02 9.007324e-02 1.003989e-01 1.146966e-01 1.323682e-01 1.518191e-01 1.705944e-01 1.858908e-01 1.951346e-01 1.965313e-01 1.894157e-01 1.744512e-01 1.535635e-01 1.291467e-01 1.039008e-01 8.012421e-02 5.946847e-02 4.275401e-02 3.012132e-02 2.115120e-02 1.516239e-02 1.140363e-02 9.181906e-03 7.951132e-03 7.316338e-03 7.024806e-03 6.924632e-03 6.937045e-03 7.019752e-03 7.152771e-03 7.308322e-03 7.483257e-03 7.650055e-03 7.789223e-03 7.887501e-03 7.936987e-03 7.936966e-03 7.897544e-03 7.827752e-03 7.742688e-03 7.645997e-03 7.549228e-03 7.449546e-03 7.350181e-03 7.253472e-03 7.169116e-03 7.110717e-03 7.100488e-03 7.170824e-03 7.357769e-03 7.713263e-03 8.296316e-03 9.168421e-03 1.039471e-02 1.202700e-02 1.409160e-02 1.655095e-02 1.932580e-02 2.226252e-02 2.515127e-02 2.774196e-02 2.982053e-02 3.120787e-02 3.182085e-02 3.168395e-02 3.093632e-02 2.976177e-02 2.838502e-02 2.699806e-02 2.573437e-02 2.466565e-02 2.379038e-02 2.309294e-02 2.253656e-02 2.213344e-02 2.191141e-02 2.193080e-02 2.226022e-02 2.295076e-02 2.401831e-02 2.543108e-02 2.713013e-02 2.899683e-02 3.090072e-02 3.270210e-02 3.422377e-02 3.533348e-02 3.588297e-02 3.579820e-02 3.504018e-02 3.368576e-02 3.187316e-02 2.983561e-02 2.781200e-02 2.605872e-02 2.474979e-02 2.397637e-02 2.369850e-02 2.378910e-02 2.405296e-02 2.428054e-02 2.429544e-02 2.398805e-02 2.333982e-02 2.239940e-02 2.129003e-02 2.013189e-02 1.903758e-02 1.807337e-02 1.725424e-02 1.654554e-02 1.588683e-02 1.520789e-02 1.445812e-02 1.361477e-02 1.268646e-02 1.171403e-02 1.075128e-02 9.867298e-03 9.120326e-03 8.559053e-03 8.224797e-03 8.140334e-03 8.322854e-03 8.780587e-03 9.510577e-03 1.051407e-02 1.175575e-02 1.321077e-02 1.482129e-02 1.650493e-02 1.817596e-02 1.972445e-02 2.104048e-02 2.204270e-02 2.266665e-02 2.289460e-02 2.276683e-02 2.235007e-02 2.176942e-02 2.113776e-02 2.057229e-02 2.014488e-02 1.989148e-02 1.979784e-02 1.979959e-02 1.982846e-02 1.980617e-02 1.968751e-02 1.946103e-02 1.915325e-02 1.882016e-02 1.854899e-02 1.840736e-02 1.845344e-02 1.871310e-02 1.916653e-02 1.976413e-02 2.042144e-02 2.105677e-02 2.157217e-02 2.189272e-02 2.197280e-02 2.178584e-02 2.135547e-02 2.070843e-02 1.991270e-02 1.903460e-02 1.813846e-02 1.728612e-02 1.651215e-02 1.582950e-02 1.524880e-02 1.473733e-02 1.428127e-02 1.385932e-02 1.346599e-02 1.311217e-02 1.283869e-02 1.269261e-02 1.273590e-02 1.302862e-02 1.360990e-02 1.448828e-02 1.562609e-02 1.694663e-02 1.834169e-02 1.966976e-02 2.077719e-02 2.154348e-02 2.187934e-02 2.173536e-02 2.113933e-02 2.016685e-02 1.891472e-02 1.753422e-02 1.613943e-02 1.483365e-02 1.370374e-02 1.277200e-02 1.203784e-02 1.147870e-02 1.104632e-02 1.070007e-02 1.040463e-02 1.013045e-02 9.873055e-03 9.632124e-03 9.417536e-03 9.239881e-03 9.106154e-03 9.016939e-03 8.964244e-03 8.931677e-03 8.900838e-03 8.849952e-03 8.760523e-03 8.620666e-03 8.427585e-03 8.187218e-03 7.913290e-03 7.625180e-03 7.344373e-03 7.092280e-03 6.881645e-03 6.724785e-03 6.620137e-03 6.565281e-03 6.547197e-03 6.554680e-03 6.575434e-03 6.601376e-03 6.630637e-03 6.669487e-03 6.728174e-03 6.827667e-03 6.987234e-03 7.229462e-03 7.566292e-03 8.007630e-03 8.546069e-03 9.163808e-03 9.827143e-03 1.049190e-02 1.110472e-02 1.161377e-02 1.197144e-02 1.214337e-02 1.210704e-02 1.186403e-02 1.143457e-02 1.085097e-02 1.016036e-02 9.414387e-03 8.660860e-03 7.944651e-03 7.300031e-03 6.750567e-03 6.309360e-03 5.981309e-03 5.762068e-03 5.646042e-03 5.622219e-03 5.678641e-03 5.804596e-03 5.983342e-03 6.200620e-03 6.440104e-03 6.678409e-03 6.902577e-03 7.091450e-03 7.235428e-03 7.327859e-03 7.371781e-03 7.383205e-03 7.385809e-03 7.418771e-03 7.523896e-03 7.756941e-03 8.162498e-03 8.792428e-03 9.678032e-03 1.084192e-02 1.227168e-02 1.393970e-02 1.578786e-02 1.772801e-02 1.967484e-02 2.151087e-02 2.315350e-02 2.453383e-02 2.561659e-02 2.641289e-02 2.699687e-02 2.745322e-02 2.792419e-02 2.855058e-02 2.946891e-02 3.076649e-02 3.251699e-02 3.469289e-02 3.721017e-02 3.995812e-02 4.269356e-02 4.520463e-02 4.726627e-02 4.861445e-02 4.910979e-02 4.862794e-02 4.714241e-02 4.472723e-02 4.151409e-02 3.768824e-02 3.352246e-02 2.920671e-02 2.500489e-02 2.107774e-02 1.756566e-02 1.453913e-02 1.202975e-02 1.001425e-02 8.449657e-03 7.267842e-03 6.401535e-03 5.778626e-03 5.341348e-03 5.038213e-03 4.829572e-03 4.687346e-03 4.591625e-03 4.525951e-03 4.483619e-03 4.456520e-03 4.442581e-03 4.435889e-03 4.441428e-03 4.453128e-03 4.470726e-03 4.493431e-03 4.520103e-03 4.549440e-03 4.580218e-03 4.609406e-03 4.636860e-03 4.659402e-03 4.676036e-03 4.685756e-03 4.688160e-03 4.683999e-03 4.673130e-03 4.657242e-03 4.638087e-03 4.617707e-03 4.596735e-03 4.576646e-03 4.558088e-03 4.541770e-03 4.526601e-03 4.512543e-03 4.499027e-03 4.486210e-03 4.473084e-03 4.460733e-03 4.448874e-03 4.438976e-03 4.432088e-03 4.429981e-03 4.433528e-03 4.443267e-03 4.460566e-03 4.486348e-03 4.521398e-03 4.564556e-03 4.615604e-03 4.674732e-03 4.742331e-03 4.817717e-03 4.902722e-03 4.998521e-03 5.109433e-03 5.240555e-03 5.400914e-03 5.600282e-03 5.853413e-03 6.175758e-03 6.584065e-03 7.094061e-03 7.722185e-03 8.476419e-03 9.355064e-03 1.034923e-02 1.143761e-02 1.258326e-02 1.373807e-02 1.484281e-02 1.583487e-02 1.665457e-02 1.724261e-02 1.755574e-02 1.756894e-02 1.727687e-02 1.669343e-02 1.585628e-02 1.481752e-02 1.363896e-02 1.238457e-02 1.111968e-02 9.898675e-03 8.765354e-03 7.750327e-03 6.873960e-03 6.140552e-03 5.544467e-03 5.073721e-03 4.711937e-03 4.440487e-03 4.242321e-03 4.099425e-03 3.998644e-03 3.928069e-03 3.878845e-03 3.844343e-03 3.819978e-03 3.802529e-03 3.790138e-03 3.781583e-03 3.776145e-03 3.773354e-03 3.775311e-03 3.780205e-03 3.788966e-03 3.801438e-03 3.819084e-03 3.840882e-03 3.867322e-03 3.897800e-03 3.931805e-03 3.968275e-03 4.006043e-03 4.042503e-03 4.076912e-03 4.106191e-03 4.129495e-03 4.144303e-03 4.150156e-03 4.145930e-03 4.132293e-03 4.109557e-03 4.079162e-03 4.045140e-03 4.007906e-03 3.972944e-03 3.943574e-03 3.923884e-03 3.918971e-03 3.932636e-03 3.971823e-03 4.040316e-03 4.144771e-03 4.288826e-03 4.477219e-03 4.713387e-03 4.999699e-03 5.332903e-03 5.710025e-03 6.125718e-03 6.566920e-03 7.020252e-03 7.471234e-03 7.901403e-03 8.289948e-03 8.623211e-03 8.883991e-03 9.061578e-03 9.151259e-03 9.150172e-03 9.064385e-03 8.900356e-03 8.671990e-03 8.393831e-03 8.079176e-03 7.747136e-03 7.405866e-03 7.068776e-03 6.743348e-03 6.431710e-03 6.140440e-03 5.865868e-03 5.609611e-03 5.369010e-03 5.142997e-03 4.930626e-03 4.730508e-03 4.543691e-03 4.369598e-03 4.209755e-03 4.065313e-03 3.935320e-03 3.821216e-03 3.723645e-03 3.640637e-03 3.572583e-03 3.518386e-03 3.476446e-03 3.445263e-03 3.424926e-03 3.413575e-03 3.410548e-03 3.415400e-03 3.427916e-03 3.447817e-03 3.476182e-03 3.512233e-03 3.557118e-03 3.610699e-03 3.672816e-03 3.742791e-03 3.819312e-03 3.900697e-03 3.984230e-03 4.066668e-03 4.144405e-03 4.213573e-03 4.270384e-03 4.311418e-03 4.333952e-03 4.336196e-03 4.317494e-03 4.278523e-03 4.221139e-03 4.148645e-03 4.063857e-03 3.972452e-03 3.878114e-03 3.785359e-03 3.699592e-03 3.622379e-03 3.557566e-03 3.506809e-03 3.470395e-03 3.448561e-03 3.440279e-03 3.443781e-03 3.457044e-03 3.476941e-03 3.500942e-03 3.526430e-03 3.550139e-03 3.570751e-03 3.584737e-03 3.592085e-03 3.591152e-03 3.582153e-03 3.565900e-03 3.542938e-03 3.515439e-03 3.484779e-03 3.453316e-03 3.424024e-03 3.398302e-03 3.378689e-03 3.366645e-03 3.363942e-03 3.371371e-03 3.389801e-03 3.419763e-03 3.460207e-03 3.511789e-03 3.572306e-03 3.640759e-03 3.716059e-03 3.794353e-03 3.874090e-03 3.952873e-03 4.025699e-03 4.090618e-03 4.144108e-03 4.183767e-03 4.206443e-03 4.210484e-03 4.195486e-03 4.160838e-03 4.107232e-03 4.037195e-03 3.952673e-03 3.856635e-03 3.752988e-03 3.645580e-03 3.537643e-03 3.432039e-03 3.331902e-03 3.240053e-03 3.156826e-03 3.083803e-03 3.020696e-03 2.967956e-03 2.924518e-03 2.889241e-03 2.861468e-03 2.839747e-03 2.823210e-03 2.810101e-03 2.800439e-03 2.794127e-03 2.788868e-03 2.784994e-03 2.782817e-03 2.781931e-03 2.782898e-03 2.786371e-03 2.793841e-03 2.806261e-03 2.825693e-03 2.855030e-03 2.897606e-03 2.956724e-03 3.040154e-03 3.149394e-03 3.292200e-03 3.476961e-03 3.707743e-03 3.994849e-03 4.344899e-03 4.761893e-03 5.256053e-03 5.826500e-03 6.476651e-03 7.211932e-03 8.020773e-03 8.906870e-03 9.857650e-03 1.086905e-02 1.193244e-02 1.302816e-02 1.415770e-02 1.530056e-02 1.644913e-02 1.759208e-02 1.871842e-02 1.981841e-02 2.087549e-02 2.187647e-02 2.281442e-02 2.365120e-02 2.438372e-02 2.498219e-02 2.542563e-02 2.568771e-02 2.576006e-02 2.560848e-02 2.524720e-02 2.465257e-02 2.384803e-02 2.283412e-02 2.164812e-02 2.030795e-02 1.885441e-02 1.733117e-02 1.576190e-02 1.421228e-02 1.268731e-02 1.124669e-02 9.896244e-03 8.662309e-03 7.558329e-03 6.580715e-03 5.742671e-03 5.023410e-03 4.429121e-03 3.937080e-03 3.541758e-03 3.224624e-03 2.978214e-03 2.786244e-03 2.640411e-03 2.529198e-03 2.446293e-03 2.384210e-03 2.338196e-03 2.303784e-03 2.277875e-03 2.258323e-03 2.243302e-03 2.231755e-03 2.222295e-03 2.214493e-03 2.207316e-03 2.202737e-03 2.198725e-03 2.195677e-03 2.193202e-03 2.191497e-03 2.190480e-03 2.190530e-03 2.191428e-03 2.192914e-03 2.195464e-03 2.199154e-03 2.203571e-03 2.209324e-03 2.216033e-03 2.223871e-03 2.233076e-03 2.243403e-03 2.254989e-03 2.268474e-03 2.283149e-03 2.299963e-03 2.318688e-03 2.339682e-03 2.363352e-03 2.389600e-03 2.418834e-03 2.451350e-03 2.486878e-03 2.525500e-03 2.567137e-03 2.611092e-03 2.657046e-03 2.703850e-03 2.750718e-03 2.796584e-03 2.839948e-03 2.879167e-03 2.913008e-03 2.940428e-03 2.959210e-03 2.969251e-03 2.969386e-03 2.958973e-03 2.938322e-03 2.907422e-03 2.867481e-03 2.818633e-03 2.763286e-03 2.702049e-03 2.636965e-03 2.569883e-03 2.502242e-03 2.435752e-03 2.370950e-03 2.310512e-03 2.253385e-03 2.201702e-03 2.154844e-03 2.113553e-03 2.077086e-03 2.045725e-03 2.018263e-03 1.995506e-03 1.975669e-03 1.958733e-03 1.944168e-03 1.932036e-03 1.920769e-03 1.911121e-03 1.902393e-03 1.894107e-03 1.886368e-03 1.879299e-03 1.872626e-03 1.866063e-03 1.859849e-03 1.854269e-03 1.849128e-03 1.844289e-03 1.840046e-03 1.837048e-03 1.834246e-03 1.833095e-03 1.832731e-03 1.833611e-03 1.836266e-03 1.840666e-03 1.846567e-03 1.854947e-03 1.865400e-03 1.877946e-03 1.893073e-03 1.911111e-03 1.931048e-03 1.953250e-03 1.977745e-03 2.003683e-03 2.030801e-03 2.059063e-03 2.087239e-03 2.115119e-03 2.141611e-03 2.166799e-03 2.189723e-03 2.209858e-03 2.226717e-03 2.240553e-03 2.251138e-03 2.258336e-03 2.263553e-03 2.266073e-03 2.267923e-03 2.269064e-03 2.271691e-03 2.275663e-03 2.283090e-03 2.294252e-03 2.310107e-03 2.331312e-03 2.358002e-03 2.390718e-03 2.427560e-03 2.470156e-03 2.514558e-03 2.562032e-03 2.608323e-03 2.653901e-03 2.696229e-03 2.732287e-03 2.762195e-03 2.782849e-03 2.794198e-03 2.794637e-03 2.783786e-03 2.762296e-03 2.730098e-03 2.687291e-03 2.636585e-03 2.578384e-03 2.514087e-03 2.446474e-03 2.376940e-03 2.306352e-03 2.237652e-03 2.171373e-03 2.108585e-03 2.051177e-03 1.997998e-03 1.950995e-03 1.909602e-03 1.872990e-03 1.841670e-03 1.814709e-03 1.792104e-03 1.772202e-03 1.755408e-03 1.740686e-03 1.727840e-03 1.716106e-03 1.705772e-03 1.696131e-03 1.687420e-03 1.679601e-03 1.672663e-03 1.667111e-03 1.662565e-03 1.659422e-03 1.658267e-03 1.658438e-03 1.660593e-03 1.664660e-03 1.670281e-03 1.677588e-03 1.686275e-03 1.696237e-03 1.707097e-03 1.718026e-03 1.728852e-03 1.739725e-03 1.749031e-03 1.757011e-03 1.763482e-03 1.767317e-03 1.768699e-03 1.767365e-03 1.763267e-03 1.756019e-03 1.746148e-03 1.733295e-03 1.717982e-03 1.700691e-03 1.681538e-03 1.660829e-03 1.639729e-03 1.617774e-03 1.596196e-03 1.574864e-03 1.554228e-03 1.535226e-03 1.517074e-03 1.500920e-03 1.486077e-03 1.473287e-03 1.461950e-03 1.452038e-03 1.443501e-03 1.435914e-03 1.429845e-03 1.423894e-03 1.418934e-03 1.414431e-03 1.410041e-03 1.406002e-03 1.402053e-03 1.398470e-03 1.395367e-03 1.392895e-03 1.391120e-03 1.390343e-03 1.391348e-03 1.394177e-03 1.399671e-03 1.408418e-03 1.420838e-03 1.437826e-03 1.460196e-03 1.488398e-03 1.523512e-03 1.566060e-03 1.617328e-03 1.677236e-03 1.746814e-03 1.826849e-03 1.917049e-03 2.018214e-03 2.129886e-03 2.252390e-03 2.385132e-03 2.527592e-03 2.678819e-03 2.837131e-03 3.001981e-03 3.171357e-03 3.343470e-03 3.515975e-03 3.687850e-03 3.855242e-03 4.016959e-03 4.169674e-03 4.312363e-03 4.441444e-03 4.554633e-03 4.650847e-03 4.727764e-03 4.783681e-03 4.817398e-03 4.828287e-03 4.815783e-03 4.779260e-03 4.720572e-03 4.638219e-03 4.535539e-03 4.412400e-03 4.272569e-03 4.116280e-03 3.947514e-03 3.768730e-03 3.582007e-03 3.391054e-03 3.198229e-03 3.006306e-03 2.816737e-03 2.633082e-03 2.456795e-03 2.290037e-03 2.132901e-03 1.987481e-03 1.853456e-03 1.731361e-03 1.621610e-03 1.523377e-03 1.436772e-03 1.360936e-03 1.294741e-03 1.237915e-03 1.189030e-03 1.147576e-03 1.112573e-03 1.083188e-03 1.058689e-03 1.038004e-03 1.020780e-03 1.006420e-03 9.944722e-04 9.844701e-04 9.757478e-04 9.684005e-04 9.618528e-04 9.560822e-04 9.504914e-04 9.455624e-04 9.407638e-04 9.358409e-04 9.309943e-04 9.258915e-04 9.205480e-04 9.146478e-04 9.084981e-04 9.017488e-04 8.943778e-04 8.861325e-04 8.770946e-04 8.669812e-04 8.557799e-04 8.434984e-04 8.298810e-04 8.150231e-04 7.986739e-04 7.808888e-04 7.617136e-04 7.410483e-04 7.188182e-04 6.951288e-04 6.701424e-04 6.437958e-04 6.163239e-04 5.877310e-04 5.583017e-04 5.281093e-04 4.974369e-04 4.664016e-04 ''') ImportString(u'flux_model_0_7(numeric)',''' 2.960127e-03 3.898180e-03 4.538399e-03 5.117529e-03 6.223361e-03 8.443569e-03 1.161904e-02 1.433340e-02 1.476923e-02 1.255534e-02 9.201769e-03 6.506684e-03 5.090013e-03 4.579968e-03 4.447838e-03 4.413846e-03 4.400058e-03 4.396189e-03 4.397586e-03 4.396905e-03 4.386854e-03 4.364470e-03 4.339086e-03 4.328808e-03 4.354148e-03 4.425474e-03 4.537631e-03 4.674295e-03 4.809780e-03 4.914867e-03 4.960325e-03 4.947345e-03 4.927428e-03 4.984884e-03 5.179337e-03 5.526901e-03 6.030719e-03 6.665152e-03 7.288787e-03 7.645638e-03 7.554182e-03 7.083422e-03 6.480433e-03 5.951771e-03 5.550399e-03 5.242111e-03 5.010124e-03 4.884662e-03 4.885051e-03 4.979834e-03 5.093996e-03 5.157995e-03 5.158198e-03 5.148880e-03 5.203719e-03 5.357216e-03 5.582650e-03 5.798474e-03 5.913054e-03 5.885488e-03 5.750393e-03 5.580127e-03 5.430615e-03 5.338663e-03 5.329562e-03 5.404790e-03 5.513447e-03 5.567058e-03 5.513664e-03 5.390704e-03 5.283020e-03 5.247741e-03 5.288353e-03 5.402729e-03 5.656997e-03 6.244950e-03 7.421661e-03 9.201732e-03 1.104040e-02 1.202964e-02 1.167755e-02 1.036167e-02 8.852511e-03 7.635790e-03 6.806801e-03 6.306770e-03 6.053404e-03 5.942179e-03 5.877909e-03 5.824433e-03 5.788146e-03 5.775002e-03 5.777035e-03 5.786637e-03 5.815971e-03 5.885222e-03 6.016816e-03 6.238043e-03 6.565017e-03 6.965520e-03 7.310446e-03 7.430524e-03 7.251556e-03 6.868551e-03 6.461494e-03 6.152929e-03 5.969613e-03 5.881832e-03 5.850369e-03 5.849684e-03 5.875109e-03 5.944403e-03 6.101490e-03 6.405338e-03 6.878087e-03 7.444120e-03 7.961059e-03 8.320566e-03 8.503940e-03 8.490849e-03 8.216070e-03 7.671875e-03 7.008395e-03 6.443795e-03 6.103557e-03 5.970719e-03 5.967635e-03 6.025407e-03 6.101462e-03 6.183536e-03 6.287287e-03 6.462595e-03 6.786587e-03 7.316158e-03 8.010244e-03 8.661106e-03 8.984734e-03 8.852773e-03 8.418823e-03 7.952355e-03 7.591396e-03 7.304902e-03 7.023414e-03 6.749637e-03 6.524888e-03 6.374770e-03 6.294181e-03 6.264651e-03 6.267293e-03 6.287976e-03 6.312986e-03 6.333719e-03 6.352297e-03 6.384126e-03 6.455818e-03 6.594409e-03 6.803618e-03 7.046162e-03 7.272353e-03 7.471230e-03 7.661150e-03 7.812639e-03 7.832351e-03 7.654584e-03 7.334662e-03 7.007094e-03 6.772770e-03 6.648619e-03 6.602378e-03 6.596916e-03 6.611587e-03 6.640989e-03 6.692581e-03 6.774412e-03 6.887186e-03 7.008251e-03 7.101106e-03 7.143964e-03 7.143591e-03 7.133951e-03 7.145017e-03 7.181084e-03 7.225574e-03 7.266162e-03 7.340247e-03 7.545477e-03 7.976945e-03 8.607261e-03 9.214542e-03 9.487975e-03 9.272526e-03 8.718247e-03 8.165139e-03 7.884301e-03 7.911400e-03 8.083208e-03 8.177343e-03 8.077710e-03 7.823295e-03 7.545377e-03 7.347345e-03 7.254073e-03 7.236221e-03 7.260816e-03 7.304771e-03 7.359999e-03 7.426362e-03 7.519101e-03 7.656467e-03 7.844352e-03 8.053956e-03 8.251062e-03 8.443192e-03 8.709261e-03 9.164157e-03 9.897548e-03 1.091347e-02 1.204785e-02 1.294289e-02 1.322353e-02 1.286212e-02 1.235710e-02 1.253288e-02 1.418151e-02 1.780613e-02 2.328819e-02 2.938928e-02 3.374957e-02 3.410266e-02 3.001171e-02 2.332717e-02 1.675964e-02 1.209145e-02 9.577644e-03 8.569764e-03 8.330896e-03 8.398942e-03 8.562882e-03 8.716597e-03 8.796167e-03 8.774783e-03 8.685703e-03 8.583850e-03 8.518599e-03 8.539189e-03 8.715560e-03 9.111929e-03 9.695668e-03 1.028540e-02 1.065277e-02 1.067826e-02 1.043651e-02 1.008176e-02 9.717317e-03 9.362831e-03 9.018431e-03 8.714753e-03 8.483836e-03 8.336628e-03 8.257042e-03 8.220593e-03 8.214160e-03 8.220356e-03 8.238998e-03 8.261262e-03 8.286742e-03 8.314819e-03 8.342952e-03 8.374476e-03 8.410899e-03 8.453525e-03 8.503521e-03 8.555354e-03 8.615251e-03 8.713386e-03 8.922225e-03 9.349107e-03 1.008609e-02 1.112222e-02 1.229568e-02 1.329855e-02 1.378705e-02 1.355963e-02 1.268444e-02 1.148297e-02 1.033928e-02 9.512042e-03 9.044262e-03 8.852127e-03 8.807704e-03 8.824340e-03 8.854005e-03 8.878395e-03 8.897493e-03 8.908582e-03 8.914967e-03 8.919770e-03 8.918653e-03 8.909144e-03 8.873051e-03 8.781673e-03 8.614950e-03 8.383610e-03 8.149443e-03 7.997592e-03 7.959776e-03 7.960403e-03 7.854051e-03 7.540452e-03 7.053298e-03 6.531589e-03 6.109568e-03 5.845498e-03 5.715793e-03 5.671904e-03 5.667936e-03 5.681901e-03 5.703205e-03 5.729217e-03 5.758529e-03 5.790872e-03 5.818757e-03 5.835803e-03 5.841740e-03 5.838498e-03 5.838447e-03 5.848585e-03 5.877578e-03 5.923159e-03 5.982175e-03 6.039282e-03 6.081440e-03 6.094282e-03 6.080564e-03 6.048785e-03 6.017065e-03 5.993270e-03 5.983217e-03 5.983445e-03 5.994094e-03 6.019390e-03 6.077086e-03 6.198476e-03 6.443219e-03 6.913088e-03 7.734777e-03 8.999220e-03 1.063516e-02 1.231249e-02 1.349413e-02 1.371116e-02 1.284685e-02 1.125386e-02 9.506701e-03 8.073179e-03 7.138472e-03 6.644627e-03 6.445741e-03 6.428681e-03 6.546698e-03 6.797493e-03 7.169737e-03 7.605185e-03 7.993665e-03 8.231495e-03 8.285820e-03 8.238515e-03 8.275026e-03 8.670697e-03 9.754004e-03 1.179051e-02 1.476615e-02 1.812732e-02 2.083617e-02 2.181327e-02 2.062007e-02 1.775078e-02 1.433463e-02 1.144887e-02 9.615536e-03 8.765321e-03 8.496395e-03 8.389816e-03 8.198531e-03 7.873841e-03 7.501112e-03 7.193843e-03 7.036048e-03 7.074254e-03 7.349217e-03 7.880680e-03 8.641529e-03 9.484537e-03 1.016947e-02 1.044655e-02 1.020689e-02 9.554788e-03 8.734426e-03 7.994536e-03 7.469190e-03 7.174354e-03 7.060587e-03 7.086135e-03 7.247129e-03 7.577963e-03 8.117152e-03 8.840683e-03 9.621995e-03 1.023873e-02 1.047593e-02 1.024391e-02 9.640235e-03 8.889017e-03 8.217474e-03 7.757142e-03 7.524492e-03 7.477149e-03 7.558837e-03 7.715782e-03 7.904020e-03 8.072513e-03 8.178604e-03 8.200102e-03 8.147780e-03 8.054144e-03 7.956036e-03 7.873085e-03 7.810992e-03 7.768456e-03 7.748119e-03 7.750516e-03 7.771073e-03 7.795711e-03 7.806086e-03 7.793751e-03 7.759152e-03 7.719363e-03 7.690059e-03 7.690627e-03 7.738977e-03 7.867218e-03 8.098211e-03 8.430336e-03 8.802360e-03 9.104138e-03 9.224736e-03 9.115278e-03 8.824034e-03 8.469356e-03 8.184228e-03 8.045198e-03 8.063433e-03 8.180297e-03 8.312327e-03 8.377594e-03 8.341962e-03 8.217651e-03 8.061583e-03 7.932048e-03 7.881698e-03 7.943803e-03 8.160812e-03 8.580476e-03 9.244765e-03 1.014443e-02 1.116321e-02 1.208268e-02 1.264495e-02 1.267334e-02 1.217734e-02 1.136190e-02 1.055139e-02 1.003285e-02 9.956781e-03 1.029413e-02 1.087588e-02 1.147101e-02 1.187052e-02 1.194258e-02 1.165980e-02 1.109132e-02 1.035357e-02 9.596229e-03 8.941106e-03 8.469497e-03 8.209771e-03 8.148061e-03 8.239838e-03 8.425917e-03 8.636608e-03 8.805538e-03 8.882228e-03 8.852070e-03 8.729234e-03 8.555645e-03 8.371741e-03 8.208545e-03 8.082299e-03 7.993716e-03 7.935530e-03 7.921733e-03 7.965598e-03 8.220197e-03 9.073138e-03 1.149528e-02 1.756746e-02 3.101540e-02 5.726705e-02 1.019838e-01 1.678098e-01 2.497843e-01 3.333407e-01 3.972229e-01 4.214890e-01 3.980621e-01 3.345206e-01 2.505518e-01 1.677327e-01 1.012805e-01 5.634250e-02 3.029798e-02 1.735596e-02 1.188816e-02 1.010739e-02 9.932546e-03 1.042359e-02 1.117907e-02 1.199363e-02 1.276577e-02 1.340851e-02 1.388496e-02 1.415901e-02 1.418437e-02 1.389882e-02 1.329144e-02 1.243257e-02 1.146172e-02 1.054420e-02 9.790503e-03 9.236124e-03 8.851115e-03 8.584013e-03 8.392323e-03 8.257817e-03 8.173894e-03 8.148514e-03 8.189110e-03 8.306450e-03 8.500712e-03 8.765319e-03 9.074441e-03 9.389206e-03 9.669512e-03 9.892940e-03 1.005243e-02 1.016487e-02 1.024334e-02 1.028762e-02 1.027809e-02 1.018810e-02 1.000528e-02 9.743224e-03 9.439408e-03 9.141863e-03 8.883017e-03 8.685010e-03 8.549401e-03 8.471791e-03 8.447894e-03 8.469976e-03 8.540663e-03 8.660073e-03 8.836240e-03 9.065991e-03 9.334003e-03 9.612817e-03 9.861641e-03 1.005731e-02 1.021012e-02 1.035765e-02 1.056609e-02 1.089637e-02 1.143287e-02 1.233342e-02 1.394713e-02 1.687716e-02 2.193286e-02 2.984014e-02 4.072876e-02 5.360405e-02 6.621631e-02 7.558471e-02 7.909726e-02 7.562791e-02 6.617672e-02 5.329244e-02 4.001451e-02 2.875574e-02 2.065354e-02 1.567703e-02 1.313946e-02 1.215824e-02 1.197477e-02 1.206765e-02 1.212130e-02 1.198997e-02 1.164727e-02 1.113854e-02 1.055708e-02 9.997049e-03 9.524122e-03 9.197932e-03 9.084083e-03 9.321747e-03 1.027066e-02 1.268970e-02 1.797892e-02 2.832676e-02 4.660252e-02 7.574301e-02 1.175657e-01 1.712523e-01 2.324975e-01 2.934030e-01 3.439203e-01 3.744144e-01 3.786828e-01 3.556364e-01 3.098219e-01 2.502314e-01 1.873092e-01 1.302553e-01 8.481877e-02 5.290705e-02 3.356128e-02 2.454664e-02 2.395526e-02 3.096322e-02 4.584013e-02 6.880349e-02 9.906499e-02 1.331854e-01 1.655032e-01 1.889249e-01 1.976456e-01 1.893034e-01 1.661206e-01 1.338478e-01 9.964475e-02 6.925201e-02 4.586915e-02 3.001861e-02 2.037991e-02 1.506296e-02 1.234541e-02 1.101848e-02 1.039086e-02 1.011298e-02 1.002831e-02 1.006738e-02 1.018961e-02 1.040265e-02 1.073894e-02 1.128613e-02 1.218518e-02 1.361638e-02 1.577201e-02 1.880303e-02 2.277419e-02 2.762404e-02 3.310011e-02 3.878719e-02 4.409776e-02 4.841720e-02 5.119253e-02 5.220903e-02 5.172019e-02 5.050914e-02 4.979609e-02 5.095143e-02 5.519238e-02 6.330054e-02 7.556026e-02 9.188043e-02 1.118142e-01 1.346575e-01 1.592054e-01 1.838305e-01 2.065712e-01 2.257908e-01 2.404048e-01 2.501293e-01 2.548637e-01 2.541668e-01 2.472168e-01 2.330752e-01 2.118809e-01 1.854399e-01 1.570281e-01 1.305438e-01 1.092020e-01 9.464280e-02 8.675305e-02 8.403017e-02 8.424836e-02 8.507838e-02 8.456123e-02 8.156984e-02 7.581957e-02 6.789507e-02 5.899438e-02 5.061303e-02 4.426210e-02 4.117208e-02 4.219978e-02 4.749728e-02 5.646183e-02 6.751933e-02 7.833651e-02 8.643230e-02 8.980897e-02 8.771384e-02 8.090673e-02 7.136626e-02 6.150588e-02 5.336856e-02 4.815053e-02 4.590474e-02 4.591396e-02 4.710210e-02 4.850046e-02 4.945895e-02 4.978151e-02 4.965020e-02 4.943003e-02 4.958477e-02 5.057728e-02 5.295086e-02 5.740999e-02 6.491864e-02 7.650673e-02 9.280539e-02 1.136113e-01 1.374047e-01 1.613400e-01 1.818870e-01 1.958475e-01 2.012718e-01 1.979657e-01 1.874851e-01 1.726043e-01 1.565975e-01 1.428584e-01 1.349196e-01 1.362181e-01 1.502829e-01 1.801733e-01 2.275842e-01 2.917465e-01 3.679773e-01 4.476063e-01 5.184686e-01 5.679940e-01 5.863932e-01 5.697607e-01 5.218078e-01 4.520990e-01 3.729249e-01 2.959581e-01 2.290827e-01 1.762598e-01 1.374384e-01 1.105785e-01 9.294958e-02 8.208787e-02 7.610568e-02 7.374725e-02 7.418338e-02 7.688164e-02 8.136597e-02 8.709437e-02 9.351331e-02 9.988935e-02 1.054833e-01 1.096629e-01 1.120196e-01 1.124609e-01 1.112301e-01 1.089661e-01 1.065675e-01 1.050636e-01 1.053783e-01 1.080589e-01 1.131090e-01 1.199156e-01 1.274435e-01 1.343687e-01 1.396195e-01 1.425686e-01 1.432068e-01 1.422292e-01 1.407882e-01 1.401087e-01 1.412751e-01 1.449906e-01 1.513014e-01 1.597328e-01 1.692872e-01 1.787416e-01 1.868190e-01 1.928353e-01 1.969443e-01 2.005860e-01 2.061909e-01 2.168368e-01 2.351935e-01 2.625233e-01 2.981558e-01 3.384342e-01 3.779880e-01 4.098327e-01 4.276902e-01 4.271323e-01 4.071823e-01 3.707059e-01 3.233549e-01 2.723569e-01 2.244421e-01 1.846067e-01 1.550906e-01 1.359179e-01 1.251092e-01 1.200927e-01 1.182635e-01 1.175941e-01 1.168988e-01 1.156118e-01 1.136303e-01 1.109796e-01 1.077289e-01 1.039257e-01 9.980093e-02 9.595789e-02 9.337254e-02 9.333248e-02 9.712571e-02 1.056385e-01 1.190841e-01 1.367336e-01 1.569803e-01 1.776668e-01 1.963953e-01 2.110761e-01 2.202471e-01 2.233006e-01 2.205243e-01 2.128153e-01 2.016585e-01 1.885353e-01 1.749376e-01 1.619066e-01 1.501627e-01 1.400890e-01 1.319053e-01 1.259214e-01 1.228726e-01 1.239032e-01 1.305530e-01 1.443705e-01 1.663614e-01 1.964972e-01 2.331833e-01 2.733651e-01 3.127477e-01 3.468107e-01 3.714429e-01 3.842555e-01 3.845891e-01 3.737481e-01 3.542330e-01 3.291938e-01 3.014634e-01 2.734221e-01 2.464585e-01 2.215368e-01 1.990747e-01 1.793052e-01 1.623819e-01 1.482981e-01 1.369209e-01 1.279397e-01 1.208448e-01 1.150556e-01 1.100659e-01 1.054762e-01 1.011948e-01 9.732743e-02 9.418336e-02 9.215418e-02 9.150715e-02 9.240822e-02 9.473208e-02 9.811165e-02 1.021007e-01 1.061455e-01 1.098765e-01 1.130830e-01 1.158335e-01 1.182925e-01 1.208093e-01 1.236048e-01 1.269020e-01 1.307782e-01 1.352202e-01 1.402484e-01 1.457827e-01 1.518479e-01 1.584845e-01 1.658068e-01 1.740890e-01 1.838063e-01 1.955232e-01 2.100108e-01 2.276698e-01 2.484301e-01 2.713525e-01 2.944449e-01 3.147860e-01 3.291861e-01 3.345089e-01 3.289011e-01 3.119166e-01 2.849467e-01 2.507058e-01 2.129171e-01 1.751275e-01 1.405208e-01 1.110897e-01 8.784912e-02 7.087205e-02 5.958149e-02 5.311729e-02 5.050030e-02 5.079257e-02 5.316955e-02 5.690591e-02 6.136206e-02 6.597739e-02 7.027803e-02 7.390693e-02 7.659137e-02 7.824707e-02 7.894589e-02 7.892942e-02 7.858036e-02 7.835929e-02 7.874124e-02 8.006661e-02 8.251293e-02 8.603037e-02 9.032673e-02 9.491573e-02 9.922995e-02 1.026992e-01 1.049265e-01 1.058098e-01 1.056169e-01 1.050260e-01 1.050749e-01 1.069597e-01 1.118166e-01 1.204290e-01 1.329270e-01 1.486479e-01 1.660892e-01 1.831774e-01 1.975922e-01 2.073913e-01 2.114555e-01 2.099018e-01 2.041217e-01 1.966978e-01 1.906923e-01 1.890707e-01 1.940735e-01 2.065068e-01 2.254821e-01 2.484998e-01 2.717602e-01 2.909664e-01 3.022251e-01 3.028333e-01 2.917553e-01 2.698899e-01 2.399825e-01 2.054327e-01 1.700943e-01 1.372408e-01 1.092233e-01 8.714168e-02 7.112133e-02 6.041747e-02 5.386949e-02 5.020262e-02 4.822029e-02 4.701348e-02 4.595758e-02 4.476451e-02 4.337365e-02 4.192777e-02 4.063855e-02 3.974693e-02 3.942517e-02 3.981235e-02 4.094990e-02 4.279568e-02 4.527697e-02 4.824365e-02 5.149234e-02 5.478309e-02 5.778987e-02 6.020859e-02 6.166871e-02 6.196611e-02 6.092510e-02 5.858558e-02 5.511833e-02 5.086701e-02 4.621620e-02 4.161150e-02 3.743857e-02 3.400281e-02 3.153643e-02 3.017650e-02 2.999426e-02 3.101825e-02 3.324782e-02 3.665177e-02 4.108953e-02 4.638637e-02 5.224596e-02 5.828870e-02 6.404173e-02 6.910512e-02 7.312435e-02 7.588940e-02 7.738502e-02 7.778990e-02 7.736056e-02 7.643516e-02 7.527815e-02 7.408263e-02 7.292838e-02 7.177950e-02 7.062083e-02 6.940363e-02 6.823558e-02 6.723811e-02 6.660286e-02 6.650222e-02 6.702153e-02 6.811781e-02 6.961613e-02 7.127337e-02 7.274231e-02 7.373298e-02 7.404064e-02 7.348102e-02 7.206271e-02 6.979762e-02 6.682780e-02 6.328631e-02 5.940868e-02 5.542029e-02 5.160024e-02 4.816841e-02 4.535224e-02 4.326293e-02 4.193963e-02 4.127519e-02 4.109443e-02 4.116395e-02 4.125036e-02 4.116836e-02 4.081281e-02 4.018335e-02 3.934353e-02 3.844560e-02 3.761313e-02 3.695516e-02 3.651576e-02 3.627198e-02 3.613722e-02 3.600078e-02 3.573894e-02 3.526843e-02 3.455390e-02 3.361388e-02 3.252738e-02 3.139973e-02 3.038570e-02 2.963231e-02 2.928328e-02 2.948091e-02 3.032427e-02 3.188384e-02 3.416584e-02 3.710841e-02 4.061935e-02 4.445050e-02 4.838966e-02 5.214581e-02 5.543761e-02 5.804991e-02 5.982273e-02 6.070132e-02 6.076403e-02 6.016629e-02 5.915629e-02 5.804235e-02 5.710211e-02 5.664089e-02 5.682870e-02 5.776628e-02 5.941824e-02 6.162021e-02 6.412568e-02 6.658619e-02 6.868313e-02 7.010636e-02 7.067198e-02 7.032243e-02 6.911733e-02 6.723961e-02 6.498842e-02 6.261174e-02 6.039079e-02 5.851097e-02 5.705302e-02 5.601151e-02 5.530527e-02 5.481886e-02 5.439768e-02 5.391862e-02 5.329728e-02 5.246486e-02 5.144593e-02 5.022991e-02 4.889421e-02 4.749600e-02 4.609647e-02 4.476835e-02 4.354208e-02 4.242963e-02 4.145037e-02 4.055157e-02 3.971256e-02 3.890549e-02 3.812588e-02 3.739881e-02 3.680819e-02 3.645562e-02 3.646203e-02 3.696283e-02 3.803680e-02 3.971346e-02 4.193015e-02 4.454412e-02 4.736454e-02 5.012731e-02 5.255828e-02 5.444351e-02 5.563060e-02 5.602951e-02 5.569450e-02 5.475479e-02 5.336096e-02 5.177049e-02 5.014921e-02 4.866247e-02 4.744599e-02 4.653377e-02 4.594832e-02 4.567862e-02 4.569458e-02 4.599379e-02 4.657990e-02 4.744823e-02 4.862688e-02 5.009979e-02 5.182566e-02 5.371227e-02 5.561664e-02 5.735354e-02 5.871481e-02 5.948927e-02 5.952343e-02 5.869602e-02 5.698837e-02 5.447241e-02 5.131383e-02 4.773987e-02 4.401726e-02 4.041862e-02 3.718454e-02 3.450470e-02 3.247772e-02 3.115165e-02 3.046328e-02 3.031845e-02 3.055913e-02 3.102795e-02 3.157305e-02 3.207159e-02 3.244737e-02 3.268160e-02 3.279494e-02 3.287294e-02 3.300608e-02 3.331000e-02 3.386491e-02 3.473795e-02 3.592481e-02 3.737498e-02 3.897784e-02 4.058292e-02 4.200234e-02 4.306228e-02 4.361171e-02 4.355433e-02 4.283898e-02 4.150205e-02 3.964364e-02 3.740039e-02 3.495293e-02 3.248767e-02 3.016669e-02 2.813500e-02 2.649316e-02 2.530023e-02 2.457601e-02 2.431395e-02 2.447162e-02 2.501110e-02 2.587348e-02 2.699473e-02 2.832873e-02 2.978788e-02 3.129971e-02 3.279286e-02 3.413889e-02 3.527329e-02 3.608095e-02 3.650489e-02 3.650095e-02 3.606167e-02 3.524594e-02 3.413271e-02 3.285788e-02 3.157253e-02 3.044845e-02 2.962556e-02 2.925727e-02 2.943243e-02 3.021303e-02 3.158290e-02 3.350799e-02 3.587971e-02 3.855207e-02 4.137921e-02 4.416614e-02 4.677434e-02 4.909067e-02 5.104851e-02 5.266101e-02 5.403892e-02 5.530748e-02 5.668296e-02 5.838356e-02 6.061503e-02 6.350230e-02 6.715403e-02 7.150391e-02 7.639694e-02 8.163513e-02 8.679625e-02 9.151164e-02 9.540792e-02 9.802556e-02 9.914309e-02 9.855767e-02 9.623404e-02 9.231404e-02 8.703026e-02 8.070613e-02 7.380795e-02 6.664458e-02 5.965659e-02 5.310174e-02 4.718643e-02 4.201253e-02 3.762017e-02 3.395115e-02 3.094013e-02 2.846894e-02 2.645507e-02 2.478925e-02 2.340713e-02 2.225163e-02 2.127120e-02 2.044374e-02 1.974852e-02 1.915416e-02 1.866037e-02 1.824254e-02 1.789810e-02 1.760971e-02 1.738637e-02 1.721545e-02 1.709527e-02 1.702742e-02 1.700741e-02 1.702975e-02 1.708912e-02 1.716951e-02 1.726313e-02 1.735208e-02 1.742391e-02 1.746621e-02 1.747148e-02 1.743848e-02 1.736454e-02 1.725686e-02 1.712511e-02 1.698140e-02 1.683477e-02 1.669584e-02 1.657170e-02 1.646731e-02 1.637947e-02 1.630653e-02 1.624411e-02 1.618933e-02 1.613590e-02 1.608600e-02 1.603887e-02 1.600243e-02 1.598462e-02 1.599718e-02 1.604947e-02 1.614808e-02 1.630196e-02 1.651616e-02 1.679230e-02 1.711973e-02 1.749009e-02 1.789357e-02 1.831934e-02 1.875161e-02 1.918723e-02 1.962367e-02 2.007545e-02 2.056768e-02 2.114892e-02 2.187826e-02 2.284282e-02 2.413810e-02 2.586759e-02 2.812824e-02 3.101975e-02 3.459636e-02 3.885938e-02 4.376695e-02 4.921389e-02 5.501051e-02 6.090492e-02 6.658990e-02 7.174029e-02 7.604320e-02 7.919050e-02 8.095347e-02 8.120037e-02 7.989922e-02 7.711794e-02 7.304280e-02 6.793827e-02 6.211698e-02 5.590190e-02 4.962214e-02 4.355265e-02 3.791244e-02 3.285453e-02 2.848062e-02 2.480849e-02 2.180897e-02 1.942129e-02 1.756335e-02 1.614374e-02 1.507852e-02 1.428290e-02 1.369345e-02 1.325582e-02 1.292942e-02 1.268481e-02 1.250101e-02 1.236320e-02 1.226215e-02 1.219057e-02 1.214238e-02 1.211281e-02 1.210717e-02 1.211438e-02 1.213393e-02 1.216283e-02 1.220249e-02 1.224840e-02 1.229963e-02 1.235426e-02 1.240973e-02 1.246393e-02 1.251377e-02 1.255355e-02 1.258344e-02 1.259675e-02 1.259487e-02 1.257320e-02 1.253339e-02 1.247558e-02 1.240342e-02 1.231937e-02 1.222744e-02 1.213866e-02 1.205271e-02 1.198126e-02 1.193189e-02 1.191045e-02 1.192796e-02 1.198997e-02 1.211116e-02 1.229794e-02 1.256329e-02 1.291397e-02 1.335930e-02 1.390528e-02 1.455658e-02 1.530457e-02 1.614228e-02 1.705658e-02 1.801984e-02 1.900309e-02 1.997506e-02 2.089825e-02 2.172907e-02 2.244101e-02 2.299971e-02 2.338571e-02 2.359213e-02 2.361527e-02 2.347034e-02 2.317205e-02 2.274990e-02 2.223397e-02 2.165067e-02 2.103571e-02 2.040232e-02 1.977492e-02 1.916324e-02 1.856771e-02 1.799791e-02 1.744308e-02 1.690552e-02 1.637867e-02 1.586221e-02 1.535522e-02 1.485901e-02 1.437861e-02 1.391646e-02 1.347965e-02 1.307484e-02 1.270038e-02 1.236292e-02 1.206605e-02 1.180458e-02 1.158154e-02 1.139454e-02 1.124016e-02 1.111560e-02 1.102149e-02 1.095369e-02 1.091157e-02 1.089420e-02 1.090139e-02 1.093257e-02 1.099101e-02 1.107385e-02 1.118416e-02 1.132105e-02 1.148341e-02 1.166881e-02 1.187320e-02 1.209187e-02 1.231711e-02 1.254017e-02 1.275150e-02 1.294097e-02 1.309883e-02 1.321627e-02 1.328635e-02 1.330457e-02 1.326930e-02 1.318232e-02 1.304834e-02 1.287596e-02 1.267171e-02 1.245072e-02 1.222175e-02 1.199619e-02 1.178851e-02 1.160161e-02 1.144602e-02 1.132617e-02 1.124265e-02 1.119614e-02 1.118445e-02 1.120344e-02 1.124852e-02 1.131170e-02 1.138720e-02 1.146905e-02 1.154944e-02 1.162584e-02 1.168916e-02 1.174121e-02 1.177828e-02 1.180243e-02 1.181722e-02 1.182543e-02 1.183505e-02 1.185085e-02 1.188132e-02 1.193687e-02 1.202223e-02 1.214572e-02 1.231269e-02 1.252814e-02 1.279409e-02 1.311164e-02 1.348040e-02 1.389460e-02 1.435364e-02 1.484385e-02 1.535884e-02 1.589094e-02 1.641805e-02 1.693094e-02 1.741737e-02 1.784985e-02 1.821818e-02 1.850497e-02 1.869781e-02 1.878159e-02 1.874940e-02 1.860034e-02 1.833319e-02 1.795438e-02 1.747851e-02 1.691910e-02 1.629412e-02 1.562749e-02 1.494114e-02 1.425477e-02 1.358522e-02 1.295166e-02 1.236938e-02 1.184227e-02 1.137826e-02 1.097655e-02 1.063849e-02 1.035917e-02 1.013189e-02 9.951614e-03 9.810468e-03 9.702484e-03 9.618912e-03 9.557200e-03 9.515783e-03 9.483976e-03 9.461894e-03 9.449512e-03 9.444560e-03 9.448319e-03 9.462571e-03 9.492229e-03 9.540240e-03 9.613574e-03 9.722074e-03 9.877603e-03 1.009114e-02 1.039193e-02 1.078277e-02 1.129164e-02 1.194947e-02 1.276964e-02 1.378874e-02 1.503251e-02 1.651280e-02 1.826878e-02 2.029762e-02 2.260968e-02 2.523040e-02 2.811489e-02 3.128116e-02 3.468496e-02 3.831253e-02 4.214123e-02 4.609709e-02 5.019291e-02 5.435932e-02 5.857211e-02 6.279117e-02 6.698547e-02 7.112133e-02 7.513537e-02 7.898439e-02 8.264580e-02 8.596457e-02 8.893831e-02 9.144354e-02 9.339614e-02 9.469099e-02 9.528888e-02 9.504757e-02 9.401723e-02 9.209470e-02 8.936236e-02 8.581278e-02 8.158410e-02 7.673472e-02 7.142035e-02 6.580388e-02 5.997267e-02 5.418349e-02 4.845010e-02 4.301064e-02 3.788661e-02 3.318371e-02 2.895980e-02 2.520096e-02 2.196790e-02 1.917741e-02 1.686382e-02 1.493693e-02 1.338185e-02 1.212660e-02 1.114562e-02 1.037645e-02 9.788728e-03 9.337643e-03 8.999838e-03 8.745925e-03 8.557712e-03 8.417606e-03 8.313276e-03 8.236103e-03 8.178626e-03 8.136319e-03 8.103930e-03 8.079169e-03 8.057298e-03 8.047121e-03 8.040157e-03 8.037925e-03 8.039122e-03 8.045240e-03 8.056566e-03 8.073693e-03 8.096946e-03 8.126091e-03 8.163713e-03 8.211005e-03 8.267676e-03 8.337256e-03 8.419885e-03 8.518060e-03 8.634836e-03 8.771677e-03 8.931952e-03 9.121128e-03 9.339885e-03 9.595656e-03 9.891369e-03 1.023235e-02 1.062407e-02 1.106942e-02 1.157298e-02 1.213775e-02 1.276301e-02 1.344779e-02 1.418830e-02 1.497597e-02 1.580190e-02 1.665023e-02 1.750493e-02 1.834696e-02 1.915304e-02 1.989824e-02 2.055993e-02 2.111785e-02 2.154451e-02 2.182956e-02 2.195752e-02 2.191971e-02 2.171759e-02 2.135359e-02 2.084243e-02 2.019366e-02 1.943582e-02 1.858639e-02 1.767397e-02 1.672603e-02 1.576696e-02 1.482156e-02 1.390596e-02 1.304660e-02 1.224551e-02 1.152100e-02 1.087245e-02 1.030560e-02 9.815045e-03 9.399401e-03 9.048929e-03 8.762146e-03 8.525607e-03 8.333687e-03 8.178554e-03 8.055588e-03 7.953460e-03 7.871676e-03 7.804023e-03 7.745854e-03 7.695487e-03 7.651979e-03 7.613001e-03 7.576570e-03 7.543098e-03 7.513281e-03 7.486089e-03 7.460333e-03 7.437165e-03 7.420018e-03 7.403485e-03 7.393188e-03 7.385157e-03 7.381142e-03 7.382731e-03 7.389425e-03 7.400057e-03 7.417765e-03 7.441052e-03 7.469546e-03 7.504709e-03 7.548724e-03 7.596594e-03 7.650353e-03 7.710819e-03 7.774602e-03 7.841828e-03 7.913326e-03 7.984827e-03 8.057108e-03 8.127184e-03 8.196381e-03 8.262491e-03 8.324887e-03 8.382776e-03 8.438149e-03 8.491314e-03 8.542188e-03 8.597136e-03 8.653245e-03 8.718872e-03 8.793177e-03 8.885135e-03 8.992364e-03 9.123140e-03 9.277182e-03 9.457156e-03 9.663965e-03 9.896304e-03 1.015585e-02 1.043147e-02 1.072995e-02 1.103075e-02 1.133999e-02 1.163617e-02 1.192088e-02 1.218172e-02 1.240421e-02 1.258884e-02 1.272065e-02 1.279959e-02 1.281820e-02 1.277505e-02 1.267336e-02 1.251373e-02 1.229669e-02 1.203610e-02 1.173450e-02 1.139876e-02 1.104379e-02 1.067616e-02 1.030113e-02 9.933656e-03 9.576710e-03 9.236231e-03 8.922411e-03 8.629629e-03 8.368453e-03 8.136621e-03 7.930825e-03 7.753832e-03 7.602089e-03 7.475012e-03 7.366045e-03 7.276549e-03 7.201903e-03 7.140825e-03 7.089948e-03 7.049944e-03 7.017271e-03 6.992685e-03 6.975495e-03 6.965241e-03 6.963434e-03 6.968759e-03 6.981758e-03 7.005359e-03 7.036176e-03 7.077074e-03 7.127347e-03 7.185685e-03 7.252536e-03 7.326387e-03 7.406772e-03 7.492342e-03 7.578539e-03 7.664872e-03 7.752018e-03 7.832199e-03 7.906002e-03 7.972893e-03 8.026704e-03 8.068410e-03 8.096425e-03 8.109995e-03 8.107465e-03 8.090753e-03 8.058514e-03 8.012610e-03 7.955951e-03 7.888776e-03 7.812858e-03 7.734107e-03 7.650464e-03 7.568231e-03 7.487124e-03 7.409463e-03 7.340601e-03 7.276530e-03 7.223582e-03 7.178548e-03 7.145296e-03 7.121032e-03 7.105664e-03 7.099282e-03 7.099570e-03 7.109569e-03 7.122087e-03 7.140874e-03 7.163182e-03 7.186696e-03 7.212156e-03 7.237755e-03 7.264045e-03 7.290324e-03 7.317571e-03 7.344144e-03 7.371196e-03 7.402068e-03 7.435552e-03 7.475697e-03 7.525118e-03 7.585420e-03 7.660910e-03 7.756053e-03 7.873261e-03 8.018486e-03 8.194804e-03 8.410383e-03 8.665429e-03 8.966440e-03 9.318208e-03 9.721901e-03 1.018257e-02 1.069909e-02 1.127472e-02 1.190878e-02 1.259960e-02 1.334318e-02 1.413327e-02 1.496807e-02 1.583794e-02 1.673456e-02 1.764704e-02 1.856994e-02 1.948340e-02 2.038150e-02 2.124615e-02 2.207101e-02 2.283658e-02 2.352924e-02 2.414135e-02 2.465886e-02 2.507050e-02 2.536754e-02 2.554434e-02 2.559512e-02 2.551420e-02 2.530930e-02 2.497033e-02 2.451342e-02 2.393633e-02 2.325793e-02 2.247875e-02 2.162012e-02 2.069508e-02 1.971542e-02 1.870148e-02 1.766688e-02 1.662782e-02 1.559308e-02 1.458253e-02 1.360669e-02 1.267801e-02 1.179814e-02 1.097954e-02 1.022133e-02 9.527385e-03 8.900329e-03 8.336098e-03 7.836776e-03 7.397303e-03 7.011916e-03 6.679283e-03 6.391686e-03 6.146113e-03 5.937971e-03 5.762449e-03 5.615505e-03 5.491430e-03 5.388102e-03 5.302466e-03 5.231951e-03 5.174008e-03 5.125059e-03 5.085493e-03 5.052250e-03 5.025141e-03 5.000809e-03 4.981800e-03 4.965204e-03 4.949530e-03 4.935818e-03 4.922143e-03 4.908557e-03 4.892848e-03 4.877406e-03 4.859760e-03 4.839820e-03 4.816145e-03 4.789037e-03 4.756798e-03 4.719343e-03 4.676615e-03 4.627055e-03 4.570822e-03 4.506440e-03 4.434051e-03 4.353672e-03 4.264285e-03 4.165221e-03 4.056819e-03 3.939735e-03 3.813271e-03 3.678292e-03 3.534746e-03 3.383996e-03 3.226229e-03 3.062945e-03 2.894667e-03 ''') ImportString(u'flux_model_1_0(numeric)',''' 2.795926e-03 3.664451e-03 4.234822e-03 4.692629e-03 5.493201e-03 7.071624e-03 9.315712e-03 1.123027e-02 1.154659e-02 1.001144e-02 7.680693e-03 5.805874e-03 4.816900e-03 4.457820e-03 4.360444e-03 4.325183e-03 4.299991e-03 4.291636e-03 4.306400e-03 4.331071e-03 4.345031e-03 4.338566e-03 4.319855e-03 4.304587e-03 4.308393e-03 4.338773e-03 4.395153e-03 4.470044e-03 4.540741e-03 4.578945e-03 4.571582e-03 4.540354e-03 4.519871e-03 4.531703e-03 4.578494e-03 4.662658e-03 4.799377e-03 4.997884e-03 5.222071e-03 5.384182e-03 5.409365e-03 5.310966e-03 5.176761e-03 5.080273e-03 5.020871e-03 4.956448e-03 4.868438e-03 4.788391e-03 4.753854e-03 4.770719e-03 4.811976e-03 4.846114e-03 4.861902e-03 4.876799e-03 4.921897e-03 5.022237e-03 5.185621e-03 5.387023e-03 5.569133e-03 5.667294e-03 5.649342e-03 5.533873e-03 5.371652e-03 5.225477e-03 5.143508e-03 5.141076e-03 5.192619e-03 5.246570e-03 5.263103e-03 5.246081e-03 5.232226e-03 5.257946e-03 5.337141e-03 5.483993e-03 5.751980e-03 6.257228e-03 7.124805e-03 8.315096e-03 9.462988e-03 1.001214e-02 9.682868e-03 8.743498e-03 7.714515e-03 6.932042e-03 6.458024e-03 6.227117e-03 6.150602e-03 6.129398e-03 6.084279e-03 6.003645e-03 5.925308e-03 5.880495e-03 5.867752e-03 5.872381e-03 5.901174e-03 5.976073e-03 6.113561e-03 6.305273e-03 6.509826e-03 6.677845e-03 6.761523e-03 6.737088e-03 6.622453e-03 6.475778e-03 6.353055e-03 6.268290e-03 6.210241e-03 6.161562e-03 6.101486e-03 6.017767e-03 5.928254e-03 5.877664e-03 5.910153e-03 6.044571e-03 6.264351e-03 6.521152e-03 6.762987e-03 6.946342e-03 7.039609e-03 7.009725e-03 6.846050e-03 6.585501e-03 6.310042e-03 6.099161e-03 5.990936e-03 5.974474e-03 6.021208e-03 6.102109e-03 6.191581e-03 6.278879e-03 6.372633e-03 6.507517e-03 6.735723e-03 7.091359e-03 7.550079e-03 7.991543e-03 8.250561e-03 8.247904e-03 8.066342e-03 7.852556e-03 7.669650e-03 7.484414e-03 7.255439e-03 7.005591e-03 6.783504e-03 6.620635e-03 6.522074e-03 6.480007e-03 6.480011e-03 6.504643e-03 6.533874e-03 6.555568e-03 6.573049e-03 6.606794e-03 6.691335e-03 6.861169e-03 7.119803e-03 7.414118e-03 7.664893e-03 7.835538e-03 7.945251e-03 7.998472e-03 7.947500e-03 7.750991e-03 7.456630e-03 7.178225e-03 7.004543e-03 6.948744e-03 6.971842e-03 7.023267e-03 7.069058e-03 7.099641e-03 7.132318e-03 7.196985e-03 7.318586e-03 7.485053e-03 7.646770e-03 7.750958e-03 7.773987e-03 7.739799e-03 7.695572e-03 7.681366e-03 7.715330e-03 7.796783e-03 7.937115e-03 8.171577e-03 8.524392e-03 8.944687e-03 9.279085e-03 9.351064e-03 9.108100e-03 8.690043e-03 8.329460e-03 8.179937e-03 8.228309e-03 8.349569e-03 8.402313e-03 8.324038e-03 8.146049e-03 7.958258e-03 7.834423e-03 7.791503e-03 7.796999e-03 7.813054e-03 7.819258e-03 7.821458e-03 7.829615e-03 7.851808e-03 7.892544e-03 7.971509e-03 8.120939e-03 8.384394e-03 8.789833e-03 9.322087e-03 9.913954e-03 1.049519e-02 1.105004e-02 1.156739e-02 1.193451e-02 1.196500e-02 1.163429e-02 1.123862e-02 1.128974e-02 1.227257e-02 1.447816e-02 1.781692e-02 2.152883e-02 2.417632e-02 2.437566e-02 2.185923e-02 1.776329e-02 1.375950e-02 1.096261e-02 9.547665e-03 9.101787e-03 9.107553e-03 9.197377e-03 9.206326e-03 9.109596e-03 8.951936e-03 8.778772e-03 8.628693e-03 8.520672e-03 8.465493e-03 8.481066e-03 8.605424e-03 8.878719e-03 9.280278e-03 9.691783e-03 9.963601e-03 1.001423e-02 9.895438e-03 9.720786e-03 9.575414e-03 9.469335e-03 9.361727e-03 9.215063e-03 9.018303e-03 8.803249e-03 8.618085e-03 8.496982e-03 8.450200e-03 8.454402e-03 8.494890e-03 8.552627e-03 8.624314e-03 8.711399e-03 8.817429e-03 8.955723e-03 9.137725e-03 9.365142e-03 9.616338e-03 9.831066e-03 9.943411e-03 9.926737e-03 9.842315e-03 9.836002e-03 1.007139e-02 1.062463e-02 1.142259e-02 1.222479e-02 1.271087e-02 1.264998e-02 1.204179e-02 1.112659e-02 1.022410e-02 9.563205e-03 9.191798e-03 9.049342e-03 9.031229e-03 9.066161e-03 9.114763e-03 9.164127e-03 9.215918e-03 9.261403e-03 9.289986e-03 9.289826e-03 9.252212e-03 9.187663e-03 9.099782e-03 8.973934e-03 8.785811e-03 8.525656e-03 8.230094e-03 7.970064e-03 7.790824e-03 7.660265e-03 7.486123e-03 7.195151e-03 6.803888e-03 6.404371e-03 6.087342e-03 5.891493e-03 5.796233e-03 5.765947e-03 5.767137e-03 5.786563e-03 5.818654e-03 5.862529e-03 5.915350e-03 5.972791e-03 6.021984e-03 6.052003e-03 6.060679e-03 6.050602e-03 6.036075e-03 6.024845e-03 6.025092e-03 6.034687e-03 6.055092e-03 6.082279e-03 6.119683e-03 6.167445e-03 6.231837e-03 6.308338e-03 6.388516e-03 6.450586e-03 6.482095e-03 6.480901e-03 6.465781e-03 6.462672e-03 6.496057e-03 6.575965e-03 6.707695e-03 6.908451e-03 7.206637e-03 7.629426e-03 8.158073e-03 8.697091e-03 9.084200e-03 9.176737e-03 8.929303e-03 8.443873e-03 7.891644e-03 7.417550e-03 7.087449e-03 6.892965e-03 6.796737e-03 6.769469e-03 6.798478e-03 6.884631e-03 7.025107e-03 7.204563e-03 7.390703e-03 7.551138e-03 7.665440e-03 7.739227e-03 7.804564e-03 7.931507e-03 8.224015e-03 8.781880e-03 9.627121e-03 1.060905e-02 1.141505e-02 1.170576e-02 1.133491e-02 1.044077e-02 9.362677e-03 8.429773e-03 7.806642e-03 7.486567e-03 7.367232e-03 7.338732e-03 7.335004e-03 7.332558e-03 7.336830e-03 7.357789e-03 7.400137e-03 7.467051e-03 7.576157e-03 7.742446e-03 7.978759e-03 8.251280e-03 8.492948e-03 8.614898e-03 8.562320e-03 8.350205e-03 8.048290e-03 7.747178e-03 7.509091e-03 7.359866e-03 7.294371e-03 7.306060e-03 7.400089e-03 7.599631e-03 7.931230e-03 8.383187e-03 8.881570e-03 9.288481e-03 9.466657e-03 9.352129e-03 8.998252e-03 8.541738e-03 8.128489e-03 7.849624e-03 7.722809e-03 7.726408e-03 7.823200e-03 7.969951e-03 8.130990e-03 8.268412e-03 8.357954e-03 8.392502e-03 8.385573e-03 8.357848e-03 8.325594e-03 8.289684e-03 8.245600e-03 8.190747e-03 8.131386e-03 8.074046e-03 8.024807e-03 7.986093e-03 7.957800e-03 7.943003e-03 7.940398e-03 7.953744e-03 7.980394e-03 8.022810e-03 8.085300e-03 8.190187e-03 8.355775e-03 8.587285e-03 8.846182e-03 9.057591e-03 9.144037e-03 9.071055e-03 8.873225e-03 8.634889e-03 8.451520e-03 8.375268e-03 8.412382e-03 8.516727e-03 8.628245e-03 8.689136e-03 8.680711e-03 8.614601e-03 8.532023e-03 8.466884e-03 8.448152e-03 8.491120e-03 8.627092e-03 8.896917e-03 9.342196e-03 9.967363e-03 1.069297e-02 1.136065e-02 1.177822e-02 1.181036e-02 1.145814e-02 1.086600e-02 1.026792e-02 9.866484e-03 9.761338e-03 9.918990e-03 1.020940e-02 1.047281e-02 1.058694e-02 1.050074e-02 1.023887e-02 9.875420e-03 9.483433e-03 9.132414e-03 8.858258e-03 8.677235e-03 8.588632e-03 8.583467e-03 8.643741e-03 8.746552e-03 8.859709e-03 8.949645e-03 8.988934e-03 8.972578e-03 8.911045e-03 8.832389e-03 8.757565e-03 8.697599e-03 8.653221e-03 8.617870e-03 8.586279e-03 8.569986e-03 8.583211e-03 8.719233e-03 9.207525e-03 1.062143e-02 1.419462e-02 2.214402e-02 3.770935e-02 6.427223e-02 1.034250e-01 1.522274e-01 2.020126e-01 2.401212e-01 2.546657e-01 2.408210e-01 2.030842e-01 1.531684e-01 1.039175e-01 6.439663e-02 3.767736e-02 2.221044e-02 1.455877e-02 1.138315e-02 1.043629e-02 1.047867e-02 1.093760e-02 1.154069e-02 1.211831e-02 1.255923e-02 1.278097e-02 1.277340e-02 1.257206e-02 1.223904e-02 1.182061e-02 1.135891e-02 1.089067e-02 1.044934e-02 1.007160e-02 9.773894e-03 9.557352e-03 9.407465e-03 9.308502e-03 9.247796e-03 9.220224e-03 9.216198e-03 9.232550e-03 9.262607e-03 9.306050e-03 9.358275e-03 9.420429e-03 9.487406e-03 9.551618e-03 9.603663e-03 9.640768e-03 9.659521e-03 9.667772e-03 9.670511e-03 9.670733e-03 9.666483e-03 9.652995e-03 9.627097e-03 9.589981e-03 9.546704e-03 9.507648e-03 9.475701e-03 9.458359e-03 9.454313e-03 9.463085e-03 9.485303e-03 9.514514e-03 9.551204e-03 9.589494e-03 9.632528e-03 9.678601e-03 9.728821e-03 9.786466e-03 9.849575e-03 9.923663e-03 1.002056e-02 1.015584e-02 1.034569e-02 1.059126e-02 1.088890e-02 1.122906e-02 1.163989e-02 1.220040e-02 1.305206e-02 1.434655e-02 1.614083e-02 1.829723e-02 2.043490e-02 2.202794e-02 2.260378e-02 2.194803e-02 2.023529e-02 1.791709e-02 1.554057e-02 1.353751e-02 1.211449e-02 1.126171e-02 1.084967e-02 1.071754e-02 1.072134e-02 1.076356e-02 1.078020e-02 1.074024e-02 1.063587e-02 1.047436e-02 1.027949e-02 1.007939e-02 9.896940e-03 9.752640e-03 9.656772e-03 9.622029e-03 9.677114e-03 9.884506e-03 1.036678e-02 1.132304e-02 1.301874e-02 1.572851e-02 1.963392e-02 2.467708e-02 3.048330e-02 3.633517e-02 4.129895e-02 4.444638e-02 4.514418e-02 4.322224e-02 3.905083e-02 3.345447e-02 2.743284e-02 2.190289e-02 1.746324e-02 1.433912e-02 1.246593e-02 1.163392e-02 1.165292e-02 1.242792e-02 1.396798e-02 1.627866e-02 1.927225e-02 2.260743e-02 2.573708e-02 2.798336e-02 2.880193e-02 2.798503e-02 2.576185e-02 2.269398e-02 1.946162e-02 1.659981e-02 1.439454e-02 1.288121e-02 1.192568e-02 1.135092e-02 1.100567e-02 1.079059e-02 1.065809e-02 1.058231e-02 1.055499e-02 1.056909e-02 1.061639e-02 1.070065e-02 1.083378e-02 1.103857e-02 1.135201e-02 1.181529e-02 1.246927e-02 1.333622e-02 1.442232e-02 1.571687e-02 1.717508e-02 1.872945e-02 2.025743e-02 2.160664e-02 2.258543e-02 2.305011e-02 2.296837e-02 2.246802e-02 2.185950e-02 2.156935e-02 2.205043e-02 2.366674e-02 2.663073e-02 3.099074e-02 3.659463e-02 4.313043e-02 5.011936e-02 5.705209e-02 6.350545e-02 6.933881e-02 7.462602e-02 7.951541e-02 8.386774e-02 8.705852e-02 8.812220e-02 8.608530e-02 8.057920e-02 7.212315e-02 6.200098e-02 5.183828e-02 4.302292e-02 3.632680e-02 3.185735e-02 2.922257e-02 2.780097e-02 2.696371e-02 2.620352e-02 2.522581e-02 2.391878e-02 2.234683e-02 2.069617e-02 1.920846e-02 1.813660e-02 1.768150e-02 1.798350e-02 1.905175e-02 2.077110e-02 2.285017e-02 2.486769e-02 2.638627e-02 2.706645e-02 2.679800e-02 2.575571e-02 2.432130e-02 2.292937e-02 2.191301e-02 2.141833e-02 2.137604e-02 2.159672e-02 2.188260e-02 2.212238e-02 2.231150e-02 2.252390e-02 2.287107e-02 2.340410e-02 2.411471e-02 2.491306e-02 2.569328e-02 2.638630e-02 2.703815e-02 2.781650e-02 2.897818e-02 3.080276e-02 3.348133e-02 3.704673e-02 4.134780e-02 4.607457e-02 5.080113e-02 5.502884e-02 5.829669e-02 6.026766e-02 6.084706e-02 6.028913e-02 5.921290e-02 5.846838e-02 5.896077e-02 6.140203e-02 6.609754e-02 7.288815e-02 8.108328e-02 8.966892e-02 9.738345e-02 1.031030e-01 1.060381e-01 1.059228e-01 1.030969e-01 9.826993e-02 9.226640e-02 8.585557e-02 7.950760e-02 7.355406e-02 6.811582e-02 6.329288e-02 5.917084e-02 5.583515e-02 5.335292e-02 5.179087e-02 5.118418e-02 5.155087e-02 5.279135e-02 5.467709e-02 5.684290e-02 5.883380e-02 6.019994e-02 6.062364e-02 5.996716e-02 5.828461e-02 5.576381e-02 5.271535e-02 4.949826e-02 4.649764e-02 4.407271e-02 4.251065e-02 4.197865e-02 4.249137e-02 4.395301e-02 4.615700e-02 4.889511e-02 5.198020e-02 5.525381e-02 5.861232e-02 6.196297e-02 6.516752e-02 6.810299e-02 7.067532e-02 7.279757e-02 7.448104e-02 7.577939e-02 7.679425e-02 7.763839e-02 7.845919e-02 7.948466e-02 8.104932e-02 8.354925e-02 8.736853e-02 9.268838e-02 9.936303e-02 1.068779e-01 1.142630e-01 1.204376e-01 1.242591e-01 1.249210e-01 1.220776e-01 1.159947e-01 1.075758e-01 9.804909e-02 8.882300e-02 8.112933e-02 7.585522e-02 7.340587e-02 7.375047e-02 7.644131e-02 8.079931e-02 8.595793e-02 9.095366e-02 9.488727e-02 9.699097e-02 9.674098e-02 9.399543e-02 8.898392e-02 8.230445e-02 7.476406e-02 6.734322e-02 6.101417e-02 5.659117e-02 5.470413e-02 5.565449e-02 5.943833e-02 6.569882e-02 7.376368e-02 8.276148e-02 9.166206e-02 9.946743e-02 1.053039e-01 1.085663e-01 1.089950e-01 1.066739e-01 1.021001e-01 9.597346e-02 8.916452e-02 8.247627e-02 7.661007e-02 7.207823e-02 6.924298e-02 6.841385e-02 7.001486e-02 7.458686e-02 8.279667e-02 9.526456e-02 1.122789e-01 1.335524e-01 1.579586e-01 1.835650e-01 2.077725e-01 2.279366e-01 2.416940e-01 2.477983e-01 2.460993e-01 2.376866e-01 2.243726e-01 2.082946e-01 1.912711e-01 1.746916e-01 1.592049e-01 1.451409e-01 1.325140e-01 1.213465e-01 1.117753e-01 1.039859e-01 9.818824e-02 9.450686e-02 9.286352e-02 9.297723e-02 9.443232e-02 9.668450e-02 9.923623e-02 1.016735e-01 1.037549e-01 1.054305e-01 1.067605e-01 1.079530e-01 1.091749e-01 1.105168e-01 1.120283e-01 1.136141e-01 1.152258e-01 1.168152e-01 1.184460e-01 1.201691e-01 1.221692e-01 1.245049e-01 1.272931e-01 1.305908e-01 1.344418e-01 1.389601e-01 1.441787e-01 1.501762e-01 1.570036e-01 1.647111e-01 1.735077e-01 1.837997e-01 1.961705e-01 2.114694e-01 2.302625e-01 2.526713e-01 2.779522e-01 3.042703e-01 3.287926e-01 3.483262e-01 3.596431e-01 3.606936e-01 3.506992e-01 3.305958e-01 3.025668e-01 2.697515e-01 2.351260e-01 2.015184e-01 1.707780e-01 1.440992e-01 1.220727e-01 1.048168e-01 9.230843e-02 8.430600e-02 8.038392e-02 7.998773e-02 8.233368e-02 8.650888e-02 9.155479e-02 9.656824e-02 1.008562e-01 1.039302e-01 1.056583e-01 1.061924e-01 1.059383e-01 1.054593e-01 1.053675e-01 1.062400e-01 1.084604e-01 1.122144e-01 1.174928e-01 1.240857e-01 1.316004e-01 1.395745e-01 1.474703e-01 1.548378e-01 1.614435e-01 1.674124e-01 1.733725e-01 1.804985e-01 1.902537e-01 2.041167e-01 2.230396e-01 2.469940e-01 2.745950e-01 3.031183e-01 3.290083e-01 3.485058e-01 3.587167e-01 3.582461e-01 3.476707e-01 3.293439e-01 3.070100e-01 2.846043e-01 2.655499e-01 2.521942e-01 2.451810e-01 2.436490e-01 2.456030e-01 2.483827e-01 2.492615e-01 2.461215e-01 2.377948e-01 2.241567e-01 2.061424e-01 1.856401e-01 1.646545e-01 1.453156e-01 1.292721e-01 1.176104e-01 1.105454e-01 1.077656e-01 1.083388e-01 1.110207e-01 1.145134e-01 1.175336e-01 1.191903e-01 1.189004e-01 1.166226e-01 1.126673e-01 1.077661e-01 1.028272e-01 9.889286e-02 9.700955e-02 9.816533e-02 1.033018e-01 1.129898e-01 1.276777e-01 1.471718e-01 1.707195e-01 1.969541e-01 2.236596e-01 2.484531e-01 2.683898e-01 2.813195e-01 2.853395e-01 2.798837e-01 2.653411e-01 2.433606e-01 2.161036e-01 1.863348e-01 1.565664e-01 1.289415e-01 1.049817e-01 8.543099e-02 7.049588e-02 5.979445e-02 5.282714e-02 4.890480e-02 4.739406e-02 4.781241e-02 4.976286e-02 5.299822e-02 5.730378e-02 6.254785e-02 6.857280e-02 7.518724e-02 8.214842e-02 8.919369e-02 9.595639e-02 1.020692e-01 1.071429e-01 1.107950e-01 1.127780e-01 1.128613e-01 1.110425e-01 1.074301e-01 1.023795e-01 9.630956e-02 8.975954e-02 8.326138e-02 7.724594e-02 7.202885e-02 6.777212e-02 6.449261e-02 6.206645e-02 6.034909e-02 5.920837e-02 5.850766e-02 5.823173e-02 5.835186e-02 5.891673e-02 5.990039e-02 6.128461e-02 6.290820e-02 6.458214e-02 6.600372e-02 6.690381e-02 6.699518e-02 6.615639e-02 6.434778e-02 6.172847e-02 5.860685e-02 5.541298e-02 5.264110e-02 5.078751e-02 5.029750e-02 5.148596e-02 5.454615e-02 5.943983e-02 6.592286e-02 7.354665e-02 8.166471e-02 8.951546e-02 9.630886e-02 1.012803e-01 1.038614e-01 1.037456e-01 1.009227e-01 9.569203e-02 8.855784e-02 8.025743e-02 7.152107e-02 6.303082e-02 5.538093e-02 4.894742e-02 4.392801e-02 4.033736e-02 3.804007e-02 3.683968e-02 3.643093e-02 3.656283e-02 3.697409e-02 3.747085e-02 3.793069e-02 3.830095e-02 3.859439e-02 3.889665e-02 3.930958e-02 3.994867e-02 4.093436e-02 4.232474e-02 4.418167e-02 4.647699e-02 4.916558e-02 5.215084e-02 5.527531e-02 5.837925e-02 6.122586e-02 6.361560e-02 6.530013e-02 6.612353e-02 6.594415e-02 6.472693e-02 6.250833e-02 5.946854e-02 5.579207e-02 5.177144e-02 4.768141e-02 4.378575e-02 4.028568e-02 3.731591e-02 3.496712e-02 3.322086e-02 3.202561e-02 3.130305e-02 3.093456e-02 3.082122e-02 3.085219e-02 3.094646e-02 3.104529e-02 3.110070e-02 3.109998e-02 3.103529e-02 3.090842e-02 3.074367e-02 3.055420e-02 3.036433e-02 3.021699e-02 3.015679e-02 3.024013e-02 3.054404e-02 3.114133e-02 3.209000e-02 3.344345e-02 3.520316e-02 3.733067e-02 3.972623e-02 4.224684e-02 4.473279e-02 4.699087e-02 4.883702e-02 5.014945e-02 5.085352e-02 5.092090e-02 5.042225e-02 4.947706e-02 4.820428e-02 4.679896e-02 4.538237e-02 4.408268e-02 4.300880e-02 4.220938e-02 4.173631e-02 4.162062e-02 4.188538e-02 4.257171e-02 4.371209e-02 4.531397e-02 4.738862e-02 4.988634e-02 5.271478e-02 5.572059e-02 5.869588e-02 6.139513e-02 6.355844e-02 6.493702e-02 6.535832e-02 6.469291e-02 6.293522e-02 6.018234e-02 5.663551e-02 5.256281e-02 4.827388e-02 4.408297e-02 4.026834e-02 3.705360e-02 3.456686e-02 3.288482e-02 3.196778e-02 3.174118e-02 3.205968e-02 3.276638e-02 3.369690e-02 3.469802e-02 3.564636e-02 3.646248e-02 3.709852e-02 3.757463e-02 3.792264e-02 3.821936e-02 3.852530e-02 3.891682e-02 3.941520e-02 4.001717e-02 4.067219e-02 4.129720e-02 4.177534e-02 4.199985e-02 4.187900e-02 4.136770e-02 4.044853e-02 3.918063e-02 3.767019e-02 3.604639e-02 3.447041e-02 3.310300e-02 3.207253e-02 3.149343e-02 3.143810e-02 3.194144e-02 3.300282e-02 3.461311e-02 3.671442e-02 3.927266e-02 4.222345e-02 4.547899e-02 4.898975e-02 5.260495e-02 5.619434e-02 5.963051e-02 6.265535e-02 6.514052e-02 6.685590e-02 6.768617e-02 6.754321e-02 6.640861e-02 6.438580e-02 6.161292e-02 5.831414e-02 5.474285e-02 5.115411e-02 4.774527e-02 4.473486e-02 4.222460e-02 4.028920e-02 3.891609e-02 3.810760e-02 3.777595e-02 3.784945e-02 3.824813e-02 3.887727e-02 3.966798e-02 4.056894e-02 4.152549e-02 4.253109e-02 4.360239e-02 4.475898e-02 4.605315e-02 4.754849e-02 4.929098e-02 5.130042e-02 5.359927e-02 5.612425e-02 5.878958e-02 6.148764e-02 6.401849e-02 6.621020e-02 6.791528e-02 6.893019e-02 6.919879e-02 6.866730e-02 6.736690e-02 6.542139e-02 6.297150e-02 6.021884e-02 5.739036e-02 5.464317e-02 5.216888e-02 5.008290e-02 4.843250e-02 4.723656e-02 4.648117e-02 4.609428e-02 4.601678e-02 4.614851e-02 4.643912e-02 4.679181e-02 4.716042e-02 4.748059e-02 4.768415e-02 4.774177e-02 4.761794e-02 4.726058e-02 4.668291e-02 4.586734e-02 4.485467e-02 4.367090e-02 4.239433e-02 4.107726e-02 3.978686e-02 3.860280e-02 3.757199e-02 3.672638e-02 3.609958e-02 3.567433e-02 3.544063e-02 3.536222e-02 3.539432e-02 3.548363e-02 3.558963e-02 3.567381e-02 3.570129e-02 3.566108e-02 3.555542e-02 3.539691e-02 3.520093e-02 3.498723e-02 3.477743e-02 3.458125e-02 3.439154e-02 3.419948e-02 3.398778e-02 3.373783e-02 3.343020e-02 3.306127e-02 3.262984e-02 3.215880e-02 3.168054e-02 3.123889e-02 3.087650e-02 3.062948e-02 3.053585e-02 3.061738e-02 3.088107e-02 3.130593e-02 3.186111e-02 3.250693e-02 3.319563e-02 3.386579e-02 3.447651e-02 3.498825e-02 3.538908e-02 3.568423e-02 3.592072e-02 3.616339e-02 3.652279e-02 3.712539e-02 3.811113e-02 3.962206e-02 4.180121e-02 4.474155e-02 4.847582e-02 5.298829e-02 5.818734e-02 6.388614e-02 6.982973e-02 7.569645e-02 8.114515e-02 8.584672e-02 8.947176e-02 9.178067e-02 9.262960e-02 9.198175e-02 8.990769e-02 8.659013e-02 8.230025e-02 7.735087e-02 7.204840e-02 6.671435e-02 6.159553e-02 5.686951e-02 5.265192e-02 4.899383e-02 4.586882e-02 4.320647e-02 4.091783e-02 3.890569e-02 3.707888e-02 3.537948e-02 3.375115e-02 3.218054e-02 3.066824e-02 2.922474e-02 2.787920e-02 2.665301e-02 2.556583e-02 2.463348e-02 2.386296e-02 2.325097e-02 2.278499e-02 2.246181e-02 2.225277e-02 2.214239e-02 2.210993e-02 2.214231e-02 2.222133e-02 2.233022e-02 2.245948e-02 2.259236e-02 2.272211e-02 2.283235e-02 2.291221e-02 2.295770e-02 2.295613e-02 2.291455e-02 2.282823e-02 2.270608e-02 2.256065e-02 2.240296e-02 2.225082e-02 2.211109e-02 2.201041e-02 2.194591e-02 2.192732e-02 2.196518e-02 2.204658e-02 2.217952e-02 2.235100e-02 2.256584e-02 2.281594e-02 2.310099e-02 2.341673e-02 2.376463e-02 2.413798e-02 2.454158e-02 2.495682e-02 2.538180e-02 2.579834e-02 2.619184e-02 2.654395e-02 2.683530e-02 2.705168e-02 2.717242e-02 2.719581e-02 2.711320e-02 2.693029e-02 2.666062e-02 2.631481e-02 2.592059e-02 2.549502e-02 2.506432e-02 2.465195e-02 2.427426e-02 2.394634e-02 2.366965e-02 2.344865e-02 2.327626e-02 2.313961e-02 2.303096e-02 2.292905e-02 2.282413e-02 2.269937e-02 2.254736e-02 2.235759e-02 2.212728e-02 2.185309e-02 2.153510e-02 2.117725e-02 2.078748e-02 2.036304e-02 1.991628e-02 1.945864e-02 1.899108e-02 1.852865e-02 1.807907e-02 1.765315e-02 1.725927e-02 1.690996e-02 1.661111e-02 1.637345e-02 1.620303e-02 1.610521e-02 1.608403e-02 1.614726e-02 1.629115e-02 1.651933e-02 1.682942e-02 1.721620e-02 1.767145e-02 1.818409e-02 1.873996e-02 1.932029e-02 1.990362e-02 2.046674e-02 2.098541e-02 2.143616e-02 2.179763e-02 2.205243e-02 2.218844e-02 2.219966e-02 2.208719e-02 2.185894e-02 2.153037e-02 2.111501e-02 2.064161e-02 2.012892e-02 1.960033e-02 1.908431e-02 1.859033e-02 1.813943e-02 1.774217e-02 1.740266e-02 1.712425e-02 1.690593e-02 1.674360e-02 1.663254e-02 1.656290e-02 1.652844e-02 1.652289e-02 1.653700e-02 1.656938e-02 1.660892e-02 1.665938e-02 1.671581e-02 1.678099e-02 1.685818e-02 1.694972e-02 1.706476e-02 1.720658e-02 1.738410e-02 1.760905e-02 1.788510e-02 1.821960e-02 1.861714e-02 1.908196e-02 1.961233e-02 2.020505e-02 2.085679e-02 2.155388e-02 2.229246e-02 2.304876e-02 2.381130e-02 2.456826e-02 2.528897e-02 2.596204e-02 2.657348e-02 2.709075e-02 2.750537e-02 2.779916e-02 2.796160e-02 2.797855e-02 2.784465e-02 2.756269e-02 2.713188e-02 2.656155e-02 2.587004e-02 2.507397e-02 2.419507e-02 2.326148e-02 2.230017e-02 2.133542e-02 2.038859e-02 1.948361e-02 1.864170e-02 1.786804e-02 1.717582e-02 1.656500e-02 1.603984e-02 1.559515e-02 1.522430e-02 1.492208e-02 1.467970e-02 1.448988e-02 1.434165e-02 1.423126e-02 1.415606e-02 1.410312e-02 1.407127e-02 1.405910e-02 1.406222e-02 1.408027e-02 1.411371e-02 1.416499e-02 1.423629e-02 1.433189e-02 1.446033e-02 1.463161e-02 1.485426e-02 1.515670e-02 1.554000e-02 1.602896e-02 1.665398e-02 1.742626e-02 1.838020e-02 1.954115e-02 2.091845e-02 2.255020e-02 2.443332e-02 2.657689e-02 2.900821e-02 3.168219e-02 3.461963e-02 3.777942e-02 4.115019e-02 4.471676e-02 4.840804e-02 5.224307e-02 5.616073e-02 6.014201e-02 6.415134e-02 6.816690e-02 7.215945e-02 7.606728e-02 7.985399e-02 8.350006e-02 8.684655e-02 8.989569e-02 9.252098e-02 9.463299e-02 9.612498e-02 9.695093e-02 9.696107e-02 9.620063e-02 9.455725e-02 9.210631e-02 8.883255e-02 8.486944e-02 8.026683e-02 7.517815e-02 6.976228e-02 6.410280e-02 5.845976e-02 5.284063e-02 4.749144e-02 4.243108e-02 3.776897e-02 3.356740e-02 2.981168e-02 2.657098e-02 2.375932e-02 2.141889e-02 1.945891e-02 1.786830e-02 1.657597e-02 1.555893e-02 1.475485e-02 1.413580e-02 1.365594e-02 1.329355e-02 1.301895e-02 1.281408e-02 1.266100e-02 1.254731e-02 1.246392e-02 1.240286e-02 1.236010e-02 1.232943e-02 1.230807e-02 1.229154e-02 1.228845e-02 1.228984e-02 1.229818e-02 1.231126e-02 1.233306e-02 1.236492e-02 1.240463e-02 1.245517e-02 1.251713e-02 1.259587e-02 1.269494e-02 1.281607e-02 1.296771e-02 1.315374e-02 1.338259e-02 1.366462e-02 1.400868e-02 1.442803e-02 1.493979e-02 1.555508e-02 1.629677e-02 1.718104e-02 1.822841e-02 1.945954e-02 2.088977e-02 2.253628e-02 2.441058e-02 2.651455e-02 2.884533e-02 3.138914e-02 3.411918e-02 3.700255e-02 3.998591e-02 4.301087e-02 4.600924e-02 4.889948e-02 5.159426e-02 5.401164e-02 5.607701e-02 5.769854e-02 5.883249e-02 5.942576e-02 5.944755e-02 5.889928e-02 5.778960e-02 5.616545e-02 5.406386e-02 5.157664e-02 4.876849e-02 4.573561e-02 4.257141e-02 3.936114e-02 3.618891e-02 3.311545e-02 3.022208e-02 2.752819e-02 2.508773e-02 2.290483e-02 2.099642e-02 1.934851e-02 1.795328e-02 1.678384e-02 1.582695e-02 1.504624e-02 1.441955e-02 1.392080e-02 1.353068e-02 1.321910e-02 1.297647e-02 1.278443e-02 1.262865e-02 1.250108e-02 1.239614e-02 1.230685e-02 1.222764e-02 1.215747e-02 1.209614e-02 1.204107e-02 1.198868e-02 1.194064e-02 1.190495e-02 1.186972e-02 1.184364e-02 1.181967e-02 1.180051e-02 1.178789e-02 1.178049e-02 1.177620e-02 1.177886e-02 1.178632e-02 1.179729e-02 1.181336e-02 1.183843e-02 1.186443e-02 1.189483e-02 1.193193e-02 1.197019e-02 1.201152e-02 1.205844e-02 1.210504e-02 1.215458e-02 1.220413e-02 1.225677e-02 1.231086e-02 1.236693e-02 1.242454e-02 1.248748e-02 1.255648e-02 1.263144e-02 1.272114e-02 1.282052e-02 1.294014e-02 1.307768e-02 1.324432e-02 1.343408e-02 1.365712e-02 1.391160e-02 1.419935e-02 1.451954e-02 1.487013e-02 1.525203e-02 1.565056e-02 1.607310e-02 1.649381e-02 1.692009e-02 1.732486e-02 1.771054e-02 1.806198e-02 1.836046e-02 1.860792e-02 1.878578e-02 1.889429e-02 1.892450e-02 1.887455e-02 1.874986e-02 1.855037e-02 1.827668e-02 1.794748e-02 1.756444e-02 1.713763e-02 1.668436e-02 1.621408e-02 1.573260e-02 1.525842e-02 1.479677e-02 1.435342e-02 1.394325e-02 1.355759e-02 1.321184e-02 1.290219e-02 1.262562e-02 1.238569e-02 1.217837e-02 1.200434e-02 1.185411e-02 1.173068e-02 1.162843e-02 1.154605e-02 1.147847e-02 1.142785e-02 1.138854e-02 1.136180e-02 1.134647e-02 1.134178e-02 1.134942e-02 1.136743e-02 1.139552e-02 1.143911e-02 1.149144e-02 1.155748e-02 1.163610e-02 1.172520e-02 1.182570e-02 1.193545e-02 1.205440e-02 1.218049e-02 1.230787e-02 1.243618e-02 1.256665e-02 1.268922e-02 1.280382e-02 1.291074e-02 1.300094e-02 1.307624e-02 1.313442e-02 1.317457e-02 1.319419e-02 1.319611e-02 1.317838e-02 1.314342e-02 1.309606e-02 1.303593e-02 1.296626e-02 1.289557e-02 1.282111e-02 1.275243e-02 1.268939e-02 1.263614e-02 1.260112e-02 1.257921e-02 1.258060e-02 1.260184e-02 1.264916e-02 1.271863e-02 1.281179e-02 1.292856e-02 1.306636e-02 1.323029e-02 1.340884e-02 1.360609e-02 1.381751e-02 1.403719e-02 1.426505e-02 1.449561e-02 1.472740e-02 1.495387e-02 1.517561e-02 1.538488e-02 1.558046e-02 1.576540e-02 1.593224e-02 1.608709e-02 1.623172e-02 1.636704e-02 1.650000e-02 1.663798e-02 1.678575e-02 1.695549e-02 1.715278e-02 1.739780e-02 1.769201e-02 1.805059e-02 1.848474e-02 1.900364e-02 1.961914e-02 2.033193e-02 2.115216e-02 2.208495e-02 2.312920e-02 2.427937e-02 2.553037e-02 2.688110e-02 2.831550e-02 2.982059e-02 3.138006e-02 3.298398e-02 3.459661e-02 3.620942e-02 3.778851e-02 3.932116e-02 4.077209e-02 4.211444e-02 4.333187e-02 4.439691e-02 4.528596e-02 4.597996e-02 4.646552e-02 4.672812e-02 4.675386e-02 4.655365e-02 4.610557e-02 4.543627e-02 4.453855e-02 4.344463e-02 4.215353e-02 4.070328e-02 3.911645e-02 3.741483e-02 3.563528e-02 3.380293e-02 3.194850e-02 3.008839e-02 2.825892e-02 2.648267e-02 2.478329e-02 2.316443e-02 2.165051e-02 2.024069e-02 1.894312e-02 1.776342e-02 1.669457e-02 1.574322e-02 1.489915e-02 1.415265e-02 1.350258e-02 1.293480e-02 1.244454e-02 1.202494e-02 1.166755e-02 1.136578e-02 1.110976e-02 1.089647e-02 1.072091e-02 1.057920e-02 1.046730e-02 1.037903e-02 1.031607e-02 1.027290e-02 1.025003e-02 1.024122e-02 1.025165e-02 1.027635e-02 1.031259e-02 1.036325e-02 1.042395e-02 1.049590e-02 1.057399e-02 1.066391e-02 1.075971e-02 1.086228e-02 1.096802e-02 1.107674e-02 1.118333e-02 1.128782e-02 1.138905e-02 1.148184e-02 1.156307e-02 1.162847e-02 1.167670e-02 1.170516e-02 1.170694e-02 1.167800e-02 1.161672e-02 1.152223e-02 1.138944e-02 1.121664e-02 1.100222e-02 1.074785e-02 1.045185e-02 1.011713e-02 9.743785e-03 ''') ImportString(u'flux_model_1_0_scaled(numeric)',''' 3.886337e-03 5.093587e-03 5.886402e-03 6.522754e-03 7.635550e-03 9.829557e-03 1.294884e-02 1.561008e-02 1.604977e-02 1.391590e-02 1.067616e-02 8.070165e-03 6.695492e-03 6.196370e-03 6.061017e-03 6.012004e-03 5.976988e-03 5.965375e-03 5.985896e-03 6.020189e-03 6.039592e-03 6.030606e-03 6.004598e-03 5.983376e-03 5.988667e-03 6.030895e-03 6.109263e-03 6.213361e-03 6.311629e-03 6.364734e-03 6.354499e-03 6.311092e-03 6.282620e-03 6.299068e-03 6.364106e-03 6.481094e-03 6.671134e-03 6.947059e-03 7.258679e-03 7.484013e-03 7.519017e-03 7.382242e-03 7.195698e-03 7.061579e-03 6.979010e-03 6.889463e-03 6.767129e-03 6.655864e-03 6.607857e-03 6.631299e-03 6.688647e-03 6.736098e-03 6.758044e-03 6.778751e-03 6.841437e-03 6.980910e-03 7.208014e-03 7.487961e-03 7.741094e-03 7.877539e-03 7.852586e-03 7.692083e-03 7.466596e-03 7.263413e-03 7.149476e-03 7.146096e-03 7.217741e-03 7.292732e-03 7.315713e-03 7.292052e-03 7.272794e-03 7.308546e-03 7.418625e-03 7.622750e-03 7.995253e-03 8.697547e-03 9.903479e-03 1.155798e-02 1.315355e-02 1.391688e-02 1.345919e-02 1.215346e-02 1.072318e-02 9.635538e-03 8.976653e-03 8.655692e-03 8.549337e-03 8.519863e-03 8.457147e-03 8.345066e-03 8.236178e-03 8.173887e-03 8.156175e-03 8.162610e-03 8.202632e-03 8.306742e-03 8.497850e-03 8.764330e-03 9.048658e-03 9.282205e-03 9.398518e-03 9.364552e-03 9.205209e-03 9.001332e-03 8.830747e-03 8.712923e-03 8.632236e-03 8.564571e-03 8.481066e-03 8.364697e-03 8.240273e-03 8.169953e-03 8.215113e-03 8.401954e-03 8.707448e-03 9.064402e-03 9.400551e-03 9.655415e-03 9.785057e-03 9.743517e-03 9.516009e-03 9.153847e-03 8.770958e-03 8.477834e-03 8.327402e-03 8.304519e-03 8.369479e-03 8.481931e-03 8.606298e-03 8.727641e-03 8.857960e-03 9.045449e-03 9.362656e-03 9.856989e-03 1.049461e-02 1.110824e-02 1.146828e-02 1.146459e-02 1.121222e-02 1.091505e-02 1.066081e-02 1.040334e-02 1.008506e-02 9.737771e-03 9.429071e-03 9.202683e-03 9.065683e-03 9.007210e-03 9.007216e-03 9.041454e-03 9.082085e-03 9.112240e-03 9.136538e-03 9.183444e-03 9.300955e-03 9.537025e-03 9.896526e-03 1.030562e-02 1.065420e-02 1.089140e-02 1.104390e-02 1.111788e-02 1.104702e-02 1.077388e-02 1.036472e-02 9.977732e-03 9.736314e-03 9.658755e-03 9.690860e-03 9.762340e-03 9.825991e-03 9.868500e-03 9.913922e-03 1.000381e-02 1.017283e-02 1.040422e-02 1.062901e-02 1.077383e-02 1.080584e-02 1.075832e-02 1.069685e-02 1.067710e-02 1.072431e-02 1.083753e-02 1.103259e-02 1.135849e-02 1.184890e-02 1.243311e-02 1.289793e-02 1.299798e-02 1.266026e-02 1.207916e-02 1.157795e-02 1.137011e-02 1.143735e-02 1.160590e-02 1.167922e-02 1.157041e-02 1.132301e-02 1.106198e-02 1.088985e-02 1.083019e-02 1.083783e-02 1.086015e-02 1.086877e-02 1.087183e-02 1.088316e-02 1.091401e-02 1.097064e-02 1.108040e-02 1.128811e-02 1.165431e-02 1.221787e-02 1.295770e-02 1.378040e-02 1.458832e-02 1.535955e-02 1.607867e-02 1.658897e-02 1.663135e-02 1.617166e-02 1.562168e-02 1.569273e-02 1.705887e-02 2.012465e-02 2.476552e-02 2.992508e-02 3.360508e-02 3.388217e-02 3.038433e-02 2.469098e-02 1.912571e-02 1.523802e-02 1.327125e-02 1.265148e-02 1.265950e-02 1.278435e-02 1.279679e-02 1.266234e-02 1.244319e-02 1.220249e-02 1.199388e-02 1.184373e-02 1.176704e-02 1.178868e-02 1.196154e-02 1.234142e-02 1.289959e-02 1.347158e-02 1.384941e-02 1.391978e-02 1.375466e-02 1.351189e-02 1.330982e-02 1.316238e-02 1.301280e-02 1.280894e-02 1.253544e-02 1.223652e-02 1.197914e-02 1.181081e-02 1.174578e-02 1.175162e-02 1.180790e-02 1.188815e-02 1.198780e-02 1.210884e-02 1.225623e-02 1.244845e-02 1.270144e-02 1.301755e-02 1.336671e-02 1.366518e-02 1.382134e-02 1.379816e-02 1.368082e-02 1.367204e-02 1.399923e-02 1.476824e-02 1.587741e-02 1.699246e-02 1.766811e-02 1.758348e-02 1.673809e-02 1.546596e-02 1.421149e-02 1.329285e-02 1.277660e-02 1.257859e-02 1.255341e-02 1.260196e-02 1.266952e-02 1.273814e-02 1.281013e-02 1.287335e-02 1.291308e-02 1.291286e-02 1.286057e-02 1.277085e-02 1.264870e-02 1.247377e-02 1.221228e-02 1.185066e-02 1.143983e-02 1.107839e-02 1.082925e-02 1.064777e-02 1.040571e-02 1.000126e-02 9.457405e-03 8.902076e-03 8.461405e-03 8.189175e-03 8.056764e-03 8.014666e-03 8.016321e-03 8.043322e-03 8.087929e-03 8.148916e-03 8.222336e-03 8.302180e-03 8.370558e-03 8.412285e-03 8.424343e-03 8.410336e-03 8.390144e-03 8.374534e-03 8.374878e-03 8.388215e-03 8.416578e-03 8.454367e-03 8.506359e-03 8.572748e-03 8.662253e-03 8.768590e-03 8.880037e-03 8.966314e-03 9.010111e-03 9.008452e-03 8.987435e-03 8.983114e-03 9.029520e-03 9.140591e-03 9.323696e-03 9.602747e-03 1.001723e-02 1.060490e-02 1.133972e-02 1.208896e-02 1.262704e-02 1.275566e-02 1.241173e-02 1.173698e-02 1.096938e-02 1.031039e-02 9.851554e-03 9.581221e-03 9.447464e-03 9.409562e-03 9.449885e-03 9.569637e-03 9.764898e-03 1.001434e-02 1.027308e-02 1.049608e-02 1.065496e-02 1.075753e-02 1.084834e-02 1.102479e-02 1.143138e-02 1.220681e-02 1.338170e-02 1.474658e-02 1.586692e-02 1.627100e-02 1.575552e-02 1.451267e-02 1.301412e-02 1.171738e-02 1.085123e-02 1.040633e-02 1.024045e-02 1.020084e-02 1.019566e-02 1.019226e-02 1.019819e-02 1.022733e-02 1.028619e-02 1.037920e-02 1.053086e-02 1.076200e-02 1.109047e-02 1.146928e-02 1.180520e-02 1.197471e-02 1.190162e-02 1.160678e-02 1.118712e-02 1.076858e-02 1.043764e-02 1.023021e-02 1.013918e-02 1.015542e-02 1.028612e-02 1.056349e-02 1.102441e-02 1.165263e-02 1.234538e-02 1.291099e-02 1.315865e-02 1.299946e-02 1.250757e-02 1.187302e-02 1.129860e-02 1.091098e-02 1.073470e-02 1.073971e-02 1.087425e-02 1.107823e-02 1.130208e-02 1.149309e-02 1.161756e-02 1.166558e-02 1.165595e-02 1.161741e-02 1.157258e-02 1.152266e-02 1.146138e-02 1.138514e-02 1.130263e-02 1.122292e-02 1.115448e-02 1.110067e-02 1.106134e-02 1.104077e-02 1.103715e-02 1.105570e-02 1.109275e-02 1.115171e-02 1.123857e-02 1.138436e-02 1.161453e-02 1.193633e-02 1.229619e-02 1.259005e-02 1.271021e-02 1.260877e-02 1.233378e-02 1.200250e-02 1.174761e-02 1.164162e-02 1.169321e-02 1.183825e-02 1.199326e-02 1.207790e-02 1.206619e-02 1.197429e-02 1.185951e-02 1.176897e-02 1.174293e-02 1.180266e-02 1.199166e-02 1.236671e-02 1.298565e-02 1.385464e-02 1.486323e-02 1.579130e-02 1.637173e-02 1.641640e-02 1.592682e-02 1.510374e-02 1.427241e-02 1.371441e-02 1.356826e-02 1.378740e-02 1.419106e-02 1.455721e-02 1.471585e-02 1.459603e-02 1.423203e-02 1.372683e-02 1.318197e-02 1.269406e-02 1.231298e-02 1.206136e-02 1.193820e-02 1.193102e-02 1.201480e-02 1.215771e-02 1.231500e-02 1.244001e-02 1.249462e-02 1.247188e-02 1.238635e-02 1.227702e-02 1.217302e-02 1.208966e-02 1.202798e-02 1.197884e-02 1.193493e-02 1.191228e-02 1.193066e-02 1.211973e-02 1.279846e-02 1.476379e-02 1.973052e-02 3.078019e-02 5.241600e-02 8.933840e-02 1.437608e-01 2.115961e-01 2.807975e-01 3.337684e-01 3.539854e-01 3.347412e-01 2.822871e-01 2.129040e-01 1.444454e-01 8.951132e-02 5.237153e-02 3.087252e-02 2.023669e-02 1.582258e-02 1.450644e-02 1.456535e-02 1.520326e-02 1.604156e-02 1.684445e-02 1.745733e-02 1.776555e-02 1.775502e-02 1.747516e-02 1.701226e-02 1.643065e-02 1.578889e-02 1.513803e-02 1.452458e-02 1.399952e-02 1.358571e-02 1.328472e-02 1.307638e-02 1.293882e-02 1.285444e-02 1.281611e-02 1.281051e-02 1.283324e-02 1.287502e-02 1.293541e-02 1.300800e-02 1.309440e-02 1.318749e-02 1.327675e-02 1.334909e-02 1.340067e-02 1.342673e-02 1.343820e-02 1.344201e-02 1.344232e-02 1.343641e-02 1.341766e-02 1.338167e-02 1.333007e-02 1.326992e-02 1.321563e-02 1.317123e-02 1.314712e-02 1.314149e-02 1.315369e-02 1.318457e-02 1.322517e-02 1.327617e-02 1.332940e-02 1.338921e-02 1.345326e-02 1.352306e-02 1.360319e-02 1.369091e-02 1.379389e-02 1.392858e-02 1.411661e-02 1.438050e-02 1.472185e-02 1.513557e-02 1.560839e-02 1.617945e-02 1.695856e-02 1.814236e-02 1.994170e-02 2.243576e-02 2.543315e-02 2.840451e-02 3.061884e-02 3.141925e-02 3.050776e-02 2.812706e-02 2.490476e-02 2.160140e-02 1.881714e-02 1.683914e-02 1.565377e-02 1.508104e-02 1.489737e-02 1.490267e-02 1.496135e-02 1.498447e-02 1.492893e-02 1.478386e-02 1.455936e-02 1.428849e-02 1.401035e-02 1.375675e-02 1.355617e-02 1.342291e-02 1.337462e-02 1.345119e-02 1.373946e-02 1.440982e-02 1.573903e-02 1.809605e-02 2.186262e-02 2.729115e-02 3.430114e-02 4.237179e-02 5.050589e-02 5.740554e-02 6.178047e-02 6.275041e-02 6.007892e-02 5.428066e-02 4.650172e-02 3.813165e-02 3.044502e-02 2.427390e-02 1.993138e-02 1.732765e-02 1.617115e-02 1.619756e-02 1.727481e-02 1.941549e-02 2.262734e-02 2.678843e-02 3.142433e-02 3.577454e-02 3.889687e-02 4.003469e-02 3.889919e-02 3.580897e-02 3.154463e-02 2.705166e-02 2.307374e-02 2.000842e-02 1.790489e-02 1.657669e-02 1.577778e-02 1.529788e-02 1.499892e-02 1.481474e-02 1.470940e-02 1.467143e-02 1.469104e-02 1.475679e-02 1.487391e-02 1.505896e-02 1.534361e-02 1.577929e-02 1.642325e-02 1.733228e-02 1.853735e-02 2.004703e-02 2.184644e-02 2.387336e-02 2.603393e-02 2.815782e-02 3.003323e-02 3.139374e-02 3.203966e-02 3.192604e-02 3.123055e-02 3.038470e-02 2.998139e-02 3.065009e-02 3.289676e-02 3.701671e-02 4.307712e-02 5.086653e-02 5.995129e-02 6.966591e-02 7.930240e-02 8.827257e-02 9.638095e-02 1.037302e-01 1.105264e-01 1.165762e-01 1.210113e-01 1.224899e-01 1.196586e-01 1.120051e-01 1.002512e-01 8.618137e-02 7.205521e-02 5.980185e-02 5.049425e-02 4.428172e-02 4.061937e-02 3.864335e-02 3.747955e-02 3.642290e-02 3.506387e-02 3.324710e-02 3.106209e-02 2.876767e-02 2.669977e-02 2.520987e-02 2.457728e-02 2.499707e-02 2.648193e-02 2.887184e-02 3.176174e-02 3.456608e-02 3.667691e-02 3.762236e-02 3.724922e-02 3.580043e-02 3.380660e-02 3.187182e-02 3.045908e-02 2.977148e-02 2.971270e-02 3.001944e-02 3.041681e-02 3.075011e-02 3.101298e-02 3.130822e-02 3.179078e-02 3.253170e-02 3.351945e-02 3.462915e-02 3.571365e-02 3.667695e-02 3.758303e-02 3.866494e-02 4.027967e-02 4.281583e-02 4.653904e-02 5.149496e-02 5.747344e-02 6.404365e-02 7.061357e-02 7.649008e-02 8.103240e-02 8.377204e-02 8.457741e-02 8.380190e-02 8.230594e-02 8.127104e-02 8.195547e-02 8.534883e-02 9.187558e-02 1.013145e-01 1.127058e-01 1.246398e-01 1.353630e-01 1.433132e-01 1.473929e-01 1.472327e-01 1.433047e-01 1.365952e-01 1.282503e-01 1.193392e-01 1.105156e-01 1.022401e-01 9.468098e-02 8.797711e-02 8.224747e-02 7.761085e-02 7.416056e-02 7.198931e-02 7.114602e-02 7.165571e-02 7.337998e-02 7.600115e-02 7.901163e-02 8.177898e-02 8.367791e-02 8.426686e-02 8.335435e-02 8.101560e-02 7.751170e-02 7.327433e-02 6.880258e-02 6.463172e-02 6.126107e-02 5.908980e-02 5.835032e-02 5.906301e-02 6.109468e-02 6.415823e-02 6.796421e-02 7.225248e-02 7.680279e-02 8.147113e-02 8.612853e-02 9.058285e-02 9.466316e-02 9.823870e-02 1.011886e-01 1.035286e-01 1.053333e-01 1.067440e-01 1.079174e-01 1.090583e-01 1.104837e-01 1.126586e-01 1.161335e-01 1.214423e-01 1.288369e-01 1.381146e-01 1.485602e-01 1.588256e-01 1.674083e-01 1.727201e-01 1.736402e-01 1.696879e-01 1.612326e-01 1.495303e-01 1.362882e-01 1.234640e-01 1.127698e-01 1.054388e-01 1.020342e-01 1.025131e-01 1.062534e-01 1.123110e-01 1.194815e-01 1.264256e-01 1.318933e-01 1.348174e-01 1.344700e-01 1.306536e-01 1.236877e-01 1.144032e-01 1.039220e-01 9.360708e-02 8.480969e-02 7.866173e-02 7.603874e-02 7.735974e-02 8.261928e-02 9.132136e-02 1.025315e-01 1.150385e-01 1.274103e-01 1.382597e-01 1.463724e-01 1.509071e-01 1.515031e-01 1.482767e-01 1.419191e-01 1.334031e-01 1.239387e-01 1.146420e-01 1.064880e-01 1.001887e-01 9.624775e-02 9.509525e-02 9.732065e-02 1.036757e-01 1.150874e-01 1.324177e-01 1.560677e-01 1.856379e-01 2.195624e-01 2.551554e-01 2.888038e-01 3.168318e-01 3.359547e-01 3.444396e-01 3.420780e-01 3.303844e-01 3.118778e-01 2.895295e-01 2.658668e-01 2.428213e-01 2.212948e-01 2.017458e-01 1.841945e-01 1.686717e-01 1.553676e-01 1.445404e-01 1.364816e-01 1.313645e-01 1.290803e-01 1.292384e-01 1.312609e-01 1.343915e-01 1.379384e-01 1.413262e-01 1.442193e-01 1.465485e-01 1.483971e-01 1.500546e-01 1.517530e-01 1.536184e-01 1.557193e-01 1.579236e-01 1.601639e-01 1.623731e-01 1.646399e-01 1.670350e-01 1.698152e-01 1.730618e-01 1.769374e-01 1.815212e-01 1.868741e-01 1.931546e-01 2.004084e-01 2.087449e-01 2.182350e-01 2.289485e-01 2.411758e-01 2.554815e-01 2.726770e-01 2.939425e-01 3.200649e-01 3.512131e-01 3.863536e-01 4.229357e-01 4.570218e-01 4.841734e-01 4.999039e-01 5.013641e-01 4.874719e-01 4.595282e-01 4.205678e-01 3.749545e-01 3.268251e-01 2.801105e-01 2.373814e-01 2.002979e-01 1.696811e-01 1.456953e-01 1.283087e-01 1.171853e-01 1.117336e-01 1.111830e-01 1.144438e-01 1.202473e-01 1.272612e-01 1.342299e-01 1.401901e-01 1.444630e-01 1.468650e-01 1.476074e-01 1.472542e-01 1.465885e-01 1.464608e-01 1.476736e-01 1.507600e-01 1.559780e-01 1.633150e-01 1.724791e-01 1.829246e-01 1.940085e-01 2.049837e-01 2.152246e-01 2.244065e-01 2.327033e-01 2.409878e-01 2.508929e-01 2.644527e-01 2.837222e-01 3.100251e-01 3.433217e-01 3.816870e-01 4.213345e-01 4.573215e-01 4.844231e-01 4.986162e-01 4.979621e-01 4.832623e-01 4.577881e-01 4.267439e-01 3.956000e-01 3.691144e-01 3.505500e-01 3.408016e-01 3.386721e-01 3.413882e-01 3.452520e-01 3.464735e-01 3.421088e-01 3.305348e-01 3.115778e-01 2.865380e-01 2.580398e-01 2.288698e-01 2.019887e-01 1.796882e-01 1.634784e-01 1.536581e-01 1.497941e-01 1.505910e-01 1.543188e-01 1.591736e-01 1.633716e-01 1.656745e-01 1.652715e-01 1.621054e-01 1.566075e-01 1.497948e-01 1.429298e-01 1.374611e-01 1.348433e-01 1.364498e-01 1.435896e-01 1.570558e-01 1.774720e-01 2.045688e-01 2.373000e-01 2.737662e-01 3.108869e-01 3.453497e-01 3.730618e-01 3.910341e-01 3.966219e-01 3.890383e-01 3.688241e-01 3.382712e-01 3.003840e-01 2.590054e-01 2.176273e-01 1.792287e-01 1.459246e-01 1.187491e-01 9.798927e-02 8.311429e-02 7.342973e-02 6.797767e-02 6.587774e-02 6.645924e-02 6.917038e-02 7.366753e-02 7.965225e-02 8.694151e-02 9.531619e-02 1.045103e-01 1.141863e-01 1.239792e-01 1.333794e-01 1.418762e-01 1.489286e-01 1.540050e-01 1.567615e-01 1.568773e-01 1.543491e-01 1.493279e-01 1.423075e-01 1.338703e-01 1.247658e-01 1.157333e-01 1.073719e-01 1.001201e-01 9.420325e-02 8.964473e-02 8.627237e-02 8.388523e-02 8.229964e-02 8.132565e-02 8.094210e-02 8.110908e-02 8.189426e-02 8.326154e-02 8.518560e-02 8.744240e-02 8.976917e-02 9.174517e-02 9.299629e-02 9.312330e-02 9.195739e-02 8.944342e-02 8.580258e-02 8.146352e-02 7.702404e-02 7.317113e-02 7.059464e-02 6.991352e-02 7.156548e-02 7.581916e-02 8.262137e-02 9.163277e-02 1.022298e-01 1.135139e-01 1.244265e-01 1.338693e-01 1.407796e-01 1.443673e-01 1.442064e-01 1.402826e-01 1.330119e-01 1.230954e-01 1.115578e-01 9.941429e-02 8.761284e-02 7.697950e-02 6.803692e-02 6.105994e-02 5.606893e-02 5.287570e-02 5.120715e-02 5.063899e-02 5.082233e-02 5.139399e-02 5.208449e-02 5.272366e-02 5.323832e-02 5.364620e-02 5.406634e-02 5.464032e-02 5.552865e-02 5.689876e-02 5.883139e-02 6.141252e-02 6.460301e-02 6.834016e-02 7.248966e-02 7.683268e-02 8.114715e-02 8.510395e-02 8.842568e-02 9.076718e-02 9.191171e-02 9.166237e-02 8.997043e-02 8.688658e-02 8.266128e-02 7.755098e-02 7.196231e-02 6.627716e-02 6.086219e-02 5.599710e-02 5.186911e-02 4.860430e-02 4.617700e-02 4.451559e-02 4.351124e-02 4.299904e-02 4.284149e-02 4.288455e-02 4.301558e-02 4.315296e-02 4.322998e-02 4.322897e-02 4.313906e-02 4.296270e-02 4.273370e-02 4.247033e-02 4.220642e-02 4.200161e-02 4.191794e-02 4.203379e-02 4.245622e-02 4.328645e-02 4.460511e-02 4.648639e-02 4.893239e-02 5.188963e-02 5.521946e-02 5.872311e-02 6.217858e-02 6.531731e-02 6.788346e-02 6.970774e-02 7.068640e-02 7.078006e-02 7.008693e-02 6.877312e-02 6.700395e-02 6.505055e-02 6.308149e-02 6.127493e-02 5.978223e-02 5.867103e-02 5.801347e-02 5.785267e-02 5.822068e-02 5.917467e-02 6.075981e-02 6.298642e-02 6.587019e-02 6.934201e-02 7.327354e-02 7.745162e-02 8.158727e-02 8.533923e-02 8.834623e-02 9.026245e-02 9.084806e-02 8.992314e-02 8.747995e-02 8.365345e-02 7.872335e-02 7.306230e-02 6.710069e-02 6.127533e-02 5.597300e-02 5.150450e-02 4.804793e-02 4.570991e-02 4.443522e-02 4.412024e-02 4.456296e-02 4.554526e-02 4.683869e-02 4.823024e-02 4.954845e-02 5.068284e-02 5.156695e-02 5.222874e-02 5.271247e-02 5.312491e-02 5.355016e-02 5.409438e-02 5.478712e-02 5.562387e-02 5.653435e-02 5.740311e-02 5.806773e-02 5.837980e-02 5.821181e-02 5.750111e-02 5.622346e-02 5.446108e-02 5.236157e-02 5.010449e-02 4.791387e-02 4.601317e-02 4.458081e-02 4.377587e-02 4.369896e-02 4.439860e-02 4.587392e-02 4.811222e-02 5.103304e-02 5.458900e-02 5.869060e-02 6.321580e-02 6.809575e-02 7.312088e-02 7.811014e-02 8.288641e-02 8.709094e-02 9.054532e-02 9.292970e-02 9.408378e-02 9.388506e-02 9.230797e-02 8.949626e-02 8.564196e-02 8.105666e-02 7.609256e-02 7.110421e-02 6.636592e-02 6.218146e-02 5.869220e-02 5.600199e-02 5.409336e-02 5.296957e-02 5.250857e-02 5.261074e-02 5.316489e-02 5.403941e-02 5.513849e-02 5.639083e-02 5.772044e-02 5.911822e-02 6.060733e-02 6.221499e-02 6.401388e-02 6.609240e-02 6.851446e-02 7.130758e-02 7.450299e-02 7.801271e-02 8.171752e-02 8.546782e-02 8.898570e-02 9.203218e-02 9.440224e-02 9.581297e-02 9.618631e-02 9.544755e-02 9.363999e-02 9.093573e-02 8.753039e-02 8.370419e-02 7.977260e-02 7.595400e-02 7.251474e-02 6.961523e-02 6.732117e-02 6.565882e-02 6.460882e-02 6.407105e-02 6.396332e-02 6.414643e-02 6.455037e-02 6.504062e-02 6.555298e-02 6.599803e-02 6.628097e-02 6.636107e-02 6.618893e-02 6.569220e-02 6.488925e-02 6.375561e-02 6.234799e-02 6.070256e-02 5.892812e-02 5.709740e-02 5.530373e-02 5.365790e-02 5.222506e-02 5.104967e-02 5.017841e-02 4.958732e-02 4.926248e-02 4.915349e-02 4.919810e-02 4.932224e-02 4.946959e-02 4.958659e-02 4.962479e-02 4.956889e-02 4.942204e-02 4.920171e-02 4.892929e-02 4.863225e-02 4.834063e-02 4.806793e-02 4.780424e-02 4.753727e-02 4.724301e-02 4.689559e-02 4.646798e-02 4.595516e-02 4.535547e-02 4.470073e-02 4.403594e-02 4.342206e-02 4.291833e-02 4.257498e-02 4.244484e-02 4.255816e-02 4.292469e-02 4.351524e-02 4.428694e-02 4.518463e-02 4.614192e-02 4.707344e-02 4.792236e-02 4.863367e-02 4.919083e-02 4.960108e-02 4.992980e-02 5.026712e-02 5.076668e-02 5.160429e-02 5.297447e-02 5.507466e-02 5.810368e-02 6.219076e-02 6.738140e-02 7.365373e-02 8.088040e-02 8.880173e-02 9.706333e-02 1.052181e-01 1.127918e-01 1.193269e-01 1.243658e-01 1.275751e-01 1.287551e-01 1.278546e-01 1.249717e-01 1.203603e-01 1.143974e-01 1.075177e-01 1.001473e-01 9.273295e-02 8.561778e-02 7.904862e-02 7.318617e-02 6.810142e-02 6.375766e-02 6.005700e-02 5.687579e-02 5.407891e-02 5.153964e-02 4.917748e-02 4.691409e-02 4.473096e-02 4.262886e-02 4.062238e-02 3.875209e-02 3.704769e-02 3.553650e-02 3.424054e-02 3.316951e-02 3.231885e-02 3.167114e-02 3.122192e-02 3.093136e-02 3.077792e-02 3.073281e-02 3.077781e-02 3.088764e-02 3.103901e-02 3.121867e-02 3.140338e-02 3.158373e-02 3.173696e-02 3.184798e-02 3.191121e-02 3.190902e-02 3.185123e-02 3.173124e-02 3.156145e-02 3.135930e-02 3.114012e-02 3.092863e-02 3.073441e-02 3.059448e-02 3.050481e-02 3.047898e-02 3.053159e-02 3.064474e-02 3.082954e-02 3.106789e-02 3.136651e-02 3.171415e-02 3.211037e-02 3.254925e-02 3.303283e-02 3.355179e-02 3.411279e-02 3.468998e-02 3.528071e-02 3.585969e-02 3.640665e-02 3.689610e-02 3.730107e-02 3.760184e-02 3.776967e-02 3.780217e-02 3.768735e-02 3.743310e-02 3.705826e-02 3.657759e-02 3.602962e-02 3.543808e-02 3.483941e-02 3.426621e-02 3.374122e-02 3.328541e-02 3.290081e-02 3.259362e-02 3.235401e-02 3.216406e-02 3.201303e-02 3.187137e-02 3.172553e-02 3.155213e-02 3.134083e-02 3.107705e-02 3.075692e-02 3.037579e-02 2.993379e-02 2.943638e-02 2.889460e-02 2.830463e-02 2.768363e-02 2.704750e-02 2.639761e-02 2.575482e-02 2.512990e-02 2.453788e-02 2.399039e-02 2.350484e-02 2.308945e-02 2.275909e-02 2.252221e-02 2.238625e-02 2.235680e-02 2.244469e-02 2.264470e-02 2.296187e-02 2.339290e-02 2.393052e-02 2.456332e-02 2.527588e-02 2.604855e-02 2.685520e-02 2.766604e-02 2.844876e-02 2.916972e-02 2.979626e-02 3.029870e-02 3.065287e-02 3.084193e-02 3.085752e-02 3.070120e-02 3.038393e-02 2.992721e-02 2.934987e-02 2.869184e-02 2.797919e-02 2.724446e-02 2.652719e-02 2.584056e-02 2.521381e-02 2.466162e-02 2.418969e-02 2.380270e-02 2.349925e-02 2.327361e-02 2.311923e-02 2.302243e-02 2.297454e-02 2.296682e-02 2.298642e-02 2.303144e-02 2.308639e-02 2.315653e-02 2.323498e-02 2.332557e-02 2.343287e-02 2.356011e-02 2.372002e-02 2.391714e-02 2.416391e-02 2.447658e-02 2.486029e-02 2.532524e-02 2.587783e-02 2.652392e-02 2.726113e-02 2.808502e-02 2.899094e-02 2.995990e-02 3.098652e-02 3.203778e-02 3.309770e-02 3.414988e-02 3.515167e-02 3.608724e-02 3.693713e-02 3.765615e-02 3.823246e-02 3.864083e-02 3.886662e-02 3.889018e-02 3.870406e-02 3.831214e-02 3.771332e-02 3.692056e-02 3.595935e-02 3.485282e-02 3.363115e-02 3.233345e-02 3.099724e-02 2.965624e-02 2.834015e-02 2.708222e-02 2.591196e-02 2.483657e-02 2.387439e-02 2.302536e-02 2.229538e-02 2.167725e-02 2.116177e-02 2.074169e-02 2.040478e-02 2.014093e-02 1.993489e-02 1.978145e-02 1.967692e-02 1.960333e-02 1.955907e-02 1.954215e-02 1.954648e-02 1.957158e-02 1.961806e-02 1.968934e-02 1.978845e-02 1.992133e-02 2.009986e-02 2.033794e-02 2.064743e-02 2.106782e-02 2.160060e-02 2.228026e-02 2.314903e-02 2.422250e-02 2.554848e-02 2.716220e-02 2.907664e-02 3.134478e-02 3.396232e-02 3.694187e-02 4.032141e-02 4.403824e-02 4.812128e-02 5.251339e-02 5.719876e-02 6.215629e-02 6.728717e-02 7.261786e-02 7.806341e-02 8.359739e-02 8.917036e-02 9.475199e-02 1.003016e-01 1.057335e-01 1.109970e-01 1.160651e-01 1.207167e-01 1.249550e-01 1.286042e-01 1.315399e-01 1.336137e-01 1.347618e-01 1.347759e-01 1.337189e-01 1.314346e-01 1.280278e-01 1.234772e-01 1.179685e-01 1.115709e-01 1.044976e-01 9.696957e-02 8.910289e-02 8.125906e-02 7.344847e-02 6.601310e-02 5.897919e-02 5.249888e-02 4.665868e-02 4.143824e-02 3.693367e-02 3.302546e-02 2.977225e-02 2.704789e-02 2.483694e-02 2.304060e-02 2.162692e-02 2.050925e-02 1.964877e-02 1.898176e-02 1.847804e-02 1.809634e-02 1.781158e-02 1.759878e-02 1.744076e-02 1.732485e-02 1.723997e-02 1.718054e-02 1.713791e-02 1.710821e-02 1.708524e-02 1.708095e-02 1.708288e-02 1.709447e-02 1.711265e-02 1.714295e-02 1.718724e-02 1.724244e-02 1.731269e-02 1.739881e-02 1.750826e-02 1.764597e-02 1.781433e-02 1.802512e-02 1.828370e-02 1.860180e-02 1.899382e-02 1.947207e-02 2.005496e-02 2.076631e-02 2.162156e-02 2.265251e-02 2.388165e-02 2.533748e-02 2.704876e-02 2.903678e-02 3.132543e-02 3.393070e-02 3.685523e-02 4.009501e-02 4.363090e-02 4.742566e-02 5.143354e-02 5.558042e-02 5.978511e-02 6.395284e-02 6.797028e-02 7.171602e-02 7.507617e-02 7.794705e-02 8.020098e-02 8.177715e-02 8.260181e-02 8.263209e-02 8.186999e-02 8.032754e-02 7.806997e-02 7.514877e-02 7.169153e-02 6.778821e-02 6.357250e-02 5.917427e-02 5.471198e-02 5.030258e-02 4.603048e-02 4.200869e-02 3.826419e-02 3.487194e-02 3.183772e-02 2.918503e-02 2.689443e-02 2.495506e-02 2.332954e-02 2.199946e-02 2.091428e-02 2.004317e-02 1.934991e-02 1.880764e-02 1.837454e-02 1.803729e-02 1.777036e-02 1.755382e-02 1.737650e-02 1.723063e-02 1.710652e-02 1.699643e-02 1.689888e-02 1.681363e-02 1.673708e-02 1.666426e-02 1.659749e-02 1.654788e-02 1.649891e-02 1.646266e-02 1.642933e-02 1.640270e-02 1.638516e-02 1.637488e-02 1.636892e-02 1.637262e-02 1.638298e-02 1.639823e-02 1.642057e-02 1.645541e-02 1.649156e-02 1.653381e-02 1.658539e-02 1.663856e-02 1.669601e-02 1.676124e-02 1.682601e-02 1.689487e-02 1.696374e-02 1.703691e-02 1.711209e-02 1.719004e-02 1.727011e-02 1.735760e-02 1.745350e-02 1.755770e-02 1.768238e-02 1.782052e-02 1.798680e-02 1.817797e-02 1.840960e-02 1.867337e-02 1.898340e-02 1.933712e-02 1.973709e-02 2.018216e-02 2.066947e-02 2.120032e-02 2.175429e-02 2.234161e-02 2.292639e-02 2.351892e-02 2.408156e-02 2.461765e-02 2.510616e-02 2.552104e-02 2.586501e-02 2.611224e-02 2.626306e-02 2.630505e-02 2.623563e-02 2.606231e-02 2.578501e-02 2.540459e-02 2.494700e-02 2.441457e-02 2.382130e-02 2.319126e-02 2.253757e-02 2.186831e-02 2.120920e-02 2.056751e-02 1.995125e-02 1.938111e-02 1.884504e-02 1.836445e-02 1.793404e-02 1.754962e-02 1.721611e-02 1.692794e-02 1.668604e-02 1.647721e-02 1.630564e-02 1.616352e-02 1.604902e-02 1.595507e-02 1.588471e-02 1.583008e-02 1.579290e-02 1.577159e-02 1.576507e-02 1.577570e-02 1.580072e-02 1.583977e-02 1.590037e-02 1.597311e-02 1.606490e-02 1.617418e-02 1.629803e-02 1.643772e-02 1.659028e-02 1.675561e-02 1.693089e-02 1.710794e-02 1.728629e-02 1.746764e-02 1.763802e-02 1.779731e-02 1.794593e-02 1.807130e-02 1.817598e-02 1.825684e-02 1.831265e-02 1.833992e-02 1.834259e-02 1.831795e-02 1.826935e-02 1.820352e-02 1.811994e-02 1.802311e-02 1.792485e-02 1.782134e-02 1.772587e-02 1.763826e-02 1.756423e-02 1.751555e-02 1.748511e-02 1.748703e-02 1.751655e-02 1.758233e-02 1.767889e-02 1.780839e-02 1.797071e-02 1.816223e-02 1.839011e-02 1.863829e-02 1.891246e-02 1.920634e-02 1.951170e-02 1.982841e-02 2.014890e-02 2.047108e-02 2.078588e-02 2.109410e-02 2.138498e-02 2.165684e-02 2.191391e-02 2.214581e-02 2.236106e-02 2.256208e-02 2.275019e-02 2.293500e-02 2.312679e-02 2.333219e-02 2.356812e-02 2.384236e-02 2.418295e-02 2.459190e-02 2.509032e-02 2.569379e-02 2.641506e-02 2.727060e-02 2.826138e-02 2.940150e-02 3.069808e-02 3.214959e-02 3.374832e-02 3.548722e-02 3.736473e-02 3.935855e-02 4.145062e-02 4.361828e-02 4.584774e-02 4.808928e-02 5.033109e-02 5.252603e-02 5.465641e-02 5.667321e-02 5.853907e-02 6.023129e-02 6.171171e-02 6.294749e-02 6.391215e-02 6.458707e-02 6.495209e-02 6.498787e-02 6.470957e-02 6.408674e-02 6.315641e-02 6.190858e-02 6.038803e-02 5.859340e-02 5.657756e-02 5.437187e-02 5.200661e-02 4.953304e-02 4.698608e-02 4.440842e-02 4.182286e-02 3.927990e-02 3.681092e-02 3.444877e-02 3.219855e-02 3.009421e-02 2.813456e-02 2.633094e-02 2.469116e-02 2.320545e-02 2.188307e-02 2.070981e-02 1.967218e-02 1.876859e-02 1.797937e-02 1.729791e-02 1.671467e-02 1.621789e-02 1.579843e-02 1.544256e-02 1.514609e-02 1.490207e-02 1.470508e-02 1.454954e-02 1.442685e-02 1.433933e-02 1.427933e-02 1.424755e-02 1.423530e-02 1.424979e-02 1.428413e-02 1.433449e-02 1.440492e-02 1.448929e-02 1.458930e-02 1.469784e-02 1.482283e-02 1.495600e-02 1.509857e-02 1.524554e-02 1.539666e-02 1.554483e-02 1.569007e-02 1.583078e-02 1.595975e-02 1.607266e-02 1.616358e-02 1.623061e-02 1.627017e-02 1.627265e-02 1.623242e-02 1.614724e-02 1.601590e-02 1.583132e-02 1.559113e-02 1.529309e-02 1.493951e-02 1.452807e-02 1.406282e-02 1.354386e-02 ''') ImportString(u'fluxdelta(numeric)',''' 6.717000e+02 -3.561610e+01 -2.633984e+00 -3.129430e-01 1.666874e-01 -8.484430e-03 1.804276e-03 3.693617e-03 -4.087228e-04 1.365671e-03 -5.288830e-04 6.490390e-04 3.817240e-04 2.658840e-04 1.563056e-04 3.080680e-04 1.175751e-04 1.451300e-06 -1.818070e-05 8.834710e-05 6.513680e-05 1.187130e-04 2.168114e-04 4.193800e-04 4.341460e-04 1.143590e-03 1.568020e-03 1.912550e-03 2.722921e-03 2.798980e-03 2.224180e-03 1.184510e-03 1.666638e-03 1.154786e-03 1.211271e-03 9.903440e-04 1.085550e-03 9.823460e-04 9.830990e-04 1.063291e-03 1.044319e-03 8.725200e-04 9.690490e-04 1.037582e-03 1.139343e-03 1.067455e-03 9.966070e-04 7.403760e-04 7.110140e-04 8.018580e-04 7.857960e-04 8.050660e-04 7.357120e-04 8.008380e-04 6.144540e-04 5.787430e-04 6.268490e-04 6.103750e-04 5.726350e-04 6.020570e-04 5.193810e-04 4.530740e-04 5.553090e-04 5.810250e-04 6.288400e-04 6.092290e-04 5.501350e-04 5.639660e-04 5.311350e-04 5.593780e-04 5.034180e-04 5.230310e-04 5.447630e-04 3.832260e-04 5.639030e-04 5.085020e-04 6.083480e-04 6.059530e-04 5.551990e-04 5.257730e-04 5.815220e-04 6.213310e-04 6.409640e-04 6.142480e-04 5.756760e-04 5.605620e-04 6.908950e-04 7.122050e-04 7.171920e-04 7.597840e-04 8.048950e-04 7.890000e-04 7.510910e-04 6.976010e-04 6.585580e-04 6.521280e-04 6.003590e-04 6.524040e-04 5.868290e-04 5.523690e-04 5.973120e-04 5.266330e-04 5.820150e-04 5.820970e-04 5.934070e-04 5.957240e-04 5.966650e-04 5.550510e-04 6.392550e-04 5.402950e-04 5.725810e-04 5.674630e-04 6.107510e-04 5.200120e-04 5.561230e-04 5.302810e-04 4.995950e-04 4.905590e-04 5.799510e-04 5.670650e-04 5.462020e-04 5.767870e-04 5.873630e-04 5.904430e-04 5.915170e-04 6.614370e-04 6.329740e-04 5.626190e-04 5.911980e-04 6.229200e-04 6.789840e-04 6.951420e-04 6.874660e-04 7.938290e-04 8.608890e-04 8.534310e-04 1.035960e-03 1.046371e-03 1.175078e-03 1.114799e-03 1.084971e-03 1.151934e-03 8.935620e-04 1.016095e-03 1.000131e-03 1.092747e-03 1.191336e-03 1.429210e-03 1.361740e-03 1.328258e-03 1.501993e-03 1.344970e-03 1.386170e-03 1.306730e-03 1.275663e-03 1.142598e-03 1.112382e-03 1.073456e-03 9.359650e-04 9.922390e-04 9.385760e-04 1.182843e-03 1.145800e-03 1.182230e-03 1.054130e-03 1.021000e-03 9.440010e-04 8.509160e-04 8.455930e-04 9.099220e-04 1.030766e-03 1.095160e-03 1.004290e-03 8.589430e-04 8.748940e-04 8.570450e-04 7.116170e-04 6.705050e-04 5.868400e-04 6.365450e-04 6.337050e-04 5.576220e-04 5.953490e-04 5.522500e-04 5.456940e-04 5.697350e-04 5.255300e-04 5.301010e-04 5.654500e-04 4.940900e-04 4.632660e-04 4.663710e-04 4.903050e-04 4.204920e-04 4.616460e-04 4.475270e-04 4.871740e-04 4.697840e-04 4.752310e-04 4.217890e-04 4.725650e-04 4.191410e-04 4.272820e-04 3.585950e-04 4.536560e-04 3.619670e-04 3.725620e-04 3.562350e-04 4.285610e-04 4.276540e-04 3.878030e-04 4.073330e-04 4.123430e-04 3.329440e-04 3.465390e-04 4.070180e-04 4.006760e-04 3.757300e-04 3.756360e-04 3.704940e-04 3.410260e-04 3.928340e-04 3.932720e-04 3.821400e-04 3.529390e-04 3.855900e-04 3.630250e-04 3.797910e-04 3.591320e-04 3.480130e-04 3.478810e-04 3.685470e-04 3.415510e-04 3.589730e-04 3.526730e-04 3.233800e-04 3.694400e-04 3.497530e-04 3.187230e-04 2.844270e-04 3.342290e-04 3.650240e-04 3.402130e-04 3.353090e-04 3.282540e-04 3.750370e-04 3.438710e-04 3.654720e-04 4.282800e-04 3.945260e-04 3.393770e-04 3.284350e-04 3.305000e-04 2.884100e-04 3.889250e-04 3.178000e-04 3.878830e-04 3.272550e-04 3.107630e-04 2.545320e-04 2.719750e-04 2.726380e-04 2.597000e-04 2.907110e-04 2.954530e-04 3.018980e-04 2.857470e-04 2.999590e-04 3.357510e-04 2.832120e-04 2.767450e-04 2.326550e-04 3.005720e-04 2.920610e-04 3.141140e-04 3.216020e-04 2.960590e-04 2.944700e-04 2.763350e-04 2.979940e-04 3.319700e-04 2.778910e-04 2.417580e-04 3.229890e-04 3.201030e-04 2.907760e-04 2.646430e-04 2.946820e-04 3.108890e-04 2.797630e-04 2.683120e-04 2.958500e-04 3.097440e-04 2.564920e-04 2.516520e-04 2.997480e-04 2.566980e-04 2.582030e-04 2.536970e-04 3.009910e-04 2.794110e-04 3.321000e-04 3.129720e-04 3.275780e-04 3.546300e-04 3.712930e-04 3.761030e-04 4.286590e-04 4.936920e-04 4.735100e-04 4.801770e-04 4.532160e-04 4.423920e-04 3.954850e-04 3.871190e-04 3.474280e-04 3.097730e-04 2.737510e-04 2.717590e-04 2.583780e-04 2.582910e-04 2.390110e-04 2.331360e-04 2.443010e-04 2.723400e-04 2.603230e-04 2.408420e-04 2.263050e-04 2.833230e-04 2.149030e-04 2.936090e-04 2.517280e-04 2.234850e-04 2.514080e-04 2.662640e-04 2.771341e-04 2.045980e-04 2.326200e-04 1.810080e-04 2.006930e-04 2.410869e-04 2.597670e-04 1.867650e-04 1.901810e-04 1.916060e-04 2.093840e-04 1.955350e-04 2.565323e-04 2.037630e-04 2.192890e-04 1.968890e-04 2.704350e-04 2.173030e-04 1.760550e-04 1.725550e-04 2.216570e-04 1.926800e-04 2.033300e-04 2.158710e-04 1.716700e-04 1.582780e-04 1.991770e-04 1.960138e-04 2.370170e-04 1.977810e-04 1.855390e-04 2.126740e-04 1.549110e-04 1.961817e-04 2.122756e-04 1.861810e-04 1.654960e-04 1.791040e-04 1.820382e-04 2.405400e-04 1.242870e-04 1.501890e-04 1.754070e-04 1.639110e-04 2.381120e-04 2.045289e-04 1.997290e-04 1.934369e-04 1.827496e-04 1.725970e-04 2.004060e-04 2.467260e-04 1.736524e-04 1.935626e-04 2.078940e-04 2.016694e-04 1.329370e-04 2.095750e-04 2.004050e-04 2.036510e-04 1.409750e-04 1.874660e-04 2.163860e-04 2.560420e-04 2.817640e-04 1.715350e-04 2.116253e-04 2.174337e-04 2.128890e-04 2.452477e-04 2.027280e-04 1.742880e-04 2.697750e-04 2.191001e-04 2.379300e-04 1.765414e-04 2.417760e-04 2.599460e-04 2.425280e-04 2.192800e-04 2.232400e-04 2.113760e-04 1.629280e-04 2.081210e-04 1.732400e-04 1.752260e-04 2.160630e-04 2.113760e-04 2.080540e-04 1.746560e-04 2.258920e-04 1.885590e-04 1.922150e-04 1.844450e-04 2.215070e-04 1.965130e-04 1.940840e-04 2.119450e-04 1.637660e-04 2.243845e-04 1.360420e-04 1.471400e-04 2.076640e-04 1.943850e-04 1.922470e-04 2.383790e-04 2.052120e-04 2.432040e-04 2.247140e-04 2.567180e-04 2.139320e-04 1.633140e-04 1.947410e-04 2.109570e-04 1.937080e-04 1.842980e-04 1.945543e-04 2.099910e-04 2.205876e-04 1.979450e-04 1.687580e-04 1.535724e-04 1.834060e-04 1.859900e-04 1.792050e-04 1.419200e-04 1.621900e-04 1.986970e-04 1.983040e-04 1.677530e-04 1.642669e-04 1.974400e-04 1.395610e-04 1.750930e-04 1.632730e-04 1.397360e-04 1.573330e-04 1.414000e-04 1.795304e-04 1.730704e-04 1.715630e-04 1.949779e-04 1.283499e-04 1.524801e-04 1.779290e-04 1.161499e-04 1.327065e-04 1.278147e-04 1.491756e-04 1.229560e-04 1.148103e-04 1.509306e-04 1.731482e-04 1.551801e-04 1.381020e-04 1.314114e-04 1.472286e-04 1.344098e-04 9.988500e-05 1.303773e-04 1.125189e-04 1.289554e-04 1.432523e-04 1.366576e-04 1.322329e-04 1.305178e-04 1.389109e-04 1.662667e-04 1.336500e-04 1.511033e-04 1.114740e-04 1.503991e-04 8.616450e-05 1.101235e-04 1.220796e-04 1.961011e-04 1.292899e-04 1.188529e-04 1.527593e-04 1.467474e-04 1.307397e-04 1.303567e-04 9.265100e-05 1.402299e-04 1.396071e-04 1.621164e-04 9.949000e-05 8.862500e-05 1.337413e-04 1.507454e-04 1.300908e-04 1.180810e-04 1.695171e-04 1.506108e-04 1.511222e-04 1.328891e-04 8.778500e-05 1.785397e-04 1.243030e-04 1.716250e-04 1.131539e-04 9.775700e-05 1.678777e-04 9.306360e-05 6.967600e-05 1.603941e-04 1.383674e-04 1.824313e-04 1.388680e-04 1.443963e-04 8.206050e-05 1.675730e-04 1.738365e-04 9.086570e-05 9.601010e-05 1.285800e-04 8.368130e-05 1.200650e-04 6.754500e-05 9.305390e-05 8.082300e-05 7.390220e-05 6.676980e-05 1.203748e-04 9.672520e-05 8.817890e-05 7.994050e-05 -2.145500e-06 9.684500e-05 8.395420e-05 7.960260e-05 8.858920e-05 5.647990e-05 8.551250e-05 9.489980e-05 7.905970e-05 1.397891e-04 5.177580e-05 3.493470e-05 5.694900e-05 1.088608e-04 3.834760e-05 1.419521e-04 2.437100e-05 8.814820e-05 4.774260e-05 7.964050e-05 6.217990e-05 1.038021e-04 4.539770e-05 3.924990e-05 6.820190e-05 3.358830e-05 5.617550e-05 9.187220e-05 3.288800e-05 1.101645e-04 8.979560e-05 7.725220e-05 6.045450e-05 4.963690e-05 -2.433130e-06 5.946890e-05 6.741660e-05 6.040447e-05 -1.638516e-04 9.904860e-05 8.863740e-05 6.709960e-05 7.319323e-05 4.790710e-05 1.381440e-04 6.582190e-05 5.355960e-05 -2.700230e-05 ''') ImportString(u'lambda_model(numeric),+-',''' 3.098155e+01 1.449783e-02 3.095257e+01 1.448427e-02 3.092361e+01 1.447072e-02 3.089469e+01 1.445718e-02 3.086578e+01 1.444366e-02 3.083691e+01 1.443015e-02 3.080806e+01 1.441665e-02 3.077924e+01 1.440316e-02 3.075045e+01 1.438969e-02 3.072169e+01 1.437623e-02 3.069295e+01 1.436278e-02 3.066423e+01 1.434934e-02 3.063555e+01 1.433592e-02 3.060689e+01 1.432251e-02 3.057826e+01 1.430911e-02 3.054965e+01 1.429573e-02 3.052108e+01 1.428235e-02 3.049253e+01 1.426899e-02 3.046400e+01 1.425564e-02 3.043550e+01 1.424231e-02 3.040703e+01 1.422899e-02 3.037859e+01 1.421567e-02 3.035017e+01 1.420238e-02 3.032178e+01 1.418909e-02 3.029341e+01 1.417582e-02 3.026507e+01 1.416256e-02 3.023676e+01 1.414931e-02 3.020848e+01 1.413607e-02 3.018022e+01 1.412285e-02 3.015198e+01 1.410964e-02 3.012378e+01 1.409644e-02 3.009560e+01 1.408325e-02 3.006745e+01 1.407008e-02 3.003932e+01 1.405691e-02 3.001122e+01 1.404376e-02 2.998314e+01 1.403063e-02 2.995510e+01 1.401750e-02 2.992707e+01 1.400439e-02 2.989908e+01 1.399129e-02 2.987111e+01 1.397820e-02 2.984316e+01 1.396512e-02 2.981525e+01 1.395206e-02 2.978736e+01 1.393901e-02 2.975949e+01 1.392597e-02 2.973165e+01 1.391294e-02 2.970384e+01 1.389993e-02 2.967605e+01 1.388692e-02 2.964829e+01 1.387393e-02 2.962056e+01 1.386095e-02 2.959285e+01 1.384799e-02 2.956516e+01 1.383503e-02 2.953751e+01 1.382209e-02 2.950988e+01 1.380916e-02 2.948227e+01 1.379624e-02 2.945469e+01 1.378334e-02 2.942714e+01 1.377044e-02 2.939961e+01 1.375756e-02 2.937211e+01 1.374469e-02 2.934463e+01 1.373184e-02 2.931718e+01 1.371899e-02 2.928975e+01 1.370616e-02 2.926236e+01 1.369333e-02 2.923498e+01 1.368052e-02 2.920763e+01 1.366773e-02 2.918031e+01 1.365494e-02 2.915301e+01 1.364217e-02 2.912574e+01 1.362941e-02 2.909850e+01 1.361666e-02 2.907128e+01 1.360392e-02 2.904408e+01 1.359119e-02 2.901691e+01 1.357848e-02 2.898977e+01 1.356578e-02 2.896265e+01 1.355309e-02 2.893555e+01 1.354041e-02 2.890849e+01 1.352774e-02 2.888144e+01 1.351509e-02 2.885443e+01 1.350244e-02 2.882743e+01 1.348981e-02 2.880047e+01 1.347719e-02 2.877353e+01 1.346459e-02 2.874661e+01 1.345199e-02 2.871972e+01 1.343941e-02 2.869285e+01 1.342683e-02 2.866601e+01 1.341427e-02 2.863919e+01 1.340173e-02 2.861240e+01 1.338919e-02 2.858564e+01 1.337666e-02 2.855890e+01 1.336415e-02 2.853218e+01 1.335165e-02 2.850549e+01 1.333916e-02 2.847882e+01 1.332668e-02 2.845218e+01 1.331421e-02 2.842557e+01 1.330176e-02 2.839898e+01 1.328931e-02 2.837241e+01 1.327688e-02 2.834587e+01 1.326446e-02 2.831935e+01 1.325206e-02 2.829286e+01 1.323966e-02 2.826639e+01 1.322727e-02 2.823995e+01 1.321490e-02 2.821353e+01 1.320254e-02 2.818714e+01 1.319019e-02 2.816077e+01 1.317785e-02 2.813443e+01 1.316552e-02 2.810811e+01 1.315320e-02 2.808182e+01 1.314090e-02 2.805555e+01 1.312861e-02 2.802930e+01 1.311633e-02 2.800308e+01 1.310406e-02 2.797688e+01 1.309180e-02 2.795071e+01 1.307955e-02 2.792457e+01 1.306732e-02 2.789845e+01 1.305509e-02 2.787235e+01 1.304288e-02 2.784627e+01 1.303068e-02 2.782022e+01 1.301849e-02 2.779420e+01 1.300631e-02 2.776820e+01 1.299414e-02 2.774222e+01 1.298199e-02 2.771627e+01 1.296984e-02 2.769034e+01 1.295771e-02 2.766444e+01 1.294559e-02 2.763856e+01 1.293348e-02 2.761271e+01 1.292138e-02 2.758688e+01 1.290929e-02 2.756107e+01 1.289722e-02 2.753529e+01 1.288515e-02 2.750953e+01 1.287310e-02 2.748379e+01 1.286106e-02 2.745808e+01 1.284902e-02 2.743240e+01 1.283700e-02 2.740674e+01 1.282500e-02 2.738110e+01 1.281300e-02 2.735548e+01 1.280101e-02 2.732989e+01 1.278904e-02 2.730433e+01 1.277707e-02 2.727879e+01 1.276512e-02 2.725327e+01 1.275318e-02 2.722777e+01 1.274125e-02 2.720230e+01 1.272933e-02 2.717686e+01 1.271742e-02 2.715143e+01 1.270553e-02 2.712603e+01 1.269364e-02 2.710066e+01 1.268177e-02 2.707531e+01 1.266990e-02 2.704998e+01 1.265805e-02 2.702467e+01 1.264621e-02 2.699939e+01 1.263438e-02 2.697414e+01 1.262256e-02 2.694890e+01 1.261075e-02 2.692369e+01 1.259896e-02 2.689851e+01 1.258717e-02 2.687334e+01 1.257540e-02 2.684821e+01 1.256363e-02 2.682309e+01 1.255188e-02 2.679800e+01 1.254014e-02 2.677293e+01 1.252841e-02 2.674788e+01 1.251669e-02 2.672286e+01 1.250498e-02 2.669786e+01 1.249328e-02 2.667289e+01 1.248159e-02 2.664794e+01 1.246992e-02 2.662301e+01 1.245825e-02 2.659810e+01 1.244660e-02 2.657322e+01 1.243495e-02 2.654836e+01 1.242332e-02 2.652353e+01 1.241170e-02 2.649872e+01 1.240009e-02 2.647393e+01 1.238849e-02 2.644917e+01 1.237690e-02 2.642442e+01 1.236532e-02 2.639970e+01 1.235375e-02 2.637501e+01 1.234220e-02 2.635033e+01 1.233065e-02 2.632569e+01 1.231912e-02 2.630106e+01 1.230759e-02 2.627645e+01 1.229608e-02 2.625187e+01 1.228458e-02 2.622732e+01 1.227309e-02 2.620278e+01 1.226160e-02 2.617827e+01 1.225013e-02 2.615378e+01 1.223867e-02 2.612931e+01 1.222723e-02 2.610487e+01 1.221579e-02 2.608045e+01 1.220436e-02 2.605606e+01 1.219294e-02 2.603168e+01 1.218154e-02 2.600733e+01 1.217014e-02 2.598300e+01 1.215876e-02 2.595869e+01 1.214738e-02 2.593441e+01 1.213602e-02 2.591015e+01 1.212467e-02 2.588591e+01 1.211333e-02 2.586170e+01 1.210199e-02 2.583750e+01 1.209067e-02 2.581333e+01 1.207936e-02 2.578919e+01 1.206806e-02 2.576506e+01 1.205677e-02 2.574096e+01 1.204550e-02 2.571688e+01 1.203423e-02 2.569282e+01 1.202297e-02 2.566879e+01 1.201172e-02 2.564478e+01 1.200049e-02 2.562078e+01 1.198926e-02 2.559682e+01 1.197804e-02 2.557287e+01 1.196684e-02 2.554895e+01 1.195564e-02 2.552505e+01 1.194446e-02 2.550117e+01 1.193329e-02 2.547732e+01 1.192212e-02 2.545348e+01 1.191097e-02 2.542967e+01 1.189983e-02 2.540589e+01 1.188870e-02 2.538212e+01 1.187757e-02 2.535837e+01 1.186646e-02 2.533465e+01 1.185536e-02 2.531095e+01 1.184427e-02 2.528728e+01 1.183319e-02 2.526362e+01 1.182212e-02 2.523999e+01 1.181106e-02 2.521638e+01 1.180002e-02 2.519279e+01 1.178898e-02 2.516922e+01 1.177795e-02 2.514568e+01 1.176693e-02 2.512215e+01 1.175592e-02 2.509865e+01 1.174493e-02 2.507517e+01 1.173394e-02 2.505172e+01 1.172296e-02 2.502828e+01 1.171200e-02 2.500487e+01 1.170104e-02 2.498148e+01 1.169010e-02 2.495811e+01 1.167916e-02 2.493476e+01 1.166823e-02 2.491143e+01 1.165732e-02 2.488813e+01 1.164641e-02 2.486485e+01 1.163552e-02 2.484159e+01 1.162463e-02 2.481835e+01 1.161376e-02 2.479513e+01 1.160290e-02 2.477194e+01 1.159204e-02 2.474877e+01 1.158120e-02 2.472561e+01 1.157036e-02 2.470248e+01 1.155954e-02 2.467937e+01 1.154873e-02 2.465629e+01 1.153792e-02 2.463322e+01 1.152713e-02 2.461018e+01 1.151635e-02 2.458716e+01 1.150557e-02 2.456416e+01 1.149481e-02 2.454118e+01 1.148406e-02 2.451822e+01 1.147331e-02 2.449528e+01 1.146258e-02 2.447237e+01 1.145186e-02 2.444948e+01 1.144115e-02 2.442661e+01 1.143044e-02 2.440376e+01 1.141975e-02 2.438093e+01 1.140907e-02 2.435812e+01 1.139839e-02 2.433533e+01 1.138773e-02 2.431257e+01 1.137708e-02 2.428983e+01 1.136644e-02 2.426710e+01 1.135580e-02 2.424440e+01 1.134518e-02 2.422172e+01 1.133457e-02 2.419906e+01 1.132396e-02 2.417643e+01 1.131337e-02 2.415381e+01 1.130279e-02 2.413122e+01 1.129221e-02 2.410864e+01 1.128165e-02 2.408609e+01 1.127110e-02 2.406356e+01 1.126055e-02 2.404105e+01 1.125002e-02 2.401856e+01 1.123950e-02 2.399609e+01 1.122898e-02 2.397364e+01 1.121848e-02 2.395122e+01 1.120798e-02 2.392881e+01 1.119750e-02 2.390643e+01 1.118702e-02 2.388406e+01 1.117656e-02 2.386172e+01 1.116610e-02 2.383940e+01 1.115566e-02 2.381710e+01 1.114522e-02 2.379482e+01 1.113480e-02 2.377256e+01 1.112438e-02 2.375032e+01 1.111397e-02 2.372810e+01 1.110358e-02 2.370590e+01 1.109319e-02 2.368373e+01 1.108281e-02 2.366157e+01 1.107245e-02 2.363944e+01 1.106209e-02 2.361732e+01 1.105174e-02 2.359523e+01 1.104140e-02 2.357316e+01 1.103107e-02 2.355111e+01 1.102075e-02 2.352908e+01 1.101044e-02 2.350706e+01 1.100014e-02 2.348507e+01 1.098985e-02 2.346311e+01 1.097957e-02 2.344116e+01 1.096930e-02 2.341923e+01 1.095904e-02 2.339732e+01 1.094879e-02 2.337543e+01 1.093855e-02 2.335357e+01 1.092831e-02 2.333172e+01 1.091809e-02 2.330989e+01 1.090788e-02 2.328809e+01 1.089767e-02 2.326630e+01 1.088748e-02 2.324454e+01 1.087729e-02 2.322280e+01 1.086712e-02 2.320107e+01 1.085695e-02 2.317937e+01 1.084680e-02 2.315768e+01 1.083665e-02 2.313602e+01 1.082651e-02 2.311438e+01 1.081638e-02 2.309275e+01 1.080627e-02 2.307115e+01 1.079616e-02 2.304957e+01 1.078606e-02 2.302801e+01 1.077597e-02 2.300647e+01 1.076589e-02 2.298495e+01 1.075582e-02 2.296344e+01 1.074575e-02 2.294196e+01 1.073570e-02 2.292050e+01 1.072566e-02 2.289906e+01 1.071563e-02 2.287764e+01 1.070560e-02 2.285624e+01 1.069559e-02 2.283486e+01 1.068558e-02 2.281349e+01 1.067559e-02 2.279215e+01 1.066560e-02 2.277083e+01 1.065562e-02 2.274953e+01 1.064565e-02 2.272825e+01 1.063570e-02 2.270699e+01 1.062575e-02 2.268575e+01 1.061581e-02 2.266452e+01 1.060588e-02 2.264332e+01 1.059595e-02 2.262214e+01 1.058604e-02 2.260098e+01 1.057614e-02 2.257984e+01 1.056625e-02 2.255871e+01 1.055636e-02 2.253761e+01 1.054649e-02 2.251653e+01 1.053662e-02 2.249546e+01 1.052676e-02 2.247442e+01 1.051692e-02 2.245340e+01 1.050708e-02 2.243239e+01 1.049725e-02 2.241141e+01 1.048743e-02 2.239044e+01 1.047762e-02 2.236950e+01 1.046782e-02 2.234857e+01 1.045803e-02 2.232767e+01 1.044824e-02 2.230678e+01 1.043847e-02 2.228591e+01 1.042870e-02 2.226506e+01 1.041895e-02 2.224424e+01 1.040920e-02 2.222343e+01 1.039946e-02 2.220264e+01 1.038974e-02 2.218187e+01 1.038002e-02 2.216112e+01 1.037031e-02 2.214039e+01 1.036061e-02 2.211967e+01 1.035091e-02 2.209898e+01 1.034123e-02 2.207831e+01 1.033156e-02 2.205766e+01 1.032189e-02 2.203702e+01 1.031224e-02 2.201641e+01 1.030259e-02 2.199581e+01 1.029295e-02 2.197524e+01 1.028332e-02 2.195468e+01 1.027370e-02 2.193414e+01 1.026409e-02 2.191362e+01 1.025449e-02 2.189312e+01 1.024490e-02 2.187264e+01 1.023531e-02 2.185218e+01 1.022574e-02 2.183174e+01 1.021617e-02 2.181132e+01 1.020662e-02 2.179091e+01 1.019707e-02 2.177053e+01 1.018753e-02 2.175016e+01 1.017800e-02 2.172982e+01 1.016848e-02 2.170949e+01 1.015897e-02 2.168918e+01 1.014946e-02 2.166889e+01 1.013997e-02 2.164862e+01 1.013048e-02 2.162837e+01 1.012101e-02 2.160814e+01 1.011154e-02 2.158792e+01 1.010208e-02 2.156773e+01 1.009263e-02 2.154755e+01 1.008319e-02 2.152740e+01 1.007376e-02 2.150726e+01 1.006433e-02 2.148714e+01 1.005492e-02 2.146704e+01 1.004551e-02 2.144696e+01 1.003611e-02 2.142689e+01 1.002673e-02 2.140685e+01 1.001735e-02 2.138682e+01 1.000798e-02 2.136682e+01 9.998613e-03 2.134683e+01 9.989260e-03 2.132686e+01 9.979915e-03 2.130691e+01 9.970579e-03 2.128698e+01 9.961252e-03 2.126706e+01 9.951934e-03 2.124717e+01 9.942624e-03 2.122729e+01 9.933324e-03 2.120744e+01 9.924031e-03 2.118760e+01 9.914747e-03 2.116778e+01 9.905472e-03 2.114798e+01 9.896207e-03 2.112819e+01 9.886948e-03 2.110843e+01 9.877700e-03 2.108868e+01 9.868460e-03 2.106895e+01 9.859229e-03 2.104925e+01 9.850005e-03 2.102955e+01 9.840791e-03 2.100988e+01 9.831585e-03 2.099023e+01 9.822388e-03 2.097059e+01 9.813200e-03 2.095098e+01 9.804020e-03 2.093138e+01 9.794848e-03 2.091180e+01 9.785686e-03 2.089223e+01 9.776532e-03 2.087269e+01 9.767386e-03 2.085316e+01 9.758249e-03 2.083366e+01 9.749120e-03 2.081417e+01 9.740001e-03 2.079470e+01 9.730889e-03 2.077524e+01 9.721786e-03 2.075581e+01 9.712691e-03 2.073639e+01 9.703606e-03 2.071700e+01 9.694529e-03 2.069761e+01 9.685460e-03 2.067825e+01 9.676400e-03 2.065891e+01 9.667347e-03 2.063958e+01 9.658304e-03 2.062028e+01 9.649269e-03 2.060099e+01 9.640242e-03 2.058171e+01 9.631224e-03 2.056246e+01 9.622214e-03 2.054323e+01 9.613213e-03 2.052401e+01 9.604220e-03 2.050481e+01 9.595236e-03 2.048563e+01 9.586261e-03 2.046646e+01 9.577293e-03 2.044732e+01 9.568334e-03 2.042819e+01 9.559383e-03 2.040908e+01 9.550440e-03 2.038999e+01 9.541506e-03 2.037092e+01 9.532580e-03 2.035186e+01 9.523663e-03 2.033282e+01 9.514754e-03 2.031380e+01 9.505853e-03 2.029480e+01 9.496961e-03 2.027581e+01 9.488077e-03 2.025685e+01 9.479201e-03 2.023790e+01 9.470333e-03 2.021896e+01 9.461475e-03 2.020005e+01 9.452623e-03 2.018115e+01 9.443781e-03 2.016228e+01 9.434947e-03 2.014341e+01 9.426121e-03 2.012457e+01 9.417303e-03 2.010574e+01 9.408494e-03 2.008694e+01 9.399692e-03 2.006815e+01 9.390899e-03 2.004937e+01 9.382114e-03 2.003062e+01 9.373337e-03 2.001188e+01 9.364569e-03 1.999316e+01 9.355809e-03 1.997446e+01 9.347057e-03 1.995577e+01 9.338313e-03 1.993710e+01 9.329577e-03 1.991845e+01 9.320850e-03 1.989982e+01 9.312131e-03 1.988120e+01 9.303420e-03 1.986261e+01 9.294717e-03 1.984402e+01 9.286022e-03 1.982546e+01 9.277334e-03 1.980692e+01 9.268656e-03 1.978839e+01 9.259986e-03 1.976987e+01 9.251324e-03 1.975138e+01 9.242669e-03 1.973290e+01 9.234023e-03 1.971445e+01 9.225384e-03 1.969600e+01 9.216755e-03 1.967758e+01 9.208133e-03 1.965917e+01 9.199519e-03 1.964078e+01 9.190913e-03 1.962241e+01 9.182315e-03 1.960405e+01 9.173726e-03 1.958571e+01 9.165144e-03 1.956739e+01 9.156571e-03 1.954909e+01 9.148004e-03 1.953080e+01 9.139447e-03 1.951253e+01 9.130898e-03 1.949427e+01 9.122356e-03 1.947604e+01 9.113822e-03 1.945782e+01 9.105297e-03 1.943962e+01 9.096779e-03 1.942143e+01 9.088269e-03 1.940327e+01 9.079767e-03 1.938511e+01 9.071274e-03 1.936698e+01 9.062788e-03 1.934886e+01 9.054310e-03 1.933076e+01 9.045840e-03 1.931268e+01 9.037378e-03 1.929461e+01 9.028924e-03 1.927656e+01 9.020478e-03 1.925853e+01 9.012039e-03 1.924051e+01 9.003608e-03 1.922252e+01 8.995187e-03 1.920453e+01 8.986772e-03 1.918657e+01 8.978365e-03 1.916862e+01 8.969965e-03 1.915069e+01 8.961575e-03 1.913277e+01 8.953191e-03 1.911488e+01 8.944816e-03 1.909699e+01 8.936449e-03 1.907913e+01 8.928088e-03 1.906128e+01 8.919737e-03 1.904345e+01 8.911393e-03 1.902564e+01 8.903056e-03 1.900784e+01 8.894729e-03 1.899006e+01 8.886407e-03 1.897229e+01 8.878094e-03 1.895455e+01 8.869790e-03 1.893682e+01 8.861492e-03 1.891910e+01 8.853203e-03 1.890140e+01 8.844920e-03 1.888372e+01 8.836647e-03 1.886605e+01 8.828380e-03 1.884841e+01 8.820121e-03 1.883077e+01 8.811871e-03 1.881316e+01 8.803627e-03 1.879556e+01 8.795392e-03 1.877798e+01 8.787164e-03 1.876041e+01 8.778944e-03 1.874286e+01 8.770732e-03 1.872533e+01 8.762527e-03 1.870781e+01 8.754330e-03 1.869031e+01 8.746141e-03 1.867283e+01 8.737959e-03 1.865536e+01 8.729785e-03 1.863791e+01 8.721619e-03 1.862047e+01 8.713460e-03 1.860305e+01 8.705309e-03 1.858565e+01 8.697165e-03 1.856827e+01 8.689029e-03 1.855090e+01 8.680901e-03 1.853354e+01 8.672780e-03 1.851620e+01 8.664668e-03 1.849888e+01 8.656561e-03 1.848158e+01 8.648464e-03 1.846429e+01 8.640374e-03 1.844702e+01 8.632291e-03 1.842976e+01 8.624216e-03 1.841252e+01 8.616148e-03 1.839530e+01 8.608088e-03 1.837809e+01 8.600036e-03 1.836090e+01 8.591990e-03 1.834372e+01 8.583953e-03 1.832656e+01 8.575923e-03 1.830942e+01 8.567900e-03 1.829229e+01 8.559885e-03 1.827518e+01 8.551878e-03 1.825808e+01 8.543878e-03 1.824100e+01 8.535885e-03 1.822394e+01 8.527901e-03 1.820689e+01 8.519923e-03 1.818986e+01 8.511953e-03 1.817284e+01 8.503990e-03 1.815584e+01 8.496035e-03 1.813886e+01 8.488088e-03 1.812189e+01 8.480147e-03 1.810494e+01 8.472214e-03 1.808800e+01 8.464289e-03 1.807108e+01 8.456371e-03 1.805417e+01 8.448460e-03 1.803728e+01 8.440557e-03 1.802041e+01 8.432661e-03 1.800356e+01 8.424773e-03 1.798671e+01 8.416892e-03 1.796989e+01 8.409018e-03 1.795308e+01 8.401152e-03 1.793628e+01 8.393292e-03 1.791950e+01 8.385441e-03 1.790274e+01 8.377597e-03 1.788599e+01 8.369760e-03 1.786926e+01 8.361930e-03 1.785255e+01 8.354108e-03 1.783585e+01 8.346293e-03 1.781916e+01 8.338485e-03 1.780249e+01 8.330685e-03 1.778584e+01 8.322892e-03 1.776920e+01 8.315106e-03 1.775258e+01 8.307328e-03 1.773597e+01 8.299557e-03 1.771938e+01 8.291792e-03 1.770280e+01 8.284036e-03 1.768624e+01 8.276287e-03 1.766970e+01 8.268544e-03 1.765317e+01 8.260810e-03 1.763666e+01 8.253082e-03 1.762016e+01 8.245361e-03 1.760367e+01 8.237648e-03 1.758721e+01 8.229942e-03 1.757075e+01 8.222243e-03 1.755432e+01 8.214552e-03 1.753790e+01 8.206868e-03 1.752149e+01 8.199190e-03 1.750510e+01 8.191520e-03 1.748872e+01 8.183857e-03 1.747236e+01 8.176201e-03 1.745602e+01 8.168553e-03 1.743969e+01 8.160911e-03 1.742337e+01 8.153277e-03 1.740708e+01 8.145650e-03 1.739079e+01 8.138030e-03 1.737452e+01 8.130417e-03 1.735827e+01 8.122812e-03 1.734203e+01 8.115213e-03 1.732581e+01 8.107621e-03 1.730960e+01 8.100037e-03 1.729341e+01 8.092460e-03 1.727723e+01 8.084889e-03 1.726107e+01 8.077326e-03 1.724492e+01 8.069770e-03 1.722879e+01 8.062221e-03 1.721267e+01 8.054679e-03 1.719657e+01 8.047145e-03 1.718048e+01 8.039617e-03 1.716441e+01 8.032097e-03 1.714836e+01 8.024583e-03 1.713231e+01 8.017076e-03 1.711629e+01 8.009576e-03 1.710028e+01 8.002084e-03 1.708428e+01 7.994598e-03 1.706830e+01 7.987119e-03 1.705233e+01 7.979647e-03 1.703638e+01 7.972183e-03 1.702044e+01 7.964725e-03 1.700452e+01 7.957274e-03 1.698861e+01 7.949831e-03 1.697272e+01 7.942394e-03 1.695684e+01 7.934964e-03 1.694098e+01 7.927542e-03 1.692513e+01 7.920125e-03 1.690930e+01 7.912716e-03 1.689348e+01 7.905315e-03 1.687768e+01 7.897919e-03 1.686189e+01 7.890531e-03 1.684612e+01 7.883149e-03 1.683036e+01 7.875775e-03 1.681462e+01 7.868407e-03 1.679889e+01 7.861047e-03 1.678317e+01 7.853693e-03 1.676747e+01 7.846346e-03 1.675179e+01 7.839006e-03 1.673611e+01 7.831673e-03 1.672046e+01 7.824347e-03 1.670482e+01 7.817028e-03 1.668919e+01 7.809715e-03 1.667358e+01 7.802410e-03 1.665798e+01 7.795111e-03 1.664240e+01 7.787819e-03 1.662683e+01 7.780533e-03 1.661128e+01 7.773255e-03 1.659574e+01 7.765983e-03 1.658021e+01 7.758718e-03 1.656470e+01 7.751461e-03 1.654921e+01 7.744209e-03 1.653373e+01 7.736965e-03 1.651826e+01 7.729727e-03 1.650281e+01 7.722497e-03 1.648737e+01 7.715272e-03 1.647194e+01 7.708055e-03 1.645654e+01 7.700845e-03 1.644114e+01 7.693640e-03 1.642576e+01 7.686444e-03 1.641040e+01 7.679253e-03 1.639504e+01 7.672070e-03 1.637971e+01 7.664892e-03 1.636439e+01 7.657722e-03 1.634908e+01 7.650559e-03 1.633378e+01 7.643402e-03 1.631850e+01 7.636252e-03 1.630324e+01 7.629108e-03 1.628799e+01 7.621971e-03 1.627275e+01 7.614842e-03 1.625753e+01 7.607718e-03 1.624232e+01 7.600601e-03 1.622713e+01 7.593491e-03 1.621194e+01 7.586388e-03 1.619678e+01 7.579291e-03 1.618163e+01 7.572201e-03 1.616649e+01 7.565117e-03 1.615137e+01 7.558040e-03 1.613626e+01 7.550970e-03 1.612116e+01 7.543907e-03 1.610608e+01 7.536849e-03 1.609102e+01 7.529799e-03 1.607596e+01 7.522755e-03 1.606092e+01 7.515718e-03 1.604590e+01 7.508687e-03 1.603089e+01 7.501663e-03 1.601589e+01 7.494646e-03 1.600091e+01 7.487635e-03 1.598594e+01 7.480630e-03 1.597099e+01 7.473633e-03 1.595605e+01 7.466641e-03 1.594112e+01 7.459656e-03 1.592621e+01 7.452678e-03 1.591131e+01 7.445707e-03 1.589643e+01 7.438741e-03 1.588156e+01 7.431783e-03 1.586670e+01 7.424830e-03 1.585186e+01 7.417885e-03 1.583703e+01 7.410946e-03 1.582221e+01 7.404013e-03 1.580741e+01 7.397087e-03 1.579263e+01 7.390167e-03 1.577785e+01 7.383254e-03 1.576309e+01 7.376347e-03 1.574835e+01 7.369447e-03 1.573361e+01 7.362553e-03 1.571890e+01 7.355665e-03 1.570419e+01 7.348785e-03 1.568950e+01 7.341910e-03 1.567482e+01 7.335042e-03 1.566016e+01 7.328180e-03 1.564551e+01 7.321325e-03 1.563088e+01 7.314476e-03 1.561625e+01 7.307634e-03 1.560165e+01 7.300798e-03 1.558705e+01 7.293968e-03 1.557247e+01 7.287145e-03 1.555790e+01 7.280328e-03 1.554335e+01 7.273518e-03 1.552881e+01 7.266713e-03 1.551428e+01 7.259916e-03 1.549977e+01 7.253124e-03 1.548527e+01 7.246339e-03 1.547078e+01 7.239561e-03 1.545631e+01 7.232788e-03 1.544185e+01 7.226022e-03 1.542741e+01 7.219262e-03 1.541297e+01 7.212509e-03 1.539856e+01 7.205762e-03 1.538415e+01 7.199022e-03 1.536976e+01 7.192287e-03 1.535538e+01 7.185559e-03 1.534102e+01 7.178837e-03 1.532667e+01 7.172122e-03 1.531233e+01 7.165412e-03 1.529801e+01 7.158709e-03 1.528369e+01 7.152013e-03 1.526940e+01 7.145322e-03 1.525511e+01 7.138638e-03 1.524084e+01 7.131960e-03 1.522659e+01 7.125288e-03 1.521234e+01 7.118623e-03 1.519811e+01 7.111964e-03 1.518389e+01 7.105311e-03 1.516969e+01 7.098664e-03 1.515550e+01 7.092023e-03 1.514132e+01 7.085389e-03 1.512716e+01 7.078761e-03 1.511301e+01 7.072139e-03 1.509887e+01 7.065523e-03 1.508474e+01 7.058914e-03 1.507063e+01 7.052310e-03 1.505653e+01 7.045713e-03 1.504245e+01 7.039122e-03 1.502838e+01 7.032537e-03 1.501432e+01 7.025959e-03 1.500027e+01 7.019386e-03 1.498624e+01 7.012820e-03 1.497222e+01 7.006260e-03 1.495822e+01 6.999705e-03 1.494422e+01 6.993158e-03 1.493024e+01 6.986616e-03 1.491628e+01 6.980080e-03 1.490232e+01 6.973550e-03 1.488838e+01 6.967027e-03 1.487446e+01 6.960509e-03 1.486054e+01 6.953998e-03 1.484664e+01 6.947493e-03 1.483275e+01 6.940994e-03 1.481888e+01 6.934501e-03 1.480501e+01 6.928014e-03 1.479116e+01 6.921533e-03 1.477733e+01 6.915058e-03 1.476350e+01 6.908590e-03 1.474969e+01 6.902127e-03 1.473590e+01 6.895670e-03 1.472211e+01 6.889219e-03 1.470834e+01 6.882775e-03 1.469458e+01 6.876336e-03 1.468083e+01 6.869903e-03 1.466710e+01 6.863477e-03 1.465338e+01 6.857057e-03 1.463967e+01 6.850642e-03 1.462598e+01 6.844233e-03 1.461230e+01 6.837831e-03 1.459863e+01 6.831434e-03 1.458497e+01 6.825044e-03 1.457133e+01 6.818659e-03 1.455769e+01 6.812281e-03 1.454408e+01 6.805908e-03 1.453047e+01 6.799541e-03 1.451688e+01 6.793180e-03 1.450330e+01 6.786826e-03 1.448973e+01 6.780477e-03 1.447618e+01 6.774134e-03 1.446263e+01 6.767797e-03 1.444911e+01 6.761466e-03 1.443559e+01 6.755141e-03 1.442208e+01 6.748822e-03 1.440859e+01 6.742509e-03 1.439511e+01 6.736201e-03 1.438165e+01 6.729899e-03 1.436819e+01 6.723604e-03 1.435475e+01 6.717314e-03 1.434133e+01 6.711030e-03 1.432791e+01 6.704753e-03 1.431451e+01 6.698481e-03 1.430112e+01 6.692214e-03 1.428774e+01 6.685954e-03 1.427437e+01 6.679700e-03 1.426102e+01 6.673451e-03 1.424768e+01 6.667208e-03 1.423435e+01 6.660971e-03 1.422103e+01 6.654740e-03 1.420773e+01 6.648515e-03 1.419444e+01 6.642296e-03 1.418116e+01 6.636082e-03 1.416790e+01 6.629874e-03 1.415464e+01 6.623672e-03 1.414140e+01 6.617476e-03 1.412817e+01 6.611286e-03 1.411496e+01 6.605101e-03 1.410175e+01 6.598922e-03 1.408856e+01 6.592749e-03 1.407538e+01 6.586582e-03 1.406221e+01 6.580420e-03 1.404906e+01 6.574264e-03 1.403592e+01 6.568115e-03 1.402279e+01 6.561970e-03 1.400967e+01 6.555832e-03 1.399656e+01 6.549699e-03 1.398347e+01 6.543572e-03 1.397039e+01 6.537451e-03 1.395732e+01 6.531335e-03 1.394426e+01 6.525225e-03 1.393122e+01 6.519121e-03 1.391819e+01 6.513023e-03 1.390517e+01 6.506930e-03 1.389216e+01 6.500843e-03 1.387916e+01 6.494762e-03 1.386618e+01 6.488686e-03 1.385321e+01 6.482617e-03 1.384025e+01 6.476552e-03 1.382730e+01 6.470494e-03 1.381437e+01 6.464441e-03 1.380144e+01 6.458393e-03 1.378853e+01 6.452352e-03 1.377564e+01 6.446316e-03 1.376275e+01 6.440286e-03 1.374988e+01 6.434261e-03 1.373701e+01 6.428242e-03 1.372416e+01 6.422229e-03 1.371132e+01 6.416221e-03 1.369850e+01 6.410219e-03 1.368568e+01 6.404222e-03 1.367288e+01 6.398231e-03 1.366009e+01 6.392246e-03 1.364731e+01 6.386266e-03 1.363454e+01 6.380292e-03 1.362179e+01 6.374324e-03 1.360905e+01 6.368360e-03 1.359632e+01 6.362403e-03 1.358360e+01 6.356451e-03 1.357089e+01 6.350505e-03 1.355820e+01 6.344565e-03 1.354551e+01 6.338629e-03 1.353284e+01 6.332700e-03 1.352018e+01 6.326776e-03 1.350753e+01 6.320857e-03 1.349490e+01 6.314944e-03 1.348227e+01 6.309037e-03 1.346966e+01 6.303135e-03 1.345706e+01 6.297239e-03 1.344447e+01 6.291348e-03 1.343190e+01 6.285463e-03 1.341933e+01 6.279583e-03 1.340678e+01 6.273708e-03 1.339424e+01 6.267840e-03 1.338171e+01 6.261976e-03 1.336919e+01 6.256118e-03 1.335668e+01 6.250266e-03 1.334419e+01 6.244419e-03 1.333170e+01 6.238578e-03 1.331923e+01 6.232742e-03 1.330677e+01 6.226911e-03 1.329433e+01 6.221086e-03 1.328189e+01 6.215267e-03 1.326946e+01 6.209453e-03 1.325705e+01 6.203644e-03 1.324465e+01 6.197840e-03 1.323226e+01 6.192043e-03 1.321988e+01 6.186250e-03 1.320751e+01 6.180463e-03 1.319516e+01 6.174682e-03 1.318282e+01 6.168906e-03 1.317048e+01 6.163135e-03 1.315816e+01 6.157369e-03 1.314585e+01 6.151609e-03 1.313356e+01 6.145855e-03 1.312127e+01 6.140105e-03 1.310900e+01 6.134361e-03 1.309673e+01 6.128623e-03 1.308448e+01 6.122890e-03 1.307224e+01 6.117162e-03 1.306001e+01 6.111440e-03 1.304780e+01 6.105723e-03 1.303559e+01 6.100011e-03 1.302340e+01 6.094305e-03 1.301121e+01 6.088604e-03 1.299904e+01 6.082908e-03 1.298688e+01 6.077218e-03 1.297473e+01 6.071533e-03 1.296260e+01 6.065853e-03 1.295047e+01 6.060179e-03 1.293835e+01 6.054510e-03 1.292625e+01 6.048846e-03 1.291416e+01 6.043187e-03 1.290208e+01 6.037534e-03 1.289001e+01 6.031887e-03 1.287795e+01 6.026244e-03 1.286590e+01 6.020607e-03 1.285387e+01 6.014974e-03 1.284184e+01 6.009348e-03 1.282983e+01 6.003726e-03 1.281783e+01 5.998110e-03 1.280584e+01 5.992499e-03 1.279386e+01 5.986893e-03 1.278189e+01 5.981293e-03 1.276993e+01 5.975697e-03 1.275799e+01 5.970107e-03 1.274605e+01 5.964522e-03 1.273413e+01 5.958943e-03 1.272222e+01 5.953368e-03 1.271032e+01 5.947799e-03 1.269843e+01 5.942235e-03 1.268655e+01 5.936677e-03 1.267468e+01 5.931123e-03 1.266282e+01 5.925575e-03 1.265098e+01 5.920032e-03 1.263914e+01 5.914493e-03 1.262732e+01 5.908961e-03 1.261551e+01 5.903433e-03 1.260371e+01 5.897911e-03 1.259192e+01 5.892394e-03 1.258014e+01 5.886881e-03 1.256837e+01 5.881374e-03 1.255661e+01 5.875872e-03 1.254486e+01 5.870376e-03 1.253313e+01 5.864884e-03 1.252141e+01 5.859398e-03 1.250969e+01 5.853917e-03 1.249799e+01 5.848441e-03 1.248630e+01 5.842970e-03 1.247462e+01 5.837504e-03 1.246295e+01 5.832043e-03 1.245129e+01 5.826587e-03 1.243964e+01 5.821137e-03 1.242801e+01 5.815691e-03 1.241638e+01 5.810251e-03 1.240476e+01 5.804816e-03 1.239316e+01 5.799386e-03 1.238157e+01 5.793960e-03 1.236998e+01 5.788540e-03 1.235841e+01 5.783125e-03 1.234685e+01 5.777715e-03 1.233530e+01 5.772311e-03 1.232376e+01 5.766911e-03 1.231223e+01 5.761516e-03 1.230072e+01 5.756126e-03 1.228921e+01 5.750742e-03 1.227771e+01 5.745362e-03 1.226623e+01 5.739988e-03 1.225475e+01 5.734618e-03 1.224329e+01 5.729253e-03 1.223184e+01 5.723894e-03 1.222039e+01 5.718539e-03 1.220896e+01 5.713190e-03 1.219754e+01 5.707846e-03 1.218613e+01 5.702506e-03 1.217473e+01 5.697172e-03 1.216334e+01 5.691842e-03 1.215196e+01 5.686518e-03 1.214060e+01 5.681198e-03 1.212924e+01 5.675884e-03 1.211789e+01 5.670574e-03 1.210656e+01 5.665269e-03 1.209523e+01 5.659970e-03 1.208392e+01 5.654675e-03 1.207261e+01 5.649385e-03 1.206132e+01 5.644100e-03 1.205004e+01 5.638821e-03 1.203876e+01 5.633546e-03 1.202750e+01 5.628276e-03 1.201625e+01 5.623010e-03 1.200501e+01 5.617750e-03 1.199378e+01 5.612495e-03 1.198256e+01 5.607245e-03 1.197135e+01 5.602000e-03 1.196015e+01 5.596759e-03 1.194896e+01 5.591524e-03 1.193779e+01 5.586293e-03 1.192662e+01 5.581067e-03 1.191546e+01 5.575846e-03 1.190432e+01 5.570631e-03 1.189318e+01 5.565419e-03 1.188205e+01 5.560213e-03 1.187094e+01 5.555012e-03 1.185983e+01 5.549815e-03 1.184874e+01 5.544623e-03 1.183766e+01 5.539437e-03 1.182658e+01 5.534254e-03 1.181552e+01 5.529078e-03 1.180447e+01 5.523905e-03 1.179342e+01 5.518738e-03 1.178239e+01 5.513575e-03 1.177137e+01 5.508418e-03 1.176036e+01 5.503265e-03 1.174936e+01 5.498117e-03 1.173836e+01 5.492973e-03 1.172738e+01 5.487835e-03 1.171641e+01 5.482701e-03 1.170545e+01 5.477572e-03 1.169450e+01 5.472448e-03 1.168356e+01 5.467329e-03 1.167263e+01 5.462214e-03 1.166171e+01 5.457105e-03 1.165080e+01 5.452000e-03 1.163991e+01 5.446900e-03 1.162902e+01 5.441804e-03 1.161814e+01 5.436714e-03 1.160727e+01 5.431628e-03 1.159641e+01 5.426547e-03 1.158556e+01 5.421470e-03 1.157473e+01 5.416399e-03 1.156390e+01 5.411332e-03 1.155308e+01 5.406270e-03 1.154227e+01 5.401213e-03 1.153148e+01 5.396160e-03 1.152069e+01 5.391112e-03 1.150991e+01 5.386069e-03 1.149914e+01 5.381030e-03 1.148839e+01 5.375997e-03 1.147764e+01 5.370968e-03 1.146690e+01 5.365943e-03 1.145618e+01 5.360924e-03 1.144546e+01 5.355909e-03 1.143475e+01 5.350898e-03 1.142406e+01 5.345893e-03 1.141337e+01 5.340892e-03 1.140269e+01 5.335896e-03 1.139203e+01 5.330904e-03 1.138137e+01 5.325917e-03 1.137072e+01 5.320935e-03 1.136008e+01 5.315958e-03 1.134946e+01 5.310985e-03 1.133884e+01 5.306017e-03 1.132823e+01 5.301053e-03 1.131764e+01 5.296094e-03 1.130705e+01 5.291140e-03 1.129647e+01 5.286190e-03 1.128590e+01 5.281245e-03 1.127535e+01 5.276305e-03 1.126480e+01 5.271369e-03 1.125426e+01 5.266438e-03 1.124373e+01 5.261511e-03 1.123322e+01 5.256589e-03 1.122271e+01 5.251672e-03 1.121221e+01 5.246759e-03 1.120172e+01 5.241851e-03 1.119124e+01 5.236947e-03 1.118077e+01 5.232048e-03 1.117031e+01 5.227154e-03 1.115986e+01 5.222264e-03 1.114942e+01 5.217379e-03 1.113899e+01 5.212498e-03 1.112857e+01 5.207622e-03 1.111816e+01 5.202751e-03 1.110776e+01 5.197884e-03 1.109737e+01 5.193021e-03 1.108699e+01 5.188163e-03 1.107662e+01 5.183310e-03 1.106626e+01 5.178461e-03 1.105591e+01 5.173617e-03 1.104556e+01 5.168777e-03 1.103523e+01 5.163942e-03 1.102491e+01 5.159111e-03 1.101459e+01 5.154285e-03 1.100429e+01 5.149464e-03 1.099400e+01 5.144646e-03 1.098371e+01 5.139834e-03 1.097344e+01 5.135025e-03 1.096317e+01 5.130222e-03 1.095292e+01 5.125423e-03 1.094267e+01 5.120628e-03 1.093243e+01 5.115838e-03 1.092221e+01 5.111052e-03 1.091199e+01 5.106271e-03 1.090178e+01 5.101494e-03 1.089158e+01 5.096722e-03 1.088140e+01 5.091954e-03 1.087122e+01 5.087191e-03 1.086105e+01 5.082432e-03 1.085089e+01 5.077678e-03 1.084074e+01 5.072928e-03 1.083059e+01 5.068182e-03 1.082046e+01 5.063441e-03 1.081034e+01 5.058704e-03 1.080023e+01 5.053972e-03 1.079012e+01 5.049245e-03 1.078003e+01 5.044521e-03 1.076995e+01 5.039802e-03 1.075987e+01 5.035087e-03 1.074981e+01 5.030377e-03 1.073975e+01 5.025672e-03 1.072970e+01 5.020970e-03 1.071967e+01 5.016273e-03 1.070964e+01 5.011581e-03 1.069962e+01 5.006893e-03 1.068961e+01 5.002209e-03 1.067961e+01 4.997530e-03 1.066962e+01 4.992854e-03 1.065964e+01 4.988184e-03 1.064967e+01 4.983518e-03 1.063971e+01 4.978856e-03 1.062975e+01 4.974198e-03 1.061981e+01 4.969545e-03 1.060987e+01 4.964896e-03 1.059995e+01 4.960252e-03 1.059003e+01 4.955612e-03 1.058013e+01 4.950976e-03 1.057023e+01 4.946344e-03 1.056034e+01 4.941717e-03 1.055046e+01 4.937094e-03 1.054059e+01 4.932476e-03 1.053073e+01 4.927862e-03 1.052088e+01 4.923252e-03 1.051104e+01 4.918647e-03 1.050121e+01 4.914045e-03 1.049138e+01 4.909448e-03 1.048157e+01 4.904856e-03 1.047176e+01 4.900267e-03 1.046197e+01 4.895683e-03 1.045218e+01 4.891104e-03 1.044240e+01 4.886528e-03 1.043264e+01 4.881957e-03 1.042288e+01 4.877390e-03 1.041313e+01 4.872827e-03 1.040338e+01 4.868269e-03 1.039365e+01 4.863715e-03 1.038393e+01 4.859165e-03 1.037422e+01 4.854619e-03 1.036451e+01 4.850078e-03 1.035482e+01 4.845541e-03 1.034513e+01 4.841008e-03 1.033545e+01 4.836480e-03 1.032578e+01 4.831955e-03 1.031612e+01 4.827436e-03 1.030647e+01 4.822920e-03 1.029683e+01 4.818408e-03 1.028720e+01 4.813900e-03 1.027758e+01 4.809397e-03 1.026796e+01 4.804898e-03 1.025836e+01 4.800403e-03 1.024876e+01 4.795913e-03 1.023917e+01 4.791426e-03 1.022960e+01 4.786944e-03 1.022003e+01 4.782466e-03 1.021047e+01 4.777992e-03 1.020091e+01 4.773523e-03 1.019137e+01 4.769057e-03 1.018184e+01 4.764596e-03 1.017231e+01 4.760139e-03 1.016280e+01 4.755686e-03 1.015329e+01 4.751237e-03 1.014379e+01 4.746792e-03 1.013430e+01 4.742352e-03 1.012482e+01 4.737916e-03 1.011535e+01 4.733484e-03 1.010589e+01 4.729056e-03 1.009643e+01 4.724632e-03 1.008699e+01 4.720212e-03 1.007755e+01 4.715797e-03 1.006813e+01 4.711385e-03 1.005871e+01 4.706978e-03 1.004930e+01 4.702575e-03 1.003990e+01 4.698175e-03 1.003051e+01 4.693781e-03 1.002112e+01 4.689389e-03 1.001175e+01 4.685003e-03 1.000238e+01 4.680620e-03 9.993026e+00 4.676241e-03 9.983678e+00 4.671867e-03 9.974339e+00 4.667497e-03 9.965008e+00 4.663131e-03 9.955686e+00 4.658768e-03 9.946373e+00 4.654410e-03 9.937068e+00 4.650056e-03 9.927773e+00 4.645706e-03 9.918486e+00 4.641360e-03 9.909207e+00 4.637018e-03 9.899938e+00 4.632681e-03 9.890676e+00 4.628347e-03 9.881424e+00 4.624018e-03 9.872180e+00 4.619692e-03 9.862946e+00 4.615370e-03 9.853719e+00 4.611053e-03 9.844501e+00 4.606739e-03 9.835292e+00 4.602430e-03 9.826092e+00 4.598124e-03 9.816899e+00 4.593823e-03 9.807716e+00 4.589526e-03 9.798541e+00 4.585233e-03 9.789375e+00 4.580943e-03 9.780217e+00 4.576658e-03 9.771069e+00 4.572377e-03 9.761928e+00 4.568099e-03 9.752796e+00 4.563826e-03 9.743672e+00 4.559556e-03 9.734558e+00 4.555292e-03 9.725451e+00 4.551030e-03 9.716353e+00 4.546773e-03 9.707264e+00 4.542519e-03 9.698184e+00 4.538270e-03 9.689112e+00 4.534025e-03 9.680048e+00 4.529783e-03 9.670992e+00 4.525546e-03 9.661945e+00 4.521312e-03 9.652907e+00 4.517083e-03 9.643877e+00 4.512857e-03 9.634855e+00 4.508635e-03 9.625842e+00 4.504418e-03 9.616838e+00 4.500204e-03 9.607841e+00 4.495994e-03 9.598854e+00 4.491788e-03 9.589874e+00 4.487587e-03 9.580903e+00 4.483389e-03 9.571941e+00 4.479195e-03 9.562986e+00 4.475005e-03 9.554041e+00 4.470818e-03 9.545103e+00 4.466636e-03 9.536175e+00 4.462458e-03 9.527253e+00 4.458283e-03 9.518341e+00 4.454113e-03 9.509438e+00 4.449946e-03 9.500542e+00 4.445783e-03 9.491654e+00 4.441625e-03 9.482775e+00 4.437469e-03 9.473904e+00 4.433318e-03 9.465042e+00 4.429171e-03 9.456187e+00 4.425028e-03 9.447342e+00 4.420888e-03 9.438504e+00 4.416753e-03 9.429674e+00 4.412621e-03 9.420854e+00 4.408493e-03 9.412041e+00 4.404369e-03 9.403236e+00 4.400249e-03 9.394440e+00 4.396133e-03 9.385652e+00 4.392020e-03 9.376871e+00 4.387912e-03 9.368100e+00 4.383807e-03 9.359336e+00 4.379706e-03 9.350581e+00 4.375609e-03 9.341834e+00 4.371516e-03 9.333095e+00 4.367427e-03 9.324364e+00 4.363341e-03 9.315641e+00 4.359259e-03 9.306927e+00 4.355181e-03 9.298221e+00 4.351107e-03 9.289522e+00 4.347037e-03 9.280832e+00 4.342970e-03 9.272151e+00 4.338908e-03 9.263477e+00 4.334849e-03 9.254811e+00 4.330794e-03 9.246154e+00 4.326743e-03 9.237504e+00 4.322695e-03 9.228863e+00 4.318651e-03 9.220230e+00 4.314611e-03 9.211604e+00 4.310575e-03 9.202988e+00 4.306543e-03 9.194378e+00 4.302514e-03 9.185778e+00 4.298489e-03 9.177184e+00 4.294468e-03 9.168599e+00 4.290451e-03 9.160023e+00 4.286437e-03 9.151454e+00 4.282427e-03 9.142893e+00 4.278421e-03 9.134340e+00 4.274419e-03 9.125795e+00 4.270420e-03 9.117258e+00 4.266425e-03 9.108729e+00 4.262435e-03 9.100208e+00 4.258447e-03 9.091696e+00 4.254464e-03 9.083191e+00 4.250484e-03 9.074694e+00 4.246508e-03 9.066205e+00 4.242535e-03 9.057724e+00 4.238566e-03 9.049251e+00 4.234601e-03 9.040785e+00 4.230640e-03 9.032328e+00 4.226682e-03 9.023878e+00 4.222728e-03 9.015437e+00 4.218778e-03 9.007004e+00 4.214832e-03 8.998578e+00 4.210889e-03 8.990160e+00 4.206950e-03 8.981750e+00 4.203015e-03 8.973348e+00 4.199083e-03 8.964953e+00 4.195155e-03 8.956567e+00 4.191230e-03 8.948189e+00 4.187309e-03 8.939817e+00 4.183393e-03 8.931455e+00 4.179479e-03 8.923100e+00 4.175569e-03 8.914753e+00 4.171663e-03 8.906413e+00 4.167761e-03 8.898082e+00 4.163862e-03 8.889758e+00 4.159967e-03 8.881442e+00 4.156075e-03 8.873134e+00 4.152187e-03 8.864833e+00 4.148303e-03 8.856541e+00 4.144423e-03 8.848255e+00 4.140546e-03 8.839978e+00 4.136672e-03 8.831709e+00 4.132803e-03 8.823447e+00 4.128937e-03 8.815193e+00 4.125074e-03 8.806947e+00 4.121215e-03 8.798708e+00 4.117360e-03 8.790477e+00 4.113508e-03 8.782254e+00 4.109660e-03 8.774038e+00 4.105816e-03 8.765831e+00 4.101975e-03 8.757630e+00 4.098138e-03 8.749438e+00 4.094304e-03 8.741254e+00 4.090474e-03 8.733076e+00 4.086648e-03 8.724907e+00 4.082825e-03 8.716745e+00 4.079005e-03 8.708591e+00 4.075190e-03 8.700444e+00 4.071377e-03 8.692306e+00 4.067569e-03 8.684174e+00 4.063764e-03 8.676050e+00 4.059962e-03 8.667934e+00 4.056164e-03 8.659825e+00 4.052370e-03 8.651725e+00 4.048579e-03 8.643631e+00 4.044792e-03 8.635546e+00 4.041008e-03 8.627467e+00 4.037227e-03 8.619396e+00 4.033451e-03 8.611334e+00 4.029678e-03 8.603278e+00 4.025908e-03 8.595230e+00 4.022142e-03 8.587190e+00 4.018379e-03 8.579156e+00 4.014621e-03 8.571131e+00 4.010865e-03 8.563113e+00 4.007113e-03 8.555102e+00 4.003365e-03 8.547099e+00 3.999619e-03 8.539104e+00 3.995878e-03 8.531116e+00 3.992140e-03 8.523135e+00 3.988406e-03 8.515162e+00 3.984674e-03 8.507196e+00 3.980947e-03 8.499238e+00 3.977223e-03 8.491287e+00 3.973502e-03 8.483344e+00 3.969785e-03 8.475409e+00 3.966072e-03 8.467480e+00 3.962362e-03 8.459559e+00 3.958655e-03 8.451646e+00 3.954952e-03 8.443739e+00 3.951252e-03 8.435841e+00 3.947556e-03 8.427949e+00 3.943863e-03 8.420065e+00 3.940174e-03 8.412189e+00 3.936488e-03 8.404319e+00 3.932805e-03 8.396457e+00 3.929127e-03 8.388602e+00 3.925451e-03 8.380755e+00 3.921779e-03 8.372915e+00 3.918110e-03 8.365083e+00 3.914445e-03 8.357258e+00 3.910783e-03 8.349440e+00 3.907125e-03 8.341629e+00 3.903470e-03 8.333826e+00 3.899818e-03 8.326030e+00 3.896170e-03 8.318241e+00 3.892525e-03 8.310460e+00 3.888884e-03 8.302686e+00 3.885246e-03 8.294919e+00 3.881611e-03 8.287159e+00 3.877980e-03 8.279407e+00 3.874353e-03 8.271662e+00 3.870728e-03 8.263924e+00 3.867107e-03 8.256193e+00 3.863490e-03 8.248470e+00 3.859876e-03 8.240754e+00 3.856265e-03 8.233045e+00 3.852658e-03 8.225343e+00 3.849054e-03 8.217649e+00 3.845453e-03 8.209961e+00 3.841856e-03 8.202281e+00 3.838262e-03 8.194609e+00 3.834671e-03 8.186942e+00 3.831084e-03 8.179284e+00 3.827500e-03 8.171633e+00 3.823920e-03 8.163988e+00 3.820342e-03 8.156351e+00 3.816769e-03 8.148722e+00 3.813198e-03 8.141098e+00 3.809631e-03 8.133483e+00 3.806067e-03 8.125875e+00 3.802507e-03 8.118273e+00 3.798950e-03 8.110679e+00 3.795396e-03 8.103091e+00 3.791846e-03 8.095510e+00 3.788298e-03 8.087938e+00 3.784755e-03 8.080372e+00 3.781214e-03 8.072813e+00 3.777677e-03 8.065261e+00 3.774143e-03 8.057716e+00 3.770612e-03 8.050179e+00 3.767085e-03 8.042648e+00 3.763561e-03 8.035125e+00 3.760040e-03 8.027608e+00 3.756523e-03 8.020099e+00 3.753009e-03 8.012596e+00 3.749498e-03 8.005100e+00 3.745991e-03 7.997612e+00 3.742486e-03 7.990130e+00 3.738986e-03 7.982656e+00 3.735488e-03 7.975189e+00 3.731993e-03 7.967728e+00 3.728502e-03 7.960275e+00 3.725014e-03 7.952828e+00 3.721530e-03 7.945388e+00 3.718049e-03 7.937956e+00 3.714571e-03 7.930530e+00 3.711096e-03 7.923111e+00 3.707624e-03 7.915699e+00 3.704156e-03 7.908295e+00 3.700691e-03 7.900897e+00 3.697229e-03 7.893506e+00 3.693770e-03 7.886122e+00 3.690315e-03 7.878745e+00 3.686863e-03 7.871374e+00 3.683414e-03 7.864011e+00 3.679968e-03 7.856655e+00 3.676526e-03 7.849305e+00 3.673086e-03 7.841962e+00 3.669650e-03 7.834626e+00 3.666217e-03 7.827297e+00 3.662788e-03 7.819975e+00 3.659361e-03 7.812660e+00 3.655938e-03 7.805351e+00 3.652518e-03 7.798049e+00 3.649101e-03 7.790755e+00 3.645688e-03 7.783467e+00 3.642277e-03 7.776186e+00 3.638870e-03 7.768911e+00 3.635466e-03 7.761644e+00 3.632065e-03 7.754383e+00 3.628667e-03 7.747129e+00 3.625273e-03 7.739882e+00 3.621882e-03 7.732642e+00 3.618494e-03 7.725408e+00 3.615109e-03 7.718181e+00 3.611727e-03 7.710961e+00 3.608348e-03 7.703748e+00 3.604973e-03 7.696541e+00 3.601600e-03 7.689342e+00 3.598231e-03 7.682148e+00 3.594865e-03 7.674962e+00 3.591502e-03 7.667782e+00 3.588143e-03 7.660609e+00 3.584786e-03 7.653443e+00 3.581433e-03 7.646284e+00 3.578082e-03 7.639131e+00 3.574735e-03 7.631985e+00 3.571391e-03 7.624846e+00 3.568050e-03 7.617712e+00 3.564712e-03 7.610587e+00 3.561378e-03 7.603467e+00 3.558046e-03 7.596354e+00 3.554718e-03 7.589248e+00 3.551393e-03 7.582149e+00 3.548070e-03 7.575056e+00 3.544751e-03 7.567970e+00 3.541435e-03 7.560890e+00 3.538122e-03 7.553817e+00 3.534813e-03 7.546751e+00 3.531506e-03 7.539691e+00 3.528202e-03 7.532638e+00 3.524902e-03 7.525591e+00 3.521604e-03 7.518552e+00 3.518310e-03 7.511518e+00 3.515019e-03 7.504491e+00 3.511731e-03 7.497471e+00 3.508446e-03 7.490458e+00 3.505164e-03 7.483451e+00 3.501885e-03 7.476450e+00 3.498609e-03 7.469456e+00 3.495336e-03 7.462469e+00 3.492066e-03 7.455488e+00 3.488799e-03 7.448514e+00 3.485536e-03 7.441546e+00 3.482275e-03 7.434585e+00 3.479018e-03 7.427630e+00 3.475763e-03 7.420681e+00 3.472512e-03 7.413740e+00 3.469263e-03 7.406805e+00 3.466018e-03 7.399876e+00 3.462776e-03 7.392953e+00 3.459536e-03 7.386037e+00 3.456300e-03 7.379128e+00 3.453067e-03 7.372225e+00 3.449837e-03 7.365329e+00 3.446609e-03 7.358439e+00 3.443385e-03 7.351555e+00 3.440164e-03 7.344678e+00 3.436946e-03 7.337808e+00 3.433731e-03 7.330943e+00 3.430519e-03 7.324085e+00 3.427309e-03 7.317234e+00 3.424103e-03 7.310389e+00 3.420900e-03 7.303550e+00 3.417700e-03 7.296718e+00 3.414503e-03 7.289892e+00 3.411309e-03 7.283073e+00 3.408118e-03 7.276260e+00 3.404930e-03 7.269453e+00 3.401744e-03 7.262653e+00 3.398562e-03 7.255859e+00 3.395383e-03 7.249072e+00 3.392207e-03 7.242290e+00 3.389033e-03 7.235515e+00 3.385863e-03 7.228747e+00 3.382696e-03 7.221984e+00 3.379531e-03 7.215229e+00 3.376370e-03 7.208479e+00 3.373211e-03 7.201735e+00 3.370056e-03 7.194999e+00 3.366903e-03 7.188268e+00 3.363754e-03 7.181544e+00 3.360607e-03 7.174826e+00 3.357463e-03 7.168114e+00 3.354323e-03 7.161408e+00 3.351185e-03 7.154709e+00 3.348050e-03 7.148016e+00 3.344918e-03 7.141329e+00 3.341789e-03 7.134649e+00 3.338663e-03 7.127975e+00 3.335539e-03 7.121307e+00 3.332419e-03 7.114645e+00 3.329302e-03 7.107990e+00 3.326187e-03 7.101340e+00 3.323076e-03 7.094697e+00 3.319967e-03 7.088060e+00 3.316862e-03 7.081430e+00 3.313759e-03 7.074805e+00 3.310659e-03 7.068187e+00 3.307562e-03 7.061575e+00 3.304468e-03 7.054969e+00 3.301376e-03 7.048370e+00 3.298288e-03 7.041776e+00 3.295203e-03 7.035189e+00 3.292120e-03 7.028608e+00 3.289041e-03 7.022033e+00 3.285964e-03 7.015464e+00 3.282890e-03 7.008901e+00 3.279819e-03 7.002345e+00 3.276751e-03 6.995794e+00 3.273685e-03 6.989250e+00 3.270623e-03 6.982712e+00 3.267563e-03 6.976180e+00 3.264507e-03 6.969654e+00 3.261453e-03 6.963134e+00 3.258402e-03 6.956620e+00 3.255354e-03 6.950112e+00 3.252309e-03 6.943611e+00 3.249266e-03 6.937115e+00 3.246227e-03 6.930626e+00 3.243190e-03 6.924142e+00 3.240156e-03 6.917665e+00 3.237125e-03 6.911194e+00 3.234097e-03 6.904729e+00 3.231071e-03 6.898270e+00 3.228049e-03 6.891817e+00 3.225029e-03 6.885370e+00 3.222012e-03 6.878929e+00 3.218998e-03 6.872494e+00 3.215987e-03 6.866065e+00 3.212978e-03 6.859642e+00 3.209973e-03 6.853225e+00 3.206970e-03 6.846814e+00 3.203970e-03 6.840409e+00 3.200973e-03 6.834010e+00 3.197978e-03 6.827617e+00 3.194987e-03 6.821230e+00 3.191998e-03 6.814849e+00 3.189012e-03 6.808474e+00 3.186029e-03 6.802105e+00 3.183048e-03 6.795742e+00 3.180071e-03 6.789384e+00 3.177096e-03 6.783033e+00 3.174124e-03 6.776688e+00 3.171155e-03 6.770349e+00 3.168188e-03 6.764015e+00 3.165224e-03 6.757688e+00 3.162263e-03 6.751366e+00 3.159305e-03 6.745050e+00 3.156350e-03 6.738741e+00 3.153397e-03 6.732437e+00 3.150447e-03 6.726139e+00 3.147500e-03 6.719847e+00 3.144556e-03 6.713561e+00 3.141614e-03 6.707281e+00 3.138675e-03 6.701006e+00 3.135739e-03 6.694737e+00 3.132806e-03 6.688475e+00 3.129875e-03 6.682218e+00 3.126947e-03 6.675967e+00 3.124022e-03 6.669722e+00 3.121100e-03 6.663483e+00 3.118180e-03 6.657249e+00 3.115263e-03 6.651021e+00 3.112349e-03 6.644800e+00 3.109437e-03 6.638584e+00 3.106529e-03 6.632374e+00 3.103623e-03 6.626169e+00 3.100719e-03 6.619971e+00 3.097819e-03 6.613778e+00 3.094921e-03 6.607591e+00 3.092026e-03 6.601410e+00 3.089133e-03 6.595235e+00 3.086244e-03 6.589065e+00 3.083356e-03 6.582901e+00 3.080472e-03 6.576743e+00 3.077590e-03 6.570591e+00 3.074711e-03 6.564444e+00 3.071835e-03 6.558303e+00 3.068961e-03 6.552168e+00 3.066091e-03 6.546039e+00 3.063222e-03 6.539916e+00 3.060357e-03 6.533798e+00 3.057494e-03 6.527686e+00 3.054634e-03 6.521579e+00 3.051776e-03 6.515479e+00 3.048921e-03 6.509384e+00 3.046069e-03 6.503294e+00 3.043220e-03 6.497211e+00 3.040373e-03 6.491133e+00 3.037529e-03 6.485061e+00 3.034687e-03 6.478994e+00 3.031848e-03 6.472933e+00 3.029012e-03 6.466878e+00 3.026179e-03 6.460828e+00 3.023348e-03 6.454784e+00 3.020520e-03 6.448746e+00 3.017694e-03 6.442714e+00 3.014871e-03 6.436687e+00 3.012051e-03 6.430665e+00 3.009233e-03 6.424650e+00 3.006418e-03 6.418640e+00 3.003606e-03 6.412635e+00 3.000796e-03 6.406637e+00 2.997989e-03 6.400643e+00 2.995184e-03 6.394656e+00 2.992383e-03 6.388674e+00 2.989583e-03 6.382698e+00 2.986787e-03 6.376727e+00 2.983992e-03 6.370761e+00 2.981201e-03 6.364802e+00 2.978412e-03 6.358848e+00 2.975626e-03 6.352900e+00 2.972843e-03 6.346957e+00 2.970062e-03 6.341019e+00 2.967283e-03 6.335087e+00 2.964507e-03 6.329161e+00 2.961734e-03 6.323240e+00 2.958964e-03 6.317325e+00 2.956196e-03 6.311416e+00 2.953430e-03 6.305511e+00 2.950667e-03 6.299613e+00 2.947907e-03 6.293720e+00 2.945150e-03 6.287832e+00 2.942394e-03 6.281950e+00 2.939642e-03 6.276074e+00 2.936892e-03 6.270203e+00 2.934145e-03 6.264337e+00 2.931400e-03 6.258477e+00 2.928658e-03 6.252623e+00 2.925918e-03 6.246774e+00 2.923181e-03 6.240930e+00 2.920446e-03 6.235092e+00 2.917714e-03 6.229259e+00 2.914985e-03 6.223432e+00 2.912258e-03 6.217610e+00 2.909534e-03 6.211794e+00 2.906812e-03 6.205983e+00 2.904093e-03 6.200177e+00 2.901376e-03 6.194377e+00 2.898662e-03 6.188583e+00 2.895950e-03 6.182794e+00 2.893241e-03 6.177010e+00 2.890535e-03 6.171231e+00 2.887831e-03 6.165458e+00 2.885129e-03 6.159691e+00 2.882431e-03 6.153929e+00 2.879734e-03 6.148172e+00 2.877040e-03 6.142420e+00 2.874349e-03 6.136674e+00 2.871660e-03 6.130934e+00 2.868974e-03 6.125198e+00 2.866290e-03 6.119469e+00 2.863609e-03 6.113744e+00 2.860930e-03 6.108025e+00 2.858253e-03 6.102311e+00 2.855580e-03 6.096602e+00 2.852908e-03 6.090899e+00 2.850240e-03 6.085202e+00 2.847573e-03 6.079509e+00 2.844909e-03 6.073822e+00 2.842248e-03 6.068140e+00 2.839589e-03 6.062464e+00 2.836933e-03 6.056792e+00 2.834279e-03 6.051126e+00 2.831628e-03 6.045466e+00 2.828979e-03 6.039811e+00 2.826333e-03 6.034161e+00 2.823689e-03 6.028516e+00 2.821047e-03 6.022876e+00 2.818408e-03 6.017242e+00 2.815772e-03 6.011613e+00 2.813138e-03 6.005990e+00 2.810506e-03 6.000371e+00 2.807877e-03 5.994758e+00 2.805250e-03 5.989151e+00 2.802626e-03 5.983548e+00 2.800004e-03 5.977950e+00 2.797385e-03 5.972358e+00 2.794768e-03 5.966771e+00 2.792154e-03 5.961190e+00 2.789542e-03 5.955613e+00 2.786932e-03 5.950042e+00 2.784325e-03 5.944476e+00 2.781720e-03 5.938915e+00 2.779118e-03 5.933359e+00 2.776518e-03 5.927809e+00 2.773921e-03 5.922264e+00 2.771326e-03 5.916724e+00 2.768734e-03 5.911189e+00 2.766144e-03 5.905659e+00 2.763556e-03 5.900135e+00 2.760971e-03 5.894615e+00 2.758388e-03 5.889101e+00 2.755808e-03 5.883592e+00 2.753230e-03 5.878088e+00 2.750654e-03 5.872589e+00 2.748081e-03 5.867095e+00 2.745510e-03 5.861607e+00 2.742942e-03 5.856124e+00 2.740376e-03 5.850646e+00 2.737813e-03 5.845172e+00 2.735252e-03 5.839705e+00 2.732693e-03 5.834242e+00 2.730136e-03 5.828784e+00 2.727583e-03 5.823331e+00 2.725031e-03 5.817884e+00 2.722482e-03 5.812441e+00 2.719935e-03 5.807004e+00 2.717391e-03 5.801572e+00 2.714849e-03 5.796145e+00 2.712309e-03 5.790723e+00 2.709772e-03 5.785306e+00 2.707237e-03 5.779894e+00 2.704704e-03 5.774487e+00 2.702174e-03 5.769085e+00 2.699646e-03 5.763689e+00 2.697121e-03 5.758296e+00 2.694598e-03 5.752910e+00 2.692077e-03 5.747529e+00 2.689559e-03 5.742152e+00 2.687043e-03 5.736780e+00 2.684529e-03 5.731413e+00 2.682018e-03 5.726052e+00 2.679509e-03 5.720695e+00 2.677002e-03 5.715344e+00 2.674498e-03 5.709998e+00 2.671996e-03 5.704656e+00 2.669497e-03 5.699319e+00 2.667000e-03 5.693988e+00 2.664505e-03 5.688662e+00 2.662012e-03 5.683340e+00 2.659522e-03 5.678023e+00 2.657034e-03 5.672712e+00 2.654548e-03 5.667405e+00 2.652065e-03 5.662104e+00 2.649584e-03 5.656807e+00 2.647106e-03 5.651515e+00 2.644629e-03 5.646228e+00 2.642155e-03 5.640946e+00 2.639684e-03 5.635670e+00 2.637214e-03 5.630398e+00 2.634747e-03 5.625131e+00 2.632283e-03 5.619869e+00 2.629820e-03 5.614612e+00 2.627360e-03 5.609359e+00 2.624902e-03 5.604112e+00 2.622447e-03 5.598869e+00 2.619994e-03 5.593632e+00 2.617543e-03 5.588399e+00 2.615094e-03 5.583171e+00 2.612648e-03 5.577949e+00 2.610204e-03 5.572731e+00 2.607762e-03 5.567518e+00 2.605323e-03 5.562309e+00 2.602885e-03 5.557106e+00 2.600451e-03 5.551908e+00 2.598018e-03 5.546714e+00 2.595588e-03 5.541525e+00 2.593159e-03 5.536341e+00 2.590734e-03 5.531162e+00 2.588310e-03 5.525988e+00 2.585889e-03 5.520819e+00 2.583470e-03 5.515654e+00 2.581053e-03 5.510494e+00 2.578639e-03 5.505340e+00 2.576226e-03 5.500189e+00 2.573817e-03 5.495044e+00 2.571409e-03 5.489904e+00 2.569003e-03 5.484768e+00 2.566600e-03 5.479638e+00 2.564199e-03 5.474512e+00 2.561800e-03 5.469390e+00 2.559404e-03 5.464274e+00 2.557010e-03 5.459162e+00 2.554618e-03 5.454055e+00 2.552228e-03 5.448953e+00 2.549841e-03 5.443856e+00 2.547455e-03 5.438764e+00 2.545072e-03 5.433676e+00 2.542691e-03 5.428593e+00 2.540313e-03 5.423514e+00 2.537936e-03 5.418441e+00 2.535562e-03 5.413372e+00 2.533190e-03 5.408308e+00 2.530821e-03 5.403249e+00 2.528453e-03 5.398194e+00 2.526088e-03 5.393145e+00 2.523725e-03 5.388100e+00 2.521364e-03 5.383059e+00 2.519005e-03 5.378024e+00 2.516649e-03 5.372993e+00 2.514295e-03 5.367966e+00 2.511943e-03 5.362945e+00 2.509593e-03 5.357928e+00 2.507245e-03 5.352916e+00 2.504900e-03 5.347908e+00 2.502556e-03 5.342906e+00 2.500215e-03 5.337907e+00 2.497877e-03 5.332914e+00 2.495540e-03 5.327925e+00 2.493205e-03 5.322941e+00 2.490873e-03 5.317962e+00 2.488543e-03 5.312987e+00 2.486215e-03 5.308017e+00 2.483889e-03 5.303051e+00 2.481566e-03 5.298090e+00 2.479244e-03 5.293135e+00 2.476925e-03 5.288183e+00 2.474608e-03 5.283236e+00 2.472293e-03 5.278294e+00 2.469980e-03 5.273356e+00 2.467670e-03 5.268423e+00 2.465361e-03 5.263494e+00 2.463055e-03 5.258571e+00 2.460751e-03 5.253652e+00 2.458449e-03 5.248737e+00 2.456149e-03 5.243827e+00 2.453852e-03 5.238922e+00 2.451556e-03 5.234021e+00 2.449263e-03 5.229125e+00 2.446972e-03 5.224233e+00 2.444682e-03 5.219346e+00 2.442396e-03 5.214463e+00 2.440111e-03 5.209586e+00 2.437828e-03 5.204712e+00 2.435548e-03 5.199843e+00 2.433269e-03 5.194979e+00 2.430993e-03 5.190119e+00 2.428719e-03 5.185264e+00 2.426447e-03 5.180414e+00 2.424177e-03 5.175568e+00 2.421909e-03 5.170726e+00 2.419644e-03 5.165889e+00 2.417380e-03 5.161057e+00 2.415119e-03 5.156229e+00 2.412860e-03 5.151405e+00 2.410603e-03 5.146586e+00 2.408348e-03 5.141771e+00 2.406095e-03 5.136961e+00 2.403844e-03 5.132156e+00 2.401595e-03 5.127355e+00 2.399348e-03 5.122559e+00 2.397104e-03 5.117767e+00 2.394862e-03 5.112979e+00 2.392621e-03 5.108196e+00 2.390383e-03 5.103418e+00 2.388147e-03 5.098644e+00 2.385913e-03 5.093874e+00 2.383681e-03 5.089109e+00 2.381451e-03 5.084348e+00 2.379223e-03 5.079592e+00 2.376998e-03 5.074841e+00 2.374774e-03 5.070093e+00 2.372553e-03 5.065350e+00 2.370333e-03 5.060612e+00 2.368116e-03 5.055878e+00 2.365900e-03 5.051148e+00 2.363687e-03 5.046423e+00 2.361476e-03 5.041702e+00 2.359267e-03 5.036986e+00 2.357060e-03 5.032274e+00 2.354855e-03 5.027566e+00 2.352652e-03 5.022863e+00 2.350451e-03 5.018165e+00 2.348253e-03 5.013470e+00 2.346056e-03 5.008780e+00 2.343861e-03 5.004095e+00 2.341669e-03 4.999413e+00 2.339478e-03 4.994737e+00 2.337290e-03 4.990065e+00 2.335103e-03 4.985396e+00 2.332919e-03 4.980733e+00 2.330736e-03 4.976074e+00 2.328556e-03 4.971418e+00 2.326378e-03 4.966768e+00 2.324202e-03 4.962122e+00 2.322027e-03 4.957480e+00 2.319855e-03 4.952842e+00 2.317685e-03 4.948209e+00 2.315517e-03 4.943580e+00 2.313351e-03 4.938956e+00 2.311187e-03 4.934336e+00 2.309025e-03 4.929719e+00 2.306865e-03 4.925108e+00 2.304707e-03 4.920501e+00 2.302551e-03 4.915898e+00 2.300397e-03 4.911299e+00 2.298245e-03 4.906705e+00 2.296095e-03 4.902115e+00 2.293947e-03 4.897529e+00 2.291801e-03 4.892948e+00 2.289657e-03 4.888371e+00 2.287515e-03 4.883798e+00 2.285375e-03 4.879229e+00 2.283238e-03 4.874665e+00 2.281102e-03 4.870105e+00 2.278968e-03 4.865549e+00 2.276836e-03 4.860997e+00 2.274706e-03 4.856450e+00 2.272578e-03 4.851907e+00 2.270452e-03 4.847368e+00 2.268328e-03 4.842834e+00 2.266207e-03 4.838303e+00 2.264086e-03 4.833777e+00 2.261969e-03 4.829256e+00 2.259853e-03 4.824738e+00 2.257739e-03 4.820224e+00 2.255626e-03 4.815715e+00 2.253516e-03 4.811211e+00 2.251408e-03 4.806710e+00 2.249302e-03 4.802213e+00 2.247198e-03 4.797721e+00 2.245096e-03 4.793233e+00 2.242996e-03 4.788749e+00 2.240897e-03 4.784269e+00 2.238801e-03 4.779794e+00 2.236707e-03 4.775322e+00 2.234614e-03 4.770855e+00 2.232524e-03 ''') ImportString(u'wave(numeric)',''' 4.108090e+00 4.236760e+00 4.283090e+00 4.329410e+00 4.370590e+00 4.411770e+00 4.452940e+00 4.494120e+00 4.545590e+00 4.597060e+00 4.638240e+00 4.694850e+00 4.751470e+00 4.792650e+00 4.838970e+00 4.885290e+00 4.926470e+00 4.998530e+00 5.070590e+00 5.111760e+00 5.163240e+00 5.214710e+00 5.266180e+00 5.317650e+00 5.358820e+00 5.400000e+00 5.441180e+00 5.482350e+00 5.523530e+00 5.564710e+00 5.605880e+00 5.647060e+00 5.688240e+00 5.729410e+00 5.780880e+00 5.832350e+00 5.873530e+00 5.930150e+00 5.986760e+00 6.027940e+00 6.069120e+00 6.110290e+00 6.151470e+00 6.192650e+00 6.233820e+00 6.275000e+00 6.316180e+00 6.372790e+00 6.429410e+00 6.491180e+00 6.568380e+00 6.625000e+00 6.666180e+00 6.707350e+00 6.748530e+00 6.789710e+00 6.830880e+00 6.882350e+00 6.933820e+00 6.975000e+00 7.016180e+00 7.057350e+00 7.098530e+00 7.139710e+00 7.180880e+00 7.242650e+00 7.304410e+00 7.345590e+00 7.397060e+00 7.448530e+00 7.489710e+00 7.530880e+00 7.572060e+00 7.649260e+00 7.736760e+00 7.788240e+00 7.829410e+00 7.870590e+00 7.922060e+00 7.973530e+00 8.014710e+00 8.055880e+00 8.097060e+00 8.138240e+00 8.179410e+00 8.220590e+00 8.272060e+00 8.323530e+00 8.364710e+00 8.405880e+00 8.447060e+00 8.488240e+00 8.534560e+00 8.596320e+00 8.652940e+00 8.694120e+00 8.735290e+00 8.776470e+00 8.817650e+00 8.858820e+00 8.900000e+00 8.941180e+00 8.982350e+00 9.023530e+00 9.064710e+00 9.131620e+00 9.198530e+00 9.239710e+00 9.280880e+00 9.322060e+00 9.373530e+00 9.425000e+00 9.476470e+00 9.527940e+00 9.569120e+00 9.610290e+00 9.666910e+00 9.723530e+00 9.764710e+00 9.805880e+00 9.857350e+00 9.908820e+00 9.970590e+00 1.004780e+01 1.010440e+01 1.014560e+01 1.018680e+01 1.022790e+01 1.026910e+01 1.031030e+01 1.036180e+01 1.041320e+01 1.045440e+01 1.049560e+01 1.055220e+01 1.060880e+01 1.065000e+01 1.069120e+01 1.073240e+01 1.077350e+01 1.081470e+01 1.085590e+01 1.089710e+01 1.093820e+01 1.097940e+01 1.102060e+01 1.106180e+01 1.110290e+01 1.114410e+01 1.118530e+01 1.122650e+01 1.126760e+01 1.130880e+01 1.137060e+01 1.143240e+01 1.148380e+01 1.155070e+01 1.160740e+01 1.164850e+01 1.170000e+01 1.175150e+01 1.179260e+01 1.183380e+01 1.187500e+01 1.191620e+01 1.195740e+01 1.201400e+01 1.207060e+01 1.211180e+01 1.216320e+01 1.221470e+01 1.225590e+01 1.229710e+01 1.234340e+01 1.238970e+01 1.243090e+01 1.248240e+01 1.253380e+01 1.257500e+01 1.261620e+01 1.265740e+01 1.269850e+01 1.273970e+01 1.278090e+01 1.282210e+01 1.286320e+01 1.292500e+01 1.298680e+01 1.305370e+01 1.314630e+01 1.321320e+01 1.325440e+01 1.331100e+01 1.336760e+01 1.341910e+01 1.347060e+01 1.351180e+01 1.355290e+01 1.359410e+01 1.363530e+01 1.369190e+01 1.375880e+01 1.382570e+01 1.388240e+01 1.392350e+01 1.396470e+01 1.400590e+01 1.404710e+01 1.411400e+01 1.418090e+01 1.422210e+01 1.426320e+01 1.430440e+01 1.434560e+01 1.439710e+01 1.446910e+01 1.453600e+01 1.458240e+01 1.462350e+01 1.466470e+01 1.470590e+01 1.476250e+01 1.481910e+01 1.486030e+01 1.490150e+01 1.494260e+01 1.498380e+01 1.502500e+01 1.506620e+01 1.510740e+01 1.514850e+01 1.522060e+01 1.530290e+01 1.536990e+01 1.542650e+01 1.548310e+01 1.553970e+01 1.560660e+01 1.568900e+01 1.574560e+01 1.578680e+01 1.584340e+01 1.590000e+01 1.594120e+01 1.598240e+01 1.602350e+01 1.606470e+01 1.610590e+01 1.614710e+01 1.618820e+01 1.622940e+01 1.627060e+01 1.631180e+01 1.636320e+01 1.641470e+01 1.645590e+01 1.651250e+01 1.656910e+01 1.661030e+01 1.665150e+01 1.670290e+01 1.675440e+01 1.680590e+01 1.685740e+01 1.689850e+01 1.693970e+01 1.699120e+01 1.705810e+01 1.711470e+01 1.715590e+01 1.719710e+01 1.725370e+01 1.731030e+01 1.735150e+01 1.739260e+01 1.743380e+01 1.749040e+01 1.754710e+01 1.759850e+01 1.765000e+01 1.769120e+01 1.773240e+01 1.777350e+01 1.781470e+01 1.785590e+01 1.791760e+01 1.797940e+01 1.802060e+01 1.806180e+01 1.811320e+01 1.816470e+01 1.820590e+01 1.826250e+01 1.831910e+01 1.836030e+01 1.840150e+01 1.845290e+01 1.850440e+01 1.854560e+01 1.860220e+01 1.865880e+01 1.870000e+01 1.875660e+01 1.881320e+01 1.885440e+01 1.892130e+01 1.898820e+01 1.902940e+01 1.907060e+01 1.912210e+01 1.917350e+01 1.921470e+01 1.925590e+01 1.929710e+01 1.933820e+01 1.937940e+01 1.942060e+01 1.946180e+01 1.950290e+01 1.954410e+01 1.958530e+01 1.962650e+01 1.968310e+01 1.973970e+01 1.978090e+01 1.982210e+01 1.986320e+01 1.990440e+01 1.994560e+01 1.998680e+01 2.002790e+01 2.006910e+01 2.011030e+01 2.015150e+01 2.019260e+01 2.023380e+01 2.027500e+01 2.031620e+01 2.035740e+01 2.039850e+01 2.043970e+01 2.048090e+01 2.052210e+01 2.056320e+01 2.060440e+01 2.064560e+01 2.068680e+01 2.077940e+01 2.087210e+01 2.091320e+01 2.095440e+01 2.099560e+01 2.103680e+01 2.107790e+01 2.111910e+01 2.116030e+01 2.120150e+01 2.124260e+01 2.128380e+01 2.132500e+01 2.136620e+01 2.140740e+01 2.144850e+01 2.148970e+01 2.153090e+01 2.157210e+01 2.161320e+01 2.165440e+01 2.169560e+01 2.173680e+01 2.177790e+01 2.181910e+01 2.186030e+01 2.190150e+01 2.194260e+01 2.198380e+01 2.202500e+01 2.206620e+01 2.210740e+01 2.214850e+01 2.218970e+01 2.223090e+01 2.227210e+01 2.231320e+01 2.235440e+01 2.239560e+01 2.243680e+01 2.247790e+01 2.251910e+01 2.256030e+01 2.260150e+01 2.264260e+01 2.268380e+01 2.276100e+01 2.283820e+01 2.287940e+01 2.294120e+01 2.300290e+01 2.304410e+01 2.308530e+01 2.312650e+01 2.316760e+01 2.320880e+01 2.325000e+01 2.329120e+01 2.333240e+01 2.338900e+01 2.344560e+01 2.348680e+01 2.352790e+01 2.356910e+01 2.361030e+01 2.365150e+01 2.369260e+01 2.373380e+01 2.377500e+01 2.381620e+01 2.385740e+01 2.389850e+01 2.393970e+01 2.398090e+01 2.404260e+01 2.410440e+01 2.414560e+01 2.418680e+01 2.422790e+01 2.428970e+01 2.435150e+01 2.439260e+01 2.443380e+01 2.447500e+01 2.451620e+01 2.455740e+01 2.459850e+01 2.463970e+01 2.469120e+01 2.474260e+01 2.478380e+01 2.482500e+01 2.486620e+01 2.490740e+01 2.496910e+01 2.503090e+01 2.507210e+01 2.511320e+01 2.515440e+01 2.519560e+01 2.523680e+01 2.527790e+01 2.531910e+01 2.536030e+01 2.540150e+01 2.544260e+01 2.548380e+01 2.552500e+01 2.556620e+01 2.560740e+01 2.564850e+01 2.568970e+01 2.573090e+01 2.577210e+01 2.583900e+01 2.590590e+01 2.594710e+01 2.598820e+01 2.605000e+01 2.611180e+01 2.616840e+01 2.622500e+01 2.626620e+01 2.630740e+01 2.634850e+01 2.638970e+01 2.643090e+01 2.647210e+01 2.651320e+01 2.655440e+01 2.659560e+01 2.665220e+01 2.670880e+01 2.675000e+01 2.681180e+01 2.687350e+01 2.691470e+01 2.695590e+01 2.701760e+01 2.710000e+01 2.718240e+01 2.726470e+01 2.737280e+01 2.748090e+01 2.756320e+01 2.767130e+01 2.777940e+01 2.786180e+01 2.794410e+01 2.802650e+01 2.810880e+01 2.823240e+01 2.835590e+01 2.843820e+01 2.852060e+01 2.860290e+01 2.868530e+01 2.878820e+01 2.890150e+01 2.900960e+01 2.910740e+01 2.918970e+01 2.927210e+01 2.935440e+01 2.943680e+01 2.951910e+01 2.960150e+01 2.968380e+01 2.976620e+01 2.984850e+01 2.993090e+01 3.001320e+01 3.009560e+01 3.017790e+01 3.026030e+01 3.037870e+01 3.049710e+01 3.057940e+01 3.066180e+01 3.074410e+01 3.082650e+01 3.090880e+01 3.099120e+01 3.107350e+01 3.117130e+01 3.130510e+01 3.142350e+01 3.150590e+01 3.158820e+01 3.167060e+01 3.175290e+01 3.183530e+01 3.191760e+01 3.200000e+01 3.208240e+01 3.216470e+01 3.224710e+01 3.232940e+01 3.241180e+01 3.249410e+01 3.257650e+01 3.267430e+01 3.277210e+01 3.285440e+01 3.293680e+01 3.301910e+01 3.310150e+01 3.318380e+01 3.326620e+01 3.334850e+01 3.344120e+01 3.353380e+01 3.363680e+01 3.373970e+01 3.382210e+01 3.390440e+01 3.400740e+01 3.411030e+01 3.419260e+01 3.427500e+01 3.435740e+01 3.445510e+01 3.455290e+01 3.463530e+01 3.471760e+01 3.480000e+01 3.488240e+01 3.499040e+01 3.509850e+01 3.520150e+01 3.530440e+01 3.538680e+01 3.546910e+01 3.555150e+01 3.563380e+01 3.571620e+01 3.582940e+01 3.594260e+01 3.602500e+01 3.610740e+01 3.620510e+01 3.630290e+01 3.638530e+01 3.646760e+01 3.655000e+01 3.663240e+01 3.671470e+01 3.679710e+01 3.690000e+01 3.700290e+01 3.708530e+01 3.718310e+01 3.728090e+01 3.736320e+01 3.744560e+01 3.752790e+01 3.761030e+01 3.769260e+01 3.777500e+01 3.785740e+01 3.793970e+01 3.802210e+01 3.810440e+01 ''') ImportString(u'wave_090(numeric)',''' 4.108090e+00 4.236760e+00 4.283090e+00 4.329410e+00 4.370590e+00 4.411770e+00 4.452940e+00 4.494120e+00 4.545590e+00 4.597060e+00 4.638240e+00 4.694850e+00 4.751470e+00 4.792650e+00 4.838970e+00 4.885290e+00 4.926470e+00 4.998530e+00 5.070590e+00 5.111760e+00 5.163240e+00 5.214710e+00 5.266180e+00 5.317650e+00 5.358820e+00 5.400000e+00 5.441180e+00 5.482350e+00 5.523530e+00 5.564710e+00 5.605880e+00 5.647060e+00 5.688240e+00 5.729410e+00 5.780880e+00 5.832350e+00 5.873530e+00 5.930150e+00 5.986760e+00 6.027940e+00 6.069120e+00 6.110290e+00 6.151470e+00 6.192650e+00 6.233820e+00 6.275000e+00 6.316180e+00 6.372790e+00 6.429410e+00 6.491180e+00 6.568380e+00 6.625000e+00 6.666180e+00 6.707350e+00 6.748530e+00 6.789710e+00 6.830880e+00 6.882350e+00 6.933820e+00 6.975000e+00 7.016180e+00 7.057350e+00 7.098530e+00 7.139710e+00 7.180880e+00 7.242650e+00 7.304410e+00 7.345590e+00 7.397060e+00 7.448530e+00 7.489710e+00 7.530880e+00 7.572060e+00 7.649260e+00 7.736760e+00 7.788240e+00 7.829410e+00 7.870590e+00 7.922060e+00 7.973530e+00 8.014710e+00 8.055880e+00 8.097060e+00 8.138240e+00 8.179410e+00 8.220590e+00 8.272060e+00 8.323530e+00 8.364710e+00 8.405880e+00 8.447060e+00 8.488240e+00 8.534560e+00 8.596320e+00 8.652940e+00 8.694120e+00 8.735290e+00 8.776470e+00 8.817650e+00 8.858820e+00 8.900000e+00 8.941180e+00 8.982350e+00 9.023530e+00 9.064710e+00 9.131620e+00 9.198530e+00 9.239710e+00 9.280880e+00 9.322060e+00 9.373530e+00 9.425000e+00 9.476470e+00 9.527940e+00 9.569120e+00 9.610290e+00 9.666910e+00 9.723530e+00 9.764710e+00 9.805880e+00 9.857350e+00 9.908820e+00 9.970590e+00 1.004780e+01 1.010440e+01 1.014560e+01 1.018680e+01 1.022790e+01 1.026910e+01 1.031030e+01 1.036180e+01 1.041320e+01 1.045440e+01 1.049560e+01 1.055220e+01 1.060880e+01 1.065000e+01 1.069120e+01 1.073240e+01 1.077350e+01 1.081470e+01 1.085590e+01 1.089710e+01 1.093820e+01 1.097940e+01 1.102060e+01 1.106180e+01 1.110290e+01 1.114410e+01 1.118530e+01 1.122650e+01 1.126760e+01 1.130880e+01 1.137060e+01 1.143240e+01 1.148380e+01 1.155070e+01 1.160740e+01 1.164850e+01 1.170000e+01 1.175150e+01 1.179260e+01 1.183380e+01 1.187500e+01 1.191620e+01 1.195740e+01 1.201400e+01 1.207060e+01 1.211180e+01 1.216320e+01 1.221470e+01 1.225590e+01 1.229710e+01 1.234340e+01 1.238970e+01 1.243090e+01 1.248240e+01 1.253380e+01 1.257500e+01 1.261620e+01 1.265740e+01 1.269850e+01 1.273970e+01 1.278090e+01 1.282210e+01 1.286320e+01 1.292500e+01 1.298680e+01 1.305370e+01 1.314630e+01 1.321320e+01 1.325440e+01 1.331100e+01 1.336760e+01 1.341910e+01 1.347060e+01 1.351180e+01 1.355290e+01 1.359410e+01 1.363530e+01 1.369190e+01 1.375880e+01 1.382570e+01 1.388240e+01 1.392350e+01 1.396470e+01 1.400590e+01 1.404710e+01 1.411400e+01 1.418090e+01 1.422210e+01 1.426320e+01 1.430440e+01 1.434560e+01 1.439710e+01 1.446910e+01 1.453600e+01 1.458240e+01 1.462350e+01 1.466470e+01 1.470590e+01 1.476250e+01 1.481910e+01 1.486030e+01 1.490150e+01 1.494260e+01 1.498380e+01 1.502500e+01 1.506620e+01 1.510740e+01 1.514850e+01 1.522060e+01 1.530290e+01 1.536990e+01 1.542650e+01 1.548310e+01 1.553970e+01 1.560660e+01 1.568900e+01 1.574560e+01 1.578680e+01 1.584340e+01 1.590000e+01 1.594120e+01 1.598240e+01 1.602350e+01 1.606470e+01 1.610590e+01 1.614710e+01 1.618820e+01 1.622940e+01 1.627060e+01 1.631180e+01 1.636320e+01 1.641470e+01 1.645590e+01 1.651250e+01 1.656910e+01 1.661030e+01 1.665150e+01 1.670290e+01 1.675440e+01 1.680590e+01 1.685740e+01 1.689850e+01 1.693970e+01 1.699120e+01 1.705810e+01 1.711470e+01 1.715590e+01 1.719710e+01 1.725370e+01 1.731030e+01 1.735150e+01 1.739260e+01 1.743380e+01 1.749040e+01 1.754710e+01 1.759850e+01 1.765000e+01 1.769120e+01 1.773240e+01 1.777350e+01 1.781470e+01 1.785590e+01 1.791760e+01 1.797940e+01 1.802060e+01 1.806180e+01 1.811320e+01 1.816470e+01 1.820590e+01 1.826250e+01 1.831910e+01 1.836030e+01 1.840150e+01 1.845290e+01 1.850440e+01 1.854560e+01 1.860220e+01 1.865880e+01 1.870000e+01 1.875660e+01 1.881320e+01 1.885440e+01 1.892130e+01 1.898820e+01 1.902940e+01 1.907060e+01 1.912210e+01 1.917350e+01 1.921470e+01 1.925590e+01 1.929710e+01 1.933820e+01 1.937940e+01 1.942060e+01 1.946180e+01 1.950290e+01 1.954410e+01 1.958530e+01 1.962650e+01 1.968310e+01 1.973970e+01 1.978090e+01 1.982210e+01 1.986320e+01 1.990440e+01 1.994560e+01 1.998680e+01 2.002790e+01 2.006910e+01 2.011030e+01 2.015150e+01 2.019260e+01 2.023380e+01 2.027500e+01 2.031620e+01 2.035740e+01 2.039850e+01 2.043970e+01 2.048090e+01 2.052210e+01 2.056320e+01 2.060440e+01 2.064560e+01 2.068680e+01 2.077940e+01 2.087210e+01 2.091320e+01 2.095440e+01 2.099560e+01 2.103680e+01 2.107790e+01 2.111910e+01 2.116030e+01 2.120150e+01 2.124260e+01 2.128380e+01 2.132500e+01 2.136620e+01 2.140740e+01 2.144850e+01 2.148970e+01 2.153090e+01 2.157210e+01 2.161320e+01 2.165440e+01 2.169560e+01 2.173680e+01 2.177790e+01 2.181910e+01 2.186030e+01 2.190150e+01 2.194260e+01 2.198380e+01 2.202500e+01 2.206620e+01 2.210740e+01 2.214850e+01 2.218970e+01 2.223090e+01 2.227210e+01 2.231320e+01 2.235440e+01 2.239560e+01 2.243680e+01 2.247790e+01 2.251910e+01 2.256030e+01 2.260150e+01 2.264260e+01 2.268380e+01 2.276100e+01 2.283820e+01 2.287940e+01 2.294120e+01 2.300290e+01 2.304410e+01 2.308530e+01 2.312650e+01 2.316760e+01 2.320880e+01 2.325000e+01 2.329120e+01 2.333240e+01 2.338900e+01 2.344560e+01 2.348680e+01 2.352790e+01 2.356910e+01 2.361030e+01 2.365150e+01 2.369260e+01 2.373380e+01 2.377500e+01 2.381620e+01 2.385740e+01 2.389850e+01 2.393970e+01 2.398090e+01 2.404260e+01 2.410440e+01 2.414560e+01 2.418680e+01 2.422790e+01 2.428970e+01 2.435150e+01 2.439260e+01 2.443380e+01 2.447500e+01 2.451620e+01 2.455740e+01 2.459850e+01 2.463970e+01 2.469120e+01 2.474260e+01 2.478380e+01 2.482500e+01 2.486620e+01 2.490740e+01 2.496910e+01 2.503090e+01 2.507210e+01 2.511320e+01 2.515440e+01 2.519560e+01 2.523680e+01 2.527790e+01 2.531910e+01 2.536030e+01 2.540150e+01 2.544260e+01 2.548380e+01 2.552500e+01 2.556620e+01 2.560740e+01 2.564850e+01 2.568970e+01 2.573090e+01 2.577210e+01 2.583900e+01 2.590590e+01 2.594710e+01 2.598820e+01 2.605000e+01 2.611180e+01 2.616840e+01 2.622500e+01 2.626620e+01 2.630740e+01 2.634850e+01 2.638970e+01 2.643090e+01 2.647210e+01 2.651320e+01 2.655440e+01 2.659560e+01 2.665220e+01 2.670880e+01 2.675000e+01 2.681180e+01 2.687350e+01 2.691470e+01 2.695590e+01 2.701760e+01 2.710000e+01 2.718240e+01 2.726470e+01 2.737280e+01 2.748090e+01 2.756320e+01 2.767130e+01 2.777940e+01 2.786180e+01 2.794410e+01 2.802650e+01 2.810880e+01 2.823240e+01 2.835590e+01 2.843820e+01 2.852060e+01 2.860290e+01 2.868530e+01 2.878820e+01 2.890150e+01 2.900960e+01 2.910740e+01 2.918970e+01 2.927210e+01 2.935440e+01 2.943680e+01 2.951910e+01 2.960150e+01 2.968380e+01 2.976620e+01 2.984850e+01 2.993090e+01 3.001320e+01 3.009560e+01 3.017790e+01 3.026030e+01 3.037870e+01 3.049710e+01 3.057940e+01 3.066180e+01 3.074410e+01 3.082650e+01 3.090880e+01 3.099120e+01 3.107350e+01 3.117130e+01 3.130510e+01 3.142350e+01 3.150590e+01 3.158820e+01 3.167060e+01 3.175290e+01 3.183530e+01 3.191760e+01 3.200000e+01 3.208240e+01 3.216470e+01 3.224710e+01 3.232940e+01 3.241180e+01 3.249410e+01 3.257650e+01 3.267430e+01 3.277210e+01 3.285440e+01 3.293680e+01 3.301910e+01 3.310150e+01 3.318380e+01 3.326620e+01 3.334850e+01 3.344120e+01 3.353380e+01 3.363680e+01 3.373970e+01 3.382210e+01 3.390440e+01 3.400740e+01 3.411030e+01 3.419260e+01 3.427500e+01 3.435740e+01 3.445510e+01 3.455290e+01 3.463530e+01 3.471760e+01 3.480000e+01 3.488240e+01 3.499040e+01 3.509850e+01 3.520150e+01 3.530440e+01 3.538680e+01 3.546910e+01 3.555150e+01 3.563380e+01 3.571620e+01 3.582940e+01 3.594260e+01 3.602500e+01 3.610740e+01 3.620510e+01 3.630290e+01 3.638530e+01 3.646760e+01 3.655000e+01 3.663240e+01 3.671470e+01 3.679710e+01 3.690000e+01 3.700290e+01 3.708530e+01 3.718310e+01 3.728090e+01 3.736320e+01 3.744560e+01 3.752790e+01 3.761030e+01 3.769260e+01 3.777500e+01 3.785740e+01 3.793970e+01 3.802210e+01 3.810440e+01 ''') Set('width', '24.7cm') Set('height', '16.1cm') Set('StyleSheet/Font/font', u'Arial') Add('page', name='page1', autoadd=False) To('page1') Add('grid', name='grid1', autoadd=False) To('grid1') Set('columns', 1) Set('scaleRows', [1.0, 0.5]) Add('axis', name='x', autoadd=False) To('x') Set('label', u'Wavelength (\\AA)') Set('min', 6.0) Set('max', 26.0) Set('MajorTicks/number', 30) To('..') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', '0cm') Set('rightMargin', '0cm') Set('topMargin', '0cm') Set('bottomMargin', '0cm') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Flux (10^{-3} photon cm^{-2} s^{-1} \\AA^{-1})') Set('min', 0.0) Set('max', 0.0027000000000000001) Set('direction', 'vertical') Set('TickLabels/scale', 1000.0) To('..') Add('xy', name='psf099', autoadd=False) To('psf099') Set('xData', u'wave') Set('yData', u'flux') Set('marker', u'none') Set('key', u'99% PSF') Set('PlotLine/steps', u'centre') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/color', u'grey') To('..') Add('label', name='labelOVIII', autoadd=False) To('labelOVIII') Set('label', u'O \\size{-2}{VIII}') Set('xPos', [19.23]) Set('yPos', [0.0011999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='labelOVIII2', autoadd=False) To('labelOVIII2') Set('label', u'O \\size{-2}{VIII} Fe \\size{-2}{XVIII}') Set('xPos', [16.25]) Set('yPos', [0.00089999999999999998]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='labelFeXVII', autoadd=False) To('labelFeXVII') Set('label', u'Fe \\size{-2}{XVII}') Set('xPos', [17.300000000000001]) Set('yPos', [0.00073999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='labelFeXVII2', autoadd=False) To('labelFeXVII2') Set('label', u'Fe \\size{-2}{XVII}') Set('xPos', [15.25]) Set('yPos', [0.00089999999999999998]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='labelNVII', autoadd=False) To('labelNVII') Set('label', u'N \\size{-2}{VII}') Set('xPos', [25.100000000000001]) Set('yPos', [0.00059999999999999995]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='MgXII', autoadd=False) To('MgXII') Set('label', u'Mg \\size{-2}{XII}') Set('xPos', [8.5]) Set('yPos', [0.0016000000000000001]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('xy', name='psf090', autoadd=False) To('psf090') Set('xData', u'wave_090') Set('yData', u'flux_090') Set('marker', u'none') Set('key', u'90% PSF') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'darkgreen') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/color', u'#55aa7f') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Border/hide', True) Set('vertPosn', u'top') To('..') Add('label', name='labelSiXIV', autoadd=False) To('labelSiXIV') Set('label', u'Si \\size{-2}{XIV}') Set('xPos', [6.2060000000000004]) Set('yPos', [0.0020999999999999999]) Set('positioning', u'axes') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='labelSiXIII', autoadd=False) To('labelSiXIII') Set('label', u'Si \\size{-3}{XIII}') Set('xPos', [6.75]) Set('yPos', [0.0015]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('xy', name='psfdelta', autoadd=False) To('psfdelta') Set('xData', u'wave') Set('yData', u'fluxdelta') Set('marker', u'none') Set('key', u'99% - 90%') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'#ff007f') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/color', u'#ff557f') To('..') Add('label', name='labelNeIXFeXIX', autoadd=False) To('labelNeIXFeXIX') Set('label', u'Fe \\size{-2}{XIX}') Set('xPos', [13.529999999999999]) Set('yPos', [0.0011999999999999999]) Set('positioning', u'axes') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXX_XXII', autoadd=False) To('FeXX_XXII') Set('label', u'Fe \\size{-2}{XX}-\\size{-2}{XXII}') Set('xPos', [12.9]) Set('yPos', [0.0013500000000000001]) Set('positioning', u'axes') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXVII_3', autoadd=False) To('FeXVII_3') Set('label', u'Fe \\size{-2}{XVII}') Set('xPos', [17.0]) Set('yPos', [0.00073999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXVII_4', autoadd=False) To('FeXVII_4') Set('label', u'Fe \\size{-2}{XVII}') Set('xPos', [15.5]) Set('yPos', [0.00089999999999999998]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXVIII', autoadd=False) To('FeXVIII') Set('label', u'Fe \\size{-2}{XVIII}') Set('xPos', [17.899999999999999]) Set('yPos', [0.00067000000000000002]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXVIII_2', autoadd=False) To('FeXVIII_2') Set('label', u'Fe \\size{-2}{XVIII}') Set('xPos', [14.4]) Set('yPos', [0.00095]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXVII_5', autoadd=False) To('FeXVII_5') Set('label', u'Ne \\size{-2}{X}, Fe \\size{-2}{XVII}-\\size{-2}{XVIII}') Set('xPos', [12.1]) Set('yPos', [0.0023999999999999998]) Set('positioning', u'axes') Set('Text/size', u'12pt') To('..') Add('label', name='FeXXI_XXIII', autoadd=False) To('FeXXI_XXIII') Set('label', u'Fe \\size{-2}{XXII}-\\size{-2}{XXIII}') Set('xPos', [11.699999999999999]) Set('yPos', [0.0025000000000000001]) Set('positioning', u'axes') Set('Text/size', u'12pt') To('..') Add('label', name='FeXIX_XX', autoadd=False) To('FeXIX_XX') Set('label', u'Fe \\size{-2}{XXIV}') Set('xPos', [10.779999999999999]) Set('yPos', [0.0020999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='NeX', autoadd=False) To('NeX') Set('label', u'Ne \\size{-2}{X}') Set('xPos', [10.4]) Set('yPos', [0.0012999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='MgXII_2', autoadd=False) To('MgXII_2') Set('label', u'Mg \\size{-2}{XII}') Set('xPos', [7.25]) Set('yPos', [0.0011999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXXIV', autoadd=False) To('FeXXIV') Set('label', u'Fe \\size{-2}{XXIV}') Set('xPos', [8.0999999999999996]) Set('yPos', [0.0011999999999999999]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') Add('label', name='FeXXIII_XIV', autoadd=False) To('FeXXIII_XIV') Set('label', u'Fe \\size{-2}{XXIII}-\\size{-2}{XXIV}') Set('xPos', [10.196808119541004]) Set('yPos', [0.0025058612874790131]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('Text/size', u'12pt') To('..') Add('label', name='FeXXI', autoadd=False) To('FeXXI') Set('label', u'Fe \\size{-2}{XXI}') Set('xPos', [12.6]) Set('yPos', [0.0019]) Set('positioning', u'axes') Set('alignHorz', u'centre') Set('angle', 90.0) Set('Text/size', u'12pt') To('..') To('..') Add('graph', name='graph2', autoadd=False) To('graph2') Set('leftMargin', '0cm') Set('rightMargin', '0cm') Set('bottomMargin', '0cm') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Arbitrary units') Set('direction', 'vertical') To('..') Add('xy', name='model05', autoadd=False) To('model05') Set('xData', u'lambda_model') Set('yData', u'flux_model_0_5_reduce') Set('marker', u'none') Set('key', u'0.5 keV model spectrum') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/hide', True) To('..') Add('xy', name='model07', autoadd=False) To('model07') Set('xData', u'lambda_model') Set('yData', u'flux_model_0_7') Set('marker', u'none') Set('key', u'0.7 keV model spectrum') Set('PlotLine/color', u'#ff007f') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/hide', True) To('..') Add('xy', name='model10', autoadd=False) To('model10') Set('xData', u'lambda_model') Set('yData', u'flux_model_1_0_scaled') Set('marker', u'none') Set('key', u'1.0 keV model spectrum') Set('PlotLine/color', u'darkgreen') Set('PlotLine/width', u'1pt') Set('ErrorBarLine/hide', True) To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Border/hide', True) Set('horzPosn', 'right') Set('vertPosn', 'top') Set('horzManual', 0.0) Set('vertManual', 0.0) To('..') To('..') To('..') To('..') veusz-1.15/examples/inside.vsz0000644002344000001440000002037111734662204016437 0ustar jssusers00000000000000# Veusz saved document (version 0.6) # User: jss # Date: Thu, 19 May 2005 19:04:53 +0000 ImportString('y',''' -2.587729e+00 -1.609362e+01 -7.226746e+00 1.700540e+01 1.138952e+01 4.296140e+00 -3.227336e+00 -4.241213e+00 -5.803339e-02 4.611423e+00 -3.984561e+00 7.906768e+00 -7.745864e+00 2.987510e+00 3.210930e-01 -6.378710e+00 2.515961e+01 2.522258e+01 -3.091128e+00 5.041150e+00 -1.050379e+01 -1.316306e+01 -2.276787e+00 -5.742196e+00 -9.082636e+00 -1.266413e+01 3.199799e+00 -3.311777e+00 1.562370e+01 1.462025e+00 2.950148e+00 -2.691806e+00 -6.179390e+00 5.307104e+00 -6.554100e+00 7.560850e+00 5.671131e-01 2.216967e+00 1.233732e+01 -9.873626e+00 6.128890e+00 -1.371993e+01 8.882399e-01 1.446327e+01 2.803639e+00 -6.576762e-01 5.930219e+00 2.008943e+00 -1.090018e-01 -6.018549e+00 -1.934561e+00 -3.101619e+00 1.802873e+00 -5.754469e+00 2.941526e+01 1.362095e+01 1.984763e+00 5.046622e+00 -1.380654e+01 7.847492e+00 -4.982190e+00 4.118090e-01 -1.053461e+01 -4.595230e+00 -8.601879e+00 -1.179473e+01 -1.863332e+00 1.987707e+00 7.777890e-01 1.101049e+01 2.914746e+00 9.698452e+00 3.350153e+00 -1.289850e+01 5.864994e+00 4.532047e+00 5.557848e+00 1.153725e+00 6.995080e-01 -5.830288e+00 1.251062e+01 1.392709e+01 -1.595572e+01 1.185186e+00 -7.681659e+00 -1.987566e+01 -1.177874e+00 6.851930e+00 -8.103260e-01 -6.909352e+00 2.749254e-01 5.844212e+00 -1.041544e+00 -7.869795e+00 -1.132402e+01 8.992709e+00 1.866769e+01 1.701594e+01 3.893783e+00 5.528125e+00 -7.268189e+00 1.346805e+01 1.946215e+01 8.250416e+00 -1.334784e+01 2.024725e+01 4.509477e+00 -9.515657e+00 -4.556112e+00 -4.316370e+00 2.908727e+00 1.554901e+01 -2.681748e+00 1.779937e+01 -5.736510e+00 -3.372016e+00 -3.216765e+00 1.344067e+01 5.409029e+00 6.133639e+00 -6.941178e+00 -1.122230e+01 1.461280e+01 1.189957e+00 -8.297384e+00 -7.565539e+00 4.255451e+00 8.152416e+00 5.124337e+00 3.879700e+00 -1.763969e+01 1.595328e+01 -7.472883e+00 -6.698127e+00 -1.692927e+01 -7.813697e+00 2.329430e+00 -1.743128e+00 4.229842e+00 -1.035399e+01 -6.785784e+00 -2.798282e+00 5.799159e+00 -1.373382e+01 7.725470e+00 1.154953e+01 1.586832e+01 4.111123e+00 -9.513406e+00 1.032531e+01 -9.989098e+00 1.777364e+01 4.158636e+00 1.737422e+00 1.059494e+01 4.625862e+00 1.722851e+00 2.564189e+01 7.694167e+00 -6.319989e+00 -8.148266e-01 5.716334e+00 -4.233620e+00 1.226502e+01 5.164003e+00 2.041126e+01 9.797467e+00 -1.132923e+01 7.612042e-01 -1.930018e+00 1.436253e+01 -1.407661e+00 -1.920934e+00 1.114966e+01 2.273019e+01 -4.461129e+00 7.384006e+00 3.493240e+00 -1.636330e+00 -1.843285e+01 1.681489e+01 1.153851e+01 -1.673654e+01 1.622110e+01 3.797960e+00 1.071981e+01 2.440005e+00 4.263002e+00 1.356043e+01 7.384417e+00 2.971312e+01 3.127700e+00 -6.236787e-01 1.118915e+01 1.870411e+01 1.712239e+00 1.117415e+01 -9.288272e+00 9.190616e+00 -1.792989e+01 ''') ImportString(u'x',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 1.100000e+01 1.200000e+01 1.300000e+01 1.400000e+01 1.500000e+01 1.600000e+01 1.700000e+01 1.800000e+01 1.900000e+01 2.000000e+01 2.100000e+01 2.200000e+01 2.300000e+01 2.400000e+01 2.500000e+01 2.600000e+01 2.700000e+01 2.800000e+01 2.900000e+01 3.000000e+01 3.100000e+01 3.200000e+01 3.300000e+01 3.400000e+01 3.500000e+01 3.600000e+01 3.700000e+01 3.800000e+01 3.900000e+01 4.000000e+01 4.100000e+01 4.200000e+01 4.300000e+01 4.400000e+01 4.500000e+01 4.600000e+01 4.700000e+01 4.800000e+01 4.900000e+01 5.000000e+01 5.100000e+01 5.200000e+01 5.300000e+01 5.400000e+01 5.500000e+01 5.600000e+01 5.700000e+01 5.800000e+01 5.900000e+01 6.000000e+01 6.100000e+01 6.200000e+01 6.300000e+01 6.400000e+01 6.500000e+01 6.600000e+01 6.700000e+01 6.800000e+01 6.900000e+01 7.000000e+01 7.100000e+01 7.200000e+01 7.300000e+01 7.400000e+01 7.500000e+01 7.600000e+01 7.700000e+01 7.800000e+01 7.900000e+01 8.000000e+01 8.100000e+01 8.200000e+01 8.300000e+01 8.400000e+01 8.500000e+01 8.600000e+01 8.700000e+01 8.800000e+01 8.900000e+01 9.000000e+01 9.100000e+01 9.200000e+01 9.300000e+01 9.400000e+01 9.500000e+01 9.600000e+01 9.700000e+01 9.800000e+01 9.900000e+01 1.000000e+02 1.010000e+02 1.020000e+02 1.030000e+02 1.040000e+02 1.050000e+02 1.060000e+02 1.070000e+02 1.080000e+02 1.090000e+02 1.100000e+02 1.110000e+02 1.120000e+02 1.130000e+02 1.140000e+02 1.150000e+02 1.160000e+02 1.170000e+02 1.180000e+02 1.190000e+02 1.200000e+02 1.210000e+02 1.220000e+02 1.230000e+02 1.240000e+02 1.250000e+02 1.260000e+02 1.270000e+02 1.280000e+02 1.290000e+02 1.300000e+02 1.310000e+02 1.320000e+02 1.330000e+02 1.340000e+02 1.350000e+02 1.360000e+02 1.370000e+02 1.380000e+02 1.390000e+02 1.400000e+02 1.410000e+02 1.420000e+02 1.430000e+02 1.440000e+02 1.450000e+02 1.460000e+02 1.470000e+02 1.480000e+02 1.490000e+02 1.500000e+02 1.510000e+02 1.520000e+02 1.530000e+02 1.540000e+02 1.550000e+02 1.560000e+02 1.570000e+02 1.580000e+02 1.590000e+02 1.600000e+02 1.610000e+02 1.620000e+02 1.630000e+02 1.640000e+02 1.650000e+02 1.660000e+02 1.670000e+02 1.680000e+02 1.690000e+02 1.700000e+02 1.710000e+02 1.720000e+02 1.730000e+02 1.740000e+02 1.750000e+02 1.760000e+02 1.770000e+02 1.780000e+02 1.790000e+02 1.800000e+02 1.810000e+02 1.820000e+02 1.830000e+02 1.840000e+02 1.850000e+02 1.860000e+02 1.870000e+02 1.880000e+02 1.890000e+02 1.900000e+02 1.910000e+02 1.920000e+02 1.930000e+02 1.940000e+02 1.950000e+02 1.960000e+02 1.970000e+02 1.980000e+02 1.990000e+02 ''') ImportString(u'y3',''' 0.000000e+00 1.557408e+00 -2.185040e+00 -1.425465e-01 1.157821e+00 -3.380515e+00 -2.910062e-01 8.714480e-01 -6.799711e+00 -4.523157e-01 ''') ImportString(u'y2,+-',''' 0.000000e+00 1.000000e+00 1.000000e+00 5.000000e-01 1.414214e+00 1.200000e+00 1.732051e+00 8.000000e-01 2.000000e+00 1.000000e+00 2.236068e+00 0.000000e+00 2.449490e+00 0.000000e+00 2.645751e+00 0.000000e+00 2.828427e+00 0.000000e+00 3.000000e+00 0.000000e+00 ''') ImportString(u'x2',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph2', autoadd=False) To('graph2') Set('leftMargin', u'10.5cm') Set('rightMargin', u'0.5cm') Set('topMargin', u'0.5cm') Set('bottomMargin', u'10.5cm') Set('Background/hide', True) Add('axis', name='x', autoadd=False) To('x') Set('label', u'an x-axis') Set('Label/italic', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'sin 2\\emph{\\pi x}') Set('min', -1.0) Set('max', 1.0) Set('direction', 'vertical') To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', u'sin(x*pi*2)') Set('FillBelow/color', u'cyan') Set('FillBelow/hide', False) To('..') To('..') Add('grid', name='grid1', autoadd=False) To('grid1') Set('columns', 1) Set('leftMargin', u'2.3cm') Set('rightMargin', u'9cm') Set('topMargin', u'0.5cm') Set('bottomMargin', u'10.5cm') Add('axis', name='x', autoadd=False) Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', '0cm') Set('rightMargin', '0cm') Set('topMargin', '0cm') Set('bottomMargin', '0cm') Set('Background/color', u'#ffffc0') Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') Set('otherPosition', 1.0) To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'x2') Set('yData', u'y3') Set('marker', u'pentagon') Set('MarkerFill/color', u'magenta') To('..') To('..') Add('graph', name='graph2', autoadd=False) To('graph2') Set('leftMargin', '0cm') Set('rightMargin', '0cm') Set('topMargin', '0cm') Set('bottomMargin', '0cm') Set('Background/color', u'#ffffc0') Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') Set('otherPosition', 1.0) To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'x2') Set('yData', u'y2') Set('marker', u'square') Set('MarkerFill/color', u'green') To('..') To('..') To('..') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Random axis, maybe something interesting^{2}...') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'All the cheese in the world') Set('min', -50.0) Set('max', 100.0) Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) Add('function', name='function1', autoadd=False) To('function1') Set('function', u'0') Set('Line/color', u'red') Set('Line/width', u'2pt') To('..') To('..') To('..') veusz-1.15/examples/fit.vsz0000644002344000001440000000300311734662204015737 0ustar jssusers00000000000000# Veusz saved document (version 0.4) # User: jss # Date: Sun, 13 Mar 2005 20:22:08 +0000 ImportString('y,+-',''' 2.051912e+00 1.000000e+00 8.445439e-01 1.000000e+00 3.071220e+00 1.000000e+00 3.570666e-01 1.000000e+00 4.607197e+00 1.000000e+00 5.686059e+00 1.000000e+00 6.768538e+00 1.000000e+00 6.120451e+00 1.000000e+00 8.245063e+00 1.000000e+00 8.996650e+00 1.000000e+00 1.106673e+01 1.000000e+00 1.167119e+01 1.000000e+00 1.352823e+01 1.000000e+00 1.212483e+01 1.000000e+00 1.528200e+01 1.000000e+00 1.613522e+01 1.000000e+00 1.563430e+01 1.000000e+00 1.673132e+01 1.000000e+00 1.914110e+01 1.000000e+00 1.954017e+01 1.000000e+00 ''') ImportString('x',''' 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 1.100000e+01 1.200000e+01 1.300000e+01 1.400000e+01 1.500000e+01 1.600000e+01 1.700000e+01 1.800000e+01 1.900000e+01 2.000000e+01 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', 'A wonderful \\emph{x} axis') Set('log', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', 'A dubious \\emph{y} axis') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('key', 'data') Set('PlotLine/hide', True) Set('MarkerFill/color', 'red') To('..') Add('fit', name='fit1', autoadd=False) To('fit1') Set('key', 'fit') To('..') Add('key', name='key1', autoadd=False) To('..') To('..') veusz-1.15/examples/sin.vsz0000644002344000001440000000211711734662204015753 0ustar jssusers00000000000000# Veusz saved document (version 0.4) # User: jss # Date: Sun, 13 Mar 2005 18:05:32 +0000 ImportString('y',''' 0.000000e+00 4.067366e-01 7.431448e-01 9.510565e-01 9.945219e-01 8.660254e-01 5.877853e-01 2.079117e-01 -2.079117e-01 -5.877853e-01 -8.660254e-01 -9.945219e-01 -9.510565e-01 -7.431448e-01 -4.067366e-01 -2.449213e-16 ''') ImportString('x',''' 0.000000e+00 4.188790e-01 8.377580e-01 1.256637e+00 1.675516e+00 2.094395e+00 2.513274e+00 2.932153e+00 3.351032e+00 3.769911e+00 4.188790e+00 4.607669e+00 5.026548e+00 5.445427e+00 5.864306e+00 6.283185e+00 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', '\\italic{x}') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', 'sin \\italic{x}') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('MarkerFill/color', 'cyan') To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', 'sin(x)') Set('Line/color', 'red') To('..') To('..') To('..') veusz-1.15/examples/isolatedaxes.vsz0000644002344000001440000000216011734662204017645 0ustar jssusers00000000000000# Veusz saved document (version 1.3) # User: jss # Date: Wed, 27 May 2009 19:18:06 +0000 Set('width', '9.6cm') Set('height', '8.25cm') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', '1.75cm') Set('rightMargin', '0.116cm') Set('topMargin', '0.163cm') Set('bottomMargin', '0.137cm') Set('Border/hide', True) Add('axis', name='x', autoadd=False) To('x') Set('label', u'\\emph{x}-axis\\\\(erg)') Set('log', True) Set('autoMirror', False) Set('lowerPosition', 0.17369650762420075) Set('upperPosition', 0.94726788504290027) Set('otherPosition', 0.47642172482345269) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'\\emph{y}-axis\\\\(cm^{-3}})') Set('autoMirror', False) Set('direction', 'vertical') Set('lowerPosition', 0.043559240567171316) Set('upperPosition', 0.93040302493489757) Set('otherPosition', 0.089153959665518978) Set('Label/rotate', True) To('..') Add('function', name='function1', autoadd=False) To('function1') Set('Line/color', u'red') Set('Line/width', u'1pt') Set('Line/style', u'dotted-fine') To('..') To('..') To('..') veusz-1.15/examples/bar_labels.vsz0000644002344000001440000000510311734662204017246 0ustar jssusers00000000000000# Veusz saved document (version 1.5) # User: jss # Date: Sun, 13 Sep 2009 21:41:45 +0000 ImportFile(u'bar_labels.dat', u'', linked=True, ignoretext=True) Set('width', '10.5cm') Set('height', '16.2cm') Set('StyleSheet/Font/font', u'Arial') Set('StyleSheet/axis/Label/bold', True) Add('page', name='page1', autoadd=False) To('page1') Add('grid', name='grid1', autoadd=False) To('grid1') Set('columns', 1) Set('scaleRows', [1.0, 0.80000000000000004]) Set('leftMargin', u'0cm') Set('rightMargin', u'0cm') Set('topMargin', u'0cm') Set('bottomMargin', u'0cm') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'1.7cm') Set('rightMargin', u'1.7cm') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Colour') Set('mode', u'labels') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Number of balloons') Set('min', 0.0) Set('direction', 'vertical') Set('Label/atEdge', True) To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'spring', u'summer')) Set('keys', (u'Spring', u'Summer')) Set('labels', u'name') Set('errorstyle', u'barends') Set('BarFill/fills', [('solid', u'#aaffff', False), ('solid', u'#00aaff', False)]) Set('ErrorBarLine/width', u'2pt') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Border/hide', True) Set('horzPosn', 'left') Set('vertPosn', 'top') Set('horzManual', 0.0) Set('vertManual', 0.0) To('..') To('..') Add('graph', name='graph2', autoadd=False) To('graph2') Set('leftMargin', u'1.7cm') Set('rightMargin', u'1.7cm') Set('topMargin', u'0.40cm') Set('bottomMargin', '1.7cm') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Spring') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Summer') Set('direction', 'vertical') Set('Label/atEdge', True) To('..') Add('xy', name='data', autoadd=False) To('data') Set('xData', u'spring') Set('yData', u'summer') Set('labels', u'name') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/hide', True) Set('ErrorBarLine/hide', True) Set('Label/hide', True) To('..') Add('xy', name='datacopy', autoadd=False) To('datacopy') Set('xData', u'spring') Set('yData', u'summer') Set('markerSize', u'5pt') Set('labels', u'name') Set('xAxis', u'x2') Set('yAxis', u'y2') Set('PlotLine/hide', True) Set('MarkerFill/color', u'#55aaff') Set('Label/hide', True) To('..') Add('axis', name='x2', autoadd=False) To('x2') Set('mode', u'labels') Set('otherPosition', 1.0) To('..') Add('axis', name='y2', autoadd=False) To('y2') Set('mode', u'labels') Set('direction', u'vertical') Set('otherPosition', 1.0) To('..') To('..') To('..') To('..') veusz-1.15/examples/barplots.vsz0000644002344000001440000001160411734662204017011 0ustar jssusers00000000000000# Veusz saved document (version 1.3) # User: jss # Date: Wed, 27 May 2009 19:03:45 +0000 ImportString(u'xvals(numeric)',''' 1.000000e+00 2.718282e+00 7.389056e+00 2.008554e+01 5.459815e+01 1.484132e+02 4.034288e+02 1.096633e+03 2.980958e+03 8.103084e+03 2.202647e+04 5.987414e+04 1.627548e+05 4.424134e+05 1.202604e+06 3.269017e+06 8.886111e+06 2.415495e+07 6.565997e+07 1.784823e+08 4.851652e+08 ''') ImportString(u'ds2(numeric)',''' 1.000000e+00 9.210610e-01 6.967067e-01 3.623578e-01 -2.919952e-02 -4.161468e-01 -7.373937e-01 -9.422223e-01 -9.982948e-01 -8.967584e-01 -6.536436e-01 -3.073329e-01 8.749898e-02 4.685167e-01 7.755659e-01 9.601703e-01 9.931849e-01 8.693975e-01 6.083513e-01 2.512598e-01 -1.455000e-01 ''') ImportString(u'dswerr(numeric),+-',''' 1.000000e+00 1.000000e-01 9.210610e-01 1.000000e-01 6.967067e-01 1.000000e-01 3.623578e-01 1.000000e-01 -2.919952e-02 1.000000e-01 -4.161468e-01 1.000000e-01 -7.373937e-01 1.000000e-01 -9.422223e-01 1.000000e-01 -9.982948e-01 1.000000e-01 -8.967584e-01 1.000000e-01 -6.536436e-01 1.000000e-01 -3.073329e-01 1.000000e-01 8.749898e-02 1.000000e-01 4.685167e-01 1.000000e-01 7.755659e-01 1.000000e-01 9.601703e-01 1.000000e-01 9.931849e-01 1.000000e-01 8.693975e-01 1.000000e-01 6.083513e-01 1.000000e-01 2.512598e-01 1.000000e-01 -1.455000e-01 1.000000e-01 ''') ImportString(u'ds1(numeric)',''' 0.000000e+00 3.894183e-01 7.173561e-01 9.320391e-01 9.995736e-01 9.092974e-01 6.754632e-01 3.349882e-01 -5.837414e-02 -4.425204e-01 -7.568025e-01 -9.516021e-01 -9.961646e-01 -8.834547e-01 -6.312666e-01 -2.794155e-01 1.165492e-01 4.941134e-01 7.936679e-01 9.679197e-01 9.893582e-01 ''') Set('StyleSheet/Font/font', u'Arial') Add('page', name='page1', autoadd=False) To('page1') Add('grid', name='grid1', autoadd=False) To('grid1') Set('leftMargin', u'0.1cm') Set('bottomMargin', u'0.1cm') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'1.cm') Set('bottomMargin', u'1.cm') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') Set('GridLines/style', u'dotted-fine') Set('GridLines/hide', False) To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'ds1', u'ds2')) Set('mode', u'stacked') Set('keys', (u'a', u'b')) Set('BarFill/fills', [('solid', u'#0055ff', False), ('solid', u'#ff557f', False)]) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'stacked\\\\mode') Set('xPos', [0.5]) Set('yPos', [0.77505549748930258]) Set('alignHorz', u'centre') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Background/hide', True) Set('Border/hide', True) Set('horzPosn', 'manual') Set('vertPosn', 'manual') Set('keyLength', u'0.5cm') Set('horzManual', 0.70845934924546483) Set('vertManual', 0.03438193718616283) To('..') To('..') Add('graph', name='graph2', autoadd=False) To('graph2') Set('leftMargin', u'1.cm') Set('bottomMargin', u'1.cm') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'ds1', u'ds2')) Set('keys', ('',)) Set('BarFill/fills', [('solid', u'#0055ff', False), ('solid', u'#ff557f', False)]) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'grouped\\\\mode') Set('xPos', [0.5]) Set('yPos', [0.77505549748930258]) Set('alignHorz', u'centre') To('..') To('..') Add('graph', name='graph3', autoadd=False) To('graph3') Set('leftMargin', u'1.cm') Set('bottomMargin', u'1.cm') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'dswerr',)) Set('keys', ('',)) Set('errorstyle', u'barends') Set('BarFill/fills', [('solid', u'#0055ff', False), ('solid', u'#ff557f', False)]) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'error bars') Set('xPos', [0.5]) Set('yPos', [0.90000000000000002]) Set('alignHorz', u'centre') To('..') To('..') Add('graph', name='graph4', autoadd=False) To('graph4') Set('leftMargin', u'1.cm') Set('bottomMargin', u'1.cm') Add('axis', name='x', autoadd=False) To('x') Set('GridLines/style', u'dotted-fine') Set('GridLines/hide', False) To('..') Add('axis', name='y', autoadd=False) To('y') Set('log', True) Set('direction', 'vertical') Set('GridLines/style', u'dotted-fine') Set('GridLines/hide', False) To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'dswerr',)) Set('posn', u'xvals') Set('direction', u'horizontal') Set('mode', u'stacked') Set('keys', ('',)) Set('barfill', 1.0) Set('groupfill', 1.0) Set('errorstyle', u'barends') Set('BarFill/fills', [('solid', u'#ff557f', False)]) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'horizontal\\\\with values') Set('xPos', [0.050000000000000003]) Set('yPos', [0.80000000000000004]) To('..') To('..') To('..') To('..') veusz-1.15/examples/tutorialdata.csv0000644002344000001440000000007611734662204017632 0ustar jssusers00000000000000"alpha","beta","gamma" 1,2,4 2,5,6 3,6,5 4,13,10 5,9,6 6,3,14 veusz-1.15/examples/linked_datasets.vsz0000644002344000001440000000351611734662204020324 0ustar jssusers00000000000000# Veusz saved document (version 1.14) # Saved at 2012-01-21T17:21:10.379942 AddImportPath(u'/home/jss/code/veusz-git/veusz/examples') SetDataRange(u't', 100, (-3.141592, 3.141592), linked=True) SetDataExpression(u'x', u'sin(t)', linked=True) SetDataExpression(u'x2', u'sin(t*8)', linked=True) SetDataExpression(u'x3', u'sin(x*2)', linked=True) SetDataExpression(u'xhalf', u'x/2*y', linked=True) SetDataExpression(u'y', u'cos(t)', linked=True) SetDataExpression(u'y2', u'cos(t*16)', linked=True) SetDataExpression(u'yhalf', u'y/2', linked=True) Set('StyleSheet/Line/width', u'1pt') Set('StyleSheet/Font/font', u'Arial') Set('StyleSheet/axis/Label/size', u'18pt') Set('StyleSheet/axis/MajorTicks/number', 8) Set('StyleSheet/xy/markerSize', u'4pt') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('Background/color', u'#e9ffff') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Experiments with linked datasets') Set('autoRange', u'exact') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Another axis') Set('autoRange', u'exact') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('marker', u'diamond') Set('MarkerFill/color', u'blue') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'x2') Set('yData', u'y2') Set('PlotLine/color', u'#00aa00') Set('MarkerFill/color', u'green') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', u'x3') Set('marker', u'star') Set('markerSize', u'6pt') Set('PlotLine/color', u'red') Set('PlotLine/width', u'1pt') Set('MarkerLine/hide', False) Set('MarkerFill/color', u'red') To('..') Add('xy', name='xy4', autoadd=False) To('xy4') Set('xData', u'xhalf') Set('yData', u'yhalf') Set('marker', u'barhorz') Set('MarkerFill/color', u'yellow') To('..') To('..') To('..') veusz-1.15/examples/functions.vsz0000644002344000001440000000320511734662204017171 0ustar jssusers00000000000000# Veusz saved document (version 1.14) # Saved at 2012-01-21T17:09:32.603518 Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('rightMargin', '1.7cm') Add('axis', name='x', autoadd=False) To('x') Set('label', u'\\emph{x} axis') Set('min', -1.5) Set('max', 1.5) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', 'Left axis') Set('min', 0.0) Set('max', 30.0) Set('direction', 'vertical') To('..') Add('function', name='function3', autoadd=False) To('function3') Set('function', 'exp( -x**2 )*20') Set('Line/color', 'purple') Set('Line/width', '3pt') To('..') Add('function', name='function2', autoadd=False) To('function2') Set('function', 'sin(x*4)*5+5') Set('FillBelow/color', 'red') Set('FillBelow/transparency', 60) Set('FillBelow/hide', False) To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', 'sin(y)') Set('variable', 'y') Set('steps', 100) Set('FillBelow/color', 'cyan') Set('FillBelow/transparency', 60) Set('FillBelow/hide', False) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', '\\delta') Set('xPos', [0.98]) Set('yPos', [0.95]) Set('alignHorz', 'right') Set('alignVert', 'top') Set('Text/size', '100pt') To('..') Add('axis', name='axis1', autoadd=False) To('axis1') Set('label', 'Another axis') Set('log', True) Set('direction', 'vertical') Set('otherPosition', 1.0) Set('TickLabels/format', u'%Ve') To('..') Add('function', name='function4', autoadd=False) To('function4') Set('function', 'x**2') Set('yAxis', 'axis1') Set('FillAbove/color', 'green') Set('FillAbove/hide', False) To('..') To('..') To('..') veusz-1.15/examples/multixy.vsz0000644002344000001440000000536011734662204016700 0ustar jssusers00000000000000# Veusz saved document (version 1.15) # Saved at 2012-03-28T19:15:56.421913 ImportString('x(numeric)',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 1.100000e+01 1.200000e+01 1.300000e+01 1.400000e+01 1.500000e+01 1.600000e+01 1.700000e+01 1.800000e+01 1.900000e+01 ''') ImportString('y1(numeric)',''' -8.627642e-01 1.213047e+00 -1.682870e+00 2.540669e+00 9.385744e-04 -6.966657e-01 -8.769203e-01 -6.930465e-01 -2.456379e-01 -6.419765e-01 -1.485679e+00 -7.142200e-01 8.639527e-02 -1.155861e+00 -9.576156e-01 6.018372e-02 -1.027861e+00 2.953903e-01 -3.615840e-01 2.474292e-01 ''') ImportString('y2(numeric)',''' 7.356253e-01 2.187511e+00 9.680102e-01 -7.393343e-01 1.071199e+00 1.763134e+00 1.589872e+00 2.015283e+00 7.102356e-01 1.808795e+00 8.750188e-01 1.477934e+00 3.591239e-02 3.046406e+00 3.515513e+00 7.194178e-01 3.498590e+00 4.465251e+00 1.638100e+00 3.577523e+00 ''') ImportString('y3(numeric),+-',''' 1.537872e+00 3.000000e-01 2.879103e-01 3.000000e-01 7.127184e+00 3.000000e-01 5.775675e+00 3.000000e-01 3.390224e+00 3.000000e-01 2.470264e+00 3.000000e-01 1.019945e+00 3.000000e-01 -5.690097e-01 3.000000e-01 4.276276e+00 3.000000e-01 -4.449537e+00 3.000000e-01 -7.127589e-02 3.000000e-01 -9.531333e-01 3.000000e-01 -1.129021e+00 3.000000e-01 2.561764e+00 3.000000e-01 -1.763882e+00 3.000000e-01 -3.791216e-01 3.000000e-01 2.752641e-02 3.000000e-01 -1.044617e+00 3.000000e-01 2.075609e+00 3.000000e-01 -7.859457e-01 3.000000e-01 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', 'Winged warriors') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', 'Death rate') Set('direction', 'vertical') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Background/color', '#f0f0f0') Set('title', u'Datasets') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('yData', 'y1') Set('key', 'Valkyries') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('yData', 'y2') Set('marker', 'diamond') Set('key', 'Swindon') Set('PlotLine/style', 'dotted') Set('MarkerFill/color', 'red') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('yData', 'y3') Set('marker', 'square') Set('key', 'Discworld') Set('PlotLine/style', 'dashed') Set('MarkerFill/color', 'blue') To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', 'The joy of plots') Set('yPos', [0.9]) Set('alignHorz', 'centre') Set('Text/size', '20pt') To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', '2.5') Set('key', 'Model') Set('Line/color', '#20f020') Set('Line/width', '2pt') To('..') To('..') To('..') veusz-1.15/examples/shapes.vsz0000644002344000001440000000540511734662204016450 0ustar jssusers00000000000000# Veusz saved document (version 1.2) # User: jss # Date: Sun, 23 Nov 2008 13:04:18 +0000 Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'1.2cm') Set('bottomMargin', u'1.2cm') Add('axis', name='x', autoadd=False) To('x') Set('min', 0.0) Set('max', 1.0) To('..') Add('axis', name='y', autoadd=False) To('y') Set('min', 0.0) Set('max', 1.5) Set('direction', 'vertical') To('..') Add('rect', name='diamons', autoadd=False) To('diamons') Set('Fill/transparency', 50) Set('Fill/hide', False) Set('xPos', [0.10000000000000001, 0.5]) Set('yPos', [0.10000000000000001, 0.5]) Set('width', [0.10000000000000001, 0.20000000000000001]) Set('height', [0.10000000000000001, 0.20000000000000001]) Set('rotate', [45.0]) To('..') Add('line', name='arrow', autoadd=False) To('arrow') Set('arrowleft', u'arrow') Set('arrowright', u'arrow') Set('xPos', [0.16651257389999999]) Set('yPos', [0.17450029668958472]) Set('length', [0.33063522110660781]) Set('angle', [315.0]) To('..') Add('ellipse', name='circle', autoadd=False) To('circle') Set('Fill/color', u'blue') Set('Fill/transparency', 50) Set('Fill/hide', False) Set('xPos', [0.75361019828480202]) Set('yPos', [0.71367158438168354]) Set('width', [0.10000000000000001]) Set('height', [0.10000000000000001]) Set('rotate', [0.0]) To('..') Add('xy', name='pts', autoadd=False) To('pts') Set('xData', [0.10000000000000001, 0.20000000000000001, 0.5, 0.5]) Set('yData', [0.14999999999999999, 0.20000000000000001, 0.5, 0.75]) Set('marker', u'diamondhole') Set('markerSize', u'5pt') Set('MarkerFill/color', u'red') To('..') Add('label', name='alpha', autoadd=False) To('alpha') Set('label', u'\\alpha') Set('xPos', [0.26436217797160116]) Set('yPos', [0.31827930674081106]) Set('Text/size', u'20pt') To('..') Add('label', name='title', autoadd=False) To('title') Set('label', u'Some shapes') Set('xPos', [0.5]) Set('yPos', [0.90000000000000002]) Set('alignHorz', u'centre') Set('alignVert', u'centre') Set('Text/size', u'30pt') To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', u'x**2+0.5') Set('Line/hide', True) Set('FillBelow/color', u'green') Set('FillBelow/hide', False) To('..') Add('line', name='line2', autoadd=False) To('line2') Set('arrowleft', u'arrowreverse') Set('arrowright', u'circle') Set('xPos', [0.40548435678619482]) Set('yPos', [0.80000000000000004]) Set('length', [0.20000000000000001]) Set('angle', [90.471358757255615]) Set('Line/color', u'blue') To('..') Add('rect', name='rect2', autoadd=False) To('rect2') Set('Fill/color', u'#aaff7f') Set('Fill/hide', False) Set('Border/style', u'dotted') Set('xPos', [0.5]) Set('yPos', [0.89000000000000001]) Set('width', [0.5]) Set('height', [0.10000000000000001]) Set('rotate', [0.0]) To('..') To('..') To('..') veusz-1.15/examples/embedexample.py0000755002344000001440000000354711734662204017433 0ustar jssusers00000000000000#!/usr/bin/env python # Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """An example embedding program. Veusz needs to be installed into the Python path for this to work (use setup.py) This animates a sin plot, then finishes """ import time import numpy import veusz.embed as veusz # construct a Veusz embedded window # many of these can be opened at any time g = veusz.Embedded('window title') g.EnableToolbar() # construct the plot g.To( g.Add('page') ) g.To( g.Add('graph') ) g.Add('xy', marker='tiehorz', MarkerFill__color='green') # this stops intelligent axis extending g.Set('x/autoRange', 'exact') # zoom out g.Zoom(0.8) # loop, changing the values of the x and y datasets for i in range(10): x = numpy.arange(0+i/2., 7.+i/2., 0.05) y = numpy.sin(x) g.SetData('x', x) g.SetData('y', y) # wait to animate the graph time.sleep(2) # let the user see the final result print "Waiting for 10 seconds" time.sleep(10) print "Done!" # close the window (this is not strictly necessary) g.Close() veusz-1.15/examples/example_import.vsz0000644002344000001440000000545411734662204020216 0ustar jssusers00000000000000# Veusz saved document (version 0.10.cvs) # User: jss # Date: Sat, 17 Jun 2006 16:23:25 +0000 ImportFile('example_import_1.dat', 'x1 y1,+-', linked=True, useblocks=True) ImportFile('example_import_2.dat', '', linked=True) Set('width', u'10cm') Add('page', name='page1', autoadd=False) To('page1') Add('grid', name='grid1', autoadd=False) To('grid1') Set('rows', 3) Set('columns', 1) Set('leftMargin', u'0.1cm') Set('bottomMargin', u'0.1cm') Add('graph', name='samplefile1', autoadd=False) To('samplefile1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'This is an \\emph{x-axis}') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'\\emph{y}^1') Set('direction', 'vertical') Set('Label/rotate', True) To('..') Add('xy', name='ds1', autoadd=False) To('ds1') Set('xData', u'x1_1') Set('yData', u'y1_1') Set('PlotLine/color', u'red') Set('MarkerFill/color', u'red') To('..') Add('xy', name='ds2', autoadd=False) To('ds2') Set('xData', u'x1_2') Set('yData', u'y1_2') Set('marker', u'diamond') Set('PlotLine/color', u'blue') Set('MarkerFill/color', u'blue') To('..') To('..') Add('graph', name='file2graph1', autoadd=False) To('file2graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Another \\bold{x-axis}') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'\\emph{y}^2') Set('direction', 'vertical') Set('Label/rotate', True) To('..') Add('xy', name='thisxy', autoadd=False) To('thisxy') Set('xData', u'thisx') Set('yData', u'thisy') Set('errorStyle', u'diamond') Set('PlotLine/color', u'green') Set('MarkerFill/color', u'green') Set('ErrorBarLine/color', u'green') To('..') Add('xy', name='anotherxy', autoadd=False) To('anotherxy') Set('xData', u'anotherx') Set('yData', u'anothery') Set('errorStyle', u'curve') Set('PlotLine/color', u'magenta') Set('MarkerFill/color', u'magenta') Set('ErrorBarLine/color', u'magenta') To('..') To('..') Add('graph', name='file2graph2', autoadd=False) To('file2graph2') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Final \\underline{x axis}') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'\\emph{y}^3') Set('direction', 'vertical') Set('Label/rotate', True) To('..') Add('xy', name='noise1', autoadd=False) To('noise1') Set('xData', u'noisex') Set('yData', u'noisey_1') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'red') Set('MarkerFill/color', u'red') To('..') Add('xy', name='noise2', autoadd=False) To('noise2') Set('xData', u'noisex') Set('yData', u'noisey_2') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'cyan') Set('MarkerFill/color', u'cyan') To('..') Add('xy', name='noise3', autoadd=False) To('noise3') Set('xData', u'noisex') Set('yData', u'noisey_3') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'grey') Set('MarkerFill/color', u'grey') To('..') To('..') To('..') To('..') veusz-1.15/examples/polar.vsz0000644002344000001440000000401411734662204016275 0ustar jssusers00000000000000# Veusz saved document (version 1.9) # User: jss # Date: Thu, 25 Nov 2010 22:18:36 +0000 ImportString(u'r(numeric)',''' 2.029732e+00 2.688856e+00 2.708923e+00 2.711315e+00 2.640299e+00 2.735022e+00 2.407293e+00 2.130543e+00 2.007767e+00 2.792255e+00 2.070002e+00 2.555355e+00 2.644494e+00 2.639505e+00 2.372546e+00 2.595538e+00 2.646493e+00 2.698109e+00 ''') ImportString(u'theta(numeric)',''' 0.000000e+00 2.000000e+01 4.000000e+01 6.000000e+01 8.000000e+01 1.000000e+02 1.200000e+02 1.400000e+02 1.600000e+02 1.800000e+02 2.000000e+02 2.200000e+02 2.400000e+02 2.600000e+02 2.800000e+02 3.000000e+02 3.200000e+02 3.400000e+02 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', '0.848cm') Set('rightMargin', '10.3cm') Set('topMargin', '0.467cm') Set('bottomMargin', '11.1cm') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'r') Set('yData', u'theta') To('..') To('..') Add('polar', name='polar1', autoadd=False) To('polar1') Set('leftMargin', '3.33cm') Set('rightMargin', '0.674cm') Set('topMargin', '3.25cm') Set('bottomMargin', '0.755cm') Set('maxradius', 3.0) Add('nonorthfunc', name='nonorthfunc1', autoadd=False) To('nonorthfunc1') Set('function', u'1+cos(b/180*pi)') Set('variable', u'b') Set('PlotLine/color', u'blue') Set('PlotLine/width', u'1pt') Set('Fill1/color', u'blue') Set('Fill1/transparency', 80) Set('Fill1/hide', False) To('..') Add('nonorthfunc', name='nonorthfunc2', autoadd=False) To('nonorthfunc2') Set('function', u'2+sin(b/180*pi)*cos(b/180*pi)') Set('variable', u'b') Set('PlotLine/color', u'red') Set('PlotLine/width', u'1pt') Set('Fill1/color', u'red') Set('Fill1/transparency', 80) Set('Fill1/hide', False) To('..') Add('nonorthpoint', name='nonorthpoint1', autoadd=False) To('nonorthpoint1') Set('marker', u'cross') Set('data1', u'r') Set('data2', u'theta') Set('PlotLine/hide', False) To('..') To('..') To('..') veusz-1.15/examples/labels.dat0000644002344000001440000000010211734662204016342 0ustar jssusers000000000000001 1 "A test" 2 4 test2 3 4 "A^{200}" 4 6 "\\alpha \\beta \\gamma" veusz-1.15/examples/starchart.vsz0000644002344000001440000003770511734662204017170 0ustar jssusers00000000000000# Veusz saved document (version 1.12.99) # Saved at 2011-08-17T21:38:56.668330 ImportString('size(numeric)',''' 4.050e-01 3.455e-01 5.800e-01 8.497e-01 8.651e-01 8.397e-01 7.715e-01 3.617e-01 2.565e-01 6.906e-01 5.932e-01 8.708e-01 2.335e-01 2.521e-01 4.786e-01 7.606e-01 3.874e-01 3.600e-01 5.842e-01 7.647e-01 4.191e-01 7.555e-01 8.471e-01 4.407e-01 4.300e-01 1.249e-01 6.256e-02 4.905e-01 2.311e-02 3.583e-01 7.830e-01 5.041e-01 3.003e-01 6.946e-01 4.093e-01 3.897e-01 4.539e-01 6.352e-01 8.445e-01 2.948e-01 5.420e-01 4.801e-01 8.155e-01 4.433e-02 3.789e-01 5.625e-01 6.695e-02 7.941e-01 3.804e-01 9.344e-01 8.059e-01 1.631e-01 4.861e-02 6.394e-01 3.932e-01 2.929e-01 8.481e-01 3.624e-01 3.574e-02 1.552e-01 3.909e-01 4.646e-02 8.587e-01 1.063e-02 9.837e-01 7.219e-01 7.637e-01 6.057e-01 8.209e-01 4.198e-01 6.915e-01 9.860e-01 9.955e-01 3.070e-01 4.891e-01 9.027e-02 1.901e-01 4.686e-01 4.856e-01 8.366e-01 2.897e-02 9.239e-01 7.923e-01 4.719e-01 7.960e-01 9.449e-01 5.453e-01 6.638e-01 7.995e-01 9.712e-02 3.511e-01 5.018e-01 1.632e-01 1.294e-02 6.344e-01 6.543e-02 7.970e-01 2.727e-01 2.513e-01 8.460e-01 3.325e-02 7.681e-02 5.476e-01 7.590e-01 8.837e-01 1.755e-01 9.289e-01 1.040e-01 4.746e-01 6.847e-02 6.449e-01 6.703e-01 5.274e-02 4.158e-01 9.168e-01 1.963e-01 4.417e-01 4.567e-01 2.895e-01 5.929e-03 9.152e-01 8.444e-01 8.529e-01 5.076e-01 4.550e-01 6.566e-01 7.721e-01 3.514e-01 3.746e-01 9.664e-01 1.667e-01 9.201e-02 4.317e-01 4.654e-01 6.015e-01 6.814e-01 4.866e-01 9.389e-01 8.908e-01 4.130e-02 4.716e-01 5.987e-01 4.767e-02 8.905e-01 8.800e-01 7.935e-01 7.342e-01 3.535e-01 9.705e-01 1.858e-01 9.075e-01 9.458e-02 1.833e-01 2.194e-01 1.657e-01 2.087e-01 4.359e-01 3.506e-01 9.515e-01 8.851e-01 4.048e-02 1.991e-01 3.447e-01 2.877e-01 1.286e-01 3.182e-01 4.290e-01 7.130e-01 5.366e-01 5.687e-01 5.497e-01 7.495e-01 7.154e-01 2.551e-01 4.554e-01 6.916e-01 6.407e-01 2.440e-01 9.261e-01 5.933e-02 8.469e-01 3.196e-01 8.569e-01 6.384e-01 2.937e-01 5.292e-02 1.931e-01 2.760e-01 2.912e-02 3.069e-01 9.244e-01 1.239e-01 2.516e-01 4.644e-01 9.013e-02 9.745e-01 3.796e-01 2.707e-02 9.780e-01 2.674e-01 ''') ImportString('size2(numeric)',''' 4.149e-01 6.146e-01 2.448e-01 7.628e-02 1.768e-01 2.063e-01 5.393e-01 2.900e-01 3.645e-01 8.713e-02 5.542e-01 8.222e-02 4.418e-01 6.340e-01 6.466e-01 6.498e-01 6.988e-01 1.329e-01 3.229e-01 5.532e-01 4.700e-01 1.371e-01 3.681e-01 5.719e-01 6.000e-01 5.295e-01 3.881e-01 2.361e-01 1.955e-01 1.751e-01 7.426e-01 5.149e-01 3.111e-01 2.939e-01 1.782e-01 3.758e-01 6.307e-01 5.059e-01 7.406e-01 4.687e-01 8.772e-03 6.461e-02 4.382e-02 9.037e-02 4.053e-01 5.267e-01 9.926e-02 5.119e-01 4.667e-01 2.564e-01 1.839e-01 3.433e-01 3.153e-01 3.838e-01 5.806e-01 4.922e-01 3.729e-02 1.960e-01 6.638e-01 5.121e-01 6.760e-01 2.950e-01 2.766e-01 2.515e-01 7.347e-01 1.599e-02 2.976e-01 1.024e-01 3.245e-01 2.576e-02 4.938e-01 5.979e-01 6.476e-01 5.961e-01 2.611e-01 5.783e-01 4.673e-01 3.781e-01 7.286e-01 2.363e-01 6.622e-01 4.862e-01 7.304e-01 3.534e-01 6.367e-01 1.068e-01 3.883e-01 6.653e-01 5.703e-01 2.747e-01 3.201e-01 5.755e-01 6.744e-01 3.886e-01 7.911e-02 8.700e-02 3.535e-01 1.245e-01 1.195e-01 5.841e-01 6.398e-01 1.762e-01 5.250e-01 5.847e-01 4.941e-01 6.708e-01 3.526e-01 4.299e-01 4.609e-01 4.045e-02 3.338e-01 4.018e-01 5.591e-01 2.480e-01 3.281e-01 2.539e-01 6.600e-01 7.943e-02 3.481e-01 1.625e-02 5.123e-01 3.390e-01 3.904e-01 4.350e-01 5.448e-01 4.653e-01 4.141e-01 1.425e-01 4.031e-01 3.517e-01 6.214e-01 7.483e-01 6.904e-01 5.430e-02 2.179e-01 4.177e-01 1.834e-01 5.792e-02 2.297e-01 5.871e-01 2.647e-01 1.269e-01 2.579e-01 7.247e-01 4.171e-01 2.275e-01 7.145e-01 6.673e-01 3.532e-01 6.283e-01 9.327e-02 1.819e-01 6.711e-01 2.019e-01 5.201e-01 3.551e-01 8.790e-02 4.421e-01 6.433e-01 5.816e-01 4.612e-01 5.547e-02 2.555e-01 6.520e-01 2.823e-01 4.777e-01 4.286e-01 3.108e-01 1.322e-01 4.525e-01 3.051e-01 1.148e-01 6.799e-01 7.144e-01 1.421e-01 2.629e-01 1.992e-01 4.288e-01 2.898e-01 5.219e-01 1.484e-01 6.617e-01 2.670e-01 7.308e-01 4.514e-01 5.690e-01 2.678e-01 7.160e-02 3.514e-02 2.425e-01 3.181e-01 3.302e-01 3.846e-01 2.463e-01 5.676e-02 5.476e-01 2.593e-01 1.812e-01 2.168e-01 5.940e-01 ''') ImportString('x(numeric)',''' -1.717564e-01 4.242809e-01 1.582449e+00 9.042384e-01 -7.558175e-01 9.180096e-01 1.688422e+00 -5.624878e-01 1.148113e+00 -6.757747e-01 -7.184862e-01 9.559501e-02 -7.735163e-01 -1.368390e+00 2.839300e-01 3.863967e-01 -1.018726e+00 -1.057551e+00 1.480676e+00 -1.436318e+00 -6.077504e-01 -6.134922e-01 -1.451637e+00 6.367692e-01 -2.051966e+00 -1.061056e-01 1.132593e+00 -2.332081e+00 -1.483644e-01 -1.709232e+00 7.715420e-01 1.825963e-01 -1.242801e+00 5.126108e-01 7.855827e-01 -1.391835e+00 3.679549e-01 -1.126583e+00 7.381246e-01 -7.205989e-01 2.466142e-01 -1.842360e+00 -1.307182e+00 7.427424e-01 -3.075417e-01 1.075113e+00 -1.109696e+00 -3.353667e-02 -1.087587e+00 1.517819e-03 1.402492e+00 -4.629109e-01 -2.314234e-01 -1.404300e+00 -7.136651e-01 1.143241e-01 -2.593171e-01 5.261581e-01 7.312414e-01 9.634989e-01 6.653014e-02 -1.525272e+00 -1.979862e+00 8.790669e-02 -3.367087e-01 -1.487463e+00 -6.323704e-01 -2.691609e-01 -1.192839e+00 7.129419e-01 -2.026876e+00 -1.670084e+00 -1.105418e+00 4.942608e-01 1.440158e+00 9.895613e-01 -5.584712e-01 3.488847e-02 -5.603513e-03 9.674879e-01 6.670954e-02 9.170016e-01 -1.313607e+00 -2.246257e+00 2.042009e+00 1.025452e+00 -8.301401e-01 4.508979e-01 5.296303e-01 1.255025e+00 1.236309e+00 -8.178501e-01 1.658664e+00 -1.020029e+00 -7.597083e-01 1.642019e+00 1.163782e+00 3.648611e-01 1.205375e+00 -2.208854e+00 1.961078e-01 2.150545e-01 1.335152e+00 2.279445e-01 -3.136242e-02 9.497465e-01 2.207430e-01 -7.895290e-01 -8.275847e-01 7.666461e-01 -1.008892e-01 -1.343381e+00 -1.484571e+00 5.167052e-01 -3.572661e-01 1.440624e+00 9.023146e-01 -3.818540e-01 -1.613737e+00 -1.362967e-01 7.986278e-01 5.329052e-01 -7.147625e-02 1.977507e+00 6.095448e-01 -1.774797e+00 -5.755160e-01 -6.896392e-01 3.003489e-01 1.888296e-01 7.108283e-01 8.797144e-01 2.526157e-01 3.947440e-01 8.264080e-01 -2.686278e-01 -5.568894e-01 -8.562743e-01 -6.682676e-01 1.613319e-01 8.180479e-02 7.551501e-01 1.345002e-01 -5.441405e-01 -4.750194e-01 -1.144256e+00 -3.917083e-02 5.153497e-01 -3.864621e-01 -9.947071e-01 4.151202e-01 1.694649e-01 8.304631e-01 -2.744186e+00 -5.189704e-01 5.950722e-01 4.409983e-01 -3.860202e-01 2.654908e+00 -4.632929e-01 3.676008e-02 3.220285e-01 -1.910937e-01 -7.468510e-01 -8.650068e-01 -9.713170e-01 -1.130643e-01 -3.678296e-01 -4.921382e-01 1.181965e-01 6.255350e-01 -1.242620e+00 1.156373e+00 7.030362e-01 9.894105e-02 5.791196e-01 -8.927444e-01 1.267695e+00 -7.141264e-01 2.025816e+00 -1.293909e+00 -1.477149e+00 -3.851663e-02 -6.038046e-01 -5.637100e-01 -7.411827e-01 -1.302341e+00 -9.121214e-01 1.058442e+00 1.873947e+00 -6.876215e-01 -9.217540e-02 -1.163100e+00 1.145855e+00 -4.506662e-01 -1.423829e+00 1.236884e-01 2.303835e-01 1.388673e+00 9.321516e-01 ''') ImportString('x2(numeric)',''' -5.904152e-01 6.036018e-01 1.861803e-01 -1.182390e-01 4.273179e-01 -5.329131e-01 3.477783e-01 -4.442659e-01 4.112825e-01 -6.975073e-01 -7.894340e-02 3.737914e-01 5.502770e-01 -1.807715e-01 2.749290e-01 8.347531e-01 -2.566171e-01 4.480129e-02 -1.661492e-01 -1.005103e+00 6.827219e-02 1.267272e-01 -7.289300e-02 7.991350e-02 -4.951967e-01 1.395421e+00 -1.094929e+00 3.143240e-01 1.436579e-01 5.987043e-01 8.988077e-04 6.491476e-01 -1.945068e-01 -1.464254e-01 1.318037e-01 5.736032e-01 -6.424045e-01 1.929405e-02 1.083004e-01 -4.876157e-01 -1.975081e-01 -9.470392e-01 7.348370e-02 -3.555848e-01 -4.303004e-01 -1.937558e-01 -3.060530e-01 -1.239214e+00 -7.159538e-01 -1.180139e-01 -8.582931e-01 9.332523e-01 8.186905e-02 1.658889e-01 3.878644e-01 4.596426e-01 -2.580098e-01 1.466808e-01 -2.334527e-01 -7.628955e-01 -4.923554e-02 -1.866111e-01 -1.203111e-01 7.535110e-01 3.363780e-01 -7.143939e-01 9.262992e-01 4.826713e-02 -3.288597e-01 -3.829378e-01 6.682712e-01 -7.421102e-01 4.511552e-02 -7.936474e-01 5.949329e-01 6.510491e-02 -9.386907e-02 7.775805e-01 1.218719e-01 -1.494402e-02 -6.653168e-03 -1.096615e+00 -5.639730e-01 -2.858281e-01 4.137544e-01 9.787053e-02 -3.736432e-02 1.178890e-01 8.644677e-02 8.907128e-01 -2.575110e-01 8.681292e-01 3.192787e-01 -4.730755e-02 3.308247e-02 5.196318e-01 1.612190e-02 -9.430100e-02 -3.501541e-01 1.988158e-01 -6.610444e-02 3.183876e-01 9.110975e-01 3.537435e-01 2.405701e-02 -1.046873e+00 3.043448e-01 1.020531e-01 -5.537825e-01 -1.344010e-01 -4.608990e-01 9.335337e-01 -9.321411e-02 3.717192e-01 3.558881e-01 -2.581769e-01 7.042958e-01 4.890943e-02 -3.936620e-01 -1.006861e+00 -4.333877e-01 7.150886e-01 -4.349794e-01 4.448926e-01 4.621154e-01 1.543000e-01 -1.210074e-01 -9.640434e-02 6.018297e-01 -1.054106e-02 -9.582333e-02 -1.221224e+00 -2.510498e-01 8.605007e-01 -2.235232e-01 6.005776e-01 5.389756e-02 2.774058e-01 -7.967761e-02 -2.286575e-01 -2.433566e-01 -1.338816e-01 -1.915733e-01 -9.346132e-02 -3.945893e-01 -1.000764e+00 -4.389132e-01 -5.512491e-01 -3.093361e-01 -5.310395e-01 5.232963e-01 -4.543161e-01 1.269040e-01 -2.244547e-01 6.759115e-01 2.601059e-01 -4.742974e-01 7.627344e-02 1.575795e-01 2.050366e-01 3.363180e-01 5.956497e-01 7.390307e-01 6.510501e-01 -7.047546e-01 8.470191e-01 -2.368321e-01 -2.557962e-01 9.486894e-01 -3.198666e-01 -6.815602e-02 -2.756185e-01 9.079499e-01 6.653649e-01 1.566721e-01 -1.089555e+00 -1.078753e-01 -1.503253e-01 1.093731e+00 6.795102e-01 9.591976e-01 7.418597e-02 -4.870427e-01 -1.311627e-01 3.575294e-01 2.585647e-01 -1.156918e-01 -9.740063e-02 7.856365e-01 7.415529e-01 2.188530e-01 5.858654e-01 6.431388e-01 7.472439e-01 -3.185660e-01 1.466183e-01 -3.765560e-01 -2.363125e-01 -7.311099e-03 4.543543e-01 ''') ImportString('y(numeric)',''' -1.179418e+00 9.963364e-01 1.035245e+00 -4.930856e-01 -3.079140e-01 -8.363301e-01 3.772350e-01 -1.793423e+00 -3.955435e-01 -7.903513e-01 2.248519e-01 8.868708e-01 1.385193e+00 -9.564458e-02 -2.326000e-01 -1.571620e-01 6.207154e-01 3.515783e-01 -6.039606e-01 -4.724230e-01 -2.933388e-01 -8.633148e-01 -1.216204e-01 -9.216055e-01 -1.218777e+00 7.417790e-01 -5.619947e-02 5.039686e-01 -2.296762e-01 -6.494386e-01 1.621411e+00 -2.070276e-01 3.945420e-01 -6.584078e-02 -1.396862e-01 1.662038e+00 -1.202367e+00 2.562593e-01 -2.478231e-01 -8.922410e-01 1.175726e+00 2.447118e-01 1.086628e+00 -1.745885e+00 1.342561e+00 -9.685250e-01 1.514767e+00 -8.541269e-01 -7.186263e-01 -2.286539e-01 -4.432723e-03 -2.748991e-01 -1.319403e-01 -1.297866e+00 -1.293355e+00 -2.800920e+00 4.857350e-01 1.273159e+00 -8.956792e-01 1.418659e+00 1.314727e+00 9.482291e-01 8.534508e-01 2.193894e+00 8.981459e-01 -8.840747e-02 -8.619552e-01 4.299827e-01 -4.838336e-01 1.537284e+00 -1.290272e+00 4.619874e-01 -1.756571e-01 1.502658e+00 -1.084621e+00 1.366688e+00 6.150953e-01 -1.909314e+00 1.499255e+00 -5.910319e-01 1.289383e-01 -1.935533e+00 -7.818810e-02 1.060331e+00 -8.665276e-01 3.874504e-01 5.042166e-01 6.286272e-01 1.567413e+00 3.106100e-01 7.147362e-01 -1.696396e+00 9.068055e-01 -1.004093e+00 -5.714048e-01 -9.721016e-01 5.559200e-01 2.595251e+00 4.741763e-01 -4.348529e-01 -1.789185e+00 -3.293816e-01 -6.850946e-01 2.887457e-01 6.801073e-01 -3.200756e-01 5.279586e-01 -1.002568e+00 -1.523962e+00 -8.351697e-01 1.209814e+00 -7.876147e-01 6.289090e-01 -2.341422e-01 -3.740775e-01 2.385541e-01 -8.140004e-02 9.481686e-02 1.225121e+00 -3.815732e-01 -7.840012e-01 2.927405e+00 9.265183e-01 7.812431e-01 1.302915e+00 6.347094e-01 5.027754e-01 -7.110444e-01 -4.297201e-01 1.828868e+00 -4.414072e-02 -1.110879e+00 3.847165e-01 -4.791328e-01 3.638377e-01 -1.728797e+00 -4.163380e-02 2.300275e-01 -8.915757e-01 -6.725092e-01 9.569900e-01 -4.703625e-01 1.274738e+00 1.264137e+00 -4.649065e-01 -4.831641e-02 -1.410860e+00 6.752751e-01 -2.005785e+00 -8.789505e-01 -5.581799e-01 6.318567e-01 -7.021317e-01 2.698140e-01 -2.171639e+00 -4.838061e-01 -4.254585e-02 7.594293e-01 1.628929e+00 -7.460201e-01 -3.770777e-01 8.849620e-02 6.164952e-02 1.475529e+00 -1.113757e+00 -1.621159e+00 4.006426e-01 1.077418e-01 -1.913829e+00 -1.317677e+00 4.880339e-01 -4.312087e-01 -1.442873e-01 -2.004930e-02 5.019437e-01 -7.684092e-01 1.541577e+00 -3.599171e-01 -9.827931e-01 -1.514225e-01 3.947186e-02 1.175757e+00 1.097290e+00 -5.108434e-01 -2.445287e+00 -7.330248e-02 -4.411181e-01 9.852479e-01 -1.161989e+00 -1.082814e+00 -5.290654e-02 1.425348e+00 -2.933696e-01 -8.431636e-01 1.201905e+00 7.220561e-02 -6.565752e-02 2.359271e+00 4.880444e-01 1.560229e+00 ''') ImportString('y2(numeric)',''' 3.277343e-01 -4.791839e-01 -5.622894e-01 -8.147905e-01 1.461145e-01 -4.588977e-01 3.968568e-01 1.423664e-01 5.319692e-01 5.065758e-01 -6.637735e-01 -2.946463e-01 -4.624716e-01 6.319409e-01 4.678516e-02 2.219609e-01 1.170330e+00 7.035864e-01 -3.346529e-01 1.734514e-03 7.248274e-01 6.135434e-01 1.028304e-01 -1.206416e-01 2.208649e-02 6.919419e-01 3.821196e-01 2.178115e-01 1.770095e-01 -1.652230e-02 9.046649e-01 -4.432710e-01 2.206628e-01 -4.543656e-01 1.301324e+00 5.292525e-01 -4.944746e-01 2.174136e-01 -3.947546e-01 4.118183e-01 3.404201e-01 6.837868e-01 8.153092e-01 -1.681122e-01 -8.328832e-01 -9.128235e-02 -7.871977e-01 1.963190e-01 6.868842e-01 7.185369e-01 -1.860621e-01 9.277049e-01 -5.712796e-01 9.867310e-01 -7.430959e-01 -4.200795e-01 -3.312437e-01 3.417390e-01 5.875478e-02 -8.900409e-01 4.459047e-01 -2.478977e-01 2.576785e-01 -1.180131e+00 3.503088e-01 2.095792e-01 -5.624632e-01 -1.504682e-01 1.082096e-01 2.748657e-02 -2.188184e-01 -3.769354e-01 3.687838e-01 -1.177511e-01 -2.714600e-01 -1.006103e-01 -1.509157e-01 7.425458e-02 1.376503e-01 -4.981824e-01 -4.858082e-01 2.773604e-01 1.011194e-02 7.998077e-01 5.213352e-01 -2.153188e-01 7.149105e-01 -4.479349e-02 4.948704e-02 -3.989437e-01 -2.229656e-01 2.417801e-01 4.207912e-01 -2.746693e-01 2.094705e-01 1.712305e-01 -5.440275e-01 2.324411e-01 1.052073e+00 -1.872281e-01 6.171029e-01 1.811711e-02 3.791602e-01 -8.040386e-02 2.399947e-01 -3.413228e-01 1.895484e-01 -2.544584e-01 -4.259011e-02 6.655630e-01 -7.508104e-01 3.565060e-01 6.546992e-01 -5.122739e-01 -5.014537e-01 4.045582e-01 9.928116e-01 6.045285e-01 5.509190e-02 6.477872e-01 4.653931e-01 3.743647e-01 8.731676e-01 2.466262e-01 1.092800e-01 -2.989571e-02 1.348799e-01 1.836956e-01 2.409095e-01 4.300016e-01 -1.057074e+00 -6.740874e-01 -9.931708e-02 -1.897434e-01 8.633123e-01 1.121861e+00 7.219913e-01 5.390399e-01 -1.552721e-02 -2.794775e-01 1.651937e-01 3.403011e-01 -4.625169e-01 2.946608e-01 2.377226e-01 -3.711845e-01 1.413453e-01 2.804835e-01 -1.872180e-01 4.300357e-01 -4.654751e-01 4.160271e-01 1.007708e+00 -1.936004e-02 2.207483e-01 -6.654413e-01 -4.479407e-01 -1.364038e-01 4.005023e-01 -2.469230e-01 -4.057847e-01 -4.814272e-01 -4.971257e-01 -2.414501e-01 -1.648537e-01 8.348442e-02 -1.048533e-01 3.140404e-01 3.173624e-01 2.430033e-02 -2.236744e-01 -2.959903e-01 -2.569196e-02 5.527547e-01 -2.281922e-01 -2.492468e-01 4.654103e-01 7.154733e-01 -5.553536e-01 -6.881153e-01 8.689605e-01 6.762373e-01 -1.865834e-01 3.385609e-01 1.006165e-01 -1.133610e+00 3.109142e-01 1.346475e-01 -2.651954e-01 7.159829e-02 -4.473595e-01 -8.366994e-01 4.135409e-01 -6.879046e-01 -2.671874e-01 5.195351e-01 -2.111901e-01 5.907788e-01 6.770766e-02 -5.751893e-02 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'0.2cm') Set('bottomMargin', u'0.2cm') Add('axis', name='x', autoadd=False) To('x') Set('TickLabels/hide', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') Set('TickLabels/hide', True) To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('scalePoints', u'size') Set('PlotLine/hide', True) To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'x2') Set('yData', u'y2') Set('marker', u'circle') Set('scalePoints', u'size2') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'#5555ff') To('..') Add('function', name=u'horz', autoadd=False) To(u'horz') Set('function', u'0') Set('Line/color', u'lightgrey') To('..') Add('function', name=u'vert', autoadd=False) To(u'vert') Set('function', u'0') Set('variable', u'y') Set('Line/color', u'lightgrey') To('..') Add('ellipse', name='ellipse1', autoadd=False) To('ellipse1') Set('xPos', [0.5]) Set('yPos', [0.5]) Set('width', [0.8]) Set('height', [0.8]) Set('rotate', [0.0]) Set('Border/color', u'grey') Set('Border/width', u'0.25pt') To('..') To('..') To('..') veusz-1.15/examples/vectorfield.vsz0000644002344000001440000000245011734662204017470 0ustar jssusers00000000000000# Veusz saved document (version 1.8) # User: jss # Date: Thu, 26 Aug 2010 20:26:59 +0000 SetData2DXYFunc(u'xvec', (-1.0, 1.0, 0.10000000000000001), (-1.0, 1.0, 0.10000000000000001), u'x', linked=True) SetData2DXYFunc(u'yvec', (-1.0, 1.0, 0.10000000000000001), (-1.0, 1.0, 0.10000000000000001), u'y+x', linked=True) SetData2DXYFunc(u'img', (-1.0, 1.0, 0.02), (-1.0, 1.0, 0.02), u'sin( sqrt(x*x+y*y)*10) / (sqrt(x*x+y*y)*10)', linked=True) Set('StyleSheet/Font/font', u'Arial') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'1cm') Set('rightMargin', u'0.5cm') Set('topMargin', u'0.5cm') Set('bottomMargin', u'1cm') Add('axis', name='x', autoadd=False) To('x') Set('min', -1.0) Set('max', 1.0) To('..') Add('axis', name='y', autoadd=False) To('y') Set('min', -1.0) Set('max', 1.0) Set('direction', 'vertical') To('..') Add('vectorfield', name='vectorfield1', autoadd=False) To('vectorfield1') Set('arrowsize', u'3pt') Set('arrowfront', u'arrow') Set('data1', u'xvec') Set('data2', u'yvec') Set('mode', u'cartesian') Set('Line/color', u'white') Set('Line/width', u'1pt') Set('Fill/color', u'white') To('..') Add('image', name='image1', autoadd=False) To('image1') Set('data', u'img') Set('colorMap', u'bluegreen') To('..') To('..') To('..') veusz-1.15/examples/labels.vsz0000644002344000001440000000211611734662204016423 0ustar jssusers00000000000000# Veusz saved document (version 1.0) # User: jss # Date: Sat, 27 Oct 2007 14:31:12 +0000 ImportFile('labels.dat', u'x y label', linked=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'X axis') Set('max', 5.0) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Y axis') Set('max', 8.0) Set('direction', 'vertical') To('..') Add('xy', name='test1', autoadd=False) To('test1') Set('labels', u'label') Set('PlotLine/hide', True) Set('Label/posnVert', u'top') Set('Label/size', u'16pt') To('..') Add('xy', name='test2', autoadd=False) To('test2') Set('xData', [0.5, 2.1000000000000001, 4.0999999999999996]) Set('yData', [0.10000000000000001, 0.10000000000000001, 3.1000000000000001]) Set('marker', u'square') Set('labels', u'dataset 2') Set('PlotLine/hide', True) Set('MarkerFill/hide', True) Set('Label/posnHorz', u'centre') Set('Label/posnVert', u'top') Set('Label/angle', 90.0) Set('Label/size', u'16pt') Set('Label/color', u'red') To('..') To('..') To('..') veusz-1.15/examples/sin_byhand.vsz0000644002344000001440000000105711734662204017302 0ustar jssusers00000000000000# this is sin.vsz # but this is what we would use if we were programming it by hand x=arange(16)/15. * 2* pi y=sin(x) SetData('x', x) SetData('y', y) To( Add('page') ) To( Add('graph') ) Set('x/label', '\\italic{x}') Set('y/label', 'sin \\italic{x}') # we could assume that the name of the xy is xy1, but # this code means other xys could be inserted before this one # but it would still work xy = Add('xy') Set('%s/MarkerFill/color' % xy, 'cyan') fn = Add('function') Set('%s/function' % fn, 'sin(x)') Set('%s/Line/color' % fn, 'red') To('..') To('..') veusz-1.15/examples/dataset_operations.vsz0000644002344000001440000001172711734662204021061 0ustar jssusers00000000000000# Veusz saved document (version 1.8.99) # User: jss # Date: Thu, 26 Aug 2010 20:55:46 +0000 ImportString('x(numeric)',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 1.100000e+01 1.200000e+01 1.300000e+01 1.400000e+01 1.500000e+01 1.600000e+01 1.700000e+01 1.800000e+01 1.900000e+01 ''') DatasetPlugin('Thin', {'ds_out': u'xthin', 'start': 1, 'interval': 2, 'ds_in': u'x'}) ImportString('y1(numeric)',''' -8.627642e-01 1.213047e+00 -1.682870e+00 2.540669e+00 9.385744e-04 -6.966657e-01 -8.769203e-01 -6.930465e-01 -2.456379e-01 -6.419765e-01 -1.485679e+00 -7.142200e-01 8.639527e-02 -1.155861e+00 -9.576156e-01 6.018372e-02 -1.027861e+00 2.953903e-01 -3.615840e-01 2.474292e-01 ''') DatasetPlugin('Add Datasets', {'ds_out': u'y1plusy2', 'ds_in': (u'y1', u'y2')}) ImportString('y2(numeric)',''' 7.356253e-01 2.187511e+00 9.680102e-01 -7.393343e-01 1.071199e+00 1.763134e+00 1.589872e+00 2.015283e+00 7.102356e-01 1.808795e+00 8.750188e-01 1.477934e+00 3.591239e-02 3.046406e+00 3.515513e+00 7.194178e-01 3.498590e+00 4.465251e+00 1.638100e+00 3.577523e+00 ''') ImportString('y3(numeric),+-',''' 1.537872e+00 3.000000e-01 2.879103e-01 3.000000e-01 7.127184e+00 3.000000e-01 5.775675e+00 3.000000e-01 3.390224e+00 3.000000e-01 2.470264e+00 3.000000e-01 1.019945e+00 3.000000e-01 -5.690097e-01 3.000000e-01 4.276276e+00 3.000000e-01 -4.449537e+00 3.000000e-01 -7.127589e-02 3.000000e-01 -9.531333e-01 3.000000e-01 -1.129021e+00 3.000000e-01 2.561764e+00 3.000000e-01 -1.763882e+00 3.000000e-01 -3.791216e-01 3.000000e-01 2.752641e-02 3.000000e-01 -1.044617e+00 3.000000e-01 2.075609e+00 3.000000e-01 -7.859457e-01 3.000000e-01 ''') DatasetPlugin('Add', {'ds_out': u'yadd', 'ds_in': u'y1', 'value': 2.0}) DatasetPlugin('Extremes', {'ds_min': '', 'ds_max': '', 'ds_errorbar': u'yextreme', 'errorbars': False, 'ds_in': (u'ysub', u'y1')}) DatasetPlugin('Mean', {'ds_out': u'ymean', 'ds_in': (u'yadd', u'y1plusy2', u'y1', u'y2')}) DatasetPlugin('Multiply', {'ds_out': u'yscale', 'ds_in': u'ymean', 'factor': 0.5}) DatasetPlugin('Subtract Datasets', {'ds_out': u'ysub', 'ds_in2': u'ymean', 'ds_in1': u'y1'}) DatasetPlugin('Thin', {'ds_out': u'ythin', 'start': 1, 'interval': 2, 'ds_in': u'yadd'}) Set('width', '18cm') Set('height', '15cm') Set('StyleSheet/Font/font', u'Verdana') Add('page', name=u'page1', autoadd=False) To(u'page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('Background/color', u'#f9faff') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name=u'y1', autoadd=False) To(u'y1') Set('yData', u'y1') Set('marker', u'plus') Set('key', u'data') Set('PlotLine/bezierJoin', False) Set('PlotLine/color', '#659f23') Set('MarkerFill/color', '#659f23') Set('ErrorBarLine/color', '#659f23') To('..') Add('xy', name=u'add', autoadd=False) To(u'add') Set('yData', u'yadd') Set('key', u'add') Set('PlotLine/color', '#de9578') Set('MarkerFill/color', '#de9578') Set('ErrorBarLine/color', '#de9578') To('..') Add('xy', name=u'plus', autoadd=False) To(u'plus') Set('yData', u'y1plusy2') Set('key', u'plus') Set('PlotLine/bezierJoin', False) Set('PlotLine/color', '#d02bf1') Set('MarkerFill/color', '#d02bf1') Set('ErrorBarLine/color', '#d02bf1') To('..') Add('xy', name=u'ymean', autoadd=False) To(u'ymean') Set('yData', u'ymean') Set('marker', u'square') Set('key', u'mean') Set('PlotLine/color', '#00a2b7') Set('MarkerFill/color', '#00a2b7') Set('ErrorBarLine/color', '#00a2b7') To('..') Add('xy', name=u'ysub', autoadd=False) To(u'ysub') Set('xData', u'x') Set('yData', u'ysub') Set('marker', u'diamond') Set('key', u'sub') Set('PlotLine/color', '#696b69') Set('MarkerFill/color', '#696b69') Set('ErrorBarLine/color', '#696b69') To('..') Add('xy', name=u'yscale', autoadd=False) To(u'yscale') Set('yData', u'yscale') Set('marker', u'squashbox') Set('key', u'scale') Set('PlotLine/color', '#684f5f') Set('MarkerFill/color', u'#cf9ebe') Set('ErrorBarLine/color', '#684f5f') To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'Using data operations to combine datasets') Set('xPos', [0.5]) Set('yPos', [-0.097615808953379762]) Set('alignHorz', u'centre') Set('Text/size', u'15pt') To('..') Add('xy', name=u'extremes', autoadd=False) To(u'extremes') Set('yData', u'yextreme') Set('marker', u'star6') Set('markerSize', u'5pt') Set('key', u'extremes') Set('errorStyle', u'fillvert') Set('MarkerFill/color', u'#bcfff2') Set('ErrorBarLine/color', u'grey') Set('FillBelow/color', u'#dbe0ed') Set('FillAbove/color', u'#dbe0ed') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Background/hide', True) Set('Border/hide', True) Set('horzPosn', 'centre') Set('vertPosn', 'top') Set('horzManual', 0.0) Set('vertManual', 0.0) Set('columns', 2) To('..') Add('xy', name=u'thin', autoadd=False) To(u'thin') Set('xData', u'xthin') Set('yData', u'ythin') Set('markerSize', u'6pt') Set('key', u'thin') Set('MarkerFill/color', u'#c7b266') To('..') To('..') To('..') veusz-1.15/examples/profile.vsz0000644002344000001440000002347611734662204016635 0ustar jssusers00000000000000# Veusz saved document (version 1.8) # User: jss # Date: Thu, 17 Jun 2010 19:05:07 +0000 ImportString(u'XMM_T(numeric),+,-',''' 5.628000e+00 4.444000e-02 -4.377000e-02 7.894300e+00 9.637000e-02 -9.432000e-02 8.572800e+00 1.505600e-01 -1.452800e-01 8.935100e+00 2.016700e-01 -1.921900e-01 8.812300e+00 2.500800e-01 -2.317200e-01 9.093300e+00 3.304300e-01 -3.117300e-01 8.778200e+00 3.833500e-01 -3.486300e-01 9.014000e+00 5.364900e-01 -4.666900e-01 8.116000e+00 6.585900e-01 -5.105600e-01 7.009700e+00 6.368200e-01 -4.733100e-01 7.267300e+00 7.396100e-01 -6.892600e-01 7.608800e+00 1.070300e+00 -7.781600e-01 6.167300e+00 9.326400e-01 -7.295500e-01 4.853700e+00 8.235500e-01 -5.775300e-01 5.289800e+00 1.210600e+00 -6.668300e-01 ''') ImportString(u'XMM_r(numeric),+,-',''' 4.079410e+01 4.079410e+01 -4.079410e+01 1.223820e+02 4.079420e+01 -4.079420e+01 2.039710e+02 4.079410e+01 -4.079410e+01 2.855590e+02 4.079410e+01 -4.079410e+01 3.671470e+02 4.079420e+01 -4.079420e+01 4.487360e+02 4.079410e+01 -4.079410e+01 5.303240e+02 4.079410e+01 -4.079410e+01 6.119120e+02 4.079420e+01 -4.079420e+01 6.935010e+02 4.079410e+01 -4.079410e+01 7.750890e+02 4.079410e+01 -4.079410e+01 8.566770e+02 4.079420e+01 -4.079420e+01 9.382660e+02 4.079410e+01 -4.079410e+01 1.019850e+03 4.079410e+01 -4.079410e+01 1.101440e+03 4.079420e+01 -4.079420e+01 1.183030e+03 4.079410e+01 -4.079410e+01 ''') ImportString(u'join_T(numeric),+,-',''' 2.848300e+00 2.038400e-01 -1.924600e-01 3.486000e+00 2.454100e-01 -2.203600e-01 4.130900e+00 2.842800e-01 -2.521200e-01 5.275100e+00 5.145600e-01 -4.345200e-01 5.181000e+00 5.605500e-01 -4.667900e-01 5.945600e+00 1.172900e+00 -8.520800e-01 5.172900e+00 7.715200e-01 -6.454600e-01 8.381900e+00 2.117600e+00 -1.484000e+00 9.151200e+00 2.933500e+00 -1.821600e+00 8.095900e+00 1.144400e+00 -9.110800e-01 6.760900e+00 7.825600e-01 -6.630700e-01 7.512700e+00 1.078900e+00 -8.886900e-01 8.290300e+00 1.508700e+00 -1.175700e+00 8.545500e+00 1.509800e+00 -1.083000e+00 1.002600e+01 2.347800e+00 -1.586200e+00 8.040800e+00 1.518800e+00 -1.075000e+00 8.924100e+00 1.402500e+00 -1.070000e+00 8.500300e+00 1.747800e+00 -1.243100e+00 1.193400e+01 2.563800e+00 -1.914100e+00 7.966200e+00 1.015300e+00 -8.143500e-01 1.115500e+01 1.851900e+00 -1.732100e+00 8.335400e+00 9.302900e-01 -8.166900e-01 1.087300e+01 2.340300e+00 -1.582200e+00 9.084100e+00 3.011000e+00 -1.691800e+00 5.619500e+00 2.557100e+00 -1.645100e+00 4.188800e+00 5.971800e-01 -5.137700e-01 ''') ImportString(u'join_Tcool(numeric)',''' 4.866533e+08 6.460709e+08 8.457038e+08 1.309789e+09 1.646014e+09 2.438356e+09 2.569063e+09 3.818538e+09 4.713869e+09 5.285466e+09 5.610075e+09 8.007782e+09 1.066876e+10 1.256449e+10 1.707578e+10 1.696616e+10 2.241039e+10 2.815604e+10 4.075918e+10 4.094509e+10 7.030750e+10 8.146804e+10 1.612034e+11 2.300251e+11 2.865877e+11 2.415128e+11 ''') ImportString(u'join_ne(numeric),+,-',''' 1.013051e-01 3.885018e-03 -3.855963e-03 8.459975e-02 2.732787e-03 -2.742314e-03 7.253090e-02 1.800797e-03 -1.736446e-03 5.611583e-02 1.295804e-03 -1.302452e-03 4.173251e-02 1.210027e-03 -1.232160e-03 3.347415e-02 1.139675e-03 -1.112957e-03 2.727904e-02 1.157072e-03 -1.168092e-03 2.577121e-02 8.503261e-04 -8.617833e-04 2.115231e-02 8.235903e-04 -8.098673e-04 1.808490e-02 3.904686e-04 -3.737143e-04 1.425932e-02 3.416427e-04 -3.560778e-04 1.145005e-02 2.591201e-04 -2.492951e-04 8.995093e-03 3.524567e-04 -2.439155e-04 7.476746e-03 1.812584e-04 -1.850032e-04 6.574554e-03 1.239572e-04 -1.802632e-04 5.492044e-03 1.489112e-04 -1.482904e-04 4.334784e-03 9.548168e-05 -9.709612e-05 3.633731e-03 7.439211e-05 -8.608619e-05 2.939645e-03 7.883378e-05 -7.137054e-05 2.261917e-03 4.206105e-05 -4.172175e-05 1.721952e-03 2.050454e-05 -3.562590e-05 1.240906e-03 1.624617e-05 -2.028029e-05 6.830295e-04 1.871479e-05 -1.764928e-05 4.394545e-04 1.473820e-05 -1.445650e-05 2.567464e-04 2.377862e-05 -2.075020e-05 2.581937e-04 9.016915e-06 -9.065599e-06 ''') ImportString(u'join_proj_T(numeric),+,-',''' 3.921200e+00 8.241000e-02 -7.806000e-02 4.463200e+00 1.025400e-01 -1.001800e-01 5.158200e+00 1.329200e-01 -1.324300e-01 5.936800e+00 1.843800e-01 -1.741000e-01 6.327500e+00 2.304300e-01 -2.105600e-01 6.879600e+00 2.833800e-01 -2.641800e-01 7.067100e+00 3.068400e-01 -2.814800e-01 8.386500e+00 4.438700e-01 -4.020400e-01 8.368100e+00 4.678800e-01 -4.193100e-01 7.987900e+00 3.082700e-01 -2.867900e-01 7.755000e+00 3.112900e-01 -2.885900e-01 8.491100e+00 3.669700e-01 -3.308300e-01 8.926800e+00 4.274000e-01 -3.907600e-01 9.433600e+00 4.752000e-01 -4.266000e-01 9.448900e+00 4.931200e-01 -4.461500e-01 8.913100e+00 4.832100e-01 -4.371800e-01 9.483900e+00 4.748600e-01 -4.375600e-01 9.535400e+00 5.495100e-01 -4.887600e-01 9.956600e+00 5.910000e-01 -5.276500e-01 8.940600e+00 4.640400e-01 -4.203600e-01 9.809900e+00 6.470500e-01 -5.633500e-01 9.168100e+00 5.598600e-01 -4.978400e-01 9.803500e+00 8.398200e-01 -7.446100e-01 7.418000e+00 7.233800e-01 -5.940900e-01 5.493000e+00 6.999100e-01 -5.576600e-01 4.669900e+00 8.224700e-01 -5.288900e-01 ''') ImportString(u'join_r(numeric),+,-',''' 1.166880e+01 5.838770e+00 -5.838770e+00 2.140010e+01 3.892510e+00 -3.892510e+00 2.918510e+01 3.892510e+00 -3.892510e+00 3.697010e+01 3.892510e+00 -3.892510e+00 4.475520e+01 3.892510e+00 -3.892510e+00 5.254020e+01 3.892510e+00 -3.892510e+00 6.032520e+01 3.892510e+00 -3.892510e+00 6.811020e+01 3.892510e+00 -3.892510e+00 7.589530e+01 3.892510e+00 -3.892510e+00 8.757280e+01 7.785030e+00 -7.785030e+00 1.031430e+02 7.785030e+00 -7.785030e+00 1.206590e+02 9.731290e+00 -9.731290e+00 1.401220e+02 9.731290e+00 -9.731290e+00 1.615310e+02 1.167750e+01 -1.167750e+01 1.848860e+02 1.167750e+01 -1.167750e+01 2.082410e+02 1.167750e+01 -1.167750e+01 2.374350e+02 1.751630e+01 -1.751630e+01 2.724670e+02 1.751630e+01 -1.751630e+01 3.113920e+02 2.140880e+01 -2.140880e+01 3.639410e+02 3.114010e+01 -3.114010e+01 4.320600e+02 3.697890e+01 -3.697890e+01 5.247020e+02 5.830020e+01 -5.830020e+01 6.607350e+02 7.773360e+01 -7.773360e+01 8.356360e+02 9.716700e+01 -9.716700e+01 1.029970e+03 9.716700e+01 -9.716700e+01 1.272890e+03 1.457500e+02 -1.457500e+02 ''') Set('width', '15.9cm') Set('height', '11.8cm') Set('StyleSheet/Font/font', u'Verdana') Set('StyleSheet/axis/Label/atEdge', True) Set('StyleSheet/graph/leftMargin', '0cm') Set('StyleSheet/graph/rightMargin', '0cm') Set('StyleSheet/graph/bottomMargin', '0cm') Add('page', name='page1', autoadd=False) To('page1') Add('grid', name='grid1', autoadd=False) To('grid1') Set('columns', 1) Set('leftMargin', '1.83cm') Set('rightMargin', '1.77cm') Set('topMargin', '0cm') Set('bottomMargin', '1.23cm') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Radius (kpc)') Set('min', 8.0) Set('log', True) To('..') Add('graph', name='T', autoadd=False) To('T') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Temperature (keV)') Set('log', True) Set('direction', 'vertical') To('..') Add('xy', name='deproj', autoadd=False) To('deproj') Set('xData', u'join_r') Set('yData', u'join_T') Set('marker', u'square') Set('markerSize', u'1pt') Set('key', u'Chandra deprojected') Set('errorStyle', u'diamond') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'magenta') Set('ErrorBarLine/color', u'grey') To('..') Add('xy', name='proj', autoadd=False) To('proj') Set('xData', u'join_r') Set('yData', u'join_proj_T') Set('marker', u'diamond') Set('markerSize', u'2.5pt') Set('key', u'Chandra projected') Set('errorStyle', u'barends') Set('PlotLine/hide', True) Set('MarkerFill/color', u'cyan') Set('ErrorBarLine/color', u'blue') Set('ErrorBarLine/width', '0.5pt') To('..') Add('xy', name='xmm', autoadd=False) To('xmm') Set('xData', u'XMM_r') Set('yData', u'XMM_T') Set('marker', u'star') Set('markerSize', u'3pt') Set('key', u'XMM projected') Set('errorStyle', u'curve') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'#ff0000') Set('ErrorBarLine/color', u'red') Set('ErrorBarLine/width', '0.5pt') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Text/size', u'12pt') Set('Border/hide', True) Set('horzPosn', 'manual') Set('vertPosn', 'manual') Set('keyLength', u'0.5cm') Set('horzManual', 0.44233856029204632) Set('vertManual', 0.028242379921377202) To('..') To('..') Add('graph', name='ne', autoadd=False) To('ne') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Electron density (cm^{-3})') Set('max', 0.20000000000000001) Set('log', True) Set('direction', 'vertical') To('..') Add('xy', name='deproj', autoadd=False) To('deproj') Set('xData', u'join_r') Set('yData', u'join_ne') Set('marker', u'square') Set('markerSize', u'1pt') Set('key', u'Chandra deproj. density') Set('errorStyle', u'diamond') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'magenta') Set('ErrorBarLine/color', u'grey') Set('ErrorBarLine/width', '0.5pt') To('..') Add('xy', name='tcool', autoadd=False) To('tcool') Set('xData', u'join_r') Set('yData', u'join_Tcool') Set('marker', u'linecross') Set('markerSize', u'3pt') Set('key', u'Chandra cooling time') Set('yAxis', u'y2') Set('errorStyle', u'bar') Set('PlotLine/hide', True) Set('MarkerLine/hide', False) Set('MarkerFill/color', u'magenta') Set('ErrorBarLine/color', u'grey') Set('ErrorBarLine/width', '0.5pt') To('..') Add('axis', name='y2', autoadd=False) To('y2') Set('label', u'Cooling time (yr)') Set('min', 100000000.0) Set('max', 1000000000000.0) Set('log', True) Set('reflect', False) Set('direction', u'vertical') Set('lowerPosition', 0.0) Set('upperPosition', 1.0) Set('otherPosition', 1.0) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'Cooling time') Set('xPos', [0.22084923837100287]) Set('yPos', [0.29071858444521959]) Set('angle', 340.0) Set('Text/size', u'14pt') To('..') Add('label', name='label2', autoadd=False) To('label2') Set('label', u'Density') Set('xPos', [0.43108631326520735]) Set('yPos', [0.65525239745398034]) Set('angle', 22.0) Set('Text/size', u'14pt') Set('Text/color', u'magenta') To('..') To('..') To('..') To('..') veusz-1.15/examples/ternary.vsz0000644002344000001440000001462111734662204016651 0ustar jssusers00000000000000# Veusz saved document (version 1.12.99) # Saved at 2011-08-14T11:00:24.842710 ImportString('a1(numeric)',''' 3.785032e+01 2.887586e+01 3.090787e+01 3.991348e+01 2.194688e+01 6.907754e+00 3.731477e+01 2.729718e+01 4.962433e+01 3.593354e+01 2.980925e+01 1.883289e+01 2.864022e+01 2.385115e+01 1.916651e+01 1.163294e+01 3.201094e+01 4.940456e+01 2.211055e+01 5.347282e+01 3.966645e+01 1.129016e+01 2.900517e+01 2.285825e+01 1.434235e+01 2.491757e+01 4.324933e+01 -5.615836e-01 1.333683e+01 1.169526e+01 5.702774e+01 -4.698968e+00 2.472287e+01 3.090273e+01 2.121293e+01 2.293004e+00 3.181916e+01 3.576081e+01 2.548760e+01 3.387097e+01 1.090547e+01 4.670771e+01 3.679762e+01 1.870816e+01 4.207707e+01 8.373339e+00 1.622552e+01 4.240925e+01 1.243271e+01 9.907227e+00 ''') ImportString('a2(numeric)',''' 3.927892e+01 3.147607e+01 3.585074e+01 2.532616e+01 2.748505e+01 3.089898e+01 3.265939e+01 2.699355e+01 2.724263e+01 3.627490e+01 3.543455e+01 2.613919e+01 2.955639e+01 2.698287e+01 3.423267e+01 2.446838e+01 3.444203e+01 4.034653e+01 2.890829e+01 2.838100e+01 2.620180e+01 3.366264e+01 2.950382e+01 3.507761e+01 2.963094e+01 3.659981e+01 2.953002e+01 3.010142e+01 2.482832e+01 4.350083e+01 2.834519e+01 2.657412e+01 3.719120e+01 3.122923e+01 3.240051e+01 2.650656e+01 3.104269e+01 3.162769e+01 3.344840e+01 3.266331e+01 2.874867e+01 2.454526e+01 3.835166e+01 3.302628e+01 3.290526e+01 3.631759e+01 3.109008e+01 3.474628e+01 3.992190e+01 3.665463e+01 ''') ImportString('a3(numeric)',''' 2.974402e+01 2.992560e+01 2.879212e+01 2.289509e+01 3.151959e+01 2.514551e+01 2.878709e+01 3.809324e+01 2.142433e+01 3.253007e+01 3.495113e+01 3.219936e+01 3.676403e+01 2.577995e+01 2.996285e+01 2.957746e+01 2.770419e+01 2.726770e+01 3.137323e+01 3.328274e+01 2.981133e+01 2.227267e+01 3.234312e+01 2.694048e+01 2.859402e+01 3.514191e+01 3.444068e+01 2.544261e+01 2.895935e+01 2.397857e+01 2.591309e+01 3.282064e+01 2.463678e+01 3.006554e+01 3.302107e+01 3.165531e+01 3.265907e+01 2.977074e+01 2.642066e+01 2.612206e+01 2.921875e+01 3.187261e+01 2.956203e+01 2.660354e+01 3.243911e+01 3.434163e+01 3.642871e+01 2.220277e+01 3.788404e+01 2.830237e+01 ''') ImportString('a4(numeric)',''' 6.023658e+01 6.951139e+01 6.130916e+01 5.480901e+01 5.694097e+01 5.796104e+01 5.963835e+01 5.135586e+01 6.062245e+01 5.609685e+01 5.564031e+01 6.290610e+01 5.559147e+01 5.874261e+01 6.085174e+01 5.703215e+01 5.848376e+01 6.636230e+01 6.031409e+01 5.848922e+01 6.486602e+01 5.198642e+01 5.658263e+01 6.206651e+01 6.050066e+01 6.178179e+01 5.837051e+01 6.618575e+01 6.036681e+01 6.307698e+01 5.658911e+01 5.888594e+01 6.263513e+01 6.270467e+01 6.305208e+01 5.303999e+01 4.733556e+01 6.382467e+01 5.859786e+01 6.703914e+01 5.636757e+01 5.305304e+01 6.576543e+01 4.626336e+01 5.763185e+01 5.699938e+01 6.279347e+01 6.506666e+01 5.402032e+01 5.895224e+01 ''') ImportString('b1(numeric)',''' 6.650787e+00 1.687328e+01 1.010450e+01 1.502892e+01 8.124262e+00 1.579514e+01 2.195101e+01 3.239890e+00 1.600109e+01 4.296486e+00 2.970698e+00 9.968865e+00 1.679330e+01 6.402929e+00 1.273309e+01 1.358886e+01 1.144324e+01 -1.422098e+00 2.708607e+00 1.120078e+01 8.676094e+00 9.079920e+00 9.543885e+00 1.468984e+01 1.288232e+01 8.349164e+00 1.372535e+01 5.718161e+00 1.638648e+01 7.551304e+00 1.155270e+01 1.067230e+01 8.879579e+00 1.171511e+01 6.194712e+00 1.445493e+01 8.503971e+00 1.397565e+01 8.748016e+00 1.188018e+01 1.910491e+01 3.901901e+00 4.879108e+00 1.863455e+01 1.608425e+01 1.645056e+01 -5.258673e+00 8.866189e+00 3.559357e-01 6.020759e+00 ''') ImportString('b2(numeric)',''' 2.738027e+01 1.836565e+01 1.566644e+01 1.609443e+01 2.403639e+01 8.289908e+00 3.651296e+01 5.184957e+01 2.844856e+00 5.319719e+00 1.482401e+01 3.813406e+01 8.649367e+00 -1.039173e+01 2.710390e+01 2.282277e+01 2.110841e+01 2.748859e+01 2.756449e+01 2.152050e+01 1.344263e+01 2.089343e+01 1.775140e+01 1.806021e+01 4.594484e+01 9.522575e+00 2.831632e+01 2.687223e+01 5.372986e+01 3.397279e+01 1.318980e+01 2.891465e+00 2.525563e+01 2.537575e+01 2.810802e+01 3.799986e+01 2.522245e+01 3.272764e+01 1.361898e+01 3.343623e+01 2.053961e+01 3.908754e+01 5.705844e+01 2.483616e+01 3.003679e+01 9.125044e+00 1.994561e+01 2.138206e+01 1.927962e+01 1.461585e+01 ''') ImportString(u'label(text)',r''' u'Nougat' u'Chocolate' ''') ImportString(u'lx(numeric)',''' 6.000000e+01 4.500000e+01 ''') ImportString(u'ly(numeric)',''' 2.500000e+01 5.000000e+01 ''') ImportString('sizes(numeric)',''' 3.703217e-01 4.291845e-01 2.342960e-01 1.000000e-01 7.013704e-01 4.164477e-01 1.065102e+00 7.756953e-01 9.482757e-01 3.618510e-01 7.314487e-01 4.392592e-01 2.367437e-01 1.065933e-01 6.584510e-01 4.378377e-01 1.532375e-01 7.235843e-01 3.666900e-01 7.430943e-01 4.697697e-01 4.473618e-01 3.818643e-01 5.482129e-01 3.484666e-01 2.090011e-01 2.950832e-01 2.944881e-01 4.601784e-01 7.396155e-01 4.672722e-01 1.195206e-01 2.994085e-01 3.087374e-01 5.446024e-01 3.683946e-01 4.009373e-01 8.466384e-01 2.609215e-01 5.970062e-01 4.180349e-01 5.768238e-01 2.538321e-01 8.176581e-01 7.842167e-01 6.022510e-01 3.519089e-01 4.928984e-01 8.822681e-01 3.058641e-01 ''') Set('width', '15cm') Set('height', '13.9cm') Add('page', name='page1', autoadd=False) To('page1') Add('ternary', name='ternary1', autoadd=False) To('ternary1') Set('topMargin', '0.72cm') Set('bottomMargin', '1.28cm') Set('labelbottom', u'Earth') Set('labelleft', u'Air') Set('labelright', u'Fire') Set('Label/size', u'20pt') Set('Label/italic', True) Add('nonorthpoint', name='nonorthpoint4', autoadd=False) To('nonorthpoint4') Set('marker', u'star') Set('markerSize', u'5pt') Set('data1', u'lx') Set('data2', u'ly') Set('labels', u'label') Set('PlotLine/hide', True) Set('Label/size', u'18pt') To('..') Add('nonorthpoint', name='nonorthpoint1', autoadd=False) To('nonorthpoint1') Set('data1', u'a1') Set('data2', u'a2') Set('PlotLine/hide', True) Set('MarkerFill/color', u'#aaffff') To('..') Add('nonorthpoint', name='nonorthpoint2', autoadd=False) To('nonorthpoint2') Set('marker', u'diamond') Set('data1', u'a3') Set('data2', u'a4') Set('PlotLine/hide', True) Set('MarkerFill/color', u'red') To('..') Add('nonorthpoint', name='nonorthpoint3', autoadd=False) To('nonorthpoint3') Set('markerSize', u'5pt') Set('data1', u'b2') Set('data2', u'b1') Set('scalePoints', u'sizes') Set('PlotLine/hide', True) Set('MarkerFill/color', u'blue') To('..') Add('nonorthfunc', name='nonorthfunc1', autoadd=False) To('nonorthfunc1') Set('function', u'40') Set('PlotLine/color', u'#aa00ff') Set('PlotLine/width', u'1.5pt') Set('PlotLine/style', u'dotted') To('..') To('..') To('..') veusz-1.15/examples/stackedxy.vsz0000644002344000001440000000416311734662204017164 0ustar jssusers00000000000000# Example stacked plot # Load in datasets x, y1, y2 and y3+symerrors ImportString('x y1 y2 y3,+-',''' 0.0 -8.627642e-01 7.356253e-01 1.537872e+00 0.3 1.0 1.213047e+00 2.187511e+00 2.879103e-01 0.3 2.0 -1.682870e+00 9.680102e-01 7.127184e+00 0.3 3.0 2.540669e+00 -7.393343e-01 5.775675e+00 0.3 4.0 9.385744e-04 1.071199e+00 3.390224e+00 0.3 5.0 -6.966657e-01 1.763134e+00 2.470264e+00 0.3 6.0 -8.769203e-01 1.589872e+00 1.019945e+00 0.3 7.0 -6.930465e-01 2.015283e+00 -5.690097e-01 0.3 8.0 -2.456379e-01 7.102356e-01 4.276276e+00 0.3 9.0 -6.419765e-01 1.808795e+00 -4.449537e+00 0.3 10.0 -1.485679e+00 8.750188e-01 -7.127589e-02 0.3 11.0 -7.142200e-01 1.477934e+00 -9.531333e-01 0.3 12.0 8.639527e-02 3.591239e-02 -1.129021e+00 0.3 13.0 -1.155861e+00 3.046406e+00 2.561764e+00 0.3 14.0 -9.576156e-01 3.515513e+00 -1.763882e+00 0.3 15.0 6.018372e-02 7.194178e-01 -3.791216e-01 0.3 16.0 -1.027861e+00 3.498590e+00 2.752641e-02 0.3 17.0 2.953903e-01 4.465251e+00 -1.044617e+00 0.3 18.0 -3.615840e-01 1.638100e+00 2.075609e+00 0.3 19.0 2.474292e-01 3.577523e+00 -7.859457e-01 0.3 ''') To(Add('page')) # grid container holds sets of plots To(Add('grid')) Set('rows', 3) Set('columns', 1) # x axis is shared by all graphs in grid Add('axis', name='x') Set('x/label', 'Traffic police') # add the first graph in the grid To(Add('graph')) Set('y/label', 'Valkyries') To(Add('xy')) Set('yData', 'y1') To('../..') # add 2nd To(Add('graph')) Set('y/label', 'Swindon') To(Add('xy')) Set('yData', 'y2') Set('marker', 'diamond') Set('PlotLine/style', 'dotted') Set('MarkerFill/color', 'red') To('../..') # add 3rd To(Add('graph')) Set('y/label', 'Discworld') To(Add('xy')) Set('yData', 'y3') Set('marker', 'square') Set('PlotLine/style', 'dashed') Set('MarkerFill/color', 'blue') To('../..') # this puts the label at the side of the plot, so # all the labels line up # of course, this seems a silly way to do it, but it's just # an example for i in GetChildren(): if 'y' in GetChildren(i): Set('%s/y/Label/atEdge' % i, True) # collapse margins of all the items in the grid Action('zeroMargins') To('/') veusz-1.15/examples/example_csv.csv0000644002344000001440000000034411734662204017441 0ustar jssusers00000000000000"Xval","Yval","+-", 1,0.1,0.03, 1.2,0.5,0.12, 1.5,0.6,0.05, 2.2,1.5,0.14, 3,2.2,0.2, 4,3.2,0.21, 4.3,4.01,0.22, 4.99,4,0.4, 5.2,4.6,0.32, 7,4.4,1, 7.1,6,0.22, ,,, "Xval2","Yval2","+","-" 1,1,0.1,-0.1 2,2,0.1,-0.12 3,3,0.12,-0.1 veusz-1.15/examples/histo.vsz0000644002344000001440000003131211734662204016307 0ustar jssusers00000000000000# Veusz saved document (version 0.5) # User: jss # Date: Sat, 16 Apr 2005 14:43:47 +0000 ImportString('y,+-',''' 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 1.000000e+00 1.414214e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 2.000000e+00 1.732051e+00 1.000000e+00 1.414214e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 1.000000e+00 1.414214e+00 1.000000e+00 1.414214e+00 3.000000e+00 2.000000e+00 1.000000e+00 1.414214e+00 0.000000e+00 1.000000e+00 1.000000e+00 1.414214e+00 1.000000e+00 1.414214e+00 6.000000e+00 2.645751e+00 4.000000e+00 2.236068e+00 2.000000e+00 1.732051e+00 6.000000e+00 2.645751e+00 1.200000e+01 3.605551e+00 7.000000e+00 2.828427e+00 9.000000e+00 3.162278e+00 9.000000e+00 3.162278e+00 9.000000e+00 3.162278e+00 1.000000e+01 3.316625e+00 1.400000e+01 3.872983e+00 1.300000e+01 3.741657e+00 2.000000e+01 4.582576e+00 1.500000e+01 4.000000e+00 1.700000e+01 4.242641e+00 2.200000e+01 4.795832e+00 3.400000e+01 5.916080e+00 2.400000e+01 5.000000e+00 3.900000e+01 6.324555e+00 4.300000e+01 6.633250e+00 4.000000e+01 6.403124e+00 5.700000e+01 7.615773e+00 4.700000e+01 6.928203e+00 6.000000e+01 7.810250e+00 6.000000e+01 7.810250e+00 8.000000e+01 9.000000e+00 7.800000e+01 8.888194e+00 9.300000e+01 9.695360e+00 1.010000e+02 1.009950e+01 9.700000e+01 9.899495e+00 1.260000e+02 1.126943e+01 1.330000e+02 1.157584e+01 1.200000e+02 1.100000e+01 1.340000e+02 1.161895e+01 1.680000e+02 1.300000e+01 1.630000e+02 1.280625e+01 1.680000e+02 1.300000e+01 1.880000e+02 1.374773e+01 1.930000e+02 1.392839e+01 2.200000e+02 1.486607e+01 2.120000e+02 1.459452e+01 2.180000e+02 1.479865e+01 2.370000e+02 1.542725e+01 2.610000e+02 1.618641e+01 2.420000e+02 1.558846e+01 2.710000e+02 1.649242e+01 2.920000e+02 1.711724e+01 2.980000e+02 1.729162e+01 3.060000e+02 1.752142e+01 3.020000e+02 1.740690e+01 3.450000e+02 1.860108e+01 3.550000e+02 1.886796e+01 3.130000e+02 1.772005e+01 3.390000e+02 1.843909e+01 3.890000e+02 1.974842e+01 3.530000e+02 1.881489e+01 3.950000e+02 1.989975e+01 4.320000e+02 2.080865e+01 3.670000e+02 1.918333e+01 4.090000e+02 2.024846e+01 4.170000e+02 2.044505e+01 3.970000e+02 1.994994e+01 4.000000e+02 2.002498e+01 4.100000e+02 2.027313e+01 3.980000e+02 1.997498e+01 4.320000e+02 2.080865e+01 4.200000e+02 2.051828e+01 3.590000e+02 1.897367e+01 3.990000e+02 2.000000e+01 3.740000e+02 1.936492e+01 3.830000e+02 1.959592e+01 3.450000e+02 1.860108e+01 3.320000e+02 1.824829e+01 3.700000e+02 1.926136e+01 3.460000e+02 1.862794e+01 3.350000e+02 1.833030e+01 3.480000e+02 1.868154e+01 2.950000e+02 1.720465e+01 2.910000e+02 1.708801e+01 2.660000e+02 1.634013e+01 2.400000e+02 1.552417e+01 2.680000e+02 1.640122e+01 2.530000e+02 1.593738e+01 2.400000e+02 1.552417e+01 2.290000e+02 1.516575e+01 1.950000e+02 1.400000e+01 1.930000e+02 1.392839e+01 1.850000e+02 1.363818e+01 1.800000e+02 1.345362e+01 1.560000e+02 1.252996e+01 1.500000e+02 1.228821e+01 1.260000e+02 1.126943e+01 1.400000e+02 1.187434e+01 1.270000e+02 1.131371e+01 1.140000e+02 1.072381e+01 1.040000e+02 1.024695e+01 9.500000e+01 9.797959e+00 7.600000e+01 8.774964e+00 1.010000e+02 1.009950e+01 6.300000e+01 8.000000e+00 7.600000e+01 8.774964e+00 5.700000e+01 7.615773e+00 6.500000e+01 8.124038e+00 5.800000e+01 7.681146e+00 4.300000e+01 6.633250e+00 4.300000e+01 6.633250e+00 3.400000e+01 5.916080e+00 2.200000e+01 4.795832e+00 4.000000e+01 6.403124e+00 3.300000e+01 5.830952e+00 2.900000e+01 5.477226e+00 1.700000e+01 4.242641e+00 1.100000e+01 3.464102e+00 1.500000e+01 4.000000e+00 1.400000e+01 3.872983e+00 1.500000e+01 4.000000e+00 8.000000e+00 3.000000e+00 1.600000e+01 4.123106e+00 3.000000e+00 2.000000e+00 5.000000e+00 2.449490e+00 7.000000e+00 2.828427e+00 9.000000e+00 3.162278e+00 6.000000e+00 2.645751e+00 4.000000e+00 2.236068e+00 3.000000e+00 2.000000e+00 5.000000e+00 2.449490e+00 2.000000e+00 1.732051e+00 1.000000e+00 1.414214e+00 4.000000e+00 2.236068e+00 2.000000e+00 1.732051e+00 3.000000e+00 2.000000e+00 1.000000e+00 1.414214e+00 1.000000e+00 1.414214e+00 2.000000e+00 1.732051e+00 2.000000e+00 1.732051e+00 1.000000e+00 1.414214e+00 1.000000e+00 1.414214e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 1.000000e+00 1.414214e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 0.000000e+00 1.000000e+00 ''') ImportString('x',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 1.100000e+01 1.200000e+01 1.300000e+01 1.400000e+01 1.500000e+01 1.600000e+01 1.700000e+01 1.800000e+01 1.900000e+01 2.000000e+01 2.100000e+01 2.200000e+01 2.300000e+01 2.400000e+01 2.500000e+01 2.600000e+01 2.700000e+01 2.800000e+01 2.900000e+01 3.000000e+01 3.100000e+01 3.200000e+01 3.300000e+01 3.400000e+01 3.500000e+01 3.600000e+01 3.700000e+01 3.800000e+01 3.900000e+01 4.000000e+01 4.100000e+01 4.200000e+01 4.300000e+01 4.400000e+01 4.500000e+01 4.600000e+01 4.700000e+01 4.800000e+01 4.900000e+01 5.000000e+01 5.100000e+01 5.200000e+01 5.300000e+01 5.400000e+01 5.500000e+01 5.600000e+01 5.700000e+01 5.800000e+01 5.900000e+01 6.000000e+01 6.100000e+01 6.200000e+01 6.300000e+01 6.400000e+01 6.500000e+01 6.600000e+01 6.700000e+01 6.800000e+01 6.900000e+01 7.000000e+01 7.100000e+01 7.200000e+01 7.300000e+01 7.400000e+01 7.500000e+01 7.600000e+01 7.700000e+01 7.800000e+01 7.900000e+01 8.000000e+01 8.100000e+01 8.200000e+01 8.300000e+01 8.400000e+01 8.500000e+01 8.600000e+01 8.700000e+01 8.800000e+01 8.900000e+01 9.000000e+01 9.100000e+01 9.200000e+01 9.300000e+01 9.400000e+01 9.500000e+01 9.600000e+01 9.700000e+01 9.800000e+01 9.900000e+01 1.000000e+02 1.010000e+02 1.020000e+02 1.030000e+02 1.040000e+02 1.050000e+02 1.060000e+02 1.070000e+02 1.080000e+02 1.090000e+02 1.100000e+02 1.110000e+02 1.120000e+02 1.130000e+02 1.140000e+02 1.150000e+02 1.160000e+02 1.170000e+02 1.180000e+02 1.190000e+02 1.200000e+02 1.210000e+02 1.220000e+02 1.230000e+02 1.240000e+02 1.250000e+02 1.260000e+02 1.270000e+02 1.280000e+02 1.290000e+02 1.300000e+02 1.310000e+02 1.320000e+02 1.330000e+02 1.340000e+02 1.350000e+02 1.360000e+02 1.370000e+02 1.380000e+02 1.390000e+02 1.400000e+02 1.410000e+02 1.420000e+02 1.430000e+02 1.440000e+02 1.450000e+02 1.460000e+02 1.470000e+02 1.480000e+02 1.490000e+02 1.500000e+02 1.510000e+02 1.520000e+02 1.530000e+02 1.540000e+02 1.550000e+02 1.560000e+02 1.570000e+02 1.580000e+02 1.590000e+02 1.600000e+02 1.610000e+02 1.620000e+02 1.630000e+02 1.640000e+02 1.650000e+02 1.660000e+02 1.670000e+02 1.680000e+02 1.690000e+02 1.700000e+02 1.710000e+02 1.720000e+02 1.730000e+02 1.740000e+02 1.750000e+02 1.760000e+02 1.770000e+02 1.780000e+02 1.790000e+02 1.800000e+02 1.810000e+02 1.820000e+02 1.830000e+02 1.840000e+02 1.850000e+02 1.860000e+02 1.870000e+02 1.880000e+02 1.890000e+02 1.900000e+02 1.910000e+02 1.920000e+02 1.930000e+02 1.940000e+02 1.950000e+02 1.960000e+02 1.970000e+02 1.980000e+02 1.990000e+02 2.000000e+02 2.010000e+02 2.020000e+02 2.030000e+02 2.040000e+02 2.050000e+02 2.060000e+02 2.070000e+02 2.080000e+02 2.090000e+02 2.100000e+02 2.110000e+02 2.120000e+02 2.130000e+02 2.140000e+02 2.150000e+02 2.160000e+02 2.170000e+02 2.180000e+02 2.190000e+02 2.200000e+02 2.210000e+02 2.220000e+02 2.230000e+02 2.240000e+02 2.250000e+02 2.260000e+02 2.270000e+02 2.280000e+02 2.290000e+02 2.300000e+02 2.310000e+02 2.320000e+02 2.330000e+02 2.340000e+02 2.350000e+02 2.360000e+02 2.370000e+02 2.380000e+02 2.390000e+02 2.400000e+02 2.410000e+02 2.420000e+02 2.430000e+02 2.440000e+02 2.450000e+02 2.460000e+02 2.470000e+02 2.480000e+02 2.490000e+02 2.500000e+02 2.510000e+02 2.520000e+02 2.530000e+02 2.540000e+02 2.550000e+02 2.560000e+02 2.570000e+02 2.580000e+02 2.590000e+02 2.600000e+02 2.610000e+02 2.620000e+02 2.630000e+02 2.640000e+02 2.650000e+02 2.660000e+02 2.670000e+02 2.680000e+02 2.690000e+02 2.700000e+02 2.710000e+02 2.720000e+02 2.730000e+02 2.740000e+02 2.750000e+02 2.760000e+02 2.770000e+02 2.780000e+02 2.790000e+02 2.800000e+02 2.810000e+02 2.820000e+02 2.830000e+02 2.840000e+02 2.850000e+02 2.860000e+02 2.870000e+02 2.880000e+02 2.890000e+02 2.900000e+02 2.910000e+02 2.920000e+02 2.930000e+02 2.940000e+02 2.950000e+02 2.960000e+02 2.970000e+02 2.980000e+02 2.990000e+02 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', 'Wingspan (m)') Set('max', 200.0) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', 'Dragons') Set('direction', 'vertical') To('..') Add('fit', name='fit1', autoadd=False) To('fit1') Set('function', 'exp( -(x-b)**2 / c )*a') Set('values', {'a': 402.44000280769995, 'c': 791.92768286084686, 'b': 99.159997652570837}) Set('key', 'Fit to histogram') Set('Line/color', 'blue') To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Text/size', '12pt') Set('horzPosn', 'left') Set('vertPosn', 'top') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('marker', 'none') Set('key', 'Histogram') Set('PlotLine/steps', 'centre') Set('ErrorBarLine/hide', True) Set('FillBelow/color', 'lightgreen') Set('FillBelow/hide', False) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', 'Example \\emph{histogram}') Set('xPos', 0.84999999999999998) Set('alignVert', 'centre') Set('angle', 90.0) Set('Text/size', '35pt') To('..') To('..') To('..') veusz-1.15/examples/mandelbrot.vsz0000644002344000001440000000253611734662204017316 0ustar jssusers00000000000000# Veusz script (version 0.7 or later) # Jeremy Sanders (2005) # computes the Mandelbrot set in real time size = 300 maxiters = 20 image = zeros( (size, size) ) print "This takes some time, please wait" minx = 100000 maxx = -100000 miny = 100000 maxy = -100000 for i in xrange(size): for j in xrange(size/2): c1 = -2+4.*i/size c2 = 2-4.*j/size x = c1 y = c2 minx=min(x, minx) maxx=max(x, maxx) miny=min(y, miny) maxy=max(y, maxy) n = 0 while n < maxiters and x**2+y**2 < 4.: x1 = x**2-y**2+c1 y1 = 2*x*y+c2 x = x1 y = y1 n += 1 image[j, i] = n image[size-j-1, i] = n # set output data into veusz SetData2D('image', image, xrange=(minx, maxx), yrange=(miny, maxy)) # construct the graph To(Add('page')) To(Add('graph')) # Add a label Add('label', label='The Mandelbrot Set', yPos=0.95, alignHorz='centre', alignVert='top', Text__size='30pt') # add colorbar in front of image Add('colorbar', name='colorbar1', image='image1', direction='vertical', vertPosn='top') # add image Add('image', name='image1', data='image', min=1, colorScaling='log', colorMap='heat', colorInvert=True) # adjust axes Set('x/min', -2.2) Set('x/max', 1.2) Set('y/min', 0.3) Set('y/max', 1.9) To('/') veusz-1.15/examples/example_import_1.dat0000644002344000001440000000126411734662204020357 0ustar jssusers00000000000000# Example data file 1 for importing # this is read in with blocks enabled 1 0.1 0.04 1.5 1.2 0.21 2 2.1 0.11 2.4 2.0 0.11 6.3 3.5 0.21 6.9 4.0 0.34 8.1 5.1 0.11 0 0.598891867123 0.571271171527 1 1.27652599928 0.293211984296 2 1.80910012902 0.59316037268 3 2.49652366924 0.212350438791 4 2.46688314965 0.418003360397 5 2.84211196873 0.303143691697 6 3.03125458979 0.905546142022 7 3.34096409413 0.276983451243 8 3.38876182999 0.644970504048 9 3.3431954804 0.0314161044416 10 3.72957202336 0.586131190135 11 3.32604926178 0.611351645308 12 4.31563271648 0.703522901568 13 4.59995446895 0.844015647958 14 4.15530039017 0.724286803521 veusz-1.15/examples/markerspolygon.vsz0000644002344000001440000000375711734662204020251 0ustar jssusers00000000000000# Veusz saved document (version 1.6) # User: jss # Date: Tue, 19 Jan 2010 21:30:02 +0000 SetDataExpression(u'stary_offset', u'stary+0.1', linked=True) SetDataExpression(u'starx_offset', u'starx+0.1', linked=True) # star shape for polygon ImportString(u'stary(numeric)',''' -1.200000e+00 -3.708000e-01 -3.708000e-01 1.416000e-01 9.708000e-01 4.584000e-01 9.708000e-01 1.416000e-01 -3.708000e-01 -3.708000e-01 ''') ImportString(u'starx(numeric)',''' 0.000000e+00 -2.700000e-01 -1.141200e+00 -4.356000e-01 -7.056000e-01 0.000000e+00 7.056000e-01 4.356000e-01 1.141200e+00 2.700000e-01 ''') Set('StyleSheet/xy/markerSize', u'5pt') Set('StyleSheet/xy/MarkerFill/color', u'#aaaaff') Set('StyleSheet/xy/PlotLine/color', 'grey') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Outward ticks on this x axis') Set('min', -2.0) Set('max', 2.0) Set('outerticks', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Outward ticks on this y axis') Set('min', -2.0) Set('max', 2.0) Set('outerticks', True) Set('direction', 'vertical') To('..') Add('polygon', name='polygon2', autoadd=False) To('polygon2') Set('xPos', u'starx') Set('yPos', u'stary') Set('positioning', u'axes') Set('Line/hide', True) Set('Fill/color', u'cyan') Set('Fill/transparency', 10) To('..') Add('polygon', name='polygon1', autoadd=False) To('polygon1') Set('xPos', u'starx_offset') Set('yPos', u'stary_offset') Set('positioning', u'axes') Set('Line/hide', True) Set('Fill/color', u'blue') To('..') colours = ['blue', 'cyan', 'lightgreen', 'purple', 'pink'] # add plot symbols for each type of plot symbol codes = veusz_markercodes for i, mcode in enumerate(codes): r = 1.6 + 0.2 * sin(16*pi/len(codes)*i) x = r*sin(2*pi/len(codes)*i) y = r*cos(2*pi/len(codes)*i) Add('xy', name=mcode, marker=mcode, xData=[x,x*1.2], yData=[y,y*1.2], MarkerFill__color=colours[i % len(colours)]) To('..') To('..') veusz-1.15/examples/hatching.vsz0000644002344000001440000000606611734662204016756 0ustar jssusers00000000000000# Veusz saved document (version 1.15) # Saved at 2012-03-21T19:30:52.420824 ImportString(u'y1(numeric)',''' 0.000000e+00 2.000000e+00 3.000000e+00 5.000000e+00 6.000000e+00 9.000000e+00 3.000000e+00 ''') ImportString(u'y2(numeric)',''' 1.000000e+00 4.000000e+00 5.000000e+00 8.000000e+00 8.000000e+00 1.000000e+01 8.000000e+00 ''') DatasetPlugin('Add', {'ds_out': u'y3', 'ds_in': u'y2', 'value': 1.0}) Set('StyleSheet/Font/font', u'Lucida Sans') Set('StyleSheet/xy/marker', u'none') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'0.8cm') Set('rightMargin', u'0.8cm') Set('topMargin', u'0.8cm') Set('bottomMargin', u'0.8cm') Set('Border/width', u'1pt') Add('axis', name='x', autoadd=False) To('x') Set('min', 1.0) Set('max', 7.0) Set('MinorTicks/hide', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('max', 12.0) Set('direction', 'vertical') Set('MinorTicks/hide', True) To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', []) Set('yData', u'y1') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'blue') Set('PlotLine/width', u'1pt') Set('FillBelow/color', u'blue') Set('FillBelow/style', u'backward diagonals') Set('FillBelow/hide', False) Set('FillBelow/patternspacing', u'6pt') Set('FillBelow/backhide', False) To('..') Add('function', name='function3', autoadd=False) To('function3') Set('function', u'4-y/2') Set('variable', u'y') Set('Line/color', u'magenta') Set('Line/width', u'1pt') Set('FillBelow/color', u'magenta') Set('FillBelow/style', u'vertical forward') Set('FillBelow/hide', False) To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', []) Set('yData', u'y2') Set('PlotLine/color', u'red') Set('PlotLine/width', u'1pt') Set('FillBelow/color', u'red') Set('FillBelow/style', u'forward diagonals') Set('FillBelow/hide', False) Set('FillBelow/linestyle', u'dashed') Set('FillBelow/backhide', False) To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', []) Set('yData', u'y3') Set('PlotLine/color', u'grey') Set('PlotLine/width', u'1pt') Set('FillBelow/style', u'horizontal') Set('FillBelow/hide', False) Set('FillBelow/patternspacing', u'3pt') Set('FillBelow/backhide', False) To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', u'10-y/2') Set('variable', u'y') Set('Line/color', u'green') Set('FillAbove/color', u'#00ff7f') Set('FillAbove/style', u'horizontal double') Set('FillAbove/hide', False) Set('FillAbove/linewidth', u'0.25pt') Set('FillAbove/backcolor', u'green') Set('FillAbove/backhide', False) To('..') Add('function', name='function2', autoadd=False) To('function2') Set('function', u'9-y/2') Set('variable', u'y') Set('Line/color', u'#5500ff') Set('FillAbove/color', u'#5500ff') Set('FillAbove/style', u'diagonal cross 2') Set('FillAbove/hide', False) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'\\color{green}{Hatch}ing') Set('xPos', [0.5]) Set('yPos', [1.06]) Set('alignHorz', u'centre') Set('alignVert', u'top') Set('Text/size', u'18pt') To('..') To('..') To('..') veusz-1.15/examples/example_csv.vsz0000644002344000001440000000203011734662204017462 0ustar jssusers00000000000000# Veusz saved document (version 0.10) ImportFileCSV('example_csv.csv', linked=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Imported CSV file example') Set('min', 0.0) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Data read into from CSV') Set('direction', 'vertical') To('..') Add('xy', name='lineplot', autoadd=False) To('lineplot') Set('xData', u'Xval') Set('yData', u'Yval') Set('key', u'Line plot') Set('errorStyle', u'barends') To('..') Add('xy', name='histo', autoadd=False) To('histo') Set('xData', u'Xval2') Set('yData', u'Yval2') Set('marker', u'square') Set('key', u'Histogram') Set('PlotLine/steps', u'centre') Set('PlotLine/style', u'dotted') Set('MarkerFill/color', u'red') Set('ErrorBarLine/color', u'red') Set('FillBelow/color', u'grey') Set('FillBelow/hide', False) To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Border/hide', True) To('..') To('..') To('..') veusz-1.15/examples/boxplot.vsz0000644002344000001440000000364711734662204016662 0ustar jssusers00000000000000# Veusz saved document (version 1.9.99) # User: jss # Date: Thu, 02 Dec 2010 19:28:57 +0000 ImportString(u'd1(numeric)',''' 1.503414e+01 1.771985e+01 1.603614e+01 1.697182e+01 1.408432e+01 1.375135e+01 1.290579e+01 1.474151e+01 1.383836e+01 1.370710e+01 ''') SetDataExpression(u'd1_x', u'd1*0+0.6', linked=True) ImportString(u'd2(numeric)',''' 1.298826e+01 4.121945e+00 4.666195e+00 7.293386e+00 1.682599e+00 1.611030e+01 1.834109e+01 1.385013e+01 6.923665e+00 1.859132e+01 ''') SetDataExpression(u'd2_x', u'd2*0+2.4', linked=True) ImportString(u'label(text)',r''' u'Bees' u'Butterflys' ''') Set('width', '14.5cm') Set('height', '12cm') Set('StyleSheet/Font/font', u'Arial') Set('StyleSheet/boxplot/Border/width', u'1pt') Set('StyleSheet/boxplot/Whisker/width', u'1pt') Set('StyleSheet/boxplot/MarkersLine/width', u'1pt') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', u'1.3cm') Set('rightMargin', '0.2cm') Set('topMargin', '0.2cm') Set('bottomMargin', u'1.3cm') Set('Background/color', u'#fffeea') Add('axis', name='x', autoadd=False) To('x') Set('mode', u'labels') Set('TickLabels/size', u'18pt') To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Number of insects') Set('direction', 'vertical') To('..') Add('boxplot', name='boxplot1', autoadd=False) To('boxplot1') Set('values', (u'd1', u'd2')) Set('labels', u'label') Set('whiskermode', u'9/91 percentile') Set('fillfraction', 0.5) Set('Fill/color', u'white') To('..') Add('xy', name=u'd2vals', autoadd=False) To(u'd2vals') Set('xData', u'd2_x') Set('yData', u'd2') Set('marker', u'cross') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'blue') To('..') Add('xy', name=u'd1vals', autoadd=False) To(u'd1vals') Set('xData', u'd1_x') Set('yData', u'd1') Set('PlotLine/hide', True) Set('MarkerLine/hide', True) Set('MarkerFill/color', u'magenta') To('..') To('..') To('..') veusz-1.15/examples/example_import_2.dat0000644002344000001440000000531611734662204020362 0ustar jssusers00000000000000# second example file # descriptors can be placed down the file multiple times # to specify sets of data descriptor thisx,+- thisy,+,- 1.0048e-01 2.1160e-01 9.5824e-01 1.3893e-01 -6.0814e-01 1.2665e+00 9.5604e-01 6.7138e-01 4.1650e-01 -6.7098e-01 2.4097e+00 6.2898e-01 2.1296e-01 4.9144e-01 -3.3067e-02 3.9563e+00 4.7805e-01 2.3593e-01 4.9881e-01 -2.7282e-02 4.5674e+00 4.2886e-01 3.8157e-01 5.3305e-01 -3.2969e-01 5.4321e+00 5.1148e-02 1.5812e+00 1.2613e-01 -2.2894e-01 6.9457e+00 8.1255e-01 1.6777e+00 9.8834e-01 -9.7390e-01 7.4897e+00 9.3973e-01 1.7167e+00 4.3308e-01 -3.7178e-01 8.0426e+00 5.6998e-01 1.5577e+00 8.7185e-01 -1.1033e-01 9.9084e+00 9.6405e-01 1.0996e+00 5.9948e-01 -3.3716e-01 1.0963e+01 9.5036e-01 1.7831e+00 3.6308e-03 -1.3141e-01 1.1522e+01 3.2964e-02 1.8577e+00 3.3410e-01 -4.1994e-01 1.2634e+01 2.4651e-01 1.5259e+00 6.8782e-01 -8.0710e-01 1.3764e+01 5.7442e-01 1.6989e+00 6.6327e-01 -4.2052e-01 1.4102e+01 9.0656e-01 1.1045e+00 8.2707e-01 -2.7248e-01 descriptor anotherx,+- anothery,+,- 4.4622e-01 6.4802e-01 1.0884e+00 2.5945e-01 -4.7283e-01 1.2226e+00 3.2900e-01 1.5053e+00 2.8890e-01 -3.6051e-01 2.6112e+00 2.7019e-01 1.6834e+00 7.0039e-01 -7.7591e-01 3.7616e+00 1.5996e-01 1.3153e+00 7.8240e-01 -5.5298e-01 4.2028e+00 5.4426e-01 1.6729e+00 8.0976e-01 -1.2393e-01 5.5345e+00 2.6658e-01 8.9366e-01 1.2642e-01 -8.6600e-02 6.4766e+00 9.2462e-01 1.1606e+00 8.4187e-01 -6.3709e-01 7.1592e+00 7.4514e-01 6.7465e-01 2.9700e-01 -2.9862e-01 8.2514e+00 6.7011e-01 1.3665e+00 2.1944e-01 -2.1589e-01 9.1714e+00 7.5465e-01 1.1409e+00 7.2534e-01 -8.5968e-01 1.0905e+01 4.0861e-02 9.5769e-02 4.7422e-01 -9.2371e-02 1.1232e+01 8.9452e-01 -9.0869e-02 3.2122e-01 -8.0847e-01 1.2988e+01 4.4257e-01 2.5282e-01 5.9169e-02 -5.4564e-01 1.3888e+01 6.8980e-01 -1.0379e-01 4.0765e-01 -3.7785e-01 1.4439e+01 1.9561e-01 3.7001e-01 3.4897e-01 -9.3970e-01 descriptor noisex noisey[1:3] 0.0000e+00 4.2279e-01 6.4775e-01 2.8592e+00 1.0000e+00 9.2479e-01 1.0987e+00 8.5007e-01 2.0000e+00 8.8329e-01 6.8191e-01 5.7917e-01 3.0000e+00 9.5791e-01 1.1615e+00 5.1192e-01 4.0000e+00 1.5129e-01 8.5718e-01 2.3247e+00 5.0000e+00 5.3964e-01 1.5094e+00 7.9621e-01 6.0000e+00 1.8649e-01 1.2638e+00 1.5540e-01 7.0000e+00 8.4299e-01 1.1033e+00 6.9083e-01 8.0000e+00 8.0603e-01 1.5430e+00 1.2785e+00 9.0000e+00 3.9557e-02 1.4083e+00 1.9398e+00 1.0000e+01 6.6562e-02 1.1485e+00 2.7042e+00 1.1000e+01 2.3951e-01 3.8934e-01 9.6851e-01 1.2000e+01 9.2450e-01 7.2653e-01 1.1478e-01 1.3000e+01 5.3937e-01 1.1756e+00 1.6637e+00 1.4000e+01 4.9036e-01 3.5158e-01 2.3081e+00 1.5000e+01 3.0818e-01 1.5758e-01 9.5081e-01 1.6000e+01 1.6127e-01 1.0475e-01 9.0180e-01 1.7000e+01 4.1190e-02 9.0279e-01 2.8207e+00 1.8000e+01 9.4011e-01 1.4106e+00 5.1369e-01 1.9000e+01 6.2362e-01 1.3872e-01 4.2210e-01 veusz-1.15/examples/datebar.vsz0000644002344000001440000000373411734662204016572 0ustar jssusers00000000000000# Veusz saved document (version 1.3) # User: jss # Date: Wed, 27 May 2009 19:46:06 +0000 ImportFile('datebar.dat', '', linked=True, ignoretext=True) Set('StyleSheet/Line/color', u'#005500') Set('StyleSheet/Font/font', u'Verdana') Set('StyleSheet/Font/color', u'#00557f') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Set('leftMargin', '1.59cm') Set('rightMargin', '0.416cm') Set('topMargin', '2.11cm') Set('bottomMargin', '4.02cm') Set('Background/color', u'#f5ffcd') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Date') Set('mode', u'datetime') Set('TickLabels/rotate', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Crazy bar value') Set('min', 0.0) Set('direction', 'vertical') To('..') Add('bar', name='bar1', autoadd=False) To('bar1') Set('lengths', (u'value',)) Set('posn', u'd') Set('keys', ('',)) Set('BarFill/fills', [('solid', u'#00aa7f', False)]) Set('BarLine/lines', [('solid', u'1pt', 'black', False)]) Set('ErrorBarLine/width', u'1pt') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'd') Set('yData', u'value') Set('marker', u'none') Set('PlotLine/hide', True) Set('FillBelow/color', u'#d9ffe5') Set('FillBelow/hide', False) To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'Look!') Set('xPos', [0.31336981405284353]) Set('yPos', [0.63329357828966459]) Set('alignHorz', u'right') Set('angle', 50.0) Set('Text/size', u'20pt') To('..') Add('line', name='line1', autoadd=False) To('line1') Set('arrowleft', u'bar') Set('arrowright', u'arrow') Set('xPos', [0.30322686916441111]) Set('yPos', [0.63075731090716558]) Set('length', [0.24757735730509881]) Set('angle', [45.467843029728137]) To('..') To('..') Add('label', name='label1', autoadd=False) To('label1') Set('label', u'A graph title') Set('xPos', [0.5]) Set('yPos', [0.97999999999999998]) Set('alignHorz', u'centre') Set('alignVert', u'top') Set('Text/size', u'30pt') To('..') To('..') veusz-1.15/examples/multiaxes.vsz0000644002344000001440000012366111734662204017205 0ustar jssusers00000000000000# Veusz saved document (version 0.10.cvs) # User: jss # Date: Wed, 14 Jun 2006 19:52:16 +0000 ImportString('noreson_noi3_r_Z1,+-',''' 6.572439e-01 6.572439e-01 1.971732e+00 6.572439e-01 3.812014e+00 1.183039e+00 6.178092e+00 1.183039e+00 8.675619e+00 1.314488e+00 1.130459e+01 1.314488e+00 1.472226e+01 2.103180e+00 1.892862e+01 2.103180e+00 2.366078e+01 2.628975e+00 2.891873e+01 2.628975e+00 3.417668e+01 2.628975e+00 3.943463e+01 2.628975e+00 4.469258e+01 2.628975e+00 4.995053e+01 2.628975e+00 5.573428e+01 3.154771e+00 6.204382e+01 3.154771e+00 ''') ImportString('noreson_noi3_ne1,+,-',''' 2.365000e-01 8.600000e-03 -8.600000e-03 1.032000e-01 4.700000e-03 -4.850000e-03 4.753000e-02 1.500000e-03 -1.550000e-03 3.183000e-02 1.050000e-03 -1.070000e-03 2.355000e-02 7.800000e-04 -8.000000e-04 1.742000e-02 6.400000e-04 -6.700000e-04 1.579000e-02 3.200000e-04 -3.300000e-04 1.182000e-02 2.900000e-04 -3.000000e-04 1.054000e-02 2.000000e-04 -1.900000e-04 9.236000e-03 1.580000e-04 -1.640000e-04 8.422000e-03 1.330000e-04 -1.340000e-04 6.945000e-03 1.210000e-04 -1.240000e-04 5.908000e-03 1.120000e-04 -1.150000e-04 5.452000e-03 9.100000e-05 -9.300000e-05 4.269000e-03 1.090000e-04 -1.140000e-04 7.672000e-03 5.100000e-05 -5.100000e-05 ''') ImportString('w_vapec_vff2,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('w_vapec_vff3,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_Ca,+,-',''' 3.716330e+00 1.399600e+00 -1.278560e+00 2.700520e+00 9.703600e-01 -9.306400e-01 2.048950e+00 5.923000e-01 -5.697500e-01 2.267210e+00 5.086000e-01 -4.897200e-01 2.767100e+00 4.975600e-01 -4.807000e-01 2.152230e+00 3.837200e-01 -3.796200e-01 2.846590e+00 3.995200e-01 -3.921200e-01 2.470460e+00 4.705900e-01 -4.050500e-01 1.537570e+00 4.106800e-01 -4.086700e-01 1.332220e+00 4.451300e-01 -4.373170e-01 1.619270e+00 3.715300e-01 -3.665700e-01 1.458970e+00 4.215100e-01 -4.261000e-01 9.320300e-01 4.597800e-01 -4.651430e-01 6.933560e-01 4.729340e-01 -4.775060e-01 1.462040e+00 6.719800e-01 -6.686430e-01 ''') ImportString('noreson_noi3_kT1,+,-',''' 7.955000e-01 7.900000e-03 -7.900000e-03 7.827000e-01 5.200000e-03 -5.200000e-03 1.219000e+00 1.100000e-02 -1.100000e-02 1.571000e+00 1.800000e-02 -1.800000e-02 1.636000e+00 1.500000e-02 -1.500000e-02 2.025000e+00 3.700000e-02 -3.900000e-02 2.095000e+00 2.500000e-02 -2.700000e-02 2.494000e+00 4.500000e-02 -4.700000e-02 2.444000e+00 4.200000e-02 -4.300000e-02 2.576000e+00 3.900000e-02 -3.900000e-02 2.141000e+00 1.800000e-02 -1.900000e-02 3.084000e+00 6.200000e-02 -6.300000e-02 2.516000e+00 7.000000e-02 -7.300000e-02 4.895000e+00 2.060000e-01 -2.030000e-01 2.311000e+00 1.210000e-01 -1.260000e-01 3.838000e+00 6.900000e-02 -6.800000e-02 ''') ImportString('xmm_vapec_vff2,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_T3lim',''' -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('w_vapec_Ni,+,-',''' 5.220240e+00 1.538330e+00 -1.378750e+00 2.841710e+00 8.109300e-01 -6.269900e-01 4.395780e+00 3.462400e-01 -3.461100e-01 3.755960e+00 4.584800e-01 -4.227900e-01 3.612520e+00 4.330400e-01 -4.000000e-01 3.728600e+00 4.309600e-01 -4.210900e-01 3.507120e+00 3.511500e-01 -3.361500e-01 3.155250e+00 2.450200e-01 -1.592600e-01 2.583700e+00 2.671500e-01 -2.584700e-01 2.788240e+00 2.247300e-01 -1.435600e-01 2.792480e+00 3.293000e-01 -3.235300e-01 1.349330e+00 3.288700e-01 -3.150600e-01 1.762790e+00 4.861700e-01 -4.468000e-01 1.023060e+00 4.031700e-01 -4.316150e-01 1.777940e+00 6.155400e-01 -5.641900e-01 4.882850e-01 5.636750e-01 -4.882850e-01 ''') ImportString('w_vapec_Si,+,-',''' 1.637960e+00 3.718000e-01 -2.959400e-01 1.232940e+00 2.357600e-01 -1.840900e-01 1.921590e+00 1.905600e-01 -1.146000e-01 1.867870e+00 1.506500e-01 -1.393800e-01 2.131080e+00 1.576800e-01 -1.468200e-01 2.271250e+00 1.776200e-01 -1.520300e-01 2.113510e+00 1.264300e-01 -1.203200e-01 2.203080e+00 1.085700e-01 -1.168700e-01 1.676660e+00 8.897000e-02 -8.578000e-02 1.919650e+00 9.615000e-02 -8.869000e-02 1.732540e+00 1.051100e-01 -1.044100e-01 1.245640e+00 1.041600e-01 -1.009300e-01 9.975870e-01 1.198630e-01 -1.132420e-01 8.007290e-01 1.167890e-01 -1.167040e-01 7.871770e-01 1.511040e-01 -1.379370e-01 3.781040e-01 1.387510e-01 -1.318470e-01 ''') ImportString('w_vapec_Ne,+,-',''' 1.428580e+00 6.057100e-01 -5.517210e-01 6.461840e-01 5.654460e-01 -5.232090e-01 1.726750e-16 3.317240e-01 -1.726750e-16 9.738280e-01 4.509420e-01 -4.385050e-01 6.710520e-01 4.909380e-01 -4.325650e-01 6.183760e-01 5.343040e-01 -5.158100e-01 9.863740e-01 4.258460e-01 -4.148540e-01 1.960950e+00 2.824000e-01 -2.145500e-01 7.150440e-01 5.349760e-01 -3.452330e-01 1.660800e+00 2.547600e-01 -1.661800e-01 1.050920e-01 3.810390e-01 -1.050920e-01 1.273890e+00 3.623300e-01 -4.093630e-01 3.587130e-01 5.077150e-01 -3.587130e-01 9.175210e-01 2.430390e-01 -1.475510e-01 1.639650e+00 3.160800e-01 -3.073900e-01 7.509840e-01 3.301860e-01 -3.055670e-01 ''') ImportString('e_vapec_Ar,+,-',''' 1.196560e+00 6.043200e-01 -5.700870e-01 2.223310e+00 7.141600e-01 -6.391500e-01 1.912140e+00 4.398800e-01 -4.162100e-01 2.069310e+00 3.356400e-01 -3.207000e-01 2.793290e+00 3.683100e-01 -3.514200e-01 2.124330e+00 3.080200e-01 -2.992300e-01 1.718660e+00 2.864600e-01 -2.810600e-01 9.387050e-01 2.976250e-01 -2.950320e-01 1.695820e+00 3.651400e-01 -3.572000e-01 1.501640e+00 3.474300e-01 -3.419100e-01 5.647910e-01 3.006720e-01 -3.028350e-01 1.453630e+00 3.714700e-01 -3.749700e-01 1.014740e+00 3.886000e-01 -4.032950e-01 1.433080e+00 4.352400e-01 -4.305500e-01 2.008770e-15 3.915200e-01 -2.008770e-15 ''') ImportString('xmm_vapec_Mg,+,-',''' 1.361130e+00 1.604800e-01 -1.500400e-01 1.281650e+00 1.694900e-01 -1.606700e-01 1.052190e+00 1.536200e-01 -1.486720e-01 7.997150e-01 1.574100e-01 -1.550140e-01 5.988460e-01 1.430020e-01 -1.477240e-01 5.375700e-01 1.430460e-01 -1.474580e-01 6.082150e-01 1.667030e-01 -1.476110e-01 5.230250e-01 1.803190e-01 -1.763550e-01 5.093080e-02 1.769270e-01 -5.093080e-02 1.175880e-08 1.600750e-01 -1.175880e-08 ''') ImportString('xmm_vapec_nH,+,-',''' 1.209520e-01 6.958000e-03 -6.805000e-03 1.139110e-01 5.662000e-03 -5.521000e-03 9.977550e-02 4.350500e-03 -4.290300e-03 1.011170e-01 4.257000e-03 -3.916200e-03 1.025230e-01 3.427000e-03 -2.721100e-03 9.963410e-02 3.928900e-03 -3.413900e-03 9.161620e-02 4.316700e-03 -3.962600e-03 8.841800e-02 3.996300e-03 -3.958200e-03 9.553880e-02 4.473200e-03 -4.317000e-03 1.000480e-01 4.387000e-03 -5.056400e-03 ''') ImportString('w_vapec_norm1,+,-',''' 1.202990e-04 7.574400e-05 -3.545990e-05 1.086800e-04 1.399600e-05 -1.535650e-05 3.071000e-05 5.610300e-06 -3.204000e-06 8.033110e-04 6.120700e-05 -9.153700e-05 8.475340e-04 6.470700e-05 -6.309000e-05 7.778100e-04 9.163200e-05 -2.053210e-04 1.406030e-03 1.868400e-04 -2.665800e-04 1.916220e-03 2.116000e-05 -5.161000e-05 2.135240e-03 2.110500e-04 -1.038200e-04 2.359360e-03 3.070000e-05 -6.537000e-05 1.527710e-03 1.469100e-04 -1.623500e-04 1.812450e-03 1.218700e-04 -2.909000e-04 6.409580e-04 2.320000e-04 -2.028610e-04 1.538210e-03 4.505000e-05 -1.786100e-04 1.433060e-03 3.825000e-05 -3.863000e-05 1.066660e-03 3.658000e-05 -3.603000e-05 ''') ImportString('e_vapec_norm1,+,-',''' 2.986830e-04 2.006990e-04 -1.347780e-04 1.412000e-04 2.564300e-05 -2.180600e-05 8.558810e-05 1.016910e-05 -9.357600e-06 1.148300e-04 1.232200e-05 -1.080800e-05 3.931920e-05 3.459560e-05 -1.101640e-05 4.763550e-04 3.141540e-04 -7.224600e-05 1.048760e-03 1.188400e-04 -1.118940e-04 1.365210e-03 2.090300e-04 -4.739160e-04 4.557120e-04 5.045590e-04 -2.693790e-04 1.030460e-03 2.901800e-04 -3.078500e-04 1.787220e-03 6.020000e-05 -6.753000e-05 1.194470e-04 9.593000e-06 -3.054830e-05 1.984400e-04 7.346600e-05 -1.124880e-04 1.337940e-03 3.514000e-05 -3.489000e-05 6.368380e-04 2.333900e-05 -2.375400e-05 ''') ImportString('w_vapec_norm3,+,-',''' 1.085520e-04 1.291500e-05 -1.222280e-05 3.911400e-05 1.619060e-05 -5.843400e-06 2.486800e-04 1.895400e-05 -2.551400e-05 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('noreson_noi3_Z1,+,-',''' 3.254000e-01 3.310000e-02 -2.950000e-02 5.806000e-01 6.720000e-02 -5.710000e-02 8.501000e-01 7.960000e-02 -7.050000e-02 1.556000e+00 1.590000e-01 -1.410000e-01 1.597000e+00 1.620000e-01 -1.440000e-01 2.101000e+00 2.680000e-01 -2.300000e-01 1.464000e+00 1.170000e-01 -1.070000e-01 2.294000e+00 2.110000e-01 -1.890000e-01 1.780000e+00 1.290000e-01 -1.240000e-01 1.736000e+00 1.300000e-01 -1.180000e-01 1.345000e+00 8.600000e-02 -8.100000e-02 1.795000e+00 1.480000e-01 -1.360000e-01 8.523000e-01 9.960000e-02 -8.940000e-02 9.556000e-01 1.494000e-01 -1.398000e-01 5.893000e-01 1.068000e-01 -8.820000e-02 4.215000e-01 3.960000e-02 -3.850000e-02 ''') ImportString('e_vapec_vff2,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('w_vapec_T1,+,-',''' 3.429670e-01 3.245200e-02 -3.388400e-02 6.476780e-01 1.927100e-02 -2.232800e-02 7.306980e-01 3.712700e-02 -5.412700e-02 1.631900e+00 1.542000e-02 -1.974000e-02 1.712780e+00 6.577000e-02 -1.244000e-02 1.901260e+00 7.212000e-02 -1.669600e-01 2.074650e+00 5.018000e-02 -7.353000e-02 2.440710e+00 3.433000e-02 -3.345000e-02 2.148340e+00 1.832400e-01 -3.256000e-02 2.482780e+00 2.825000e-02 -1.962000e-02 2.099050e+00 5.173000e-02 -3.384000e-02 2.624040e+00 9.363000e-02 -1.872800e-01 2.069760e+00 2.444600e-01 -2.516800e-01 3.116520e+00 1.446100e-01 -9.084000e-02 3.720470e+00 8.894000e-02 -9.720000e-02 3.304820e+00 1.001500e-01 -1.017900e-01 ''') ImportString('w_vapec_T2,+,-',''' 8.690070e-01 4.510200e-02 -3.072000e-02 1.296370e+00 3.030000e-02 -3.584000e-02 1.363440e+00 7.932000e-02 -1.148000e-02 4.771230e+00 1.245700e+00 -1.217630e+00 4.984230e+00 9.058300e-01 -7.411100e-01 4.289610e+00 7.080600e-01 -8.033700e-01 4.054950e+00 9.031900e-01 -6.352800e-01 -1.000000e+00 0.000000e+00 0.000000e+00 6.121400e+00 2.550590e+00 -6.574600e-01 -1.000000e+00 0.000000e+00 0.000000e+00 5.332460e+00 7.370900e-01 -5.272800e-01 6.844790e+00 1.564160e+00 -1.555430e+00 4.751460e+00 6.110800e-01 -4.721100e-01 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('w_vapec_T3,+,-',''' 1.781740e+00 1.266800e-01 -1.164600e-01 -1.000000e+00 0.000000e+00 0.000000e+00 3.542100e+00 3.170700e-01 -3.936600e-01 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString(u'xmm_vapec_Fe_outer,+,-',''' 9.716710e-01 2.446800e-02 -3.534100e-02 6.921560e-01 3.152200e-02 -2.766700e-02 5.156140e-01 5.149700e-02 -3.500000e-02 5.237700e-01 2.780900e-02 -2.708700e-02 4.533620e-01 2.902700e-02 -2.945400e-02 3.195460e-01 2.948100e-02 -2.584100e-02 ''') ImportString('xmm_vapec_T2lim',''' -1.000000e+00 -1.000000e+00 7.000000e+00 7.000000e+00 7.000000e+00 7.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('e_vapec_S,+,-',''' 1.015330e+00 2.368500e-01 -2.076470e-01 2.095670e+00 3.632400e-01 -3.070200e-01 2.300860e+00 2.344600e-01 -2.137600e-01 1.881200e+00 1.742900e-01 -1.601700e-01 2.078350e+00 1.779500e-01 -1.653700e-01 2.014260e+00 1.549000e-01 -1.460000e-01 2.024250e+00 1.428700e-01 -1.363800e-01 1.435920e+00 1.395100e-01 -1.341600e-01 1.248200e+00 1.578400e-01 -1.560300e-01 9.812950e-01 1.363250e-01 -1.323830e-01 8.041630e-01 1.168520e-01 -1.199240e-01 1.012960e+00 1.484000e-01 -1.506200e-01 8.673690e-01 1.689410e-01 -1.633150e-01 7.010460e-01 1.817880e-01 -1.779650e-01 7.679440e-01 2.580860e-01 -2.523300e-01 ''') ImportString('noreson_noi3_r_ne1,+-',''' 6.572439e-01 6.572439e-01 1.971732e+00 6.572439e-01 3.812014e+00 1.183039e+00 6.178092e+00 1.183039e+00 8.675619e+00 1.314488e+00 1.130459e+01 1.314488e+00 1.472226e+01 2.103180e+00 1.892862e+01 2.103180e+00 2.366078e+01 2.628975e+00 2.891873e+01 2.628975e+00 3.417668e+01 2.628975e+00 3.943463e+01 2.628975e+00 4.469258e+01 2.628975e+00 4.995053e+01 2.628975e+00 5.573428e+01 3.154771e+00 6.204382e+01 3.154771e+00 ''') ImportString('e_vapec_chi2',''' 1.332930e+02 1.714160e+02 2.309590e+02 2.869450e+02 3.018510e+02 3.607710e+02 3.884980e+02 3.462930e+02 3.617740e+02 3.858490e+02 3.717910e+02 4.267050e+02 3.566420e+02 3.915720e+02 2.712790e+02 ''') ImportString('xmm_vapec_vff3,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_r,+,-',''' 6.572440e-01 6.572440e-01 -6.572440e-01 1.971730e+00 6.572440e-01 -6.572440e-01 3.812020e+00 1.183040e+00 -1.183040e+00 6.178090e+00 1.183040e+00 -1.183040e+00 8.675620e+00 1.314490e+00 -1.314490e+00 1.130460e+01 1.314490e+00 -1.314490e+00 1.472230e+01 2.103180e+00 -2.103180e+00 1.892860e+01 2.103180e+00 -2.103180e+00 2.366080e+01 2.628980e+00 -2.628980e+00 2.891870e+01 2.628980e+00 -2.628980e+00 3.417670e+01 2.628980e+00 -2.628980e+00 3.943460e+01 2.628980e+00 -2.628980e+00 4.469260e+01 2.628980e+00 -2.628980e+00 4.995050e+01 2.628980e+00 -2.628980e+00 5.573430e+01 3.154770e+00 -3.154770e+00 ''') ImportString('e_vapec_norm3,+,-',''' 1.292770e-04 1.407000e-05 -1.239100e-05 1.569400e-04 3.939200e-05 -4.959100e-05 3.595150e-04 4.458400e-05 -8.452000e-05 2.676830e-04 1.353790e-04 -1.015080e-04 4.252700e-04 1.826200e-04 -1.061770e-04 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_norm2,+,-',''' 1.378100e-04 2.917000e-05 -2.449100e-05 1.480820e-04 5.549200e-05 -3.986000e-05 4.601560e-04 6.177900e-05 -5.589800e-05 8.871530e-04 1.201470e-04 -1.392190e-04 8.913080e-04 1.179820e-04 -1.807330e-04 9.654580e-04 5.823200e-05 -2.638900e-04 9.542620e-04 8.449800e-05 -8.771400e-05 4.041940e-04 4.765970e-04 -2.001000e-04 1.520130e-03 2.504700e-04 -4.952500e-04 1.049150e-03 3.007400e-04 -3.004370e-04 4.979740e-04 6.114300e-05 -5.935600e-05 1.821650e-03 4.154000e-05 -2.502000e-05 1.448620e-03 1.015700e-04 -5.265000e-05 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_Si,+,-',''' 1.267530e+00 2.731200e-01 -2.229700e-01 1.643210e+00 3.023800e-01 -2.542200e-01 2.088980e+00 2.078000e-01 -1.888200e-01 1.744580e+00 1.502400e-01 -1.380100e-01 2.110290e+00 1.628700e-01 -1.508600e-01 2.070400e+00 1.465900e-01 -1.370200e-01 2.096770e+00 1.290500e-01 -1.228100e-01 1.510690e+00 1.231400e-01 -1.271200e-01 1.492930e+00 1.330500e-01 -1.263900e-01 1.077560e+00 1.105200e-01 -1.054800e-01 1.148770e+00 1.019600e-01 -1.010600e-01 1.073640e+00 1.228400e-01 -1.158160e-01 1.027980e+00 1.382300e-01 -1.277980e-01 9.082630e-01 1.454670e-01 -1.397210e-01 7.961150e-01 2.089550e-01 -1.908520e-01 ''') ImportString('e_vapec_T2lim',''' -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('xmm_vapec_T3,+,-',''' 5.436570e+00 1.041910e+00 -1.266280e+00 5.842940e+00 2.061370e+00 -1.165890e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_nH,+,-',''' 2.097500e-01 4.803600e-02 -3.979400e-02 1.219790e-01 1.414400e-02 -1.373800e-02 1.173800e-01 7.217000e-03 -7.083000e-03 1.240180e-01 6.085000e-03 -6.009000e-03 1.238180e-01 5.443000e-03 -5.365000e-03 1.142350e-01 4.958000e-03 -4.887000e-03 1.085810e-01 4.126000e-03 -4.076000e-03 1.047080e-01 4.954000e-03 -4.402000e-03 9.670090e-02 3.842100e-03 -3.794200e-03 9.809700e-02 4.181000e-03 -4.117400e-03 8.893080e-02 3.779700e-03 -3.577800e-03 1.061510e-01 3.466000e-03 -4.271000e-03 1.067240e-01 4.733000e-03 -4.871000e-03 8.918740e-02 4.958400e-03 -4.882900e-03 8.897070e-02 7.264700e-03 -6.985100e-03 ''') ImportString('w_vapec_chi2',''' 1.610190e+02 1.603230e+02 2.225990e+02 2.866170e+02 3.083730e+02 3.059660e+02 3.603620e+02 3.288060e+02 4.123440e+02 3.759790e+02 3.946320e+02 3.870070e+02 4.222800e+02 3.660120e+02 3.504910e+02 3.622920e+02 ''') ImportString('xmm_vapec_chi2',''' 6.537200e+02 7.356420e+02 7.588410e+02 6.942740e+02 7.495430e+02 6.829290e+02 7.499200e+02 7.353300e+02 7.619050e+02 7.007340e+02 ''') ImportString('xmm_vapec_Ca,+,-',''' 1.831400e+00 4.120700e-01 -4.035700e-01 3.184550e+00 3.999300e-01 -3.875200e-01 1.449420e+00 2.756400e-01 -2.732400e-01 1.606000e+00 2.794100e-01 -2.694300e-01 1.407790e+00 2.738500e-01 -2.719400e-01 1.108740e+00 2.720700e-01 -2.599300e-01 1.057800e+00 3.160700e-01 -3.117370e-01 1.313620e+00 3.035400e-01 -3.043900e-01 9.819120e-01 3.051580e-01 -3.061040e-01 1.258950e+00 3.305700e-01 -3.250920e-01 ''') ImportString('e_vapec_Mg,+,-',''' 9.647790e-01 2.735110e-01 -2.568160e-01 1.317530e+00 3.323500e-01 -2.766200e-01 1.595460e+00 2.493300e-01 -2.253500e-01 1.132020e+00 1.779600e-01 -1.634770e-01 1.288840e+00 1.892500e-01 -1.755900e-01 1.296630e+00 1.881000e-01 -1.756700e-01 1.259430e+00 1.604900e-01 -1.531300e-01 8.847890e-01 1.899710e-01 -1.992240e-01 9.626180e-01 2.023720e-01 -1.937020e-01 8.205670e-01 1.979030e-01 -1.879700e-01 1.007100e+00 1.734600e-01 -1.744780e-01 1.043060e+00 1.983100e-01 -1.884730e-01 6.483730e-01 2.154960e-01 -1.956090e-01 5.325700e-01 2.566990e-01 -2.480800e-01 1.060300e+00 3.914500e-01 -3.614740e-01 ''') ImportString('w_vapec_Fe,+,-',''' 1.142760e+00 2.147200e-01 -2.006420e-01 1.038570e+00 1.464800e-01 -1.187820e-01 1.420330e+00 8.939000e-02 -6.251000e-02 1.497210e+00 9.649000e-02 -7.899000e-02 1.583470e+00 8.227000e-02 -7.777000e-02 1.706010e+00 9.421000e-02 -8.042000e-02 1.700910e+00 6.814000e-02 -6.507000e-02 1.767900e+00 3.961000e-02 -5.456000e-02 1.492170e+00 4.743000e-02 -4.632000e-02 1.527640e+00 3.653000e-02 -5.372000e-02 1.387660e+00 5.398000e-02 -5.368000e-02 1.125260e+00 5.653000e-02 -5.886000e-02 8.423050e-01 5.660000e-02 -5.480100e-02 7.408840e-01 5.404300e-02 -5.544300e-02 7.804360e-01 5.989600e-02 -5.716000e-02 4.899940e-01 5.548800e-02 -5.086800e-02 ''') ImportString('e_vapec_T2,+,-',''' 8.170850e-01 2.926000e-02 -2.621100e-02 1.151240e+00 1.052100e-01 -1.349700e-01 1.350820e+00 9.763000e-02 -2.423000e-02 1.507710e+00 6.748000e-02 -8.969000e-02 1.524400e+00 8.070000e-02 -1.139300e-01 2.619730e+00 5.345200e-01 -1.073800e-01 3.855380e+00 3.534100e-01 -2.897800e-01 -1.000000e+00 0.000000e+00 0.000000e+00 4.073780e+00 8.236900e-01 -3.879600e-01 5.689180e+00 1.164600e+00 -8.292500e-01 -1.000000e+00 0.000000e+00 0.000000e+00 3.863980e+00 9.401000e-02 -1.095300e-01 3.987330e+00 2.703300e-01 -2.646600e-01 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('e_vapec_Fe,+,-',''' 1.048560e+00 2.058700e-01 -1.548750e-01 1.100720e+00 1.566200e-01 -1.362230e-01 1.458910e+00 1.215400e-01 -1.124400e-01 1.385790e+00 9.432000e-02 -8.882000e-02 1.554010e+00 1.010600e-01 -9.609000e-02 1.606450e+00 9.489000e-02 -8.930000e-02 1.529250e+00 6.969000e-02 -6.718000e-02 1.313880e+00 7.225000e-02 -8.319000e-02 1.225040e+00 6.154000e-02 -6.191000e-02 1.032810e+00 6.701000e-02 -6.126800e-02 9.529260e-01 4.919400e-02 -4.681300e-02 9.398610e-01 3.345500e-02 -4.384400e-02 8.007790e-01 6.300700e-02 -5.745300e-02 8.208400e-01 5.647500e-02 -5.382100e-02 7.343680e-01 7.786000e-02 -7.430000e-02 ''') ImportString('e_vapec_O,+,-',''' 1.346400e-01 5.562400e-02 -3.971320e-02 6.969920e-01 1.751980e-01 -1.496780e-01 1.121500e+00 1.855900e-01 -1.671610e-01 1.035620e+00 1.513500e-01 -1.393290e-01 1.320390e+00 1.781200e-01 -1.642600e-01 1.233930e+00 1.740000e-01 -1.612900e-01 1.157490e+00 1.552300e-01 -1.468100e-01 1.089620e+00 1.750400e-01 -1.667680e-01 1.250230e+00 1.991600e-01 -1.890700e-01 1.134050e+00 1.802400e-01 -1.723560e-01 4.286040e-01 1.519190e-01 -1.458140e-01 1.397420e+00 1.918800e-01 -2.280700e-01 1.145320e+00 2.479300e-01 -2.315990e-01 6.985830e-01 2.456170e-01 -2.332980e-01 3.928440e-01 3.369730e-01 -3.010350e-01 ''') ImportString('e_vapec_T1,+,-',''' 2.745850e-01 2.679900e-02 -1.995800e-02 6.248730e-01 1.719000e-02 -4.832400e-02 6.684300e-01 3.380700e-02 -2.551600e-02 8.386280e-01 1.535100e-02 -1.670900e-02 8.404180e-01 1.360610e-01 -5.980300e-02 1.359200e+00 1.303300e-01 -1.292000e-02 1.697460e+00 1.406000e-02 -1.566000e-02 2.455290e+00 2.301300e-01 -4.488900e-01 2.020880e+00 4.592100e-01 -3.485500e-01 2.340970e+00 2.791100e-01 -2.826700e-01 2.954450e+00 8.982000e-02 -4.641000e-02 1.351290e+00 6.710000e-02 -4.874000e-02 1.609720e+00 1.038300e-01 -2.500900e-01 3.658260e+00 8.712000e-02 -8.881000e-02 3.667030e+00 1.168800e-01 -1.413400e-01 ''') ImportString(u'xmm_vapec_r_outer,+,-',''' 3.954150e+01 5.557190e+00 -5.557190e+00 5.183140e+01 6.732740e+00 -6.732740e+00 6.700680e+01 8.442650e+00 -8.442650e+00 8.570890e+01 1.025940e+01 -1.025940e+01 1.066550e+02 1.068690e+01 -1.068690e+01 1.292050e+02 1.186250e+01 -1.186250e+01 ''') ImportString('xmm_vapec_Si,+,-',''' 1.739540e+00 1.247900e-01 -1.144500e-01 2.171690e+00 1.374300e-01 -1.299100e-01 1.572300e+00 1.008600e-01 -9.711000e-02 1.401390e+00 9.963000e-02 -9.862000e-02 1.098490e+00 8.679000e-02 -9.062000e-02 8.252330e-01 7.850700e-02 -8.600800e-02 5.724550e-01 8.708900e-02 -7.987100e-02 6.397480e-01 9.830000e-02 -9.599200e-02 4.286160e-01 9.423600e-02 -9.201000e-02 2.969530e-01 9.390500e-02 -8.422500e-02 ''') ImportString('xmm_vapec_T1,+,-',''' 7.451530e-01 1.805300e-02 -2.393200e-02 8.120480e-01 4.129900e-02 -4.986500e-02 2.470540e+00 3.541000e-02 -4.347000e-02 2.691670e+00 2.825000e-02 -3.749000e-02 2.802990e+00 3.743000e-02 -7.019000e-02 3.102100e+00 5.576000e-02 -8.149000e-02 2.568540e+00 3.260900e-01 -1.517700e-01 3.571740e+00 6.298000e-02 -6.324000e-02 3.441660e+00 7.434000e-02 -6.171000e-02 3.335980e+00 7.183000e-02 -6.483000e-02 ''') ImportString('w_vapec_nH,+,-',''' 1.696420e-01 3.464800e-02 -2.406000e-02 1.350730e-01 1.367400e-02 -1.322900e-02 1.236700e-01 5.154000e-03 -7.407000e-03 1.288200e-01 6.315000e-03 -6.219000e-03 1.118550e-01 5.665000e-03 -5.580000e-03 1.064620e-01 5.373000e-03 -5.287000e-03 1.090400e-01 4.065000e-03 -4.021000e-03 1.052890e-01 3.381000e-03 -2.799000e-03 1.108210e-01 3.548000e-03 -3.516000e-03 9.842950e-02 3.291500e-03 -2.165900e-03 9.790390e-02 3.714100e-03 -3.677700e-03 1.028250e-01 4.115000e-03 -3.926000e-03 9.456390e-02 4.577800e-03 -4.499600e-03 9.497500e-02 3.399500e-03 -3.577000e-03 9.387150e-02 4.809600e-03 -5.042700e-03 1.030820e-01 6.777000e-03 -6.634200e-03 ''') ImportString('xmm_vapec_T2,+,-',''' 1.629200e+00 3.216000e-02 -4.667000e-02 1.972270e+00 6.799000e-02 -8.689000e-02 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('w_vapec_norm2,+,-',''' 8.598860e-05 2.851040e-05 -1.850470e-05 2.836810e-04 4.044100e-05 -4.287600e-05 4.915330e-04 2.900100e-05 -5.081600e-05 1.702200e-04 6.290700e-05 -3.112700e-05 2.987300e-04 3.770600e-05 -5.824300e-05 3.801550e-04 1.882880e-04 -7.706600e-05 6.248600e-04 2.449640e-04 -1.631940e-04 1.637290e-04 2.614200e-05 -3.512200e-05 6.819840e-04 8.349600e-05 -2.745950e-04 3.474520e-04 4.324800e-05 -4.490000e-05 1.061070e-03 1.324800e-04 -1.253200e-04 5.232910e-04 3.006040e-04 -1.266120e-04 1.294470e-03 1.910600e-04 -2.227400e-04 2.848150e-04 1.667980e-04 -7.361000e-05 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('w_vapec_r,+,-',''' 6.572440e-01 6.572440e-01 -6.572440e-01 1.971730e+00 6.572440e-01 -6.572440e-01 3.812020e+00 1.183040e+00 -1.183040e+00 6.178090e+00 1.183040e+00 -1.183040e+00 8.675620e+00 1.314490e+00 -1.314490e+00 1.130460e+01 1.314490e+00 -1.314490e+00 1.472230e+01 2.103180e+00 -2.103180e+00 1.892860e+01 2.103180e+00 -2.103180e+00 2.366080e+01 2.628980e+00 -2.628980e+00 2.891870e+01 2.628980e+00 -2.628980e+00 3.417670e+01 2.628980e+00 -2.628980e+00 3.943460e+01 2.628980e+00 -2.628980e+00 4.469260e+01 2.628980e+00 -2.628980e+00 4.995050e+01 2.628980e+00 -2.628980e+00 5.573430e+01 3.154770e+00 -3.154770e+00 6.204380e+01 3.154770e+00 -3.154770e+00 ''') ImportString('xmm_vapec_S,+,-',''' 1.639220e+00 1.290200e-01 -1.186100e-01 1.719320e+00 1.249400e-01 -1.193700e-01 1.111410e+00 9.947000e-02 -9.676000e-02 1.106420e+00 1.039700e-01 -1.030900e-01 6.610860e-01 9.197000e-02 -9.063600e-02 4.159690e-01 9.123500e-02 -9.577800e-02 3.613560e-01 9.468600e-02 -9.361400e-02 2.907620e-01 1.097530e-01 -1.086120e-01 3.395760e-01 1.101070e-01 -1.087750e-01 2.753940e-01 1.162440e-01 -1.072240e-01 ''') ImportString('w_vapec_T3lim',''' -1.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('xmm_vapec_Ni,+,-',''' 4.160330e+00 4.458200e-01 -3.857100e-01 4.165420e+00 3.733000e-01 -3.566000e-01 2.831570e+00 3.059000e-01 -2.957900e-01 2.679100e+00 3.304500e-01 -3.274300e-01 2.077200e+00 3.032300e-01 -3.211800e-01 1.128640e+00 3.100900e-01 -3.214360e-01 1.468840e+00 3.218200e-01 -3.155600e-01 2.315950e+00 4.245700e-01 -4.105200e-01 1.277250e+00 4.047600e-01 -3.922850e-01 4.754150e-01 3.959910e-01 -3.548700e-01 ''') ImportString('xmm_vapec_norm2,+,-',''' 4.591550e-03 2.153000e-04 -2.821700e-04 5.623180e-03 3.000600e-04 -4.316700e-04 7.211340e-04 1.490140e-04 -1.228460e-04 6.590060e-04 1.561520e-04 -1.082900e-04 1.198030e-03 2.077400e-04 -1.154600e-04 1.063390e-03 3.241600e-04 -1.352120e-04 4.845880e-03 1.016690e-03 -2.026720e-03 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('xmm_vapec_norm3,+,-',''' 8.156340e-04 2.726360e-04 -1.208810e-04 9.477940e-04 4.479160e-04 -3.078780e-04 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('noreson_noi3_r_kT1,+-',''' 6.572439e-01 6.572439e-01 1.971732e+00 6.572439e-01 3.812014e+00 1.183039e+00 6.178092e+00 1.183039e+00 8.675619e+00 1.314488e+00 1.130459e+01 1.314488e+00 1.472226e+01 2.103180e+00 1.892862e+01 2.103180e+00 2.366078e+01 2.628975e+00 2.891873e+01 2.628975e+00 3.417668e+01 2.628975e+00 3.943463e+01 2.628975e+00 4.469258e+01 2.628975e+00 4.995053e+01 2.628975e+00 5.573428e+01 3.154771e+00 6.204382e+01 3.154771e+00 ''') ImportString('xmm_vapec_norm1,+,-',''' 5.592600e-04 3.378500e-05 -3.850700e-05 1.005050e-04 1.405600e-05 -1.527040e-05 8.063610e-03 2.150400e-04 -2.318900e-04 8.543630e-03 1.703300e-04 -2.436100e-04 9.320720e-03 1.271100e-04 -3.280600e-04 1.095930e-02 1.239000e-04 -3.805000e-04 9.223530e-03 1.938070e-03 -1.075730e-03 1.493910e-02 2.709000e-04 -2.690000e-04 1.567030e-02 3.119000e-04 -3.020000e-04 1.628450e-02 3.059000e-04 -3.594000e-04 ''') ImportString('e_vapec_dof',''' 1.270000e+02 1.680000e+02 2.510000e+02 2.710000e+02 2.900000e+02 3.050000e+02 3.490000e+02 3.570000e+02 3.730000e+02 3.800000e+02 3.790000e+02 3.770000e+02 3.580000e+02 3.470000e+02 2.740000e+02 ''') ImportString('e_vapec_T3,+,-',''' 1.654380e+00 9.472000e-02 -8.660000e-02 2.483200e+00 6.821800e-01 -2.939300e-01 3.102090e+00 4.408200e-01 -2.675500e-01 3.759760e+00 1.757310e+00 -7.349300e-01 3.524790e+00 7.288100e-01 -5.713200e-01 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') ImportString('xmm_vapec_T3lim',''' -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('w_vapec_Mg,+,-',''' 1.443820e+00 4.488400e-01 -3.804900e-01 1.025460e+00 2.845400e-01 -2.489260e-01 1.509200e+00 2.381100e-01 -1.671300e-01 1.263380e+00 1.980600e-01 -1.851200e-01 1.171410e+00 1.937000e-01 -1.824890e-01 1.174830e+00 2.153100e-01 -1.990720e-01 1.248480e+00 1.707600e-01 -1.635700e-01 1.351540e+00 1.486900e-01 -1.496600e-01 1.216970e+00 1.422500e-01 -1.376600e-01 1.141710e+00 1.334200e-01 -7.712000e-02 1.108660e+00 1.560500e-01 -1.506500e-01 8.064350e-01 1.732740e-01 -1.723520e-01 7.749050e-01 2.008770e-01 -1.918290e-01 2.166200e-01 1.906550e-01 -1.962510e-01 9.227620e-01 2.799280e-01 -2.578960e-01 6.504750e-01 2.851400e-01 -2.669250e-01 ''') ImportString('w_vapec_Ar,+,-',''' 2.649000e+00 8.616700e-01 -8.149900e-01 1.351970e+00 5.880700e-01 -5.512460e-01 2.126600e+00 4.235300e-01 -4.231400e-01 1.848280e+00 3.590100e-01 -3.466700e-01 2.158700e+00 3.675200e-01 -3.525700e-01 2.061160e+00 3.824700e-01 -3.759100e-01 1.948440e+00 2.814500e-01 -2.742000e-01 1.586480e+00 2.481800e-01 -2.498700e-01 1.654460e+00 2.363200e-01 -2.322700e-01 1.453280e+00 2.325500e-01 -2.335400e-01 1.597710e+00 2.877300e-01 -2.816300e-01 1.048670e+00 2.826400e-01 -2.806180e-01 1.514230e+00 4.059000e-01 -3.909400e-01 2.088770e-01 3.579440e-01 -2.088770e-01 8.714640e-14 3.041480e-01 -8.714640e-14 8.185000e-01 4.488100e-01 -4.513780e-01 ''') ImportString('xmm_vapec_Ne,+,-',''' 9.361670e-01 3.434030e-01 -3.412630e-01 1.385930e+00 4.084800e-01 -4.014490e-01 7.761310e-01 2.055630e-01 -2.028290e-01 1.434930e+00 2.199600e-01 -2.256800e-01 8.127890e-01 1.642670e-01 -1.513400e-01 5.347550e-01 1.531910e-01 -1.623020e-01 4.833630e-02 3.016400e-01 -4.833630e-02 1.199430e+00 1.827300e-01 -1.775700e-01 1.015950e+00 1.797700e-01 -1.816600e-01 1.104030e+00 2.023100e-01 -1.745800e-01 ''') ImportString('w_vapec_dof',''' 1.290000e+02 1.570000e+02 2.350000e+02 2.620000e+02 2.870000e+02 3.000000e+02 3.470000e+02 3.560000e+02 3.740000e+02 3.800000e+02 3.820000e+02 3.780000e+02 3.700000e+02 3.760000e+02 3.640000e+02 3.300000e+02 ''') ImportString('w_vapec_T2lim',''' -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 7.000000e+00 -1.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 -1.000000e+00 7.000000e+00 -1.000000e+00 -1.000000e+00 ''') ImportString('w_vapec_S,+,-',''' 1.408500e+00 3.357600e-01 -2.858500e-01 1.651380e+00 3.089800e-01 -2.527300e-01 1.979900e+00 2.176400e-01 -1.478400e-01 1.936210e+00 1.738300e-01 -1.611600e-01 1.945040e+00 1.687800e-01 -1.574800e-01 2.175680e+00 2.014400e-01 -1.715300e-01 1.846060e+00 1.372500e-01 -1.311700e-01 1.783530e+00 1.049900e-01 -1.044100e-01 1.614670e+00 1.037100e-01 -1.005100e-01 1.572580e+00 9.488000e-02 -9.432000e-02 1.520210e+00 1.214400e-01 -1.216800e-01 1.131780e+00 1.228300e-01 -1.195200e-01 7.961090e-01 1.542590e-01 -1.469910e-01 5.222720e-01 1.387780e-01 -1.415090e-01 6.046580e-01 1.818960e-01 -1.780230e-01 2.285100e-01 1.766680e-01 -1.734860e-01 ''') ImportString('xmm_vapec_r,+,-',''' 4.274760e+00 4.274760e+00 -4.274760e+00 1.207620e+01 3.526680e+00 -3.526680e+00 1.998450e+01 4.381630e+00 -4.381630e+00 2.917520e+01 4.809100e+00 -4.809100e+00 3.954150e+01 5.557190e+00 -5.557190e+00 5.183140e+01 6.732740e+00 -6.732740e+00 6.700680e+01 8.442650e+00 -8.442650e+00 8.570890e+01 1.025940e+01 -1.025940e+01 1.066550e+02 1.068690e+01 -1.068690e+01 1.292050e+02 1.186250e+01 -1.186250e+01 ''') ImportString('w_vapec_O,+,-',''' 2.820910e-01 1.099850e-01 -8.803400e-02 5.584030e-01 1.740170e-01 -1.553700e-01 1.160410e+00 1.350800e-01 -1.669170e-01 1.354080e+00 2.125000e-01 -1.953200e-01 1.110240e+00 1.925800e-01 -1.781900e-01 1.145610e+00 2.013700e-01 -1.836940e-01 1.222390e+00 1.633200e-01 -1.551600e-01 1.219230e+00 1.456400e-01 -7.133000e-02 1.128310e+00 1.341000e-01 -1.290310e-01 1.102700e+00 1.339600e-01 -1.305630e-01 9.983200e-01 1.440200e-01 -1.454460e-01 9.723590e-01 1.640610e-01 -1.566130e-01 7.209870e-01 1.929730e-01 -1.827270e-01 7.292290e-01 2.028710e-01 -1.976390e-01 1.000950e+00 2.560000e-01 -2.545000e-01 6.371190e-01 2.689800e-01 -2.528830e-01 ''') ImportString('xmm_vapec_Ar,+,-',''' 1.399770e+00 2.670500e-01 -2.525700e-01 1.063000e+00 2.476000e-01 -2.420860e-01 1.201370e+00 2.232400e-01 -2.208760e-01 1.255830e+00 2.363700e-01 -2.357600e-01 6.954610e-01 2.280740e-01 -2.275700e-01 5.353790e-01 2.350840e-01 -2.368460e-01 2.641940e-01 2.404860e-01 -2.395580e-01 2.382310e-01 2.706060e-01 -2.382310e-01 4.714250e-01 2.744610e-01 -2.733350e-01 3.079720e-01 2.870670e-01 -2.817710e-01 ''') ImportString('w_vapec_Ca,+,-',''' 3.290130e+00 1.403570e+00 -1.305190e+00 3.841850e+00 1.231350e+00 -1.173890e+00 3.051440e+00 6.177000e-01 -6.592300e-01 2.920440e+00 5.656700e-01 -5.558100e-01 2.948100e+00 5.408900e-01 -5.313500e-01 2.891460e+00 5.196500e-01 -5.198600e-01 2.912200e+00 3.722500e-01 -3.656700e-01 2.361340e+00 3.343000e-01 -3.316500e-01 2.851440e+00 3.285600e-01 -3.243900e-01 1.750890e+00 3.000500e-01 -2.965900e-01 2.519880e+00 3.827900e-01 -3.788500e-01 1.386630e+00 3.593800e-01 -3.527700e-01 1.219710e+00 4.797300e-01 -4.775630e-01 1.211200e+00 2.215500e-01 -4.342010e-01 1.614740e+00 5.005200e-01 -4.966500e-01 1.042920e+00 5.196800e-01 -5.249960e-01 ''') ImportString('e_vapec_Ni,+,-',''' 4.082670e+00 1.310920e+00 -1.127990e+00 4.101250e+00 1.156940e+00 -9.989900e-01 4.817130e+00 6.510000e-01 -5.913400e-01 3.220970e+00 4.730400e-01 -4.497700e-01 4.466750e+00 5.578800e-01 -4.933900e-01 3.953000e+00 4.378100e-01 -4.109100e-01 3.579040e+00 3.565500e-01 -3.365800e-01 2.080900e+00 3.506400e-01 -3.365800e-01 2.333900e+00 4.640900e-01 -4.354700e-01 1.824980e+00 3.859700e-01 -3.724500e-01 1.881650e+00 1.933200e-01 -1.933300e-01 3.146120e+00 5.366600e-01 -5.185200e-01 1.640190e+00 4.458200e-01 -5.189500e-01 1.919390e+00 5.963200e-01 -5.687500e-01 1.098390e+00 8.187400e-01 -7.483130e-01 ''') ImportString('xmm_vapec_Fe,+,-',''' 1.449610e+00 9.015000e-02 -8.800000e-02 1.678040e+00 9.248000e-02 -8.888000e-02 1.397970e+00 5.039000e-02 -4.878000e-02 1.268240e+00 4.763000e-02 -4.640000e-02 9.716710e-01 2.446800e-02 -3.534100e-02 6.921560e-01 3.152200e-02 -2.766700e-02 5.156140e-01 5.149700e-02 -3.500000e-02 5.237700e-01 2.780900e-02 -2.708700e-02 4.533620e-01 2.902700e-02 -2.945400e-02 3.195460e-01 2.948100e-02 -2.584100e-02 ''') ImportString('xmm_vapec_O,+,-',''' 7.382590e-01 1.463210e-01 -1.334080e-01 1.019530e+00 1.851200e-01 -1.710780e-01 5.370840e-01 1.360040e-01 -1.304450e-01 6.737480e-01 1.552830e-01 -1.464110e-01 5.347110e-01 1.152680e-01 -1.107980e-01 3.433930e-01 1.408490e-01 -6.574500e-02 5.139470e-02 1.300530e-01 -5.139470e-02 1.822440e-01 1.600090e-01 -1.547040e-01 1.246940e-01 1.629460e-01 -1.246940e-01 9.443200e-02 1.693000e-01 -9.443200e-02 ''') ImportString('xmm_vapec_dof',''' 4.660000e+02 5.490000e+02 6.120000e+02 6.320000e+02 6.560000e+02 6.810000e+02 6.920000e+02 7.090000e+02 7.110000e+02 7.200000e+02 ''') ImportString('e_vapec_Ne,+,-',''' 9.485340e-01 3.454360e-01 -3.276710e-01 8.456360e-01 5.568140e-01 -4.845330e-01 1.248710e+00 5.516000e-01 -5.101620e-01 9.210160e-01 4.313440e-01 -4.054500e-01 9.319650e-01 4.889850e-01 -4.620450e-01 9.424340e-01 4.867160e-01 -4.592030e-01 6.682280e-01 3.645820e-01 -3.539620e-01 1.236400e+00 7.678800e-01 -1.000600e+00 9.597320e-01 4.901580e-01 -4.750880e-01 9.478000e-01 4.819100e-01 -5.880230e-01 1.023460e+00 2.221100e-01 -2.123970e-01 0.000000e+00 2.795380e-01 0.000000e+00 1.295810e-02 5.087140e-01 -1.295810e-02 1.414180e+00 3.012500e-01 -2.875100e-01 1.083770e+00 4.088300e-01 -3.916840e-01 ''') ImportString('e_vapec_vff3,+,-',''' -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 -1.000000e+00 0.000000e+00 0.000000e+00 ''') Set('width', u'20cm') Add('page', name='flux', autoadd=False) To('flux') Add('graph', name='graph1', autoadd=False) To('graph1') Set('rightMargin', u'1.7cm') Add('axis', name='x', autoadd=False) To('x') Set('label', u'Radius (kpc)') Set('min', 8.0) Set('log', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('label', u'Optical surface brightness (mag arcsec^{-2})') Set('min', 31.0) Set('max', 22.0) Set('direction', 'vertical') To('..') Add('function', name='Bsb', autoadd=False) To('Bsb') Set('function', u'(1.39+22.6)-8.33+8.33*(x/15.43)**0.25') Set('key', u'Optical surface brightness') Set('Line/color', u'red') To('..') Add('function', name='jamesne', autoadd=False) To('jamesne') Set('function', u'((x/0.096)**-0.87-0.00055)*1.2') Set('key', u'Electron density') Set('yAxis', u'ydensity') Set('Line/color', u'black') Set('Line/style', u'dashed') To('..') Add('xy', name='FeW', autoadd=False) To('FeW') Set('xData', u'w_vapec_r') Set('yData', u'w_vapec_Fe') Set('key', u'Iron \\emph{Chandra} Western') Set('yAxis', u'yZ') Set('MarkerFill/color', u'blue') To('..') Add('xy', name='FeE', autoadd=False) To('FeE') Set('xData', u'e_vapec_r') Set('yData', u'e_vapec_Fe') Set('marker', u'cross') Set('key', u'Iron \\emph{Chandra} Eastern') Set('yAxis', u'yZ') Set('MarkerFill/color', u'grey') To('..') Add('axis', name='yZ', autoadd=False) To('yZ') Set('label', u'Iron metallicity (solar units)') Set('min', 0.29999999999999999) Set('max', 1.8999999999999999) Set('direction', u'vertical') Set('otherPosition', 0.80000000000000004) Set('MajorTicks/number', 8) To('..') Add('xy', name='FeWXMM', autoadd=False) To('FeWXMM') Set('xData', u'xmm_vapec_r_outer') Set('yData', u'xmm_vapec_Fe_outer') Set('marker', u'square') Set('key', u'Iron \\emph{XMM}') Set('yAxis', u'yZ') Set('MarkerFill/color', u'white') To('..') Add('axis', name='ydensity', autoadd=False) To('ydensity') Set('label', u'Electron density (cm^{-3})') Set('min', 0.001) Set('max', 0.029999999999999999) Set('log', True) Set('direction', u'vertical') Set('otherPosition', 1.0) To('..') Add('key', name='key1', autoadd=False) To('key1') Set('Text/size', u'14pt') Set('Border/hide', True) Set('horzPosn', u'left') To('..') To('..') To('..') Add('page', name='mag', autoadd=False) To('mag') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('min', 10.0) Set('max', 200.0) Set('log', True) To('..') Add('axis', name='y', autoadd=False) To('y') Set('min', 24.0) Set('max', 15.0) Set('direction', 'vertical') Set('MajorTicks/number', 8) To('..') Add('function', name='function1', autoadd=False) To('function1') Set('function', u'12.29+8.33*(x/172.)**0.25') To('..') To('..') To('..') veusz-1.15/windows/0000755002344000001440000000000011734662466014303 5ustar jssusers00000000000000veusz-1.15/windows/mainwindow.py0000644002344000001440000014507211734662204017030 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Implements the main window of the application.""" import os.path import sys import traceback import glob import veusz.qtall as qt4 import veusz.document as document import veusz.utils as utils import veusz.utils.vzdbus as vzdbus import veusz.setting as setting import veusz.plugins as plugins import consolewindow import plotwindow import treeeditwindow from datanavigator import DataNavigatorWindow from veusz.dialogs.aboutdialog import AboutDialog from veusz.dialogs.reloaddata import ReloadData from veusz.dialogs.datacreate import DataCreateDialog from veusz.dialogs.datacreate2d import DataCreate2DDialog from veusz.dialogs.preferences import PreferencesDialog from veusz.dialogs.errorloading import ErrorLoadingDialog from veusz.dialogs.capturedialog import CaptureDialog from veusz.dialogs.stylesheet import StylesheetDialog from veusz.dialogs.custom import CustomDialog from veusz.dialogs.safetyimport import SafetyImportDialog from veusz.dialogs.histodata import HistoDataDialog from veusz.dialogs.plugin import handlePlugin import veusz.dialogs.importdialog as importdialog import veusz.dialogs.dataeditdialog as dataeditdialog # shortcut to this setdb = setting.settingdb class DBusWinInterface(vzdbus.Object): """Simple DBus interface to window for triggering actions.""" interface = 'org.veusz.actions' def __init__(self, actions, index): prefix = '/Windows/%i/Actions' % index vzdbus.Object.__init__(self, vzdbus.sessionbus, prefix) self.actions = actions @vzdbus.method(dbus_interface=interface, out_signature='as') def GetActions(self): """Get list of actions which can be activated.""" return list(sorted(self.actions.keys())) @vzdbus.method(dbus_interface=interface, in_signature='s') def TriggerAction(self, action): """Activate action given.""" self.actions[unicode(action)].trigger() class MainWindow(qt4.QMainWindow): """ The main window class for the application.""" windows = [] @classmethod def CreateWindow(cls, filename=None): """Window factory function. If filename is given then that file is loaded into the window. Returns window created """ # create the window, and optionally load a saved file win = cls() win.show() if filename: # load document win.openFileInWindow(filename) else: win.setupDefaultDoc() # try to select first graph of first page win.treeedit.doInitialWidgetSelect() cls.windows.append(win) # check if tutorial wanted if not setting.settingdb['ask_tutorial']: win.askTutorial() # don't ask again setting.settingdb['ask_tutorial'] = True return win def __init__(self, *args): qt4.QMainWindow.__init__(self, *args) self.setAcceptDrops(True) # icon and different size variations d = utils.imagedir self.setWindowIcon( utils.getIcon('veusz') ) # master documenent self.document = document.Document() # filename for document and update titlebar self.filename = '' self.updateTitlebar() # keep a list of references to dialogs self.dialogs = [] # construct menus and toolbars self._defineMenus() # make plot window self.plot = plotwindow.PlotWindow(self.document, self, menu = self.menus['view']) self.setCentralWidget(self.plot) self.plot.showToolbar() # likewise with the tree-editing window self.treeedit = treeeditwindow.TreeEditDock(self.document, self) self.addDockWidget(qt4.Qt.LeftDockWidgetArea, self.treeedit) self.propdock = treeeditwindow.PropertiesDock(self.document, self.treeedit, self) self.addDockWidget(qt4.Qt.LeftDockWidgetArea, self.propdock) self.formatdock = treeeditwindow.FormatDock(self.document, self.treeedit, self) self.addDockWidget(qt4.Qt.LeftDockWidgetArea, self.formatdock) self.datadock = DataNavigatorWindow(self.document, self, self) self.addDockWidget(qt4.Qt.RightDockWidgetArea, self.datadock) # make the console window a dock self.console = consolewindow.ConsoleWindow(self.document, self) self.console.hide() self.interpreter = self.console.interpreter self.addDockWidget(qt4.Qt.BottomDockWidgetArea, self.console) # assemble the statusbar statusbar = self.statusbar = qt4.QStatusBar(self) self.setStatusBar(statusbar) self.updateStatusbar('Ready') # a label for the picker readout self.pickerlabel = qt4.QLabel(statusbar) self._setPickerFont(self.pickerlabel) statusbar.addPermanentWidget(self.pickerlabel) self.pickerlabel.hide() # plot queue - how many plots are currently being drawn self.plotqueuecount = 0 self.connect( self.plot, qt4.SIGNAL("queuechange"), self.plotQueueChanged ) self.plotqueuelabel = qt4.QLabel() self.plotqueuelabel.setToolTip("Number of rendering jobs remaining") statusbar.addWidget(self.plotqueuelabel) self.plotqueuelabel.show() # a label for the cursor position readout self.axisvalueslabel = qt4.QLabel(statusbar) statusbar.addPermanentWidget(self.axisvalueslabel) self.axisvalueslabel.show() self.slotUpdateAxisValues(None) # a label for the page number readout self.pagelabel = qt4.QLabel(statusbar) statusbar.addPermanentWidget(self.pagelabel) self.pagelabel.show() # working directory - use previous one self.dirname = setdb.get('dirname', qt4.QDir.homePath()) self.dirname_export = setdb.get('dirname_export', self.dirname) if setdb['dirname_usecwd']: self.dirname = self.dirname_export = os.getcwd() # connect plot signals to main window self.connect( self.plot, qt4.SIGNAL("sigUpdatePage"), self.slotUpdatePage ) self.connect( self.plot, qt4.SIGNAL("sigAxisValuesFromMouse"), self.slotUpdateAxisValues ) self.connect( self.plot, qt4.SIGNAL("sigPickerEnabled"), self.slotPickerEnabled ) self.connect( self.plot, qt4.SIGNAL("sigPointPicked"), self.slotUpdatePickerLabel ) # disable save if already saved self.connect( self.document, qt4.SIGNAL("sigModified"), self.slotModifiedDoc ) # if the treeeditwindow changes the page, change the plot window self.connect( self.treeedit, qt4.SIGNAL("sigPageChanged"), self.plot.setPageNumber ) # if a widget in the plot window is clicked by the user self.connect( self.plot, qt4.SIGNAL("sigWidgetClicked"), self.treeedit.selectWidget ) self.connect( self.treeedit, qt4.SIGNAL("widgetsSelected"), self.plot.selectedWidgets ) # enable/disable undo/redo self.connect(self.menus['edit'], qt4.SIGNAL('aboutToShow()'), self.slotAboutToShowEdit) #Get the list of recently opened files self.populateRecentFiles() self.setupWindowGeometry() self.defineViewWindowMenu() # if document requests it, ask whether an allowed import self.connect(self.document, qt4.SIGNAL('check_allowed_imports'), self.slotAllowedImportsDoc) # add on dbus interface self.dbusdocinterface = document.DBusInterface(self.document) self.dbuswininterface = DBusWinInterface( self.vzactions, self.dbusdocinterface.index) def updateStatusbar(self, text): '''Display text for a set period.''' self.statusBar().showMessage(text, 2000) def dragEnterEvent(self, event): """Check whether event is valid to be dropped.""" if (event.provides("text/uri-list") and self._getVeuszDropFiles(event)): event.acceptProposedAction() def dropEvent(self, event): """Respond to a drop event on the current window""" if event.provides("text/uri-list"): files = self._getVeuszDropFiles(event) if files: if self.document.isBlank(): self.openFileInWindow(files[0]) else: self.CreateWindow(files[0]) for filename in files[1:]: self.CreateWindow(filename) def _getVeuszDropFiles(self, event): """Return a list of veusz files from a drag/drop event containing a text/uri-list""" mime = event.mimeData() if not mime.hasUrls(): return [] else: # get list of vsz files dropped urls = [unicode(u.path()) for u in mime.urls()] urls = [u for u in urls if os.path.splitext(u)[1] == '.vsz'] return urls def setupDefaultDoc(self): """Setup default document.""" # add page and default graph self.document.makeDefaultDoc() # load defaults if set self.loadDefaultStylesheet() self.loadDefaultCustomDefinitions() def loadDefaultStylesheet(self): """Loads the default stylesheet for the new document.""" filename = setdb['stylesheet_default'] if filename: try: self.document.applyOperation( document.OperationLoadStyleSheet(filename) ) except EnvironmentError, e: qt4.QMessageBox.warning( self, "Error - Veusz", "Unable to load default stylesheet '%s'\n\n%s" % (filename, e.strerror)) else: # reset any modified flag self.document.setModified(False) self.document.changeset = 0 def loadDefaultCustomDefinitions(self): """Loads the custom definitions for the new document.""" filename = setdb['custom_default'] if filename: try: self.document.applyOperation( document.OperationLoadCustom(filename) ) except EnvironmentError, e: qt4.QMessageBox.warning( self, "Error - Veusz", "Unable to load custom definitions '%s'\n\n%s" % (filename, e.strerror)) else: # reset any modified flag self.document.setModified(False) self.document.changeset = 0 def slotAboutToShowEdit(self): """Enable/disable undo/redo menu items.""" # enable distable, and add appropriate text to describe # the operation being undone/redone canundo = self.document.canUndo() undotext = 'Undo' if canundo: undotext = "%s %s" % (undotext, self.document.historyundo[-1].descr) self.vzactions['edit.undo'].setText(undotext) self.vzactions['edit.undo'].setEnabled(canundo) canredo = self.document.canRedo() redotext = 'Redo' if canredo: redotext = "%s %s" % (redotext, self.document.historyredo[-1].descr) self.vzactions['edit.redo'].setText(redotext) self.vzactions['edit.redo'].setEnabled(canredo) def slotEditUndo(self): """Undo the previous operation""" if self.document.canUndo(): self.document.undoOperation() self.treeedit.checkWidgetSelected() def slotEditRedo(self): """Redo the previous operation""" if self.document.canRedo(): self.document.redoOperation() def slotEditPreferences(self): dialog = PreferencesDialog(self) dialog.exec_() def slotEditStylesheet(self): dialog = StylesheetDialog(self, self.document) self.showDialog(dialog) return dialog def slotEditCustom(self): dialog = CustomDialog(self, self.document) self.showDialog(dialog) return dialog def definePlugins(self, pluginlist, actions, menuname): """Create menu items and actions for plugins. pluginlist: list of plugin classes actions: dict of actions to add new actions to menuname: string giving prefix for new menu entries (inside actions) """ menu = [] for pluginkls in pluginlist: def loaddialog(pluginkls=pluginkls): """Load plugin dialog""" handlePlugin(self, self.document, pluginkls) actname = menuname + '.' + '.'.join(pluginkls.menu) text = pluginkls.menu[-1] if pluginkls.has_parameters: text += '...' actions[actname] = utils.makeAction( self, pluginkls.description_short, text, loaddialog) # build up menu from tuple of names menulook = menu namebuild = [menuname] for cmpt in pluginkls.menu[:-1]: namebuild.append(cmpt) name = '.'.join(namebuild) for c in menulook: if c[0] == name: menulook = c[2] break else: menulook.append( [name, cmpt, []] ) menulook = menulook[-1][2] menulook.append(actname) return menu def _defineMenus(self): """Initialise the menus and toolbar.""" # these are actions for main menu toolbars and menus a = utils.makeAction self.vzactions = { 'file.new': a(self, 'New document', '&New', self.slotFileNew, icon='kde-document-new', key='Ctrl+N'), 'file.open': a(self, 'Open a document', '&Open...', self.slotFileOpen, icon='kde-document-open', key='Ctrl+O'), 'file.save': a(self, 'Save the document', '&Save', self.slotFileSave, icon='kde-document-save', key='Ctrl+S'), 'file.saveas': a(self, 'Save the current graph under a new name', 'Save &As...', self.slotFileSaveAs, icon='kde-document-save-as'), 'file.print': a(self, 'Print the document', '&Print...', self.slotFilePrint, icon='kde-document-print', key='Ctrl+P'), 'file.export': a(self, 'Export the current page', '&Export...', self.slotFileExport, icon='kde-document-export'), 'file.close': a(self, 'Close current window', 'Close Window', self.slotFileClose, icon='kde-window-close', key='Ctrl+W'), 'file.quit': a(self, 'Exit the program', '&Quit', self.slotFileQuit, icon='kde-application-exit', key='Ctrl+Q'), 'edit.undo': a(self, 'Undo the previous operation', 'Undo', self.slotEditUndo, icon='kde-edit-undo', key='Ctrl+Z'), 'edit.redo': a(self, 'Redo the previous operation', 'Redo', self.slotEditRedo, icon='kde-edit-redo', key='Ctrl+Shift+Z'), 'edit.prefs': a(self, 'Edit preferences', 'Preferences...', self.slotEditPreferences, icon='veusz-edit-prefs'), 'edit.custom': a(self, 'Edit custom functions and constants', 'Custom definitions...', self.slotEditCustom, icon='veusz-edit-custom'), 'edit.stylesheet': a(self, 'Edit stylesheet to change default widget settings', 'Default styles...', self.slotEditStylesheet, icon='settings_stylesheet'), 'view.edit': a(self, 'Show or hide edit window', 'Edit window', None, checkable=True), 'view.props': a(self, 'Show or hide property window', 'Properties window', None, checkable=True), 'view.format': a(self, 'Show or hide formatting window', 'Formatting window', None, checkable=True), 'view.console': a(self, 'Show or hide console window', 'Console window', None, checkable=True), 'view.datanav': a(self, 'Show or hide data navigator window', 'Data navigator window', None, checkable=True), 'view.maintool': a(self, 'Show or hide main toolbar', 'Main toolbar', None, checkable=True), 'view.datatool': a(self, 'Show or hide data toolbar', 'Data toolbar', None, checkable=True), 'view.viewtool': a(self, 'Show or hide view toolbar', 'View toolbar', None, checkable=True), 'view.edittool': a(self, 'Show or hide editing toolbar', 'Editing toolbar', None, checkable=True), 'view.addtool': a(self, 'Show or hide insert toolbar', 'Insert toolbar', None, checkable=True), 'data.import': a(self, 'Import data into Veusz', '&Import...', self.slotDataImport, icon='kde-vzdata-import'), 'data.edit': a(self, 'Edit existing datasets', '&Edit...', self.slotDataEdit, icon='kde-edit-veuszedit'), 'data.create': a(self, 'Create new datasets', '&Create...', self.slotDataCreate, icon='kde-dataset-new-veuszedit'), 'data.create2d': a(self, 'Create new 2D datasets', 'Create &2D...', self.slotDataCreate2D, icon='kde-dataset2d-new-veuszedit'), 'data.capture': a(self, 'Capture remote data', 'Ca&pture...', self.slotDataCapture, icon='veusz-capture-data'), 'data.histogram': a(self, 'Histogram data', '&Histogram...', self.slotDataHistogram, icon='button_bar'), 'data.reload': a(self, 'Reload linked datasets', '&Reload', self.slotDataReload, icon='kde-view-refresh'), 'help.home': a(self, 'Go to the Veusz home page on the internet', 'Home page', self.slotHelpHomepage), 'help.project': a(self, 'Go to the Veusz project page on the internet', 'GNA Project page', self.slotHelpProjectPage), 'help.bug': a(self, 'Report a bug on the internet', 'Suggestions and bugs', self.slotHelpBug), 'help.tutorial': a(self, 'An interactive Veusz tutorial', 'Tutorial', self.slotHelpTutorial), 'help.about': a(self, 'Displays information about the program', 'About...', self.slotHelpAbout, icon='veusz') } # create main toolbar tb = self.maintoolbar = qt4.QToolBar("Main toolbar - Veusz", self) iconsize = setdb['toolbar_size'] tb.setIconSize(qt4.QSize(iconsize, iconsize)) tb.setObjectName('veuszmaintoolbar') self.addToolBar(qt4.Qt.TopToolBarArea, tb) utils.addToolbarActions(tb, self.vzactions, ('file.new', 'file.open', 'file.save', 'file.print', 'file.export')) # data toolbar tb = self.datatoolbar = qt4.QToolBar("Data toolbar - Veusz", self) tb.setIconSize(qt4.QSize(iconsize, iconsize)) tb.setObjectName('veuszdatatoolbar') self.addToolBar(qt4.Qt.TopToolBarArea, tb) utils.addToolbarActions(tb, self.vzactions, ('data.import', 'data.edit', 'data.create', 'data.capture', 'data.reload')) # menu structure filemenu = [ 'file.new', 'file.open', ['file.filerecent', 'Open &Recent', []], '', 'file.save', 'file.saveas', '', 'file.print', 'file.export', '', 'file.close', 'file.quit' ] editmenu = [ 'edit.undo', 'edit.redo', '', ['edit.select', '&Select', []], '', 'edit.prefs', 'edit.stylesheet', 'edit.custom', '' ] viewwindowsmenu = [ 'view.edit', 'view.props', 'view.format', 'view.console', 'view.datanav', '', 'view.maintool', 'view.viewtool', 'view.addtool', 'view.edittool' ] viewmenu = [ ['view.viewwindows', '&Windows', viewwindowsmenu], '' ] insertmenu = [ ] # load dataset plugins and create menu datapluginsmenu = self.definePlugins( plugins.datasetpluginregistry, self.vzactions, 'data.ops' ) datamenu = [ ['data.ops', '&Operations', datapluginsmenu], 'data.import', 'data.edit', 'data.create', 'data.create2d', 'data.capture', 'data.histogram', 'data.reload', ] helpmenu = [ 'help.home', 'help.project', 'help.bug', '', 'help.tutorial', '', ['help.examples', '&Example documents', []], '', 'help.about' ] # load tools plugins and create menu toolsmenu = self.definePlugins( plugins.toolspluginregistry, self.vzactions, 'tools' ) menus = [ ['file', '&File', filemenu], ['edit', '&Edit', editmenu], ['view', '&View', viewmenu], ['insert', '&Insert', insertmenu], ['data', '&Data', datamenu], ['tools', '&Tools', toolsmenu], ['help', '&Help', helpmenu], ] self.menus = {} utils.constructMenus(self.menuBar(), self.menus, menus, self.vzactions) self.populateExamplesMenu() def _setPickerFont(self, label): f = label.font() f.setBold(True) f.setPointSizeF(f.pointSizeF() * 1.2) label.setFont(f) def populateExamplesMenu(self): """Add examples to help menu.""" examples = glob.glob(os.path.join(utils.exampleDirectory, '*.vsz')) menu = self.menus["help.examples"] for ex in sorted(examples): name = os.path.splitext(os.path.basename(ex))[0] def _openexample(ex=ex): MainWindow.CreateWindow(ex) a = menu.addAction(name, _openexample) a.setStatusTip("Open %s example document" % name) def defineViewWindowMenu(self): """Setup View -> Window menu.""" def viewHideWindow(window): """Toggle window visibility.""" w = window def f(): w.setVisible(not w.isVisible()) return f # set whether windows are visible and connect up to toggle windows self.viewwinfns = [] for win, act in ((self.treeedit, 'view.edit'), (self.propdock, 'view.props'), (self.formatdock, 'view.format'), (self.console, 'view.console'), (self.datadock, 'view.datanav'), (self.maintoolbar, 'view.maintool'), (self.datatoolbar, 'view.datatool'), (self.treeedit.edittoolbar, 'view.edittool'), (self.treeedit.addtoolbar, 'view.addtool'), (self.plot.viewtoolbar, 'view.viewtool')): a = self.vzactions[act] fn = viewHideWindow(win) self.viewwinfns.append( (win, a, fn) ) self.connect(a, qt4.SIGNAL('triggered()'), fn) # needs to update state every time menu is shown self.connect(self.menus['view.viewwindows'], qt4.SIGNAL('aboutToShow()'), self.slotAboutToShowViewWindow) def slotAboutToShowViewWindow(self): """Enable/disable View->Window item check boxes.""" for win, act, fn in self.viewwinfns: act.setChecked(not win.isHidden()) def showDialog(self, dialog): """Show dialog given.""" self.connect(dialog, qt4.SIGNAL('dialogFinished'), self.deleteDialog) self.dialogs.append(dialog) dialog.show() self.emit( qt4.SIGNAL('dialogShown'), dialog ) def deleteDialog(self, dialog): """Remove dialog from list of dialogs.""" try: idx = self.dialogs.index(dialog) del self.dialogs[idx] except ValueError: pass def slotDataImport(self): """Display the import data dialog.""" dialog = importdialog.ImportDialog(self, self.document) self.showDialog(dialog) return dialog def slotDataEdit(self, editdataset=None): """Edit existing datasets. If editdataset is set to a dataset name, edit this dataset """ dialog = dataeditdialog.DataEditDialog(self, self.document) self.showDialog(dialog) if editdataset is not None: dialog.selectDataset(editdataset) return dialog def slotDataCreate(self): """Create new datasets.""" dialog = DataCreateDialog(self, self.document) self.showDialog(dialog) return dialog def slotDataCreate2D(self): """Create new datasets.""" dialog = DataCreate2DDialog(self, self.document) self.showDialog(dialog) return dialog def slotDataCapture(self): """Capture remote data.""" dialog = CaptureDialog(self.document, self) self.showDialog(dialog) return dialog def slotDataHistogram(self): """Histogram data.""" dialog = HistoDataDialog(self, self.document) self.showDialog(dialog) return dialog def slotDataReload(self): """Reload linked datasets.""" dialog = ReloadData(self.document, self) self.showDialog(dialog) return dialog def slotHelpHomepage(self): """Go to the veusz homepage.""" qt4.QDesktopServices.openUrl(qt4.QUrl('http://home.gna.org/veusz/')) def slotHelpProjectPage(self): """Go to the veusz project page.""" qt4.QDesktopServices.openUrl(qt4.QUrl('http://gna.org/projects/veusz/')) def slotHelpBug(self): """Go to the veusz bug page.""" qt4.QDesktopServices.openUrl( qt4.QUrl('https://gna.org/bugs/?group=veusz') ) def askTutorial(self): """Ask if tutorial wanted.""" retn = qt4.QMessageBox.question( self, "Veusz Tutorial", "Veusz includes a tutorial to help get you started.\n" "Would you like to start the tutorial now?\n" "If not, you can access it later through the Help menu.", qt4.QMessageBox.Yes | qt4.QMessageBox.No ) if retn == qt4.QMessageBox.Yes: self.slotHelpTutorial() def slotHelpTutorial(self): """Show a Veusz tutorial.""" if self.document.isBlank(): # run the tutorial from veusz.windows.tutorial import TutorialDock tutdock = TutorialDock(self.document, self, self) self.addDockWidget(qt4.Qt.RightDockWidgetArea, tutdock) tutdock.show() else: # open up a blank window for tutorial win = self.CreateWindow() win.slotHelpTutorial() def slotHelpAbout(self): """Show about dialog.""" AboutDialog(self).exec_() def queryOverwrite(self): """Do you want to overwrite the current document. Returns qt4.QMessageBox.(Yes,No,Cancel).""" # include filename in mesage box if we can filetext = '' if self.filename: filetext = " '%s'" % os.path.basename(self.filename) # show message box mb = qt4.QMessageBox("Save file?", "Document%s was modified. Save first?" % filetext, qt4.QMessageBox.Warning, qt4.QMessageBox.Yes | qt4.QMessageBox.Default, qt4.QMessageBox.No, qt4.QMessageBox.Cancel | qt4.QMessageBox.Escape, self) mb.setButtonText(qt4.QMessageBox.Yes, "&Save") mb.setButtonText(qt4.QMessageBox.No, "&Discard") mb.setButtonText(qt4.QMessageBox.Cancel, "&Cancel") return mb.exec_() def closeEvent(self, event): """Before closing, check whether we need to save first.""" # if the document has been modified then query user for saving if self.document.isModified(): v = self.queryOverwrite() if v == qt4.QMessageBox.Cancel: event.ignore() return elif v == qt4.QMessageBox.Yes: self.slotFileSave() # store working directory setdb['dirname'] = self.dirname setdb['dirname_export'] = self.dirname_export # store the current geometry in the settings database geometry = ( self.x(), self.y(), self.width(), self.height() ) setdb['geometry_mainwindow'] = geometry # store docked windows data = str(self.saveState()) setdb['geometry_mainwindowstate'] = data # save current setting db setdb.writeSettings() event.accept() def setupWindowGeometry(self): """Restoring window geometry if possible.""" # count number of main windows shown nummain = 0 for w in qt4.qApp.topLevelWidgets(): if isinstance(w, qt4.QMainWindow): nummain += 1 # if we can restore the geometry, do so if 'geometry_mainwindow' in setdb: geometry = setdb['geometry_mainwindow'] self.resize( qt4.QSize(geometry[2], geometry[3]) ) if nummain <= 1: self.move( qt4.QPoint(geometry[0], geometry[1]) ) # restore docked window geometry if 'geometry_mainwindowstate' in setdb: b = qt4.QByteArray(setdb['geometry_mainwindowstate']) self.restoreState(b) def slotFileNew(self): """New file.""" self.CreateWindow() def slotFileSave(self): """Save file.""" if self.filename == '': self.slotFileSaveAs() else: # show busy cursor qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.WaitCursor) ) try: ofile = open(self.filename, 'w') self.document.saveToFile(ofile) self.updateStatusbar("Saved to %s" % self.filename) except EnvironmentError, e: qt4.QApplication.restoreOverrideCursor() qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to save document as '%s'\n\n%s" % (self.filename, e.strerror)) else: # restore the cursor qt4.QApplication.restoreOverrideCursor() def updateTitlebar(self): """Put the filename into the title bar.""" if self.filename == '': self.setWindowTitle('Untitled - Veusz') else: self.setWindowTitle( "%s - Veusz" % os.path.basename(self.filename) ) def plotQueueChanged(self, incr): self.plotqueuecount += incr text = u'•' * self.plotqueuecount self.plotqueuelabel.setText(text) def _fileSaveDialog(self, filetype, filedescr, dialogtitle): """A generic file save dialog for exporting / saving.""" fd = qt4.QFileDialog(self, dialogtitle) fd.setDirectory(self.dirname) fd.setFileMode( qt4.QFileDialog.AnyFile ) fd.setAcceptMode( qt4.QFileDialog.AcceptSave ) fd.setFilter( "%s (*.%s)" % (filedescr, filetype) ) # okay was selected (and is okay to overwrite if it exists) if fd.exec_() == qt4.QDialog.Accepted: # save directory for next time self.dirname = fd.directory().absolutePath() # update the edit box filename = unicode( fd.selectedFiles()[0] ) if os.path.splitext(filename)[1] == '': filename += '.' + filetype return filename return None def _fileOpenDialog(self, filetype, filedescr, dialogtitle): """Display an open dialog and return a filename.""" fd = qt4.QFileDialog(self, dialogtitle) fd.setDirectory(self.dirname) fd.setFileMode( qt4.QFileDialog.ExistingFile ) fd.setAcceptMode( qt4.QFileDialog.AcceptOpen ) fd.setFilter( "%s (*.%s)" % (filedescr, filetype) ) # if the user chooses a file if fd.exec_() == qt4.QDialog.Accepted: # save directory for next time self.dirname = fd.directory().absolutePath() filename = unicode( fd.selectedFiles()[0] ) try: open(filename) except EnvironmentError, e: qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to open '%s'\n\n%s" % (filename, e.strerror)) return None return filename return None def slotFileSaveAs(self): """Save As file.""" filename = self._fileSaveDialog('vsz', 'Veusz script files', 'Save as') if filename: self.filename = filename self.updateTitlebar() self.slotFileSave() def openFile(self, filename): """Select whether to load the file in the current window or in a blank window and calls the appropriate loader""" if self.document.isBlank(): # If the file is new and there are no modifications, # reuse the current window self.openFileInWindow(filename) else: # create a new window self.CreateWindow(filename) class _unsafeCmdMsgBox(qt4.QMessageBox): """Show document is unsafe.""" def __init__(self, window, filename): qt4.QMessageBox.__init__(self, "Unsafe code in document", "The document '%s' contains potentially " "unsafe code which may damage your " "computer or data. Please check that the " "file comes from a " "trusted source." % filename, qt4.QMessageBox.Warning, qt4.QMessageBox.Yes, qt4.QMessageBox.No | qt4.QMessageBox.Default, qt4.QMessageBox.NoButton, window) self.setButtonText(qt4.QMessageBox.Yes, "C&ontinue anyway") self.setButtonText(qt4.QMessageBox.No, "&Stop loading") class _unsafeVeuszCmdMsgBox(qt4.QMessageBox): """Show document has unsafe Veusz commands.""" def __init__(self, window): qt4.QMessageBox.__init__(self, 'Unsafe Veusz commands', 'This Veusz document contains potentially' ' unsafe Veusz commands for Saving, ' 'Exporting or Printing. Please check that the' ' file comes from a trusted source.', qt4.QMessageBox.Warning, qt4.QMessageBox.Yes, qt4.QMessageBox.No | qt4.QMessageBox.Default, qt4.QMessageBox.NoButton, window) self.setButtonText(qt4.QMessageBox.Yes, "C&ontinue anyway") self.setButtonText(qt4.QMessageBox.No, "&Ignore command") def openFileInWindow(self, filename): """Actually do the work of loading a new document. """ # FIXME: This function suffers from spaghetti code # it needs splitting up into bits to make it clearer # the steps are fairly well documented below, however ##################################################### qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.WaitCursor) ) # read script try: script = open(filename, 'rU').read() except EnvironmentError, e: qt4.QApplication.restoreOverrideCursor() qt4.QMessageBox.critical( self, "Error - Veusz", "Cannot open document '%s'\n\n%s" % (filename, e.strerror)) self.setupDefaultDoc() return # check code for any security issues ignore_unsafe = setting.transient_settings['unsafe_mode'] if not ignore_unsafe: errors = utils.checkCode(script, securityonly=True) if errors: qt4.QApplication.restoreOverrideCursor() if ( self._unsafeCmdMsgBox(self, filename).exec_() == qt4.QMessageBox.No ): return ignore_unsafe = True # allow unsafe veusz commands below # set up environment to run script env = self.document.eval_context.copy() interface = document.CommandInterface(self.document) # allow safe commands as-is for cmd in interface.safe_commands: env[cmd] = getattr(interface, cmd) # define root node env['Root'] = interface.Root # wrap "unsafe" commands with a message box to check the user safenow = [ignore_unsafe] def _unsafeCaller(func): def wrapped(*args, **argsk): if not safenow[0]: qt4.QApplication.restoreOverrideCursor() if ( self._unsafeVeuszCmdMsgBox(self).exec_() == qt4.QMessageBox.No ): return safenow[0] = True func(*args, **argsk) return wrapped for name in interface.unsafe_commands: env[name] = _unsafeCaller(getattr(interface, name)) # save stdout and stderr, then redirect to console stdout, stderr = sys.stdout, sys.stderr sys.stdout = self.console.con_stdout sys.stderr = self.console.con_stderr # get ready to load document env['__file__'] = os.path.abspath(filename) self.document.wipe() self.document.suspendUpdates() # allow import to happen relative to loaded file interface.AddImportPath( os.path.dirname(os.path.abspath(filename)) ) try: # actually run script text exec script in env except Exception, e: # need to remember to restore stdout, stderr sys.stdout, sys.stderr = stdout, stderr # display error dialog if there is an error loading qt4.QApplication.restoreOverrideCursor() self.document.enableUpdates() i = sys.exc_info() backtrace = traceback.format_exception( *i ) d = ErrorLoadingDialog(self, filename, str(e), ''.join(backtrace)) d.exec_() return # need to remember to restore stdout, stderr sys.stdout, sys.stderr = stdout, stderr # document is loaded self.document.enableUpdates() self.document.setModified(False) self.document.clearHistory() # remember file for recent list self.addRecentFile(filename) # let the main window know self.filename = filename self.updateTitlebar() self.updateStatusbar("Opened %s" % filename) # use current directory of file if not using cwd mode if not setdb['dirname_usecwd']: self.dirname = os.path.dirname( os.path.abspath(filename) ) self.dirname_export = self.dirname # notify cmpts which need notification that doc has finished opening self.emit(qt4.SIGNAL("documentopened")) qt4.QApplication.restoreOverrideCursor() def addRecentFile(self, filename): """Add a file to the recent files list.""" recent = setdb['main_recentfiles'] filename = os.path.abspath(filename) if filename in recent: del recent[recent.index(filename)] recent.insert(0, filename) setdb['main_recentfiles'] = recent[:10] self.populateRecentFiles() def slotFileOpen(self): """Open an existing file in a new window.""" filename = self._fileOpenDialog('vsz', 'Veusz script files', 'Open') if filename: self.openFile(filename) def populateRecentFiles(self): """Populate the recently opened files menu with a list of recently opened files""" menu = self.menus["file.filerecent"] menu.clear() newMenuItems = [] if setdb['main_recentfiles']: files = [f for f in setdb['main_recentfiles'] if os.path.isfile(f)] self._openRecentFunctions = [] # add each recent file to menu for i, path in enumerate(files): def fileOpener(filename=path): self.openFile(filename) self._openRecentFunctions.append(fileOpener) newMenuItems.append(('filerecent%i' % i, 'Open File %s' % path, os.path.basename(path), 'file.filerecent', fileOpener, '', False, '')) menu.setEnabled(True) self.recentFileActions = utils.populateMenuToolbars( newMenuItems, self.maintoolbar, self.menus) else: menu.setEnabled(False) def slotFileExport(self): """Export the graph.""" # check there is a page if self.document.getNumberPages() == 0: qt4.QMessageBox.warning(self, "Error - Veusz", "No pages to export") return # File types we can export to in the form ([extensions], Name) fd = qt4.QFileDialog(self, 'Export page') fd.setDirectory( self.dirname_export ) fd.setFileMode( qt4.QFileDialog.AnyFile ) fd.setAcceptMode( qt4.QFileDialog.AcceptSave ) # Create a mapping between a format string and extensions filtertoext = {} # convert extensions to filter exttofilter = {} filters = [] # a list of extensions which are allowed validextns = [] formats = document.Export.formats for extns, name in formats: extensions = " ".join(["*." + item for item in extns]) # join eveything together to make a filter string filterstr = '%s (%s)' % (name, extensions) filtertoext[filterstr] = extns for e in extns: exttofilter[e] = filterstr filters.append(filterstr) validextns += extns fd.setNameFilters(filters) # restore last format if possible try: filt = setdb['export_lastformat'] fd.selectNameFilter(filt) extn = formats[filters.index(filt)][0][0] except (KeyError, IndexError, ValueError): extn = 'pdf' fd.selectNameFilter( exttofilter[extn] ) if self.filename: # try to convert current filename to export name filename = os.path.basename(self.filename) filename = os.path.splitext(filename)[0] + '.' + extn fd.selectFile(filename) if fd.exec_() == qt4.QDialog.Accepted: # save directory for next time self.dirname_export = fd.directory().absolutePath() filterused = str(fd.selectedFilter()) setdb['export_lastformat'] = filterused chosenextns = filtertoext[filterused] # show busy cursor qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.WaitCursor) ) filename = unicode( fd.selectedFiles()[0] ) # Add a default extension if one isn't supplied # this is the extension without the dot ext = os.path.splitext(filename)[1][1:] if (ext not in validextns) and (ext not in chosenextns): filename += "." + chosenextns[0] export = document.Export( self.document, filename, self.plot.getPageNumber(), bitmapdpi=setdb['export_DPI'], pdfdpi=setdb['export_DPI_PDF'], antialias=setdb['export_antialias'], color=setdb['export_color'], quality=setdb['export_quality'], backcolor=setdb['export_background'], svgtextastext=setdb['export_SVG_text_as_text'], ) try: export.export() except (RuntimeError, EnvironmentError), e: if isinstance(e, EnvironmentError): msg = e.strerror else: msg = unicode(e) qt4.QApplication.restoreOverrideCursor() qt4.QMessageBox.critical( self, "Error - Veusz", "Error exporting to file '%s'\n\n%s" % (filename, msg)) else: qt4.QApplication.restoreOverrideCursor() def slotFilePrint(self): """Print the document.""" if self.document.getNumberPages() == 0: qt4.QMessageBox.warning(self, "Error - Veusz", "No pages to print") return prnt = qt4.QPrinter(qt4.QPrinter.HighResolution) prnt.setColorMode(qt4.QPrinter.Color) prnt.setCreator('Veusz %s' % utils.version()) prnt.setDocName(self.filename) dialog = qt4.QPrintDialog(prnt, self) dialog.setMinMax(1, self.document.getNumberPages()) if dialog.exec_(): # get page range if dialog.printRange() == qt4.QAbstractPrintDialog.PageRange: # page range minval, maxval = dialog.fromPage(), dialog.toPage() else: # all pages minval, maxval = 1, self.document.getNumberPages() # pages are relative to zero minval -= 1 maxval -= 1 # reverse or forward order if prnt.pageOrder() == qt4.QPrinter.FirstPageFirst: pages = range(minval, maxval+1) else: pages = range(maxval, minval-1, -1) # if more copies are requested pages *= prnt.numCopies() # do the printing self.document.printTo( prnt, pages ) def slotModifiedDoc(self, ismodified): """Disable certain actions if document is not modified.""" # enable/disable file, save menu item self.vzactions['file.save'].setEnabled(ismodified) def slotFileClose(self): """File close window chosen.""" self.close() def slotFileQuit(self): """File quit chosen.""" qt4.qApp.closeAllWindows() def slotUpdatePage(self, number): """Update page number when the plot window says so.""" np = self.document.getNumberPages() if np == 0: self.pagelabel.setText("No pages") else: self.pagelabel.setText("Page %i/%i" % (number+1, np)) def slotUpdateAxisValues(self, values): """Update the position where the mouse is relative to the axes.""" if values: # construct comma separated text representing axis values valitems = [] for name, val in values.iteritems(): valitems.append('%s=%#.4g' % (name, val)) valitems.sort() self.axisvalueslabel.setText(', '.join(valitems)) else: self.axisvalueslabel.setText('No position') def slotPickerEnabled(self, enabled): if enabled: self.pickerlabel.setText('No point selected') self.pickerlabel.show() else: self.pickerlabel.hide() def slotUpdatePickerLabel(self, info): """Display the picked point""" xv, yv = info.coords xn, yn = info.labels xt, yt = info.displaytype ix = str(info.index) if ix: ix = '[' + ix + ']' # format values for display def fmt(val, dtype): if dtype == 'date': return utils.dateFloatToString(val) elif dtype == 'numeric': return '%0.5g' % val elif dtype == 'text': return val else: raise RuntimeError xtext = fmt(xv, xt) ytext = fmt(yv, yt) t = '%s: %s%s = %s, %s%s = %s' % ( info.widget.name, xn, ix, xtext, yn, ix, ytext) self.pickerlabel.setText(t) def slotAllowedImportsDoc(self, module, names): """Are allowed imports?""" d = SafetyImportDialog(self, module, names) d.exec_() veusz-1.15/windows/simplewindow.py0000644002344000001440000000504111734662204017364 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.document as document import plotwindow """ A simple window class for wrapping a plotwindow """ class SimpleWindow(qt4.QMainWindow): """ The main window class for the application.""" def __init__(self, title, doc=None): qt4.QMainWindow.__init__(self) self.setWindowTitle(title) self.document = doc if not doc: self.document = document.Document() self.plot = plotwindow.PlotWindow(self.document, self) self.toolbar = None self.setCentralWidget( self.plot ) def enableToolbar(self, enable=True): """Enable or disable the zoom toolbar in this window.""" if self.toolbar is None and enable: self.toolbar = self.plot.createToolbar(self, None) self.toolbar.show() if self.toolbar is not None and not enable: self.toolbar.close() self.toolbar = None def setZoom(self, zoom): """Zoom(zoom) Set the plot zoom level: This is a number to for the zoom from 1:1 or 'page': zoom to page 'width': zoom to fit width 'height': zoom to fit height """ if zoom == 'page': self.plot.slotViewZoomPage() elif zoom == 'width': self.plot.slotViewZoomWidth() elif zoom == 'height': self.plot.slotViewZoomHeight() else: self.plot.setZoomFactor(zoom) def setAntiAliasing(self, ison): """AntiAliasing(ison) Switches on or off anti aliasing in the plot.""" self.plot.antialias = ison self.plot.actionForceUpdate() veusz-1.15/windows/widgettree.py0000644002344000001440000002730711734662204017017 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Contains a model and view for handling a tree of widgets.""" import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document class WidgetTreeModel(qt4.QAbstractItemModel): """A model representing the widget tree structure. """ def __init__(self, document, parent=None): """Initialise using document.""" qt4.QAbstractItemModel.__init__(self, parent) self.document = document self.connect( self.document, qt4.SIGNAL("sigModified"), self.slotDocumentModified ) self.connect( self.document, qt4.SIGNAL("sigWiped"), self.slotDocumentModified ) # suspend signals to the view that the model has changed self.suspendmodified = False def slotDocumentModified(self): """The document has been changed.""" if not self.suspendmodified: # needs to be suspended within insert/delete row operations self.emit( qt4.SIGNAL('layoutChanged()') ) def columnCount(self, parent): """Return number of columns of data.""" return 2 def data(self, index, role): """Return data for the index given.""" # why do we get passed invalid indicies? :-) if not index.isValid(): return qt4.QVariant() column = index.column() obj = index.internalPointer() if role == qt4.Qt.DisplayRole: # return text for columns if column == 0: return qt4.QVariant(obj.name) elif column == 1: return qt4.QVariant(obj.typename) elif role == qt4.Qt.DecorationRole: # return icon for first column if column == 0: filename = 'button_%s' % obj.typename return qt4.QVariant(utils.getIcon(filename)) elif role == qt4.Qt.ToolTipRole: # provide tool tip showing description if obj.userdescription: return qt4.QVariant(obj.userdescription) elif role == qt4.Qt.TextColorRole: # show disabled looking text if object or any parent is hidden hidden = False p = obj while p is not None: if 'hide' in p.settings and p.settings.hide: hidden = True break p = p.parent # return brush for hidden widget text, based on disabled text if hidden: return qt4.QVariant(qt4.QPalette().brush(qt4.QPalette.Disabled, qt4.QPalette.Text)) # return nothing return qt4.QVariant() def setData(self, index, value, role): """User renames object. This renames the widget.""" widget = index.internalPointer() name = unicode(value.toString()) # check symbols in name if not utils.validateWidgetName(name): return False # check name not already used if widget.parent.hasChild(name): return False # actually rename the widget self.document.applyOperation( document.OperationWidgetRename(widget, name)) self.emit( qt4.SIGNAL( 'dataChanged(const QModelIndex &, const QModelIndex &)'), index, index ) return True def flags(self, index): """What we can do with the item.""" if not index.isValid(): return qt4.Qt.ItemIsEnabled flags = ( qt4.Qt.ItemIsEnabled | qt4.Qt.ItemIsSelectable | qt4.Qt.ItemIsDropEnabled ) if ( index.internalPointer() is not self.document.basewidget and index.column() == 0 ): # allow items other than root to be edited and dragged flags |= qt4.Qt.ItemIsEditable | qt4.Qt.ItemIsDragEnabled return flags def headerData(self, section, orientation, role): """Return the header of the tree.""" if orientation == qt4.Qt.Horizontal and role == qt4.Qt.DisplayRole: val = ('Name', 'Type')[section] return qt4.QVariant(val) return qt4.QVariant() def index(self, row, column, parent): """Construct an index for a child of parent.""" if parent.isValid(): # normal widget try: child = parent.internalPointer().children[row] except IndexError: return qt4.QModelIndex() else: # root widget child = self.document.basewidget return self.createIndex(row, column, child) def getWidgetIndex(self, widget): """Returns index for widget specified.""" return self.createIndex(widget.widgetSiblingIndex(), 0, widget) def parent(self, index): """Find the parent of the index given.""" parentobj = index.internalPointer().parent if parentobj is None: return qt4.QModelIndex() else: try: return self.createIndex(parentobj.widgetSiblingIndex(), 0, parentobj) except ValueError: return qt4.QModelIndex() def rowCount(self, index): """Return number of rows of children of index.""" if index.isValid(): return len(index.internalPointer().children) else: # always 1 root node return 1 def getSettings(self, index): """Return the settings for the index selected.""" obj = index.internalPointer() return obj.settings def getWidget(self, index): """Get associated widget for index selected.""" return index.internalPointer() def removeRows(self, row, count, parentindex): """Remove widgets from parent.""" if not parentindex.isValid(): return parent = self.getWidget(parentindex) self.suspendmodified = True self.beginRemoveRows(parentindex, row, row+count-1) # construct an operation for deleting the rows deleteops = [] for w in parent.children[row:row+count]: deleteops.append( document.OperationWidgetDelete(w) ) op = document.OperationMultiple(deleteops, descr="remove widget(s)") self.document.applyOperation(op) self.endRemoveRows() self.suspendmodified = False return True def supportedDropActions(self): """Supported drag and drop actions.""" return qt4.Qt.MoveAction | qt4.Qt.CopyAction def mimeData(self, indexes): """Get mime data for indexes.""" widgets = [idx.internalPointer() for idx in indexes] return document.generateWidgetsMime(widgets) def mimeTypes(self): """Accepted mime types.""" return [document.widgetmime] def dropMimeData(self, mimedata, action, row, column, parentindex): """User drags and drops widget.""" if action == qt4.Qt.IgnoreAction: return True if not mimedata.hasFormat(document.widgetmime): return False data = str(mimedata.data(document.widgetmime)) if parentindex.isValid(): parent = self.getWidget(parentindex) else: parent = self.document.basewidget # check parent supports child if not document.isMimeDropable(parent, data): return False # work out where row will be pasted startrow = row if row == -1: startrow = len(parent.children) # need to tell qt that these rows are being inserted, so that the # right number of rows are removed afterwards self.suspendmodified = True self.beginInsertRows(parentindex, startrow, startrow+document.getMimeWidgetCount(data)-1) op = document.OperationWidgetPaste(parent, data, index=startrow) self.document.applyOperation(op) self.endInsertRows() self.suspendmodified = False return True class WidgetTreeView(qt4.QTreeView): """A model view for viewing the widgets.""" def __init__(self, model, *args): qt4.QTreeView.__init__(self, *args) self.setModel(model) self.expandAll() # stretch header hdr = self.header() hdr.setStretchLastSection(False) hdr.setResizeMode(0, qt4.QHeaderView.Stretch) hdr.setResizeMode(1, qt4.QHeaderView.Custom) # setup drag and drop self.setSelectionMode(qt4.QAbstractItemView.ExtendedSelection) self.setDragEnabled(True) self.viewport().setAcceptDrops(True) self.setDropIndicatorShown(True) def testModifier(self, e): """Look for keyboard modifier for copy or move.""" if e.keyboardModifiers() & qt4.Qt.ControlModifier: e.setDropAction(qt4.Qt.CopyAction) else: e.setDropAction(qt4.Qt.MoveAction) def handleInternalMove(self, event): """Handle a move inside treeview.""" # make sure qt doesn't handle this event.setDropAction(qt4.Qt.IgnoreAction) event.ignore() if not self.viewport().rect().contains(event.pos()): return # get widget at event position index = self.indexAt(event.pos()) if not index.isValid(): index = self.rootIndex() # adjust according to drop indicator position row = -1 posn = self.dropIndicatorPosition() if posn == qt4.QAbstractItemView.AboveItem: row = index.row() index = index.parent() elif posn == qt4.QAbstractItemView.BelowItem: row = index.row() + 1 index = index.parent() if index.isValid(): parent = self.model().getWidget(index) data = str(event.mimeData().data(document.widgetmime)) if document.isMimeDropable(parent, data): # move the widget! parentpath = parent.path widgetpaths = document.getMimeWidgetPaths(data) ops = [] r = row for path in widgetpaths: ops.append( document.OperationWidgetMove(path, parentpath, r) ) if r >= 0: r += 1 self.model().document.applyOperation( document.OperationMultiple(ops, descr='move')) event.ignore() def dropEvent(self, e): """When an object is dropped on the view.""" self.testModifier(e) if e.source() is self and e.dropAction() == qt4.Qt.MoveAction: self.handleInternalMove(e) qt4.QTreeView.dropEvent(self, e) def dragMoveEvent(self, e): """Make items move by default and copy if Ctrl is held down.""" self.testModifier(e) qt4.QTreeView.dragMoveEvent(self, e) veusz-1.15/windows/tutorial.py0000644002344000001440000007047211734662204016520 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import os.path import veusz.qtall as qt4 import veusz.utils as utils import veusz.setting as setting class TutorialStep(qt4.QObject): def __init__(self, text, mainwin, nextstep=None, flash=None, disablenext=False, closestep=False, nextonsetting=None, nextonselected=None): """ nextstep is class next TutorialStep class to use If flash is set, flash widget disablenext: wait until nextStep is emitted before going to next slide closestep: add a close button nextonsetting: (setnpath, lambda val: ok) - check setting to go to next slide nextonselected: go to next if widget with name is selected """ qt4.QObject.__init__(self) self.text = text self.nextstep = nextstep self.flash = flash self.disablenext = disablenext self.closestep = closestep self.mainwin = mainwin self.nextonsetting = nextonsetting if nextonsetting is not None: self.connect( mainwin.document, qt4.SIGNAL('sigModified'), self.slotNextSetting ) self.nextonselected = nextonselected if nextonselected is not None: self.connect(mainwin.treeedit, qt4.SIGNAL('widgetsSelected'), self.slotWidgetsSelected) def slotNextSetting(self, *args): """Check setting to emit next.""" try: setn = self.mainwin.document.basewidget.prefLookup( self.nextonsetting[0]).get() if self.nextonsetting[1](setn): self.emit( qt4.SIGNAL('nextStep') ) except ValueError: pass def slotWidgetsSelected(self, widgets, *args): """Go to next page if widget selected.""" if len(widgets) == 1 and widgets[0].name == self.nextonselected: self.emit( qt4.SIGNAL('nextStep') ) ########################## ## Introduction to widgets class StepIntro(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Welcome to Veusz!

This tutorial aims to get you working with Veusz as quickly as possible.

You can close this tutorial at any time using the close button to the top-right of this panel. The tutorial can be replayed in the help menu.

Press Next to go to the next step

''', mainwin, nextstep=StepWidgets1) class StepWidgets1(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Widgets

Plots in Veusz are constructed from widgets. Different types of widgets are used to make different parts of a plot. For example, there are widgets for axes, for a graph, for plotting data and for plotting functions.

There are also special widgets. The grid widget arranges graphs inside it in a grid arrangement.

''', mainwin, nextstep=StepWidgets2) class StepWidgets2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Widget can often be placed inside each other. For instance, a graph widget is placed in a page widget or a grid widget. Plotting widgets are placed in graph widget.

You can have multiple widgets of different types. For example, you can have several graphs on the page, optionally arranged in a grid. Several plotting widgets and axis widgets can be put in a graph.

''', mainwin, nextstep=StepWidgetWin) class StepWidgetWin(TutorialStep): def __init__(self, mainwin): t = mainwin.treeedit TutorialStep.__init__( self, '''

Widget editing

The flashing window is the Editing window, which shows the widgets currently in the plot in a hierarchical tree. Each widget has a name (the left column) and a type (the right column).

Press Next to continue.

''', mainwin, nextstep=StepWidgetWinExpand, flash=t) class StepWidgetWinExpand(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

The graph widget is the currently selected widget.

Expand the graph widget - click the arrow or plus to its left in the editing window - and select the x axis widget.

''', mainwin, disablenext=True, nextonselected='x', nextstep=StepPropertiesWin) class StepPropertiesWin(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Widget properties

This window shows the properties of the currently selected widget, the x axis widget of the graph.

Enter a new label for the widget, by clicking in the text edit box to the right of "Label", typing some text and press the Enter key.

''', mainwin, flash = mainwin.propdock, disablenext = True, nextonsetting = ('/page1/graph1/x/label', lambda val: val != ''), nextstep = StepPropertiesWin2) class StepPropertiesWin2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Notice that the x axis label of your plot has now been updated. Veusz supports LaTeX style formatting for labels, so you could include superscripts, subscripts and fractions.

Other important axis properties include the minimum, maximum values of the axis and whether the axis is logarithmic.

Click Next to continue.

''', mainwin, nextstep=WidgetAdd) class WidgetAdd(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Adding widgets

The flashing Add Widget toolbar and the Insert menu add widgets to the document. New widgets are inserted in the currently selected widget, if possible, or its parents.

Hold your mouse pointer over one of the toolbar buttons to see a description of a widget type.

Press Next to continue.

''', mainwin, flash=mainwin.treeedit.addtoolbar, nextstep=FunctionAdd ) class FunctionAdd(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Add a function

We will now add a function plotting widget to the current graph.

Click on the flashing icon, or go to the Insert menu and choosing "Add function".

''', mainwin, flash=mainwin.treeedit.addtoolbar.widgetForAction( mainwin.vzactions['add.function']), disablenext=True, nextonsetting = ('/page1/graph1/function1/function', lambda val: val != ''), nextstep=FunctionSet) class FunctionSet(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

You have now added a function widget to the graph widget. By default function widgets plot y=x.

Go to the Function property and change the function to be x**2, plotting x squared.

(Veusz uses Python syntax for its functions, so the power operator is **, rather than ^)

''', mainwin, nextonsetting = ('/page1/graph1/function1/function', lambda val: val.strip() == 'x**2'), disablenext = True, nextstep=FunctionFormatting) class FunctionFormatting(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Formatting

Widgets have a number of formatting options. The Formatting window (flashing) shows the options for the currently selected widget, here the function widget.

Press Next to continue

''', mainwin, flash=mainwin.formatdock, nextstep=FunctionFormatLine) class FunctionFormatLine(TutorialStep): def __init__(self, mainwin): tb = mainwin.formatdock.tabwidget.tabBar() label = qt4.QLabel(" ") tb.setTabButton(1, qt4.QTabBar.LeftSide, label) TutorialStep.__init__( self, '''

Different types of formatting properties are grouped under separate tables. The options for drawing the function line are grouped under the flashing Line tab (%s).

Click on the Line tab to continue.

''' % utils.pixmapAsHtml(utils.getPixmap('settings_plotline.png')), mainwin, flash=label, disablenext=True, nextstep=FunctionLineFormatting) self.connect(tb, qt4.SIGNAL('currentChanged(int)'), self.slotCurrentChanged) def slotCurrentChanged(self, idx): if idx == 1: self.emit( qt4.SIGNAL('nextStep') ) class FunctionLineFormatting(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Veusz lets you choose a line style, thickness and color for the function line.

Choose a new line color for the line.

''', mainwin, disablenext=True, nextonsetting = ('/page1/graph1/function1/Line/color', lambda val: val.strip() != 'black'), nextstep=DataStart) ########### ## Datasets class DataStart(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Datasets

Many widgets in Veusz plot datasets. Datasets can be imported from files, entered manually or created from existing datasets using operations or expressions.

Imported data can be linked to an external file or embedded in the document.

Press Next to continue

''', mainwin, nextstep=DataImport) class DataImport(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Importing data

Let us start by importing data.

Click the flashing Data Import icon, or choose "Import..." From the Data menu.

''', mainwin, flash=mainwin.datatoolbar.widgetForAction( mainwin.vzactions['data.import']), disablenext=True, nextstep=DataImportDialog) # make sure we have the default delimiters for k in ( 'importdialog_csvdelimitercombo_HistoryCombo', 'importdialog_csvtextdelimitercombo_HistoryCombo' ): if k in setting.settingdb: del setting.settingdb[k] self.connect(mainwin, qt4.SIGNAL('dialogShown'), self.slotDialogShown ) def slotDialogShown(self, dialog): """Called when a dialog is opened in the main window.""" from veusz.dialogs.importdialog import ImportDialog if isinstance(dialog, ImportDialog): # make life easy by sticking in filename dialog.filenameedit.setText( os.path.join(utils.exampleDirectory, 'tutorialdata.csv')) # and choosing tab dialog.guessImportTab() # get rid of existing values self.emit( qt4.SIGNAL('nextStep') ) class DataImportDialog(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

This is the data import dialog. In this tutorial, we have selected an example CSV (comma separated value) file for you, but you would normally browse to find your data file.

This example file defines three datasets, alpha, beta and gamma, entered as columns in the CSV file.

Press Next to continue

''', mainwin, nextstep=DataImportDialog2) class DataImportDialog2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Veusz will try to guess the datatype - numeric, text or date - from the data in the file or you can specify it manually.

Several different data formats are supported in Veusz and plugins can be defined to import any data format. The Link option links data to the original file.

Click the Import button in the dialog.

''', mainwin, nextstep=DataImportDialog3, disablenext=True) self.connect( mainwin.document, qt4.SIGNAL('sigModified'), self.slotDocModified ) def slotDocModified(self): if 'alpha' in self.mainwin.document.data: self.emit( qt4.SIGNAL('nextStep') ) class DataImportDialog3(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Notice how Veusz has loaded the three different datasets from the file. You could carry on importing new datasets from the Import dialog box or reopen it later.

Close the Import dialog box.

''', mainwin, disablenext=True, nextstep=DataImportDialog4) self.timer = qt4.QTimer() self.connect( self.timer, qt4.SIGNAL('timeout()'), self.slotTimeout ) self.timer.start(200) def slotTimeout(self): from veusz.dialogs.importdialog import ImportDialog closed = True for dialog in self.mainwin.dialogs: if isinstance(dialog, ImportDialog): closed = False if closed: # move forward if no import dialog open self.emit( qt4.SIGNAL('nextStep') ) class DataImportDialog4(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

The Data viewing window (flashing) shows the currently loaded datasets in the document.

Hover your mouse over datasets to get information about them. You can see datasets in more detail in the Data Edit dialog box.

Click Next to continue

''', mainwin, flash=mainwin.datadock, nextstep=AddXYPlotter) ############## ## XY plotting class AddXYPlotter(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Plotting data

The point plotting widget plots datasets loaded in Veusz.

The flashing icon adds a point plotting (xy) widget. Click on this, or go to the Add menu and choose "Add xy".

''', mainwin, flash=mainwin.treeedit.addtoolbar.widgetForAction( mainwin.vzactions['add.xy']), disablenext=True, nextonsetting = ('/page1/graph1/xy1/xData', lambda val: val != ''), nextstep=SetXY_X) class SetXY_X(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

The datasets to be plotted are in the widget's properties.

Change the "X data" setting to be the alpha dataset. You can choose this from the drop down menu or type it.

''', mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy1/xData', lambda val: val == 'alpha'), nextstep=SetXY_Y) class SetXY_Y(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Change the "Y data" setting to be the beta dataset.

''', mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy1/yData', lambda val: val == 'beta'), nextstep=SetXYLine) class SetXYLine(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Veusz has now plotted the data on the graph. You can manipulate how the data are shown using the formatting settings.

Make sure that the line Formatting tab (%s) for the widget is selected.

Click on the check box next to the Hide option at the bottom, to hide the line plotted between the data points.

''' % utils.pixmapAsHtml(utils.getPixmap('settings_plotline.png')), mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy1/PlotLine/hide', lambda val: val), nextstep=SetXYFill) class SetXYFill(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Now we will change the point color.

Click on the "Marker fill (%s)" formatting tab. Change the fill color of the plotted data.

''' % utils.pixmapAsHtml(utils.getPixmap('settings_plotmarkerfill.png')), mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy1/MarkerFill/color', lambda val: val != 'black'), nextstep=AddXY2nd) class AddXY2nd(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Adding a second dataset

We will now plot dataset alpha against gamma on the same graph.

Add a second point plotting (xy) widget using the flashing icon, or go to the Add menu and choose "Add xy".

''', mainwin, flash=mainwin.treeedit.addtoolbar.widgetForAction( mainwin.vzactions['add.xy']), disablenext=True, nextonsetting = ('/page1/graph1/xy2/xData', lambda val: val != ''), nextstep=AddXY2nd_2) class AddXY2nd_2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Change the "X data" setting to be the alpha dataset.

''', mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy2/xData', lambda val: val == 'alpha'), nextstep=AddXY2nd_3) class AddXY2nd_3(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Next, change the "Y data" setting to be the gamma dataset.

''', mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy2/yData', lambda val: val == 'gamma'), nextstep=AddXY2nd_4) class AddXY2nd_4(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

We can fill regions under plots using the Fill Below Formatting tab (%s).

Go to this tab, and unselect the "Hide edge fill" option.

''' % utils.pixmapAsHtml(utils.getPixmap('settings_plotfillbelow.png')), mainwin, disablenext=True, nextonsetting = ('/page1/graph1/xy2/FillBelow/hide', lambda val: not val), nextstep=File1) class File1(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Saving

The document can be saved under the File menu, choosing "Save as...", or by clicking on the Save icon (flashing).

Veusz documents are simple text files which can be easily modified outside the program.

Click Next to continue

''', mainwin, flash=mainwin.maintoolbar.widgetForAction( mainwin.vzactions['file.save']), nextstep=File2) class File2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Exporting

The document can be exported in scalable (EPS, PDF, SVG and EMF) or bitmap formats.

The "Export..." command under the File menu exports the selected page. Alternatively, click on the Export icon (flashing).

Click Next to continue

''', mainwin, flash=mainwin.maintoolbar.widgetForAction( mainwin.vzactions['file.export']), nextstep=Cut1, ) class Cut1(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Cut and paste

Widgets can be cut and pasted to manipulate the document.

Select the "graph1" widget in the Editing window.

''', mainwin, disablenext=True, nextonselected='graph1', nextstep=Cut2) class Cut2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Now click the Cut icon (flashing) or choose "Cut" from the Edit menu.

This copies the currently selected widget to the clipboard and deletes it from the document.

''', mainwin, disablenext=True, flash=mainwin.treeedit.edittoolbar.widgetForAction( mainwin.vzactions['edit.cut']), nextstep=AddGrid) self.connect( mainwin.document, qt4.SIGNAL('sigModified'), self.slotCheckDelete ) def slotCheckDelete(self, *args): d = self.mainwin.document try: d.resolve(d.basewidget, '/page1/graph1') except ValueError: # success! self.emit( qt4.SIGNAL('nextStep') ) class AddGrid(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Adding a grid

Now we will add a grid widget to paste the graph back into.

Click on the flashing Grid widget icon, or choose "Add grid" from the Insert menu.

''', mainwin, flash=mainwin.treeedit.addtoolbar.widgetForAction( mainwin.vzactions['add.grid']), disablenext=True, nextonsetting = ('/page1/grid1/rows', lambda val: val != ''), nextstep=Paste1) class Paste1(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Now click the Paste icon (flashing) or choose "Paste" from the Edit menu.

This pastes back the widget from the clipboard.

''', mainwin, disablenext=True, flash=mainwin.treeedit.edittoolbar.widgetForAction( mainwin.vzactions['edit.paste']), nextonsetting = ('/page1/grid1/graph1/leftMargin', lambda val: val != ''), nextstep=Paste2) class Paste2(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

For a second time, click the Paste icon (flashing) or choose "Paste" from the Edit menu.

This adds a second copy of the original graph to the grid.

''', mainwin, disablenext=True, flash=mainwin.treeedit.edittoolbar.widgetForAction( mainwin.vzactions['edit.paste']), nextonsetting = ('/page1/grid1/graph2/leftMargin', lambda val: val != ''), nextstep=Paste3) class Paste3(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

Having the graphs side-by-side looks a bit messy. We would like to change the graphs to be arranged in rows.

Navigate to the grid1 widget properties. Change the number of columns to 1.

''', mainwin, disablenext=True, nextonsetting = ('/page1/grid1/columns', lambda val: val == 1), nextstep=Paste4) class Paste4(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

We could now adjust the margins of the graphs and the grid.

Axes can also be shared by the graphs of the grid by moving them into the grid widget. This shares the same axis scale for graphs.

Click Next to continue

''', mainwin, nextstep=EndStep) class EndStep(TutorialStep): def __init__(self, mainwin): TutorialStep.__init__( self, '''

The End

Thank you for working through this Veusz tutorial. We hope you enjoy using Veusz!

Please send comments, bug reports and suggestions to the developers via the mailing list.

You can try this tutorial again from the Help menu.

''', mainwin, closestep=True, disablenext=True) class TutorialDock(qt4.QDockWidget): '''A dock tutorial window.''' def __init__(self, document, mainwin, *args): qt4.QDockWidget.__init__(self, *args) self.setAttribute(qt4.Qt.WA_DeleteOnClose) self.setMinimumHeight(300) self.setWindowTitle('Tutorial - Veusz') self.setObjectName('veusztutorialwindow') self.setStyleSheet('background: lightyellow;') self.document = document self.mainwin = mainwin self.layout = l = qt4.QVBoxLayout() txtdoc = qt4.QTextDocument(self) txtdoc.setDefaultStyleSheet( "p.usercmd { color: blue; } " "h1 { font-size: x-large;} " "code { color: green;} " ) self.textedit = qt4.QTextEdit(readOnly=True) self.textedit.setDocument(txtdoc) l.addWidget(self.textedit) self.buttonbox = qt4.QDialogButtonBox() self.nextb = self.buttonbox.addButton( 'Next', qt4.QDialogButtonBox.ActionRole) self.connect(self.nextb, qt4.SIGNAL('clicked()'), self.slotNext) l.addWidget(self.buttonbox) # have to use a separate widget as dialog already has layout self.widget = qt4.QWidget() self.widget.setLayout(l) self.setWidget(self.widget) # timer for controlling flashing self.flashtimer = qt4.QTimer(self) self.connect(self.flashtimer, qt4.SIGNAL('timeout()'), self.slotFlashTimeout) self.flash = self.oldflash = None self.flashon = False self.flashct = 0 self.flashtimer.start(500) self.changeStep(StepIntro) def ensureShowFlashWidgets(self): '''Ensure we can see the widgets flashing.''' w = self.flash while w is not None: w.show() w = w.parent() def changeStep(self, stepklass): '''Apply the next step.''' # this is the current text self.step = stepklass(self.mainwin) # listen to step for next step self.connect(self.step, qt4.SIGNAL('nextStep'), self.slotNext) # update text self.textedit.setHtml(self.step.text) # handle requests for flashing self.flashct = 20 self.flashon = True self.flash = self.step.flash if self.flash is not None: self.ensureShowFlashWidgets() # enable/disable next button self.nextb.setEnabled(not self.step.disablenext) # add a close button if requested if self.step.closestep: closeb = self.buttonbox.addButton( 'Close', qt4.QDialogButtonBox.ActionRole) self.connect(closeb, qt4.SIGNAL('clicked()'), self.close) def slotFlashTimeout(self): '''Handle flashing of UI components.''' if self.flash is not self.oldflash and self.oldflash is not None: # clear any flashing on previous widget self.oldflash.setStyleSheet('') self.oldflash = None if self.flash: # set flash state and toggle variable if self.flashon: self.flash.setStyleSheet('background: yellow;') else: self.flash.setStyleSheet('') self.flashon = not self.flashon self.oldflash = self.flash # stop flashing after N iterations self.flashct -= 1 if self.flashct == 0: self.flash = None def slotNext(self): """Move to the next page of the tutorial.""" nextstepklass = self.step.nextstep if nextstepklass is not None: self.changeStep( nextstepklass ) veusz-1.15/windows/datanavigator.py0000644002344000001440000000261111734662204017467 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.qtall as qt4 from veusz.qtwidgets.datasetbrowser import DatasetBrowser class DataNavigatorWindow(qt4.QDockWidget): """A dock window containing a dataset browsing widget.""" def __init__(self, thedocument, mainwin, *args): qt4.QDockWidget.__init__(self, *args) self.setWindowTitle("Data - Veusz") self.setObjectName("veuszdatawindow") self.nav = DatasetBrowser(thedocument, mainwin, self) self.setWidget(self.nav) veusz-1.15/windows/consolewindow.py0000644002344000001440000002562711734662204017551 0ustar jssusers00000000000000# consolewindow.py # a python-like qt console # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import codeop import traceback import sys import veusz.qtall as qt4 import veusz.document as document import veusz.utils as utils import veusz.setting as setting # TODO - command line completion class _Writer(object): """ Class to behave like an output stream. Pipes input back to the specified function.""" def __init__(self, function): """ Set the function output is sent to.""" self.function = function def write(self, text): """ Send text to the output function.""" self.function(text) def flush(self): """ Does nothing as yet.""" pass class _CommandEdit(qt4.QLineEdit): """ A special class to allow entering of the command line. emits sigEnter if the return key is pressed, and returns command The edit control has a history (press up and down keys to access) """ def __init__(self, *args): qt4.QLineEdit.__init__(self, *args) self.history = [] self.history_posn = 0 self.entered_text = '' qt4.QObject.connect( self, qt4.SIGNAL("returnPressed()"), self.slotReturnPressed ) self.setToolTip("Input a python expression here and press enter" ) def slotReturnPressed(self): """ Called if the return key is pressed in the edit control.""" # retrieve the text command = unicode( self.text() ) self.setText("") # keep the command for history self.history.append( command ) self.history_posn = len(self.history) self.entered_text = '' # tell the console we have a command self.emit( qt4.SIGNAL("sigEnter"), command) historykeys = (qt4.Qt.Key_Up, qt4.Qt.Key_Down) def keyPressEvent(self, key): """ Overridden to handle history. """ qt4.QLineEdit.keyPressEvent(self, key) code = key.key() # check whether one of the "history keys" has been pressed if code in _CommandEdit.historykeys: # look for the next or previous history item which our current text # is a prefix of if self.isModified(): text = unicode(self.text()) self.history_posn = len(self.history) else: text = self.entered_text if code == qt4.Qt.Key_Up: step = -1 elif code == qt4.Qt.Key_Down: step = 1 newpos = self.history_posn + step while True: if newpos >= len(self.history): break if newpos < 0: return if self.history[newpos].startswith(text): break newpos += step if newpos >= len(self.history): # go back to whatever the user had typed in self.history_posn = len(self.history) self.setText(self.entered_text) return # found a relevant history item self.history_posn = newpos # user has modified text since last set if self.isModified(): self.entered_text = text # replace the text in the control text = self.history[ self.history_posn ] self.setText(text) introtext=u'''Welcome to Veusz %s --- a scientific plotting application.
Copyright \u00a9 2003-2012 Jeremy Sanders <jeremy@jeremysanders.net> and contributors.
Veusz comes with ABSOLUTELY NO WARRANTY. Veusz is Free Software, and you are
welcome to redistribute it under certain conditions. Enter "GPL()" for details.
This window is a Python command line console and acts as a calculator.
''' % utils.version() class ConsoleWindow(qt4.QDockWidget): """ A python-like qt console.""" def __init__(self, thedocument, *args): qt4.QDockWidget.__init__(self, *args) self.setWindowTitle("Console - Veusz") self.setObjectName("veuszconsolewindow") # arrange sub-widgets in a vbox self.vbox = qt4.QWidget() self.setWidget(self.vbox) vlayout = qt4.QVBoxLayout(self.vbox) vlayout.setMargin( vlayout.margin()/4 ) vlayout.setSpacing( vlayout.spacing()/4 ) # start an interpreter instance to the document self.interpreter = document.CommandInterpreter(thedocument) self.document = thedocument # output from the interpreter goes to self.output_stdxxx self.con_stdout = _Writer(self.output_stdout) self.con_stderr = _Writer(self.output_stderr) self.interpreter.setOutputs(self.con_stdout, self.con_stderr) self.stdoutbuffer = "" self.stderrbuffer = "" # (mostly) hidden notification self._hiddennotify = qt4.QLabel() vlayout.addWidget(self._hiddennotify) self._hiddennotify.hide() # the output from the console goes here self._outputdisplay = qt4.QTextEdit() self._outputdisplay.setReadOnly(True) self._outputdisplay.insertHtml( introtext ) vlayout.addWidget(self._outputdisplay) self._hbox = qt4.QWidget() hlayout = qt4.QHBoxLayout(self._hbox) hlayout.setMargin(0) vlayout.addWidget(self._hbox) self._prompt = qt4.QLabel(">>>") hlayout.addWidget(self._prompt) # where commands are typed in self._inputedit = _CommandEdit() hlayout.addWidget(self._inputedit) self._inputedit.setFocus() # keep track of multiple line commands self.command_build = '' # get called if enter is pressed in the input control self.connect( self._inputedit, qt4.SIGNAL("sigEnter"), self.slotEnter ) # called if document logs something self.connect( thedocument, qt4.SIGNAL("sigLog"), self.slotDocumentLog ) def _makeTextFormat(self, cursor, color): fmt = cursor.charFormat() if color is not None: brush = qt4.QBrush(color) fmt.setForeground(brush) else: # use the default foreground color fmt.clearForeground() return fmt def appendOutput(self, text, style): """Add text to the tail of the error log, with a specified style""" if style == 'error': color = setting.settingdb.color('error') elif style == 'command': color = setting.settingdb.color('command') else: color = None cursor = self._outputdisplay.textCursor() cursor.movePosition(qt4.QTextCursor.End) cursor.insertText(text, self._makeTextFormat(cursor, color)) self._outputdisplay.setTextCursor(cursor) self._outputdisplay.ensureCursorVisible() def runFunction(self, func): """Execute the function within the console window, trapping exceptions.""" # preserve output streams temp_stdout = sys.stdout temp_stderr = sys.stderr sys.stdout = _Writer(self.output_stdout) sys.stderr = _Writer(self.output_stderr) # catch any exceptions, printing problems to stderr self.document.suspendUpdates() try: func() except: # print out the backtrace to stderr i = sys.exc_info() backtrace = traceback.format_exception( *i ) for l in backtrace: sys.stderr.write(l) self.document.enableUpdates() # return output streams sys.stdout = temp_stdout sys.stderr = temp_stderr def checkVisible(self): """If this window is hidden, show it, then hide it again in a few seconds.""" if self.isHidden(): self._hiddennotify.setText("This window will shortly disappear. " "You can bring it back by selecting " "View, Windows, Console Window on the " "menu.") qt4.QTimer.singleShot(5000, self.hideConsole) self.show() self._hiddennotify.show() def hideConsole(self): """Hide window and notification widget.""" self._hiddennotify.hide() self.hide() def output_stdout(self, text): """ Write text in stdout font to the log.""" self.checkVisible() self.appendOutput(text, 'normal') def output_stderr(self, text): """ Write text in stderr font to the log.""" self.checkVisible() self.appendOutput(text, 'error') def insertTextInOutput(self, text): """ Inserts the text into the log.""" self.appendOutput(text, 'normal') def slotEnter(self, command): """ Called if the return key is pressed in the edit control.""" newc = self.command_build + '\n' + command # check whether command can be compiled # c set to None if incomplete try: c = codeop.compile_command(newc) except Exception: # we want errors to be caught by self.interpreter.run below c = 1 # which prompt? prompt = '>>>' if self.command_build != '': prompt = '...' # output the command in the log pane self.appendOutput('%s %s\n' % (prompt, command), 'command') # are we ready to run this? if c is None or (len(command) != 0 and len(self.command_build) != 0 and (command[0] == ' ' or command[0] == '\t')): # build up the expression self.command_build = newc # modify the prompt self._prompt.setText( '...' ) else: # actually execute the command self.interpreter.run( unicode(newc) ) self.command_build = '' # modify the prompt self._prompt.setText( '>>>' ) def slotDocumentLog(self, text): """Output information if the document logs something.""" self.output_stderr(text + '\n') veusz-1.15/windows/plotwindow.py0000644002344000001440000013134111734662204017054 0ustar jssusers00000000000000# plotwindow.py # the main window for showing plots # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import sys from itertools import izip import traceback import veusz.qtall as qt4 import numpy as N import veusz.setting as setting import veusz.dialogs.exceptiondialog as exceptiondialog import veusz.document as document import veusz.utils as utils import veusz.widgets as widgets class PickerCrosshairItem( qt4.QGraphicsPathItem ): """The picker cross widget: it moves from point to point and curve to curve with the arrow keys, and hides itself when it looses focus""" def __init__(self, parent=None): path = qt4.QPainterPath() path.addRect(-4, -4, 8, 8) path.addRect(-5, -5, 10, 10) path.moveTo(-8, 0) path.lineTo(8, 0) path.moveTo(0, -8) path.lineTo(0, 8) qt4.QGraphicsPathItem.__init__(self, path, parent) self.setBrush(qt4.QBrush(qt4.Qt.black)) self.setFlags(self.flags() | qt4.QGraphicsItem.ItemIsFocusable) def paint(self, painter, option, widget): """Override this to enforce the global antialiasing setting""" aa = setting.settingdb['plot_antialias'] painter.save() painter.setRenderHint(qt4.QPainter.Antialiasing, aa) qt4.QGraphicsPathItem.paint(self, painter, option, widget) painter.restore() def focusOutEvent(self, event): qt4.QGraphicsPathItem.focusOutEvent(self, event) self.hide() class RenderControl(qt4.QObject): """Object for rendering plots in a separate thread.""" def __init__(self, plotwindow): """Start up numthreads rendering threads.""" qt4.QObject.__init__(self) self.sem = qt4.QSemaphore() self.mutex = qt4.QMutex() self.threads = [] self.exit = False self.latestjobs = [] self.latestaddedjob = -1 self.latestdrawnjob = -1 self.plotwindow = plotwindow self.updateNumberThreads() def updateNumberThreads(self, num=None): """Changes the number of rendering threads.""" if num is None: if qt4.QFontDatabase.supportsThreadedFontRendering(): # use number of threads in preference num = setting.settingdb['plot_numthreads'] else: # disable threads num = 0 if self.threads: # delete old ones self.exit = True self.sem.release(len(self.threads)) for t in self.threads: t.wait() del self.threads[:] self.exit = False # start new ones for i in xrange(num): t = RenderThread(self) t.start() self.threads.append(t) def exitThreads(self): """Exit threads started.""" self.updateNumberThreads(num=0) def processNextJob(self): """Take a job from the queue and process it. emits renderfinished(jobid, img, painthelper) when done, if job has not been superseded """ self.mutex.lock() jobid, helper = self.latestjobs[-1] del self.latestjobs[-1] lastadded = self.latestaddedjob self.mutex.unlock() # don't process jobs which have been superseded if lastadded == jobid: img = qt4.QImage(helper.pagesize[0], helper.pagesize[1], qt4.QImage.Format_ARGB32_Premultiplied) img.fill( setting.settingdb.color('page').rgb() ) painter = qt4.QPainter(img) aa = self.plotwindow.antialias painter.setRenderHint(qt4.QPainter.Antialiasing, aa) painter.setRenderHint(qt4.QPainter.TextAntialiasing, aa) helper.renderToPainter(painter) painter.end() self.mutex.lock() # just throw away result if it older than the latest one if jobid > self.latestdrawnjob: self.emit( qt4.SIGNAL("renderfinished"), jobid, img, helper ) self.latestdrawnjob = jobid self.mutex.unlock() # tell any listeners that a job has been processed self.plotwindow.emit( qt4.SIGNAL("queuechange"), -1 ) def addJob(self, helper): """Process drawing job in PaintHelper given.""" # indicate that there is a new item to be processed to listeners self.plotwindow.emit( qt4.SIGNAL("queuechange"), 1 ) # add the job to the queue self.mutex.lock() self.latestaddedjob += 1 self.latestjobs.append( (self.latestaddedjob, helper) ) self.mutex.unlock() if self.threads: # tell a thread to process job self.sem.release(1) else: # process job in current thread if multithreading disabled self.processNextJob() class RenderThread( qt4.QThread ): """A thread for processing rendering jobs. This is controlled by a RenderControl object """ def __init__(self, rendercontrol): qt4.QThread.__init__(self) self.rc = rendercontrol def run(self): """Repeat forever until told to exit. If it aquires 1 resource from the semaphore it will process the next job. """ while True: # wait until we can aquire the resources self.rc.sem.acquire(1) if self.rc.exit: break try: self.rc.processNextJob() except Exception: sys.stderr.write("Error in rendering thread\n") traceback.print_exc(file=sys.stderr) class PlotWindow( qt4.QGraphicsView ): """Class to show the plot(s) in a scrollable window.""" # how often the document can update updateintervals = ( (0, 'Disable'), (-1, 'On document change'), (100, 'Every 0.1s'), (250, 'Every 0.25s'), (500, 'Every 0.5s'), (1000, 'Every 1s'), (2000, 'Every 2s'), (5000, 'Every 5s'), (10000, 'Every 10s'), ) def __init__(self, document, parent, menu=None): """Initialise the window. menu gives a menu to add any menu items to """ qt4.QGraphicsView.__init__(self, parent) self.setBackgroundRole(qt4.QPalette.Dark) self.scene = qt4.QGraphicsScene() self.setScene(self.scene) # this graphics scene item is the actual graph pixmap = qt4.QPixmap(1, 1) self.dpi = (pixmap.logicalDpiX(), pixmap.logicalDpiY()) self.pixmapitem = self.scene.addPixmap(pixmap) # whether full screen mode self.isfullscreen = False # set to be parent's actions self.vzactions = None # for controlling plot elements g = self.controlgraphgroup = qt4.QGraphicsItemGroup() g.setHandlesChildEvents(False) self.scene.addItem(g) # zoom rectangle for zooming into graph (not shown normally) self.zoomrect = self.scene.addRect( 0, 0, 100, 100, qt4.QPen(qt4.Qt.DotLine) ) self.zoomrect.setZValue(2.) self.zoomrect.hide() # picker graphicsitem for marking the picked point self.pickeritem = PickerCrosshairItem() self.scene.addItem(self.pickeritem) self.pickeritem.setZValue(2.) self.pickeritem.hide() # all the widgets that picker key-navigation might cycle through self.pickerwidgets = [] # the picker state self.pickerinfo = widgets.PickInfo() # set up so if document is modified we are notified self.document = document self.docchangeset = -100 self.oldpagenumber = -1 self.connect(self.document, qt4.SIGNAL("sigModified"), self.slotDocModified) # state of last plot from painthelper self.painthelper = None self.lastwidgetsselected = [] self.oldzoom = -1. self.zoomfactor = 1. self.pagenumber = 0 self.ignoreclick = False # for rendering plots in separate threads self.rendercontrol = RenderControl(self) self.connect(self.rendercontrol, qt4.SIGNAL("renderfinished"), self.slotRenderFinished) # mode for clicking self.clickmode = 'select' self.currentclickmode = None # wheel zooming accumulator self.sumwheeldelta = 0 # set up redrawing timer self.timer = qt4.QTimer(self) self.connect( self.timer, qt4.SIGNAL('timeout()'), self.checkPlotUpdate ) # for drag scrolling self.grabpos = None self.scrolltimer = qt4.QTimer(self) self.scrolltimer.setSingleShot(True) # for turning clicking into scrolling after a period self.connect( self.scrolltimer, qt4.SIGNAL('timeout()'), self.slotBecomeScrollClick ) # get plot view updating policy # -1: update on document changes # 0: never update automatically # >0: check for updates every x ms self.interval = setting.settingdb['plot_updatepolicy'] # if using a time-based document update checking, start timer if self.interval > 0: self.timer.start(self.interval) # load antialias settings self.antialias = setting.settingdb['plot_antialias'] # allow window to get focus, to allow context menu self.setFocusPolicy(qt4.Qt.StrongFocus) # get mouse move events if mouse is not pressed self.setMouseTracking(True) # create toolbar in main window (urgh) self.createToolbar(parent, menu) def hideEvent(self, event): """Window closing, so exit rendering threads.""" self.rendercontrol.exitThreads() qt4.QGraphicsView.hideEvent(self, event) def sizeHint(self): """Return size hint for window.""" p = self.pixmapitem.pixmap() if p.width() <= 1 and p.height() <= 1: # if the document has been uninitialized, get the doc size return qt4.QSize(*self.document.docSize()) return p.size() def showToolbar(self, show=True): """Show or hide toolbar""" self.viewtoolbar.setVisible(show) def createToolbar(self, parent, menu=None): """Make a view toolbar, and optionally update menu.""" self.viewtoolbar = qt4.QToolBar("View toolbar - Veusz", parent) self.viewtoolbar.setObjectName('veuszviewtoolbar') iconsize = setting.settingdb['toolbar_size'] self.viewtoolbar.setIconSize(qt4.QSize(iconsize, iconsize)) self.viewtoolbar.hide() if parent: parent.addToolBar(qt4.Qt.TopToolBarArea, self.viewtoolbar) if parent and hasattr(parent, 'vzactions'): # share actions with parent if possible # as plot windows can be isolated from mainwindows, we need this self.vzactions = actions = parent.vzactions else: self.vzactions = actions = {} a = utils.makeAction actions.update({ 'view.zoomin': a(self, 'Zoom into the plot', 'Zoom &In', self.slotViewZoomIn, icon='kde-zoom-in', key='Ctrl++'), 'view.zoomout': a(self, 'Zoom out of the plot', 'Zoom &Out', self.slotViewZoomOut, icon='kde-zoom-out', key='Ctrl+-'), 'view.zoom11': a(self, 'Restore plot to natural size', 'Zoom 1:1', self.slotViewZoom11, icon='kde-zoom-1-veuszedit', key='Ctrl+1'), 'view.zoomwidth': a(self, 'Zoom plot to show whole width', 'Zoom to width', self.slotViewZoomWidth, icon='kde-zoom-width-veuszedit'), 'view.zoomheight': a(self, 'Zoom plot to show whole height', 'Zoom to height', self.slotViewZoomHeight, icon='kde-zoom-height-veuszedit'), 'view.zoompage': a(self, 'Zoom plot to show whole page', 'Zoom to page', self.slotViewZoomPage, icon='kde-zoom-page-veuszedit'), 'view.zoommenu': a(self, 'Zoom functions menu', 'Zoom', self.doZoomMenuButton, icon='kde-zoom-veuszedit'), 'view.prevpage': a(self, 'Move to the previous page', '&Previous page', self.slotViewPreviousPage, icon='kde-go-previous', key='Ctrl+PgUp'), 'view.nextpage': a(self, 'Move to the next page', '&Next page', self.slotViewNextPage, icon='kde-go-next', key='Ctrl+PgDown'), 'view.select': a(self, 'Select items from the graph or scroll', 'Select items or scroll', None, icon='kde-mouse-pointer'), 'view.pick': a(self, 'Read data points on the graph', 'Read data points', None, icon='veusz-pick-data'), 'view.zoomgraph': a(self, 'Zoom into graph', 'Zoom graph', None, icon='veusz-zoom-graph'), 'view.fullscreen': a(self, 'View plot full screen', 'Full screen', self.slotFullScreen, icon='veusz-view-fullscreen', key='Ctrl+F11'), }) if menu: # only construct menu if required menuitems = [ ('view', '', [ 'view.zoomin', 'view.zoomout', 'view.zoom11', 'view.zoomwidth', 'view.zoomheight', 'view.zoompage', '', 'view.prevpage', 'view.nextpage', 'view.fullscreen', '', 'view.select', 'view.pick', 'view.zoomgraph', ]), ] utils.constructMenus(menu, {'view': menu}, menuitems, actions) # populate menu on zoom menu toolbar icon zoommenu = qt4.QMenu(self) zoomag = qt4.QActionGroup(self) for act in ('view.zoomin', 'view.zoomout', 'view.zoom11', 'view.zoomwidth', 'view.zoomheight', 'view.zoompage'): a = actions[act] zoommenu.addAction(a) zoomag.addAction(a) a.vzname = act actions['view.zoommenu'].setMenu(zoommenu) self.connect(zoomag, qt4.SIGNAL('triggered(QAction*)'), self.zoomActionTriggered) lastzoom = setting.settingdb.get('view_defaultzoom', 'view.zoompage') self.updateZoomMenuButton(actions[lastzoom]) # add items to toolbar utils.addToolbarActions(self.viewtoolbar, actions, ('view.prevpage', 'view.nextpage', 'view.fullscreen', 'view.select', 'view.pick', 'view.zoomgraph', 'view.zoommenu')) # define action group for various different selection models grp = self.selectactiongrp = qt4.QActionGroup(self) grp.setExclusive(True) for a in ('view.select', 'view.pick', 'view.zoomgraph'): actions[a].setActionGroup(grp) actions[a].setCheckable(True) actions['view.select'].setChecked(True) self.connect( grp, qt4.SIGNAL('triggered(QAction*)'), self.slotSelectMode ) return self.viewtoolbar def zoomActionTriggered(self, action): """Keep track of the last zoom action selected.""" setting.settingdb['view_defaultzoom'] = action.vzname self.updateZoomMenuButton(action) def updateZoomMenuButton(self, action): """Make zoom button call default zoom action and change icon.""" menuact = self.vzactions['view.zoommenu'] setting.settingdb['view_defaultzoom'] = action.vzname menuact.setIcon( action.icon() ) def doZoomMenuButton(self): """Select previous zoom option when clicking on zoom menu.""" act = self.vzactions[setting.settingdb['view_defaultzoom']] act.trigger() def doZoomRect(self, endpos): """Take the zoom rectangle drawn by the user and do the zooming. endpos is a QPoint end point This is pretty messy - first we have to work out the graph associated to the first point Then we have to iterate over each of the plotters, identify their axes, and change the range of the axes to match the screen region selected. """ # safety net if self.grabpos is None or endpos is None: return # get points corresponding to corners of rectangle pt1 = self.grabpos pt2 = endpos # work out whether it's worthwhile to zoom: only zoom if there # are >=5 pixels movement if abs((pt2-pt1).x()) < 10 or abs((pt2-pt1).y()) < 10: return # try to work out in which widget the first point is in widget = self.painthelper.pointInWidgetBounds( pt1.x(), pt1.y(), widgets.Graph) if widget is None: return # convert points on plotter to points on axis for each axis xpts = N.array( [pt1.x(), pt2.x()] ) ypts = N.array( [pt1.y(), pt2.y()] ) # build up operation list to do zoom operations = [] axes = {} # iterate over children, to look for plotters for c in [i for i in widget.children if isinstance(i, widgets.GenericPlotter)]: # get axes associated with plotter caxes = c.parent.getAxes( (c.settings.xAxis, c.settings.yAxis) ) for a in caxes: if a: axes[a] = True # iterate over each axis, and update the ranges for axis in axes.iterkeys(): s = axis.settings if s.direction == 'horizontal': p = xpts else: p = ypts # convert points on plotter to axis coordinates # FIXME: Need To Trap Conversion Errors! try: r = axis.plotterToGraphCoords( self.painthelper.widgetBounds(axis), p) except KeyError: continue # invert if min and max are inverted if r[1] < r[0]: r[1], r[0] = r[0], r[1] # build up operations to change axis if s.min != r[0]: operations.append( document.OperationSettingSet(s.get('min'), float(r[0])) ) if s.max != r[1]: operations.append( document.OperationSettingSet(s.get('max'), float(r[1])) ) # finally change the axes self.document.applyOperation( document.OperationMultiple(operations,descr='zoom axes') ) def axesForPoint(self, mousepos): """Find all the axes which contain the given mouse position""" if self.painthelper is None: return [] pos = self.mapToScene(mousepos) px, py = pos.x(), pos.y() axes = [] for widget, bounds in self.painthelper.widgetBoundsIterator( widgettype=widgets.Axis): # if widget is axis, and point lies within bounds if ( px>=bounds[0] and px<=bounds[2] and py>=bounds[1] and py<=bounds[3] ): # convert correct pointer position if widget.settings.direction == 'horizontal': val = px else: val = py coords=widget.plotterToGraphCoords(bounds, N.array([val])) axes.append( (widget, coords[0]) ) return axes def emitPicked(self, pickinfo): """Report that a new point has been picked""" self.pickerinfo = pickinfo self.pickeritem.setPos(pickinfo.screenpos[0], pickinfo.screenpos[1]) self.emit(qt4.SIGNAL("sigPointPicked"), pickinfo) def doPick(self, mousepos): """Find the point on any plot-like widget closest to the cursor""" self.pickerwidgets = [] pickinfo = widgets.PickInfo() pos = self.mapToScene(mousepos) for w, bounds in self.painthelper.widgetBoundsIterator(): try: # ask the widget for its (visually) closest point to the cursor info = w.pickPoint(pos.x(), pos.y(), bounds) # this is a pickable widget, so remember it for future key navigation self.pickerwidgets.append(w) if info.distance < pickinfo.distance: # and remember the overall closest pickinfo = info except AttributeError: # ignore widgets that don't support axes or picking continue if not pickinfo: self.pickeritem.hide() return self.emitPicked(pickinfo) def slotBecomeScrollClick(self): """If the click is still down when this timer is reached then we turn the click into a scrolling click.""" if self.currentclickmode == 'select': qt4.QApplication.setOverrideCursor(qt4.QCursor(qt4.Qt.SizeAllCursor)) self.currentclickmode = 'scroll' def mousePressEvent(self, event): """Allow user to drag window around.""" qt4.QGraphicsView.mousePressEvent(self, event) # work out whether user is clicking on a control point # we have to ignore the item group which seems to be above # its constituents items = self.items(event.pos()) if len(items) > 0 and isinstance(items[0], qt4.QGraphicsItemGroup): del items[0] self.ignoreclick = len(items)==0 or items[0] is not self.pixmapitem if event.button() == qt4.Qt.LeftButton and not self.ignoreclick: # need to copy position, otherwise it gets reused! self.winpos = qt4.QPoint(event.pos()) self.grabpos = self.mapToScene(self.winpos) if self.clickmode == 'select': # we set this to true unless the timer runs out (400ms), # then it becomes a scroll click # scroll clicks drag the window around, and selecting clicks # select widgets! self.scrolltimer.start(400) elif self.clickmode == 'pick': self.pickeritem.show() self.pickeritem.setFocus(qt4.Qt.MouseFocusReason) self.doPick(event.pos()) elif self.clickmode == 'scroll': qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.SizeAllCursor)) elif self.clickmode == 'graphzoom': self.zoomrect.setRect(self.grabpos.x(), self.grabpos.y(), 0, 0) self.zoomrect.show() #self.label.drawRect(self.grabpos, self.grabpos) # record what mode we were clicked in self.currentclickmode = self.clickmode def mouseMoveEvent(self, event): """Scroll window by how much the mouse has moved since last time.""" qt4.QGraphicsView.mouseMoveEvent(self, event) if self.currentclickmode == 'scroll': event.accept() # move scroll bars by amount pos = event.pos() dx = self.winpos.x()-pos.x() scrollx = self.horizontalScrollBar() scrollx.setValue( scrollx.value() + dx ) dy = self.winpos.y()-pos.y() scrolly = self.verticalScrollBar() scrolly.setValue( scrolly.value() + dy ) # need to copy point self.winpos = qt4.QPoint(event.pos()) elif self.currentclickmode == 'graphzoom' and self.grabpos is not None: pos = self.mapToScene(event.pos()) r = self.zoomrect.rect() self.zoomrect.setRect( r.x(), r.y(), pos.x()-r.x(), pos.y()-r.y() ) elif self.clickmode == 'select' or self.clickmode == 'pick': # find axes which map to this position axes = self.axesForPoint(event.pos()) vals = dict([ (a[0].name, a[1]) for a in axes ]) self.emit( qt4.SIGNAL('sigAxisValuesFromMouse'), vals ) if self.currentclickmode == 'pick': # drag the picker around self.doPick(event.pos()) def mouseReleaseEvent(self, event): """If the mouse button is released, check whether the mouse clicked on a widget, and emit a sigWidgetClicked(widget).""" qt4.QGraphicsView.mouseReleaseEvent(self, event) if event.button() == qt4.Qt.LeftButton and not self.ignoreclick: event.accept() self.scrolltimer.stop() if self.currentclickmode == 'select': # work out where the mouse clicked and choose widget pos = self.mapToScene(event.pos()) self.locateClickWidget(pos.x(), pos.y()) elif self.currentclickmode == 'scroll': # return the cursor to normal after scrolling self.clickmode = 'select' self.currentclickmode = None qt4.QApplication.restoreOverrideCursor() elif self.currentclickmode == 'graphzoom': self.zoomrect.hide() self.doZoomRect(self.mapToScene(event.pos())) self.grabpos = None elif self.currentclickmode == 'viewgetclick': self.clickmode = 'select' elif self.currentclickmode == 'pick': self.currentclickmode = None def keyPressEvent(self, event): """Keypad motion moves the picker if it has focus""" if self.pickeritem.hasFocus(): k = event.key() if k == qt4.Qt.Key_Left or k == qt4.Qt.Key_Right: # navigate to the previous or next point on the curve event.accept() dir = 'right' if k == qt4.Qt.Key_Right else 'left' ix = self.pickerinfo.index pickinfo = self.pickerinfo.widget.pickIndex( ix, dir, self.painthelper.widgetBounds( self.pickerinfo.widget)) if pickinfo: # more points visible in this direction self.emitPicked(pickinfo) return elif k == qt4.Qt.Key_Up or k == qt4.Qt.Key_Down: # navigate to the next plot up or down on the screen event.accept() p = self.pickeritem.pos() oldw = self.pickerinfo.widget pickinfo = widgets.PickInfo() dist = float('inf') for w in self.pickerwidgets: if w == oldw: continue # ask the widgets to pick their point which is closest horizontally # to the last (screen) x value picked pi = w.pickPoint(self.pickerinfo.screenpos[0], p.y(), self.painthelper.widgetBounds(w), distance='horizontal') if not pi: continue dy = p.y() - pi.screenpos[1] # take the new point which is closest vertically to the current # one and either above or below it as appropriate if abs(dy) < dist and ( (k == qt4.Qt.Key_Up and dy > 0) or (k == qt4.Qt.Key_Down and dy < 0) ): pickinfo = pi dist = abs(dy) if pickinfo: oldx = self.pickerinfo.screenpos[0] self.emitPicked(pickinfo) # restore the previous x-position, so that vertical navigation # stays repeatable pickinfo.screenpos = (oldx, pickinfo.screenpos[1]) return # handle up-stream qt4.QGraphicsView.keyPressEvent(self, event) def wheelEvent(self, event): """For zooming in or moving.""" if event.modifiers() & qt4.Qt.ControlModifier: self.sumwheeldelta += event.delta() while self.sumwheeldelta <= -120: self.slotViewZoomOut() self.sumwheeldelta += 120 while self.sumwheeldelta >= 120: self.slotViewZoomIn() self.sumwheeldelta -= 120 else: qt4.QGraphicsView.wheelEvent(self, event) def locateClickWidget(self, x, y): """Work out which widget was clicked, and if necessary send a sigWidgetClicked(widget) signal.""" if self.document.getNumberPages() == 0: return widget = self.painthelper.identifyWidgetAtPoint( x, y, antialias=self.antialias) if widget is None: # select page if nothing clicked widget = self.document.basewidget.getPage(self.pagenumber) # tell connected objects that widget was clicked if widget is not None: self.emit( qt4.SIGNAL('sigWidgetClicked'), widget ) def setPageNumber(self, pageno): """Move the the selected page.""" # we don't need to do anything if (self.pagenumber == pageno and self.document.changeset == self.docchangeset): return # keep within bounds pageno = min(pageno, self.document.getNumberPages()-1) pageno = max(0, pageno) self.pagenumber = pageno if self.pagenumber != self.oldpagenumber and self.interval != 0: self.checkPlotUpdate() def getPageNumber(self): """Get the the selected page.""" return self.pagenumber def slotDocModified(self, ismodified): """Update plot on document being modified.""" # only update if doc is modified and the update policy is set # to update on document updates if ismodified and self.interval == -1: self.checkPlotUpdate() def checkPlotUpdate(self): """Check whether plot needs updating.""" # print >>sys.stderr, "checking update" # no threads, so can't get interrupted here # draw data into background pixmap if modified if ( self.zoomfactor != self.oldzoom or self.document.changeset != self.docchangeset or self.pagenumber != self.oldpagenumber ): # print >>sys.stderr, "updating" self.pickeritem.hide() self.pagenumber = min( self.document.getNumberPages() - 1, self.pagenumber ) self.oldpagenumber = self.pagenumber if self.pagenumber >= 0: size = self.document.pageSize( self.pagenumber, scaling=self.zoomfactor) # draw the data into the buffer # errors cause an exception window to pop up try: phelper = document.PaintHelper( size, scaling=self.zoomfactor, dpi=self.dpi) self.document.paintTo(phelper, self.pagenumber) except Exception: # stop updates this time round and show exception dialog d = exceptiondialog.ExceptionDialog(sys.exc_info(), self) self.oldzoom = self.zoomfactor self.docchangeset = self.document.changeset d.exec_() self.painthelper = phelper self.rendercontrol.addJob(phelper) else: self.painthelper = None self.pagenumber = 0 size = self.document.docSize() pixmap = qt4.QPixmap(*size) pixmap.fill( setting.settingdb.color('page') ) self.setSceneRect(0, 0, *size) self.pixmapitem.setPixmap(pixmap) self.emit( qt4.SIGNAL("sigUpdatePage"), self.pagenumber ) self.updatePageToolbar() self.updateControlGraphs(self.lastwidgetsselected) self.oldzoom = self.zoomfactor self.docchangeset = self.document.changeset def slotRenderFinished(self, jobid, img, helper): """Update image on display if rendering (usually in other thread) finished.""" bufferpixmap = qt4.QPixmap.fromImage(img) self.setSceneRect(0, 0, bufferpixmap.width(), bufferpixmap.height()) self.pixmapitem.setPixmap(bufferpixmap) def updatePlotSettings(self): """Update plot window settings from settings.""" self.setTimeout(setting.settingdb['plot_updatepolicy']) self.antialias = setting.settingdb['plot_antialias'] self.rendercontrol.updateNumberThreads() self.actionForceUpdate() def contextMenuEvent(self, event): """Show context menu.""" menu = qt4.QMenu(self) # add some useful entries menu.addAction( self.vzactions['view.zoommenu'] ) menu.addSeparator() menu.addAction( self.vzactions['view.prevpage'] ) menu.addAction( self.vzactions['view.nextpage'] ) menu.addSeparator() # force an update now menu item menu.addAction('Force update', self.actionForceUpdate) if self.isfullscreen: menu.addAction('Close full screen', self.slotFullScreen) else: menu.addAction( self.vzactions['view.fullscreen'] ) # Update policy submenu submenu = menu.addMenu('Updates') intgrp = qt4.QActionGroup(self) # bind interval options to actions for intv, text in self.updateintervals: act = intgrp.addAction(text) act.setCheckable(True) fn = utils.BoundCaller(self.actionSetTimeout, intv) self.connect(act, qt4.SIGNAL('triggered(bool)'), fn) if intv == self.interval: act.setChecked(True) submenu.addAction(act) # antialias menu.addSeparator() act = menu.addAction('Antialias', self.actionAntialias) act.setCheckable(True) act.setChecked(self.antialias) menu.exec_(qt4.QCursor.pos()) def actionForceUpdate(self): """Force an update for the graph.""" self.docchangeset = -100 self.checkPlotUpdate() def slotFullScreen(self): """Show window full screen or not.""" if not self.isfullscreen: self._fullscreenwindow = FullScreenPlotWindow( self.document, self.pagenumber) else: # cheesy way of closing full screen window p = self while p.parent() is not None: p = p.parent() p.close() def setTimeout(self, interval): """Change timer setting without changing save value.""" self.interval = interval if interval <= 0: # stop updates if self.timer.isActive(): self.timer.stop() else: # change interval to one selected self.timer.setInterval(interval) # start timer if it was stopped if not self.timer.isActive(): self.timer.start() def actionSetTimeout(self, interval, checked): """Called by setting the interval.""" self.setTimeout(interval) # remember changes for next time setting.settingdb['plot_updatepolicy'] = self.interval def actionAntialias(self): """Toggle antialias.""" self.antialias = not self.antialias setting.settingdb['plot_antialias'] = self.antialias self.actionForceUpdate() def setZoomFactor(self, zoomfactor): """Set the zoom factor of the window.""" self.zoomfactor = float(zoomfactor) self.checkPlotUpdate() def slotViewZoomIn(self): """Zoom into the plot.""" self.setZoomFactor(self.zoomfactor * N.sqrt(2.)) def slotViewZoomOut(self): """Zoom out of the plot.""" self.setZoomFactor(self.zoomfactor / N.sqrt(2.)) def slotViewZoomWidth(self): """Make the zoom factor so that the plot fills the whole width.""" # need to take account of scroll bars when deciding size viewportsize = self.maximumViewportSize() aspectwin = viewportsize.width()*1./viewportsize.height() r = self.pixmapitem.boundingRect() aspectplot = r.width() / r.height() width = viewportsize.width() if aspectwin > aspectplot: # take account of scroll bar width -= self.verticalScrollBar().width() mult = width / r.width() self.setZoomFactor(self.zoomfactor * mult) def slotViewZoomHeight(self): """Make the zoom factor so that the plot fills the whole width.""" # need to take account of scroll bars when deciding size viewportsize = self.maximumViewportSize() aspectwin = viewportsize.width()*1./viewportsize.height() r = self.pixmapitem.boundingRect() aspectplot = r.width() / r.height() height = viewportsize.height() if aspectwin < aspectplot: # take account of scroll bar height -= self.horizontalScrollBar().height() mult = height / r.height() self.setZoomFactor(self.zoomfactor * mult) def slotViewZoomPage(self): """Make the zoom factor correct to show the whole page.""" viewportsize = self.maximumViewportSize() r = self.pixmapitem.boundingRect() multw = viewportsize.width()*1./r.width() multh = viewportsize.height()*1./r.height() self.setZoomFactor(self.zoomfactor * min(multw, multh)) def slotViewZoom11(self): """Restore the zoom to 1:1""" self.setZoomFactor(1.) def slotViewPreviousPage(self): """View the previous page.""" self.setPageNumber( self.pagenumber - 1 ) def slotViewNextPage(self): """View the next page.""" self.setPageNumber( self.pagenumber + 1 ) def updatePageToolbar(self): """Update page number when the plot window says so.""" # disable previous and next page actions if self.vzactions is not None: np = self.document.getNumberPages() self.vzactions['view.prevpage'].setEnabled(self.pagenumber != 0) self.vzactions['view.nextpage'].setEnabled(self.pagenumber < np-1) def slotSelectMode(self, action): """Called when the selection mode has changed.""" modecnvt = { self.vzactions['view.select'] : 'select', self.vzactions['view.pick'] : 'pick', self.vzactions['view.zoomgraph'] : 'graphzoom' } # close the current picker self.pickeritem.hide() self.emit(qt4.SIGNAL('sigPickerEnabled'), False) # convert action into clicking mode self.clickmode = modecnvt[action] if self.clickmode == 'select': self.pixmapitem.unsetCursor() #self.label.setCursor(qt4.Qt.ArrowCursor) elif self.clickmode == 'graphzoom': self.pixmapitem.unsetCursor() #self.label.setCursor(qt4.Qt.CrossCursor) elif self.clickmode == 'pick': self.pixmapitem.setCursor(qt4.Qt.CrossCursor) self.emit(qt4.SIGNAL('sigPickerEnabled'), True) def getClick(self): """Return a click point from the graph.""" # wait for click from user qt4.QApplication.setOverrideCursor(qt4.QCursor(qt4.Qt.CrossCursor)) oldmode = self.clickmode self.clickmode = 'viewgetclick' while self.clickmode == 'viewgetclick': qt4.qApp.processEvents() self.clickmode = oldmode qt4.QApplication.restoreOverrideCursor() # take clicked point and convert to coords of scrollview pt = self.grabpos # try to work out in which widget the first point is in widget = self.painthelper.pointInWidgetBounds( pt.x(), pt.y(), widgets.Graph) if widget is None: return [] # convert points on plotter to points on axis for each axis xpts = N.array( [pt.x()] ) ypts = N.array( [pt.y()] ) axesretn = [] # iterate over children, to look for plotters for c in [i for i in widget.children if isinstance(i, widgets.GenericPlotter)]: # get axes associated with plotter axes = c.parent.getAxes( (c.settings.xAxis, c.settings.yAxis) ) # iterate over each, and update the ranges for axis in [a for a in axes if a is not None]: s = axis.settings if s.direction == 'horizontal': p = xpts else: p = ypts # convert point on plotter to axis coordinate # FIXME: Need To Trap Conversion Errors! r = axis.plotterToGraphCoords( self.painthelper.widgetBounds(axis), p) axesretn.append( (axis.path, r[0]) ) return axesretn def selectedWidgets(self, widgets): """Update control items on screen associated with widget. Called when widgets have been selected in the tree edit window """ self.updateControlGraphs(widgets) self.lastwidgetsselected = widgets def updateControlGraphs(self, widgets): """Add control graphs for the widgets given.""" cgg = self.controlgraphgroup # delete old items for c in cgg.childItems(): cgg.removeFromGroup(c) self.scene.removeItem(c) # add each item to the group for widget in widgets: if self.painthelper and widget in self.painthelper.states: for control in self.painthelper.states[widget].cgis: graphitem = control.createGraphicsItem() cgg.addToGroup(graphitem) class FullScreenPlotWindow(qt4.QScrollArea): """Window for showing plot in full-screen mode.""" def __init__(self, document, pagenumber): qt4.QScrollArea.__init__(self) self.setFrameShape(qt4.QFrame.NoFrame) self.setWidgetResizable(True) # window which shows plot self.document = document pw = self.plotwin = PlotWindow(document, None) pw.isfullscreen = True pw.pagenumber = pagenumber self.setWidget(pw) pw.setFocus() self.showFullScreen() self.toolbar = qt4.QToolBar("Full screen toolbar", self) self.toolbar.addAction(utils.getIcon("kde-window-close"), "Close", self.close) for a in ('view.zoom11', 'view.zoomin', 'view.zoomout', 'view.zoomwidth', 'view.zoomheight', 'view.zoompage', 'view.prevpage', 'view.nextpage'): self.toolbar.addAction( pw.vzactions[a] ) self.toolbar.show() def resizeEvent(self, event): """Make zoom fit screen.""" qt4.QScrollArea.resizeEvent(self, event) # size graph to fill screen pagesize = self.document.pageSize(self.plotwin.pagenumber, dpi=self.plotwin.dpi) screensize = self.plotwin.size() aspectw = screensize.width() * 1. / pagesize[0] aspecth = screensize.height() * 1. / pagesize[1] self.plotwin.zoomfactor = min(aspectw, aspecth) self.plotwin.checkPlotUpdate() def keyPressEvent(self, event): k = event.key() if k == qt4.Qt.Key_Escape: event.accept() self.close() return qt4.QScrollArea.keyPressEvent(self, event) veusz-1.15/windows/icons/0000755002344000001440000000000011734662466015416 5ustar jssusers00000000000000veusz-1.15/windows/icons/settings_axismajorticks.png0000644002344000001440000000047211734662204023070 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDùC» pHYs  šœtIMEÖ 1#ÒÇ;tEXtCommentCreated with The GIMPïd%nžIDAT8Ëc`ò€Ƙ3gÇ÷ï‰ÒTRZŠ]¢§»Cì?33^5L¸lùß1®ù?33ÃÿŽ™XÕ1!{Åoé ŒÿBØÿ20V¤ã7 %%»SZ§¡¸ §8CÙæê,â½€+ Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/veusz-edit-custom.svg0000644002344000001440000002321611734662204021540 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:40:13 2004)
  • </Agent> </publisher> <creator id="creator30"> <Agent id="Agent31" about=""> <title id="title32">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-go-down.svg0000644002344000001440000001133411734662204020242 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent id="Agent18" about=""> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-zoom-1-veuszedit.svg0000644002344000001440000001347111734662204022036 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/veusz_48.png0000644002344000001440000000532611734662204017607 0ustar jssusers00000000000000‰PNG  IHDR00Wù‡sBIT|dˆ pHYsŒŒçg™tEXtSoftwarewww.inkscape.org›î< SIDAThÅ™l[ÕÇ¿çúÅó§Ð&YHššÆiŠ›ít l•*†ø¥²¨ÛÓ6AË€1ÖÁ²Ñ•±+Œ_kÅ&~i‘ «Ø†¦A³‰µãGp“”Ð¦Ž“–¶†´³ÓØŽ7ïžýa?÷ùù9±CÑŽôûåÞ{¾Ÿsî=÷½kR”E˜ˆJ_x)™þ ÖïºZ=Î |J¦ª«@»ZqZGê"¢jŽF_‹û(B¨ÇTÂØ5Zø˜gúnëé­^é3@ä¼ Àª´5øI$òrÄÜG¡„pB5s© RÝ>JõÁ Êú‘†š†ÉªînØ>©x—ë†ùB¨èþMšö¡>oÕO!R†¨¯jU]S‹ý=ahŸÉÀ¨v„S.ž<5‰uëÊ´O „º J‹©#ÚogK!R¦é€ ÅÚg2ÐQWp°°­œAØÛû# *–'›"'ýºu֭ˉÔ[,‚€ž…y/_ß„Û`^XÌ}žŽ+ñ#š†…óó¶B} ÅÂÿºw¦ñÓúbÄmj_Š"mUsÕÇ3ý¿²ò¾ë…P/·ˆ<ˆèž±±†f rH#"[6„@:FËÉ€Ñí^¯79S£UU=ì rn`ZƒÞè·³¡„ÃOžZ°`Ó‘Ô|'†òUÀà œ§q¯2¯^õûÑëñ  ½Bõéu†ÓQˆÚ Þ,gHÐ,Ö€ÑòM¡$iÚù4/ªŒû€üó]·ššç ¡Þm¨óÐGDÏ=ú•·fÄp™Å4XfÀëErï|‡Záž?^ˆc!Ô­9,*Ï8@› #@¤ò”°êsÎy®ôرoè{AÞE¼b©ëó="¢½O®,Ù×¶ÔqT¿¿hÑ_.B½>Oåë:|øóÇ‹0–Rˆy/°Ê€žÿœ†™¹o$¶ÙdæQ£¾~—’.›°¸úˆh[¡â-‘†Ðµ‘Û•¾¡H§ôžÄS­­Î1+mç:Þ6~B½ åyvÿ ##mEíê&ãÓ^‘ÈmhŸ•aw’*|vÌælÉ’}•B8ï3–kÃÛ=o@ÿÐÉ+ŽŠão®õx¦ 8|xÍ ·{O òìˆD"/@KƒúœïÀÉ}+›œïÎæ„p>ÐüìÊCD„ˆîΈšN¾µÖ;»ø @J¨¨Í"­F€œE¼²©¢ ñcíDêMÙQÒY¿oh¨"¨·õz+£…Œ™žFmsÓ0ãNœÏšš¢$„ú@¢ò ôø\Ƶ€…·UûŒ®LDêz!¨CQEìvÀfK}Vl|ýõ¹¿Í ïË–%K÷ï·'òŒ‘c}H'4|±­±|Ó5× ¼¹™~¡()Ñv{F8l6¼ôä“xCï×ÝÝm[·n]ñUˆœ"2F%%€¢€æ÷‚¤2*1õGhiA—¢ Fn?¦(ø^z(^»ô³k¯8Œß·Ô#\4€×KCdÌ‘rÐêqG^|K[[q{:Æñ (¸ÿ’KpDï3­Å¤‚ÒãÉñàêk uu ¥£Š©(f‡ŠwÁ#¬­ ¿R”˜"EÁÁ’I)ˆè£dR+)F|@8‘Ê€I¼žwµ=ÿ@Ïì}FH–Oç¼!ÛxáÆ@êiÏññUuq½oKcùó˜Ä–ço\ÙxåØ ý/D™Ùi1Ý0ìŽ\ŸWþ^Gâáxø[k+˜¦,NI–·ûŧ€9÷­®(ÇÃ#’e‹1iQnc!êŽ6 ¦WÕDäá®]m—,ïÏêsº’m}tÍ£þb…Í $d‹añé¢Ü2§l­ Î}ýñÛmí %B[$Ëâ?dæŸ}Zâ-2ë¦nHA¨ÏßøÊ­Ìüm‹ÝòÎ7ì(ª,~b€P"0휺07€œÃ¦p<ü¸„´YÔý×}þÎ7†#×´4”ïsÊÖ™HÚ ßò%e9Ç2sÇ ›Yöœ®Nh“6ãÃÖEÏ^ô%f¾TnØ?NI–e‰¨fÆ+ýñµ­ jÖãÃf@\m5ƒkR[ÜÄûí5(:[–kÀ<}Ò¢h42äºUï6o3ÿRBBJi®<ºíУ£(%c+–/UûÌ~®w š·]B[oK&~Þê)Ÿô˜<§ÄÆWüa³ÇÙ3'€p"<ÊÌ,Y’yNLp©vñð=²Î4(YþÜn$€\ñ>‚ü·ßw=^çE6IFƒñè(JSý‹ØËþDåÖÊcÌ\kÞ †Ç÷»ÀñsÇÉò.sÕIÛ]ÜÅðù£•6µ6Xz€ÇƒÌñI[ƒsWŸrüÄ‘²æ8´|IÇÃF Àh‡"Ã.HL'õ Å¿¸‹ÿ‡Ož%`Û †¯·7¶·ãT!‚Úx i™2auSc-`c“Am¡5®¶ê`ôûãu-õ üDÏX‰ðG/Í'ª7ˆ²žž¥³ÿúi `uóãøÑùËÓç)îâ÷ûýÑ[mGßÁè7){Õ\}zz ôǾÏÀoüÑ'ö NœmÈ‹]»°î‚//éÍ«onÁ؇U€é™:ec~ @‚YÐN„ïhå Í W¯Æ4±Q‰¨ÃëÊú ÌçVJð) œTÊãU³ä;¨²˜Á6qŸ8È ¯ƒqöÊsKY5¶‰Ÿ$ís¶œçÛ•çñÞ^¼b¯„³ãb‹çWÚBŸÌmniÿpwû |fÌh Å~[™Á$€ ¸‹óþýiÛL‹d¤€þOÿ?Å3Öî²7“s]MË¡”H–š]Øå¶Ëþ|e¿?::¿“& WÄôÝxÿ¤ù~«Ç™)ËÿƒNêUˆÃšIEND®B`‚veusz-1.15/windows/icons/kde-document-new.svg0000644002344000001440000001561711734662204021305 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator26"> <Agent about="" id="Agent27"> <title id="title28">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_bar.svg0000644002344000001440000000510611734662204020104 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz.ico0000644002344000001440000003535611734662204017270 0ustar jssusers00000000000000 h6  ¨ž00 ¨%F(  ÿÿÿÿÿÿ‚áƒô„õ±nmóÛÑHóÛÑHóÛÑHóÛÑHóÛÑ<óÛÑ/óÛÑ!óÛÑóÛÑÿÿÿÿÿÿ‚‚ÿ‚ÿ‚ÿ‚ÈèÕÃòÛÑNÿÿÿèÑÑ ô×Ò-ÿÿÿëÖÌÿÿÿÿÿÿÿÿÿÿÿÿ‚3‚ÿ„æ‚Ä‚ÿ‹iñÛÑCöÙÐîÝÌôÚÏ0ÿÿÿôÞÓÿÿÿÿÿÿÿÿÿÿÿÿ'•'U#“#ÿ*”(ÌŽ1ŒýŠê&”&ÿß¿ÿÿÿëØÎÿèÐ ôÝÒ-ÿÿÿÿÿÿÿÿÿÿÿÿ[¯[wV¬Vÿb¯_²ÿÿÿI¥I˜D£DÿG£E•ôÞÓ.ÿÿÿóÛÒ?ò××ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈ™‹Æ‹ÿ˜È“˜ÿ¿¿ÝÕÃMx½xît»tüo¸o.ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÄâÄ»¿à¿ÿÈÝ¿~ÿÿÿíÛÈ­Ö­k¨Ô¨ÿ£Ò£ÂôÞÓ.ïÏÏóÛÏ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøûøÝóùóÿðíädÿÿÿóÛÑ›ôÙÓÜîÜÑØëØÿÓéÓ[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿzzÿ††ûÿ““ÿøÁ»JÿÿÿÿÿÿÿÿÿÿÏÏ?ÿÝÝÿÿèèæÿõõ ÿëØ ÿÿÿÿÿÿÿÿÿÿÿÿñ%%"ô&&ÿ÷&&üôÍÄ3ÿÿÿÿÿÿÿÿÿöÜÓÿSS¨ÿ``ÿÿkk‹ðáÒÿÿÿÿÿÿÿÿÿÿÿÿÐ DÓ!!ÿÖ!!àóÛÑ-ÿÿÿÿÕÕÿÿÿÿÿÿè$$ë$$õí%%úñ%%(ÿÿÿÿÿÿÿÿÿÿÿÿ®f²ÿ´ÀóÛÑ%ÿÿÿ÷ÞÑ=ÿÿÿÿÿÿããÆ É|Ì ÿÏ »ÿßßÿÿÿÿÿÿÿÿÿˆÿ“ óÛÑÿÿÿÿóÚÍ)ÿÿÿÿãã ¨«Ý®ÿ±Sÿÿÿÿÿÿÿÿÿlªnÿr€óÛÑÿÿÿÿÿÿíÛÈÿÿÿÿÿÿÿÿÿŠOŒÿá’ ÿÿÿÿÿÿJ ÌM ÿP `óÛÑóÛÑóÛÑ ÿÿÿÿÿÿÿÿÕÿÿÿÿÿÿk¹nÿqƒÿÿÿÿÿÿ(î,ÿ.@óÛÑóÛÑóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJ (L úP øR #ÿÿÿÇÿÃÿÃÿÉÿÈÿŒÿžšŸ?ŸŸŸŸÏÏç?ã?ó( @ ÿÿÿÿÿÿÿÿÿÿÿÿ‚»‚à‚àˆñ‹øŠñÂɧ¥óÛјóÛјóÛјóÛјóÛјóÛјóÛјóÛјóÛÑ”óÛцóÛÑwóÛÑjóÛÑ[óÛÑMóÛÑ?óÛÑ1óÛÑ#óÛÑóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚õ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚IÿÿÿÿÌÌëØØ ÿ¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚:‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚Ú‚òÜÐQóÛÑòñÚÑ7ÿÿÿÿÿÿÿÿÿõÜÒ3òÛÑ¡ìÙÐÿÿÿÿÿÿëØÎôÚÒDÿèÑ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚\‚ÿ‚ÿ‚ÿˆä‚Ñ‚ÿ‚ÿ‚ÿ‚zôÙÔ/òÛÐŽñÕÕÿ¿¿ÿÿÿÿÿÿöÛÑ8ôÛÒžñÝÏ%ÿÿÿÿÿÿòÙÌôÚÎDÿÿ¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚~‚ÿ‚ÿ‚ÿ#΂>‚ÿ‚ÿ‚ÿ‚õ‚ÿÿÿóÜÑ‘óÛÒkÿÿÿÿÿÿÿÿÿÿªªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽ ÿŒÿ‹ÿCœ;ºÿÿÿ ‡ § † ÿ…ÿ„ÿƒªÿÿÿÿæÌ ÿãÆ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïßÏóÛÎ?èÑÑ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3›3Â1š1ÿ.˜.ÿ,—,ÿk¬`¦ÿÿÿ%”%#“#ô!’!ÿ‘ÿÿŽCÿ¿¿óÛÎÿÿÿÿÿÿÿÿÿóÛÎóÚÐRÿÌÌõÖÌóÛÏkðÒÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿM¨MäK¦KÿI¥IÿF¤Fÿ’¼ƒ“ÿÿÿÿÿÿ=Ÿ=z:ž:ÿ88ÿ6œ6ÿ4›4ÖÚйôÛѲÿÿªÿÿÿÿÿÿóÜÑ,óÛÑÁõÝÒJÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿj¶jg´gþe³eÿc²cÿ`±`ÿ¹Ê¤ÿÿÿÿÿÿW¬WU«UÜRªRÿP©PÿN¨NÿL§LsÿÿÿÿÿÿÿÿÿÿÿÿÿÛÛßß¿ÿãã ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„„'‚Á‚ÿÀÿ|¿|ÿz¾zÿßÖÃkÿÿÿèÑÑ ôÛЈt»qPm·mÿj¶jÿhµhÿe³eòc²cÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿžÏžI›Î›ÿ™Í™ÿ—Ì—ÿ”Ê”ñóÛÑ`ÿÿÿÿßßóÛÒkÿÕՇć·„Äÿ‚Á‚ÿÀÿ}¿}£ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸Ü¸kµÛµÿ³Ú³ÿ±Ø±ÿ¯×¯ÒóÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿ Ð 'žÏžúœÎœÿšÍšÿ—Ì—ÿ•Ë•<ÿÿÿôÞÓ.ÿßÏÿÿÿðØÑ!õàÖÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÒéÒÐèÐÿÎçÎÿËåËÿÉäɲóÛÑ`ÿÿÿÿÿÿ÷ÝÕöÜÓÿÿÿ¸Ü¸Š¶Û¶ÿ´Ú´ÿ²Ù²ÿ¯×¯Ð­Ö­òÛÐöÛÑ8ÿÿÿôÛÐròÙÐQÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìõì¯êôêÿèóèÿåòåÿâñâ’óÛÑ`ÿÿÿÿÿÿôÛѲóÛÑÙÿÛÛÓéÓ ÐèÐæÎçÎÿËåËÿÉäÉÿÆãÆlÿÿÿÿÿÿÿÿÿÿÕÕÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêêÑÿññÿÿ÷÷ÿÿýýÿýþýróÛÑ`ÿÿÿÿÌÌóÜÑXóÚÐ}îÝÌÿÿÿêõê]èôèÿåòåÿãñãÿáðáïßïßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦¦óÿ¬¬ÿÿ±±ÿÿ¹¹ÿÿ¾¾SóÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïïÿõõÆÿûûÿýþýÿûýûÿùüùœÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿZZÿbbÿÿhhÿÿmmÿÿssÿÿzz3óÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±±3ÿ··ýÿ¼¼ÿÿÄÄÿÿÊÊþÿÏÏ6ÿÿÿÿÿÿõÛÕ1¿¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿú''7ü''ÿþ''ÿÿ))ÿÿ//ÿÿ55óÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòÛÑNóÛÎÿssšÿyyÿÿ~~ÿÿ„„ÿÿ‹‹Ëÿ‘‘ÿÿÿóÚÒ>ÿßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿê$$Yë$$ÿí%%ÿî%%ÿð%%óÿÿÿóÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôÞÓÿÿÿÿ--ÿ55ïÿ::ÿÿ@@ÿÿFFÿÿMMeÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÙ""{Û""ÿÜ""ÿÝ""ÿß##ÔÿÿÿóÛÑ]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñ%%mó&&ÿô&&ÿõ&&ÿ÷&&ëù''ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈœÊÿËÿÍ ÿÎ ´ÿÿÿóÛÑUÿÿÿÿÿÿÿÿÿò×Éÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿà##â##Óã##ÿå##ÿæ$$ÿç$$–ÿÿÿÿÿÿÿ¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¸¾¹ÿºÿ¼ÿ¾”ÿÿÿóÛÑNÿÿÿÿÿÿõÜÑ{óÚÐgÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑ @Ò!!ÿÔ!!ÿÖ!!ÿ×!!ýØ""1ÿèÑ òÙÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ§à¨ÿªÿ«ÿ­tÿÿÿóÛÑFÿÿÿÿÿÿÿæÌ ñÕÕÿÿÿÿÿÿÿÿÿÿÿÿñÜÍ$ÿÿÿÿÿÿªÃÿÅÿÆÿÈÅÉÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ•–ý—ÿ™ÿ›ÿœUÿÿÿóÛÑ?ÿÿÿÿÿÿÿÿÿÿÿÿÿ€€ÿÿÿÿÿÿÿÿÿóÚÓ)ÿÿÿÿÿÿ±³õ´ÿµÿ·ÿ¹^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„$…ÿ‡ÿˆÿŠÿ‹5ÿÿÿóÛÑ7ÿÿÿÿÿÿÿÿÿÿÿÿòÜÒeóÜÑ,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¢}£ÿ¥ÿ¦ÿ¨è©ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿsFuÿvÿxÿyÿzÿÿÿóÛÑ0ÿÿÿÿÿÿÿÿÿÿÌÌôÞÓ.ëØØ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‘’Þ”ÿ–ÿ—ÿ˜ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿbhdÿeÿgÿhõÿÿÿÿÿÿóÛÑ)ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚Pƒÿ…ÿ†ÿˆû‰,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿR ŠS ÿU ÿV ÿX ÖÿÿÿÿÿÿóÛÑ!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõàÖªªªÿÿÿÿÿÿÿÿÿÿÿÿs¹tÿuÿwÿy¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿA ¬C ÿD ÿE ÿG ¶ÿÿÿÿÿÿóÛÑÿÿÿóÛÑóÛÑóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿb(cúeÿfÿhÿiXÿÿÿÿÿÿÿÿÿÿÿÿ0Î2ÿ3ÿ5ÿ6–ÿÿÿÿÿÿóÛÑÿÿÿóÛÑóÛÑóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿS ŒT ÿV ÿW ÿX äZ ÿÿÿÿÿÿÿÿÿ è!ø"ø$ø&sÿÿÿÿÿÿóÛÑ ÿÿÿÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿB C âE øF øH øI ‚ÿÿÿÿÿÿÿÿÿðÿðÿÿðÿÿð ÷ÿð ÷ÿð†ÿÿàƒÿÿàÃÿÿàá{ÿááÿÿáÐÿÿáðÿáøÿÁø/ÿÁÌ?ÿÃþÿÃþÿÃÿÿÃÿÿÃÿ‡ÿÃÿÃÿƒÿÁÿƒÿáÿ‡ÿàÿ‡ÿðÿ‡ÿø‡ÿø?‡ÿü?ÿüÿþÿþÿÿ(0` ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚“‚ЂЂЂÐ'!ø+’%ý)‘#ú/“)øàÔÀâóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑàóÛÑÖóÛÑÈóÛѺóÛѬóÛÑŸóÛÑ‘óÛуóÛÑuóÛÑgóÛÑYóÛÑLóÛÑ=óÛÑ/óÛÑ!óÛÑóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚Ô‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚—ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚õ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ý‚2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚Ç‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚:‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚aÿÿÿÿÿÿòÜÒPôÜÑ^ôÛÑpÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ÝÕôÞÓöÜÓÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÌÌÿáÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚\‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚é‚ÿÿÿòÚÑuóÛÑÿóÛÑÏ÷ÞÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòÛÒOôÜÑßóÛДÿÿÿÿÿÿÿÿÿÿÿÿóÛÎóÛÐVóÛÐVöÙÐÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚~‚ÿ‚ÿ‚ÿ‚ÿ‚ÿé‚ׂÿ‚ÿ‚ÿ‚ÿ‚ÿ‚‘ÿÿÿóÛÑjóÛÑÌóÚÐgèÑÑ ÿÿÿÿÿÿÿÿÿÿÿÿÿ¿¿ôÜÑuóÛÑÚóÛÒ“ñÕÆÿÿÿÿÿÿÿÿÿÿÿÿôÛÒ[ôÛÓ\ÿÌÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚ ‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ8–0Ù‚F‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ü‚-ÿÿÿöÛÑÿÿÿóÜÐWôÜÑHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóÛÒ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿæÌ ¿¿¿ÿÿªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿƒÂƒÿ‚ÿ‚ÿ‚ÿ‚ÿQ FËÿÿÿ‚°‚ÿ‚ÿ‚ÿ‚ÿ‚ÿ‚ÁÿÿÿÿÿÿðáÒóÛÐÛóÛѪÿÌÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒä‹ÿŠÿ‰ÿ‰ÿ ˆ ÿt­e½ÿÿÿ†"…ø„ÿƒÿƒÿ‚ÿ‚ÿ‚ZÿÿÿÿÿÿóÛÎóÜÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõÛÐ1ÿ¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'•'&”&þ$“$ÿ#“#ÿ!’!ÿ‘ÿÿœ½ˆ¯ÿÿÿÿÿÿ„ÿŒÿ‹ÿŠÿŠÿ‰æ ˆ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÛÛÿÿÿÿÿÿóÜÐAóÛÑ€ôÛÓ\ÿßßÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ88(77ÿ5œ5ÿ4›4ÿ2š2ÿ1š1ÿ0™0ÿÃË©¡ÿÿÿÿÿÿ)–) (•(ã&•&ÿ$“$ÿ#“#ÿ!’!ÿ ‘ ÿ‘‹ÿÿÿÿßßôÙÔ/ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïßÏöÜÔ;óÜÒ˜êÕÕ ÿÿÿõØÎôÛÓ]óÛÓ@ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿJ¦JJI¥IÿG¤GÿE¤EÿD£DÿB¢BÿA¢AþéØÉ“ÿÿÿÿÿÿÿÿÿ9ž9W88ÿ6œ6ÿ4›4ÿ2š2ÿ1š1ÿ0™0ú.˜.)óÚÓ)óÛÑÿôÜÑHÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€òÛÒ£óÜÑÂòÛÑ…ÿæÌ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ[¯[lZ®ZÿY­YÿV¬VÿU«UÿT«TÿRªRçóÛÑÿÿÿÿÿÿÿÿÿÿÿÿI¥IÀG¥GÿF¤FÿD£DÿB¢BÿA¢Aÿ?¡?¼ñÕÕòÜÒPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõÛÑcóÜÒfõÝÔ5ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿm·mŽk¶kÿj¶jÿhµhÿg´gÿe³eÿc²cÇóÛÑÿÿÿÿÿÿÿÿÿÿÿÿZ®Z.Y­YüW¬WÿV¬VÿT«TÿRªRÿQ©QÿO©OTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~¿~°|¿|ÿ{¾{ÿz½zÿx½xÿw¼wÿu»u§óÛÑÿÿÿÿÿÿÿÿÿôÜÑ_ôÙÔ/j¶j•hµhÿg´gÿe³eÿd³dÿb²bÿ`±`â_°_ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÈÒŽÇŽÿŒÇŒÿ‹Æ‹ÿ‰Å‰ÿˆÄˆÿ‡Ä‡‡óÛÑÿÿÿÿÿÿóÜÑ,óÛÑïòÛÑz|¾|z½zìx½xÿw¼wÿu»uÿt»tÿrºrÿp¹p…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¡Ñ¡ó Ð ÿžÏžÿœÎœÿ›Î›ÿ™Í™ÿ˜Ì˜góÛÑÿÿÿÿÿÿÿÿÿòÜÏ;ÿãÆ ÿÿÿŒÆŒh‰Å‰ÿˆÄˆÿ‡Ä‡ÿ…Ã…ÿ„„ÿ‚‚ø€Á€$ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ´Ú´²Ù²ÿ±Ø±ÿ¯Ø¯ÿ®×®ÿ¬Ö¬ÿªÕªÿ©Õ©GóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿϛΛϙ͙ÿ˜Ì˜ÿ–Ë–ÿ•Ë•ÿ“Ê“ÿ’É’¶ÿÿÿÿÿÿÿÿÿãÆÆ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÅâÅ8ÄâÄÿÂáÂÿÁàÁÿ¿à¿ÿ¾ß¾ÿ¼Þ¼ÿºÝº'óÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ­Ö­<«Ö«ÿªÕªÿ¨Ô¨ÿ¦Ó¦ÿ¥Ò¥ÿ£Ò£ÿ¢Ñ¢NÿÿÿÿÿªóÛÑÒôÜÑ_ÿÿÿÿÿÿòÚÒ`óÛÒñÙÏ5ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×ë×ZÕêÕÿÓéÓÿÒéÒÿÑèÑÿÏçÏÿÎçÎþËåËóÛÑÿÿÿÿÿÿÿÿÿõÛÐ1ïßÏóÛÒ?ÿÿÿÿÿÿ¼Þ¼¥»Ý»ÿºÝºÿ·Ü·ÿ¶Û¶ÿµÚµÿ³Ú³Þ²Ù²óÛÎ*óÜÒ™ðÚÎDÿÿÿÿÿÿôÜÑ^óÚЙôßÕ0ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèôè|çóçÿåòåÿãñãÿâðâÿàðàÿßïßçÿÿÿóÛÑÿÿÿÿÿÿÿÿÿóÛÒkóÛÑûóÛÑ«ÿÿÿÿÿÿÎçÎÌæÌôËåËÿÉäÉÿÇãÇÿÆãÆÿÄâÄÿÃáÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿêÕ ÿÿÿæÌÌ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùüùžøûøÿ÷û÷ÿõúõÿóùóÿòøòÿð÷ðÇÿÿÿóÛÑÿÿÿÿÿÿíÛÈóÛÑÄóÛÑÿóÛÑäõÛÐ1ÿÿÿÿÿÿÝîÝyÜîÜÿÛíÛÿÙìÙÿØëØÿÖêÖÿÔêÔöÓéÓ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÞÞÀÿââÿÿææÿÿêêÿÿîîÿÿóóÿÿ÷÷§ÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿóÛÑ›êÕÕ ÿÿÿÿÿÿÿÿÿï÷ïíöíÜìõìÿêõêÿéôéÿèóèÿåòåÿäñä¯ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ±±âÿµµÿÿ¹¹ÿÿ¼¼ÿÿÀÀÿÿÄÄÿÿÈȇÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿMýþýÿüýüÿúüúÿùüùÿ÷û÷ÿõúõÿôùôHÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~ÿ‚‚ýÿ††ÿÿ‹‹ÿÿÿÿ““ÿÿ——ÿÿ››gÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕ¶ÿÙÙÿÿÝÝÿÿààÿÿääÿÿèèÿÿììÚÿññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿQQ&ÿUUÿÿYYÿÿ\\ÿÿbbÿÿffÿÿjjÿÿmmGÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¦¦&ÿªªúÿ¯¯ÿÿ³³ÿÿ··ÿÿ»»ÿÿ¾¾ÿÿÂÂxÿÿÿÿÿÿÿÿÿëÖÌóÙÐWæÌÌ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ''Hÿ((ÿÿ++ÿÿ//ÿÿ33ÿÿ88ÿÿ<<ÿÿ@@'ÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöÛÑïÚÏ0ÿÿÿÿ||Šÿ€€ÿÿ††ÿÿŠŠÿÿÿÿ‘‘ÿÿ••ôÿ™™ÿÿÿÿÿÿöÙÐóÜÐlÿáÒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿò&&jó&&ÿô&&ÿõ&&ÿö&&ÿ÷&&ÿø&&þù''ÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòÝÐ<òÚЈÿÿÿÿOO ÿSSæÿWWÿÿZZÿÿ``ÿÿddÿÿhhÿÿkk©ÿÿÿÿÿÿÿÿÿªªªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿç$$Œè$$ÿé$$ÿê$$ÿë$$ÿì%%ÿí%%çÿÿÿÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ''^ÿ))ÿÿ--ÿÿ11ÿÿ77ÿÿ::ÿÿ>>ÿÿBBBÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÜ""®Ý""ÿÞ""ÿß##ÿà##ÿá##ÿâ##ÇÿÿÿÿÿÿóÛÑŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿó&&ô&&Æõ&&ÿö&&ÿ÷&&ÿø&&ÿù''ÿú''Õû''ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÑ ÐÒ ÿÓ!!ÿÔ!!ÿÕ!!ÿÖ!!ÿ×!!§ÿÿÿÿÿÿóÛÑ…ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿé$$3ê$$ýë$$ÿì%%ÿí%%ÿí%%ÿï%%ÿð%%rÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÆòÇÿÈÿÉÿÊÿÊÿÌ ‡ÿÿÿÿÿÿóÛÑ}ÿÿÿÿÿÿÿÿÿÿÿÿðØÎ4ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿß##›à##ÿá##ÿâ##ÿâ##ÿã##ÿå##òæ$$ÿÿÿÿÿÿêÕÕ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¹ºÿ¼ÿ½ÿ¾ÿ¿ÿ¿ÿÀgÿÿÿÿÿÿóÛÑvÿÿÿÿÿÿÿÿÿôÜÒ`óÜѽõÜÒPÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÓ!!Õ!!ïÖ!!ÿÖ!!ÿ×!!ÿØ""ÿÙ""ÿÛ""£ÿÿÿÿÿÿðÙÑCÛÛ¶ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ®6¯ÿ°ÿ²ÿ³ÿ³ÿ´ÿµGÿÿÿÿÿÿóÛÑoÿÿÿÿÿÿÿÿÿòÙÐ<óÛÒóÑÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÉoÊÿËÿÌ ÿÍ ÿÎ ÿÏ ÿÐ <ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ£X¤ÿ¥ÿ¦ÿ§ÿ¨ÿ©ÿª'ÿÿÿÿÿÿóÛÑgÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöÞÐ6ôÞÓÿÿÿÿÿÿ¾¿ÔÀÿÁÿÂÿÃÿÄÿÅÐÆÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ˜z™ÿšÿ›ÿœÿÿžþŸÿÿÿÿÿÿóÛÑ`ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÛÐGöÙÐÿÿÿÿÿÿÿÿÿ´Bµÿ¶ÿ·ÿ¸ÿ¹ÿºÿ»lÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿœŽÿÿÿ‘ÿ’ÿ“çÿÿÿÿÿÿÿÿÿóÛÑXÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿñÙÏ5ñÙÐ6ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿª¬«ÿ¬ÿ­ÿ®ÿ¯ÿ°ï±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‚¾ƒÿ„ÿ…ÿ†ÿ‡ÿˆÇÿÿÿÿÿÿÿÿÿóÛÑQÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕóÛÑjôÛÒqïßÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŸ ö¡ÿ¢ÿ¢ÿ¤ÿ¥ÿ¦ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿwàxÿyÿyÿ{ÿ|ÿ}§ÿÿÿÿÿÿÿÿÿóÛÑIÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕ öÞÐ6óÜÔAÿÛÛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ•€–ÿ–ÿ—ÿ˜ÿšÿ›þœ7ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿklümÿnÿnÿoÿqÿr‡ÿÿÿÿÿÿÿÿÿóÛÑBÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕÕÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠ ‹à‹ÿŒÿÿŽÿÿË‘ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ_$aÿbÿbÿcÿdÿeÿfgÿÿÿÿÿÿÿÿÿóÛÑ;ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿS€ÿÿ‚ÿƒÿ„ÿ…ÿ†fÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿT FU ÿV ÿW ÿX ÿYÿZÿ[GÿÿÿÿÿÿÿÿÿóÛÑ3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿéÓÈïÏÏÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿu½vÿwÿxÿyÿzÿ{ì|ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿI hJ ÿK ÿL ÿM ÿN ÿO ÿP 'ÿÿÿÿÿÿÿÿÿóÛÑ,ÿÿÿÿÿÿÿÿÿÿÿÿÿÿªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿò×ÉÿëØ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿj+kûlÿmÿnÿoÿpÿq—ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ> Š? ÿ@ ÿA ÿB ÿC ÿD þE ÿÿÿÿÿÿÿÿÿóÛÑ$ÿÿÿÿÿÿããÆ ñÖÏ%ëØÎÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ`‘aÿbÿcÿdÿeÿfýg2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3¬4ÿ5ÿ6ÿ7ÿ8ÿ9çÿÿÿÿÿÿÿÿÿÿÿÿóÛÑÿÿÿÿÿÿÿææ òÝÐ&õâØÿªªÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿU V êV ÿW ÿYÿZÿ[ÿ\Æ\ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(Î(ÿ*ÿ+ÿ,ÿ-ÿ.ÇÿÿÿÿÿÿÿÿÿÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿ¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿK dK ÿL ÿM ÿO ÿP ÿQ ÿQ `ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿèøø ø!ø"ø"¢ÿÿÿÿÿÿÿÿÿÿÿÿóÛÑÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ? @ ÈA øB øC øD øE øF áG ÿÿÿÿÿÿÿÿÿÿÿÿüÿüÿÿÿÿüÿÿÿÿüÿÿÿüÿÿÿü9üÿÿüüÿÿøÿÿÿøÿÿø ÿÿÿø ÿ÷ÿøÿÿø~?ÿøÿÿÿðÿÿÿð€ÿÿÿðÀÿÿð/àÿÿð/à?ÿÿð/ð;ßÿð/ðßÿð/8ÿÿà.<ÿÿà/|ÿÿà/þÿÿàoþÿÿàoÿÿÿàoÿÿÿàoý€ÿÿÀoÿÀÿÿÀoÿÀÿÀoÿàÿÀÿà?ÿÀÿðÿÀÿøÿÀÿÿøÿÀÿÿüÿ€ÿÿüÿ€ÿÿþÿ€ÿÿþÿ€ÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÀÿÿÿÀÿÿÿà?ÿÿÿð?ÿÿÿðveusz-1.15/windows/icons/kde-application-exit.svg0000644002344000001440000001237611734662204022151 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004) Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_polygon.svg0000644002344000001440000000544111734662204021213 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_plotfillabove.png0000644002344000001440000000047711734662204022704 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ !=ÛÆtEXtCommentCreated with The GIMPïd%n£IDAT8ËíR» Â0¼—7ëP0C¤& X u‘w‰”yÌ £!qü‘H"ÑqÕù}Î>?"8çV@æÍ‡÷‹.:5M¹qi[d¨ÄœÆ~fª\F$,'ü\gÓUÙ_¼LsªB^OÝhA5³ ‰&ÅGõni¤<}Afa\ ±èª‚ªSýk õa¸î÷÷‰ÿö#mÁÖZä°©?ÎIEND®B`‚veusz-1.15/windows/icons/button_boxplot.svg0000644002344000001440000001217111734662204021211 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_key.svg0000644002344000001440000002073511734662204020317 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz-shape-menu.svg0000644002344000001440000000567511734662204021356 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz.png0000644002344000001440000000063511734662204017272 0ustar jssusers00000000000000‰PNG  IHDR‘h6 pHYs  šœtIMEÕ  ®Ò²tEXtCommentCreated with The GIMPïd%nIDAT(Ïcüÿÿ?)€‰‘‘‘4 $ÛÀÀÀÐÔÔa¸çoc·™ƒ¬‚Ón®söâ‡ÿ0P<ñ8›õl87¯ç(2÷ÿÿÿèNêɳ@æ.ÜvËH]Ÿ“ ¾q?C˼sßþ9>7‹†ºº:„ã­G¦¯½¦)Ë‹7”Õ~½¾uãÙL7”_ZÊ $ÄÀÍÍÀƆÍI÷ï3Ü¿¯ënö…‘­EÔE‘ás诛 !! “'3üúQÉøÿÿFFFD|74tî}U÷߈aš5crg2^'ik3œ9£ëbÌÀÀ %Ê©ÿÿÿÿêÔB=â :q3HJ­ÿÿÿg$5yÉiÉ CK¢*IEND®B`‚veusz-1.15/windows/icons/button_colorbar.svg0000644002344000001440000000671411734662204021333 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-edit-delete.svg0000644002344000001440000001334111734662204021055 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator26"> <Agent id="Agent27" about=""> <title id="title28">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/settings_bgfill.png0000644002344000001440000000046711734662204021300 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ  bûÄtEXtCommentCreated with The GIMPïd%n›IDAT8ËÕR» Ã0“ê|ª¡Ð¹Bç‚! ÷éĨKÆqÀK¡½Igû„t2ð÷ŘÞãX5tmÛòųë’N9·âýà´§™ÃÀ¤aJR$0³Å‰·3’qc\Ï ¼÷éjXTär%€„û¥ÞBŠzÈ5dœf%}} y"Õ)””àñúòGÚöP×ÿF}ùƒ]@ çIEND®B`‚veusz-1.15/windows/icons/button_polar.svg0000644002344000001440000001157711734662204020650 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_image.svg0000644002344000001440000000655011734662204020610 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_plotfillbelow.png0000644002344000001440000000046611734662204022716 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ &ˆX¦tEXtCommentCreated with The GIMPïd%nšIDAT8Ëc`ò€Ƙ3gÇ÷ï‰ÒTRZŠ]¢§»ΞvbØff8] º!0Å™ÇV!ØŽ"(†`sæÌ›Ž¬îF4sþ“vXÂà?’!¨ìÿÌ̸Ã`Μ90Ã!ìÿ3þ3³02þýóŸáÇL±€Í%x]€©Ó% ­Óhœá@pÇïXkRUiIEND®B`‚veusz-1.15/windows/icons/error_diamondfill.svg0000644002344000001440000000604311734662204021623 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_axisminorgridlines.png0000644002344000001440000000047511734662204023752 0ustar jssusers00000000000000‰PNG  IHDRóÿasRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÚ {N¡îtEXtCommentCreated with The GIMPïd%n”IDAT8Ëc` ظqãrh&J-g„™øúõkFQQÑÿ·oÝbTUS#Hûûû3¢¸ %%…¡¤´….®¨@ᣜ^øß1/À9sæ ú­"/Ø0@ë4¼H0pÙLÐ aÂÌLœ6n܈Uá¦uëÈóÉ *FÆ™üýý‰¢©æŠ)^.¬ÊÝAIEND®B`‚veusz-1.15/windows/icons/error_barbox.svg0000644002344000001440000000556311734662204020624 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-document-open.svg0000644002344000001440000001206611734662204021450 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:40:13 2004)
  • </Agent> </publisher> <creator id="creator27"> <Agent about="" id="Agent28"> <title id="title29">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-go-next.svg0000644002344000001440000001154711734662204020257 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent about="" id="Agent18"> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_box.svg0000644002344000001440000000567111734662204020137 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-document-export.svg0000644002344000001440000002014411734662204022024 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator22"> <Agent about="" id="Agent23"> <title id="title24">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/veusz-edit-prefs.svg0000644002344000001440000001513211734662204021343 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:40:13 2004)
  • </Agent> </publisher> <creator id="creator30"> <Agent id="Agent31" about=""> <title id="title32">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_fit.svg0000644002344000001440000001632211734662204020306 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-clipboard.svg0000644002344000001440000001204511734662204020627 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator28"> <Agent id="Agent29" about=""> <title id="title30">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_page.svg0000644002344000001440000000655211734662204020444 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_rect.svg0000644002344000001440000000456511734662204020467 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_nonorthpoint.svg0000644002344000001440000001066511734662204022271 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/logo.png0000644002344000001440000003061111734662204017053 0ustar jssusers00000000000000‰PNG  IHDRúW-nX sBIT|dˆ pHYsBBWF›tEXtSoftwarewww.inkscape.org›î< IDATxœíyœUÕ÷¿§¦§'Éô$“É2K¶N2YɦA –À ʦl DÔàQPEQ\AD"‹FÀ˜„,¤3Ù&dß;Ý“é™:ïU=SÝ]U]=K}æ7ŸúTuMÕ¹§nÝß=çž»”„B5k€~ XŽ}úØú-"Û›WíÆõ±x/ \Wñ¼æ`CŸ>Ç y¤/€õ˜nù‘qîS{÷¾4¯£i–•Q rP2d00¤° d›Û@ê×wïþc¼£iý_ÆÀß.‘)À;ûÙï¹/ˆ²Ø ²Ø²U„- nÞ|ãúª|"$Þ2ÜúéNðL¢Ë`‘L(îjE»"%saY>DÿK<þ—‚H^VöÉ2ãD8d¦Hx¢weÒ¾·uIõëwÁ[ O÷ïÚõ»D!i»¡¼üНuö;ÍJ›ÄŽ?¿ª³ixaÀ€NÎÍÎ×¶lGnܺuÊ4è–#€Ï‚-R20Ú+ïöôÚ³ÏYù^YùãU /ƒÌæmÚtÍÖ éWUýl ÈWrå·ïÝó½ó×Zù˜s. 7€LÉ/$ý"$Š?Ñ‹€°ÏÿeÉ’%Å'Nlö¹f¿!9uŠHørë—3³2;Ž÷_ *¿oßó‚\-ž Ò:ô’‹£@Žn(/¿ìVà¾;j ªG6DÂ_‘ûœò¯ŽÊ ˜öyÀÅÙ„³Ëg3È…Ê<øÖÈ•À¥í•(ù~ùžmàÚ® \r9 UU÷¾g“þ±¿°ÀçùN¹ÄMn—|upžÏ$o!×Èü4ÑóÞìõz@E¸XôWW¯îUŠÊ–¦x¤·1̶味_ ¾Ë…sžõsß´{÷ã+óÉí×ï¢Jàz‘ðÿ€”¶ËèôK®î.êßÿª3wì¸w]°'mGEÅW+DÂ#| †g!î ˆ„§¹["Y¼yóÊÊ;Î ß 2,WnúØ·òιÖÒ3ãZ&ƒLÆ*»>D/™ê'7«2ñÕÁýÚ|Ïá´ê²0$RÒ$!GfE=ž/ W×½$•‰=#ŒfKÈl}'7Jš×î:–½oßs/)9ªýŒ¯ûÈàûùä–—_:K$ü;`€ãÞ y…¼dw}˜ òvEÅ5ŸÚ¾ýÎ7òéä„HØQ3\æô~a!ò ÁàÁ·õ ÷ªèDä ²*+ï©áa‘ðéîå5wßE.³oE˜™¿î{—÷IÞ¾¶]÷¶k„ Üù¢½o¶÷Q¯´a¡ì“G%¹h5¦QÒŠ˜Àî‰'¦òÈê6ôëwaD$üC¿Ìs±ê×ìÜùPÒKfÿþW ß Ï¡>A^œßÞõÅ¥ÏW‚¼4`À Ó·m»í½|ÏÜv·„§ä>g†Ün³è"áÉ@Q6ÁϨ’©ªºoHøo"2Éy¿—\ï|÷·’.³gþ úd?‘ð¨\yÙ2‚”·Š'صYò„DÒDv3ù‰îÚF_²dK¤%ÜÛ…C{iní7¥¶Ï–?$’çÅ9÷}@ž8ð¦lÝú½@Í!ËâxÊÝ, "§#pwÛÓÇ—èÕÕ¿¬)™âQ!»Éd°QD6‚lzc¢GåÞï(CÞêÆÆ³<óY¤dJû ùˆ¬œdVRÁ®µt&`IH$¼h‘¢ 7Œè9®ûĉƒâØhÁ{æ‘á‰úM”†¶-IÁD&N¤CnÿþWŽ _„vf5ƒ|ÉK^EÅW ‘ðc"rR>yöþyWDX,Y³uë\Mß9pàœ¡ €€| ¤<ÏK޳ò=;8‰.Žsm²mÚôÕnó´¼Úçö¾È뙈„9$}¯¥[< ò8°jýús<ƒ—C‡>] ·‰9äd²ã7ÿΣÛVàšv=‚Xõ`×Zò3ÎOùlîµÎë¥~Õªi-¡½{_JE"§5‚Œðh—f£¢¬ìSe{ö<¹ÇãYóv¯M[¨›Âždÿæ^C{·²c3ôïÑEJîÂ>5ÛÞΓ;¶m»ýyw§xqõ WoÝúÝWüôÛºõæF øÛ Asrp^žþý“þÑ ›7_ÿ²ŸìÊÊ»ÊDJÆ8´Ï~Öýˆs­d—}øážM#€!Cþðy‘ðq™–+'?ž~òJcã™ê"&gîÞ·7€ŸöסÀgEäsÀ([¾oþ¬]{Òb`q4;ƒ‘#~ ¸Éƒ£ö±¨€Ý–)iaþwžâÝÅæÚFï ÌŸßØ«wÿHHC¡âYv*£¢âÚÓE§´ŸÉ[“6Šp‹—¼oú‚H¸­ÏÔýÞ2øÅ–-ßj-Dß-[¾¹8ðàÛ#"a ôðÒýÛ€/Ñ­6²ÓgÌ!L·↠y¬HÉ„ötstðM{èÐ?  ÿȧB¸iݺÓnî }×­;¹¸eĈWç3@.þÙ²;ƒQ£Í ? ±Î¸z4&Èå ã~ mD7Ç‘ŒâMtW×½+pä‘C“ `}Ñ꥓¢‘…Þ?`À%"á;}évîÚ­[¿³×MÞ A·œlwÏÑN–y¯gnÞ|CÁ•’›7_÷|eå=׋p¿wåÄÇ«ªî;jãÆÿñŒÂg·Ï³÷"Ý×µ&žé&b:Ý |‰.RrmméÌûEÄ®Z»ö¤û»Ti`Íš ¼bo£F-?U¤äI —Ï{L­\9êñô}i¢¯´öA‰.Q] üÌt Ó E´¼`’ˆ„¯Fk¢ ß²åp“5xðmEÂh/¸íÊ‘‹@ÎÜ´éšN‘Ü¡ÿƒ"òi,ëâ¢/€œt€è¿ÁPBžö9äéÖ Ÿã¸6ûþ'×®Ñå$?˜0zôª³EŠˆmHÝÊ/M çÄbCŸsÞë°èníW÷Š•¬‚¼ëu爯UŠ„?îSQü­«µ=˜P[Ûx‰Hø!ì®ÉÜò {AÎX±bðß³ï÷ z^«õÑ©£mtN:‘’¥îZzŸqîîM›¾âÚÍTUõ³3DÂGçÞß¶ß!"³6l¸¢Ë'ElØðÙXMͣψÈùitÈ'"ëן“3 fèЧz‹Oð®àº¯} R2Í'X¹rÍš£='B‰”Lgy£ÀY6|®Ûú¢ £ä}´BöûɉüŠ„ëÈijd<·µÏGŒx£·5ݳ|åsÛ«sïi«5ZA6v¶ÆŽÝqƒHøVëWfåè(¿[@NZ¾¼ÔÓ# lß~ç¶¾¾›¶iš¾í(€¨n…´Ñ>ØSa†Â­*“úµ ½»zç{Ïüº|÷·¿P†/ªªî-²ÇAÛg2÷Ìõ6\æÚ}(þ,5ó,ãÞç>üð‚׺Bwo„ßÏ3ªj€Û]yÚçÐC_8oª¢çÜ_ ¼ÔuxŒ·÷‘ð7½<0;×',[^æ'«ÍŶ­úäöš"ShVBƒÍ-Û²å›nd(Ä¢kR8B´e¼"fLÑThÜÄÿ‘.9gO*©sœqì]­úë~xÑ#n²jjí-þnn¦·Ë~ÐzûA$ü~žQU®’r‰žñ-Xsß»Ù8—I>¢ûE™n=zÕ«+WŽ<(fEvãÇ7 p—=ÃÐ>›ûÞDd•3—,aU>™¢—4`Íαg õ°ênÚBÚè:iLÙ_êöŽ5û(¨Hèµµ½»„ä55¿(¾Ùƒ82,}Ü 2ÛKža”|â#ïµÆÆO¸¶‘ºV/‰˜Ö¨­Ê=î›êóN—®^ýQßÁ*ÔyšK~;÷ùˆ¾Ø½’j“1ä×µµ^‹Õ°9Å„ ¡PøÃà2Àô Yœtœ[jœðÊ+|Dn–E‡Ì—ÑþÛ¥€Dq'zAmôX,†šR`1&cDŒ.ëš ßô÷³YÏù³ÆÆOút/…/Êã2çÙÖ ‡@\SHï%gaŠhôßÅ"%‡º\›>îæ@\戸¬Þµ±Øpßqú"áw=Žãüù “ÇŒÙzõŠÿÚ•úïwÅ55üÖ08/M袢 r§·wB!N~ôQDO¢{¸WÎ}ÔCf¾…'2Ð IÙ¶]wM›VX²„psóÎ>PôvO úäT·%Ú÷9Ϲ ¸ÉKÞ°a/Ž âã2/\»væ~i#Š„+=“>ÎéÒ  öy§Ýˆ«­]×ËŽš“©wðJfÅŠš5cÇnÛÒ¿ý¬ësŒyqܸ=ó@~<½|ùÁ¿¬ÙùçSR]Í"œž¶à.§¨ˆù†Á©wÝEAcIòÝ žD/h)©ÑhSZ’5I¥Ü³õMX™¬i1÷ ˜<¦Ü3Â8tè³"Rr­‹‡þ9ÏyÃÚµ'yöy‹”œÞ~}ÛY§œ–#þqŸå`¥ÙdíEÒÇmÝ#Òþg&§¯Ï•á”+®Èãç‘ðÔ<+¬tw .”•ž³|JÛLÝÛ.'{Ÿqn¦ˆÌöŸzø3ÈóË–…ºèü—¿LiUÏ3wºêöùy†Á'n¼בš~pÝ^5ÅÕÚ‘›±¾Dï–±îãVƘʥEE!]´bßîÃÆ¤ÍÈ„Hø™î8Cž‚ñO~ã—¶µ¸uŸ‡Ë|¸µ9Ï¥¯Ï[(3Îûtû½vG®þ%S¬½«\ð¬8;‹Ü‰,9•l Å&–//ûùøñͧbO j—å.×&J‰að {ÓÃçmÃàiþ0>yW ênÌK¿êjž…˜žÕ'˲?+Â9—]Æ¾Ž¤ã$úû…./—ÌŒzÈì¶‘qu£#ÛÞíŽiQ±ɇ¥,½ DÀñì&Èì5k¦{Ö‰Fßê+>Ö)ǃ0žû<Í _Û®Kis¢°ÎUO\V7Y¾bEUÁV"(DJ¦e¦—cÕ{†¾Ì0xË0‘íÞŠøº¼bnoß?÷\xôQÖtéCÀ/~ÁÀš^2 ¦¸ÜAüÇ ƒ O;–ަÕFô?¼(5dÈÖAz›[ÎØG=dêG÷ƒÝ<¶ï(¨¿¼urmßGJä@$ü-ì%ò[F¸oõêúZ‘’“i«¼òU‚ÙV·{¯uyÎU+VTe :3fs‘í>ÛÏ“#?LMm××™¶ˆlZº4Xä`É6Ÿ|2‡?á †áÕ¦õ"¾ƒDÓìí‡×]Ç¿B!5 øþ÷;f5 ÁsÏQSSÈÁ„<ú> qÅ´iW’ábÛ}é#ì_é³x *† {¡lݺS²ûÒý,ºQ¿bÏm; Cލßħë* ooà1T6}Ë^P¢]Ï<íÒ­À·ò%&Îiâ^·ëìZw«îŸžã9sF䉔Œúø¼ÓnkŸOžL¯>}B‡¤ °K»³à´ÿúWö³/»ŒGB!4 ñ]ˆµ?ÂÞ®þéO¹fölþÜ ÙÀÛo­®fž£¼ôµõú‰apͰaîFôãì_é³.DϰêÙÝQ~mtSDV«r– ¦îŽ_½d›ùó‰ûnïð85“’ŸÐæM"ä ÚÚ³ lèSZUÝ{ü¶ã¨L܇_úU"îûBìÄâ.DÏ\ ²]÷¶+ºÍ¢×ÔPg„<Úˆt<øÐCÌÿö·9Ì08Æ0øŒapN(Dÿ|¤ÏcíG‰ðÌÓOó¢að¥3Î Ö•ù±f 㪪xÙ0ê’N}oéÕ‹9]•®ÑÓ¿p·0û(¹D÷´èﮦÜP=¤D”}F‘¬¢‹†6Œµät‘ð¬,ýüˆþðSFx@dxª)~Â’ÕòljÑÒ¶è¬H¸*[®Cö‡ÀeþD÷t™=¯õÉs×kò=ˆž{­]ØÕ0º/W]Í4vgzë”7ñÝï¢ÀkÀk¿ú_2 N1 >mœ 1À'‚ô³ ƒy R7ujá œ¸¡©‰IÕÕ¼d …@UQU˦˜j¢–}¹!\þaW¤™†ÑÝ, ¸²¨‹LÏ6º±{S*TQyѾ¦=ãŠDÎ?lTé£SßÂèѱ¶%²õWËk‚̎ņe¸DÉ&¶„ óÍÃF÷Ýä<ŸIôÙË>ø`À‹]ñÝ…òò¢©ÙsìØ_ÿJ·õ3WU1ËÇ5í4ÑøÜçhžžyåÄ08Ì08Ö08Ù081"\ µnÜ \ÐYÝš[›?&!}ÁDû·ªÒœ²HŽ‚‰‰u¨ªª³ûõêwo§3# YDÏ^ã=/a¢.2=-z]]å^`/”Íÿ`ÞÖ: "áë@F;Î8ö®–ñ¡+ªÞΖsøø²m¸|XÂ"ºgžð.?œqRUÕÕui+w›Û~÷Ý”bÝÃzî¸àVwGÚLJõövÏš5ô5 N7 >eœ QÄÚŸ1Õüƒ!Æ3ÕeWÓ®Š>«ªÛb£ªN‚£h+Ê¥•‘JßnÞŽÂÅ¢C¦åjÿíB˜¨‹Ì@ýèc«;¼@dÆŒÙ8T$|£C'¼ÝZØÜTþ¸q{{‹„ûù¸Î]_è.TW3Ò0èë6”Ò.ÔÝæ¶ÂY"–õp™¬¡³1‚ÝX+å>¢ªÃ½IU? „ÅT+¨mP1³ÕD[”Y¥áÒ}c|㩊>©ª½Úåg”fE?->Ù5O›‹ B®Z5eë¨QKvã¿rŠó\ÔEæ~]aF$|;ñÉ£öãösÝ¿¹|y$ð砬ᦹò猎i¾PSÃD—9½uÛŒµª*Î÷ð"ÒÄïÖñõ^‘u‚\OŨªT´.Ý^2HoÿV.-8U;W­ªÅ鶸so“=©ªŸœ0hB·6ÿr,¯mÕ'»&Ç}ºÈ ÔÞ7nϱb}°‹‹C÷…`-ápqIžèè„Î>Gw¢ºÚê§õé^ê¢Çb ª©áx—ôœÄ? DO#Ž¬Ø´wÓñ(/+:Ù…àé6ô¡…Ê^ºeé%Š>¤hQvÐÍAö=ªzÚÔꩯwí“åƒè2ÙqƱϱêcÆl,[±¢ÊÙ—¾_,ú¸qûŠDJîöÒ3ûœˆ¨_\²¤°C†°Ù'…aPp!ØŸ¨ªb¢[”Ù~ŽÝGM·|¼¦†OYÝj"9~a7–ŒÊÒÊm ;.ê³Éhj›‹ÝoéÖ¥á 'šë¾pÃÂ/*zªŠÁQÕíŠÎ:jØQ9±¢î€—E'`{¬vzv›ÐÍ AD견«_·Éï¼BÁŸ~áv\v)àØÃñµ¯ùáÉY£í`À!D}*ªnù¢msks(TÂUéön«*ښᮢªqEWô-éÛ*„QýG-Z¼yq0ÊÕņ­Sª¦"ùëÞ¸AUouzmþ¶ÉÜœ8säÌn[q7yˆž·½ îDï2¼·2~ܤёŒõ´>š••ò=ŸHr6ñw_ï¨55l1 jÜ\PÄ08x³ÓÛ ¨ª¢Â'Ÿ:´lv>$SÉë=ÌÙîÍ&¢ïV–VvÉra]dKR]žn¯òz^nxùE¿ ™îV›|¢3OsZ—õ:—ëžþ娻ÎÀ£Þi,X@qqß=‰Èáõ+â×Ô‰´E$««™kô÷‰$g»¨7Ýq›;ªKu5KœDwI÷hR¢WWSáâ2§ peW‡S6Å751ojK+—àé@ÛçN¼¾æõQŠŽvZà¬öúZ¿ûŸ[ñœ¨ê]À—}Ž¢1UyÎÄs|åuݵ½ë´ ÑîPlÚ4Rï­¥‚ õ.m³è³g3µ¦†Ïç‰$;I_oXƒ2°`Á‚â¢þc¢E¦ 4 ¶MUæùmµª*^2¬‘V^ƒ>®ûÇ?øùÑGwhÜ~·¢©5¡Y.³s_“H%ú—÷*Ï™ÖÚ¬Ú¹j°ª>ôò‰2§Ï4DO´$NÌÉgŨú¯{ŸXò„<¨è¥9#Ü2Ÿw±¢'^\wñ™ŸCôP(¼Æ00C! Ÿö®“LÑîRnÒ˜Èà¦'À˜Ì‹ Â=†=c)Àì$ ƒÙ—\BÎ÷ΦM›–ª_?E¡RZÍÅoƶoüXm…ë±!CxÉ0ø¡KÄ=½U77tW^tÉTr™¢Ã\,jºZÞ«üMgé–¥eо èŸ(³sPýéeO—*úyW‚[Ç-ŠþÑíÞßÖÿ¶XÑߪêyàè–Ë}Þ«ê¬+§]Ù-1‘ È!úâÅ4Ÿr " ÷hïf[Ðݬ£žƒEÔª*.4 ¦ ¾aünÖ,\ ñ’%„¥åj¢&ì^Zá9×·ªŠzÃ`a0Ì'‚ýµ¦&VõêÅ}Ý•ilMl-SôJ”òA¥ƒ|gß%R‰¥ŠžèBðôþkàžGA±pÃÂÑj ™”àªÚ¤èR/yóVÍ›­ªS}ðÄQ'vÛb›-~¬—ª>£èG|òç¥ÏMþ\AXø@‰ª>œîCp€¨êi_ùØWèrV®}UU4Ãó,kÓ­®{6ž{޲êjnó|‘MÀÝ¡×{ÉÛ¼ùUf|¿_ÍÞC0Œ««ÉYP1#PÓäÃà~Ÿ‰¨êÏšZ´BUoí]Ü»ËM»+ý"ÊlEû+êùɨ4-‰¥ØÒ=­~Sýåu•uvD§7Ö½q&ð+SÍòl² 9ù“N{ÑI£Oò¬X©ÄQÀùªú¹§—?½åE{Öø³º,xøËwVÕ§™¥›3RŠ~#û޻ߺ»TUŸ11gæyÞ—=ëëG}ݳlí/¸½¦†•†Á ¯@WéÜ{/eW]…×÷Ò»55Ì1 ªê„aðÝÊJ<ÛC3f̰ Zé{AÒßךü¥¶ê5ŠŽ's"‚³pŠÎf%R‰kôÐé>Ò† ÃL5? |RÑÛi¤räBšL%—zÜyîÎùóCÀƒG=2Ð*&óVÍ›|ÇTóXÈd²ÜÄŒ¢”dT0í×øºíÉ–ä‡n‡*z7Êm,zä U}ຠ=?"™÷þûÞRU½DÑ«19wVVª·^{äµåã¶7n+Rô%UžAð¬a­ªú'àüïûƒbyW¢WWÓ0Е&Ùºñãïë×3¶ºš« Ðé}Uu|¥óè]Ü»e[bÛõ&æ³9µ8¦3pƒ¢G+úÖÞ]{ßTô”劮TtÍÈò‘žDZ¶uY™¢u(“Ô©õ`J6AmWQU5o%•H%)º(ñ‰—ªê½Š^órÃËs}h8qÔ‰mc^ˆ½P¦è$”S=ÕTs²ãyy±åREßð©`<‰þë÷~Q»ï¸E{«êÅŠ^|ÿ‚û—5Õ|ø·¢+®úÈUž‹3Üù楪:RÑ Q®P´¿G“ÂyîEoqÉÏñŠEr‚ì6Õ\|ãú—¯Ï”ô«€óþÏ¡÷¼_âמM0O¢{º<¥‰^]ÍO ƒbUõ xÐÖÀŒVýRŸâ>^_Ë ú xní®µßRô–‚;^ˆ#êú1E?æÐ±uñæÅk•j ì§h¿ô¨ª’ýŒéã¬.ŸG ;*ï éæo¹á函*K7·Â6x8ýûéeOoQ´IÑA¨I÷ÕMiPôxE§çÉO¢'R‰:Àð+Ô&æ” Ž f÷íóo_kª¹ ئª; 7Õ®hE€ÊÙYi½ƒrâÜãçæXãdKrªãy3d:ò§¯¢ßôhÞu„Ä^‡sÿ¾_9ðrÝ3,z€èvÔ/‘l¼ú*¡CöÎ8lLiÞ/`6µ4¡ª³ÌÖÜŒuË@àñŠÞ¯ø í†÷>wéÖ¥#P.w|£®ékŠi[—޾ÐtZg~%[’·šj^¢h©Á½ô”£ƒ÷õ«=îóS>ßøÀÂ÷©[ð`•lI¶y0äO_EuI+·Ì¸T>y«¼­èÉwœt‡k—c"•˜æöîÜ*@òé­›_…u}λËÓ‹áŒóŒ¸»?ê—H6 Ùs¡Š|ëÕW?c†÷Ê–;’;J½ÓÅ=ts—0ÕŒ+zmEïŠBÔ)ÉTò ŠnWÕë#OÔ5‡$ùHì÷’V=0ÑO{úºß¿ÿû‹Õš©%]ìBª¢×Ïþèìvþîù¾Tß¿~úõž‹/&R‰)®ºùU€YäÊÖÑ«Ìd=“ªêýŠ~íç§þÜ3Þ”&ºß{r‹ßdè <&¸u¼KQ×U‘Óp%úÈ‘lmiaaPfîî†óaM4 ½üÒaÁúh­ˆ|^1tÏCïÅxMDÞªÉqý©ÄuŠŽòËŒ DoS1¦[&i81µzj+ðõ××¾þ,ʯÕ1ªªZÜ¥Vv-8J†ÅØ«ªž-Dçó9ÿ©‡Þyè:E]äBƽâº#¯kó n}ãÖ"`ªíb»YTÿ@\*Ùæ’{êæŸ?~Í/™K½âWgüÊ7È÷™§>&ôÆ=úIDATc(:Å›¾î|0‚ç{î׿3ÿÒùê§»çÔ¡š´U'¥'#ä±DÑ^!¢O«&QÓ#¹¶ý¬\,pR«23ûúØöØPà¼ÜåÀ¾Jt1Ž~Ì/®|qÊçMÌ«PÆ:_$¸ß µ~Ç}^­Z¯]2é’-G|Ù”Ëî¸ç­{ê}ì©[~‚oTôAUýÁœcæd|˜1™Jbbf4Ò…Ú~^_O$Ñ’8V­>ôcTõXà㊖{’+ Á=ÞÅ|”ûM5ûý§~Ÿ7:žhIŒSÕÒŒ´‚ÜM·MŠ ÝÞEûõyy=‘J4“Z¢(½ó%uµe×-Z¥ÂYés†r|ݘHÎà‰dKòvUíôåšj~鈡Gì÷¯hÎ=k/ð“'—>y·¢'¨µjÉ‘¦š#:º’Ø›ô EßPÕçýËS¯è² _:üK/ßúÆ­£+UõjE‡úêÖNð=(O)úˆªþ}îñssF$ZEªúgÎ’éû}º;Oº³xËÞnÿü³Ÿ7ÔZâXEŽVÕ.•{^“á2·(ú¾¢¯£üâ©sŸ*h.~"•©êO Påìö^]u+˜à¹ïÉuäž’œÆÝ·+zm€B™Þ—06^‹Ÿœ_W9ÓMn}lÏó 'c-Á4P —=Îü­õo«è«,Kº =9#:ãl§Œ'ž àœs2‡¿Ö/oÙ«T›Çí½~ÁÒDu¨(iL; ËÝý‡ß{x ªÔ™jV(ZôSÕrE÷©êFE7©êF`“¢Uu“¢›ftö~Eõ­W¾5HѱXÞÈXEÇ©j‘¢ªÚ4*ºNUß¼ýÄÛ»í³ÊAqޓ牪V)Z Tªê`EÛ÷è`SѦš;v^/Pôç?ýü†O‹žlI6h‰¢êb“å†rí¾HdM8ÿªiJ ÐFô×Ö¼V¤è=~5t–»”Pô«`Ïx+Û{ŒIkɏɯ5{Sæy¹f¼„öU45}½>¶ggq±¨¡%wpb!øì¤Ïnž··ƒ·wË` ÐáA(ûêq6Ø[›è©äÊûÿ¢ z]mäšö_‘œ vDû0×´ÜÛCßÿä„O®kÆ[ý ó Ebœ¨,/ å|y[ÑŽ÷+Z"ÐK!i¤"õ*®=èAWÀ“è{S{ `ä¸èŸ–ýi ¢ß+ XSÕÛ2Z1ï Qt" Í©Ökëœÿ˜8Òt ˜k)Ú5ú­ê¬îE},.À0`¬½úb-|™ÞúxüHMö–•Ù[]íÁÿ­ðt=ü,úEMœýÄYA†,ÒG;«L"•˜«hÿâ_¹|êåhC&5äæÐ¾Ä}-å RÚ6ŠŠ~‚ÊÚjFQc¿­X P‹—‡`‘y íÄ®… !MO”Ù[cã]Òßݾ`ú/à_uµ‘.ýìP>xãžýàÙ5Àð<OŸ{ò܉çž/ç…_¼ó‹©ÀÛjOÚ`Õÿüåÿü‰N<û~A},^ L޲·‰tn~+–ÅîµgW`6é±VÊy«®6Ò%Ÿ!êÁÁß%8í€Üp4¤0ÚQ%~úöOEUïQÔðJ+«·IU¯îhzÝ…úX< L¥ÔÓJŸ[Z°\êeö¶ ØĽöÞy¼·®6’´Ó ôʳ¶XÞÂ{óÓûúÿgof},^¼Ž5WýõºÚH‡—âêÁ‡?ÑSÉSÍiQÁµÿ/š/±úå{Æ’)u£#¿wžO¤Ó6PÕÛ¾sìw†vµLN´·é¸ÜE;™ÛʺÚH‡úþëj#ŠU)ì|× «ÅûÓNú À¡À‘X17Àd{û²-c9ñ_Ç"¾oš=8¸àKôD*ÑàFpp%ã€ÏÿqäÄAWºÊZ¾|k™•\)Êß‹Åo6áSj#K¾÷ú÷ÊL5oóéÖDX¥ª·vi.€úX¼8˜ÌIJ†ÙX‹e ÿ×ÞÞ·‰y@PWÙüÓÞ€¶Jjðqàh{«ò3ÎÞ.·ï_ƒEú×€¿×ÕFxÅÛoø½%Ñ&ZÀ!…Q/YME½84,Èö)µ¥K©ÄE«sFy7®¾{ÖÝM]ñðAQ‹Ggb‘¼†vkhïã v]mdÝþÔ¯#¨«˜XÃQßî¶xÂÑ´“ŒˆÀEöF},¾ø{z««ôôsDÈ뺻 çó!cÔKÖ¤ÚÒ—Åâ?F™©†®[ðÁž¿Yõ­é¶¶ë¬#ršÏ?xÚƒî‚ç„úX¼ø0 ¶ÿµ˜¼¼VWé–õÑ÷7ìè{ ø´y/NâOÂ;.µ7êcñe´ÿÕºÚàß»ëA0ÔÇâ#€Ã€¿äó»îh `ðüJjK«ñ§)ãúüË rÿÉß)Zœg|rº2Ù§ª_ ”„íÖ~ «-;+ŸÖü_鎪«lÂzæ?ÔÇâeXqˆ™ÀÉ@Ïíãíí*@ëcñ÷h'þëuµ‘n]zì¿õ±ø@à\àÓXã(NÒ,ôí^¸ú¯Wï6Õ,ƒü3„TõdzÇßñot¯]ôôEg¨µg†wàÓD˜ûÔ9Où®xÚ°£Ùé~íÀ¬6vϺ,ÔÇâ5ÀIXñŠ  ´ÿ¦øÿL÷&ô õ±x«Ùø¬|au…žÔ›Ìû…;ÛªOò#8´‘1Šõ}qög[ J8Ýÿ&ÂZE¿äü`“¸Ê«ýX‹ƒ\+–ÖÕFök<à? uµ‘‡‡m/è£X–~p8Þn~+æqð `_},>‹ô¯ov´7â¿vWí,,rŸŽe½Óx8½®6ø{ù‰nä&eÑÛÅŽI4Ñ’¸Íú á«wO.iu0®®62/HZõ±øYXÁ³w€s€zì6¨ «»jk]m$ˆøذƒ{oÚÛ÷ì€'`Ö“¡>·—`ÅAfØ¿“¶«¿Ð±-þo'¿ô=88kŒDë€'ìíÍB{qò=; çåbÛÿ‹æ“wüo¦ªßð™[›ÝDøÛ¿.ýדvFÌÃ>ÔÇâ¯ÕÕF‚,¹˜CûpÏ%wìÌëТ=È„íR:Û÷‡ÐníÆI¢Þ´[ü4šëcñÅd’Q]m䀯™ÞQÔÇâEÀ4à8¬áÊ'Ór¯ÃÊ¿?Ðr;ÔuºjÆ€íû6õª(ñˆ•H%Ú”ÿYq¦š)Àù‘‚5XrV[å…Ï8 Hg³lksÏK´ü?uµ‘÷±º!ï¨Å{cYï“ím|éQ‡Sç´>ÿ«§ {[y°üêcñáXóޱ·ìAK´“û_]5þ"˜E÷¶¶9D]ºãAGUÍr•5éþI3=ò¬šÑþ¿;WÌ^±Új¿yXó×ÖÕFò’¼>¯ÀêîyÚ>õVí¸Ó¾&Ýz+Â^lÊtìÀÛ ö–î&šŽEâiX£ ˈJ7å†Çfÿ³>ߌEúuX½AÙÛ¶ôqWí lV·kâ!ö6‘ÌIGN¤Éý0¿;Wj£š6ŠÕ¶^µûW¢ûÙ¸"EïöZHÂ…ôë½9}]m¤x >/ÍIÀuµ‘í´÷ëžœTWy&ëšfÛ­œ†åÚ?Dvºuµ‘5XÞÚcÐ4E;ñ§ÙÇ….ï;˜ö±¾¨ÅX¤ß‹Õ+Ђ5Èï¸7©b¼OŽ`wìÆ`õ*Vðqawœ ⺯ÌœYeíõÆøªA®rZ_@9,ÏÂùN™×n¿~»kT±®6Rðç‰ëj#®Åsѫŧ`Õ´), Q[‹/ ØþïA7À.ôé9ôO¤ÏÛ–¿«™µ"´ú` ŠêlÄŠ)¼jo mõߗè‹ÿgqó¨{F­WÕaž.¶ÃÚoÜÛ82×jtû &æ÷,$‘>÷JâÆÄã]ý°uµ‘FçïúX¼ËÅZŽUxvbÕ¶UXîT"8,N‹íåE±HÅš±7”µ@×Mïu"5ûp9Ö„¥¶ýÁ0å7/Ñ¡- 7ÌàéãMÉ ƒÈZÏ+‘J|_­¯iç·€kÛ±í}½¾aÞÔÕFöa¯éV‹_¼øŸ0N½¹°½¼tÀÏv³ ?íįÀ ô…°*€Ï1XÆ`;Vû¾m°·DôdKr¥ZËíæ](rKbSÑ›iŠ^V€NwëœÌïH½ÛÞ·ÌèSÞ‹ðŸ—,Ùòñ‰(P~°Ei{Ðõ°›ÛímùVg¿ÁrQ2•lH¤$R ’-I’©$‰븩¥‰}­ûhnm&e¦ØµoG[]nQôž é`µe¾›}²·–ü¿³eµÀ¤–’>‹êc{N(/0zHÞƒÿf"`ÊL5´j+¦š Ë»¡ÙÜÙݼ3í)\„µÀAP\¯s4Ç5ßÛ»ôi€šúR]mYAŸ#êAþ¯#¨¥õý€[ÎÅ»–õ‘›¥ ¸­€ÛþWçèïœ'Þ³º½èײ·ŸaÈ …C1ø{Põ+ãß, },À¹P¤<zЃƒÚèHôÕ{VônÂÅ'Z/:O¼»zg¹AèùwgOUú6–[y‚-i,Ší9äÊE+ã[D‹çí3›wM[æ9… ¸ßîiõ1\Td¬imÑÚº1‘g°§Ôö ÿÉd±tŽnÁZœ0ÞØðò` ¹ã÷ê­xoEü­ÜûœÑú_`¸ÑÚúwWî9*ËÚzbqCâ#õ±økˆñ$€*ך´ü±8dLÊw¯bìŒÑ--z¤ EàýIçôà? …¸¦­úßÖþi$#&°ËúÐËlzDUG` ‘S •‹ûî=Ûãþ :ªÏ¿Áø±i¶M´à×u£J}g»½»lgÔþ&œ¬ô^¼29÷½ÿè¢ûë\ðu£m“KƸGÈøâ¦sJݘÒÇ‚ ¬«íógš@¶©’@Íš|÷$#ñM˜Æã¦Ê Ã0LZŠ^ß'M=cÞ{ð_ mt(°ÿÂZ¸ ŠÌá~Uí rñû£û®šX€Ð (.î§¿õ‹ü Ù‚Yœˆ‘ïž#‡Më_]²eWE8rhݸ^«ügRö ÿ9È»”TÛ…7ËUÀϺ0mø¨ÎÉùˆ»±`EÓ¦‘X´2qJoíó÷ÚÚžyâ=èAGq -ú.$0§Mkt6ºO9ç=èA|Ð-môØÔÇ݃ô ã(„è«éº>åuŽnï"Y=èAò 0ÑuŽ6ë» Í·‡º@NzЃ€(´¸³î»³uŽöŒ6ëAö# ÆEôœµ¹‚bzõÌå÷÷ô¥‹VÆ/í¨Œ|PP1I™¢)£ÙÔÖÃ0šMµ~«j‹Ñ,¦¦#…IŠ"£³%%R”ÂÔT‹hJDS!)j–M‰¡©fÃLÑl¦´4Ü\¶ÏLÅC-)c÷¾Tª®²ù#Т6x =ØŸ(”èÏ’Ç 8¦fÖ å%†dŸ¯Nž;^5ЊŸª$©h1šTI †ý›&L’¨4)$E4‰Ùbý3©"É"HŠI“i˜I14ib4µÉÖp(YÔl&›Ål’æ’d(d$÷6bêО¡²=8¸ñÿß@hù½é¹˜IEND®B`‚veusz-1.15/windows/icons/veusz.svg0000644002344000001440000006514511734662204017314 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/downarrow_blank.png0000644002344000001440000000030311734662204021277 0ustar jssusers00000000000000‰PNG  IHDR Vu\çbKGDÿÿÿ ½§“ pHYs  šœtIMEÖ 4c{£ôtEXtCommentCreated with The GIMPïd%n'IDAT(Ïcd```hnnf ÔÖÖ¯f0‰`TÃàÐÀHjÒ%> ',@íIEND®B`‚veusz-1.15/windows/icons/logo.svg0000644002344000001440000010377211734662204017077 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/kde-window-close.svg0000644002344000001440000001066511734662204021310 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent id="Agent18" about=""> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_none.svg0000644002344000001440000000422211734662204020275 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz_128.png0000644002344000001440000002002711734662204017661 0ustar jssusers00000000000000‰PNG  IHDR€€Ã>aËsBIT|dˆ pHYs  ;dtEXtSoftwarewww.inkscape.org›î<”IDATxœí}ytÕ•÷ïVWkë–¬–lË»ÛRË‹°$Œ'd†2NÈ$ Î’™L Áì›EÁ1›YŒIæËC‚3$|a‚!q€ baKvËjµ„¼c[Xî–¥.uÕýþèEÕݵw·$¿sút½W˽¯Þ¯î»o©[ärMßC„ éÿzyYùÿ32Ò{)l =©àj x?°sÞ‡ eeãð6€ ­{M”žÎ>&µ/ÐòÁÁ-Gìȉē굄Y&B“¡ ¸ˆÎûPH¼ @}"…ô½šopo|Ý3…Ñ2ŽL c·U“•¶.+íU^-ÀŸ:–׸&K¢ùº¦S$zˆÖŸøìcˆ ´ªP:&‘јõOÕÛ†ÍÀ˜À+–|b'€I­ùº¦ÔÔ<òU@<7ž²ëH§Ý÷:te[ÔLAmzRzÊéôO3óüeç»÷‡«‰i!XnÚ—#ù¸®]L›öxI|¶Èq<å@-Q2© 8vìþèäÉ·0ËίA ¿EÙ¦sFho?ìáRÏ߃¸’˜¼‘"@ƒánŽü~S âŒéBñf~sGÚ”·<øíãù×/O h–Å']oŸß†lÇ ©©f°½'üÄðe€Á)`b4I—Š4²cXùÓ§ÿxNb¥œ9Ò©íwúIÔÌBÚªšÑiáÌ)áø¿…)a;ÓÂ97MóÊ;Á VÜ7Xq³<üëÅuU{s¹¶]‰‰¥éS»Ù÷Jo…Ob¿Bä¾êÀoŽÙR·´ 0Z!¬Ÿ—Õ ð[”ÀÅäfÀ †€YOrMÎ넉fÌøùç1±DIË‘¶ìüxÿþ•Û ¢¤2ÖÕ¹CÚ ŒÖŽ.Iü¦ÔÔl*³ ;'`4 ÀžWˆÞ!4(F«s¿®5ÌœùK‘¸ÉÊ“n°žDb?‘xËX鄆ô+ë€ÄXÀ_-ÈÎÝ®7N«+ÙË ð—}%¦'æ Dâ¿Xlc´4+/a,nß»÷‚cQÒ™èN*•þo¾­Ñ ˜ /M@CmI«ZÌÆ9Žæò©mÏÐŒæù¥ûíœ4kÖo&â]‰K¨/§ñ ¨ÓY÷o;@OÚ‘/¤UÀ¡C«ÞŸ6í‰A€<€é0eÆ¥lûã>°uëV±fÞ²)Ã#Ê|Q¤Zÿ6zDâÝU&Rê=ÐËÓdc«úú¾0.¯¼eU@bV°1‘Bú|ÛB3à·({\ pöÙgË;‚'Îq±°HaæÁÁÕ1ðO–Ì×Ìžý»¥€û2ÀhäÔ)~Ö×÷¹ÿuX„œ¡A1Aë#Yìö[=îCÀ;ÌÔÈÒKg,ð™Vþœ9¯Rb¼?áD=(Ùùªÿkœ«Ÿ;4* }RȸÈL§þýdO€Á-2ÿ‡"òd°ÛÒ2‘øM€>£ÊÉø7ÎSYÐ;{{—ç}!ŠèX゘¿GH~ ² ½ý°G,õÖ7Ô{vØ=W K^[+mæÎ}³pßO©ïIzÚ)vxÌŽìB@ÏPçdüëç©nÂÔÙ³_,Ý»÷KFoòZ ¶à‰o¸qwLŽ ežÕ1(ïÈ ì‚H¼ ÄÂqó^’]ÕÓ³$V%m@§ 4Ì• †sÉ?Œ»‚V-£JQ”ï ŠÌDÎË;üþmóñ:û‘<²¶ÑÓÓøÇBèhYo؉½D"khÎX˜0{WÐr/@$áUnpb€ùü¶®Èåo½µ¯ÔVis‘{#‘»Èd<ß`äÔ "w„H¼IG„«µµÕÝÑqÄ;Veʪ€½{W Ï™óÊ€fŽæÚm ~€e@!:`7ƒr¼ÜSãëðš•käŠÚÚ]_"ÏͱÖKÒ˜ý[×Ý]¯9àÔ ¯pU̯V¨è `l^‚Õ©wÀÌôB6½]¿‰lK>Àö`¤Áþ,!4ŒRf<Ô\_þ¼Ù¹ùBmíîb@ܨݵ³3HFÖ’±«H𹈽²"-êèèÞÛÐÐPˆWáÒ I€ÄXÀgÍ{Úù `© Hxé_€Ýá3Ð á ³óò "ñ€ê2r3þµ¾¬{rM08[³«yd3¸jAä¿”²Ò³šë+^ÆdJ؈pêä$n„ßD¶ínàéuåjë ¯_R_ž×7xŒôÎ$o³éä¥ý'”绺¦¿¢'gåJÈ@Í €W0†¡ot* ³'`«›„ßD¶£q€æúòíž“Ä <ê2:©7Ð ŽiÜ# àØÉIn×ÔÖv”†B zcæÝ@=Ô×ú,‘xqßúà’¥µ¾ÌcÎ?Ÿ´*[‹jd»åG?˜ÍSä ³^#U›–=_õ±„øXÀ¸Ìß'!‹øÜfvsѫۃ‘™ ?«ªp+€YzO¼EŒ‚õ`N›7ov­\¹RÎK!B—ž¡… ¥ƒfÄs¬x¾£ûTN’ãL7¹~=‚Ø=b ^³¤.½ò¯º u>nôM~ò—于exø¶ÛŒËÚÛ‹¿í!LZÐ|þù~‘çâÚ‚I7L 4#™R{¾6ÚHt·Œht˜¢Ùå*-(kø¹² Š}“¯&€NÙXg¦Ë yðì¶ /sCˆA` þÔŒÛw àõ !AÀYÚ½ M€ÓVöêíÛ°çù|ø²•2™ঋ/6_Ä1RæyÝ Ÿâ@Rûžðe¢è~¥¡¶¤/ç‚:€!|>„ŒÚ=‹m¤Ý0òc†§ž‚ÛçÃF«VÍÀûÿãŠxÖŠÌâ¡“ %£´ 1òî¡Ò’£ù/5X&€^Û§—7‘,€|>\+X`Õ²éXæÄ ¹‰\Ï‚x+rióüò7ó].;È;4L¤¿€ú;Æ«¯bZUþpDjuþã Ö?–Õ(}+±¹£-Y˜¿9ƒ%81‘ª¼i=†âU«Æ?|›>î#B9`îø<‡Ü¡# rsÀk5²zÁ`F€C‚€!"”æÐFRÂØSÀrØB0ˆOû|øg“±|+XSR‚¬A¥S †X³üôÓè!BƒÔù:Ö B€#G ø|xTâãWV ÀœX‘Žø6€·ü³ ;¡i¦Úû|© àÀIšP~€Ï‡KŸPW*«"L$Óœ:¤0À±ÑÊfV|U™»ì”ün¡¦¨¬u`â$úó¥ðξŸÃ( d©„»3+5IN¾wÂݧJ«Ž{²²¤r{>Ê3Þ°drp’’yþ|(»=™â‚ø[Ëá`ôL’¥;<@Z¥f<ÝfÄ8àöœ 3A`J€êj„çH›—Á Ñj0ª½+raS½÷¿ìœÛ?Ô€«Ò*Xýt'I J§5 œÊ½uvÅìþ|”g"À’Ì»JºON|ÛŸã pÚÕYÂUñnïì<úÊ‚“-ÇÑ—diƒEJÅhý³&1çmcðÓ¹d¢Á”Þr¥GËIR7FÇIʼ‘Ó£r´Ø[äµ5ÐŒ|€;Ì¥…~;=ê*9±³;r{H «k=;õ®Ó7Ð÷ >G§R A1˜Á«M^4.Áœ S$œŠ 0ÍŠ“¤Óž€9l}ð°9àÝÜŒœ `‘Ö~fL'УÊöKü`š>zúã§M5MoÛÑÿT€¥NlTކL,9I©¼Œë‡Mঀ÷Šö`¾›¹“€UÏã&º¯aæ”bø´g–itû8€µ6u?%`‰’,…Šdî$éµ§~‡:2[Xƒ’0bø9•퇶Ïp³N¥A]&-gÎ>sÌ^HKX·Öœ$£ëwª$3šU‹Þ0ܲk!]³,ÉÒCÌ\ªS©V-X3ÿÐ@=€n¬Ÿ`02% 4”ÉÞw,@€Ð˜'(¢wÉ|OÁ¿bëÀÜIÒ¼‰ª›ìw¬%Q3ƒ·+-ýû+Z}3#— î`Ò'À}o|žÁfÕ‚¿’IŒUçÕ§¹n¯­+ü%"úVSÀ»ROõïqweäÊaW„Ú»ÐÅ„IEär³‹óþP'°F€˜²í='óF‡[ýN•dðÚÔÌY=xŸl=ˆÿ#ó´Ž­ç5À¦ÌJ5rütš¶g.˜AÚ|}{0üÎe@!¢Ùã©à¥K1Òä6-a‚ ` &ç,7&N’•}~§JjM›Æ?ñ®=•£«ÀhHébß_ƒÃnμ¶@#0ý+n$Ž¥Êmô"X’’ 7Õyß·~ ÁÊA’,di8‹"*Ç’,A’¥´íd:™—ñ?ý½(*t~Óù›©RLº3©GTŽBŠê6ÎÒ_3“¾wÑ¢‹f^q]Õ^~™H¦¹ƒ‘,U FœŒIP²gßÐL½ãÇ–,À²™Ëøõ¾×{ÁXh·=U¥‰ÁsóZ‚ H²t73OŠ«—aþ­÷ÿ;˜ù=D™ALô[0">¿ é níí-©ÂäÅ`$v0sµÂ˜:2¬,`+<}!`y2[ŠIÝ ^Øëÿg˜^? H€gv>ó 0.Õ”mâ¯d4_W_±ô ]Ï^aêaRÎ?½Îû'3ʆ«=TDwínõvÄãïìWñd§åÌ'¬@uí¶§©¼ü3üdÇOÀ£ÌLšŽŸuÇuóªO®ú½‘¬æ€G3Ô›–-,?àXcmvžÕk– rí÷ÿ“à\º‚fúÅ¢ÿàÓš²a­ÿÏàA þ†ÐGö-€Iÿß„þ<êžÂ¦w6U0ó½Fí»…™×ßrÖ-cú¹¹ñ†= à¬ÿ¯>ÎoWÁöàÐ,òõ¦:ϺºÅ¢ÿÎàÔ\ER'µ©<ýþs|Òè£ëˆE{Gý51üvd–—p_{÷ Óžæ€çwÉýëß\¿Œkôü#%ƒ×®?g}ÁC³N4X&ÀwÏøîàc~ì0€G3‚ñã¦ßÿ¿÷Ýü™›MotGǯ\\r1]@ó $¤M K1i#ƒÝ†²u,–*ý†s7¼dõ^|˜`kMsbZ¸ÆJ{ªN«*C003"z'‘"Ÿ] @àÿ^\Wšj£×¾¶öü=`b}ŒûÿÃÌ|½ûða‚-H1)`¹éÓndzãÍ€)ß𻸭+r‚—aà‚Ö=áÉKç—½ñ•‹üF[®û´kúÌ÷=ñÅ'zì܇œX¶ûÿ©mØôˆpÀDÉ{½Rr²©ˆG*ÊÑ›ÔêTªv•MŒßkGŸìY€DW°Øÿ×&†ßžŠ®§›¥ûâÛeÛàò/ŸàVCÙÉŒ°oœõÿó³0À%Ï_ò·þ)S¶Íþ¿Æ5¹èña= ‹îep1ý´ë™^u:\´ù"ƒ7i9~™–ɤÿÿàK—¼dwª#ìê¬Y<Ï3®‡4‚¥õI<õ¥§8*G{%YB4ÕœOÏ\#E‘\G ɤ˜4cå¯V:zK$*Gÿ$KMRL‚K›»ÏõËÃVæÿ÷I²´>óÚ»‚uZ2­¢½;2ugè䊷ƒýÐÑ¢ö=áïÈ1¾+—ë¶ßmN4 ,>íZ]Eãël἟ŸWÍàu&ÍKº>ÚÝÔÞø×7`g×à¹LÂn†ò5‚ëÓ¾fW¯$~ýŒ÷èW¿¹±îïµuE¶‘•M….ïxÂ6’Ž ÚñS§ úÿêãæ:û}û,ËÖ&Æïß¾ìíÍ©cH9à-‰8mÁ\Ìõw@iÑÿƒ‚oÑK`ŒDEÁ Љ!% û@½@ÔFÿ?—I¡3zæ—ë]SÓñË>.¤Gñd¸~NPnŠÌ]m¡ðŠæZg›XA*¬ªÌÊ´“ˆ<º£+öƒÓë+Çås·Fpfà¨ÿ¯>ÎoSæ&fNù+†²“yÙC›:®ìHÿP4ñ5ªSöW,®äÈdï -'æ›@Èzw“/ÚÙ=ÑX_¬þ6sÖ2²Í›áJ® 8óT•jðÄ¥¥3*ÇoU^Ó“M—8ËV7/»ÿñ—LÓ@ŠüD‘L7ˆÂM ž¼8à¬ò[CLr£è2&n c™U€ð°×åyÝ_‡¬§öîÈE‚"DÜJÙ]®~:©¸>Ùp†K¼p¢‡8&€Yÿ߈‰&à€™¬E?Xäeð†,+b¿ÿsßµ}Y¯’7ÕW$_*¹¥½3¼ÀÖP!}ü;Éôö=Ç›EAÜðůè¨L|.»#gpÌ#B,Ʊ-NõpÛxëÒ·"KžZrÀ3Ó«Ng<™~X @TŽÞ`†3©ÛÿWãÍC72|‡š”ç- õ’ù•m;ö„€IFDZ"D`ˆD¤ 7 –cäŽB\%¬À­ö=U9Æ CfŽ(’àô_˜µqVÀ ™•ªköã™™DëQ<ó‰Óç—ÿ¦·7-¦Av† ø gä(¹»zÃc5xäˆ GðSކÄó„¾pwYÝ$Í×þ“26‚Q¤¾¦ƒþÿ#k#ãö¦ßŸÝîñA#0}ž™%e=¬(Ë[[¾²téô‚u$8éÿ§¶ß wyôPyå 1­‚í÷ÿ0¸ÅIù Äka 5ôÁœ"Å}ËT¤ü¨9­|LtqÖĤnÀQÿ?E޾H¨LëÚe÷”1xcë“XË-|ܨ(¢“ÕK•e½6VXZëëkÛép‘пxN¹£ðw¹ 7 ÛýÿÔö¾H¯GçÚרשT«xÀOÍ)Š´£ëÄ›¥Šôº¨cùDCCE?P1¢íÍ&•£û%Y’2gÿF”‘Ô/¦Ä ³ …•ÔOM†#C´è~¯Ã²$ñ4·ð6½ue­ ìgæ/&²¦ $¼u? Æ?óQ‚#È·ËŠÂJof¥ÚÁÑ¡÷Ó@ëè3¾éèb£èp«ÙA ¤­"æ5Mò™¨6…„#$2?DRiL‰Å¿D¸Žær½nç6é’ù¿øVý !§×´;‚‘ÓÌš˜7(¬ÐþÁÞ¤ògär=Û<©µ#Dq[pð†dº9P~SSÀûõ¦@ù= ,"tã šaW0Z~±«g°Æé5 ÌklÄŽŽœ›DCäì>'ÀÞpÈMë–ød­Ð± p·°fÏ“¹Ÿ—¶·~²©©f±KôɇkQ ]Ð$EÆz¨æ€™/lÛ>$¹F¶sÉ”*RæÇŠ\o¥¦_#sŠq%À¾Á^7€»ä,ágÜÂÉoñ ½;²˜e¥Œ]ävNCb(—y/ðŸ9ÊÂ_{†çÊrìS Ó'AÂ?ÆùÇg·ÿLà~€÷E$^½¼¡ÂVPipX€à/ᢠYbÒÈÀñ~ 4W•u1®x}ÿË^Wæx™kÔ̼ŒáÇ4:4 ðÃ;ƒá¿ã5Ä×Ô¯™YÅÖ­«fÉW¸Ä#¤Ä{fäZ»¼¡ÌVåoÝ ±r¦Rr¥Åå*- ÷!jbíî²ù‰ë¨À‰üªã×qKv<Ÿ¶àà½^£u€£þNc Üñ{;º¿ ü8%Čۛ뽶>ÝÚz°L¬ðþ“@˜ ñ0rÄC@)*ön]8³0E;ÜÂaãöÁÃvÐŒÜðÜí0,¿EjÌ¥òàôzÏVðrf¾ ØoΊ*ʦ‘@ïÉ„N…y(™Ï;È1×Þh8\ì϶ç¹~ñ(„ÜÛï\°Š[X3˜Ó®žÁFÕ™ù$Ðê¿çP>„0ˆð’ÂØOàÌÂYž²sÆúI!$šÔ¶Ýáùé P„žæúòWó¡§réyðrÀ³ÜÂÔÛ©È|!€!¿ø6Ç€eYsÊaÈò%uÞ+šÞ»ÊpøoI‘ŸE÷µyaù\‡JÉóNÞô4@>,Àx `µÑ ñ–ÒXôÁäø~ë_Oþ´HTW!oh x¯€ÊÏ Q9š(Sö¿˜¸VÁáØ ZG—O¨¬á¾ßɉ(jhÀG.ŒNÅ& €å8}™ø¸òÓq*àjnaÝÀÌÃr%À^DÉ.žçÓeÓväD€ÄØû{yÒÅ Cn0=êcØB®»fànná±"ÛG§ ºl99œ*¸Ž[ø#·Zg,ë@P`ÌôÌ}÷¾¼ËÛŒ|½rl‚_S 'þóñ+ĵ¸)à5èÉ £H(Æ“çüö ÏJ†CÄ š´ð±y|¾ÎŒmæƒ]°¹®~riMù7æÿ›éâMÅ|Ìöj~ìT!·Y¼\Ï7Åÿæpr`Mé¹GIEND®B`‚veusz-1.15/windows/icons/error_linehorzbar.svg0000644002344000001440000000662411734662204021665 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz_16.png0000644002344000001440000000151011734662204017571 0ustar jssusers00000000000000‰PNG  IHDRóÿasBIT|dˆ pHYs„„—|¦tEXtSoftwarewww.inkscape.org›î<ÅIDAT8m‘Mh\U†Ÿsî9·3;ÕÔ2щÂÜË !’IŠ‹YT(ÝXhÁŸEqaqS¥q£EìB»©ˆàƽ 5KwB E0NM'Hb’+¦ÓĶ&¤3cæçÜ{‹Î ÍàÃ÷Áy߇ïýŽpÝñ]P#BÈS¿-ÿò+ÐT©è†ŽçBÞ•!_ª×ç6¤ÖAèº>ZàPO«ú¦ßÁí÷Zû_kh­ýÙz}n@i„BÈ—A€T©è=Úóë»GÊÅ£õ èär³¯»®ÿ*¨M!ä}¨r]?!œÁBÀâîP¯ÕH—Ë?X­ýkB8€óa­öÚþ µ‚B9,…íWJ¾w à.5´ö/ áàü|ãÆôOOî¥ÁœP­Ô|_ðÎ)}Lëà²Ò€ú P 3ðû€çõؘ˜xœ{y£õÂÔÔèl6ËáL†//~üèþðϨsçŽlzQ6‹j5;£O>Ftpõ*oe2lg³|~wÿ©a?Xki›öF£Ó°µ½w­µbi­ñ]uµ™Zß]_Zy¸b«÷«o[k±ÖÊ^\°°½šÄÄéüØÑ²mÑýè^|ó‚Ù2%“˜[ÓÞ›ÕÛ·÷žÆSÓÓEïæ [ ¡I )™Í³YV*·w²òwåJ”D±‰ÍÌùÓçïôôÌ@e»šØv¼üÌ£3ßÏœ5‰8ß^;1@u½yRGvy|<»ó¿DI„‡òù¯òÇMbÞ‹’èaîpîÓDf ŠÛkÉȱv5üwr2ÈTVwVC-ݼIºßòÙôØg×OWŽÛnke1l7™ÎM²µü'XÙ…&é>œæ¯ŸYüDfêÅô=ÝìFNL Ði·d€°Ö>n®ˆ¿¡ž‘BŠ÷'/raâÒÚоš@¨÷k©èuÿ¹I!E&eIEND®B`‚veusz-1.15/windows/icons/kde-edit-undo.svg0000644002344000001440000000764311734662204020570 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:40:13 2004)
  • </Agent> </publisher> <creator id="creator30"> <Agent about="" id="Agent31"> <title id="title32">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/veusz-view-fullscreen.svg0000644002344000001440000002242311734662204022414 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders veusz-1.15/windows/icons/button_ellipse.svg0000644002344000001440000000515211734662204021160 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_plotmarkerline.png0000644002344000001440000000052611734662204023065 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ 1ÐÚ½štEXtCommentCreated with The GIMPïd%nºIDAT8Ëí’± ƒ@ Eßå&`€0H ÆÈTÔ¡ÊHG BbO‰y"‘ œ¢wGP긳lëÿgþñ-ÔOÆq Œ_,Ër;lm3~áV×q¹Ö¹¨2 Ý4£Íà÷pJØ6[§U‘)÷* »ð)@7ÍɴΡͰ‹ GE8ç9"bDd+DX´ èžraå_8U­E­MÎW¨Š €þrýüIÿxÆN>Òkž9M>Ò/ñλJz L0IEND®B`‚veusz-1.15/windows/icons/error_boxfill.svg0000644002344000001440000000572311734662204021004 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-go-up.svg0000644002344000001440000001055011734662204017716 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent id="Agent18" about=""> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-view-refresh.svg0000644002344000001440000001266711734662204021310 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004) Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-edit.svg0000644002344000001440000002201411734662204017612 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004)
  • </Agent> </publisher> <creator id="creator34"> <Agent id="Agent35" about=""> <title id="title36">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/settings_axisline.png0000644002344000001440000000044611734662204021652 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ Ã˜itEXtCommentCreated with The GIMPïd%nŠIDAT8Ëc`ò€Ƙ3gÇ÷ï‰ÒTRZŠ]¢§»Cì?33^5L¸lùß1®ù?33ÃÿŽ™XÕ1!{Åoé ŒÿBØÿ20V¤ã7 %%»SZ§¡¸ §8CÙæê,â½€+ Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent id="Agent18" about=""> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/settings_subcontourline.svg0000644002344000001440000000625411734662204023127 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/veusz-edit-cut.svg0000644002344000001440000001262011734662204021016 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright Jeremy Sanders Released under the GPL veusz-1.15/windows/icons/kde-dataset-new-veuszedit.svg0000644002344000001440000003032511734662204023125 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator26"> <Agent about="" id="Agent27"> <title id="title28">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_bardiamond.svg0000644002344000001440000000570411734662204021444 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz_32.png0000644002344000001440000000343111734662204017573 0ustar jssusers00000000000000‰PNG  IHDR szzôsBIT|dˆ pHYs ŒP„tEXtSoftwarewww.inkscape.org›î<–IDATX…¥—ml[ÕÇÿçßk×¾NîmÝÔTq§o*Wí<•UÚXТjã˦Y5ñ­|êÚi£Ú€RÑŠ½tk5`Ù†XµUC€”SLhBê$`[…Wj¼–$®“b7iÓ´öµSÛ÷œ³¾×ܸv´#]Å>ç9ÿç÷¼Üã¢(Ñ+Ñ€ W«cÇ •±BfL+9ŸaÆ4|ž }­ i€®#ÄÕ'Ç,ë­']¥z ]ƒ~†@)•±|ºRkÖŒiµ•0f< Ž.2M=æµñ1fdºÃ1!4êYxõØ­üàÍ“º¾w3¥ú÷ Op?šŸÿ­µ€R#ë1°ÀfL³\nå…Kåué3oÎ&‰EŒÏDñ”öôÜÜO_iÞïcLϺΠ¡HO› ´[z‚ù-=‰Esk×ý¥únOô6@´ÚO3²Œ`Ì¥3‚‘Èãk½pG2 %=Y2ÇÆ®…Ûuu=`L?áÕ¤TÿÍììÁt+ûV%@¢®6Äãà]wW| ¥ cÆ£éõD?Ãí€)cÆ%J A©—š1#ê¬7—@¼öbø¤Ù¯w'ÒÖ‡ÝÝ£3~\×ÐÝŒœž~°ÐÀwåʾÚúõ¯|R¯}£Ü>hd ™GpÕ|!pøpÇu¯@•ŠéD AOôïäÏíœu ÔíÝ­ÛmPÊåÖšhøBGz{SƒŒé OícúþÑï &Ç‹‘¶¨“ëY€Þëy\€FÌþàÏÏ|\Ô[‰Äb—cÆsnÕ£'ÏOLÜq9}!\ÁÆ%ŒIÏiå=Œ¼=Àwnϵ¡ÔØGÝæîè5Bèβly€}@©‘õD Ñ¬ÝÆôd©k[ohÖ4±F×;Ž(  ªõÇïÇc££¸ÞnïmŸF”¨*´;±æ/µß(¸¸ûlÆúçîÝÚÓª Ýðûñªâ$|4aÝV´3Ñ(n- +Ù@ .àŠ.EnÆÂ£££ˆ a¯¢Î~©(øÞöíÀ-NM¶£ÑÁ¶:DJ xùeUš+¦(x`Í+lÆ´?yíá¼r7n€¨*ÞSUì¢L@H!ÅI•©/Þ204„¬ßÓ­#ˆèI_¬×1™„¢èÖýR`Àfµgã}ÆÍšzõ¡Š»øwßRü$ª/™¸ö44—­Ha.ؼÆÁ%ÆuˆÇQKeÈw ‘›:…q,=›sÉ!¤u.ù¡]Ý»®ºzçÏC]Ø –¼?4rV.ëDâŠF0þÓ0¤l¿RÆúQyûâôQ!ÅûsBŠßËøFÌØªÕÔÒ~߸˜ÄÆðë+(æ²\:éBŠhŸokÃpk_à2œ:wj“â çõ=û÷}qOO–NÙdÕe Ü~] (m»i€zT=}F}mj ¢míeUí…¼•ÿ5—\¢áüÅ#ƒGÞ€m½¡”«gnêœ\ÎùmnDŽpGA»4 3Lµ Ì=zŸmW¾î‚rÁ‹BŠƒ+q´<€•›â‚K!qA.iã‘/# Óì*íyuÏa!ŽYR<õÆž7f¼‚™ üŸú¦HÌ%ÝÝXX€º†¿1\ÉY¹\®˜ƒûLÆ¿b¹bî‘éât¿gýÂ-'þ’Ê”þ:â9²ËÄXmGÞž¯–ïý\pœd…ëÝWëb`l lÞØ-¤x¬ÑtõúPç¦yÈÚ›ðÜŠ¹Ä •x¦6ü{òBùN¦ˆo³þÒ]wuί€Kþ7ÅÊÇgþ—BЧëGøüdÁ[\[tÍÞÓ®x >œª.HÛ—–òVè\>y+Ÿõ~ÿÄ7!GÉ=öx¦Ë¥lýL•´awqd,‘¨gd{T¿à@ûÿ¨hÓ÷E7+óë<×dótú;Öês™â _E<5÷©ö¼»Ê,|ióŽÒ f î\r¼9;Q:Ýìʪ××0=S~e3ÑG@¶ x/„Èš„`Ä®•dEõX=³ø†ÝØãþ9JîoeèŒûä!ù·dЦ] äó‘…¥n;+‹ˆ€` ÛQyH~óÿqÖj´ªËû S5 …ùýBòÊðW_µRë÷ËèÙ n(: MÏM3¦Uà{Œ ¬y”rIEND®B`‚veusz-1.15/windows/icons/error_curve.svg0000644002344000001440000000640611734662204020470 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_ternary.svg0000644002344000001440000000554311734662204021213 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-document-print.svg0000644002344000001440000001362111734662204021641 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:39:51 2004)
  • </Agent> </publisher> <creator id="creator23"> <Agent about="" id="Agent24"> <title id="title25">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_grid.svg0000644002344000001440000000654711734662204020461 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-go-previous.svg0000644002344000001440000001151511734662204021150 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator17"> <Agent id="Agent18" about=""> <title id="title19">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_label.svg0000644002344000001440000001352011734662204020600 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_axisminorticks.png0000644002344000001440000000047611734662204023110 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ qÍ"=tEXtCommentCreated with The GIMPïd%n¢IDAT8ËÕR; Ã0 }ŠÎàsù†@ç t| ,½€Q‡R“¤Vê1Õô,=ýžü½ÑÄñ\–ª¤kוqüò)ó!§±ºè0çde†s‘׬WØìÖ· ”Þ8%Pß!”G¹O›©Ì¦ÊëηKý –Ö$YaÑLÔ !"ªÌõ¿D¤–ÅCrÎiÍ!yï©Øëðë}{Í>tà×EhIEND®B`‚veusz-1.15/windows/icons/kde-edit-paste.svg0000644002344000001440000001514311734662204020731 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator28"> <Agent id="Agent29" about=""> <title id="title30">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_barcurve.svg0000644002344000001440000000630611734662204021154 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_ploterrorline.png0000644002344000001440000000064311734662204022735 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ 6˜öïDtEXtCommentCreated with The GIMPïd%nIDAT8ËÝ’1nÂ@EŸcÑPs€ÐrƒDJÁ)>@v‰#å‘ìírî‰27 %m${›4MŠÈ+,Œ!J—‘¶Ù™ÿçÏ̇r©àæ7lÖÚ“¿à8©”ò c Zk1Æ®,k t>?U ”"Ï2vÎ €sNzqL÷^1;Díó,ÃSÍÞøª&#ˆûု÷•¤iJžçtï€Îv+Kf‡ˆàù©NP)èű'™Žú²Ø¬µn¿Âí`@Et¶ëZÑ9pÀZËÇnG’$ì‡cOÒn\âbSü8èå@$ ‘0¼ÞHÓQ€åÃÄïhùöYkrö eQ\¥ôØHŽoêFm[¨ŒÛ›IEND®B`‚veusz-1.15/windows/icons/kde-zoom-width-veuszedit.svg0000644002344000001440000001433211734662204023012 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/settings_border.png0000644002344000001440000000043711734662204021313 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ;kgGtEXtCommentCreated with The GIMPïd%nƒIDAT8Ëc`ò€Ƙ3gÇ÷ïÿ£©¤´”«DOw7†ÿ™™ñªaÂeËÿŽ™pÍÿ™™þwÌÄªŽ Ù (~«Hg`üûÂþû—±"¿)))ØÒ: ÅU8 ÀÊÈ6Wgï\áË%8c=FˆŽBáA›„„„øƒsì image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_vectorfield.svg0000644002344000001440000001140311734662204022025 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_bar.svg0000644002344000001440000000737711734662204020302 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz-pick-data.svg0000644002344000001440000000510111734662204021131 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/button_function.svg0000644002344000001440000000561311734662204021352 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_nonorthfunc.svg0000644002344000001440000000561311734662204022070 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz_64.png0000644002344000001440000000734211734662204017605 0ustar jssusers00000000000000‰PNG  IHDR@@ªiqÞsBIT|dˆ pHYsÄ,OætEXtSoftwarewww.inkscape.org›î<_IDATxœÕ[kp×uþÎÅ’Ø%Ò¢d=ø!¤TŠ¥A8:tÝ4ã$“¨êÄM)n&²5IýH+n¬Úžø%[Vë4uó’cËéT:i:q&–j§±Óˆ®J´ø)‹¢LQ¢L߿谅H"¢- ‚fö¨ŒˆêœµÏ" ·å33ç¸]÷ÅÅ»Vn÷M‚H{ ™.³î@b€v…Ã/Xu@B Y8mVæAÌÌœWœUîÆ”ƒS'‡c޶ •p…"HÝ P m§•ý@ÿ45õ£ÿΧG!Ò °dPWäðª…¼ÓøCÓ!%•) •÷¯jÛpeœ¯®þ󫈴s7Óe"úz!] ‘æóÅF<6òYœj½zfìé™Z±mRÅ838§¢€<$؆ªB¨ßèê‘»wrò@Á44¤€í ‚LX!+¶!ít{{õåb@³4³ZÆç*÷Š  ¦f÷f"íK vêNg‘qÀwŠ1@!ÒÎHä°p"Àn(¬²va6»ª«  R¤äIYh×Å‹÷J8üB²ºúó£Dh´OòØÈ/•ø|ˆ•ãÅÜ»jÕ}Ÿ&ÒþÐfœÒ?‡&&öüW±í B i R  D„P!„ÚPS³[XÈ/™€b±zõƒšê£B,Ø–m« !Ô°êÝ¥è@¤†ŒNg”!ݘ "µŒH­µwZœËA×àTS)FYH»‡Hk4;®Û)„"í¾ññ¯M:”ŒòMSŒšäóFÀÉáØ™Jm(ƒckp(úê 'Þz¯¯&\аvíÓ"õ‹2Ýhï)€–ª[Ò)`縩úµI>/2 —{‹L¥\Ñ1‰É ¦TA¤>Z ÷w}f®TÝ™È ³l¼)à÷¯‰uDâ ;½s£×/ÕÀººçoBý¤}ýÔ±RuY)°¨b¨à x¹ìÒóÏl08^ TXÞÓ ”· óùúú#JzÚ3Ú‘3ïGÜYÈ;¿ã¢Z$Ïè !4…|A:=žY  —édhúãr(zsWÊr.’º‹Hm]˜‘4óà "ußÙ³;W¢ßóP …ê0ö¼) <òEÍyÀ2J d €¤ñÂúõ/¯&Òþ&Ïó êhÿR 0 …t,8mn”êë_££Ÿ0–«9Ð;[+œš“×t4»VÈ€6Ÿë˜Õy!Ô‡0¿P“[¦g:èöáá@ÒJ¾XÌ7B¨¡ô\š]Ò \Í\ 䜕U©"AŸ NÖ/ƨ Þ¸†HÛ‘[œ-ÌùBhG†‡¿ÌˆÐ1Cg–c å4€)‘] äŽj#@²iõÈ­òÂëí¥ôBȾާi_Ñe‚CÑÍ+™›às¿ˆ×L)`žÍ ÃÀXgçÐÑì:ÒÕuùWåW9ßßÞ¤½^Š1é¶ÕÏôÞ<‹è¡ÁÁº·t“ëÍžž©sXÄâË<öÅÐÂ1yLò–ƒ` °b À”jŒÏ7V%„úí<Õ( Ñ#&ÑT àY0FÀ ¡7\Äcñ²> ¡~ 5zû6éʒ *;Ì µ&ššB£­·¨q¥ ظqz‘z‡M±£·ÿ³Ó§•_®6ÓÈ)„h}v£YÓŽ'ŸüR@¤>‰ô 4@@Q¾Ì ÝËÕžŽ,2ÅP§õ3Ðú§©¯¯bÑ‹}}ÝI‡zãÅÑãG:;;çàúëqSCƒøcEˆ²×æ…=öù¯G†Ç,×ÞTÑŒü¥TR¯LVýgEäíÈV$ôü–™¿ý+K¢ñh(Oþë‘á±’õ§_NàÑÿxÍóÅ”)$KÉà]G¶¹â[jóÁ’€ýÙaç‹;c ®Ì3g{ì”Þü“›k™ù^4›4zæ¥[^:##PÓ %ÖøŸ¾éÚ©óV5̬“HUû›\'—ìy¶«º™™ Ý¶pÉC@$y˜™]Vd¢hR²ÜÓ34`Níè5Àõ´­‘eØRS½;ÅHø €wŸSþƒ™-«Ák¿wí˜ù»ÞÏèùFð¶à¥ž¡i@Ÿ£¶+tù¹€wÅ”•NWpeÆçOô‡Ïoi©úLÏ ËK@<Ê“ÿú ˜5z·}§M0óSƇ(‹üC²ü.Lóì€*Ê6rJTØ9@”1!Ï’·Cà‹Mî ÐÓÙ(A$Ä KêêhÑJ^¬-öEÌ Î" ÜÊà­FÒLÀÌüå‰;'$dvŒªççôõÄ@ì->×ýBÊ•‚áRQ(8]ªóù 0E€)ÿ!!Õ‰™óe@zÉjå£+W0óƔɑgùÃÙ=³%¿1Ö±¥¹ò„~ÜÛ‹r¡@ÀA„9ý¢V È ×:òG€}þƒ™q&2èZÒ÷Ç#÷3x•9e ˜pñDw(ÒÂ`Þâ­(ÅhhmEp¿ÚÝ)—å³ïÙä¾Tª ?ÃÌÌ &󓛎3áA'Ð>jðWÚú&ïåùðíŒýž ÐÈÉáØÎ¶ •ÁÅ8°Ô7CVûñ=ñxR&Çæä$K«ŹØ}ÀŽ<íü]Ö™ê0à\>ç{{'\yô.…Þ»x>ö–“öÑŸè, çvÞË)èélb[;ZªøýÓ§#+ó ¦œÚ㽃Ñ'[}®Sv÷ô Ly%—ÇËImæØX °nº€=ó(D@ÀuvG"ƒ€G èø1ïåWz¦¼LʺŽÁÓ'£ç]p½¶É&w»£› x ÀóƒácÉðÀî@ wW)Š‹æRO2E5mݳVúì`›ä}ýæä 7€†<·DÜíÍÕ!N·×L@‡¾<7± ïŸëX¾ °ÁáPÿ`å|0ˆJ9‡ÍDH9 ÉòéÍ|ÊB1°ÜÏ{ù¼þƒÀ ü†€k„ƒvmò¹òܤŒ@ðÃHñ¨LIË}¶~?b@åó'†"ï¯HqK‰×»’¼ àI㉤HÞð®˜:ÕëpÌÉ‚;»ý¾Š‹mìßžu¿~ß}¥Õ@Ìö¥4í£5Þ.UiÄ{ùW‹”}×w à½<,êÅÃ?ÿp(<@©»/bþz¶üŸ ˜]^!mÅ*ôU·~ï_>úÛ•ÁÁhÞù½HH¤ÿ‹( ŸB¿óÞã÷¹²r¾~‹¡‚âÚµ×g½úr•¹ñàûž¹ÀíE»hµøÄlÎ{=kðý_]p¬näxÍIEND®B`‚veusz-1.15/windows/icons/settings_stylesheet.png0000644002344000001440000000046311734662204022226 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖA ÉËtEXtCommentCreated with The GIMPïd%n—IDAT8ËÍRÁ Ã0 ¼«²L+] ŽH5ÝÁÓ”#ÔONÜÚ¦’{é„u§³ l &µ­x†ÓÑÌëÛƒ€.=p¦¢Ûóž÷ºu£†7Põ½AŸ ¾¹Ù8{,˜ˆóv^Î@ªU×#¸ë‡[Ø:\ŒÛ8ÃD~ ß‡¿DU ÆX­ÿú„PâûÄ P(3t@’L‹IEND®B`‚veusz-1.15/windows/icons/kde-edit-rename.svg0000644002344000001440000001571511734662204021071 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator22"> <Agent about="" id="Agent23"> <title id="title24">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-zoom-veuszedit.svg0000644002344000001440000001160611734662204021676 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-edit-redo.svg0000644002344000001440000000764411734662204020555 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:40:13 2004)
  • </Agent> </publisher> <creator id="creator30"> <Agent id="Agent31" about=""> <title id="title32">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_fillvert.svg0000644002344000001440000000614411734662204021172 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_imagefile.svg0000644002344000001440000000723211734662204021446 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/button_contour.svg0000644002344000001440000001015511734662204021213 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-edit-veuszedit.svg0000644002344000001440000002203011734662204021630 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:31:40 2004)
  • </Agent> </publisher> <creator id="creator34"> <Agent id="Agent35" about=""> <title id="title36">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-zoom-in.svg0000644002344000001440000001506711734662204020267 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-document-save-as.svg0000644002344000001440000002217711734662204022052 0ustar jssusers00000000000000 image/svg+xml image/svg+xml veusz-1.15/windows/icons/settings_plotmarkerfill.png0000644002344000001440000000053611734662204023065 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ ¤.ntEXtCommentCreated with The GIMPïd%nÂIDAT8ËíR± Â0<Ç Àd Æ ¸JW,É®Eòa ¤ŒZ$gÐS ˆ‘í$¢æ¤/lÿýß½øcDþÉZd0ÿQ%k¥™Oª¤Œ÷ÓJyäï0½Õ­—óB¶$ûò°!œÊà>ó-T²¢¹‰°Ç€êvÎB( ¤À.ÏÑug–èÏf XkqEÓl 9jÁô“O"âÄy’“…ÓÞšýñÓ¹¹Ü"_Y¤Ñ¹UJ“‹ô ž¤–Zñ FRFIEND®B`‚veusz-1.15/windows/icons/settings_main.png0000644002344000001440000000043311734662204020756 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ  &!Æj¹f¨IDAT8Ëc`hÀˆÄþOŽ^d‘ŒÜ Déœ1ÙÎf‚kÎÈ@‘À§9##«þrÔF ã tÛ±¹¯+ÐmÇp>W`³«lllŒ8½0c#Aü­1\Ëv¬|ûö!À\š‘Û±‚y›¶¬YÌ‘‘ñš:ÿCÙÄüìt†ysç 'oÒ’xtdƒ³½ù9Œ•…‰hµ‘Î9†EM‹=IEND®B`‚veusz-1.15/windows/icons/kde-dataset2d-new-veuszedit.svg0000644002344000001440000003560011734662204023354 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator26"> <Agent about="" id="Agent27"> <title id="title28">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/downarrow.png0000644002344000001440000000034211734662204020133 0ustar jssusers00000000000000‰PNG  IHDR Vu\çbKGDÿÿÿ ½§“ pHYs  šœtIMEÖ $½†øtEXtCommentCreated with The GIMPïd%nFIDAT(Ïcd```hnnþÏ@¨­­e$Z1Ì`&ÉXØ„œÆˆn#!ÅØœÄˆO1.?0â3€…(%’50’š4Ð0bæ_SIEND®B`‚veusz-1.15/windows/icons/kde-zoom-out.svg0000644002344000001440000001343711734662204020467 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/veusz-capture-data.svg0000644002344000001440000001164211734662204021655 0ustar jssusers00000000000000 image/svg+xml Veusz capture data icon Jeremy Sanders Released under GPL veusz-1.15/windows/icons/button_xy.svg0000644002344000001440000001574111734662204020170 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_contourfill.svg0000644002344000001440000000375111734662204022413 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/error_diamond.svg0000644002344000001440000000572011734662204020755 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/error_linevert.svg0000644002344000001440000000614111734662204021170 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_axislabel.png0000644002344000001440000000054011734662204021775 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ $»îßtEXtCommentCreated with The GIMPïd%nÄIDAT8ËíÒ1NBAÆñŸy¾Î‚ÄPÐK ‘‚;ˆ‘Jj`ÁYÌ;'Àì4j¥‰±§# 4c²^¡ àkvv÷?;óM–½¶ª\$û> ¼ÿá:Á®¨Ž'd¸Â0â"añ,üj‚Úèá?Xà#˜[L1(³ÑŒ„óäì$ÖJØ9N’ø(¼½a3s©¢…/Ü­«œáÝðù‚³¸;Å'^‘—µ^$ᣤËïx|cå¸Áø¿c†GÔv÷û.Ú)Êûø¡õIEND®B`‚veusz-1.15/windows/icons/settings_axisgridlines.png0000644002344000001440000000047011734662204022700 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ úß&.tEXtCommentCreated with The GIMPïd%nœIDAT8ËÍS] Â0 þ²œ¡×2°3¼€/ óYr‰\§;L}fvjg}ð{Ji¿Ÿø%Ì,oé k½”æBUBÀ”ÒÇ¥Ã0ÔKÈÌ‹þ2Žuòùö¨™ý*TfVäÀK1F(]?]ÛR ãþí¼ó¦”ÈE„f?23v}OÏþ)x image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-zoom-page-veuszedit.svg0000644002344000001440000001633711734662204022616 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/kde-document-save.svg0000644002344000001440000001240511734662204021442 0ustar jssusers00000000000000 image/svg+xml image/svg+xml veusz-1.15/windows/icons/kde-vzdata-import.svg0000644002344000001440000002063711734662204021477 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator22"> <Agent about="" id="Agent23"> <title id="title24">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_linehorz.svg0000644002344000001440000000614111734662204021172 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/veusz-zoom-graph.svg0000644002344000001440000001036111734662204021363 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_whisker.svg0000644002344000001440000000417311734662204021526 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/link.png0000644002344000001440000000035411734662204017051 0ustar jssusers00000000000000‰PNG  IHDR Vu\çbKGDÿÿÿ ½§“¡IDATxÚ­‘1 Ã0 E¹Hn`²§k¶îÚƒæ;tìrÄC Î¢Nê´…þIƒ__*‰È0U5ÀDäÊA]5}ß/)%T•RÊ iÈrÎæ½·‚ÕNM Zh?ê-°ïûWð8ϳ©ª¥”LUMDìºvX·m›œsäœqÎLÀzvÒàõÚO†RÊc¬{¸Có­1Æÿ÷ðš»t˨/ §IEND®B`‚veusz-1.15/windows/icons/settings_contourline.svg0000644002344000001440000000401411734662204022405 0ustar jssusers00000000000000 image/svg+xml veusz-1.15/windows/icons/kde-zoom-original.svg0000644002344000001440000001337611734662204021466 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator24"> <Agent id="Agent25" about=""> <title id="title26">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_line.svg0000644002344000001440000000505411734662204020453 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_plotline.png0000644002344000001440000000050211734662204021655 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ .9ÜᮯtEXtCommentCreated with The GIMPïd%n¦IDAT8Ëc`ØÀ\sæÌÁcD–LIIA6.×ÓÝ¢©¤´» h Q\2íÐ{†ÿ31 cÂãFdC²ìª³01aóß´Cï±Âø÷/Äi3q{I3QˈÌY¹r%†‚ððp¬‹5Š`„ì 4ýÇ›½ð¿c&$ä™™þ33—‚£n33Š+У%!}xÿž(‹p&$róIuŠS•IEND®B`‚veusz-1.15/windows/icons/error_barends.svg0000644002344000001440000000667511734662204020772 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/kde-edit-copy.svg0000644002344000001440000001464411734662204020574 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator19"> <Agent about="" id="Agent20"> <title id="title21">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/error_linevertbar.svg0000644002344000001440000000641511734662204021661 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/icons/settings_axisticklabels.png0000644002344000001440000000040411734662204023032 0ustar jssusers00000000000000‰PNG  IHDRóÿabKGDÿÿÿ ½§“ pHYs  šœtIMEÖ  —Zª‰tEXtCommentCreated with The GIMPïd%nhIDAT8ËíÐ!ƒPEÑ#±MH¬ ¢Š… »Åêvóeûk@Χm*¹j&or“yœü• RõxaÁ Fä KèpÅ ª‚`熩tP´x¢ùEpÇõQ‘9˜çmϼyò-o7½OpÿlkIEND®B`‚veusz-1.15/windows/icons/kde-document-import.svg0000644002344000001440000001723011734662204022017 0ustar jssusers00000000000000 Part of the Flat Icon Collection (Thu Aug 26 14:38:01 2004)
  • </Agent> </publisher> <creator id="creator22"> <Agent about="" id="Agent23"> <title id="title24">Danny Allen Danny Allen image/svg+xml en image/svg+xml veusz-1.15/windows/icons/button_graph.svg0000644002344000001440000001115111734662204020620 0ustar jssusers00000000000000 image/svg+xml Jeremy Sanders Copyright (C) Jeremy Sanders veusz-1.15/windows/__init__.py0000644002344000001440000000165711734662204016413 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Veusz windows module.""" veusz-1.15/windows/treeeditwindow.py0000644002344000001440000014070111734662204017703 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Window to edit the document using a tree, widget properties and formatting properties.""" import veusz.qtall as qt4 import veusz.widgets as widgets import veusz.utils as utils import veusz.document as document import veusz.setting as setting from widgettree import WidgetTreeModel, WidgetTreeView class SettingsProxy(object): """Object to handle communication between widget/settings or sets of widgets/settings.""" def childProxyList(self): """Return a list settings and setting variables proxified.""" def settingsProxyList(self): """Return list of SettingsProxy objects for sub Settings.""" def settingList(self): """Return list of Setting objects.""" def actionsList(self): """Return list of Action objects.""" def onSettingChanged(self, control, setting, val): """Called when a setting has been modified.""" def onAction(self, action, console): """Called if action pressed. Console window is given.""" def name(self): """Return name of Settings.""" def pixmap(self): """Return pixmap for Settings.""" def usertext(self): """Return text for user.""" def setnsmode(self): """Return setnsmode of Settings.""" def multivalued(self, name): """Is setting with name multivalued?""" return False def resetToDefault(self, name): """Reset setting to default.""" class SettingsProxySingle(SettingsProxy): """A proxy wrapping settings for a single widget.""" def __init__(self, document, settings, actions=None): """Initialise settings proxy. settings is the widget settings, actions is its actions.""" self.document = document self.settings = settings self.actions = actions def childProxyList(self): """Return a list settings and setting variables proxified.""" retn = [] s = self.settings for n in s.getNames(): o = s.get(n) if isinstance(o, setting.Settings): retn.append( SettingsProxySingle(self.document, o) ) else: retn.append(o) return retn def settingsProxyList(self): """Return list of SettingsProxy objects.""" return [ SettingsProxySingle(self.document, s) for s in self.settings.getSettingsList() ] def settingList(self): """Return list of Setting objects.""" return self.settings.getSettingList() def actionsList(self): """Return list of actions.""" return self.actions def onSettingChanged(self, control, setting, val): """Change setting in document.""" if setting.val != val: self.document.applyOperation( document.OperationSettingSet(setting, val)) def onAction(self, action, console): """Run action on console.""" console.runFunction(action.function) def name(self): """Return name.""" return self.settings.name def pixmap(self): """Return pixmap.""" return self.settings.pixmap def usertext(self): """Return text for user.""" return self.settings.usertext def setnsmode(self): """Return setnsmode of Settings.""" return self.settings.setnsmode def resetToDefault(self, name): """Reset setting to default.""" setn = self.settings.get(name) self.document.applyOperation( document.OperationSettingSet(setn, setn.default)) class SettingsProxyMulti(SettingsProxy): """A proxy wrapping settings for multiple widgets.""" def __init__(self, document, widgets, _root=''): """Initialise settings proxy. widgets is a list of widgets to proxy for.""" self.document = document self.widgets = widgets self._root = _root self._settingsatlevel = self._getSettingsAtLevel() self._cachesettings = self._cachesetting = self._cachechild = None def _getSettingsAtLevel(self): """Return settings of widgets at level given.""" if self._root: levels = self._root.split('/') else: levels = [] setns = [] for w in self.widgets: s = w.settings for lev in levels: s = s.get(lev) setns.append(s) return setns def _objList(self, filterclasses): """Return a list of objects with the type in filterclasses.""" setns = self._settingsatlevel # get list of names with appropriate class names = [] for n in setns[0].getNames(): o = setns[0].get(n) for c in filterclasses: if isinstance(o, c): names.append(n) break sset = set(names) for s in setns[1:]: sset &= set(s.getNames()) names = [n for n in names if n in sset] proxylist = [] for n in names: o = setns[0].get(n) if isinstance(o, setting.Settings): # construct new proxy settings (adding on name of root) newroot = n if self._root: newroot = self._root + '/' + newroot v = SettingsProxyMulti(self.document, self.widgets, _root=newroot) else: # use setting from first settings as template v = o proxylist.append(v) return proxylist def childProxyList(self): """Make a list of proxy settings.""" if self._cachechild is None: self._cachechild = self._objList( (setting.Settings, setting.Setting) ) return self._cachechild def settingsProxyList(self): """Get list of settings proxy.""" if self._cachesettings is None: self._cachesettings = self._objList( (setting.Settings,) ) return self._cachesettings def settingList(self): """Set list of common Setting objects for each widget.""" if self._cachesetting is None: self._cachesetting = self._objList( (setting.Setting,) ) return self._cachesetting def actionsList(self): """Get list of common actions.""" anames = None for widget in self.widgets: a = set([a.name for a in widget.actions]) if anames is None: anames = a else: anames &= a actions = [a for a in self.widgets[0].actions if a.name in anames] return actions def onSettingChanged(self, control, setting, val): """Change setting in document.""" # construct list of operations to change each setting ops = [] sname = setting.name if self._root: sname = self._root + '/' + sname for w in self.widgets: s = self.document.resolveFullSettingPath(w.path + '/' + sname) if s.val != val: ops.append(document.OperationSettingSet(s, val)) # apply all operations if ops: self.document.applyOperation( document.OperationMultiple(ops, descr='change settings')) def onAction(self, action, console): """Run actions with same name.""" aname = action.name for w in self.widgets: for a in w.actions: if a.name == aname: console.runFunction(a.function) def name(self): return self._settingsatlevel[0].name def pixmap(self): """Return pixmap.""" return self._settingsatlevel[0].pixmap def usertext(self): """Return text for user.""" return self._settingsatlevel[0].usertext def setnsmode(self): """Return setnsmode.""" return self._settingsatlevel[0].setnsmode def multivalued(self, name): """Is setting multivalued?""" slist = [s.get(name) for s in self._settingsatlevel] first = slist[0].get() for s in slist[1:]: if s.get() != first: return True return False def resetToDefault(self, name): """Reset settings to default.""" ops = [] for s in self._settingsatlevel: setn = s.get(name) ops.append(document.OperationSettingSet(setn, setn.default)) self.document.applyOperation( document.OperationMultiple(ops, descr="reset to default")) class PropertyList(qt4.QWidget): """Edit the widget properties using a set of controls.""" def __init__(self, document, showformatsettings=True, *args): qt4.QWidget.__init__(self, *args) self.document = document self.showformatsettings = showformatsettings self.layout = qt4.QGridLayout(self) self.layout.setSpacing( self.layout.spacing()/2 ) self.layout.setMargin(4) self.childlist = [] self.setncntrls = {} # map setting name to controls def getConsole(self): """Find console window. This is horrible: HACK.""" win = self.parent() while not hasattr(win, 'console'): win = win.parent() return win.console def _addActions(self, setnsproxy, row): """Add a list of actions.""" for action in setnsproxy.actionsList(): text = action.name if action.usertext: text = action.usertext lab = qt4.QLabel(text) self.layout.addWidget(lab, row, 0) self.childlist.append(lab) button = qt4.QPushButton(text) button.setToolTip(action.descr) # need to save reference to caller object button.caller = utils.BoundCaller(setnsproxy.onAction, action, self.getConsole()) self.connect(button, qt4.SIGNAL('clicked()'), button.caller) self.layout.addWidget(button, row, 1) self.childlist.append(button) row += 1 return row def _addControl(self, setnsproxy, setn, row): """Add a control for a setting.""" cntrl = setn.makeControl(None) if cntrl: lab = SettingLabel(self.document, setn, setnsproxy) self.layout.addWidget(lab, row, 0) self.childlist.append(lab) self.connect(cntrl, qt4.SIGNAL('settingChanged'), setnsproxy.onSettingChanged) self.layout.addWidget(cntrl, row, 1) self.childlist.append(cntrl) self.setncntrls[setn.name] = (lab, cntrl) row += 1 return row def _addGroupedSettingsControl(self, grpdsetting, row): """Add a control for a set of grouped settings.""" slist = grpdsetting.settingList() # make first widget with expandable button # this is a label with a + button by this side setnlab = SettingLabel(self.document, slist[0], grpdsetting) expandbutton = qt4.QPushButton("+", checkable=True, flat=True, maximumWidth=16) l = qt4.QHBoxLayout(spacing=0) l.setContentsMargins(0,0,0,0) l.addWidget( expandbutton ) l.addWidget( setnlab ) lw = qt4.QWidget() lw.setLayout(l) self.layout.addWidget(lw, row, 0) self.childlist.append(lw) # make main control cntrl = slist[0].makeControl(None) self.connect(cntrl, qt4.SIGNAL('settingChanged'), grpdsetting.onSettingChanged) self.layout.addWidget(cntrl, row, 1) self.childlist.append(cntrl) row += 1 # set of controls for remaining settings l = qt4.QGridLayout() grp_row = 0 for setn in slist[1:]: cntrl = setn.makeControl(None) if cntrl: lab = SettingLabel(self.document, setn, grpdsetting) l.addWidget(lab, grp_row, 0) self.connect(cntrl, qt4.SIGNAL('settingChanged'), grpdsetting.onSettingChanged) l.addWidget(cntrl, grp_row, 1) grp_row += 1 grpwidget = qt4.QFrame( frameShape = qt4.QFrame.Panel, frameShadow = qt4.QFrame.Raised, visible=False ) grpwidget.setLayout(l) def ontoggle(checked): """Toggle button text and make grp visible/invisible.""" expandbutton.setText( ("+","-")[checked] ) grpwidget.setVisible( checked ) self.connect(expandbutton, qt4.SIGNAL("toggled(bool)"), ontoggle) # add group to standard layout self.layout.addWidget(grpwidget, row, 0, 1, -1) self.childlist.append(grpwidget) row += 1 return row def updateProperties(self, setnsproxy, title=None, showformatting=True, onlyformatting=False): """Update the list of controls with new ones for the SettingsProxy.""" # keep a reference to keep it alive self._setnsproxy = setnsproxy # delete all child widgets self.setUpdatesEnabled(False) while len(self.childlist) > 0: c = self.childlist.pop() self.layout.removeWidget(c) c.deleteLater() del c if setnsproxy is None: self.setUpdatesEnabled(True) return row = 0 self.setncntrls = {} self.layout.setEnabled(False) # add a title if requested if title is not None: lab = qt4.QLabel(title[0], frameShape=qt4.QFrame.Panel, frameShadow=qt4.QFrame.Sunken, toolTip=title[1]) self.layout.addWidget(lab, row, 0, 1, -1) row += 1 # add actions if parent is widget if setnsproxy.actionsList() and not showformatting: row = self._addActions(setnsproxy, row) if setnsproxy.settingsProxyList() and self.showformatsettings: # if we have subsettings, use tabs tabbed = TabbedFormatting(self.document, setnsproxy) self.layout.addWidget(tabbed, row, 1, 1, 2) row += 1 self.childlist.append(tabbed) else: # else add settings proper as a list for setn in setnsproxy.childProxyList(): # add setting # only add if formatting setting and formatting allowed # and not formatting and not formatting not allowed if ( isinstance(setn, setting.Setting) and ( (setn.formatting and (showformatting or onlyformatting)) or (not setn.formatting and not onlyformatting)) and not setn.hidden ): row = self._addControl(setnsproxy, setn, row) elif ( isinstance(setn, SettingsProxy) and setn.setnsmode() == 'groupedsetting' and not onlyformatting ): row = self._addGroupedSettingsControl(setn, row) # add empty widget to take rest of space w = qt4.QWidget( sizePolicy=qt4.QSizePolicy( qt4.QSizePolicy.Maximum, qt4.QSizePolicy.MinimumExpanding) ) self.layout.addWidget(w, row, 0) self.childlist.append(w) self.setUpdatesEnabled(True) self.layout.setEnabled(True) def showHideSettings(self, setnshow, setnhide): """Show or hide controls for settings.""" for vis, setns in ( (True, setnshow), (False, setnhide) ): for setn in setns: if setn in self.setncntrls: for cntrl in self.setncntrls[setn]: cntrl.setVisible(vis) class TabbedFormatting(qt4.QTabWidget): """Class to have tabbed set of settings.""" def __init__(self, document, setnsproxy, shownames=False): qt4.QTabWidget.__init__(self) self.document = document if setnsproxy is None: return # get list of settings self.setnsproxy = setnsproxy setnslist = setnsproxy.settingsProxyList() # add formatting settings if necessary numformat = len( [setn for setn in setnsproxy.settingList() if setn.formatting] ) if numformat > 0: # add on a formatting tab setnslist.insert(0, setnsproxy) self.connect( self, qt4.SIGNAL('currentChanged(int)'), self.slotCurrentChanged ) # subsettings for tabs self.tabsubsetns = [] # collected titles and tooltips for tabs self.tabtitles = [] self.tabtooltips = [] # tabs which have been initialized self.tabinit = set() # add tab for each subsettings for subset in setnslist: if subset.setnsmode() not in ('formatting', 'widgetsettings'): continue self.tabsubsetns.append(subset) # details of tab if subset is setnsproxy: # main tab formatting, so this is special pixmap = 'settings_main' tabname = title = 'Main' tooltip = 'Main formatting' else: # others if hasattr(subset, 'pixmap'): pixmap = subset.pixmap() else: pixmap = None tabname = subset.name() tooltip = title = subset.usertext() # hide name in tab if not shownames: tabname = '' self.tabtitles.append(title) self.tabtooltips.append(tooltip) # create tab indx = self.addTab(qt4.QWidget(), utils.getIcon(pixmap), tabname) self.setTabToolTip(indx, tooltip) def slotCurrentChanged(self, tab): """Lazy loading of tab when displayed.""" if tab in self.tabinit: # already initialized return self.tabinit.add(tab) # settings to show subsetn = self.tabsubsetns[tab] # whether these are the main settings mainsettings = subsetn is self.setnsproxy # add this property list to the scroll widget for tab plist = PropertyList(self.document, showformatsettings=not mainsettings) plist.updateProperties(subsetn, title=(self.tabtitles[tab], self.tabtooltips[tab]), onlyformatting=mainsettings) # create scrollable area scroll = qt4.QScrollArea() scroll.setWidgetResizable(True) scroll.setWidget(plist) # layout for tab widget layout = qt4.QVBoxLayout() layout.setMargin(2) layout.addWidget(scroll) # finally use layout containing items for tab self.widget(tab).setLayout(layout) class FormatDock(qt4.QDockWidget): """A window for formatting the current widget. Provides tabbed formatting properties """ def __init__(self, document, treeedit, *args): qt4.QDockWidget.__init__(self, *args) self.setWindowTitle("Formatting - Veusz") self.setObjectName("veuszformattingdock") self.document = document self.tabwidget = None # update our view when the tree edit window selection changes self.connect(treeedit, qt4.SIGNAL('widgetsSelected'), self.selectedWidgets) def selectedWidgets(self, widgets, setnsproxy): """Created tabbed widgets for formatting for each subsettings.""" # get current tab (so we can set it afterwards) if self.tabwidget: tab = self.tabwidget.currentIndex() else: tab = 0 # delete old tabwidget if self.tabwidget: self.tabwidget.deleteLater() self.tabwidget = None self.tabwidget = TabbedFormatting(self.document, setnsproxy) self.setWidget(self.tabwidget) # wrap tab from zero to max number tab = max( min(self.tabwidget.count()-1, tab), 0 ) self.tabwidget.setCurrentIndex(tab) class PropertiesDock(qt4.QDockWidget): """A window for editing properties for widgets.""" def __init__(self, document, treeedit, *args): qt4.QDockWidget.__init__(self, *args) self.setWindowTitle("Properties - Veusz") self.setObjectName("veuszpropertiesdock") self.document = document # update our view when the tree edit window selection changes self.connect(treeedit, qt4.SIGNAL('widgetsSelected'), self.slotWidgetsSelected) # construct scrollable area self.scroll = qt4.QScrollArea() self.scroll.setWidgetResizable(True) self.setWidget(self.scroll) # construct properties list in scrollable area self.proplist = PropertyList(document, showformatsettings=False) self.scroll.setWidget(self.proplist) def slotWidgetsSelected(self, widgets, setnsproxy): """Update properties when selected widgets change.""" self.proplist.updateProperties(setnsproxy, showformatting=False) class TreeEditDock(qt4.QDockWidget): """A dock window presenting widgets as a tree.""" def __init__(self, document, parentwin): """Initialise dock given document and parent widget.""" qt4.QDockWidget.__init__(self, parentwin) self.parentwin = parentwin self.setWindowTitle("Editing - Veusz") self.setObjectName("veuszeditingwindow") self.selwidgets = [] self.document = document self.connect( self.document, qt4.SIGNAL("sigWiped"), self.slotDocumentWiped ) # construct tree self.treemodel = WidgetTreeModel(document) self.treeview = WidgetTreeView(self.treemodel) # receive change in selection self.connect(self.treeview.selectionModel(), qt4.SIGNAL('selectionChanged(const QItemSelection &,' ' const QItemSelection &)'), self.slotTreeItemsSelected) # set tree as main widget self.setWidget(self.treeview) # toolbar to create widgets self.addtoolbar = qt4.QToolBar("Insert toolbar - Veusz", parentwin) # note wrong description!: backwards compatibility self.addtoolbar.setObjectName("veuszeditingtoolbar") # toolbar for editting widgets self.edittoolbar = qt4.QToolBar("Edit toolbar - Veusz", parentwin) self.edittoolbar.setObjectName("veuszedittoolbar") self._constructToolbarMenu() parentwin.addToolBarBreak(qt4.Qt.TopToolBarArea) parentwin.addToolBar(qt4.Qt.TopToolBarArea, self.addtoolbar) parentwin.addToolBar(qt4.Qt.TopToolBarArea, self.edittoolbar) # this sets various things up self.selectWidget(document.basewidget) # update paste button when clipboard changes self.connect(qt4.QApplication.clipboard(), qt4.SIGNAL('dataChanged()'), self.updatePasteButton) self.updatePasteButton() def slotDocumentWiped(self): """If the document is wiped, reselect root widget.""" self.selectWidget(self.document.basewidget) def slotTreeItemsSelected(self, current, previous): """New item selected in tree. This updates the list of properties """ # get selected widgets self.selwidgets = widgets = [ self.treemodel.getWidget(idx) for idx in self.treeview.selectionModel().selectedRows() ] if len(widgets) == 0: setnsproxy = None elif len(widgets) == 1: setnsproxy = SettingsProxySingle(self.document, widgets[0].settings, actions=widgets[0].actions) else: setnsproxy = SettingsProxyMulti(self.document, widgets) self._enableCorrectButtons() self._checkPageChange() self.emit( qt4.SIGNAL('widgetsSelected'), self.selwidgets, setnsproxy ) def contextMenuEvent(self, event): """Bring up context menu.""" # no widgets selected if not self.selwidgets: return m = qt4.QMenu(self) # selection m.addMenu(self.parentwin.menus['edit.select']) m.addSeparator() # actions on widget(s) for act in ('edit.cut', 'edit.copy', 'edit.paste', 'edit.moveup', 'edit.movedown', 'edit.delete', 'edit.rename'): m.addAction(self.vzactions[act]) # allow show or hides of selected widget anyhide = False anyshow = False for w in self.selwidgets: if 'hide' in w.settings: if w.settings.hide: anyshow = True else: anyhide = True for (enabled, menutext, showhide) in ( (anyhide, 'Hide', True), (anyshow, 'Show', False) ): if enabled: m.addSeparator() act = qt4.QAction(menutext, self) self.connect(act, qt4.SIGNAL('triggered()'), utils.BoundCaller(self.slotWidgetHideShow, self.selwidgets, showhide)) m.addAction(act) m.exec_(self.mapToGlobal(event.pos())) event.accept() def _checkPageChange(self): """Check to see whether page has changed.""" w = None if self.selwidgets: w = self.selwidgets[0] while w is not None and not isinstance(w, widgets.Page): w = w.parent if w is not None: # have page, so check what number we are in basewidget children try: i = self.document.basewidget.children.index(w) self.emit(qt4.SIGNAL("sigPageChanged"), i) except ValueError: pass def _enableCorrectButtons(self): """Make sure the create graph buttons are correctly enabled.""" selw = None if self.selwidgets: selw = self.selwidgets[0] # has to be visible if is to be enabled (yuck) nonorth = self.vzactions['add.nonorthpoint'].setVisible(True) # check whether each button can have this widget # (or a parent) as parent for wc, action in self.addslots.iteritems(): w = selw while w is not None and not wc.willAllowParent(w): w = w.parent self.vzactions['add.%s' % wc.typename].setEnabled(w is not None) # exclusive widgets nonorth = self.vzactions['add.nonorthpoint'].isEnabled() self.vzactions['add.nonorthpoint'].setVisible(nonorth) self.vzactions['add.xy'].setVisible(not nonorth) self.vzactions['add.nonorthfunc'].setVisible(nonorth) self.vzactions['add.function'].setVisible(not nonorth) # certain actions shouldn't work on root isnotroot = not any([isinstance(w, widgets.Root) for w in self.selwidgets]) for act in ('edit.cut', 'edit.copy', 'edit.delete', 'edit.moveup', 'edit.movedown', 'edit.rename'): self.vzactions[act].setEnabled(isnotroot) self.updatePasteButton() def _constructToolbarMenu(self): """Add items to edit/add graph toolbar and menu.""" iconsize = setting.settingdb['toolbar_size'] self.addtoolbar.setIconSize( qt4.QSize(iconsize, iconsize) ) self.edittoolbar.setIconSize( qt4.QSize(iconsize, iconsize) ) self.addslots = {} self.vzactions = actions = self.parentwin.vzactions for widgettype in ('page', 'grid', 'graph', 'axis', 'xy', 'bar', 'fit', 'function', 'boxplot', 'image', 'contour', 'vectorfield', 'key', 'label', 'colorbar', 'rect', 'ellipse', 'imagefile', 'line', 'polygon', 'polar', 'ternary', 'nonorthpoint', 'nonorthfunc'): wc = document.thefactory.getWidgetClass(widgettype) slot = utils.BoundCaller(self.slotMakeWidgetButton, wc) self.addslots[wc] = slot actionname = 'add.' + widgettype actions[actionname] = utils.makeAction( self, wc.description, 'Add %s' % widgettype, slot, icon='button_%s' % widgettype) a = utils.makeAction actions.update({ 'edit.cut': a(self, 'Cut the selected widget', 'Cu&t', self.slotWidgetCut, icon='veusz-edit-cut', key='Ctrl+X'), 'edit.copy': a(self, 'Copy the selected widget', '&Copy', self.slotWidgetCopy, icon='kde-edit-copy', key='Ctrl+C'), 'edit.paste': a(self, 'Paste widget from the clipboard', '&Paste', self.slotWidgetPaste, icon='kde-edit-paste', key='Ctrl+V'), 'edit.moveup': a(self, 'Move the selected widget up', 'Move &up', utils.BoundCaller(self.slotWidgetMove, -1), icon='kde-go-up'), 'edit.movedown': a(self, 'Move the selected widget down', 'Move d&own', utils.BoundCaller(self.slotWidgetMove, 1), icon='kde-go-down'), 'edit.delete': a(self, 'Remove the selected widget', '&Delete', self.slotWidgetDelete, icon='kde-edit-delete'), 'edit.rename': a(self, 'Renames the selected widget', '&Rename', self.slotWidgetRename, icon='kde-edit-rename'), 'add.shapemenu': a(self, 'Add a shape to the plot', 'Shape', self.slotShowShapeMenu, icon='veusz-shape-menu'), }) # add actions to menus for adding widgets and editing addact = [('add.'+w) for w in ('page', 'grid', 'graph', 'axis', 'xy', 'nonorthpoint', 'bar', 'fit', 'function', 'nonorthfunc', 'boxplot', 'image', 'contour', 'vectorfield', 'key', 'label', 'colorbar', 'polar', 'ternary')] menuitems = [ ('insert', '', addact + [ ['insert.shape', 'Add shape', ['add.rect', 'add.ellipse', 'add.line', 'add.imagefile', 'add.polygon'] ]]), ('edit', '', [ 'edit.cut', 'edit.copy', 'edit.paste', 'edit.moveup', 'edit.movedown', 'edit.delete', 'edit.rename' ]), ] utils.constructMenus( self.parentwin.menuBar(), self.parentwin.menus, menuitems, actions ) # create shape toolbar button # attach menu to insert shape button actions['add.shapemenu'].setMenu(self.parentwin.menus['insert.shape']) # add actions to toolbar to create widgets utils.addToolbarActions(self.addtoolbar, actions, addact + ['add.shapemenu']) # add action to toolbar for editing utils.addToolbarActions(self.edittoolbar, actions, ('edit.cut', 'edit.copy', 'edit.paste', 'edit.moveup', 'edit.movedown', 'edit.delete', 'edit.rename')) self.connect( self.parentwin.menus['edit.select'], qt4.SIGNAL('aboutToShow()'), self.updateSelectMenu ) def slotMakeWidgetButton(self, wc): """User clicks button to make widget.""" self.makeWidget(wc.typename) def slotShowShapeMenu(self): a = self.vzactions['add.shapemenu'] a.menu().popup( qt4.QCursor.pos() ) def makeWidget(self, widgettype, autoadd=True, name=None): """Called when an add widget button is clicked. widgettype is the type of widget autoadd specifies whether to add default children if name is set this name is used if possible (ie no other children have it) """ # if no widget selected, bomb out if not self.selwidgets: return parent = document.getSuitableParent(widgettype, self.selwidgets[0]) assert parent is not None if name in parent.childnames: name = None # make the new widget and update the document w = self.document.applyOperation( document.OperationWidgetAdd(parent, widgettype, autoadd=autoadd, name=name) ) # select the widget self.selectWidget(w) def slotWidgetCut(self): """Cut the selected widget""" self.slotWidgetCopy() self.slotWidgetDelete() def slotWidgetCopy(self): """Copy selected widget to the clipboard.""" if self.selwidgets: mimedata = document.generateWidgetsMime(self.selwidgets) clipboard = qt4.QApplication.clipboard() clipboard.setMimeData(mimedata) def updatePasteButton(self): """Is the data on the clipboard a valid paste at the currently selected widget? If so, enable paste button""" data = document.getClipboardWidgetMime() if len(self.selwidgets) == 0: show = False else: show = document.isWidgetMimePastable(self.selwidgets[0], data) self.vzactions['edit.paste'].setEnabled(show) def doInitialWidgetSelect(self): """Select a sensible initial widget.""" w = self.document.basewidget for i in xrange(2): try: c = w.children[0] except IndexError: break if c: w = c self.selectWidget(w) def slotWidgetPaste(self): """Paste something from the clipboard""" data = document.getClipboardWidgetMime() if data: op = document.OperationWidgetPaste(self.selwidgets[0], data) widgets = self.document.applyOperation(op) if widgets: self.selectWidget(widgets[0]) def slotWidgetDelete(self): """Delete the widget selected.""" widgets = self.selwidgets # if no item selected, leave if not widgets: return # get list of widgets in order widgetlist = [] self.document.basewidget.buildFlatWidgetList(widgetlist) # find indices of widgets to be deleted - find one to select after indexes = [widgetlist.index(w) for w in widgets] if -1 in indexes: raise RuntimeError, "Invalid widget in list of selected widgets" minindex = min(indexes) # delete selected widget self.document.applyOperation( document.OperationWidgetsDelete(widgets)) # rebuild list widgetlist = [] self.document.basewidget.buildFlatWidgetList(widgetlist) # find next to select if minindex < len(widgetlist): nextwidget = widgetlist[minindex] else: nextwidget = widgetlist[-1] # select the next widget (we have to select root first!) self.selectWidget(self.document.basewidget) self.selectWidget(nextwidget) def slotWidgetRename(self): """Allows the user to rename the selected widget.""" selected = self.treeview.selectedIndexes() if len(selected) != 0: self.treeview.edit(selected[0]) def selectWidget(self, widget): """Select the associated listviewitem for the widget w in the listview.""" index = self.treemodel.getWidgetIndex(widget) if index is not None: self.treeview.scrollTo(index) self.treeview.selectionModel().select( index, qt4.QItemSelectionModel.Clear | qt4.QItemSelectionModel.Current | qt4.QItemSelectionModel.Rows | qt4.QItemSelectionModel.Select ) def slotWidgetMove(self, direction): """Move the selected widget up/down in the hierarchy. a is the action (unused) direction is -1 for 'up' and +1 for 'down' """ if not self.selwidgets: return # widget to move w = self.selwidgets[0] # actually move the widget self.document.applyOperation( document.OperationWidgetMoveUpDown(w, direction) ) # re-highlight moved widget self.selectWidget(w) def slotWidgetHideShow(self, widgets, hideshow): """Hide or show selected widgets. hideshow is True for hiding, False for showing """ ops = [ document.OperationSettingSet(w.settings.get('hide'), hideshow) for w in widgets ] descr = ('show', 'hide')[hideshow] self.document.applyOperation( document.OperationMultiple(ops, descr=descr)) def checkWidgetSelected(self): """Check widget is selected.""" if len(self.treeview.selectionModel().selectedRows()) == 0: self.selectWidget(self.document.basewidget) def _selectWidgetsTypeAndOrName(self, wtype, wname): """Select widgets with type or name given. Give None if you don't care for either.""" def selectwidget(path, w): """Select widget if of type or name given.""" if ( (wtype is None or w.typename == wtype) and (wname is None or w.name == wname) ): idx = self.treemodel.getWidgetIndex(w) self.treeview.selectionModel().select( idx, qt4.QItemSelectionModel.Select | qt4.QItemSelectionModel.Rows) self.document.walkNodes(selectwidget, nodetypes=('widget',)) def _selectWidgetSiblings(self, w, wtype): """Select siblings of widget given with type.""" for c in w.parent.children: if c is not w and c.typename == wtype: idx = self.treemodel.getWidgetIndex(c) self.treeview.selectionModel().select( idx, qt4.QItemSelectionModel.Select | qt4.QItemSelectionModel.Rows) def updateSelectMenu(self): """Update edit.select menu.""" menu = self.parentwin.menus['edit.select'] menu.clear() if len(self.selwidgets) == 0: return wtype = self.selwidgets[0].typename name = self.selwidgets[0].name menu.addAction( "All '%s' widgets" % wtype, lambda: self._selectWidgetsTypeAndOrName(wtype, None)) menu.addAction( "Siblings of '%s' with type '%s'" % (name, wtype), lambda: self._selectWidgetSiblings(self.selwidgets[0], wtype)) menu.addAction( "All '%s' widgets called '%s'" % (wtype, name), lambda: self._selectWidgetsTypeAndOrName(wtype, name)) menu.addAction( "All widgets called '%s'" % name, lambda: self._selectWidgetsTypeAndOrName(None, name)) class SettingLabel(qt4.QWidget): """A label to describe a setting. This widget shows the name, a tooltip description, and gives access to the context menu """ def __init__(self, document, setting, setnsproxy): """Initialise button, passing document, setting, and parent widget.""" qt4.QWidget.__init__(self) self.setFocusPolicy(qt4.Qt.StrongFocus) self.document = document self.connect(document, qt4.SIGNAL('sigModified'), self.slotDocModified) self.setting = setting self.setnsproxy = setnsproxy self.layout = qt4.QHBoxLayout(self) self.layout.setMargin(2) if setting.usertext: text = setting.usertext else: text = setting.name self.labelicon = qt4.QLabel(text) self.layout.addWidget(self.labelicon) self.iconlabel = qt4.QLabel() self.layout.addWidget(self.iconlabel) self.connect(self, qt4.SIGNAL('clicked'), self.settingMenu) self.infocus = False self.inmouse = False self.inmenu = False # initialise settings self.slotDocModified() def mouseReleaseEvent(self, event): """Emit clicked(pos) on mouse release.""" self.emit( qt4.SIGNAL('clicked'), self.mapToGlobal(event.pos()) ) return qt4.QWidget.mouseReleaseEvent(self, event) def keyReleaseEvent(self, event): """Emit clicked(pos) on key release.""" if event.key() == qt4.Qt.Key_Space: self.emit( qt4.SIGNAL('clicked'), self.mapToGlobal(self.iconlabel.pos()) ) event.accept() else: return qt4.QWidget.keyReleaseEvent(self, event) def slotDocModified(self): """If the document has been modified.""" # update pixmap (e.g. link added/removed) self.updateHighlight() # update tooltip tooltip = self.setting.descr if self.setting.isReference(): tooltip += ('\nLinked to %s' % self.setting.getReference().resolve(self.setting).path) self.setToolTip(tooltip) # if not default, make label bold f = qt4.QFont(self.labelicon.font()) multivalued = self.setnsproxy.multivalued(self.setting.name) f.setBold( (not self.setting.isDefault()) or multivalued ) f.setItalic( multivalued ) self.labelicon.setFont(f) def updateHighlight(self): """Show drop down arrow if item has focus.""" if self.inmouse or self.infocus or self.inmenu: pixmap = 'downarrow.png' else: if self.setting.isReference() and not self.setting.isDefault(): pixmap = 'link.png' else: pixmap = 'downarrow_blank.png' self.iconlabel.setPixmap(utils.getPixmap(pixmap)) def enterEvent(self, event): """Focus on mouse enter.""" self.inmouse = True self.updateHighlight() return qt4.QWidget.enterEvent(self, event) def leaveEvent(self, event): """Clear focus on mouse leaving.""" self.inmouse = False self.updateHighlight() return qt4.QWidget.leaveEvent(self, event) def focusInEvent(self, event): """Focus if widgets gets focus.""" self.infocus = True self.updateHighlight() return qt4.QWidget.focusInEvent(self, event) def focusOutEvent(self, event): """Lose focus if widget loses focus.""" self.infocus = False self.updateHighlight() return qt4.QWidget.focusOutEvent(self, event) def addCopyToWidgets(self, menu): """Make a menu with list of other widgets in it.""" def getWidgetsOfType(widget, widgettype, widgets=[]): """Recursively build up a list of widgets of the type given.""" for w in widget.children: if w.typename == widgettype: widgets.append(w) getWidgetsOfType(w, widgettype, widgets) # get list of widget paths to copy setting to # this is all widgets of same type widgets = [] setwidget = self.setting.getWidget() if setwidget is None: return getWidgetsOfType(self.document.basewidget, setwidget.typename, widgets) widgets = [w.path for w in widgets if w != setwidget] widgets.sort() # chop off widget part of setting path # this is so we can add on a different widget path # note setpath needs to include Settings part of path too setpath = self.setting.path wpath = self.setting.getWidget().path setpath = setpath[len(wpath):] # includes / for widget in widgets: action = menu.addAction(widget) def modify(widget=widget): """Modify the setting for the widget given.""" wpath = widget + setpath self.document.applyOperation( document.OperationSettingSet(wpath, self.setting.get())) menu.connect(action, qt4.SIGNAL('triggered()'), modify) def settingMenu(self, pos): """Pop up menu for each setting.""" # forces settings to be updated self.parentWidget().setFocus() # get it back straight away self.setFocus() # get widget, with its type and name widget = self.setting.parent while widget is not None and not isinstance(widget, widgets.Widget): widget = widget.parent if widget is None: return self._clickwidget = widget wtype = widget.typename name = widget.name popup = qt4.QMenu(self) popup.addAction('Reset to default', self.actionResetDefault) copyto = popup.addMenu('Copy to') copyto.addAction("all '%s' widgets" % wtype, self.actionCopyTypedWidgets) copyto.addAction("'%s' siblings" % wtype, self.actionCopyTypedSiblings) copyto.addAction("'%s' widgets called '%s'" % (wtype, name), self.actionCopyTypedNamedWidgets) copyto.addSeparator() self.addCopyToWidgets(copyto) popup.addAction('Use as default style', self.actionSetStyleSheet) # special actions for references if self.setting.isReference(): popup.addSeparator() popup.addAction('Unlink setting', self.actionUnlinkSetting) self.inmenu = True self.updateHighlight() popup.exec_(pos) self.inmenu = False self.updateHighlight() def actionResetDefault(self): """Reset setting to default.""" self.setnsproxy.resetToDefault(self.setting.name) def actionCopyTypedWidgets(self): """Copy setting to widgets of same type.""" self.document.applyOperation( document.OperationSettingPropagate(self.setting) ) def actionCopyTypedSiblings(self): """Copy setting to siblings of the same type.""" self.document.applyOperation( document.OperationSettingPropagate(self.setting, root=self._clickwidget.parent, maxlevels=1) ) def actionCopyTypedNamedWidgets(self): """Copy setting to widgets with the same name and type.""" self.document.applyOperation( document.OperationSettingPropagate(self.setting, widgetname= self._clickwidget.name) ) def actionUnlinkSetting(self): """Unlink the setting if it is a reference.""" self.document.applyOperation( document.OperationSettingSet(self.setting, self.setting.get()) ) def actionSetStyleSheet(self): """Use the setting as the default in the stylesheet.""" # get name of stylesheet setting sslink = self.setting.getStylesheetLink() # apply operation to change it self.document.applyOperation( document.OperationMultiple( [ document.OperationSettingSet(sslink, self.setting.get()), document.OperationSettingSet(self.setting, self.setting.default) ], descr="make default style") ) veusz-1.15/setup.cfg0000644002344000001440000000046511734662204014425 0ustar jssusers00000000000000[bdist_rpm] release = 1 packager = Jeremy Sanders doc_files = COPYING README INSTALL AUTHORS ChangeLog examples/ Documents/ group = Applications/Engineering requires = PyQt4 >= 4.3 python >= 2.3 NumPy >= 1.0 veusz-1.15/AUTHORS0000644002344000001440000000015211734662204013645 0ustar jssusers00000000000000Jeremy Sanders http://www.jeremysanders.net/ James Graham veusz-1.15/dialogs/0000755002344000001440000000000011734662466014233 5ustar jssusers00000000000000veusz-1.15/dialogs/import.ui0000644002344000001440000001625411734662204016102 0ustar jssusers00000000000000 importdialog 0 0 645 640 Import data - Veusz 6 0 &Filename filenameedit Enter the filename to be imported here &Browse... Read in data from clipboard rather than file -1 General options 0 0 <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Data imported are not stored in the Veusz saved file, but are reloaded each time the Veusz file is opened, or Data-&gt;Reload is selected on the menu.</p></body></html> &Link datasets to file true &Prefix prefixcombo 0 0 Prefix to prepend to each dataset name imported, or enter $FILENAME to have filename prepended true Character encoding Character encoding of input file Suffi&x suffixcombo 0 0 Suffix to append to each dataset name imported, or enter $FILENAME to have filename appended true Tag Enter a list of tags to apply to the imported datasets true QDialogButtonBox::Close|QDialogButtonBox::Reset qPixmapFromMimeSource HistoryCombo QComboBox
    historycombo.h
    HistoryCheck QCheckBox
    historycheck.h
    HistoryValueCombo QComboBox
    historyvaluecombo.h
    buttonBox rejected() importdialog close() 322 623 322 319
    veusz-1.15/dialogs/license.ui0000644002344000001440000000425511734662204016210 0ustar jssusers00000000000000 LicenseDialog 0 0 609 373 License - Veusz 9 6 Veusz license 9 6 QTextEdit::NoWrap true 0 6 Qt::Horizontal 131 31 OK okButton clicked() LicenseDialog accept() 542 347 294 186 veusz-1.15/dialogs/datacreate.ui0000644002344000001440000002050011734662204016652 0ustar jssusers00000000000000 DataCreateDialog 0 0 567 534 Create dataset - Veusz 6 0 &Name nameedit 0 0 Method of creating dataset 6 9 &Value or range 6 0 Number of steps &Parametric (as an expression of t) 6 0 t = to in steps (inclusive) &Expression using existing datasets Dataset values or expressions Enter expressions as a function of t, or leave blank Enter constant values here, leave blank if appropriate, or enter an inclusive range, e.g. 1:10 true Enter expressions as a function of other datasets. Append suffixes _data, _serr, _nerr and _perr to access different parts of datasets. If a dataset name contains punctuation or spaces, surround the name with backticks (`). true &Symmetric error symerroredit P&ositive error poserroredit Ne&gative error negerroredit V&alue valueedit &Link this dataset to these expressions true QDialogButtonBox::Close|QDialogButtonBox::Reset HistoryCombo QComboBox
    historycombo.h
    HistoryGroupBox QGroupBox
    historygroupbox.h
    1
    buttonBox rejected() DataCreateDialog close() 283 498 283 257
    veusz-1.15/dialogs/importhelp.ui0000644002344000001440000002671111734662204016752 0ustar jssusers00000000000000 ImportHelpDialog 0 0 581 368 Import data help true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Arial'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Veusz assumes that data are stored as columns in a text file separated by tabs or spaces. Names should be entered for the datasets read from each column, separated by spaces or commas (in the dataset names or descriptor box). If you leave the descriptor blank, automatic dataset names will be used (prefix + column + suffix, or &quot;colX&quot; if the prefix and suffix are blank).</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">If you want to supply errors or uncertainties on the data, these can be given in the columns following the dataset column (one column for symmetric errors or two for asymmetric errors). To tell Veusz that a dataset has errors, add &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">+-</span><span style=" font-size:10pt;">&quot; or &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">+,-</span><span style=" font-size:10pt;">&quot; to the dataset name to specify symmetric or asymmetric errors, respectively.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Commas or spaces separate the dataset name and the error bars. They are interchangable, except multiple commas will skip an input columns. </span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">Examples</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">x y</span><span style=" font-size:10pt;"> x and y with no errors (2 columns for 2 datasets)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">x,+-</span><span style=" font-size:10pt;"> x with symmetric errors (2 columns for single dataset)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">y + -</span><span style=" font-size:10pt;"> y with asymmetric errors (3 columns for dataset)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">x[1:5]+,-</span><span style=" font-size:10pt;"> x_1 to x_5, each with asymmetric errors (15 columns in total)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">x y +-</span><span style=" font-size:10pt;"> x with no errors, y with symmetric errors (3 columns in total)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">,x,y,-,+</span><span style=" font-size:10pt;"> skip first column, x with no errors, y followed by negative then postive error bars</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">Data types</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">A file can contain different types of data. This type is specified immediately after the dataset name in round brackets, e.g. &quot;x(float)&quot;, &quot;labels(text)&quot; or &quot;y(float),+-&quot;.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">Only numerical (use float, number or numeric) and text (using text or string) data are supported. If a text column has spaces it should be surrounded by quotation marks.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">Comments</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt;">If any of the &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">#</span><span style=" font-size:10pt;">&quot;, &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">!</span><span style=" font-size:10pt;">&quot;, &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">;</span><span style=" font-size:10pt;">&quot; or &quot;</span><span style=" font-family:'Courier New,courier'; font-size:10pt;">%</span><span style=" font-size:10pt;">&quot; characters are found without being inside quotation marks, the rest of a line is ignored. Use these characters to add comments to a file.</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:10pt; font-weight:600;">Further notes</span></p> <ol style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" font-size:10pt;" style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Extra tabs or spaces between columns are ignored</li> <li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Extra data at the end of a line are ignored</li> <li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The text <span style=" font-family:'Courier New,courier';">nan</span> or <span style=" font-family:'Courier New,courier';">inf</span> translates to the usual numerical values. These values aren't plotted in a plot, giving a break in the line.</li> <li style=" font-size:10pt;" style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can encode the descriptor describing the data in the file itself with a line <span style=" font-family:'Courier New,courier';">descriptor XXX</span> before the data. Leave the descriptor blank in the import dialog if you do this. Multiple descriptors can be placed in the file to store multiple sets of data. </li></ol> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:10pt; font-weight:600;"></p></body></html> Qt::Horizontal QDialogButtonBox::Close buttonBox clicked(QAbstractButton*) ImportHelpDialog close() 248 254 157 274 veusz-1.15/dialogs/datacreate.py0000644002344000001440000003034411734662204016674 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Dataset creation dialog.""" import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document import veusz.setting as setting from veuszdialog import VeuszDialog import dataeditdialog class _DSException(RuntimeError): """A class to handle errors while trying to create datasets.""" pass class DataCreateDialog(VeuszDialog): """Dialog to create datasets. They can be created from numerical ranges, parametrically or from expressions involving other dataset.""" def __init__(self, parent, document): """Initialise dialog with document.""" VeuszDialog.__init__(self, parent, 'datacreate.ui') self.document = document # create button group to get notification of changes self.connect( self.methodGroup, qt4.SIGNAL('radioClicked'), self.slotMethodChanged ) # connect create button self.createbutton = self.buttonBox.addButton( "C&reate", qt4.QDialogButtonBox.ApplyRole ) self.replacebutton = self.buttonBox.addButton( "&Replace", qt4.QDialogButtonBox.ApplyRole ) self.connect( self.buttonBox.button(qt4.QDialogButtonBox.Reset), qt4.SIGNAL('clicked()'), self.resetButtonClicked ) self.connect( self.createbutton, qt4.SIGNAL('clicked()'), self.createButtonClicked ) self.connect( self.replacebutton, qt4.SIGNAL('clicked()'), self.createButtonClicked ) # connect notification of document change self.connect( self.document, qt4.SIGNAL("sigModified"), self.modifiedDocSlot ) # set validators for edit controls self.numstepsedit.setValidator( qt4.QIntValidator(1, 99999999, self) ) self.tstartedit.setValidator( qt4.QDoubleValidator(self) ) self.tendedit.setValidator( qt4.QDoubleValidator(self) ) self.tstepsedit.setValidator( qt4.QIntValidator(1, 99999999, self) ) # connect up edit control to update create button status for edit in (self.numstepsedit, self.tstartedit, self.tendedit, self.tstepsedit, self.nameedit, self.valueedit): self.connect( edit, qt4.SIGNAL('editTextChanged(const QString &)'), self.editsEditSlot ) self.connect( self.nameedit, qt4.SIGNAL('currentIndexChanged(int)'), self.datasetSelected ) # edit controls for dataset self.dsedits = { 'data': self.valueedit, 'serr': self.symerroredit, 'perr': self.poserroredit, 'nerr': self.negerroredit } # update button state self.editsEditSlot('') def slotMethodChanged(self, button): """Called when a new data creation method is used.""" # enable and disable correct widgets depending on method isvalue = button is self.valueradio self.valuehelperlabel.setVisible(isvalue) self.numstepsedit.setEnabled(isvalue) isparametric = button is self.parametricradio self.parametrichelperlabel.setVisible(isparametric) self.tstartedit.setEnabled(isparametric) self.tendedit.setEnabled(isparametric) self.tstepsedit.setEnabled(isparametric) isfunction = button is self.expressionradio self.expressionhelperlabel.setVisible(isfunction) # enable/disable create button self.editsEditSlot('') def modifiedDocSlot(self): """Update create button if document changes.""" self.editsEditSlot('') def datasetSelected(self, index): """If dataset is selected from drop down box, reload entries for editing.""" if index >= 0: dsname = unicode( self.nameedit.text() ) if dsname in self.document.data: self.reEditDataset(self.document.data[dsname], dsname) def reEditDataset(self, ds, dsname): """Given a dataset name, allow it to be edited again (if it is editable).""" if isinstance(ds, document.DatasetExpression): # change selected method if ds.parametric is None: # standard expression self.expressionradio.click() else: # parametric dataset self.parametricradio.click() p = ds.parametric self.tstartedit.setText( '%g' % p[0] ) self.tendedit.setText( '%g' % p[1] ) self.tstepsedit.setText( str(p[2]) ) # make sure name is set self.nameedit.setText(dsname) # set expressions for part in self.dsedits.iterkeys(): text = ds.expr[part] if text is None: text = '' self.dsedits[part].setText(text) elif isinstance(ds, document.DatasetRange): # change selected method self.valueradio.click() # make sure name is set self.nameedit.setText(dsname) # set expressions for part in self.dsedits.iterkeys(): data = getattr(ds, 'range_%s' % part) if data is None: text = '' else: text = '%g:%g' % data self.dsedits[part].setText(text) def editsEditSlot(self, dummytext): """Enable/disable createbutton.""" # dataset name checks dstext = unicode(self.nameedit.text()) dsvalid = utils.validateDatasetName(dstext) dsexists = dstext in self.document.data # check other edit controls method = self.methodGroup.getRadioChecked() if method is self.valueradio: # value editsokay = self.numstepsedit.hasAcceptableInput() elif method is self.parametricradio: # parametric editsokay = (self.tstartedit.hasAcceptableInput() and self.tendedit.hasAcceptableInput() and self.tstepsedit.hasAcceptableInput()) else: # function editsokay = True # we needs some input on the value if len(unicode(self.valueedit.text())) == 0: editsokay = False # hide / show create button depending whether dataset exists self.createbutton.setVisible(not dsexists) self.replacebutton.setVisible(dsexists) # enable buttons if expressions valid enabled = dsvalid and editsokay self.createbutton.setEnabled(enabled) self.replacebutton.setEnabled(enabled) def resetButtonClicked(self): """Reset button clicked - reset dialog.""" for cntrl in (self.valueedit, self.symerroredit, self.poserroredit, self.negerroredit, self.numstepsedit, self.tstartedit, self.tendedit, self.tstepsedit, self.nameedit): cntrl.setEditText("") self.linkcheckbox.setChecked(True) self.valueradio.click() def createButtonClicked(self): """Create button pressed.""" dsname = unicode( self.nameedit.text() ) dsexists = dsname in self.document.data try: # select function to create dataset with createfn = { self.valueradio: self.createFromRange, self.parametricradio: self.createParametric, self.expressionradio: self.createFromExpression }[ self.methodGroup.getRadioChecked()] # make a new dataset using method op = createfn(dsname) self.document.applyOperation(op) if dsexists: status = "Replaced dataset '%s'" % dsname else: status = "Created dataset '%s'" % dsname self.statuslabel.setText(status) except (document.CreateDatasetException, document.DatasetException, _DSException), e: # all bad roads lead here - take exception string and tell user if dsexists: status = "Replacement failed" else: status = "Creation failed" self.statuslabel.setText(status) qt4.QMessageBox.warning(self, "Veusz", unicode(e)) def createFromRange(self, name): """Make dataset from a range or constant. name is the name of the dataset Raises _DSException if error """ numsteps = int( unicode(self.numstepsedit.text()) ) # go over each of the ranges / values vals = {} for key, cntrl in self.dsedits.iteritems(): text = unicode( cntrl.text() ).strip() if not text: continue if text.find(':') != -1: # an actual range parts = text.split(':') if len(parts) != 2: raise _DSException("Incorrect range format, use form 1:10") try: minval, maxval = float(parts[0]), float(parts[1]) except ValueError: raise _DSException("Invalid number in range") else: try: minval = float(text) except ValueError: raise _DSException("Invalid number") maxval = minval vals[key] = (minval, maxval) linked = self.linkcheckbox.checkState() == qt4.Qt.Checked return document.OperationDatasetCreateRange(name, numsteps, vals, linked=linked) def createParametric(self, name): """Use a parametric form to create the dataset. Raises _DSException if error """ t0 = float( unicode(self.tstartedit.text()) ) t1 = float( unicode(self.tendedit.text()) ) numsteps = int( unicode(self.tstepsedit.text()) ) # get expressions vals = {} for key, cntrl in self.dsedits.iteritems(): text = unicode( cntrl.text() ).strip() if text: vals[key] = text linked = self.linkcheckbox.checkState() == qt4.Qt.Checked return document.OperationDatasetCreateParameteric(name, t0, t1, numsteps, vals, linked=linked) def createFromExpression(self, name): """Create a dataset based on the expressions given.""" # get expression for each part of the dataset vals = {} for key, cntrl in self.dsedits.iteritems(): text = unicode( cntrl.text() ).strip() if text: vals[key] = text link = self.linkcheckbox.checkState() == qt4.Qt.Checked op = document.OperationDatasetCreateExpression(name, vals, link) op.validateExpression(self.document) return op def recreateDataset(mainwindow, document, dataset, datasetname): """Open dialog to recreate a DatasetExpression / DatasetRange.""" dialog = DataCreateDialog(mainwindow, document) mainwindow.showDialog(dialog) dialog.reEditDataset(dataset, datasetname) dataeditdialog.recreate_register[document.DatasetExpression] = recreateDataset dataeditdialog.recreate_register[document.DatasetRange] = recreateDataset veusz-1.15/dialogs/plugin.ui0000644002344000001440000000327311734662204016063 0ustar jssusers00000000000000 WorkerPluginDialog Dialog 0 0 TextLabel Options Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Reset buttonBox rejected() WorkerPluginDialog close() 316 260 286 274 veusz-1.15/dialogs/preferences.ui0000644002344000001440000003504711734662204017072 0ustar jssusers00000000000000 PrefsDialog 0 0 430 252 Preferences 0 View Use antialiasing to smooth jagged edges Antialiasing Please restart Veusz after changing this option Override system locale settings to show Veusz in US/English Update interval How often Veusz will update the plot if it has changed Toolbar icon size 8 16 24 32 48 64 Number of drawing threads Maximum number of parallel threads to use for drawing plots. Set to 0 to disable threads. 16 File File dialogs will open in the current working directory when starting the program, rather than the one last used by Veusz when it was previously run Open file dialog in current working directory Export Bitmap DPI Dots Per Inch is used to convert from the physical size of the plot to the number of pixels in output bitmaps. Increase this to make output bitmaps have more pixels. true Appy antialiasing, or smoothing, to output bitmap images. This is recommended for most purposes. Antialias PDF/EPS DPI The number of dots per inch used for writing PDF and EPS files. As these are vector formats, this does not make much difference to the output, but larger values improve the placement of characters and also make hatched fills finer. true Bitmap background Use alpha channel values of 0 for transparency Jpeg quality Choose Jpeg quality setting. Lower values give poorer quality and are more compressed. 0 100 85 Postscript color Output postscript as full color, or convert to greyscale Color Greyscale Editable text in SVG Exports text in SVG files as text, rather than curves. Curves mean that the file will display the same on any system, but text can be edited easily in other programs. New documents 0 0 Default stylesheet and custom definition files to load in new documents true Stylesheet A stylesheet file name specified here will be automatically loaded when creating a new document. Leave blank for no stylesheet to be loaded. Browse... Custom definitons A custom definiton file name specified here will be automatically loaded when creating a new document. Leave blank for no file to be loaded. Browse... Colors Colors used by the Veusz user interface Plugins Add entries here to load Veusz import plugins. Entries should consist of a Python file to load. true Qt::Horizontal 40 20 0 0 Remove Add... QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() PrefsDialog accept() 169 236 169 126 buttonBox rejected() PrefsDialog reject() 169 236 169 126 veusz-1.15/dialogs/errorloading.py0000644002344000001440000000337211734662204017267 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Dialog to show if there is an error loading.""" import veusz.qtall as qt4 import veusz.utils as utils from veuszdialog import VeuszDialog class ErrorLoadingDialog(VeuszDialog): """Dialog when error loading.""" def __init__(self, parent, filename, error, traceback): VeuszDialog.__init__(self, parent, 'errorloading.ui') # insert filename into label text = unicode(self.errorlabel.text()) text = text % filename self.errorlabel.setText(text) self.errormessagelabel.setText(error) # put backtrace into error edit box self.errortextedit.setPlainText(traceback) # set warning pixmap to left of dialog icon = qt4.qApp.style().standardIcon(qt4.QStyle.SP_MessageBoxWarning, None, self) self.iconlabel.setPixmap(icon.pixmap(32)) veusz-1.15/dialogs/custom.py0000644002344000001440000002214311734662204016107 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document from veuszdialog import VeuszDialog class CustomItemModel(qt4.QAbstractTableModel): """A model for editing custom items.""" def __init__(self, parent, document): qt4.QAbstractTableModel.__init__(self, parent) self.document = document # connect notification of document change self.connect( self.document, qt4.SIGNAL('sigModified'), self.doUpdate ) def rowCount(self, parent): return len(self.document.customs) def columnCount(self, parent): return 3 def data(self, index, role): """Lookup data in document customs list.""" if role in (qt4.Qt.DisplayRole, qt4.Qt.EditRole): d = self.document.customs[index.row()][index.column()] return qt4.QVariant(d) elif role == qt4.Qt.ToolTipRole: return ('Constant, function or import', 'Name for constant, function(arg1, arg2...) or module name', 'Expression defining constant or function, ' 'or list of symbols to import from module')[index.column()] return qt4.QVariant() def flags(self, index): """Items are editable""" return ( qt4.Qt.ItemIsSelectable | qt4.Qt.ItemIsEditable | qt4.Qt.ItemIsEnabled ) def headerData(self, section, orientation, role): """Return the headers at the top of the view.""" if role == qt4.Qt.DisplayRole: if orientation == qt4.Qt.Horizontal: return qt4.QVariant( ('Type', 'Name', 'Definition')[section] ) else: return qt4.QVariant(str(section+1)) return qt4.QVariant() def doUpdate(self): """Document changed.""" self.emit( qt4.SIGNAL('layoutChanged()') ) def addNewEntry(self): """Add a new row to the list of custom items.""" newcustom = list(self.document.customs) newcustom.append( ['constant', 'name', 'None'] ) self.document.applyOperation( document.OperationSetCustom(newcustom) ) def deleteEntry(self, num): """Delete row num.""" newcustom = list(self.document.customs) del newcustom[num] self.document.applyOperation( document.OperationSetCustom(newcustom) ) def moveUpEntry(self, num): """Move up entry.""" if num == 0 or len(self.document.customs) == 0: return newcustom = list(self.document.customs) row = newcustom[num] del newcustom[num] newcustom.insert(num-1, row) self.document.applyOperation( document.OperationSetCustom(newcustom) ) def moveDownEntry(self, num): """Move down entry.""" if num >= len(self.document.customs)-1: return newcustom = list(self.document.customs) row = newcustom[num] del newcustom[num] newcustom.insert(num+1, row) self.document.applyOperation( document.OperationSetCustom(newcustom) ) def setData(self, index, value, role): """Edit an item.""" if index.isValid() and role == qt4.Qt.EditRole: col = index.column() row = index.row() value = unicode(value.toString()) if col == 0: ok = value in ('constant', 'function', 'import') elif col == 1: dtype = self.document.customs[row][0] if dtype == 'constant': ok = document.identifier_re.match(value) is not None elif dtype == 'function': ok = document.function_re.match(value) is not None elif dtype == 'import': ok = True else: ok = True if not ok: return False newcustom = list(self.document.customs) newcustom[row][col] = value self.document.applyOperation( document.OperationSetCustom(newcustom) ) self.emit(qt4.SIGNAL('dataChanged(const QModelIndex &, const QModelIndex &'), index, index) return True return False class ComboTypeDeligate(qt4.QItemDelegate): """This class is for choosing between the constant and function types in a combo box in a model view.""" def createEditor(self, parent, option, index): """Create combobox for editing type.""" w = qt4.QComboBox(parent) w.addItems(['constant', 'function', 'import']) w.setFocusPolicy( qt4.Qt.StrongFocus ) return w def setEditorData(self, editor, index): """Update data in editor.""" i = editor.findText( index.data().toString() ) editor.setCurrentIndex(i) def setModelData(self, editor, model, index): """Update data in model.""" model.setData(index, qt4.QVariant(editor.currentText()), qt4.Qt.EditRole) def updateEditorGeometry(self, editor, option, index): """Update editor geometry.""" editor.setGeometry(option.rect) class CustomDialog(VeuszDialog): """A dialog to create or edit custom constant and function definitions.""" def __init__(self, parent, document): VeuszDialog.__init__(self, parent, 'custom.ui') self.document = document self.model = CustomItemModel(self, document) self.definitionView.setModel(self.model) self.combodeligate = ComboTypeDeligate(self) self.definitionView.setItemDelegateForColumn(0, self.combodeligate) # connect buttons to slots self.connect(self.addButton, qt4.SIGNAL('clicked()'), self.slotAdd) self.connect(self.removeButton, qt4.SIGNAL('clicked()'), self.slotRemove) self.connect(self.upButton, qt4.SIGNAL('clicked()'), self.slotUp) self.connect(self.downButton, qt4.SIGNAL('clicked()'), self.slotDown) self.connect(self.saveButton, qt4.SIGNAL('clicked()'), self.slotSave) self.connect(self.loadButton, qt4.SIGNAL('clicked()'), self.slotLoad) # recent button shows list of recently used files for loading self.connect(self.recentButton, qt4.SIGNAL('filechosen'), self.loadFile) self.recentButton.setSetting('customdialog_recent') def loadFile(self, filename): """Load the given file.""" self.document.applyOperation( document.OperationLoadCustom(filename) ) def slotAdd(self): """Add an entry.""" self.model.addNewEntry() def slotRemove(self): """Remove an entry.""" selrows = self.definitionView.selectionModel().selectedRows() if len(selrows) != 0: self.model.deleteEntry(selrows[0].row()) def slotUp(self): """Move item up list.""" selrows = self.definitionView.selectionModel().selectedRows() if len(selrows) != 0: self.model.moveUpEntry(selrows[0].row()) def slotDown(self): """Move item down list.""" selrows = self.definitionView.selectionModel().selectedRows() if len(selrows) != 0: self.model.moveDownEntry(selrows[0].row()) def slotSave(self): """Save entries.""" filename = self.parent()._fileSaveDialog( 'vsz', 'Veusz document', 'Save custom definitions') if filename: try: f = open(filename, 'w') self.document.saveCustomFile(f) f.close() self.recentButton.addFile(filename) except EnvironmentError, e: qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to save '%s'\n\n%s" % (filename, e.strerror)) def slotLoad(self): """Load entries.""" filename = self.parent()._fileOpenDialog( 'vsz', 'Veusz document', 'Load custom definitions') if filename: try: self.loadFile(filename) except EnvironmentError, e: qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to load '%s'\n\n%s" % (filename, e.strerror)) else: # add to recent file list self.recentButton.addFile(filename) veusz-1.15/dialogs/dataeditdialog.py0000644002344000001440000004031711734662204017537 0ustar jssusers00000000000000# data editting dialog # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Module for implementing dialog box for viewing/editing data.""" import bisect import veusz.qtall as qt4 import veusz.document as document import veusz.utils as utils from veusz.qtwidgets.datasetbrowser import DatasetBrowser from veuszdialog import VeuszDialog # register function to dataset class to edit dataset recreate_register = {} class DatasetTableModel1D(qt4.QAbstractTableModel): """Provides access to editing and viewing of datasets.""" def __init__(self, parent, document, datasetname): qt4.QAbstractTableModel.__init__(self, parent) self.document = document self.dsname = datasetname self.connect(document, qt4.SIGNAL('sigModified'), self.slotDocumentModified) def rowCount(self, parent): """Return number of rows.""" try: return len(self.document.data[self.dsname].data)+1 except (KeyError, AttributeError): return 0 def slotDocumentModified(self): """Called when document modified.""" self.emit( qt4.SIGNAL('layoutChanged()') ) def columnCount(self, parent): """Return number of columns.""" try: ds = self.document.data[self.dsname] except KeyError: return 0 return len( ds.column_descriptions ) def data(self, index, role): """Return data associated with column given.""" # get dataset ds = self.document.data[self.dsname] if ds is not None: # select correct part of dataset data = getattr(ds, ds.columns[index.column()]) if ds is not None and data is not None and role == qt4.Qt.DisplayRole: # blank row at end of data if index.row() == len(data): return qt4.QVariant() # convert data to QVariant d = data[index.row()] return ds.uiDataItemToQVariant(d) # empty entry return qt4.QVariant() def headerData(self, section, orientation, role): """Return headers at top.""" try: ds = self.document.data[self.dsname] except KeyError: return qt4.QVariant() if role == qt4.Qt.DisplayRole: if orientation == qt4.Qt.Horizontal: # column names return qt4.QVariant( ds.column_descriptions[section] ) else: if section == len(ds.data): return "+" # return row numbers return qt4.QVariant(section+1) return qt4.QVariant() def flags(self, index): """Update flags to say that items are editable.""" if not index.isValid(): return qt4.Qt.ItemIsEnabled else: return qt4.QAbstractTableModel.flags(self, index) | qt4.Qt.ItemIsEditable def removeRows(self, row, count): """Remove rows.""" self.document.applyOperation( document.OperationDatasetDeleteRow(self.dsname, row, count)) def insertRows(self, row, count): """Remove rows.""" self.document.applyOperation( document.OperationDatasetInsertRow(self.dsname, row, count)) def setData(self, index, value, role): """Called to set the data.""" if not index.isValid() or role != qt4.Qt.EditRole: return False row = index.row() column = index.column() ds = self.document.data[self.dsname] data = getattr(ds, ds.columns[index.column()]) # add new column if necessary ops = document.OperationMultiple([], descr='add value') if data is None: ops.addOperation( document.OperationDatasetAddColumn(self.dsname, ds.columns[column])) # add a row if necessary if row == len(ds.data): ops.addOperation( document.OperationDatasetInsertRow(self.dsname, row, 1)) # update if conversion okay try: val = ds.uiConvertToDataItem( value.toString() ) except ValueError: return False ops.addOperation( document.OperationDatasetSetVal(self.dsname, ds.columns[column], row, val)) self.document.applyOperation(ops) return True class DatasetTableModel2D(qt4.QAbstractTableModel): """A 2D dataset model.""" def __init__(self, parent, document, datasetname): qt4.QAbstractTableModel.__init__(self, parent) self.document = document self.dsname = datasetname self.connect(document, qt4.SIGNAL('sigModified'), self.slotDocumentModified) def rowCount(self, parent): ds = self.document.data[self.dsname].data if ds is not None: return ds.shape[0] else: return 0 def columnCount(self, parent): ds = self.document.data[self.dsname].data if ds is not None: return ds.shape[1] else: return 0 def data(self, index, role): if role == qt4.Qt.DisplayRole: # get data (note y is reversed, sigh) ds = self.document.data[self.dsname].data if ds is not None: num = ds[ds.shape[0]-index.row()-1, index.column()] return qt4.QVariant( float(num) ) return qt4.QVariant() def headerData(self, section, orientation, role): """Return headers at top.""" if role == qt4.Qt.DisplayRole: ds = self.document.data[self.dsname] if ds is not None: # return a number for the top left of the cell if orientation == qt4.Qt.Horizontal: r = ds.xrange num = ds.data.shape[1] else: r = ds.yrange r = (r[1], r[0]) # swap (as y reversed) num = ds.data.shape[0] val = (r[1]-r[0])/num*(section+0.5)+r[0] return qt4.QVariant( '%g' % val ) return qt4.QVariant() def flags(self, index): """Update flags to say that items are editable.""" if not index.isValid(): return qt4.Qt.ItemIsEnabled else: return qt4.QAbstractTableModel.flags(self, index) | qt4.Qt.ItemIsEditable def slotDocumentModified(self): """Called when document modified.""" self.emit( qt4.SIGNAL('layoutChanged()') ) def setData(self, index, value, role): """Called to set the data.""" if not index.isValid() or role != qt4.Qt.EditRole: return False ds = self.document.data[self.dsname] row = ds.data.shape[0]-index.row()-1 col = index.column() # update if conversion okay try: val = ds.uiConvertToDataItem( value.toString() ) except ValueError: return False op = document.OperationDatasetSetVal2D( self.dsname, row, col, val) self.document.applyOperation(op) return True class DataEditDialog(VeuszDialog): """Dialog for editing and rearranging data sets.""" def __init__(self, parent, document): VeuszDialog.__init__(self, parent, 'dataedit.ui') self.document = document # set up dataset list self.dsbrowser = DatasetBrowser(document, parent, parent) self.splitter.insertWidget(0, self.dsbrowser) # actions for data table for text, slot in ( ('Copy', self.slotCopy), ('Delete row', self.slotDeleteRow), ('Insert row', self.slotInsertRow), ): act = qt4.QAction(text, self) self.connect(act, qt4.SIGNAL('triggered()'), slot) self.datatableview.addAction(act) self.datatableview.setContextMenuPolicy( qt4.Qt.ActionsContextMenu ) # layout edit dialog improvement self.splitter.setStretchFactor(0, 3) self.splitter.setStretchFactor(1, 4) # don't want text to look editable or special self.linkedlabel.setFrameShape(qt4.QFrame.NoFrame) self.linkedlabel.viewport().setBackgroundRole(qt4.QPalette.Window) # document changes self.connect(document, qt4.SIGNAL('sigModified'), self.slotDocumentModified) # select first item, if any or initialise if none if len(self.document.data) > 0: self.selectDataset( sorted(self.document.data.keys())[0] ) else: self.slotDatasetSelected("") self.connect(self.dsbrowser.navtree, qt4.SIGNAL("selecteditem"), self.slotDatasetSelected) # connect buttons for btn, slot in ( (self.deletebutton, self.slotDatasetDelete), (self.unlinkbutton, self.slotDatasetUnlink), (self.duplicatebutton, self.slotDatasetDuplicate), (self.importbutton, self.slotDatasetImport), (self.createbutton, self.slotDatasetCreate), (self.editbutton, self.slotDatasetEdit), ): self.connect(btn, qt4.SIGNAL('clicked()'), slot) # menu for new button self.newmenu = qt4.QMenu() for text, slot in ( ('Numerical dataset', self.slotNewNumericalDataset), ('Text dataset', self.slotNewTextDataset), ('Date/time dataset', self.slotNewDateDataset) ): a = self.newmenu.addAction(text) self.connect(a, qt4.SIGNAL('triggered()'), slot) self.newbutton.setMenu(self.newmenu) def slotDatasetSelected(self, name): """Called when a new dataset is selected.""" # FIXME: Make readonly models readonly!! model = None if name: # get selected dataset ds = self.document.data[name] # make model for dataset if ds.dimensions == 1: model = DatasetTableModel1D(self, self.document, name) elif ds.dimensions == 2: model = DatasetTableModel2D(self, self.document, name) # disable context menu if no menu for a in self.datatableview.actions(): a.setEnabled(model is not None) self.datatableview.setModel(model) self.setUnlinkState() def setUnlinkState(self): """Enable the unlink button correctly.""" # get dataset dsname = self.getSelectedDataset() try: ds = self.document.data[dsname] unlink = ds.canUnlink() linkinfo = ds.linkedInformation() except KeyError: ds = None unlink = False linkinfo = "" self.editbutton.setVisible(type(ds) in recreate_register) self.unlinkbutton.setEnabled(unlink) self.linkedlabel.setText(linkinfo) self.deletebutton.setEnabled(ds is not None) self.duplicatebutton.setEnabled(ds is not None) def slotDocumentModified(self): """Set unlink status when document modified.""" self.setUnlinkState() def getSelectedDataset(self): """Return the selected dataset.""" return self.dsbrowser.navtree.getSelectedDataset() def selectDataset(self, dsname): """Select dataset with name given.""" self.dsbrowser.navtree.selectDataset(dsname) self.slotDatasetSelected(dsname) def slotDatasetDelete(self): """Delete selected dataset.""" self.document.applyOperation( document.OperationDatasetDelete(self.getSelectedDataset()) ) def slotDatasetUnlink(self): """Allow user to remove link to file or other datasets.""" datasetname = self.getSelectedDataset() d = self.document.data[datasetname] if d.linked is not None: op = document.OperationDatasetUnlinkFile(datasetname) else: op = document.OperationDatasetUnlinkRelation(datasetname) self.document.applyOperation(op) def slotDatasetDuplicate(self): """Duplicate selected dataset.""" datasetname = self.getSelectedDataset() if datasetname is not None: # generate new name for dataset newname = datasetname + '_copy' index = 2 while newname in self.document.data: newname = '%s_copy_%i' % (datasetname, index) index += 1 self.document.applyOperation( document.OperationDatasetDuplicate(datasetname, newname)) def slotDatasetImport(self): """Show import dialog.""" self.mainwindow.slotDataImport() def slotDatasetCreate(self): """Show dataset creation dialog.""" self.mainwindow.slotDataCreate() def slotDatasetEdit(self): """Reload dataset into dataset creation dialog.""" dsname = self.getSelectedDataset() if dsname: dataset = self.document.data[dsname] recreate_register[type(dataset)](self.mainwindow, self.document, dataset, dsname) def slotCopy(self): """Copy text from selection.""" # get list of selected rows and columns selmodel = self.datatableview.selectionModel() model = self.datatableview.model() indices = [] for index in selmodel.selectedIndexes(): indices.append( (index.row(), index.column()) ) indices.sort() # build up text stream for copying to clipboard lines = [] rowitems = [] lastrow = -1 for row, column in indices: if row != lastrow: if rowitems: # items are tab separated lines.append( '\t'.join(rowitems) ) rowitems = [] lastrow = row rowitems.append( unicode( model.createIndex(row, column).data().toString()) ) if rowitems: lines.append( '\t'.join(rowitems) ) lines.append('') # blank line at end lines = '\n'.join(lines) # put text on clipboard qt4.QApplication.clipboard().setText(lines) def slotDeleteRow(self): """Delete the current row.""" self.datatableview.model().removeRows( self.datatableview.currentIndex().row(), 1) def slotInsertRow(self): """Insert a new row.""" self.datatableview.model().insertRows( self.datatableview.currentIndex().row(), 1) def slotNewNumericalDataset(self): """Add new value dataset.""" self.newDataset( document.Dataset(data=[0.]) ) def slotNewTextDataset(self): """Add new text dataset.""" self.newDataset( document.DatasetText(data=['']) ) def slotNewDateDataset(self): """Add new date dataset.""" self.newDataset( document.DatasetDateTime(data=[]) ) def newDataset(self, ds): """Add new dataset to document.""" # get a name for dataset name = 'new dataset' if name in self.document.data: count = 1 while name in self.document.data: name = 'new dataset %i' % count count += 1 # add new dataset self.document.applyOperation( document.OperationDatasetSet(name, ds)) self.dsbrowser.selectDataset(name) veusz-1.15/dialogs/reloaddata.py0000644002344000001440000001152511734662204016677 0ustar jssusers00000000000000# data reload dialog # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Dialog for reloading linked data.""" import os import veusz.qtall as qt4 import veusz.document as document import veusz.utils as utils from veuszdialog import VeuszDialog class ReloadData(VeuszDialog): """Dialog for reloading linked datasets.""" def __init__(self, document, parent, filenames=None): """Initialise the dialog. document: veusz document parent: parent window filenames: if a set() only reload from these filenames """ VeuszDialog.__init__(self, parent, 'reloaddata.ui') self.document = document self.filenames = filenames # update on reloading self.reloadct = 1 # get a record of names, dates and sizes of files linked self.filestats = self.statLinkedFiles() # actually reload the data (and show the user) self.reloadData() # if interval changed or enabled update timer self.connect(self.intervalCheck, qt4.SIGNAL('clicked()'), self.intervalUpdate) self.connect(self.intervalTime, qt4.SIGNAL('valueChanged(int)'), self.intervalUpdate) # timer to reload data self.intervalTimer = qt4.QTimer() self.connect(self.intervalTimer, qt4.SIGNAL('timeout()'), self.reloadIfChanged) # manual reload self.reloadbutton = self.buttonBox.addButton( "&Reload again", qt4.QDialogButtonBox.ApplyRole) self.connect(self.reloadbutton, qt4.SIGNAL('clicked()'), self.reloadData) # close by default, not reload self.buttonBox.button(qt4.QDialogButtonBox.Close).setDefault(True) def statLinkedFiles(self): """Stat linked files. Returns a list of (filename, mtime, size) """ files = [] for lf in self.document.getLinkedFiles(): filename = lf.filename try: s = os.stat(filename) files.append( (filename, s.st_mtime, s.st_size) ) except OSError: pass files.sort() return files def intervalUpdate(self, *args): """Reload at intervals option toggled.""" if self.intervalCheck.isChecked(): self.intervalTimer.start( self.intervalTime.value()*1000 ) else: self.intervalTimer.stop() def reloadIfChanged(self): """Reload linked data if it has changed.""" newstat = self.statLinkedFiles() if newstat != self.filestats: self.filestats = newstat self.reloadData() def reloadData(self): """Reload linked data. Show the user what was done.""" text = '' self.document.suspendUpdates() try: # try to reload the datasets datasets, errors = self.document.reloadLinkedDatasets( self.filenames) # show errors in read data for var, count in errors.items(): if count != 0: text += ( '%i conversions failed for dataset "%s"\n' % (count, var) ) # show successes if len(datasets) != 0: text += 'Reloaded (%i)\n' % self.reloadct self.reloadct += 1 for var in datasets: descr = self.document.data[var].description() if descr: text += ' %s\n' % descr else: text += ' %s\n' % var except EnvironmentError, e: text = 'Error reading file:\n' + unicode(e) except document.DescriptorError: text = 'Could not interpret descriptor. Reload failed.' except: self.document.enableUpdates() raise if text == '': text = 'Nothing to do. No linked datasets.' self.document.enableUpdates() self.outputedit.setPlainText(text) veusz-1.15/dialogs/import_plugins.ui0000644002344000001440000000323511734662204017636 0ustar jssusers00000000000000 plugintab 0 0 282 346 Plugin: Description goes here true Preview true Parameters veusz-1.15/dialogs/errorloading.ui0000644002344000001440000000477411734662204017263 0ustar jssusers00000000000000 Dialog Qt::WindowModal 0 0 488 229 Error opening file icon Veusz could not open the file '%s'. The following error occured: true 75 true TextLabel true Qt::Horizontal QDialogButtonBox::Ok true buttonBox accepted() Dialog accept() 248 254 157 274 buttonBox rejected() Dialog reject() 316 260 286 274 veusz-1.15/dialogs/capture.ui0000644002344000001440000002036211734662204016226 0ustar jssusers00000000000000 CaptureDialog 0 0 392 507 Capture data - Veusz &Datasets: descriptorEdit Enter a descriptor to describe the format of the incoming data, e.g. "x,+,- y,+-" (see the Data->Import dialog box for details) true Capture method &File or named pipe true Filename: filenameEdit 0 0 Browse for file ... false Connect to &socket Host: hostEdit Port: TCP port to connect to E&xternal program Command line: commandLineEdit Stop after Clicking fi&nish button true Number of input &lines Total &time period (s) 0 0 Update document at intervals (s) 0 0 Only retain latest N values Maximum number of values to retain QDialogButtonBox::Close HistoryCombo QComboBox
    historycombo.h
    HistoryCheck QCheckBox
    historycheck.h
    buttonBox rejected() CaptureDialog close() 195 461 180 243
    veusz-1.15/dialogs/preferences.py0000644002344000001440000002541211734662204017100 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting import veusz.utils as utils from veuszdialog import VeuszDialog class PreferencesDialog(VeuszDialog): """Preferences dialog.""" def __init__(self, mainwindow): """Setup dialog.""" VeuszDialog.__init__(self, mainwindow, 'preferences.ui') self.plotwindow = mainwindow.plot # for ease of use setdb = setting.settingdb # view settings self.antialiasCheck.setChecked( setdb['plot_antialias'] ) self.englishCheck.setChecked( setdb['ui_english'] ) for intv in self.plotwindow.updateintervals: self.intervalCombo.addItem(intv[1]) index = [i[0] for i in self.plotwindow.updateintervals].index( setdb['plot_updatepolicy']) self.intervalCombo.setCurrentIndex(index) self.threadSpinBox.setValue( setdb['plot_numthreads'] ) # disable thread option if not supported if not qt4.QFontDatabase.supportsThreadedFontRendering(): self.threadSpinBox.setEnabled(False) self.threadSpinBox.setToolTip("Disabled because of lack of " "threaded drawing support") # use cwd for file dialogs self.cwdCheck.setChecked( setdb['dirname_usecwd'] ) # set icon size self.iconSizeCombo.setCurrentIndex( self.iconSizeCombo.findText( str(setdb['toolbar_size']))) # set export dpi dpis = ('75', '90', '100', '150', '200', '300') self.exportDPI.addItems(dpis) self.exportDPIPDF.addItems(dpis) self.exportDPI.setValidator( qt4.QIntValidator(10, 10000, self) ) self.exportDPI.setEditText( str(setdb['export_DPI']) ) self.exportDPIPDF.setValidator( qt4.QIntValidator(10, 10000, self) ) self.exportDPIPDF.setEditText( str(setdb['export_DPI_PDF']) ) self.exportSVGTextAsText.setChecked( setdb['export_SVG_text_as_text'] ) # set export antialias self.exportAntialias.setChecked( setdb['export_antialias']) # quality of jpeg export self.exportQuality.setValue( setdb['export_quality'] ) # changing background color of bitmaps self.connect( self.exportBackgroundButton, qt4.SIGNAL('clicked()'), self.slotExportBackgroundChanged ) self.updateExportBackground(setdb['export_background']) # set color setting self.exportColor.setCurrentIndex( {True:0, False:1}[setdb['export_color']]) # default stylesheet self.styleLineEdit.setText(setdb['stylesheet_default']) self.connect( self.styleBrowseButton, qt4.SIGNAL('clicked()'), self.styleBrowseClicked ) # default custom settings self.customLineEdit.setText(setdb['custom_default']) self.connect( self.customBrowseButton, qt4.SIGNAL('clicked()'), self.customBrowseClicked ) # for plugins plugins = list( setdb.get('plugins', []) ) self.pluginmodel = qt4.QStringListModel(plugins) self.pluginList.setModel(self.pluginmodel) self.connect( self.pluginAddButton, qt4.SIGNAL('clicked()'), self.pluginAddClicked ) self.connect( self.pluginRemoveButton, qt4.SIGNAL('clicked()'), self.pluginRemoveClicked ) # specifics for color tab self.setupColorTab() def setupColorTab(self): """Initialise color tab this makes a grid of controls for each color consisting of label, isdefault check and change color button.""" self.chosencolors = {} self.colorbutton = {} self.colordefaultcheck = {} layout = qt4.QGridLayout() setdb = setting.settingdb for row, colname in enumerate(setdb.colors): isdefault, colval = setting.settingdb['color_%s' % colname] self.chosencolors[colname] = qt4.QColor(colval) # label name, tooltip = setdb.color_names[colname] label = qt4.QLabel(name) label.setToolTip(tooltip) layout.addWidget(label, row, 0) # is default check defcheck = qt4.QCheckBox("Default") defcheck.setToolTip( "Use the default color instead of the one chosen here") layout.addWidget(defcheck, row, 1) self.colordefaultcheck[colname] = defcheck defcheck.setChecked(isdefault) # button button = qt4.QPushButton() # connect button to method to change color def clicked(color=colname): self.colorButtonClickedSlot(color) self.connect(button, qt4.SIGNAL('clicked()'), clicked) layout.addWidget(button, row, 2) self.colorbutton[colname] = button self.colorGroup.setLayout(layout) self.updateButtonColors() def colorButtonClickedSlot(self, color): """Open color dialog if color button clicked.""" retcolor = qt4.QColorDialog.getColor( self.chosencolors[color], self ) if retcolor.isValid(): self.chosencolors[color] = retcolor self.updateButtonColors() def updateButtonColors(self): """Update color icons on color buttons.""" for name, val in self.chosencolors.iteritems(): pixmap = qt4.QPixmap(16, 16) pixmap.fill(val) self.colorbutton[name].setIcon( qt4.QIcon(pixmap) ) def updateExportBackground(self, colorname): """Update color on export background.""" pixmap = qt4.QPixmap(16, 16) col = utils.extendedColorToQColor(colorname) pixmap.fill(col) # update button (storing color in button itself - what fun!) self.exportBackgroundButton.setIcon(qt4.QIcon(pixmap)) self.exportBackgroundButton.iconcolor = colorname def slotExportBackgroundChanged(self): """Button clicked to change background.""" color = qt4.QColorDialog.getColor( utils.extendedColorToQColor(self.exportBackgroundButton.iconcolor), self, "Choose color", qt4.QColorDialog.ShowAlphaChannel ) if color.isValid(): self.updateExportBackground( utils.extendedColorFromQColor(color) ) def accept(self): """Keep settings if okay pressed.""" qt4.QDialog.accept(self) # view settings setdb = setting.settingdb setdb['plot_updatepolicy'] = ( self.plotwindow.updateintervals[self.intervalCombo.currentIndex()][0] ) setdb['plot_antialias'] = self.antialiasCheck.isChecked() setdb['ui_english'] = self.englishCheck.isChecked() setdb['plot_numthreads'] = self.threadSpinBox.value() # use cwd setdb['dirname_usecwd'] = self.cwdCheck.isChecked() # update icon size if necessary iconsize = int( self.iconSizeCombo.currentText() ) if iconsize != setdb['toolbar_size']: setdb['toolbar_size'] = iconsize for widget in self.parent().children(): # find toolbars if isinstance(widget, qt4.QToolBar): widget.setIconSize( qt4.QSize(iconsize, iconsize) ) # update dpi if possible # FIXME: requires some sort of visual notification of validator for cntrl, setn in ((self.exportDPI, 'export_DPI'), (self.exportDPIPDF, 'export_DPI_PDF')): try: text = cntrl.currentText() valid = cntrl.validator().validate(text, 0)[0] if valid == qt4.QValidator.Acceptable: setdb[setn] = int(text) except ValueError: pass # export settings setdb['export_antialias'] = self.exportAntialias.isChecked() setdb['export_quality'] = self.exportQuality.value() setdb['export_color'] = {0: True, 1: False}[ self.exportColor.currentIndex()] setdb['export_background'] = self.exportBackgroundButton.iconcolor setdb['export_SVG_text_as_text'] = self.exportSVGTextAsText.isChecked() # new document settings setdb['stylesheet_default'] = unicode(self.styleLineEdit.text()) setdb['custom_default'] = unicode(self.customLineEdit.text()) # colors for name, color in self.chosencolors.iteritems(): isdefault = self.colordefaultcheck[name].isChecked() colorname = unicode(color.name()) setdb['color_' + name] = (isdefault, colorname) # plugins plugins = [unicode(x) for x in self.pluginmodel.stringList()] setdb['plugins'] = plugins self.plotwindow.updatePlotSettings() # write settings out now, rather than wait until the end setdb.writeSettings() def styleBrowseClicked(self): """Browse for a stylesheet.""" filename = self.parent()._fileOpenDialog( 'vst', 'Veusz stylesheet', 'Choose stylesheet') if filename: self.styleLineEdit.setText(filename) def customBrowseClicked(self): """Browse for a custom definitons.""" filename = self.parent()._fileOpenDialog( 'vsz', 'Veusz documents', 'Choose custom definitons') if filename: self.customLineEdit.setText(filename) def pluginAddClicked(self): """Add a new plugin.""" filename = self.parent()._fileOpenDialog( 'py', 'Python scripts', 'Choose plugin') if filename: self.pluginmodel.insertRows(0, 1) self.pluginmodel.setData( self.pluginmodel.index(0), qt4.QVariant(filename) ) def pluginRemoveClicked(self): """Remove selected plugin.""" sel = self.pluginList.selectionModel().currentIndex() if sel.isValid(): self.pluginmodel.removeRow( sel.row() ) veusz-1.15/dialogs/reloaddata.ui0000644002344000001440000000613211734662204016662 0ustar jssusers00000000000000 ReloadDialog 0 0 587 381 Reload data - Veusz Results of reload data Qt::Horizontal 0 0 Reload if files change every false s 1 99999 5 Sans Serif 9 50 false false false false QTextEdit::NoWrap true QDialogButtonBox::Close buttonBox rejected() ReloadDialog close() 20 20 20 20 intervalCheck toggled(bool) intervalTime setEnabled(bool) 20 20 20 20 veusz-1.15/dialogs/datacreate2d.ui0000644002344000001440000001325011734662204017104 0ustar jssusers00000000000000 Dialog 0 0 638 396 Create 2D dataset &Name namecombo 0 0 true Method of creating dataset From x, y and z values based on &1D datasets or expressions From expression based on existing &2D dataset(s) From &function of x and y Values Enter range of values in form min:max:step or expression &X expression or range xexprcombo 0 0 true &Y expression or range yexprcombo 0 0 true &Z expression zexprcombo 0 0 true &Link this dataset to these expressions true QDialogButtonBox::Close buttonBox rejected() Dialog close() 318 383 318 199 veusz-1.15/dialogs/datacreate2d.py0000644002344000001440000001700411734662204017120 0ustar jssusers00000000000000# Copyright (C) 2007 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Dataset creation dialog for 2d data.""" import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document from veuszdialog import VeuszDialog def checkGetStep(text): """Check step syntax is okay. Syntax is min:max:stepsize Returns None if fails """ parts = text.split(':') if len(parts) == 3: try: return tuple([float(x) for x in parts]) except ValueError: pass return None class DataCreate2DDialog(VeuszDialog): def __init__(self, parent, document): """Initialise dialog with document.""" VeuszDialog.__init__(self, parent, 'datacreate2d.ui') self.document = document self.createbutton = self.buttonBox.addButton( "C&reate", qt4.QDialogButtonBox.ApplyRole ) self.connect( self.createbutton, qt4.SIGNAL('clicked()'), self.createButtonClickedSlot ) self.connect( self.fromxyfunc, qt4.SIGNAL('toggled(bool)'), self.fromxyfuncSlot ) self.connect( self.fromxyzexpr, qt4.SIGNAL('toggled(bool)'), self.fromxyzexprSlot ) self.connect( self.from2dexpr, qt4.SIGNAL('toggled(bool)'), self.from2dexprSlot ) self.connect(document, qt4.SIGNAL('sigModified'), self.updateDatasetLists) for combo in (self.namecombo, self.xexprcombo, self.yexprcombo, self.zexprcombo): self.connect(combo, qt4.SIGNAL('editTextChanged(const QString&)'), self.enableDisableCreate) self.fromxyzexpr.toggle() self.enableDisableCreate() # change mode according to radio pressed def fromxyfuncSlot(self, checked): self.mode = 'xyfunc' if checked: self.updateDatasetLists() def fromxyzexprSlot(self, checked): self.mode = 'xyzexpr' if checked: self.updateDatasetLists() def from2dexprSlot(self, checked): self.mode = '2dexpr' if checked: self.updateDatasetLists() def escapeDatasets(self, dsnames): """Escape dataset names if they are not typical python ones.""" for i in xrange(len(dsnames)): if not utils.validPythonIdentifier(dsnames[i]): dsnames[i] = '`%s`' % dsnames[i] def updateDatasetLists(self): """Update controls depending on selected mode.""" # get list of 1d and 2d numeric datasets datasets = [[],[]] for name, ds in self.document.data.iteritems(): if ds.datatype == 'numeric': datasets[ds.dimensions-1].append(name) datasets[0].sort() datasets[1].sort() # make sure names are escaped if they have funny characters self.escapeDatasets(datasets[0]) self.escapeDatasets(datasets[1]) # help the user by listing existing datasets utils.populateCombo(self.namecombo, datasets[0]) if self.mode == 'xyzexpr': # enable everything for combo in self.xexprcombo, self.yexprcombo, self.zexprcombo: combo.setDisabled(False) utils.populateCombo(combo, datasets[0]) elif self.mode == '2dexpr': # only enable the z expression button self.xexprcombo.setDisabled(True) self.yexprcombo.setDisabled(True) self.zexprcombo.setDisabled(False) utils.populateCombo(self.zexprcombo, datasets[1]) else: # enable everything for combo in self.xexprcombo, self.yexprcombo, self.zexprcombo: combo.setDisabled(False) # put in some examples to help the the user utils.populateCombo(self.xexprcombo, ['0:10:0.1']) utils.populateCombo(self.yexprcombo, ['0:10:0.1']) utils.populateCombo(self.zexprcombo, ['x+y']) def enableDisableCreate(self): """Enable or disable create button.""" # get contents of combo boxes text = {} for name in ('xexpr', 'yexpr', 'zexpr', 'name'): text[name] = unicode(getattr(self, name+'combo').currentText()).strip() disable = False # need name and zexpr disable = disable or not text['name'] or not text['zexpr'] if self.mode == 'xyzexpr': # need x and yexpr disable = disable or not text['xexpr'] or not text['yexpr'] elif self.mode == '2dexpr': # nothing else pass elif self.mode == 'xyfunc': # need x and yexpr in special step format min:max:step disable = disable or ( checkGetStep(text['xexpr']) is None or checkGetStep(text['yexpr']) is None ) # finally check button self.createbutton.setDisabled(disable) def createButtonClickedSlot(self): """Create button pressed.""" text = {} for name in ('xexpr', 'yexpr', 'zexpr', 'name'): text[name] = unicode(getattr(self, name+'combo').currentText()).strip() link = self.linkcheckbox.checkState() == qt4.Qt.Checked if self.mode == 'xyzexpr': # build operation op = document.OperationDataset2DCreateExpressionXYZ( text['name'], text['xexpr'], text['yexpr'], text['zexpr'], link) elif self.mode == '2dexpr': op = document.OperationDataset2DCreateExpression( text['name'], text['zexpr'], link) elif self.mode == 'xyfunc': xstep = checkGetStep(text['xexpr']) ystep = checkGetStep(text['yexpr']) # build operation op = document.OperationDataset2DXYFunc( text['name'], xstep, ystep, text['zexpr'], link) # apply operation, catching evaluation errors try: # check expression is okay op.validateExpression(self.document) # try to make dataset self.document.applyOperation(op) # forces an evaluation self.document.data[text['name']].data except (document.CreateDatasetException, document.DatasetException), e: # error in evaluation or something qt4.QMessageBox.warning(self, "Veusz", 'Dataset creation failed: %s' % unicode(e)) msg = "Failed to create dataset '%s'" % text['name'] else: msg = "Created dataset '%s'" % text['name'] self.notifylabel.setText(msg) qt4.QTimer.singleShot(4000, self.notifylabel.clear) veusz-1.15/dialogs/exceptionlist.ui0000644002344000001440000000664611734662204017466 0ustar jssusers00000000000000 exceptiondialog 0 0 576 431 Problem occured - Veusz erroricon A problem occured within Veusz. This means you have encountered a bug. You can help improve Veusz by sending a bug report to the developers. It doesn't take very long to submit a problem! true TextLabel true Details true Qt::Horizontal 131 31 &Send report &Ignore this time Ignore in &session okButton clicked() exceptiondialog accept() 278 253 96 254 cancelButton clicked() exceptiondialog reject() 369 253 179 282 veusz-1.15/dialogs/importdialog.py0000644002344000001440000011133511734662204017271 0ustar jssusers00000000000000# data import dialog # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Module for implementing dialog boxes for importing data in Veusz.""" import os.path import re import csv import sys import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils import veusz.plugins as plugins import exceptiondialog from veuszdialog import VeuszDialog class ImportTab(qt4.QWidget): """Tab for a particular import type.""" resource = '' def __init__(self, importdialog, *args): """Initialise dialog. importdialog is the import dialog itself.""" qt4.QWidget.__init__(self, *args) self.dialog = importdialog self.uiloaded = False def loadUi(self): """Load up UI file.""" qt4.loadUi(os.path.join(utils.veuszDirectory, 'dialogs', self.resource), self) self.uiloaded = True def reset(self): """Reset controls to initial conditions.""" pass def doPreview(self, filename, encoding): """Update the preview window, returning whether import should be attempted.""" pass def doImport(self, filename, linked, encoding, prefix, suffix, tags): """Do the import iteself.""" pass def okToImport(self): """Secondary check (after preview) for enabling import button.""" return True def isFiletypeSupported(self, ftype): """Is the filetype supported by this tab?""" return False def useFiletype(self, ftype): """If the tab can do something with the selected filetype, update itself.""" pass class ImportTabStandard(ImportTab): """Standard import format tab.""" resource = 'import_standard.ui' def loadUi(self): """Load widget and setup controls.""" ImportTab.loadUi(self) self.connect( self.helpbutton, qt4.SIGNAL('clicked()'), self.slotHelp ) self.blockcheckbox.default = False self.ignoretextcheckbox.default = True def reset(self): """Reset controls.""" self.blockcheckbox.setChecked(False) self.ignoretextcheckbox.setChecked(True) self.descriptoredit.setEditText("") def slotHelp(self): """Asked for help.""" d = VeuszDialog(self.dialog.mainwindow, 'importhelp.ui') self.dialog.mainwindow.showDialog(d) def doPreview(self, filename, encoding): """Standard preview - show start of text.""" try: ifile = utils.openEncoding(filename, encoding) text = ifile.read(4096)+'\n' if len(ifile.read(1)) != 0: # if there is remaining data add ... text += '...\n' self.previewedit.setPlainText(text) return True except (UnicodeError, EnvironmentError): self.previewedit.setPlainText('') return False def doImport(self, doc, filename, linked, encoding, prefix, suffix, tags): """Standard Veusz importing.""" # convert controls to values descriptor = unicode( self.descriptoredit.text() ) useblocks = self.blockcheckbox.isChecked() ignoretext = self.ignoretextcheckbox.isChecked() params = document.ImportParamsSimple( descriptor=descriptor, filename=filename, useblocks=useblocks, linked=linked, prefix=prefix, suffix=suffix, tags=tags, ignoretext=ignoretext, encoding=encoding, ) try: # construct operation. this checks the descriptor. op = document.OperationDataImport(params) except document.DescriptorError: qt4.QMessageBox.warning(self, "Veusz", "Cannot interpret descriptor") return # actually import the data doc.applyOperation(op) # tell the user what happened # failures in conversion lines = [] for var, count in op.outinvalids.iteritems(): if count != 0: lines.append('%i conversions failed for dataset "%s"' % (count, var)) if len(lines) != 0: lines.append('') lines += self.dialog.retnDatasetInfo(op.outdatasets, linked, filename) self.previewedit.setPlainText( '\n'.join(lines) ) def isFiletypeSupported(self, ftype): """Is the filetype supported by this tab?""" return ftype in ('.dat', '.txt') class ImportTabCSV(ImportTab): """For importing data from CSV files.""" resource = 'import_csv.ui' def loadUi(self): """Load user interface and setup panel.""" ImportTab.loadUi(self) self.connect( self.csvhelpbutton, qt4.SIGNAL('clicked()'), self.slotHelp ) self.connect( self.csvdelimitercombo, qt4.SIGNAL('editTextChanged(const QString&)'), self.dialog.slotUpdatePreview ) self.connect( self.csvtextdelimitercombo, qt4.SIGNAL('editTextChanged(const QString&)'), self.dialog.slotUpdatePreview ) self.csvdelimitercombo.default = [ ',', '{tab}', '{space}', '|', ':', ';'] self.csvtextdelimitercombo.default = ['"', "'"] self.csvdatefmtcombo.default = [ 'YYYY-MM-DD|T|hh:mm:ss', 'DD/MM/YY| |hh:mm:ss', 'M/D/YY| |hh:mm:ss' ] self.csvnumfmtcombo.defaultlist = ['System', 'English', 'European'] self.csvheadermodecombo.defaultlist = ['Multiple', '1st row', 'None'] def reset(self): """Reset controls.""" self.csvdelimitercombo.setEditText(",") self.csvtextdelimitercombo.setEditText('"') self.csvdirectioncombo.setCurrentIndex(0) self.csvignorehdrspin.setValue(0) self.csvignoretopspin.setValue(0) self.csvblanksdatacheck.setChecked(False) self.csvnumfmtcombo.setCurrentIndex(0) self.csvdatefmtcombo.setEditText( document.ImportParamsCSV.defaults['dateformat']) self.csvheadermodecombo.setCurrentIndex(0) def slotHelp(self): """Asked for help.""" d = VeuszDialog(self.dialog.mainwindow, 'importhelpcsv.ui') self.dialog.mainwindow.showDialog(d) def getCSVDelimiter(self): """Get CSV delimiter, converting friendly names.""" delim = str( self.csvdelimitercombo.text() ) if delim == '{space}': delim = ' ' elif delim == '{tab}': delim = '\t' return delim def doPreview(self, filename, encoding): """CSV preview - show first few rows""" t = self.previewtablecsv t.verticalHeader().show() # restore from a previous import t.horizontalHeader().show() t.horizontalHeader().setStretchLastSection(False) t.clear() t.setColumnCount(0) t.setRowCount(0) try: delimiter = self.getCSVDelimiter() textdelimiter = str(self.csvtextdelimitercombo.currentText()) except UnicodeEncodeError: # need to be real str not unicode return False # need to be single character if len(delimiter) != 1 or len(textdelimiter) != 1: return False try: reader = utils.UnicodeCSVReader( filename, delimiter=delimiter, quotechar=textdelimiter, encoding=encoding ) # construct list of rows rows = [] numcols = 0 try: for i in xrange(10): row = reader.next() rows.append(row) numcols = max(numcols, len(row)) rows.append(['...']) numcols = max(numcols, 1) except StopIteration: pass numrows = len(rows) except (EnvironmentError, UnicodeError, csv.Error): return False # fill up table t.setColumnCount(numcols) t.setRowCount(numrows) for r in xrange(numrows): for c in xrange(numcols): if c < len(rows[r]): item = qt4.QTableWidgetItem(unicode(rows[r][c])) t.setItem(r, c, item) return True def doImport(self, doc, filename, linked, encoding, prefix, suffix, tags): """Import from CSV file.""" # get various values inrows = self.csvdirectioncombo.currentIndex() == 1 try: delimiter = self.getCSVDelimiter() textdelimiter = str(self.csvtextdelimitercombo.currentText()) except UnicodeEncodeError: return numericlocale = ( str(qt4.QLocale().name()), 'en_US', 'de_DE' )[self.csvnumfmtcombo.currentIndex()] headerignore = self.csvignorehdrspin.value() rowsignore = self.csvignoretopspin.value() blanksaredata = self.csvblanksdatacheck.isChecked() dateformat = unicode(self.csvdatefmtcombo.currentText()) headermode = ('multi', '1st', 'none')[ self.csvheadermodecombo.currentIndex()] # create import parameters and operation objects params = document.ImportParamsCSV( filename=filename, readrows=inrows, encoding=encoding, delimiter=delimiter, textdelimiter=textdelimiter, headerignore=headerignore, rowsignore=rowsignore, blanksaredata=blanksaredata, numericlocale=numericlocale, dateformat=dateformat, headermode=headermode, prefix=prefix, suffix=suffix, tags=tags, linked=linked, ) op = document.OperationDataImportCSV(params) # actually import the data doc.applyOperation(op) # update output, showing what datasets were imported lines = self.dialog.retnDatasetInfo(op.outdatasets, linked, filename) t = self.previewtablecsv t.verticalHeader().hide() t.horizontalHeader().hide() t.horizontalHeader().setStretchLastSection(True) t.clear() t.setColumnCount(1) t.setRowCount(len(lines)) for i, l in enumerate(lines): item = qt4.QTableWidgetItem(l) t.setItem(i, 0, item) def isFiletypeSupported(self, ftype): """Is the filetype supported by this tab?""" return ftype in ('.tsv', '.csv') class ImportTab2D(ImportTab): """Tab for importing from a 2D data file.""" resource = 'import_2d.ui' def loadUi(self): """Load user interface and set up validators.""" ImportTab.loadUi(self) # set up some validators for 2d edits dval = qt4.QDoubleValidator(self) for i in (self.twod_xminedit, self.twod_xmaxedit, self.twod_yminedit, self.twod_ymaxedit): i.setValidator(dval) def reset(self): """Reset controls.""" for combo in (self.twod_xminedit, self.twod_xmaxedit, self.twod_yminedit, self.twod_ymaxedit, self.twod_datasetsedit): combo.setEditText("") for check in (self.twod_invertrowscheck, self.twod_invertcolscheck, self.twod_transposecheck): check.setChecked(False) def doPreview(self, filename, encoding): """Preview 2d dataset files.""" try: ifile = utils.openEncoding(filename, encoding) text = ifile.read(4096)+'\n' if len(ifile.read(1)) != 0: # if there is remaining data add ... text += '...\n' self.twod_previewedit.setPlainText(text) return True except (UnicodeError, EnvironmentError): self.twod_previewedit.setPlainText('') return False def doImport(self, doc, filename, linked, encoding, prefix, suffix, tags): """Import from 2D file.""" # this really needs improvement... # get datasets and split into a list datasets = unicode( self.twod_datasetsedit.text() ) datasets = re.split('[, ]+', datasets) # strip out blank items datasets = [d for d in datasets if d != ''] # an obvious error... if len(datasets) == 0: self.twod_previewedit.setPlainText('At least one dataset needs to ' 'be specified') return # convert range parameters ranges = [] for e in (self.twod_xminedit, self.twod_xmaxedit, self.twod_yminedit, self.twod_ymaxedit): f = unicode(e.text()) r = None try: r = float(f) except ValueError: pass ranges.append(r) # propagate settings from dialog to reader rangex = None rangey = None if ranges[0] is not None and ranges[1] is not None: rangex = (ranges[0], ranges[1]) if ranges[2] is not None and ranges[3] is not None: rangey = (ranges[2], ranges[3]) invertrows = self.twod_invertrowscheck.isChecked() invertcols = self.twod_invertcolscheck.isChecked() transpose = self.twod_transposecheck.isChecked() # loop over datasets and read... params = document.ImportParams2D( datasetnames=datasets, filename=filename, xrange=rangex, yrange=rangey, invertrows=invertrows, invertcols=invertcols, transpose=transpose, prefix=prefix, suffix=suffix, tags=tags, linked=linked, encoding=encoding ) try: op = document.OperationDataImport2D(params) doc.applyOperation(op) output = ['Successfully read datasets:'] for ds in op.outdatasets: output.append(' %s' % doc.data[ds].description( showlinked=False)) output = '\n'.join(output) except document.Read2DError, e: output = 'Error importing datasets:\n %s' % str(e) # show status in preview box self.twod_previewedit.setPlainText(output) pyfits = None class ImportTabFITS(ImportTab): """Tab for importing from a FITS file.""" resource = 'import_fits.ui' def loadUi(self): ImportTab.loadUi(self) # if different items are selected in fits tab self.connect( self.fitshdulist, qt4.SIGNAL('itemSelectionChanged()'), self.slotFitsUpdateCombos ) self.connect( self.fitsdatasetname, qt4.SIGNAL('editTextChanged(const QString&)'), self.dialog.enableDisableImport ) self.connect( self.fitsdatacolumn, qt4.SIGNAL('currentIndexChanged(int)'), self.dialog.enableDisableImport ) def reset(self): """Reset controls.""" self.fitsdatasetname.setEditText("") for c in ('data', 'sym', 'pos', 'neg'): cntrl = getattr(self, 'fits%scolumn' % c) cntrl.setCurrentIndex(0) def doPreview(self, filename, encoding): """Set up controls for FITS file.""" # load pyfits if available global pyfits if pyfits is None: try: import pyfits as PF pyfits = PF except ImportError: pyfits = None # if it isn't if pyfits is None: self.fitslabel.setText( 'FITS file support requires that PyFITS is installed.' ' You can download it from' ' http://www.stsci.edu/resources/software_hardware/pyfits') return False # try to identify fits file try: ifile = open(filename, 'rU') line = ifile.readline() # is this a hack? if line.find('SIMPLE = T') == -1: raise EnvironmentError ifile.close() except EnvironmentError: self.clearFITSView() return False self.updateFITSView(filename) return True def clearFITSView(self): """If invalid filename, clear fits preview.""" self.fitshdulist.clear() for c in ('data', 'sym', 'pos', 'neg'): cntrl = getattr(self, 'fits%scolumn' % c) cntrl.clear() cntrl.setEnabled(False) def updateFITSView(self, filename): """Update the fits file details in the import dialog.""" f = pyfits.open(str(filename), 'readonly') l = self.fitshdulist l.clear() # this is so we can lookup item attributes later self.fitsitemdata = [] items = [] for hdunum, hdu in enumerate(f): header = hdu.header hduitem = qt4.QTreeWidgetItem([str(hdunum), hdu.name]) data = [] try: # if this fails, show an image cols = hdu.get_coldefs() # it's a table data = ['table', cols] rows = header['NAXIS2'] descr = 'Table (%i rows)' % rows except AttributeError: # this is an image naxis = header['NAXIS'] if naxis == 1 or naxis == 2: data = ['image'] else: data = ['invalidimage'] dims = [ str(header['NAXIS%i' % (i+1)]) for i in xrange(naxis) ] dims = '*'.join(dims) if dims: dims = '(%s)' % dims descr = '%iD image %s' % (naxis, dims) hduitem = qt4.QTreeWidgetItem([str(hdunum), hdu.name, descr]) items.append(hduitem) self.fitsitemdata.append(data) if items: l.addTopLevelItems(items) l.setCurrentItem(items[0]) def slotFitsUpdateCombos(self): """Update list of fits columns when new item is selected.""" items = self.fitshdulist.selectedItems() if len(items) != 0: item = items[0] hdunum = int( str(item.text(0)) ) else: item = None hdunum = -1 cols = ['N/A'] enablecolumns = False if hdunum >= 0: data = self.fitsitemdata[hdunum] if data[0] == 'table': enablecolumns = True cols = ['None'] cols += ['%s (%s)' % (i.name, i.format) for i in data[1]] for c in ('data', 'sym', 'pos', 'neg'): cntrl = getattr(self, 'fits%scolumn' % c) cntrl.setEnabled(enablecolumns) cntrl.clear() cntrl.addItems(cols) self.dialog.enableDisableImport() def okToImport(self): """Check validity of Fits import.""" items = self.fitshdulist.selectedItems() if len(items) != 0: item = items[0] hdunum = int( str(item.text(0)) ) # any name for the dataset? if not unicode(self.fitsdatasetname.text()): return False # if a table, need selected item data = self.fitsitemdata[hdunum] if data[0] != 'image' and self.fitsdatacolumn.currentIndex() == 0: return False return True return False def doImport(self, doc, filename, linked, encoding, prefix, suffix, tags): """Import fits file.""" item = self.fitshdulist.selectedItems()[0] hdunum = int( str(item.text(0)) ) data = self.fitsitemdata[hdunum] name = prefix + unicode(self.fitsdatasetname.text()) + suffix if data[0] == 'table': # get list of appropriate columns cols = [] # get data from controls for c in ('data', 'sym', 'pos', 'neg'): cntrl = getattr(self, 'fits%scolumn' % c) i = cntrl.currentIndex() if i == 0: cols.append(None) else: cols.append(data[1][i-1].name) else: # item is an image, so no columns cols = [None]*4 # construct operation to import fits params = document.ImportParamsFITS( dsname=name, filename=filename, hdu=hdunum, datacol=cols[0], symerrcol=cols[1], poserrcol=cols[2], negerrcol=cols[3], tags=tags, linked=linked, ) op = document.OperationDataImportFITS(params) # actually do the import doc.applyOperation(op) # inform user self.fitsimportstatus.setText("Imported dataset '%s'" % name) qt4.QTimer.singleShot(2000, self.fitsimportstatus.clear) def isFiletypeSupported(self, ftype): """Is the filetype supported by this tab?""" return ftype in ('.fit', '.fits') class ImportTabPlugins(ImportTab): """Tab for importing using a plugin.""" resource = 'import_plugins.ui' def __init__(self, importdialog, promote=None): """Initialise dialog. importdialog is the import dialog itself. If promote is set to a name of a plugin, it is promoted to its own tab """ ImportTab.__init__(self, importdialog) self.promote = promote self.plugininstance = None def loadUi(self): """Load the user interface.""" ImportTab.loadUi(self) # fill plugin combo names = list(sorted([p.name for p in plugins.importpluginregistry])) self.pluginType.addItems(names) self.connect(self.pluginType, qt4.SIGNAL('currentIndexChanged(int)'), self.pluginChanged) self.fields = [] # load previous plugin idx = -1 if self.promote is None: if 'import_plugin' in setting.settingdb: try: idx = names.index(setting.settingdb['import_plugin']) except ValueError: pass else: # set the correct entry for the plugin idx = names.index(self.promote) # then hide the widget so it can't be changed self.pluginchoicewidget.hide() if idx >= 0: self.pluginType.setCurrentIndex(idx) self.pluginChanged(-1) def getPluginFields(self): """Return a dict of the fields given.""" results = {} plugin = self.getSelectedPlugin() for field, cntrls in zip(plugin.fields, self.fields): results[field.name] = field.getControlResults(cntrls) return results def getSelectedPlugin(self): """Get instance selected plugin or none.""" selname = unicode(self.pluginType.currentText()) names = [p.name for p in plugins.importpluginregistry] try: idx = names.index(selname) except ValueError: return None p = plugins.importpluginregistry[idx] if isinstance(p, type): # this is a class, rather than an object if not isinstance(self.plugininstance, p): # create new instance, if required self.plugininstance = p() return self.plugininstance else: # backward compatibility with old API return p def pluginChanged(self, index): """Update controls based on index.""" plugin = self.getSelectedPlugin() if self.promote is None: setting.settingdb['import_plugin'] = plugin.name # delete old controls layout = self.pluginParams.layout() for line in self.fields: for cntrl in line: layout.removeWidget(cntrl) cntrl.deleteLater() del self.fields[:] # make new controls for row, field in enumerate(plugin.fields): cntrls = field.makeControl(None, None) layout.addWidget(cntrls[0], row, 0) layout.addWidget(cntrls[1], row, 1) self.fields.append(cntrls) # update label self.pluginDescr.setText("%s (%s)\n%s" % (plugin.name, plugin.author, plugin.description)) self.dialog.slotUpdatePreview() def doPreview(self, filename, encoding): """Preview using plugin.""" # check file exists if filename != '{clipboard}': try: f = open(filename, 'r') f.close() except EnvironmentError: self.pluginPreview.setPlainText('') return False # get the plugin selected plugin = self.getSelectedPlugin() if plugin is None: self.pluginPreview.setPlainText('') return False # ask the plugin for text params = plugins.ImportPluginParams(filename, encoding, self.getPluginFields()) try: text, ok = plugin.getPreview(params) except plugins.ImportPluginException, ex: text = unicode(ex) ok = False self.pluginPreview.setPlainText(text) return bool(ok) def doImport(self, doc, filename, linked, encoding, prefix, suffix, tags): """Import using plugin.""" fields = self.getPluginFields() plugin = unicode(self.pluginType.currentText()) params = document.ImportParamsPlugin( plugin=plugin, filename=filename, linked=linked, encoding=encoding, prefix=prefix, suffix=suffix, tags=tags, **fields) op = document.OperationDataImportPlugin(params) try: doc.applyOperation(op) except plugins.ImportPluginException, ex: self.pluginPreview.setPlainText( unicode(ex) ) return out = ['Imported data for datasets:'] for ds in op.outdatasets: out.append( doc.data[ds].description(showlinked=False) ) if op.outcustoms: out.append('') out.append('Set custom definitions:') # format custom definitions out += ['%s %s=%s' % tuple(c) for c in op.outcustoms] self.pluginPreview.setPlainText('\n'.join(out)) def isFiletypeSupported(self, ftype): """Is the filetype supported by this tab?""" if self.promote is None: # look through list of supported plugins to check filetypes inany = False for p in plugins.importpluginregistry: if ftype in p.file_extensions: inany = True return inany else: # find plugin class and check filetype for p in plugins.importpluginregistry: if p.name == self.promote: return ftype in p.file_extensions def useFiletype(self, ftype): """Select the plugin corresponding to the filetype.""" if self.promote is None: plugin = None for p in plugins.importpluginregistry: if ftype in p.file_extensions: plugin = p.name idx = self.pluginType.findText(plugin, qt4.Qt.MatchExactly) self.pluginType.setCurrentIndex(idx) self.pluginChanged(-1) class ImportDialog(VeuszDialog): """Dialog box for importing data. See ImportTab classes above which actually do the work of importing """ dirname = '.' def __init__(self, parent, document): VeuszDialog.__init__(self, parent, 'import.ui') self.document = document # whether file import looks likely to work self.filepreviewokay = False # tabs loaded currently in dialog self.tabs = {} for tabname, tabclass in ( ('&Standard', ImportTabStandard), ('CS&V', ImportTabCSV), ('FI&TS', ImportTabFITS), ('&2D', ImportTab2D), ('Plugins', ImportTabPlugins), ): w = tabclass(self) self.methodtab.addTab(w, tabname) # add promoted plugins for p in plugins.importpluginregistry: if p.promote_tab is not None: w = ImportTabPlugins(self, promote=p.name) self.methodtab.addTab(w, p.promote_tab) self.connect( self.methodtab, qt4.SIGNAL('currentChanged(int)'), self.slotUpdatePreview ) self.connect(self.browsebutton, qt4.SIGNAL('clicked()'), self.slotBrowseClicked) self.connect( self.filenameedit, qt4.SIGNAL('editTextChanged(const QString&)'), self.slotUpdatePreview ) self.importbutton = self.buttonBox.addButton("&Import", qt4.QDialogButtonBox.ApplyRole) self.connect( self.importbutton, qt4.SIGNAL('clicked()'), self.slotImport) self.connect( self.buttonBox.button(qt4.QDialogButtonBox.Reset), qt4.SIGNAL('clicked()'), self.slotReset ) self.connect( self.encodingcombo, qt4.SIGNAL('currentIndexChanged(int)'), self.slotUpdatePreview ) # change to tab last used self.methodtab.setCurrentIndex( setting.settingdb.get('import_lasttab', 0)) # add completion for filename if there is support in version of qt # (requires qt >= 4.3) if hasattr(qt4, 'QDirModel'): c = self.filenamecompleter = qt4.QCompleter(self) model = qt4.QDirModel(c) c.setModel(model) self.filenameedit.setCompleter(c) # defaults for prefix and suffix self.prefixcombo.default = self.suffixcombo.default = ['', '$FILENAME'] # default state for check boxes self.linkcheckbox.default = True # further defaults self.encodingcombo.defaultlist = utils.encodings self.encodingcombo.defaultval = 'utf_8' # load icon for clipboard self.clipbutton.setIcon( utils.getIcon('kde-clipboard') ) self.connect(qt4.QApplication.clipboard(), qt4.SIGNAL('dataChanged()'), self.updateClipPreview) self.connect( self.clipbutton, qt4.SIGNAL("clicked()"), self.slotClipButtonClicked) self.updateClipPreview() def slotBrowseClicked(self): """Browse for a data file.""" fd = qt4.QFileDialog(self, 'Browse data file') fd.setFileMode( qt4.QFileDialog.ExistingFile ) # use filename to guess a path if possible filename = unicode(self.filenameedit.text()) if os.path.isdir(filename): ImportDialog.dirname = filename elif os.path.isdir( os.path.dirname(filename) ): ImportDialog.dirname = os.path.dirname(filename) fd.setDirectory(ImportDialog.dirname) # update filename if changed if fd.exec_() == qt4.QDialog.Accepted: ImportDialog.dirname = fd.directory().absolutePath() self.filenameedit.replaceAndAddHistory( fd.selectedFiles()[0] ) self.guessImportTab() def guessImportTab(self): """Guess import tab based on filename.""" filename = unicode( self.filenameedit.text() ) fname, ftype = os.path.splitext(filename) # strip off any gz, bz2 extensions to get real extension while ftype.lower() in ('gz', 'bz2'): fname, ftype = os.path.splitext(fname) ftype = ftype.lower() # examine from left to right # promoted plugins come after plugins idx = -1 for i in xrange(self.methodtab.count()): w = self.methodtab.widget(i) if w.isFiletypeSupported(ftype): idx = i if idx >= 0: self.methodtab.setCurrentIndex(idx) self.methodtab.widget(idx).useFiletype(ftype) def slotUpdatePreview(self, *args): """Update preview window when filename or tab changed.""" # save so we can restore later tab = self.methodtab.currentIndex() setting.settingdb['import_lasttab'] = tab filename = unicode(self.filenameedit.text()) encoding = str(self.encodingcombo.currentText()) importtab = self.methodtab.currentWidget() if encoding == '': return if isinstance(importtab, ImportTab): if not importtab.uiloaded: importtab.loadUi() self.filepreviewokay = importtab.doPreview( filename, encoding) # enable or disable import button self.enableDisableImport() def enableDisableImport(self, *args): """Disable or enable import button if allowed.""" importtab = self.methodtab.currentWidget() enabled = self.filepreviewokay and importtab.okToImport() # actually enable or disable import button self.importbutton.setEnabled( enabled ) def slotImport(self): """Do the importing""" filename = unicode( self.filenameedit.text() ) linked = self.linkcheckbox.isChecked() encoding = str(self.encodingcombo.currentText()) if filename == '{clipboard}': linked = False else: # normalise filename filename = os.path.abspath(filename) # import according to tab selected importtab = self.methodtab.currentWidget() prefix, suffix = self.getPrefixSuffix(filename) tags = unicode(self.tagcombo.currentText()).split() try: qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.WaitCursor) ) self.document.suspendUpdates() importtab.doImport(self.document, filename, linked, encoding, prefix, suffix, tags) qt4.QApplication.restoreOverrideCursor() except Exception: qt4.QApplication.restoreOverrideCursor() # show exception dialog d = exceptiondialog.ExceptionDialog(sys.exc_info(), self) d.exec_() self.document.enableUpdates() def retnDatasetInfo(self, dsnames, linked, filename): """Return a list of information for the dataset names given.""" lines = ['Imported data for datasets:'] dsnames.sort() for name in dsnames: ds = self.document.getData(name) # build up description lines.append( ' %s' % ds.description(showlinked=False) ) # whether the data were linked if linked: lines.append('') lines.append('Datasets were linked to file "%s"' % filename) return lines def getPrefixSuffix(self, filename): """Get prefix and suffix values.""" f = utils.cleanDatasetName( os.path.basename(filename) ) prefix = unicode( self.prefixcombo.lineEdit().text() ) prefix = prefix.replace('$FILENAME', f) suffix = unicode( self.suffixcombo.lineEdit().text() ) suffix = suffix.replace('$FILENAME', f) return prefix, suffix def slotReset(self): """Reset input fields.""" self.filenameedit.setText("") self.encodingcombo.setCurrentIndex( self.encodingcombo.findText("utf_8")) self.linkcheckbox.setChecked(True) self.prefixcombo.setEditText("") self.suffixcombo.setEditText("") importtab = self.methodtab.currentWidget() importtab.reset() def slotClipButtonClicked(self): """Clicked clipboard button.""" self.filenameedit.setText("{clipboard}") def updateClipPreview(self): """Clipboard contents changed, so update preview if showing clipboard.""" filename = unicode(self.filenameedit.text()) if filename == '{clipboard}': self.slotUpdatePreview() veusz-1.15/dialogs/safetyimport.py0000644002344000001440000000614211734662204017324 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Ask user whether to import symbols.""" import veusz.qtall as qt4 import veusz.setting as setting class SafetyImportDialog(qt4.QMessageBox): def __init__(self, parent, module, names): """Initialise dialog. parent is parent widget module is module to import symbols from names is a list of names to import.""" qt4.QMessageBox.__init__(self, parent) self.names = names self.module = module self.setIcon(qt4.QMessageBox.Warning) self.setWindowTitle("Allow Python import?") self.setText("The document has requested that the symbol(s):\n" " %s\nbe loaded from Python module '%s'.\n\n" "This could be unsafe if the document comes from " "an untrusted source." % ( ', '.join(names), module)) self.allow = self.addButton("Allow", qt4.QMessageBox.YesRole) self.allow.setToolTip("Allow use of symbol in module during session") self.allowalways = self.addButton("Allow always", qt4.QMessageBox.YesRole) self.allowalways.setToolTip("Always allow use of symbol in module") self.notallow = self.addButton("Do not allow", qt4.QMessageBox.NoRole) self.notallow.setToolTip("Do allow use of symbol in module in session") def exec_(self): """Execute dialog.""" # when loading the document the busy cursor is on, this gets # rid of it for a while qt4.qApp.setOverrideCursor(qt4.QCursor(qt4.Qt.ArrowCursor)) retn = qt4.QMessageBox.exec_(self) qt4.qApp.restoreOverrideCursor() b = self.clickedButton() # update lists of variables in settings depending on chosen button if b is self.allow: a = setting.transient_settings['import_allowed'][self.module] a |= set(self.names) elif b is self.allowalways: a = setting.settingdb['import_allowed'][self.module] a.update( [(x, True) for x in self.names] ) elif b is self.notallow: a = setting.transient_settings['import_notallowed'][self.module] a |= set(self.names) veusz-1.15/dialogs/exceptionsend.ui0000644002344000001440000001007311734662204017431 0ustar jssusers00000000000000 ExceptSendDialog 0 0 469 509 Send problem - Veusz <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Email address</span> (optional). If provided you can be notified about the bug status or to get further details.</p></body></html> true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">What you were doing when the problem occured</span> (optional). This is very helpful for trying to reproduce the bug.</p></body></html> true This is what Veusz will send: true No personal details will be sent other than those listed here and the IP address. You will need an internet connection to send the error report. true QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() ExceptSendDialog accept() 234 487 234 254 buttonBox rejected() ExceptSendDialog reject() 234 487 234 254 veusz-1.15/dialogs/import_standard.ui0000644002344000001440000000737711734662204017770 0ustar jssusers00000000000000 standardtab 0 0 264 277 File preview: QTextEdit::NoWrap true 6 0 Dataset &names descriptoredit The import descriptor, consisting of the dataset names used during import, e.g. "x y" or "a[:]" Help 0 0 Ignores lines consisting of text when importing the data I&gnore text lines true 0 0 <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If this is selected, blank lines or the word "no" are used to separate the file into blocks. An underscore followed by the block number is added to the dataset names</p></body></html> Read data in bloc&ks HistoryCombo QComboBox
    historycombo.h
    HistoryCheck QCheckBox
    historycheck.h
    veusz-1.15/dialogs/aboutdialog.py0000644002344000001440000000405111734662204017065 0ustar jssusers00000000000000# about dialog box # aboutdialog.py # Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """About dialog module.""" import os.path import veusz.qtall as qt4 import veusz.utils as utils from veuszdialog import VeuszDialog class AboutDialog(VeuszDialog): """About dialog.""" def __init__(self, mainwindow): VeuszDialog.__init__(self, mainwindow, 'about.ui') # draw logo in dialog self.frame.setBackgroundRole(qt4.QPalette.Base) self.frame.setAutoFillBackground(True) self.logolabel.setPixmap( utils.getPixmap('logo.png') ) # add version to copyright text copyrighttext = unicode(self.copyrightlabel.text()) copyrighttext = copyrighttext % {'version': utils.version()} self.copyrightlabel.setText(copyrighttext) self.connect(self.licenseButton, qt4.SIGNAL('clicked()'), self.licenseClicked) def licenseClicked(self): """Show the license.""" LicenseDialog(self).exec_() class LicenseDialog(VeuszDialog): """About license dialog.""" def __init__(self, parent): VeuszDialog.__init__(self, parent, 'license.ui') self.licenseEdit.setPlainText(utils.getLicense()) veusz-1.15/dialogs/histodata.py0000644002344000001440000002516611734662204016565 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting import veusz.utils as utils import veusz.document as document from veuszdialog import VeuszDialog import dataeditdialog import numpy as N def checkValidator(combo): """Is this validator ok?""" valid = combo.validator() state, x = valid.validate( combo.currentText(), 0 ) return state == qt4.QValidator.Acceptable class ManualBinModel(qt4.QAbstractListModel): """Model to store a list of floating point values in a list.""" def __init__(self, data): qt4.QAbstractListModel.__init__(self) self.data = data def data(self, index, role): if role == qt4.Qt.DisplayRole and index.isValid(): return qt4.QVariant(float(self.data[index.row()])) return qt4.QVariant() def rowCount(self, parent): return len(self.data) def flags(self, index): return ( qt4.Qt.ItemIsSelectable | qt4.Qt.ItemIsEnabled | qt4.Qt.ItemIsEditable ) def setData(self, index, value, role): if role == qt4.Qt.EditRole: val, ok = value.toDouble() if ok: self.data[ index.row() ] = val self.emit( qt4.SIGNAL("dataChanged(const QModelIndex &," " const QModelIndex &)"), index, index) return True return False class HistoDataDialog(VeuszDialog): """Preferences dialog.""" def __init__(self, parent, document): """Setup dialog.""" VeuszDialog.__init__(self, parent, 'histodata.ui') self.document = document self.minval.default = self.maxval.default = ['Auto'] regexp = qt4.QRegExp("^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?|Auto$") validator = qt4.QRegExpValidator(regexp, self) self.minval.setValidator(validator) self.maxval.setValidator(validator) self.connect( self.buttonBox.button(qt4.QDialogButtonBox.Apply), qt4.SIGNAL("clicked()"), self.applyClicked ) self.connect( self.buttonBox.button(qt4.QDialogButtonBox.Reset), qt4.SIGNAL('clicked()'), self.resetClicked ) self.connect( self.bingenerate, qt4.SIGNAL('clicked()'), self.generateManualBins ) self.connect( self.binadd, qt4.SIGNAL('clicked()'), self.addManualBins ) self.connect( self.binremove, qt4.SIGNAL('clicked()'), self.removeManualBins ) self.bindata = [] self.binmodel = ManualBinModel(self.bindata) self.binmanuals.setModel(self.binmodel) self.connect(document, qt4.SIGNAL("sigModified"), self.updateDatasetLists) self.updateDatasetLists() def escapeDatasets(self, dsnames): """Escape dataset names if they are not typical python ones.""" for i in xrange(len(dsnames)): if not utils.validPythonIdentifier(dsnames[i]): dsnames[i] = '`%s`' % dsnames[i] def updateDatasetLists(self): """Update list of datasets.""" datasets = [] for name, ds in self.document.data.iteritems(): if ds.datatype == 'numeric' and ds.dimensions == 1: datasets.append(name) datasets.sort() # make sure names are escaped if they have funny characters self.escapeDatasets(datasets) # help the user by listing existing datasets utils.populateCombo(self.indataset, datasets) def datasetExprChanged(self): """Validate expression.""" text = self.indataset.text() res = document.simpleEvalExpression(self.document, unicode(text)) class Params(object): """Parameters to creation of histogram.""" def __init__(self, dialog): """Initialise parameters from dialog.""" numbins = dialog.numbins.value() if not checkValidator(dialog.minval): raise RuntimeError("Invalid minimum value") minval = unicode( dialog.minval.text() ) if minval != 'Auto': minval = float(minval) if not checkValidator(dialog.maxval): raise RuntimeError("Invalid maximum value") maxval = unicode( dialog.maxval.text() ) if maxval != 'Auto': maxval = float(maxval) islog = dialog.logarithmic.isChecked() self.binparams = (numbins, minval, maxval, islog) self.expr = unicode( dialog.indataset.currentText() ) self.outdataset = unicode( dialog.outdataset.currentText() ) self.outbins = unicode( dialog.outbins.currentText() ) self.method = unicode( dialog.methodGroup.getRadioChecked(). objectName() ) self.manualbins = list( dialog.bindata ) self.manualbins.sort() if len(self.manualbins) == 0: self.manualbins = None self.errors = dialog.errorBars.isChecked() cuml = dialog.cumlGroup.getRadioChecked().objectName() self.cumulative = 'none' if cuml == 'cumlStoL': self.cumulative = 'smalltolarge' elif cuml == 'cumlLtoS': self.cumulative = 'largetosmall' def getGenerator(self, doc): """Return dataset generator.""" return document.DatasetHistoGenerator( doc, self.expr, binparams = self.binparams, binmanual = self.manualbins, method = self.method, cumulative = self.cumulative, errors = self.errors) def getOperation(self): """Get operation to make histogram.""" return document.OperationDatasetHistogram( self.expr, self.outbins, self.outdataset, binparams = self.binparams, binmanual = self.manualbins, method = self.method, cumulative = self.cumulative, errors = self.errors) def generateManualBins(self): """Generate manual bins.""" try: p = HistoDataDialog.Params(self) except RuntimeError, ex: qt4.QMessageBox.warning(self, "Invalid parameters", unicode(ex)) return if p.expr != '': p.manualbins = [] gen = p.getGenerator(self.document) self.bindata[:] = list(gen.binLocations()) else: del self.bindata[:] self.binmodel.reset() def addManualBins(self): """Add an extra bin to the manual list.""" self.bindata.insert(0, 0.) self.binmodel.reset() def removeManualBins(self): """Remove selected bins.""" indexes = self.binmanuals.selectionModel().selectedIndexes() if indexes: del self.bindata[ indexes[0].row() ] self.binmodel.reset() def resetClicked(self): """Reset button clicked.""" for cntrl in (self.indataset, self.outdataset, self.outbins): cntrl.setEditText("") self.numbins.setValue(10) self.minval.setEditText("Auto") self.maxval.setEditText("Auto") self.logarithmic.setChecked(False) del self.bindata[:] self.binmodel.reset() self.errorBars.setChecked(False) self.counts.click() self.cumlOff.click() def reEditDataset(self, ds, dsname): """Re-edit dataset.""" gen = ds.generator self.indataset.setEditText(gen.inexpr) # need to map backwards to get dataset names revds = dict( (a,b) for b,a in self.document.data.iteritems() ) self.outdataset.setEditText( revds[gen.valuedataset] ) self.outbins.setEditText( revds[gen.bindataset] ) # if there are parameters if gen.binparams: p = gen.binparams self.numbins.setValue( p[0] ) self.minval.setEditText( unicode(p[1]) ) self.maxval.setEditText( unicode(p[2]) ) self.logarithmic.setChecked( bool(p[3]) ) else: self.numbins.setValue(10) self.minval.setEditText("Auto") self.maxval.setEditText("Auto") self.logarithmic.setChecked(False) # if there is a manual list of bins if gen.binmanual: self.bindata[:] = list(gen.binmanual) self.binmodel.reset() # select correct method {'counts': self.counts, 'density': self.density, 'fractions': self.fractions}[gen.method].click() # select if cumulative {'none': self.cumlOff, 'smalltolarge': self.cumlStoL, 'largetosmall': self.cumlLtoS}[gen.cumulative].click() # if error bars self.errorBars.setChecked( bool(gen.errors) ) def applyClicked(self): """Create histogram.""" qt4.QTimer.singleShot(4000, self.statuslabel.clear) try: p = HistoDataDialog.Params(self) except RuntimeError, ex: self.statuslabel.setText("Invalid parameters: %s" % unicode(ex)) return exprresult = N.array( document.simpleEvalExpression(self.document, p.expr), dtype=N.float64) if len(exprresult) == 0: self.statuslabel.setText("Invalid expression") return op = p.getOperation() self.document.applyOperation(op) self.statuslabel.setText( 'Created datasets "%s" and "%s"' % (p.outbins, p.outdataset)) def recreateDataset(mainwindow, document, dataset, datasetname): """Open dialog to recreate histogram.""" dialog = HistoDataDialog(mainwindow, document) mainwindow.showDialog(dialog) dialog.reEditDataset(dataset, datasetname) dataeditdialog.recreate_register[document.DatasetHistoValues] = recreateDataset dataeditdialog.recreate_register[document.DatasetHistoBins] = recreateDataset veusz-1.15/dialogs/exceptiondialog.py0000644002344000001440000001356511734662204017763 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## '''Dialog to pop up if an exception occurs in Veusz. This allows the user to send a bug report in via email.''' import sys import os.path import time import traceback import urllib2 import sip import re import numpy import veusz.qtall as qt4 import veusz.utils as utils from veuszdialog import VeuszDialog _reportformat = \ '''Veusz version: %s Python version: %s Python platform: %s Numpy version: %s Qt version: %s PyQt version: %s SIP version: %s Date: %s %s ''' _sendformat = \ '''Email: %s Error report ------------ %s What the user was doing before the crash ---------------------------------------- %s ''' class ExceptionSendDialog(VeuszDialog): """Dialog to send debugging report.""" def __init__(self, exception, parent): VeuszDialog.__init__(self, parent, 'exceptionsend.ui') # debugging report text self.text = _reportformat % ( utils.version(), sys.version, sys.platform, numpy.__version__, qt4.qVersion(), qt4.PYQT_VERSION_STR, sip.SIP_VERSION_STR, time.strftime('%a, %d %b %Y %H:%M:%S +0000', time.gmtime()), exception ) self.detailstosend.setPlainText(self.text) def accept(self): """Send text.""" # build up the text of the message text = ( _sendformat % ( unicode(self.emailedit.text()), self.text, unicode(self.detailsedit.toPlainText()) )) # send the message as base-64 encoded utf-8 text = str( qt4.QString(text).toUtf8().toBase64() ) try: # send the message urllib2.urlopen('http://barmag.net/veusz-mail.php', 'message=%s' % text) except urllib2.URLError: # something went wrong... qt4.QMessageBox.critical(None, "Veusz", "Failed to connect to error server " "to send report. Is your internet " "connected?") return qt4.QMessageBox.information(self, "Submitted", "Thank you for submitting an error report") VeuszDialog.accept(self) def _raiseIgnoreException(): """Ignore this exception to clear out stack frame of previous exception.""" raise utils.IgnoreException() class ExceptionDialog(VeuszDialog): """Choose an exception to send to developers.""" ignore_exceptions = set() def __init__(self, exception, parent): VeuszDialog.__init__(self, parent, 'exceptionlist.ui') # create backtrace text from exception, and add to list self.backtrace = ''.join(traceback.format_exception(*exception)).strip() self.errortextedit.setPlainText(self.backtrace) # set critical pixmap to left of dialog icon = qt4.qApp.style().standardIcon(qt4.QStyle.SP_MessageBoxCritical, None, self) self.erroriconlabel.setPixmap(icon.pixmap(32)) self.connect(self.ignoreSessionButton, qt4.SIGNAL('clicked()'), self.ignoreSessionSlot) self.checkVeuszVersion() def checkVeuszVersion(self): """See whether there is a later version of veusz and inform the user.""" try: p = urllib2.urlopen('http://download.gna.org/veusz/').read() versions = re.findall('veusz-([0-9.]+).tar.gz', p) except urllib2.URLError: versions = [] if not versions: msg = 'Could not check the latest Veusz version' else: vsort = sorted([[int(i) for i in v.split('.')] for v in versions]) latest = '.'.join([str(x) for x in vsort[-1]]) current = [int(i) for i in utils.version().split('.')] if current == vsort[-1]: msg = 'You are running the latest released Veusz version' elif current > vsort[-1]: msg = 'You are running an unreleased Veusz version' else: msg = ('Your current version of Veusz is old. ' 'Veusz %s is available.' % latest) self.veuszversionlabel.setText(msg) def accept(self): """Accept by opening send dialog.""" d = ExceptionSendDialog(self.backtrace, self) if d.exec_() == qt4.QDialog.Accepted: VeuszDialog.accept(self) def ignoreSessionSlot(self): """Ignore exception for session.""" ExceptionDialog.ignore_exceptions.add(self.backtrace) self.reject() def exec_(self): """Exec dialog if exception is not ignored.""" if self.backtrace not in ExceptionDialog.ignore_exceptions: VeuszDialog.exec_(self) # send another exception shortly - this clears out the current one # so the stack frame of the current exception is released qt4.QTimer.singleShot(0, _raiseIgnoreException) veusz-1.15/dialogs/veuszdialog.py0000644002344000001440000000342711734662204017135 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Define a base dialog class cleans up self after being hidden.""" import os.path import veusz.qtall as qt4 import veusz.utils as utils class VeuszDialog(qt4.QDialog): """Base dialog class. - Loads self from ui file. - Deletes self on closing. - Emits dialogFinished when dialog is done """ def __init__(self, mainwindow, uifile): """Initialise dialog given Veusz mainwindow and uifile for dialog.""" qt4.QDialog.__init__(self, mainwindow) self.setAttribute(qt4.Qt.WA_DeleteOnClose) qt4.loadUi(os.path.join(utils.veuszDirectory, 'dialogs', uifile), self) self.mainwindow = mainwindow def hideEvent(self, event): """Emits dialogFinished if hidden.""" if not event.spontaneous(): self.emit( qt4.SIGNAL('dialogFinished'), self ) return qt4.QDialog.hideEvent(self, event) veusz-1.15/dialogs/histodata.ui0000644002344000001440000002305511734662204016545 0ustar jssusers00000000000000 HistogramDialog 0 0 472 558 Histogram data Count data values in bins to calculate a histogram Datasets &Input dataset expression indataset true &Output bin height dataset name outdataset Output &bin position dataset name outbins Automatic bin parameters &Number of bins numbins 1 999999 10 &Minimum value minval Minimum value of lowest value bin or "Auto" to get from dataset Ma&ximum value maxval Maximum value of highest value bin or " Auto" to get from dataset &Logarithmic Manual bin boundaries Add Remove Generate bin boundaries from parameters Generate Calculate Count number of items in bin &Counts true Compute probability density in bins &Density Compute fraction of items in bin &Fractions Cumulative Not cumulative true Small to large Large to small Compute error bars (Gehrels Poisson approximation) Qt::Horizontal QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Reset HistoryCombo QComboBox
    historycombo.h
    HistoryGroupBox QGroupBox
    historygroupbox.h
    1
    HistoryCheck QCheckBox
    historycheck.h
    buttonBox accepted() HistogramDialog accept() 248 254 157 274 buttonBox rejected() HistogramDialog reject() 316 260 286 274
    veusz-1.15/dialogs/capturedialog.py0000644002344000001440000003075611734662204017431 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Veusz data capture dialog.""" import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document import veusz.setting as setting from veuszdialog import VeuszDialog class CaptureDialog(VeuszDialog): """Capture dialog. This allows the user to set the various capture options.""" def __init__(self, document, mainwindow): VeuszDialog.__init__(self, mainwindow, 'capture.ui') self.document = document # set values of edit controls from previous invocation (if any) d = setting.settingdb # Validate edit controls validator = qt4.QIntValidator(1, 65535, self) self.portEdit.setValidator(validator) validator = qt4.QIntValidator(1, 1000000000, self) self.numLinesStopEdit.setValidator(validator) self.timeStopEdit.setValidator(validator) self.tailEdit.setValidator(validator) # floating point values for interval self.updateIntervalsEdit.setValidator( qt4.QDoubleValidator(1e-2, 10000000, 2, self)) # add completion for filename if there is support in version of qt # (requires qt >= 4.3) if hasattr(qt4, 'QDirModel'): c = self.filenamecompleter = qt4.QCompleter(self) model = qt4.QDirModel(c) c.setModel(model) self.filenameEdit.setCompleter(c) # get notification of change of capture method self.methodBG = qt4.QButtonGroup(self) self.methodBG.addButton( self.captureFileButton, 0 ) self.methodBG.addButton( self.captureInternetButton, 1 ) self.methodBG.addButton( self.captureProgramButton, 2 ) self.connect(self.methodBG, qt4.SIGNAL('buttonClicked(int)'), self.slotMethodChanged) # restore previously clicked button self.methodBG.button( d.get('CaptureDialog_method', 0) ).click() # get notification of change of stop method self.stopBG = qt4.QButtonGroup(self) self.stopBG.addButton( self.clickingStopButton, 0 ) self.stopBG.addButton( self.numLinesStopButton, 1 ) self.stopBG.addButton( self.timeStopButton, 2 ) self.connect(self.stopBG, qt4.SIGNAL('buttonClicked(int)'), self.slotStopChanged) self.stopBG.button( d.get('CaptureDialog_stop', 0) ).click() # update interval self.connect(self.updateIntervalsCheck, qt4.SIGNAL('toggled(bool)'), self.updateIntervalsEdit.setEnabled) # tail data self.connect(self.tailCheck, qt4.SIGNAL('toggled(bool)'), self.tailEdit.setEnabled) # user starts capture self.captureButton = self.buttonBox.addButton( "Ca&pture", qt4.QDialogButtonBox.ApplyRole ) self.connect(self.captureButton, qt4.SIGNAL('clicked()'), self.slotCaptureClicked) # filename browse button clicked self.connect(self.browseButton, qt4.SIGNAL('clicked()'), self.slotBrowseClicked) def done(self, r): """Dialog is closed.""" VeuszDialog.done(self, r) # record values for next time dialog is opened d = setting.settingdb d['CaptureDialog_method'] = self.methodBG.checkedId() d['CaptureDialog_stop'] = self.stopBG.checkedId() def slotMethodChanged(self, buttonid): """Enable/disable correct controls in methodBG.""" # enable correct buttons fc = buttonid==0 self.filenameEdit.setEnabled(fc) self.browseButton.setEnabled(fc) ic = buttonid==1 self.hostEdit.setEnabled(ic) self.portEdit.setEnabled(ic) xc = buttonid==2 self.commandLineEdit.setEnabled(xc) def slotStopChanged(self, buttonid): """Enable/disable correct controls in stopBG.""" ns = buttonid == 1 self.numLinesStopEdit.setEnabled(ns) ts = buttonid == 2 self.timeStopEdit.setEnabled(ts) def slotBrowseClicked(self): """Browse for a data file.""" fd = qt4.QFileDialog(self, 'Browse data file or socket') fd.setFileMode( qt4.QFileDialog.ExistingFile ) # update filename if changed if fd.exec_() == qt4.QDialog.Accepted: self.filenameEdit.replaceAndAddHistory( fd.selectedFiles()[0] ) def slotCaptureClicked(self): """User requested capture.""" # object to interpret data from stream descriptor = unicode( self.descriptorEdit.text() ) simpleread = document.SimpleRead(descriptor) maxlines = None timeout = None updateinterval = None tail = None try: stop = self.stopBG.checkedId() if stop == 1: # number of lines to read before stopping maxlines = int( self.numLinesStopEdit.text() ) elif stop == 2: # maximum time period before stopping timeout = int( self.timeStopEdit.text() ) # whether to do an update periodically if self.updateIntervalsCheck.isChecked(): updateinterval = float( self.updateIntervalsEdit.text() ) # whether to only retain N values if self.tailCheck.isChecked(): tail = int( self.tailEdit.text() ) except ValueError: qt4.QMessageBox.critical(self, "Invalid number", "Invalid number") return # get method of getting data method = self.methodBG.checkedId() try: # create stream if method == 0: # file/socket stream = document.FileCaptureStream( unicode(self.filenameEdit.text()) ) elif method == 1: # internet socket stream = document.SocketCaptureStream( unicode(self.hostEdit.text()), int(self.portEdit.text()) ) elif method == 2: # external program stream = document.CommandCaptureStream( unicode(self.commandLineEdit.text()) ) except EnvironmentError, e: # problem opening stream qt4.QMessageBox.critical(self, "Cannot open input", "Cannot open input:\n" " %s (error %i)" % (e.strerror, e.errno)) return stream.maxlines = maxlines stream.timeout = timeout simpleread.tail = tail cd = CapturingDialog(self.document, simpleread, stream, self, updateinterval=updateinterval) self.mainwindow.showDialog(cd) ######################################################################## class CapturingDialog(VeuszDialog): """Capturing data dialog. Shows progress to user.""" def __init__(self, document, simpleread, stream, parent, updateinterval = None): """Initialse capture dialog: document: document to send data to simpleread: object to interpret data stream: capturestream to read data from parent: parent widget updateinterval: if set, interval of seconds to update data in doc """ VeuszDialog.__init__(self, parent, 'capturing.ui') self.document = document self.simpleread = simpleread self.stream = stream # connect buttons self.connect( self.finishButton, qt4.SIGNAL('clicked()'), self.slotFinish ) self.connect( self.cancelButton, qt4.SIGNAL('clicked()'), self.slotCancel ) # timer which governs reading from source self.readtimer = qt4.QTimer(self) self.connect( self.readtimer, qt4.SIGNAL('timeout()'), self.slotReadTimer ) # record time capture started self.starttime = qt4.QTime() self.starttime.start() # sort tree by dataset name self.datasetTreeWidget.sortItems(0, qt4.Qt.AscendingOrder) # timer for updating display self.displaytimer = qt4.QTimer(self) self.connect( self.displaytimer, qt4.SIGNAL('timeout()'), self.slotDisplayTimer ) self.sourceLabel.setText( unicode(self.sourceLabel.text()) % stream.name ) self.txt_statusLabel = unicode(self.statusLabel.text()) self.slotDisplayTimer() # initialise label # timer to update document self.updatetimer = qt4.QTimer(self) self.updateoperation = None if updateinterval: self.connect( self.updatetimer, qt4.SIGNAL('timeout()'), self.slotUpdateTimer ) self.updatetimer.start( int(updateinterval*1000) ) # start display and read timers self.displaytimer.start(1000) self.readtimer.start(10) def slotReadTimer(self): """Time to read more data.""" try: self.simpleread.readData(self.stream) except document.CaptureFinishException, e: # stream tells us it's time to finish self.streamCaptureFinished( unicode(e) ) def slotDisplayTimer(self): """Time to update information about data source.""" self.statusLabel.setText( self.txt_statusLabel % (self.stream.bytesread, self.starttime.elapsed() // 1000) ) tree = self.datasetTreeWidget cts = self.simpleread.getDatasetCounts() # iterate over each dataset for name, length in cts.iteritems(): find = tree.findItems(name, qt4.Qt.MatchExactly, 0) if find: # if already in tree, update number of counts find[0].setText(1, str(length)) else: # add new item tree.addTopLevelItem( qt4.QTreeWidgetItem([name, str(length)])) def slotUpdateTimer(self): """Called to update document while data is being captured.""" # undo any previous update if self.updateoperation: self.updateoperation.undo(self.document) # create new one self.updateoperation = document.OperationDataCaptureSet( self.simpleread) # apply it (bypass history here - urgh) self.updateoperation.do(self.document) self.document.setModified() def streamCaptureFinished(self, message): """Stop timers, close stream and display message about finished stream.""" # stop reading / displaying self.readtimer.stop() self.displaytimer.stop() self.updatetimer.stop() if self.stream: # update stats self.slotDisplayTimer() # close stream self.stream.close() self.stream = None # show message from stream self.statusLabel.setText(message) def slotFinish(self): """Finish capturing and save the results.""" # close down timers self.streamCaptureFinished('') # undo any in-progress update if self.updateoperation: self.updateoperation.undo(self.document) # apply real document operation update op = document.OperationDataCaptureSet(self.simpleread) self.document.applyOperation(op) # close dialog self.close() def slotCancel(self): """Cancel capturing.""" # close down timers self.streamCaptureFinished('') # undo any in-progress update if self.updateoperation: self.updateoperation.undo(self.document) self.document.setModified() # close dialog self.close() veusz-1.15/dialogs/import_2d.ui0000644002344000001440000001542111734662204016462 0ustar jssusers00000000000000 tab2d 0 0 394 475 File preview: QTextEdit::NoWrap true 6 0 Datasets: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">A space separated list of dataset names to import from the file</p></body></html> Range of &X: twod_xminedit <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enter a number for the minimum coordinate of the X axis (default 0)</p></body></html> to <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enter a value for a maximum coordinate of the X axis (default is number of columns in file)</p></body></html> Range of &Y: twod_yminedit <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enter a number for the minimum coordinate of the Y axis (default 0)</p></body></html> to <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Enter a number for the maximum coordinate of the Y axis (default number of rows in file)</p></body></html> Invert &rows when reading Invert colu&mns when reading Trans&pose rows and columns Data are read as a 2D matrix from the file. X is read from left to right, Y from bottom to top, by default. These parameters listed here can be altered by including xrange A B, yrange A B, invertrows, invertcols and transpose as lines in the data file. Multiple datasets can be included by separating with blank lines. true HistoryCombo QComboBox
    historycombo.h
    veusz-1.15/dialogs/about.ui0000644002344000001440000001507511734662204015702 0ustar jssusers00000000000000 AboutDialog 0 0 412 398 About Veusz 6 9 QFrame::StyledPanel QFrame::Raised 6 9 logo Qt::AlignCenter <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Arial'; font-size:10pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt; font-weight:600; color:#800080;">Veusz %(version)s</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt; color:#000000;">Copyright © 2003-2012 Jeremy Sanders and contributors</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://home.gna.org/veusz/"><span style=" font-size:9pt; text-decoration: underline; color:#539fa3;">http://home.gna.org/veusz/</span></a></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Authors:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Jeremy Sanders &lt;jeremy@jeremysanders.net&gt;</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">James Graham &lt;jg307@cam.ac.uk&gt;</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Thanks to:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Benjamin K. Stuhl</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Bryan Harris</span></p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:9pt;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:9pt;">Veusz comes with ABSOLUTELY NO WARRANTY. Veusz is Free Software and you are entitled to distribute it under the terms of the GNU Public License (GPL). See the file COPYING for details, or click &quot;Show license&quot;.</span></p></body></html> Qt::AlignCenter true true 6 0 Qt::Horizontal 131 31 Show license false OK true okButton clicked() AboutDialog accept() 278 253 96 254 veusz-1.15/dialogs/import_fits.ui0000644002344000001440000001477311734662204017133 0ustar jssusers00000000000000 fitstab 0 0 264 376 6 0 List of HDUs in FITS file: true Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 3 HDU Name Type Dataset name to use: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Column or image specified is imported using the name given here</p></body></html> Data FITS column: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If importing a table, this is the column used to provide the main data</p></body></html> false Symmetric error FITS column: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Column used for symmetric errors, if appropriate</p></body></html> false Positive error FITS column: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Column used for positive errors, if appropriate</p></body></html> false Negative error FITS column: <html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-size:9pt; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Column used for negative errors, if appropriate</p></body></html> false HistoryCombo QComboBox
    historycombo.h
    veusz-1.15/dialogs/plugin.py0000644002344000001440000001656511734662204016106 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Dialog boxes for tools and dataset plugins.""" import sys from itertools import izip import veusz.qtall as qt4 import veusz.utils as utils import veusz.document as document import veusz.plugins as plugins import exceptiondialog import dataeditdialog from veuszdialog import VeuszDialog def handlePlugin(mainwindow, doc, pluginkls): """Show plugin dialog or directly execute (if it takes no parameters).""" plugin = pluginkls() if plugin.has_parameters: d = PluginDialog(mainwindow, doc, plugin, pluginkls) mainwindow.showDialog(d) else: fields = {'currentwidget': '/'} if mainwindow.treeedit.selwidgets: fields = {'currentwidget': mainwindow.treeedit.selwidgets[0].path} runPlugin(mainwindow, doc, plugin, fields) def wordwrap(text, linelength=80): """Wrap on a word boundary.""" out = [] l = 0 for w in text.split(' '): if w.find('\n') >= 0: l = 0 if l + len(w) > linelength: out.append('\n') l = 0 out.append(w) l += len(w) return ' '.join(out) class PluginDialog(VeuszDialog): """Dialog box class for plugins.""" def __init__(self, mainwindow, doc, plugininst, pluginkls): VeuszDialog.__init__(self, mainwindow, 'plugin.ui') reset = self.buttonBox.button(qt4.QDialogButtonBox.Reset) reset.setAutoDefault(False) reset.setDefault(False) self.connect(reset, qt4.SIGNAL('clicked()'), self.slotReset) self.connect( self.buttonBox.button(qt4.QDialogButtonBox.Apply), qt4.SIGNAL('clicked()'), self.slotApply ) self.pluginkls = pluginkls self.plugininst = plugininst self.document = doc title = ': '.join(list(plugininst.menu)) self.setWindowTitle(title) descr = plugininst.description_full if plugininst.author: descr += '\n Author: ' + plugininst.author self.descriptionLabel.setText( wordwrap(descr) ) self.fieldcntrls = [] self.fields = [] self.addFields() def addFields(self): """Add any fields, removing existing ones if required.""" layout = self.fieldGroup.layout() for line in self.fieldcntrls: for cntrl in line: layout.removeWidget(cntrl) cntrl.deleteLater() del self.fieldcntrls[:] currentwidget = '/' if self.mainwindow.treeedit.selwidgets: currentwidget = self.mainwindow.treeedit.selwidgets[0].path for row, field in enumerate(self.plugininst.fields): if isinstance(field, list) or isinstance(field, tuple): for c, f in enumerate(field): cntrls = f.makeControl(self.document, currentwidget) layout.addWidget(cntrls[0], row, c*2) layout.addWidget(cntrls[1], row, c*2+1) self.fieldcntrls.append(cntrls) self.fields.append(f) else: cntrls = field.makeControl(self.document, currentwidget) layout.addWidget(cntrls[0], row, 0) layout.addWidget(cntrls[1], row, 1) self.fieldcntrls.append(cntrls) self.fields.append(field) def slotReset(self): """Reset fields to defaults.""" self.addFields() def reEditDataset(self, ds, dsname): """Open up dataset in dialog for editing.""" oldfields = ds.pluginmanager.fields for field, cntrl in izip(self.fields, self.fieldcntrls): field.setControlVal(cntrl, oldfields[field.name]) def slotApply(self): """Use the plugin with the inputted data.""" # default field fields = {'currentwidget': '/'} if self.mainwindow.treeedit.selwidgets: fields = {'currentwidget': self.mainwindow.treeedit.selwidgets[0].path} # read values from controls for field, cntrls in izip(self.fields, self.fieldcntrls): fields[field.name] = field.getControlResults(cntrls) # run plugin plugin = self.pluginkls() statustext = runPlugin(self, self.document, plugin, fields) # show any results self.notifyLabel.setText(statustext) qt4.QTimer.singleShot(3000, self.notifyLabel.clear) def runPlugin(window, doc, plugin, fields): """Execute a plugin. window - parent window doc - veusz document plugin - plugin object.""" if isinstance(plugin, plugins.ToolsPlugin): mode = 'tools' elif isinstance(plugin, plugins.DatasetPlugin): mode = 'dataset' else: raise RuntimeError("Invalid plugin class") # use correct operation class for different plugin types if mode == 'tools': op = document.OperationToolsPlugin(plugin, fields) elif mode == 'dataset': # a bit of a hack as we don't give currentwidget to this plugin del fields['currentwidget'] op = document.OperationDatasetPlugin(plugin, fields) resultstext = '' qt4.QApplication.setOverrideCursor( qt4.QCursor(qt4.Qt.WaitCursor) ) try: results = doc.applyOperation(op) # evaluate datasets using plugin to check it works if mode == 'dataset': op.validate() resultstext = 'Created datasets: ' + ', '.join(results) else: resultstext = 'Done' except (plugins.ToolsPluginException, plugins.DatasetPluginException), ex: # unwind operations op.undo(doc) qt4.QApplication.restoreOverrideCursor() qt4.QMessageBox.warning( window, "Error in %s" % plugin.name, unicode(ex)) except Exception: op.undo(doc) qt4.QApplication.restoreOverrideCursor() # show exception dialog exceptiondialog.ExceptionDialog(sys.exc_info(), window).exec_() else: qt4.QApplication.restoreOverrideCursor() return resultstext def recreateDataset(mainwindow, document, dataset, datasetname): """Open dialog to recreate plugin dataset(s).""" # make a new instance of the plugin class kls = dataset.pluginmanager.plugin.__class__ newplugin = kls() dialog = PluginDialog(mainwindow, document, newplugin, kls) mainwindow.showDialog(dialog) dialog.reEditDataset(dataset, datasetname) dataeditdialog.recreate_register[document.Dataset1DPlugin] = recreateDataset dataeditdialog.recreate_register[document.Dataset2DPlugin] = recreateDataset dataeditdialog.recreate_register[document.DatasetTextPlugin] = recreateDataset veusz-1.15/dialogs/__init__.py0000644002344000001440000000173511734662204016340 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Veusz dialogs module.""" # load custom widgets import veusz.qtwidgets veusz-1.15/dialogs/stylesheet.ui0000644002344000001440000001227611734662204016761 0ustar jssusers00000000000000 StylesheetDialog 0 0 489 526 Default styles - Veusz 1 0 2 0 Qt::Vertical 0 0 &Properties true 0 0 195 301 0 0 &Formatting Save definitions to a vsz script file Save... false Load definitions from a vsz script file Load... false Recent false Qt::Horizontal 40 20 Qt::Horizontal QDialogButtonBox::Close RecentFilesButton QPushButton
    recentfilesbutton.h
    buttonBox accepted() StylesheetDialog accept() 248 254 157 274 buttonBox rejected() StylesheetDialog reject() 316 260 286 274
    veusz-1.15/dialogs/capturing.ui0000644002344000001440000000400411734662204016552 0ustar jssusers00000000000000 CapturingDialog 0 0 400 300 Capturing data - Veusz Reading from: %s %i bytes read in %i seconds false true Dataset Items Qt::Horizontal 40 20 &Finish &Cancel veusz-1.15/dialogs/import_csv.ui0000644002344000001440000001701211734662204016746 0ustar jssusers00000000000000 csvtab 0 0 548 491 File preview: QAbstractItemView::NoEditTriggers false Behaviour Header mode 'Multiple': allow multiple headers in file 'Single': 1st non-blank row is header 'None': no headers, guess data types &Direction csvdirectioncombo Are the data arranged in columns or rows? Columns Rows Ignore rows at top Ignore N row at the top of the file. If data are arranged in rows, ignores columns instead. Ignore rows after headers After reading a header, ignore N rows in that column. If Direction is set to Rows, ignore N columns instead. Treat blanks as data values Help on how CSV files should be formatted Help Locale Numerics Numerical format of numbers in file: System - what this computer is set to use English - format 123,456.78 European - format 123.456,78 Dates Format for dates and times in file. This will be combination of YYYY, YY, MM, M, DD, D, hh, h, mm, m, ss and s separated by | true Delimiters Column Delimiter between fields. This is usually a comma. true Text Character to delimit text, usually a quote ("). true HistoryCombo QComboBox
    historycombo.h
    HistorySpinBox QSpinBox
    historyspinbox.h
    HistoryCheck QCheckBox
    historycheck.h
    HistoryValueCombo QComboBox
    historyvaluecombo.h
    veusz-1.15/dialogs/importhelpcsv.ui0000644002344000001440000001676411734662204017475 0ustar jssusers00000000000000 ImportCSVHelpDialog 0 0 457 400 CSV import help true <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Arial'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:11pt; font-weight:600;">CSV (Comma Separated Value)</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">CSV files are often used to export data from applications such as Excel and OpenOffice.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Headers</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Place a dataset name at the top of each column. To import error bars, columns with the names &quot;+&quot;, &quot;-&quot; or &quot;+-&quot; should be given in columns immediately to the right of the dataset, for positive, negative or symmetric errors, respectively.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In the standard Multiple header mode, multiple datasets can be placed below each other if new names are given. If your data only consist of a single header, you can choose the Single header mode, which will prevent Veusz from starting new datasets if it sees text. If your data have no header, choose None and your columns will be named automatically.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Veusz can also read data organised in rows rather than columns (choose the rows option under Directions). Veusz can also ignore the specified number of lines in columns which follow header items.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Data types</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Veusz will try to guss the data type (numeric, text or date) depending on what values it sees. You can override this by putting the datatype name in brackets in the column header. If you want to read in text use (text) after the name of the dataset in the top column, e.g. &quot;name (text)&quot;. Date-times can have &quot; (date)&quot; after the column name. Veusz uses ISO dates by default YYYY-MM-DDThh:mm:ss, but this can be changed.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Options</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">There are several variants of CSV files used. You may wish to change the delimiter to be a tab (TSV) or space. Your file may also use European or English numerical values e.g. (1,23 or 1.23), so you may wish to override your computer's default format.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">There are many different date and time formats. You can enter your own format in the box given [a combination of YYYY (or YY), MM (or M), DD (or D), HH, MM and SS].</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The &quot;Treat blanks as data values&quot; option will insert NaN values or empty strings into datasets, if blank data values are encountered.</p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">If you are using non-ASCII characters in your text you need to encode the text in your file with a Unicode encoding (e.g. UTF-8) and choose the correct encoding in the encoding drop down box. </p> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> Qt::Horizontal QDialogButtonBox::Close buttonBox clicked(QAbstractButton*) ImportCSVHelpDialog close() 248 254 157 274 veusz-1.15/dialogs/custom.ui0000644002344000001440000001204211734662204016071 0ustar jssusers00000000000000 CustomDialog 0 0 609 464 Custom definitions Definitions A list of custom functions, constants or symbols imported from external Python modules. These can be used when plotting functions, creating new datasets and fitting data. Definitions are saved with the document and evaluated from the top down, in order. A file with default custom settings can be specified in the preferences dialog. true false Qt::Horizontal 40 20 Move up Move down &Add &Remove Save definitions to a vsz script file Save... Load definitions from a vsz script file Load... Recent false Qt::Horizontal 40 18 Qt::Horizontal QDialogButtonBox::Close RecentFilesButton QPushButton
    recentfilesbutton.h
    buttonBox accepted() CustomDialog accept() 248 254 157 274 buttonBox rejected() CustomDialog reject() 316 260 286 274
    veusz-1.15/dialogs/stylesheet.py0000644002344000001440000001204311734662204016764 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.utils as utils import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting from veusz.windows.treeeditwindow import TabbedFormatting, PropertyList, \ SettingsProxySingle from veuszdialog import VeuszDialog class StylesheetDialog(VeuszDialog): """This is a dialog box to edit stylesheets. Most of the work is done elsewhere, so this doesn't do a great deal """ def __init__(self, parent, document): VeuszDialog.__init__(self, parent, 'stylesheet.ui') self.document = document self.stylesheet = document.basewidget.settings.StyleSheet self.stylesListWidget.setMinimumWidth(100) # initial properties widget self.tabformat = None self.properties = None self.fillStyleList() self.connect(self.stylesListWidget, qt4.SIGNAL( 'currentItemChanged(QListWidgetItem *,QListWidgetItem *)'), self.slotStyleItemChanged) self.stylesListWidget.setCurrentRow(0) # we disable default buttons as they keep grabbing the enter key close = self.buttonBox.button(qt4.QDialogButtonBox.Close) close.setDefault(False) close.setAutoDefault(False) self.connect(self.saveButton, qt4.SIGNAL('clicked()'), self.slotSaveStyleSheet) self.connect(self.loadButton, qt4.SIGNAL('clicked()'), self.slotLoadStyleSheet) # recent button shows list of recently used files for loading self.connect(self.recentButton, qt4.SIGNAL('filechosen'), self.loadStyleSheet) self.recentButton.setSetting('stylesheetdialog_recent') def loadStyleSheet(self, filename): """Load the given stylesheet.""" self.document.applyOperation( document.OperationLoadStyleSheet(filename) ) def fillStyleList(self): """Fill list of styles.""" for stns in self.stylesheet.getSettingsList(): item = qt4.QListWidgetItem(utils.getIcon(stns.pixmap), stns.usertext) item.VZsettings = stns self.stylesListWidget.addItem(item) def slotStyleItemChanged(self, current, previous): """Item changed in list of styles.""" if current is None: return if self.tabformat: self.tabformat.deleteLater() if self.properties: self.properties.deleteLater() style = str(current.text()) settings = current.VZsettings # update formatting properties setnsproxy = SettingsProxySingle(self.document, settings) self.tabformat = TabbedFormatting(self.document, setnsproxy) self.formattingGroup.layout().addWidget(self.tabformat) # update properties self.properties = PropertyList(self.document, showformatsettings=False) self.properties.updateProperties(setnsproxy, showformatting=False) self.propertiesScrollArea.setWidget(self.properties) def slotSaveStyleSheet(self): """Save stylesheet as a file.""" filename = self.parent()._fileSaveDialog( 'vst', 'Veusz stylesheet', 'Save stylesheet') if filename: try: f = open(filename, 'w') self.document.exportStyleSheet(f) f.close() self.recentButton.addFile(filename) except EnvironmentError, e: qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to save '%s'\n\n%s" % (filename, e.strerror)) def slotLoadStyleSheet(self): """Load a style sheet.""" filename = self.parent()._fileOpenDialog( 'vst', 'Veusz stylesheet', 'Load stylesheet') if filename: try: self.loadStyleSheet(filename) except EnvironmentError, e: qt4.QMessageBox.critical( self, "Error - Veusz", "Unable to load '%s'\n\n%s" % (filename, e.strerror)) else: # add to recent file list self.recentButton.addFile(filename) veusz-1.15/dialogs/dataedit.ui0000644002344000001440000001232511734662204016342 0ustar jssusers00000000000000 Jeremy Sanders dataeditdialog 0 0 749 439 Edit data - Veusz 6 9 Qt::Horizontal false 6 0 6 0 0 0 true Linked file: None 0 0 Edit 0 0 Unlink 6 0 Qt::Horizontal 35 31 &Delete D&uplicate New false Crea&te... &Import... Qt::Horizontal QSizePolicy::Fixed 10 20 &Close closebutton clicked() dataeditdialog close() 369 253 179 282 veusz-1.15/utils/0000755002344000001440000000000011734662466013751 5ustar jssusers00000000000000veusz-1.15/utils/action.py0000644002344000001440000001242511734662204015572 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.qtall as qt4 import utilfuncs import os.path # where images are stored imagedir = os.path.join(utilfuncs.veuszDirectory, 'windows', 'icons') _pixmapcache = {} def getPixmap(pixmap): """Return a cached QPixmap for the filename in the icons directory.""" if pixmap not in _pixmapcache: _pixmapcache[pixmap] = qt4.QPixmap(os.path.join(imagedir, pixmap)) return _pixmapcache[pixmap] def pixmapExists(pixmap): """Does the pixmap exist?""" return (pixmap in _pixmapcache or os.path.exists(os.path.join(imagedir, pixmap))) _iconcache = {} def getIcon(icon): """Return a cached QIconSet for the filename in the icons directory.""" if icon not in _iconcache: svg = os.path.join(imagedir, icon+'.svg') if os.path.exists(svg): filename = svg else: filename = os.path.join(imagedir, icon+'.png') _iconcache[icon] = qt4.QIcon(filename) return _iconcache[icon] def makeAction(parent, descr, menutext, slot, icon=None, key=None, checkable=False): """A quick way to set up an QAction object.""" a = qt4.QAction(parent) a.setText(menutext) a.setStatusTip(descr) a.setToolTip(descr) if slot: parent.connect(a, qt4.SIGNAL('triggered()'), slot) if icon: a.setIcon(getIcon(icon)) if key: a.setShortcut( qt4.QKeySequence(key) ) if checkable: a.setCheckable(True) return a def addToolbarActions(toolbar, actions, which): """Add actions listed in "which" from dict "actions" to toolbar "toolbar". """ for w in which: toolbar.addAction(actions[w]) def constructMenus(rootobject, menuout, menutree, actions): """Add menus to the output dict from the tree, listing actions from actions. rootobject: QMenu or QMenuBar to add menus to menuout: dict to store menus menutree: tree structure to create menus from actions: dict of actions to assign to menu items """ for menuid, menutext, actlist in menutree: # make a new menu if necessary if menuid not in menuout: menu = rootobject.addMenu(menutext) menuout[menuid] = menu else: menu = menuout[menuid] # add actions to the menu for action in actlist: if hasattr(action, '__iter__'): # recurse for submenus constructMenus(menu, menuout, [action], actions) elif action == '': # blank means separator menu.addSeparator() else: # normal action menu.addAction(actions[action]) def populateMenuToolbars(items, toolbar, menus): """Construct the menus and toolbar from the list of items. toolbar is a QToolbar object menus is a dict of menus to add to Items are tuples consisting of: (actioname, status bar text, menu text, menu id, slot, icon filename, add to toolbar (bool), shortcut text) Returns a dict of actions """ actions = {} parent = toolbar.parent() for i in items: if len(i) == 1: if menus is not None: menus[i[0]].addSeparator() continue menuid, descr, menutext, menu, slot, icon, addtool, key = i # create action action = qt4.QAction(parent) action.setText(menutext) action.setStatusTip(descr) action.setToolTip(descr) # set shortcut if set if key: action.setShortcut( qt4.QKeySequence(key) ) # load icon if set if icon: action.setIcon(getIcon(icon)) if callable(slot): # connect the action to the slot if slot is not None: qt4.QObject.connect( action, qt4.SIGNAL('triggered()'), slot ) # add to menu if menus is not None: menus[menu].addAction(action) elif slot is not None: if menus is not None: submenu = menus[menu].addMenu(menutext) menus["%s.%s"%(menu ,menuid)] = submenu populateMenuToolbars(slot, toolbar, menus) else: if menus is not None: menus[menu].addAction(action) # add to toolbar if addtool and toolbar is not None: toolbar.addAction(action) # save for later actions[menuid] = action return actions veusz-1.15/utils/version.py0000644002344000001440000000224711734662204016003 0ustar jssusers00000000000000# version.py # return the version number # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Return Veusz' version number """ import utilfuncs import os.path def version(): """Return the version number as a string.""" f = open( os.path.join(utilfuncs.veuszDirectory, 'VERSION') ) return f.readline().strip() veusz-1.15/utils/slowfuncs.py0000644002344000001440000001734311734662204016344 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ These are slow versions of routines also implemented in C++ """ from itertools import izip, count import sys import struct import veusz.qtall as qt4 import numpy as N sys.stderr.write("Warning: Using slow substitutes for some functions. " "Bezier curves are disabled.\n" "Compile helpers to avoid this warning - " "see INSTALL document\n") def addNumpyToPolygonF(poly, *args): """Add a set of numpy arrays to a QPolygonF. The first argument is the QPolygonF to add to Subsequent arguments should be pairs of x and y coordinate arrays """ # we stick the datasets next to each other horizontally, then # reshape it into an array of x, y pairs minlen = min([x.shape[0] for x in args]) cols = N.hstack([N.reshape(x[:minlen], (minlen, 1)) for x in args]) points = N.reshape(cols, (minlen*len(args)/2, 2)) # finally draw the points pappend = poly.append qpointf = qt4.QPointF for p in points: pappend( qpointf(*p) ) def addNumpyPolygonToPath(path, clip, *args): """Add a set of polygons to a path, clipping (optionally).""" if clip is None: clip = qt4.QRectF(qt4.QPointF(-32767,-32767),qt4.QPointF(32767,32767)) else: clip = qt4.QRectF(clip) for i in count(): p = qt4.QPolygonF() dobreak = True for c in xrange(len(args)/2): if i < len(args[c*2]) and i < len(args[c*2+1]): p.append(qt4.QPointF(args[c*2][i], args[c*2+1][i])) dobreak = False if dobreak: break else: if clip is None: path.addPolygon(p) else: cp = qt4.QPolygonF() polygonClip(p, clip, cp) path.addPolygon(cp) path.closeSubpath() def plotPathsToPainter(painter, path, x, y, scaling=None, clip=None, colorimg=None): """Plot array of x, y points.""" if clip is None: clip = qt4.QRectF(qt4.QPointF(-32767,-32767),qt4.QPointF(32767,32767)) else: clip = qt4.QRectF(clip) # adjust bounding box by size of path pathbox = path.boundingRect() clip.adjust(pathbox.left(), pathbox.top(), pathbox.bottom(), pathbox.right()) # draw the paths numpts = min(len(x), len(y)) if scaling is not None: numpts = min(numpts, len(scaling)) if colorimg is not None: numpts = min(numpts, colorimg.width()) origtrans = painter.worldTransform() for i in xrange(numpts): pt = qt4.QPointF(x[i], y[i]) if clip.contains(pt): painter.translate(pt) # scale if wanted if scaling is not None: painter.scale(scaling[i], scaling[i]) # set color if given if colorimg is not None: b = qt4.QBrush( qt4.QColor.fromRgba(colorimg.pixel(i, 0)) ) painter.setBrush(b) painter.drawPath(path) painter.setWorldTransform(origtrans) def plotLinesToPainter(painter, x1, y1, x2, y2, clip=None, autoexpand=True): """Plot lines given in numpy arrays to painter.""" lines = [] lappend = lines.append qlinef = qt4.QLineF for p in izip(x1, y1, x2, y2): lappend( qlinef(*p) ) painter.drawLines(lines) def plotClippedPolyline(painter, cliprect, pts, autoexpand=True): """Draw a polyline, trying to clip the points. The python version does nothing really as it would be too hard. """ ptsout = qt4.QPolygonF() for p in pts: x = max( min(p.x(), 32767.), -32767.) y = max( min(p.y(), 32767.), -32767.) ptsout.append( qt4.QPointF(x, y) ) painter.drawPolyline(ptsout) def polygonClip(inpoly, rect, outpoly): """Clip a polygon to the rectangle given, writing to outpoly The python version does nothing really as it would be too hard. """ for p in inpoly: x = max( min(p.x(), 32767.), -32767.) y = max( min(p.y(), 32767.), -32767.) outpoly.append( qt4.QPointF(x, y) ) def plotClippedPolygon(painter, inrect, inpoly, autoexpand=True): """Plot a polygon, clipping if necessary.""" outpoly = qt4.QPolygonF() polygonClip(inpoly, inrect, outpoly) painter.drawPolygon(outpoly) def plotBoxesToPainter(painter, x1, y1, x2, y2, clip=None, autoexpand=True): """Plot a set of rectangles.""" if clip is None: clip = qt4.QRectF(qt4.QPointF(-32767,-32767), qt4.QPointF(32767,32767)) # expand clip by line width if autoexpand: clip = qt4.QRectF(clip) lw = painter.pen().widthF() clip.adjust(-lw, -lw, lw, lw) # construct rectangle list rects = [] for ix1, iy1, ix2, iy2 in izip(x1, y1, x2, y2): rect = qt4.QRectF( qt4.QPointF(ix1, iy1), qt4.QPointF(ix2, iy2) ) if clip.intersects(rect): rects.append(clip.intersected(rect)) # paint it if rects: painter.drawRects(rects) def slowNumpyToQImage(img, cmap, transparencyimg): """Slow version of routine to convert numpy array to QImage This is hard work in Python, but it was like this originally. img: numpy array to convert to QImage cmap: 2D array of colors (BGRA rows) forcetrans: force image to have alpha component.""" if struct.pack("h", 1) == "\000\001": # have to swap colors for big endian architectures cmap2 = cmap.copy() cmap2[:,0] = cmap[:,3] cmap2[:,1] = cmap[:,2] cmap2[:,2] = cmap[:,1] cmap2[:,3] = cmap[:,0] cmap = cmap2 fracs = N.clip(N.ravel(img), 0., 1.) # Work out which is the minimum colour map. Assumes we have <255 bands. numbands = cmap.shape[0]-1 bands = (fracs*numbands).astype(N.uint8) bands = N.clip(bands, 0, numbands-1) # work out fractional difference of data from band to next band deltafracs = (fracs - bands * (1./numbands)) * numbands # need to make a 2-dimensional array to multiply against triplets deltafracs.shape = (deltafracs.shape[0], 1) # calculate BGRalpha quadruplets # this is a linear interpolation between the band and the next band quads = (deltafracs*cmap[bands+1] + (1.-deltafracs)*cmap[bands]).astype(N.uint8) # apply transparency if a transparency image is set if transparencyimg is not None and transparencyimg.shape == img.shape: quads[:,3] = ( N.clip(N.ravel(transparencyimg), 0., 1.) * quads[:,3] ).astype(N.uint8) # convert 32bit quads to a Qt QImage s = quads.tostring() fmt = qt4.QImage.Format_RGB32 if N.any(cmap[:,3] != 255) or transparencyimg is not None: # any transparency fmt = qt4.QImage.Format_ARGB32 img = qt4.QImage(s, img.shape[1], img.shape[0], fmt) img = img.mirrored() # hack to ensure string isn't freed before QImage img.veusz_string = s return img veusz-1.15/utils/textrender.py0000644002344000001440000006661511734662204016513 0ustar jssusers00000000000000# textrender.py # module to render text, tries to understand a basic LateX-like syntax # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import math import re import numpy as N import veusz.qtall as qt4 import points # this definition is monkey-patched when veusz is running in self-test # mode as we need to hack the metrics - urgh FontMetrics = qt4.QFontMetricsF # lookup table for special symbols symbols = { # escaped characters r'\_': '_', r'\^': '^', r'\{': '{', r'\}': '}', r'\[': '[', r'\]': ']', r'\backslash' : u'\u005c', # operators r'\pm': u'\u00b1', r'\mp': u'\u2213', r'\times': u'\u00d7', r'\cdot': u'\u22c5', r'\ast': u'\u2217', r'\star': u'\u22c6', r'\deg': u'\u00b0', r'\divide': u'\u00f7', r'\dagger': u'\u2020', r'\ddagger': u'\u2021', r'\cup': u'\u22c3', r'\cap': u'\u22c2', r'\uplus': u'\u228e', r'\vee': u'\u22c1', r'\wedge': u'\u22c0', r'\nabla': u'\u2207', r'\lhd': u'\u22b2', r'\rhd': u'\u22b3', r'\unlhd': u'\u22b4', r'\unrhd': u'\u22b5', r'\oslash': u'\u2298', r'\odot': u'\u0e4f', r'\oplus': u'\u2295', r'\ominus': u'\u2296', r'\otimes': u'\u2297', r'\diamond': u'\u22c4', r'\bullet': u'\u2022', r'\AA': u'\u212b', r'\sqrt': u'\u221a', r'\propto': u'\u221d', r'\infty': u'\u221e', r'\int': u'\u222b', r'\leftarrow': u'\u2190', r'\Leftarrow': u'\u21d0', r'\uparrow': u'\u2191', r'\rightarrow': u'\u2192', r'\to': u'\u2192', r'\Rightarrow': u'\u21d2', r'\downarrow': u'\u2193', r'\leftrightarrow': u'\u2194', r'\Leftrightarrow': u'\u21d4', r'\circ': u'\u0e50', # relations r'\le': u'\u2264', r'\ge': u'\u2265', r'\neq': u'\u2260', r'\sim': u'\u223c', r'\ll': u'\u226a', r'\gg': u'\u226b', r'\doteq': u'\u2250', r'\simeq': u'\u2243', r'\subset': u'\u2282', r'\supset': u'\u2283', r'\approx': u'\u2248', r'\asymp': u'\u224d', r'\subseteq': u'\u2286', r'\supseteq': u'\u2287', r'\sqsubset': u'\u228f', r'\sqsupset': u'\u2290', r'\sqsubseteq': u'\u2291', r'\sqsupseteq': u'\u2292', r'\in': u'\u2208', r'\ni': u'\u220b', r'\equiv': u'\u2261', r'\prec': u'\u227a', r'\succ': u'\u227b', r'\preceq': u'\u227c', r'\succeq': u'\u227d', r'\bowtie': u'\u22c8', r'\vdash': u'\u22a2', r'\dashv': u'\u22a3', r'\models': u'\u22a7', r'\perp': u'\u22a5', r'\parallel': u'\u2225', r'\umid': u'\u2223', # lower case greek letters r'\alpha': u'\u03b1', r'\beta': u'\u03b2', r'\gamma': u'\u03b3', r'\delta': u'\u03b4', r'\epsilon': u'\u03b5', r'\zeta': u'\u03b6', r'\eta': u'\u03b7', r'\theta': u'\u03b8', r'\iota': u'\u03b9', r'\kappa': u'\u03ba', r'\lambda': u'\u03bb', r'\mu': u'\u03bc', r'\nu': u'\u03bd', r'\xi': u'\u03be', r'\omicron': u'\u03bf', r'\pi': u'\u03c0', r'\rho': u'\u03c1', r'\stigma': u'\u03c2', r'\sigma': u'\u03c3', r'\tau': u'\u03c4', r'\upsilon': u'\u03c5', r'\phi': u'\u03c6', r'\chi': u'\u03c7', r'\psi': u'\u03c8', r'\omega': u'\u03c9', # upper case greek letters r'\Alpha': u'\u0391', r'\Beta': u'\u0392', r'\Gamma': u'\u0393', r'\Delta': u'\u0394', r'\Epsilon': u'\u0395', r'\Zeta': u'\u0396', r'\Eta': u'\u0397', r'\Theta': u'\u0398', r'\Iota': u'\u0399', r'\Kappa': u'\u039a', r'\Lambda': u'\u039b', r'\Mu': u'\u039c', r'\Nu': u'\u039d', r'\Xi': u'\u039e', r'\Omicron': u'\u039f', r'\Pi': u'\u03a0', r'\Rho': u'\u03a1', r'\Sigma': u'\u03a3', r'\Tau': u'\u03a4', r'\Upsilon': u'\u03a5', r'\Phi': u'\u03a6', r'\Chi': u'\u03a7', r'\Psi': u'\u03a8', r'\Omega': u'\u03a9' } class RenderState(object): """Holds the state of the rendering.""" def __init__(self, font, painter, x, y, alignhorz, actually_render=True): self.font = font self.painter = painter self.device = painter.device() self.x = x # current x position self.y = y # current y position self.alignhorz = alignhorz self.actually_render = actually_render self.maxlines = 1 # maximim number of lines drawn def fontMetrics(self): """Returns font metrics object.""" return FontMetrics(self.font, self.device) def getPixelsPerPt(self): """Return number of pixels per point in the rendering.""" painter = self.painter pixperpt = painter.device().logicalDpiY() / 72. try: pixperpt *= painter.scaling except AttributeError: pass return pixperpt class Part(object): """Represents a part of the text to be rendered, made up of smaller parts.""" def __init__(self, children): self.children = children def render(self, state): for p in self.children: p.render(state) class PartText(Part): """Fundamental bit of text to be rendered: some text.""" def __init__(self, text): self.text = text def addText(self, text): self.text += text def render(self, state): """Render some text.""" width = state.fontMetrics().width(self.text) # actually write the text if requested if state.actually_render: state.painter.drawText( qt4.QPointF(state.x, state.y), self.text ) # move along, nothing to see state.x += width class PartLines(Part): """Render multiple lines.""" def __init__(self, children): Part.__init__(self, children) self.widths = [] def render(self, state): """Render multiple lines.""" # record widths of individual lines if not state.actually_render: self.widths = [] height = state.fontMetrics().height() inity = state.y initx = state.x state.y -= height*(len(self.children)-1) # iterate over lines (reverse as we draw from bottom up) for i, part in enumerate(self.children): if state.actually_render and self.widths: xwidth = max(self.widths) # if we're rendering, use max width to justify line if state.alignhorz < 0: # left alignment state.x = initx elif state.alignhorz == 0: # centre alignment state.x = initx + (xwidth - self.widths[i])*0.5 elif state.alignhorz > 0: # right alignment state.x = initx + (xwidth - self.widths[i]) else: # if not, just left justify to get widths state.x = initx # render the line itself part.render(state) # record width if we're not rendering if not state.actually_render: self.widths.append( state.x - initx ) # move up a line state.y += height # move on x posn if self.widths: state.x = initx + max(self.widths) else: state.x = initx state.y = inity # keep track of number of lines rendered state.maxlines = max(state.maxlines, len(self.children)) class PartSuperScript(Part): """Represents superscripted part.""" def render(self, state): font = state.font painter = state.painter # change text height oldheight = state.fontMetrics().height() size = font.pointSizeF() font.setPointSizeF(size*0.6) painter.setFont(font) # set position oldy = state.y state.y -= oldheight*0.4 # draw children Part.render(self, state) # restore font and position state.y = oldy font.setPointSizeF(size) painter.setFont(font) class PartFrac(Part): """"A fraction, do latex \frac{a}{b}.""" def render(self, state): if len(self.children) != 2: return font = state.font painter = state.painter # make font half size size = font.pointSizeF() font.setPointSizeF(size*0.5) painter.setFont(font) # keep track of width above and below line if not state.actually_render: self.widths = [] initx = state.x inity = state.y # render bottom of fraction if state.actually_render and len(self.widths) == 2: # centre line state.x = initx + (max(self.widths) - self.widths[0])*0.5 self.children[1].render(state) if not state.actually_render: # get width if not rendering self.widths.append(state.x - initx) # render top of fraction m = state.fontMetrics() state.y -= (m.ascent() + m.descent()) if state.actually_render and len(self.widths) == 2: # centre line state.x = initx + (max(self.widths) - self.widths[1])*0.5 else: state.x = initx self.children[0].render(state) if not state.actually_render: self.widths.append(state.x - initx) state.x = initx + max(self.widths) state.y = inity # restore font font.setPointSizeF(size) painter.setFont(font) height = state.fontMetrics().ascent() # draw line between lines with 0.5pt thickness painter.save() pen = painter.pen() painter.setPen( qt4.QPen(painter.pen().brush(), state.getPixelsPerPt()*0.5) ) painter.setPen(pen) painter.drawLine(qt4.QPointF(initx, inity-height/2.), qt4.QPointF(initx+max(self.widths), inity-height/2)) painter.restore() class PartSubScript(Part): """Represents subscripted part.""" def render(self, state): font = state.font # change text height size = font.pointSizeF() font.setPointSizeF(size*0.6) state.painter.setFont(font) # set position oldy = state.y state.y += state.fontMetrics().descent() # draw children Part.render(self, state) # restore font and position state.y = oldy font.setPointSizeF(size) state.painter.setFont(font) class PartMultiScript(Part): """Represents multiple parts with the same starting x, e.g. a combination of super- and subscript parts.""" def render(self, state): oldx = state.x newx = oldx for p in self.children: state.x = oldx p.render(state) newx = max([state.x, newx]) state.x = newx def append(p): self.children.append(p) class PartItalic(Part): """Represents italic part.""" def render(self, state): font = state.font font.setItalic( not font.italic() ) state.painter.setFont(font) Part.render(self, state) font.setItalic( not font.italic() ) state.painter.setFont(font) class PartBold(Part): """Represents bold part.""" def render(self, state): font = state.font font.setBold( not font.bold() ) state.painter.setFont(font) Part.render(self, state) font.setBold( not font.bold() ) state.painter.setFont(font) class PartUnderline(Part): """Represents underlined part.""" def render(self, state): font = state.font font.setUnderline( not font.underline() ) state.painter.setFont(font) Part.render(self, state) font.setUnderline( not font.underline() ) state.painter.setFont(font) class PartFont(Part): """Change font name in part.""" def __init__(self, children): try: self.fontname = children[0].text except AttributeError: self.fontname = '' self.children = children[1:] def render(self, state): font = state.font oldfamily = font.family() font.setFamily(self.fontname) state.painter.setFont(font) Part.render(self, state) font.setFamily(oldfamily) state.painter.setFont(font) class PartSize(Part): """Change font size in part.""" def __init__(self, children): self.size = None self.deltasize = None # convert size try: size = children[0].text.replace('pt', '') # crap code if size[:1] in '+-': # is a modification of font size self.deltasize = float(size) else: # is an absolute font size self.size = float(size) except AttributeError, ValueError: self.deltasize = 0. self.children = children[1:] def render(self, state): font = state.font size = oldsize = font.pointSizeF() if self.size: # absolute size size = self.size elif self.deltasize: # change of size size = max(size+self.deltasize, 0.1) font.setPointSizeF(size) state.painter.setFont(font) Part.render(self, state) font.setPointSizeF(oldsize) state.painter.setFont(font) class PartBar(Part): """Draw a bar over text.""" def render(self, state): initx = state.x # draw material under bar Part.render(self, state) # draw line over text with 0.5pt thickness painter = state.painter height = state.fontMetrics().ascent() painter.save() pen = painter.pen() penw = state.getPixelsPerPt()*0.5 painter.setPen( qt4.QPen(painter.pen().brush(), penw) ) painter.drawLine(qt4.QPointF(initx, state.y-height+penw), qt4.QPointF(state.x, state.y-height+penw)) painter.restore() class PartDot(Part): """Draw a dot over text.""" def render(self, state): initx = state.x # draw material under bar Part.render(self, state) # draw circle over text with 1pt radius painter = state.painter height = state.fontMetrics().ascent() painter.save() circsize = state.getPixelsPerPt() painter.setBrush( qt4.QBrush(painter.pen().color()) ) painter.setPen( qt4.QPen(qt4.Qt.NoPen) ) x = 0.5*(initx + state.x) y = state.y-height + circsize painter.drawEllipse( qt4.QRectF( qt4.QPointF(x-circsize,y-circsize), qt4.QPointF(x+circsize,y+circsize)) ) painter.restore() class PartMarker(Part): """Draw a marker symbol.""" def render(self, state): painter = state.painter size = state.fontMetrics().ascent() painter.save() pen = painter.pen() pen.setWidthF( state.getPixelsPerPt() * 0.5 ) painter.setPen(pen) try: points.plotMarker( painter, state.x + size/2., state.y - size/2., self.children[0].text, size*0.3) except ValueError: pass painter.restore() state.x += size class PartColor(Part): def __init__(self, children): try: self.colorname = children[0].text except AttributeError: self.colorname = '' self.children = children[1:] def render(self, state): painter = state.painter pen = painter.pen() oldcolor = pen.color() pen.setColor( qt4.QColor(self.colorname) ) painter.setPen(pen) Part.render(self, state) pen.setColor(oldcolor) painter.setPen(pen) # a dict of latex commands, the part object they correspond to, # and the number of arguments part_commands = { '^': (PartSuperScript, 1), '_': (PartSubScript, 1), r'\italic': (PartItalic, 1), r'\emph': (PartItalic, 1), r'\bold': (PartBold, 1), r'\underline': (PartUnderline, 1), r'\textbf': (PartBold, 1), r'\textit': (PartItalic, 1), r'\font': (PartFont, 2), r'\size': (PartSize, 2), r'\frac': (PartFrac, 2), r'\bar': (PartBar, 1), r'\overline': (PartBar, 1), r'\dot': (PartDot, 1), r'\marker': (PartMarker, 1), r'\color': (PartColor, 2), } # split up latex expression into bits splitter_re = re.compile(r''' ( \\[A-Za-z]+[ ]* | # normal latex command \\[\[\]{}_^] | # escaped special characters \\\\ | # line end \{ | # begin block \} | # end block \^ | # power _ # subscript ) ''', re.VERBOSE) def makePartList(text): """Make list of parts from text""" parts = [] parents = [parts] def doAdd(p): """Add the part at the correct level.""" parents[-1].append(p) return p for p in splitter_re.split(text): if p[:1] == '\\': # we may need to drop excess spaces after \foo commands ps = p.rstrip() if ps in symbols: # it will become a symbol, so preserve whitespace doAdd(ps) if ps != p: doAdd(p[len(ps)-len(p):]) else: # add as possible command, so drop excess whitespace doAdd(ps) elif p == '{': # add a new level parents.append( doAdd([]) ) elif p == '}': if len(parents) > 1: parents.pop() elif p: # if not blank, keep it doAdd(p) return parts def makePartTree(partlist): """Make a tree of parts from the part list.""" lines = [] itemlist = [] length = len(partlist) def addText(text): """Try to merge consecutive text items for better rendering.""" if itemlist and isinstance(itemlist[-1], PartText): itemlist[-1].addText(text) else: itemlist.append( PartText(text) ) i = 0 while i < length: p = partlist[i] if p == r'\\': lines.append( Part(itemlist) ) itemlist = [] elif isinstance(p, basestring): if p in symbols: addText(symbols[p]) elif p in part_commands: klass, numargs = part_commands[p] if numargs == 1 and len(partlist) > i+1 and isinstance(partlist[i+1], basestring): # coerce a single argument to a partlist so that things # like "A^\dagger" render correctly without needing # curly brackets partargs = [makePartTree([partlist[i+1]])] else: partargs = [makePartTree(k) for k in partlist[i+1:i+numargs+1]] if (p == '^' or p == '_'): if len(itemlist) > 0 and ( isinstance(itemlist[-1], PartSubScript) or isinstance(itemlist[-1], PartSuperScript) or isinstance(itemlist[-1], PartMultiScript)): # combine sequences of multiple sub-/superscript parts into # a MultiScript item so that a single text item can have # both super and subscript indicies # e.g. X^{(q)}_{i} if isinstance(itemlist[-1], PartMultiScript): itemlist.append( klass(partargs) ) else: itemlist[-1] = PartMultiScript([itemlist[-1], klass(partargs)]) else: itemlist.append( klass(partargs) ) else: itemlist.append( klass(partargs) ) i += numargs else: addText(p) else: itemlist.append( makePartTree(p) ) i += 1 # remaining items lines.append( Part(itemlist) ) if len(lines) == 1: # single line, so optimize (itemlist == lines[0] still) if len(itemlist) == 1: # try to flatten any excess layers return itemlist[0] else: return lines[0] else: return PartLines(lines) class Renderer(object): """A class for rendering text. The class emulates latex-like formatting, allows rotated text, and alignment """ def __init__(self, painter, font, x, y, text, alignhorz = -1, alignvert = -1, angle = 0, usefullheight = False): """Initialise the renderer. painter is the painter to draw on font is the starting font to use x and y are the x and y positions to draw the text at alignhorz = (-1, 0, 1) for (left, centre, right) alignment alignvert = (-1, 0, 1) for (above, centre, below) alignment angle is the angle to draw the text at usefullheight means include descenders in calculation of height of text alignment is in the painter frame, not the text frame """ # save things we'll need later self.painter = painter self.font = font self.alignhorz = alignhorz self.alignvert = alignvert self.angle = angle self.usefullheight = usefullheight #self.text = text partlist = makePartList(text) self.parttree = makePartTree(partlist) self.x = x self.y = y self.calcbounds = None # debug position #self.painter.drawPoint( qt4.QPointF(self.x, self.y) ) def getBounds(self): """Get bounds of text on screen.""" if self.calcbounds is not None: return self.calcbounds # work out total width and height self.painter.setFont(self.font) # work out height of box, and # make the bounding box a bit bigger if we want to include descents state = RenderState(self.font, self.painter, 0, 0, self.alignhorz, actually_render = False) fm = state.fontMetrics() if self.usefullheight: totalheight = fm.ascent() dy = fm.descent() else: if self.alignvert == 0: # if want vertical centering, better to centre around middle # of typical letter (i.e. where strike position is) #totalheight = fm.strikeOutPos()*2 totalheight = fm.boundingRect(qt4.QChar('0')).height() else: # if top/bottom alignment, better to use maximum letter height totalheight = fm.ascent() dy = 0 # work out width self.parttree.render(state) totalwidth = state.x totalheight += fm.height()*(state.maxlines-1) # in order to work out text position, we rotate a bounding box # in fact we add two extra points to account for descent if reqd tw = totalwidth / 2 th = totalheight / 2 coordx = N.array( [-tw, tw, tw, -tw, -tw, tw ] ) coordy = N.array( [ th, th, -th, -th, th+dy, th+dy] ) # rotate angles by theta theta = -self.angle * (math.pi / 180.) c = math.cos(theta) s = math.sin(theta) newx = coordx*c + coordy*s newy = coordy*c - coordx*s # calculate bounding box newbound = (newx.min(), newy.min(), newx.max(), newy.max()) # use rotated bounding box to find position of start text posn if self.alignhorz < 0: xr = ( self.x, self.x+(newbound[2]-newbound[0]) ) self.xi = self.x + (newx[0] - newbound[0]) elif self.alignhorz > 0: xr = ( self.x-(newbound[2]-newbound[0]), self.x ) self.xi = self.x + (newx[0] - newbound[2]) else: xr = ( self.x+newbound[0], self.x+newbound[2] ) self.xi = self.x + newx[0] # y alignment # adjust y by these values to ensure proper alignment if self.alignvert < 0: yr = ( self.y + (newbound[1]-newbound[3]), self.y ) self.yi = self.y + (newy[0] - newbound[3]) elif self.alignvert > 0: yr = ( self.y, self.y + (newbound[3]-newbound[1]) ) self.yi = self.y + (newy[0] - newbound[1]) else: yr = ( self.y+newbound[1], self.y+newbound[3] ) self.yi = self.y + newy[0] self.calcbounds = [xr[0], yr[0], xr[1], yr[1]] return self.calcbounds def ensureInBox(self, minx = -32767, maxx = 32767, miny = -32767, maxy = 32767, extraspace = False): """Adjust position of text so that it is within this box.""" if self.calcbounds is None: self.getBounds() cb = self.calcbounds # add a small amount of extra room if requested if extraspace: self.painter.setFont(self.font) l = FontMetrics(self.font, self.painter.device()).height()*0.2 miny += l # twiddle positions and bounds if cb[2] > maxx: dx = cb[2] - maxx self.xi -= dx cb[2] -= dx cb[0] -= dx if cb[0] < minx: dx = minx - cb[0] self.xi += dx cb[2] += dx cb[0] += dx if cb[3] > maxy: dy = cb[3] - maxy self.yi -= dy cb[3] -= dy cb[1] -= dy if cb[1] < miny: dy = miny - cb[1] self.yi += dy cb[3] += dy cb[1] += dy def getDimensions(self): """Get the (w, h) of the bounding box.""" if self.calcbounds is None: self.getBounds() cb = self.calcbounds return (cb[2]-cb[0]+1, cb[3]-cb[1]+1) def render(self): """Render the text.""" if self.calcbounds is None: self.getBounds() state = RenderState(self.font, self.painter, self.xi, self.yi, self.alignhorz) # if the text is rotated, change the coordinate frame if self.angle != 0: self.painter.save() self.painter.translate( qt4.QPointF(state.x, state.y) ) self.painter.rotate(self.angle) state.x = 0 state.y = 0 # actually paint the string self.painter.setFont(self.font) self.parttree.render(state) # restore coordinate frame if text was rotated if self.angle != 0: self.painter.restore() # caller might want this information return self.calcbounds veusz-1.15/utils/points.py0000644002344000001440000004556111734662204015640 0ustar jssusers00000000000000# points.py - functions to plot points # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### from itertools import izip import veusz.qtall as qt4 import numpy as N try: from veusz.helpers.qtloops import plotPathsToPainter except ImportError: from slowfuncs import plotPathsToPainter import colormap """This is the symbol plotting part of Veusz There are actually several different ways symbols are plotted. We choose the most appropriate one for the shape: QPainterPath symbols plotted with _plotPathSymbols line symbols are plotted with _plotLineSymbols ploygon symbols are plotted wiht _plotPolygonSymbols Many of these are implemented as paths internally, and drawn using QPainterPaths """ ####################################################################### ## draw symbols which are sets of line segments linesymbols = { 'asterisk': ( ((-0.707, -0.707), (0.707, 0.707)), ((-0.707, 0.707), (0.707, -0.707)), ((-1, 0), (1, 0)), ((0, -1), (0, 1)) ), 'lineplus': ( ((-1, 0), (1, 0)), ((0, -1), (0, 1)) ), 'linecross': ( ((-0.707, -0.707), (0.707, 0.707)), ((-0.707, 0.707), (0.707, -0.707)) ), 'plushair': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)), ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)) ), 'crosshair': ( ((-0.707, -0.707), (-0.354, -0.354)), (( 0.707, 0.707), ( 0.354, 0.354)), (( 0.707, -0.707), ( 0.354, -0.354)), ((-0.707, 0.707), (-0.354, 0.354)) ), 'asteriskhair': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)), ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)), ((-0.707, -0.707), (-0.354, -0.354)), (( 0.707, 0.707), ( 0.354, 0.354)), (( 0.707, -0.707), ( 0.354, -0.354)), ((-0.707, 0.707), (-0.354, 0.354)) ), 'linehorz': ( ((-1, 0), (1, 0)), ), 'linevert': ( ((0, -1), (0, 1)), ), 'linehorzgap': ( ((-1, 0), (-0.5, 0)), ((1, 0), (0.5, 0)) ), 'linevertgap': ( ((0, -1), (0, -0.5)), ((0, 1), (0, 0.5)) ), 'arrowleft': ( ((1, -1), (0, 0), (1, 1)), ((2, 0), (0, 0)) ), 'arrowleftaway': ( ((-1, -1), (-2, 0), (-1, 1)), ((-2, 0), (0, 0)) ), 'arrowright': ( ((-1, -1), (0, 0), (-1, 1)), ((-2, 0), (0, 0)) ), 'arrowrightaway': ( ((1, -1), (2, 0), (1, 1)), ((2, 0), (0, 0)) ), 'arrowup': ( ((-1, 1), (0, 0), (1, 1)), ((0, 2), (0, 0)) ), 'arrowupaway': ( ((-1, -1), (0, -2), (1, -1)), ((0, 0), (0, -2)) ), 'arrowdown': ( ((-1, -1), (0, 0), (1, -1)), ((0, -2), (0, 0)) ), 'arrowdownaway': ( ((-1, 1), (0, 2), (1, 1)), ((0, 0), (0, 2)) ), 'limitlower': ( ((-1, -1), (0, 0), (1, -1)), ((0, -2), (0, 0)), ((-1, 0), (1, 0)) ), 'limitupper': ( ((-1, 1), (0, 0), (1, 1)), ((0, 2), (0, 0)), ((-1, 0), (1, 0)) ), 'limitleft': ( ((1, -1), (0, 0), (1, 1)), ((2, 0), (0, 0)), ((0, -1), (0, 1)) ), 'limitright': ( ((-1, -1), (0, 0), (-1, 1)), ((-2, 0), (0, 0)), ((0, -1), (0, 1)) ), 'limitupperaway': ( ((-1, -1), (0, -2), (1, -1)), ((0, 0), (0, -2)), ((-1, 0), (1, 0)) ), 'limitloweraway': ( ((-1, 1), (0, 2), (1, 1)), ((0, 0), (0, 2)), ((-1, 0), (1, 0)) ), 'limitleftaway': ( ((-1, -1), (-2, 0), (-1, 1)), ((-2, 0), (0, 0)), ((0, -1), (0, 1)) ), 'limitrightaway': ( ((1, -1), (2, 0), (1, 1)), ((2, 0), (0, 0)), ((0, -1), (0, 1)) ), # for arrows '_linearrow': ( ((-1.8, -1), (0, 0), (-1.8, 1)), ), '_linearrowreverse': ( ((1.8, -1), (0, 0), (1.8, 1)), ), } def getLinePainterPath(name, size): """Get a painter path for line like objects.""" path = qt4.QPainterPath() for lines in linesymbols[name]: path.moveTo(lines[0][0]*size, lines[0][1]*size) for x, y in lines[1:]: path.lineTo(x*size, y*size) return path ####################################################################### ## draw symbols which are polygons # X and Y pts for corners of polygons polygons = { # make the diamond the same area as the square 'diamond': ( (0., 1.414), (1.414, 0.), (0., -1.414), (-1.414, 0.) ), 'barhorz': ( (-1, -0.5), (1, -0.5), (1, 0.5), (-1, 0.5) ), 'barvert': ( (-0.5, -1), (0.5, -1), (0.5, 1), (-0.5, 1) ), 'plus': ( (0.4, 1), (0.4, 0.4), (1, 0.4), (1, -0.4), (0.4, -0.4), (0.4, -1), (-0.4, -1), (-0.4, -0.4), (-1, -0.4), (-1, 0.4), (-0.4, 0.4), (-0.4, 1) ), 'octogon': ( (0.414, 1), (1, 0.414), (1, -0.414), (0.414, -1), (-0.414, -1), (-1, -0.414), (-1, 0.414), (-0.414, 1) ), 'triangle': ( (0, -1.2), (1.0392, 0.6), (-1.0392, 0.6) ), 'triangledown': ( (0, 1.2), (1.0392, -0.6), (-1.0392, -0.6) ), 'triangleleft': ( (-1.2, 0), (0.6, 1.0392), (0.6, -1.0392) ), 'triangleright': ( (1.2, 0), (-0.6, 1.0392), (-0.6, -1.0392) ), 'cross': ( (-0.594, 1.1028), (0, 0.5088), (0.594, 1.1028), (1.1028, 0.594), (0.5088, -0), (1.1028, -0.594), (0.594, -1.1028), (-0, -0.5088), (-0.594, -1.1028), (-1.1028, -0.594), (-0.5088, 0), (-1.1028, 0.594) ), 'star': ( (0, -1.2), (-0.27, -0.3708), (-1.1412, -0.3708), (-0.4356, 0.1416), (-0.7056, 0.9708), (-0, 0.4584), (0.7056, 0.9708), (0.4356, 0.1416), (1.1412, -0.3708), (0.27, -0.3708) ), 'pentagon': ((0, -1.2), (1.1412, -0.3708), (0.6936, 0.9708), (-0.6936, 0.9708), (-1.1412, -0.3708)), 'tievert': ( (-1, -1), (1, -1), (-1, 1), (1, 1) ), 'tiehorz': ( (-1, -1), (-1, 1), (1, -1), (1, 1) ), 'lozengehorz': ( (0, 0.707), (1.414, 0), (0, -0.707), (-1.414, 0) ), 'lozengevert': ( (0, 1.414), (0.707, 0), (0, -1.414), (-0.707, 0) ), 'star3': ( (0., -1.), (0.173, -0.1), (0.866, 0.5), (0, 0.2), (-0.866, 0.5), (-0.173, -0.1) ), 'star4': ( (0.000, 1.000), (-0.354, 0.354), (-1.000, 0.000), (-0.354, -0.354), (0.000, -1.000), (0.354, -0.354), (1.000, -0.000), (0.354, 0.354), ), 'star6': ( (0.000, 1.000), (-0.250, 0.433), (-0.866, 0.500), (-0.500, 0.000), (-0.866, -0.500), (-0.250, -0.433), (-0.000, -1.000), (0.250, -0.433), (0.866, -0.500), (0.500, 0.000), (0.866, 0.500), (0.250, 0.433), ), 'star8': ( (0.000, 1.000), (-0.191, 0.462), (-0.707, 0.707), (-0.462, 0.191), (-1.000, 0.000), (-0.462, -0.191), (-0.707, -0.707), (-0.191, -0.462), (0.000, -1.000), (0.191, -0.462), (0.707, -0.707), (0.462, -0.191), (1.000, -0.000), (0.462, 0.191), (0.707, 0.707), (0.191, 0.462), ), 'hexagon': ( (0, 1), (0.866, 0.5), (0.866, -0.5), (0, -1), (-0.866, -0.5), (-0.866, 0.5), ), 'starinvert': ( (0, 1.2), (-0.27, 0.3708), (-1.1412, 0.3708), (-0.4356, -0.1416), (-0.7056, -0.9708), (0, -0.4584), (0.7056, -0.9708), (0.4356, -0.1416), (1.1412, 0.3708), (0.27, 0.3708) ), 'squashbox': ( (-1, 1), (0, 0.5), (1, 1), (0.5, 0), (1, -1), (0, -0.5), (-1, -1), (-0.5, 0) ), 'plusnarrow': ( (0.2, 1), (0.2, 0.2), (1, 0.2), (1, -0.2), (0.2, -0.2), (0.2, -1), (-0.2, -1), (-0.2, -0.2), (-1, -0.2), (-1, 0.2), (-0.2, 0.2), (-0.2, 1) ), 'crossnarrow': ( (-0.566, 0.849), (0, 0.283), (0.566, 0.849), (0.849, 0.566), (0.283, 0), (0.849, -0.566), (0.566, -0.849), (0, -0.283), (-0.566, -0.849), (-0.849, -0.566), (-0.283, 0), (-0.849, 0.566) ), 'limitupperaway2': ( (-1, 0), (0, 0), (0, -1), (-1, -1), (0, -2), (1, -1), (0, -1), (0, 0), (1, 0) ), 'limitloweraway2': ( (-1, 0), (0, 0), (0, 1), (-1, 1), (0, 2), (1, 1), (0, 1), (0, 0), (1, 0) ), 'limitleftaway2': ( (0, -1), (0, 0) , (-1, 0), (-1, -1), (-2, 0), (-1, 1), (-1, 0), (0, 0), (0, 1) ), 'limitrightaway2': ( (0, -1), (0, 0), (1, 0), (1, -1), (2, 0), (1, 1), (1, 0), (0, 0), (0, 1) ), # special arrow symbols '_arrow': ( (0, 0), (-1.8, 1), (-1.4, 0), (-1.8, -1) ), '_arrowtriangle': ( (0, 0), (-1.8, 1), (-1.8, -1) ), '_arrownarrow': ( (0, 0), (-1.8, 0.5), (-1.8, -0.5) ), '_arrowreverse': ( (0, 0), (1.8, 1), (1.4, 0), (1.8, -1) ), } def addPolyPath(path, vals): """Add a polygon with the list of x,y pts as tuples in vals.""" poly = qt4.QPolygonF() for x, y in vals: poly.append( qt4.QPointF(x, y) ) path.addPolygon(poly) path.closeSubpath() def getPolygonPainterPath(name, size): """Create a poly path for a polygon.""" path = qt4.QPainterPath() addPolyPath(path, N.array(polygons[name])*size) return path ####################################################################### ## draw symbols using a QPainterPath def squarePath(painter, path, size): """Square path of size given.""" path.addRect( qt4.QRectF(-size, -size, size*2, size*2) ) def circlePath(painter, path, size): """Circle path of size given.""" path.addEllipse( qt4.QRectF(-size, -size, size*2, size*2) ) def circlePlusPath(painter, path, size): """Circle path with plus.""" path.addEllipse( qt4.QRectF(-size, -size, size*2, size*2) ) path.moveTo(0, -size) path.lineTo(0, size) path.moveTo(-size, 0) path.lineTo(size, 0) def circleCrossPath(painter, path, size): """Circle path with cross.""" path.addEllipse( qt4.QRectF(-size, -size, size*2, size*2) ) m = N.sqrt(2.)*size*0.5 path.moveTo(-m, -m) path.lineTo(m, m) path.moveTo(-m, m) path.lineTo(m, -m) def circlePairPathHorz(painter, path, size): """2 circles next to each other (horizontal).""" path.addEllipse( qt4.QRectF(-size, -size*0.5, size, size) ) path.addEllipse( qt4.QRectF(0, -size*0.5, size, size) ) def circlePairPathVert(painter, path, size): """2 circles next to each other (vertical).""" path.addEllipse( qt4.QRectF(-size*0.5, -size, size, size) ) path.addEllipse( qt4.QRectF(-size*0.5, 0, size, size) ) def ellipseHorzPath(painter, path, size): """Horizontal ellipse path.""" path.addEllipse( qt4.QRectF(-size, -size*0.5, size*2, size) ) def ellipseVertPath(painter, path, size): """Vertical ellipse path.""" path.addEllipse( qt4.QRectF(-size*0.5, -size, size, size*2) ) def circleHolePath(painter, path, size): """Circle with centre missing.""" circlePath(painter, path, size) circlePath(painter, path, size*0.5) def squarePlusPath(painter, path, size): """Square with plus sign.""" path.addRect( qt4.QRectF(-size, -size, size*2, size*2) ) path.moveTo(0, -size) path.lineTo(0, size) path.moveTo(-size, 0) path.lineTo(size, 0) def squareCrossPath(painter, path, size): """Square with cross sign.""" path.addRect( qt4.QRectF(-size, -size, size*2, size*2) ) path.moveTo(-size, -size) path.lineTo(size, size) path.moveTo(-size, size) path.lineTo(size, -size) def squareHolePath(painter, path, size): """Square with centre missing.""" path.addRect( qt4.QRectF(-size, -size, size*2, size*2) ) path.addRect( qt4.QRectF(-size*0.5, -size*0.5, size, size) ) def diamondHolePath(painter, path, size): """Diamond with centre missing.""" pts = N.array(polygons['diamond'])*size addPolyPath(path, pts) addPolyPath(path, pts*0.5) def pentagonHolePath(painter, path, size): """Pentagon with centre missing.""" pts = N.array(polygons['pentagon'])*size addPolyPath(path, pts) addPolyPath(path, pts*0.5) def squareRoundedPath(painter, path, size): """A square with rounded corners.""" path.addRoundRect( qt4.QRectF(-size, -size, size*2, size*2), 50, 50 ) def dotPath(painter, path, size): """Draw a dot.""" w = painter.pen().widthF() path.addEllipse( qt4.QRectF(-w*0.5, -w*0.5, w, w) ) def bullseyePath(painter, path, size): """A filled circle inside a filled circle.""" path.setFillRule(qt4.Qt.WindingFill) circlePath(painter, path, size) circlePath(painter, path, size*0.5) def circleDotPath(painter, path, size): """A dot inside a circle.""" path.setFillRule(qt4.Qt.WindingFill) circlePath(painter, path, size) dotPath(painter, path, size) pathsymbols = { 'square': squarePath, 'circle': circlePath, 'circleplus': circlePlusPath, 'circlecross': circleCrossPath, 'circlepairhorz': circlePairPathHorz, 'circlepairvert': circlePairPathVert, 'ellipsehorz': ellipseHorzPath, 'ellipsevert': ellipseVertPath, 'circlehole': circleHolePath, 'squareplus': squarePlusPath, 'squarecross': squareCrossPath, 'squarehole': squareHolePath, 'diamondhole': diamondHolePath, 'pentagonhole': pentagonHolePath, 'squarerounded': squareRoundedPath, 'dot': dotPath, 'bullseye': bullseyePath, 'circledot': circleDotPath, } def getSymbolPainterPath(painter, name, size): """Get a painter path for a symbol shape.""" path = qt4.QPainterPath() pathsymbols[name](painter, path, size) return path def getPainterPath(painter, name, size): """Return a painter path for the name and size. Returns (painterpath, enablefill).""" if name in linesymbols: return getLinePainterPath(name, size), False elif name in polygons: return getPolygonPainterPath(name, size), True elif name in pathsymbols: return getSymbolPainterPath(painter, name, size), True elif name == 'none': return qt4.QPainterPath(), True else: raise ValueError, "Invalid marker name %s" % name ####################################################################### ## external interfaces # list of codes supported MarkerCodes = ( 'none', 'circle', 'diamond', 'square', 'cross', 'plus', 'star', 'barhorz', 'barvert', 'pentagon', 'hexagon', 'octogon', 'tievert', 'tiehorz', 'triangle', 'triangledown', 'triangleleft', 'triangleright', 'dot', 'circledot', 'bullseye', 'circlehole', 'squarehole', 'diamondhole', 'pentagonhole', 'squarerounded', 'squashbox', 'ellipsehorz', 'ellipsevert', 'lozengehorz', 'lozengevert', 'plusnarrow', 'crossnarrow', 'circleplus', 'circlecross', 'squareplus', 'squarecross', 'star3', 'star4', 'star6', 'star8', 'starinvert', 'circlepairhorz', 'circlepairvert', 'asterisk', 'lineplus', 'linecross', 'plushair', 'crosshair', 'asteriskhair', 'linevert', 'linehorz', 'linevertgap', 'linehorzgap', 'arrowleft', 'arrowright', 'arrowup', 'arrowdown', 'arrowleftaway', 'arrowrightaway', 'arrowupaway', 'arrowdownaway', 'limitupper', 'limitlower', 'limitleft', 'limitright', 'limitupperaway', 'limitloweraway', 'limitleftaway', 'limitrightaway', 'limitupperaway2', 'limitloweraway2', 'limitleftaway2', 'limitrightaway2', ) def plotMarkers(painter, xpos, ypos, markername, markersize, scaling=None, clip=None, cmap=None, colorvals=None): """Funtion to plot an array of markers on a painter. painter: QPainter xpos, ypos: iterable item of positions markername: name of marker from MarkerCodes markersize: size of marker to plot scaling: scale size of markers by array, or don't in None clip: rectangle if clipping wanted cmap: colormap to use if colorvals is set colorvals: color values 0-1 of each point if used """ # minor optimization if markername == 'none': return painter.save() # get sharper angles and more exact positions using these settings pen = painter.pen() pen.setJoinStyle( qt4.Qt.MiterJoin ) painter.setPen(pen) # get path to draw and whether to fill path, fill = getPainterPath(painter, markername, markersize) if not fill: # turn off brush painter.setBrush( qt4.QBrush() ) # if using colored points colorimg = None if colorvals is not None: # convert colors to rgb values via a 2D image and pass to function trans = (1-painter.brush().color().alphaF())*100 color2d = colorvals.reshape( 1, len(colorvals) ) colorimg = colormap.applyColorMap( cmap, 'linear', color2d, 0., 1., trans) # this is the fast (C++) or slow (python) helper plotPathsToPainter(painter, path, xpos, ypos, scaling, clip, colorimg) painter.restore() def plotMarker(painter, xpos, ypos, markername, markersize): """Function to plot a marker on a painter, posn xpos, ypos, type and size """ plotMarkers(painter, (xpos,), (ypos,), markername, markersize) # translate arrow shapes to point types (we reuse them) arrow_translate = { 'none': 'none', 'arrow': '_arrow', 'arrownarrow': '_arrownarrow', 'arrowtriangle': '_arrowtriangle', 'arrowreverse': '_arrowreverse', 'linearrow': '_linearrow', 'linearrowreverse': '_linearrowreverse', 'bar': 'linevert', 'linecross': 'linecross', 'asterisk': 'asterisk', 'circle': 'circle', 'square': 'square', 'diamond': 'diamond', } # codes of allowable arrows ArrowCodes = ( 'none', 'arrow', 'arrownarrow', 'arrowtriangle', 'arrowreverse', 'linearrow', 'linearrowreverse', 'bar', 'linecross', 'asterisk', 'circle', 'square', 'diamond', ) def plotLineArrow(painter, xpos, ypos, length, angle, arrowsize=0, arrowleft='none', arrowright='none'): """Plot a line or arrow. xpos, ypos is the starting point of the line angle is the angle to the horizontal (degrees) arrowleft and arrowright are arrow codes.""" painter.save() painter.translate(xpos, ypos) painter.rotate(angle) # draw line between points painter.drawLine( qt4.QPointF(0., 0.), qt4.QPointF(length, 0.) ) # plot marker at one end of line plotMarker(painter, length, 0., arrow_translate[arrowright], arrowsize) # plot reversed marker at other end painter.scale(-1, 1) plotMarker(painter, 0., 0., arrow_translate[arrowleft], arrowsize) painter.restore() veusz-1.15/utils/safe_eval.py0000644002344000001440000004363411734662204016250 0ustar jssusers00000000000000# Copyright (C) 2007 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """ 'Safe' python code evaluation Based on the public domain code of Babar K. Zafar http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496746 (version 0.1 or 1.2 May 27 2006) The idea is to examine the compiled ast tree and chack for invalid entries I have removed the timeout checking as this probably isn't a serious problem for veusz documents """ import inspect, compiler.ast import thread, time import __builtin__ import os.path import numpy as N #---------------------------------------------------------------------- # Module globals. #---------------------------------------------------------------------- # Toggle module level debugging mode. DEBUG = False # List of all AST node classes in compiler/ast.py. all_ast_nodes = [name for (name, obj) in inspect.getmembers(compiler.ast) if inspect.isclass(obj) and issubclass(obj, compiler.ast.Node)] # List of all builtin functions and types (ignoring exception classes). all_builtins = [name for (name, obj) in inspect.getmembers(__builtin__) if inspect.isbuiltin(obj) or (inspect.isclass(obj) and not issubclass(obj, Exception))] #---------------------------------------------------------------------- # Utilties. #---------------------------------------------------------------------- def classname(obj): return obj.__class__.__name__ def get_node_lineno(node): return (node.lineno) and node.lineno or 0 #---------------------------------------------------------------------- # Restricted AST nodes & builtins. #---------------------------------------------------------------------- # Deny evaluation of code if the AST contain any of the following nodes: unallowed_ast_nodes = ( # 'Add', 'And', # 'AssAttr', 'AssList', 'AssName', 'AssTuple', # 'Assert', 'Assign', 'AugAssign', 'Backquote', # 'Bitand', 'Bitor', 'Bitxor', 'Break', # 'CallFunc', 'Class', 'Compare', 'Const', 'Continue', # 'Decorators', 'Dict', 'Discard', 'Div', # 'Ellipsis', 'EmptyNode', 'Exec', # 'Expression', 'FloorDiv', # 'For', 'From', # 'Function', # 'GenExpr', 'GenExprFor', 'GenExprIf', 'GenExprInner', # 'Getattr', 'Global', 'If', 'Import', # 'Invert', # 'Keyword', 'Lambda', 'LeftShift', # 'List', 'ListComp', 'ListCompFor', 'ListCompIf', 'Mod', # 'Module', # 'Mul', 'Name', 'Node', 'Not', 'Or', 'Pass', 'Power', # 'Print', 'Printnl', 'Raise', # 'Return', 'RightShift', 'Slice', 'Sliceobj', # 'Stmt', 'Sub', 'Subscript', 'TryExcept', 'TryFinally', # 'Tuple', 'UnaryAdd', 'UnarySub', # 'While','Yield' ) # Deny evaluation of code if it tries to access any of the following builtins: unallowed_builtins = ( '__import__', # 'abs', 'apply', 'basestring', 'bool', 'buffer', # 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', # 'complex', 'delattr', # 'dict', 'dir', # 'divmod', 'enumerate', 'eval', 'execfile', 'file', # 'filter', 'float', 'frozenset', 'getattr', 'globals', 'hasattr', # 'hash', 'hex', 'id', 'input', # 'int', 'intern', 'isinstance', 'issubclass', 'iter', # 'len', 'list', 'locals', # 'long', 'map', 'max', 'min', 'object', 'oct', 'open', # 'ord', 'pow', 'property', 'range', 'raw_input', # 'reduce', 'reload', # 'repr', 'reversed', 'round', 'set', 'setattr', # 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', # 'tuple', 'type', 'unichr', 'unicode', 'vars', # 'xrange', 'zip' ) # checks there are no obvious mistakes above #for ast_name in unallowed_ast_nodes: # assert ast_name in all_ast_nodes #for name in unallowed_builtins: # assert name in all_builtins # faster lookup unallowed_ast_nodes = dict( (i, True) for i in unallowed_ast_nodes ) unallowed_builtins = dict( (i, True) for i in unallowed_builtins ) #---------------------------------------------------------------------- # Restricted attributes. #---------------------------------------------------------------------- # In addition to these we deny access to all lowlevel attrs (__xxx__). unallowed_attr = ( 'im_class', 'im_func', 'im_self', 'func_code', 'func_defaults', 'func_globals', 'func_name', 'tb_frame', 'tb_next', 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_globals', 'f_locals' ) unallowed_attr = dict( (i, True) for i in unallowed_attr ) def is_unallowed_attr(name): if name == '__file__': return False return ( (name[:2] == '__' and name[-2:] == '__') or (name in unallowed_attr) ) #---------------------------------------------------------------------- # SafeEvalVisitor. #---------------------------------------------------------------------- class SafeEvalError(object): """ Base class for all which occur while walking the AST. Attributes: errmsg = short decription about the nature of the error lineno = line offset to where error occured in source code """ def __init__(self, errmsg, lineno): self.errmsg, self.lineno = errmsg, lineno def __str__(self): return "line %d : %s" % (self.lineno, self.errmsg) class SafeEvalASTNodeError(SafeEvalError): "Expression/statement in AST evaluates to a restricted AST node type." pass class SafeEvalBuiltinError(SafeEvalError): "Expression/statement in tried to access a restricted builtin." pass class SafeEvalAttrError(SafeEvalError): "Expression/statement in tried to access a restricted attribute." pass class SafeEvalVisitor(object): """ Data-driven visitor which walks the AST for some code and makes sure it doesn't contain any expression/statements which are declared as restricted in 'unallowed_ast_nodes'. We'll also make sure that there aren't any attempts to access/lookup restricted builtin declared in 'unallowed_builtins'. By default we also won't allow access to lowlevel stuff which can be used to dynamically access non-local envrioments. Interface: walk(ast) = validate AST and return True if AST is 'safe' Attributes: errors = list of SafeEvalError if walk() returned False Implementation: The visitor will automatically generate methods for all of the available AST node types and redirect them to self.ok or self.fail reflecting the configuration in 'unallowed_ast_nodes'. While walking the AST we simply forward the validating step to each of node callbacks which take care of reporting errors. """ def __init__(self): "Initialize visitor by generating callbacks for all AST node types." self.errors = [] for ast_name in all_ast_nodes: # Don't reset any overridden callbacks. if not getattr(self, 'visit' + ast_name, None): if ast_name in unallowed_ast_nodes: setattr(self, 'visit' + ast_name, self.fail) else: setattr(self, 'visit' + ast_name, self.ok) def walk(self, ast): "Validate each node in AST and return True if AST is 'safe'." self.visit(ast) return self.errors == [] def visit(self, node, *args): "Recursively validate node and all of its children." fn = getattr(self, 'visit' + classname(node)) if DEBUG: self.trace(node) fn(node, *args) for child in node.getChildNodes(): self.visit(child, *args) def visitName(self, node, *args): "Disallow any attempts to access a restricted builtin/attr." name = node.getChildren()[0] lineno = get_node_lineno(node) if name in unallowed_builtins: self.errors.append(SafeEvalBuiltinError( \ "access to builtin '%s' is denied" % name, lineno)) elif is_unallowed_attr(name): self.errors.append(SafeEvalAttrError( \ "access to attribute '%s' is denied" % name, lineno)) def visitGetattr(self, node, *args): "Disallow any attempts to access a restricted attribute." name = node.attrname lineno = get_node_lineno(node) if is_unallowed_attr(name): self.errors.append(SafeEvalAttrError( \ "access to attribute '%s' is denied" % name, lineno)) def ok(self, node, *args): "Default callback for 'harmless' AST nodes." pass def fail(self, node, *args): "Default callback for unallowed AST nodes." lineno = get_node_lineno(node) self.errors.append(SafeEvalASTNodeError( \ "execution of '%s' statements is denied" % classname(node), lineno)) def trace(self, node): "Debugging utility for tracing the validation of AST nodes." print classname(node) for attr in dir(node): if attr[:2] != '__': print ' ' * 4, "%-15.15s" % attr, getattr(node, attr) ########################################################################## # Veusz evaluation functions ########################################################################## def checkContextOkay(context): """Check the context statements will be executed in. Returns True if context is okay """ ctx_errkeys, ctx_errors = [], [] for (key, obj) in context.items(): if inspect.isbuiltin(obj): ctx_errkeys.append(key) ctx_errors.append("key '%s' : unallowed builtin %s" % (key, obj)) if inspect.ismodule(obj): ctx_errkeys.append(key) ctx_errors.append("key '%s' : unallowed module %s" % (key, obj)) if ctx_errors: raise SafeEvalContextException(ctx_errkeys, ctx_errors) # # set up environment in dict # veusz_eval_context = {} # # add callables (not modules) and floats which don't override builtins # for name, val in N.__dict__.iteritems(): # if ( (callable(val) or type(val)==float) and # name not in __builtins__ and # name[:1] != '_' and name[-1:] != '_' ): # veusz_eval_context[name] = val # # useful safe functions # veusz_eval_context['os_path_join'] = os.path.join # veusz_eval_context['os_path_dirname'] = os.path.dirname def _filterExceptions(errs, securityonly): """Remove python exceptions from error list.""" if securityonly: errs = [e for e in errs if (isinstance(e, SafeEvalException) or isinstance(e, SafeEvalError))] if errs: return errs else: return None def checkCode(code, securityonly=False): """Check code, returning errors (if any) or None if okay. if securityonly is set, then don't return errors from Python exceptions. """ # compiler can't parse strings with unicode code = code.encode('utf8') try: ast = compiler.parse(code) except SyntaxError, e: return _filterExceptions([e], securityonly) checker = SafeEvalVisitor() checker.walk(ast) return _filterExceptions(checker.errors, securityonly) #---------------------------------------------------------------------- # Safe 'eval' replacement. #---------------------------------------------------------------------- class SafeEvalException(Exception): "Base class for all safe-eval related errors." pass class SafeEvalCodeException(SafeEvalException): """ Exception class for reporting all errors which occured while validating AST for source code in safe_eval(). Attributes: code = raw source code which failed to validate errors = list of SafeEvalError """ def __init__(self, code, errors): self.code, self.errors = code, errors def __str__(self): return '\n'.join([str(err) for err in self.errors]) class SafeEvalContextException(SafeEvalException): """ Exception class for reporting unallowed objects found in the dict intended to be used as the local enviroment in safe_eval(). Attributes: keys = list of keys of the unallowed objects errors = list of strings describing the nature of the error for each key in 'keys' """ def __init__(self, keys, errors): self.keys, self.errors = keys, errors def __str__(self): return '\n'.join([str(err) for err in self.errors]) class SafeEvalTimeoutException(SafeEvalException): """ Exception class for reporting that code evaluation execeeded the given timelimit. Attributes: timeout = time limit in seconds """ def __init__(self, timeout): self.timeout = timeout def __str__(self): return "Timeout limit execeeded (%s secs) during exec" % self.timeout def exec_timed(code, context, timeout_secs): """ Dynamically execute 'code' using 'context' as the global enviroment. SafeEvalTimeoutException is raised if execution does not finish within the given timelimit. """ assert(timeout_secs > 0) signal_finished = False def alarm(secs): def wait(secs): for n in xrange(timeout_secs): time.sleep(1) if signal_finished: break else: thread.interrupt_main() thread.start_new_thread(wait, (secs,)) try: alarm(timeout_secs) exec code in context signal_finished = True except KeyboardInterrupt: raise SafeEvalTimeoutException(timeout_secs) def timed_safe_eval(code, context = {}, timeout_secs = 5): """ Validate source code and make sure it contains no unauthorized expression/statements as configured via 'unallowed_ast_nodes' and 'unallowed_builtins'. By default this means that code is not allowed import modules or access dangerous builtins like 'open' or 'eval'. If code is considered 'safe' it will be executed via 'exec' using 'context' as the global environment. More details on how code is executed can be found in the Python Reference Manual section 6.14 (ignore the remark on '__builtins__'). The 'context' enviroment is also validated and is not allowed to contain modules or builtins. The following exception will be raised on errors: if 'context' contains unallowed objects = SafeEvalContextException if code is didn't validate and is considered 'unsafe' = SafeEvalCodeException if code did not execute within the given timelimit = SafeEvalTimeoutException """ ctx_errkeys, ctx_errors = [], [] for (key, obj) in context.items(): if inspect.isbuiltin(obj): ctx_errkeys.append(key) ctx_errors.append("key '%s' : unallowed builtin %s" % (key, obj)) if inspect.ismodule(obj): ctx_errkeys.append(key) ctx_errors.append("key '%s' : unallowed module %s" % (key, obj)) if ctx_errors: raise SafeEvalContextException(ctx_errkeys, ctx_errors) ast = compiler.parse(code) checker = SafeEvalVisitor() if checker.walk(ast): exec_timed(code, context, timeout_secs) else: raise SafeEvalCodeException(code, checker.errors) #---------------------------------------------------------------------- # Basic tests. #---------------------------------------------------------------------- import unittest class TestSafeEval(unittest.TestCase): def test_builtin(self): # attempt to access a unsafe builtin self.assertRaises(SafeEvalException, timed_safe_eval, "open('test.txt', 'w')") def test_getattr(self): # attempt to get arround direct attr access self.assertRaises(SafeEvalException, \ timed_safe_eval, "getattr(int, '__abs__')") def test_func_globals(self): # attempt to access global enviroment where fun was defined self.assertRaises(SafeEvalException, \ timed_safe_eval, "def x(): pass; print x.func_globals") def test_lowlevel(self): # lowlevel tricks to access 'object' self.assertRaises(SafeEvalException, \ timed_safe_eval, "().__class__.mro()[1].__subclasses__()") def test_timeout_ok(self): # attempt to exectute 'slow' code which finishes within timelimit def test(): time.sleep(2) env = {'test':test} timed_safe_eval("test()", env, timeout_secs = 5) def test_timeout_exceed(self): # attempt to exectute code which never teminates self.assertRaises(SafeEvalException, \ timed_safe_eval, "while 1: pass") def test_invalid_context(self): # can't pass an enviroment with modules or builtins env = {'f' : __builtins__.open, 'g' : time} self.assertRaises(SafeEvalException, \ timed_safe_eval, "print 1", env) def test_callback(self): # modify local variable via callback self.value = 0 def test(): self.value = 1 env = {'test':test} timed_safe_eval("test()", env) self.assertEqual(self.value, 1) if __name__ == "__main__": unittest.main() #---------------------------------------------------------------------- # End unittests #---------------------------------------------------------------------- veusz-1.15/utils/extbrushfilling.py0000644002344000001440000002212011734662204017517 0ustar jssusers00000000000000# Copyright (C) 2012 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Paint fills with extended brush class. Paints solid, hatching and various qt brushes """ import numpy as N import veusz.qtall as qt4 import math import utilfuncs try: from veusz.helpers.qtloops import plotLinesToPainter, polygonClip except ImportError: from slowfuncs import plotLinesToPainter, polygonClip def dumppath(p): i =0 while i < p.elementCount(): e = p.elementAt(i) if e.isLineTo(): print " l(%i,%i)" %( e.x, e.y), i += 1 elif e.isMoveTo(): print " m(%i,%i)" %(e.x, e.y), i += 1 else: print " c(%i,%i,%i,%i)" %(e.x, e.y, p.elementAt(i+1).x, p.elementAt(i+1).y, p.elementAt(i+2).x, p.elementAt(i+2).y), i += 3 print def _hatcher(painter, pen, painterpath, spacing, hatchlist): """Draw hatching on painter path given.""" painter.save() painter.setPen(pen) # debugging # dumppath(painterpath) painter.setClipPath(painterpath, qt4.Qt.IntersectClip) # this is the bounding box of the path bb = painter.clipPath().boundingRect() for params in hatchlist: # scale values offsetx, offsety, deltax, deltay = [x*spacing for x in params] # compute max number of steps numsteps = 0 if deltax != 0: numsteps += int(bb.width() / abs(deltax)) + 4 if deltay != 0: numsteps += int(bb.height() / abs(deltay)) + 4 # calculate repeat position in x and y # we start the positions of lines at multiples of these distances # to ensure the pattern doesn't shift if the area does # distances between lines mag = math.sqrt(deltax**2 + deltay**2) # angle to x of perpendicular lines theta = math.atan2(deltay, deltax) s, c = math.sin(theta), math.cos(theta) intervalx = intervaly = 1. if abs(c) > 1e-4: intervalx = abs(mag / c) if abs(s) > 1e-4: intervaly = abs(mag / s) startx = int(bb.left() / intervalx) * intervalx + offsetx starty = int(bb.top() / intervaly) * intervaly + offsety # normalise line vector linedx, linedy = -deltay/mag, deltax/mag # scale of lines from start position scale = max( bb.width(), bb.height() ) * 4 # construct points along lines # this is scales to ensure the lines are bigger than the box idx = N.arange(-numsteps, numsteps) x = idx*deltax + startx y = idx*deltay + starty x1 = x - scale*linedx x2 = x + scale*linedx y1 = y - scale*linedy y2 = y + scale*linedy # plot lines, clipping to bb plotLinesToPainter(painter, x1, y1, x2, y2, bb) painter.restore() # list of fill styles extfillstyles = ( 'solid', 'horizontal', 'vertical', 'cross', 'forward diagonals', 'backward diagonals', 'diagonal cross', 'forward 2', 'backward 2', 'forward 3', 'backward 3', 'forward 4', 'backward 4', 'forward 5', 'backward 5', 'diagonal cross 2', 'diagonal cross 3', 'diagonal cross 4', 'diagonal cross 5', 'vertical forward', 'vertical backward', 'horizontal forward', 'horizontal backward', 'star', 'triangles 1', 'triangles 2', 'triangles 3', 'triangles 4', 'horizontal double', 'vertical double', 'forward double', 'backward double', 'double cross', 'double diagonal cross', '94% dense', '88% dense', '63% dense', '50% dense', '37% dense', '12% dense', '6% dense', ) # hatching styles # map names to lists of (offsetx, offsety, gradx, grady) _hatchmap = { 'horizontal': ((0, 0, 0, 1), ), 'vertical': ((0, 0, 1, 0), ), 'cross': ( (0, 0, 0, 1), (0, 0, 1, 0), ), 'forward diagonals': ( (0, 0, 0.7071, -0.7071), ), 'backward diagonals': ( (0, 0, 0.7071, 0.7071), ), 'diagonal cross': ( (0, 0, 0.7071, 0.7071), (0, 0, 0.7071, -0.7071), ), 'forward 2': ( (0, 0, 0.8660, -0.5), ), 'backward 2': ( (0, 0, 0.8660, 0.5), ), 'forward 3': ( (0, 0, 0.5, -0.8660), ), 'backward 3': ( (0, 0, 0.5, 0.8660), ), 'forward 4': ( (0, 0, 0.2588, -0.9659), ), 'backward 4': ( (0, 0, 0.2588, 0.9659), ), 'forward 5': ( (0, 0, 0.9659, -0.2588), ), 'backward 5': ( (0, 0, 0.9659, 0.2588), ), 'diagonal cross 2': ( (0, 0, 0.8660, -0.5), (0, 0, 0.8660, 0.5), ), 'diagonal cross 3': ( (0, 0, 0.5, -0.8660), (0, 0, 0.5, 0.8660), ), 'diagonal cross 4': ( (0, 0, 0.9659, -0.2588), (0, 0, 0.9659, 0.2588), ), 'diagonal cross 5': ( (0, 0, 0.2588, 0.9659), (0, 0, 0.2588, -0.9659), ), 'vertical forward': ( (0, 0, 1, 0), (0, 0, 0.7071, -0.7071), ), 'vertical backward': ( (0, 0, 1, 0), (0, 0, 0.7071, 0.7071), ), 'horizontal forward': ( (0, 0, 0, 1), (0, 0, 0.7071, -0.7071), ), 'horizontal backward': ( (0, 0, 0, 1), (0, 0, 0.7071, 0.7071), ), 'star': ( (0, 0, 2, 0), (0, 0, 0, 2), (0, 0, -1, 1), (0, 0, 1, 1), ), 'triangles 1': ( (0, 0, 2, 0), (0, 0, 0, 2), (0, 0, -1, 1),), 'triangles 2': ( (0, 0, 2, 0), (0, 0, 0, 2), (0, 0, 1, 1),), 'triangles 3': ( (0, 0, 0, 1), (0, 0, -1, 1), (0, 0, 1, 1), ), 'triangles 4': ( (0, 0, 1, 0), (0, 0, -1, 1), (0, 0, 1, 1), ), 'horizontal double': ((0, 0, 0, 1), (0, 0.2828, 0, 1), ), 'vertical double': ((0, 0, 1, 0), (0.2828, 0, 1, 0), ), 'forward double': ((0, 0, 0.7071, -0.7071), (0.4,0, 0.7071, -0.7071)), 'backward double': ((0, 0, 0.7071, 0.7071), (0.4,0, 0.7071, 0.7071)), 'double cross': ((0, 0, 0, 1), (0, 0.2828, 0, 1), (0, 0, 1, 0), (0.2828, 0, 1, 0),), 'double diagonal cross': ((0, 0, 0.7071, -0.7071), (0.4,0, 0.7071, -0.7071), (0, 0, 0.7071, 0.7071), (0.4,0, 0.7071, 0.7071)), } # convert qt-specific fill styles into qt styles _fillcnvt = { 'solid': qt4.Qt.SolidPattern, '94% dense': qt4.Qt.Dense1Pattern, '88% dense': qt4.Qt.Dense2Pattern, '63% dense': qt4.Qt.Dense3Pattern, '50% dense': qt4.Qt.Dense4Pattern, '37% dense': qt4.Qt.Dense5Pattern, '12% dense': qt4.Qt.Dense6Pattern, '6% dense': qt4.Qt.Dense7Pattern } def brushExtFillPath(painter, extbrush, path, ignorehide=False, stroke=None): """Use an BrushExtended settings object to fill a path on painter. If ignorehide is True, ignore the hide setting on the brush object. stroke is an optional QPen for stroking outline of path """ if extbrush.hide and not ignorehide: if stroke is not None: painter.strokePath(path, stroke) return style = extbrush.style if style in _fillcnvt: # standard fill: use Qt styles for painting color = qt4.QColor(extbrush.color) color.setAlphaF( (100-extbrush.transparency) / 100.) brush = qt4.QBrush( color, _fillcnvt[style]) if stroke is None: painter.fillPath(path, brush) else: painter.save() painter.setPen(stroke) painter.setBrush(brush) painter.drawPath(path) painter.restore() elif style in _hatchmap: # fill with hatching if not extbrush.backhide: # background brush color = qt4.QColor(extbrush.backcolor) color.setAlphaF( (100-extbrush.backtransparency) / 100.) brush = qt4.QBrush( color ) painter.fillPath(path, brush) color = qt4.QColor(extbrush.color) color.setAlphaF( (100-extbrush.transparency) / 100.) width = extbrush.get('linewidth').convert(painter) lstyle, dashpattern = extbrush.get('linestyle')._linecnvt[ extbrush.linestyle] pen = qt4.QPen(color, width, lstyle) if dashpattern: pen.setDashPattern(dashpattern) # do hatching with spacing spacing = extbrush.get('patternspacing').convert(painter) if spacing > 0: _hatcher(painter, pen, path, spacing, _hatchmap[style]) if stroke is not None: painter.strokePath(path, stroke) def brushExtFillPolygon(painter, extbrush, cliprect, polygon, ignorehide=False): """Fill a polygon with an extended brush.""" clipped = qt4.QPolygonF() polygonClip(polygon, cliprect, clipped) path = qt4.QPainterPath() path.addPolygon(clipped) brushExtFillPath(painter, extbrush, path, ignorehide=ignorehide) veusz-1.15/utils/dates.py0000644002344000001440000002023211734662204015410 0ustar jssusers00000000000000import math import datetime import re import numpy as N # date format: YYYY-MM-DDTHH:MM:SS.mmmmmm # date and time part are optional (check we have at least one!) date_re = re.compile( r''' ^ # match date YYYY-MM-DD ([0-9]{4}-[0-9]{1,2}-[0-9]{1,2})? [ ,A-Za-z]? # match time HH:MM:SS ([0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}(\.[0-9]+)?)? $ ''', re.VERBOSE ) # we store dates as intervals in sec from this date as a float offsetdate = datetime.datetime(2009, 01, 01, 0, 0, 0, 0) def isDateTime(datestr): """Check date/time string looks valid.""" match = date_re.match(datestr) return match and (match.group(1) is not None or match.group(2) is not None) def _isoDataStringToDate(datestr): """Convert ISO format date time to a datetime object.""" match = date_re.match(datestr) val = None if match: try: dategrp, timegrp = match.group(1), match.group(2) if dategrp: # if there is a date part of the string dateval = [int(x) for x in dategrp.split('-')] if len(dateval) != 3: raise ValueError, "Invalid date '%s'" % dategrp else: dateval = [2009, 01, 01] if timegrp: # if there is a time part of the string p = timegrp.split(':') if len(p) != 3: raise ValueError, "Invalid time '%s'" % timegrp secfrac, sec = math.modf(float(p[2])) timeval = [ int(p[0]), int(p[1]), int(sec), int(secfrac*1e6) ] else: timeval = [0, 0, 0, 0] # either worked so return a datetime object if dategrp or timegrp: val = datetime.datetime( *(dateval+timeval) ) except ValueError: # conversion failed, return nothing pass return val def dateStringToDate(datestr): """Interpret a date string and return a Veusz-format date value.""" dt = _isoDataStringToDate(datestr) if dt is None: # try local conversions (time, date and variations) # if ISO formats don't match for fmt in ('%X %x', '%x %X', '%x', '%X'): try: dt = datetime.datetime.strptime(datestr, fmt) break except ValueError: pass if dt is not None: delta = dt - offsetdate return (delta.days*24*60*60 + delta.seconds + delta.microseconds*1e-6) else: return N.nan def floatToDateTime(f): """Convert float to datetime.""" days = int(f/24/60/60) frac, sec = math.modf(f - days*24*60*60) return datetime.timedelta(days, sec, frac*1e6) + offsetdate def dateFloatToString(f): """Convert date float to string.""" if N.isfinite(f): return floatToDateTime(f).isoformat() else: return unicode(f) def datetimeToTuple(dt): """Return tuple (year,month,day,hour,minute,second,microsecond) from datetime object.""" return (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond) def datetimeToFloat(dt): """Convert datetime to float""" delta = dt - offsetdate # convert offset into a delta val = (delta.days*24*60*60 + (delta.seconds + delta.microseconds*1e-6)) return val def tupleToFloatTime(t): """Convert a tuple interval to a float style datetime""" dt = datetime.datetime(*t) return datetimeToFloat(dt) def tupleToDateTime(t): """Convert a tuple to a datetime""" return datetime.datetime(*t) def addTimeTupleToDateTime(dt, tt): """Add a time tuple in the form (yr,mn,dy,h,m,s,us) to a datetime. Returns datetime """ # add on most of the time intervals dt = dt + datetime.timedelta(days=tt[2], hours=tt[3], minutes=tt[4], seconds=tt[5], microseconds=tt[6]) # add on years dt = dt.replace(year=dt.year + tt[0]) # add on months - this could be much simpler if tt[1] > 0: for i in xrange(tt[1]): # find interval between this month and next... m, y = dt.month + 1, dt.year if m == 13: m = 1 y += 1 dt = dt.replace(year=y, month=m) elif tt[1] < 0: for i in xrange(abs(tt[1])): # find interval between this month and next... m, y = dt.month - 1, dt.year if m == 0: m = 12 y -= 1 dt = dt.replace(year=y, month=m) return dt def roundDownToTimeTuple(dt, tt): """Take a datetime, and round down using the (yr,mn,dy,h,m,s,ms) tuple. Returns a tuple.""" #print "round down", dt, tt timein = list(datetimeToTuple(dt)) i = 6 while i >= 0 and tt[i] == 0: if i == 1 or i == 2: # month, day timein[i] = 1 else: timein[i] = 0 i -= 1 # round to nearest interval if (i == 1 or i == 2): # month, day timein[i] == ((timein[i]-1) // tt[i])*tt[i] + 1 else: timein[i] = (timein[i] // tt[i])*tt[i] #print "rounded", timein return tuple(timein) def dateStrToRegularExpression(instr): """Convert date-time string to regular expression. Converts format yyyy-mm-dd|T|hh:mm:ss to re for date """ # first rename each special string to a unique string (this is a # unicode character which is in the private use area) then rename # back again to the regular expression. This avoids the regular # expression being remapped. maps = ( ('YYYY', u'\ue001', r'(?P[0-9]{4})'), ('YY', u'\ue002', r'(?P[0-9]{2})'), ('MM', u'\ue003', r'(?P[0-9]{2})'), ('M', u'\ue004', r'(?P[0-9]{1,2})'), ('DD', u'\ue005', r'(?P
    [0-9]{2})'), ('D', u'\ue006', r'(?P
    [0-9]{1,2})'), ('hh', u'\ue007', r'(?P[0-9]{2})'), ('h', u'\ue008', r'(?P[0-9]{1,2})'), ('mm', u'\ue009', r'(?P[0-9]{2})'), ('m', u'\ue00a', r'(?P[0-9]{1,2})'), ('ss', u'\ue00b', r'(?P[0-9]{2}(\.[0-9]*)?)'), ('s', u'\ue00c', r'(?P[0-9]{1,2}(\.[0-9]*)?)'), ) out = [] for p in instr.split('|'): # escape special characters (non alpha-num) p = re.escape(p) # replace strings with characters for search, char, repl in maps: p = p.replace(search, char, 1) # replace characters with re strings for search, char, repl in maps: p = p.replace(char, repl, 1) # save as an optional group out.append( '(?:%s)?' % p ) # return final expression return '^\s*%s\s*$' % (''.join(out)) def dateREMatchToDate(match): """Take match object for above regular expression, and convert to float date value.""" if match is None: raise ValueError, "match object is None" # remove None matches grps = {} for k, v in match.groupdict().iteritems(): if v is not None: grps[k] = v # bomb out if nothing matches if len(grps) == 0: raise ValueError, "no groups matched" # get values of offset oyear = offsetdate.year omon = offsetdate.month oday = offsetdate.day ohour = offsetdate.hour omin = offsetdate.minute osec = offsetdate.second omicrosec = offsetdate.microsecond # now convert each element from the re if 'YYYY' in grps: oyear = int(grps['YYYY']) if 'YY' in grps: y = int(grps['YY']) if y >= 70: oyear = int('19' + grps['YY']) else: oyear = int('20' + grps['YY']) if 'MM' in grps: omon = int(grps['MM']) if 'DD' in grps: oday = int(grps['DD']) if 'hh' in grps: ohour = int(grps['hh']) if 'mm' in grps: omin = int(grps['mm']) if 'ss' in grps: s = float(grps['ss']) osec = int(s) omicrosec = int(1e6*(s-osec)) # convert to python datetime object d = datetime.datetime( oyear, omon, oday, ohour, omin, osec, omicrosec) # return to veusz float time return datetimeToFloat(d) veusz-1.15/utils/treemodel.py0000644002344000001440000002131611734662204016274 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """A Qt data model show a tree of Python nodes.""" import bisect import veusz.qtall as qt4 class TMNode(object): """Object to represent nodes in TreeModel. Each node has a tuple of data items, a parent node and a list of child nodes. """ def __init__(self, data, parent): self.data = data self.parent = parent self.childnodes = [] # model uses these to map objects to qmodelindexes # self._idx = None def toolTip(self, column): """Return tooltip for column, if any.""" return qt4.QVariant() def doPrint(self, indent=0): """Print out tree for debugging.""" print " "*indent, self.data, self for c in self.childnodes: c.doPrint(indent=indent+1) def deleteFromParent(self): """Delete this node from its parent.""" del self.parent.childnodes[self.parent.childnodes.index(self)] def nodeData(self, idx): """Get data with index given.""" try: return qt4.QVariant(self.data[idx]) except: return qt4.QVariant() def childWithData1(self, d): """Get child node with 1st column data d.""" for c in self.childnodes: if c.data[0] == d: return c return None def insertChildSorted(self, newchild): """Insert child alphabetically using data d.""" cdata = [c.data for c in self.childnodes] idx = bisect.bisect_left(cdata, newchild.data) newchild.parent = self self.childnodes.insert(idx, newchild) def cloneTo(self, newroot): """Make a clone of self at the root given.""" return self.__class__(self.data, newroot) class TreeModel(qt4.QAbstractItemModel): """A Qt model for storing Python nodes in a tree. The nodes are TMNode objects above.""" def __init__(self, rootdata, *args): """Construct the model. rootdata is a tuple of data for the root node - it should have the same number of columns as other datasets.""" qt4.QAbstractItemModel.__init__(self, *args) self.root = TMNode(rootdata, None) # the nodes are stored here when in the tree, # to be looked up with node._idx self.nodes = {} # next index to assign in self.nodes self.nodeindex = 0 def columnCount(self, parent): """Use root data to get column count.""" return len(self.root.data) def data(self, index, role): """Get text or tooltip.""" if index.isValid(): item = self.objFromIndex(index) if role == qt4.Qt.DisplayRole: return item.nodeData(index.column()) elif role == qt4.Qt.ToolTipRole: return item.toolTip(index.column()) return qt4.QVariant() def flags(self, index): """Return whether node is editable.""" if not index.isValid(): return qt4.Qt.NoItemFlags return qt4.Qt.ItemIsEnabled | qt4.Qt.ItemIsSelectable def headerData(self, section, orientation, role): """Use root node to get headers.""" if orientation == qt4.Qt.Horizontal and role == qt4.Qt.DisplayRole: return self.root.nodeData(section) return qt4.QVariant() def objFromIndex(self, idx): """Given an index, return the node.""" if idx.isValid(): try: return self.nodes[idx.internalId()] except KeyError: pass return None def index(self, row, column, parent): """Return index of node.""" if not self.hasIndex(row, column, parent): return qt4.QModelIndex() parentitem = self.objFromIndex(parent) if parentitem is None: parentitem = self.root childitem = parentitem.childnodes[row] if childitem: return self.createIndex(row, column, childitem._idx) return qt4.QModelIndex() def parent(self, index): """Get parent index of index.""" if not index.isValid(): return qt4.QModelIndex() childitem = self.objFromIndex(index) if childitem is None: return qt4.QModelIndex() parentitem = childitem.parent if parentitem is self.root: return qt4.QModelIndex() parentrow = parentitem.parent.childnodes.index(parentitem) return self.createIndex(parentrow, 0, parentitem._idx) def rowCount(self, parent): """Compute row count of node.""" if parent.column() > 0: return 0 if not parent.isValid(): parentitem = self.root else: parentitem = self.objFromIndex(parent) return len(parentitem.childnodes) @staticmethod def _getdata(theroot): """Get a set of child node data and a mapping of data to node.""" lookup = {} data = [] for c in theroot.childnodes: d = c.data[0] lookup[d] = c data.append(d) return lookup, set(data) def _syncbranch(self, parentidx, root, rootnew): """For synchronising branches in node tree.""" # FIXME: this doesn't work if there are duplicates # use LCS - longest common sequence instead clookup, cdata = self._getdata(root) nlookup, ndata = self._getdata(rootnew) if not cdata and not ndata: return common = cdata & ndata # items to remove (no longer in new data) todelete = cdata - common # sorted list to add (added to new data) toadd = list(ndata - common) toadd.sort() # iterate over entries, adding and deleting as necessary i = 0 c = root.childnodes while i < len(rootnew.childnodes) or i < len(c): if i < len(c): k = c[i].data[0] else: k = None # one to be deleted if k in todelete: todelete.remove(k) self.beginRemoveRows(parentidx, i, i) del self.nodes[c[i]._idx] del c[i] self.endRemoveRows() continue # one to insert if toadd and (k > toadd[0] or k is None): self.beginInsertRows(parentidx, i, i) a = nlookup[toadd[0]].cloneTo(root) a._idx = self.nodeindex self.nodeindex += 1 self.nodes[a._idx] = a c.insert(i, a) self.endInsertRows() del toadd[0] else: # neither delete or add if clookup[k].data != nlookup[k].data: # the name is the same but data are not # swap node entry to point to new cloned node clone = nlookup[k].cloneTo(root) idx = clookup[k]._idx clone._idx = idx self.nodes[idx] = clone self.emit(qt4.SIGNAL('dataChanged(const QModelIndex &, ' 'const QModelIndex &)'), self.index(i, 0, parentidx), self.index(i, len(c[i].data)-1, parentidx)) # now recurse to update any subnodes newindex = self.index(i, 0, parentidx) self._syncbranch(newindex, c[i], rootnew.childnodes[i]) i += 1 def syncTree(self, newroot): """Syncronise the displayed tree with the given tree new.""" toreset = self.root.data != newroot.data if toreset: # header changed, so do reset self.beginResetModel() self._syncbranch( qt4.QModelIndex(), self.root, newroot ) if toreset: self.root.data = newroot.data self.endResetModel() veusz-1.15/utils/fitlm.py0000644002344000001440000001170411734662204015427 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """ Numerical fitting of functions to data. """ import sys import numpy as N try: import numpy.linalg as NLA except: import scipy.linalg as NLA def fitLM(func, params, xvals, yvals, errors, stopdeltalambda = 1e-5, deltaderiv = 1e-5, maxiters = 20, Lambda = 1e-4): """ Use Marquardt method as described in Bevington & Robinson to fit data func is a python function to evaluate. It takes two parameters, the parameters to fit as a numpy, and the values of x to evaluate the function at params is a numpy of parameters to fit for. These are passed to the function. xvals are x data points (numpy), yvals are the y data points (numarray) errors are a numpy of errors on the y data points. Set all to 1 if not important. stopdeltalambda: minimum change in chi2 to carry on fitting deltaderiv: change to make in parameters to calculate derivative maxiters: maximum number of better fitting solutions before stopping Lambda: starting lambda value (as described in Bevington) """ # only use finite values for fitting finite = N.logical_and( N.logical_and( N.isfinite(xvals), N.isfinite(yvals)), N.isfinite(errors) ) xvals = xvals[finite] yvals = yvals[finite] errors = errors[finite] # optimisation to avoid computing this all the time inve2 = 1. / errors**2 # work out fit using current parameters oldfunc = func(params, xvals) chi2 = ( (oldfunc - yvals)**2 * inve2 ).sum() # initialise temporary space beta = N.zeros( len(params), dtype='float64' ) alpha = N.zeros( (len(params), len(params)), dtype='float64' ) derivs = N.zeros( (len(params), len(xvals)), dtype='float64' ) done = False iters = 0 while iters < maxiters and not done: # iterate over each of the parameters and calculate the derivatives # of chi2 to populate the beta vector # also calculate the derivative of the function at each of the points # wrt the parameters for i in range( len(params) ): params[i] += deltaderiv new_func = func(params, xvals) chi2_new = ((new_func - yvals)**2 * inve2).sum() params[i] -= deltaderiv beta[i] = chi2_new - chi2 derivs[i] = new_func - oldfunc # beta is now dchi2 / dparam beta *= (-0.5 / deltaderiv) derivs *= (1. / deltaderiv) # calculate alpha matrix # FIXME: stupid - must be a better way to avoid this iteration for j in range( len(params) ): for k in range(j+1): v = (derivs[j]*derivs[k] * inve2).sum() alpha[j][k] = v alpha[k][j] = v # twiddle alpha using lambda alpha *= 1. + N.identity(len(params), dtype='float64')*Lambda # now work out deltas on parameters to get better fit epsilon = NLA.inv( alpha ) deltas = N.dot(beta, epsilon) # new solution new_params = params+deltas new_func = func(new_params, xvals) new_chi2 = ( (new_func - yvals)**2 * inve2 ).sum() if N.isnan(new_chi2): sys.stderr.write('Chi2 is NaN. Aborting fit.\n') break if new_chi2 > chi2: # if solution is worse, increase lambda Lambda *= 10. else: # better fit, so we accept this solution # if the change is small done = chi2 - new_chi2 < stopdeltalambda chi2 = new_chi2 params = new_params oldfunc = new_func Lambda *= 0.1 # format new parameters iters += 1 p = [iters, chi2] + params.tolist() str = ("%5i " + "%8g " * (len(params)+1)) % tuple(p) print str if not done: sys.stderr.write("Warning: maximum number of iterations reached\n") # print out fit statistics at end dof = len(yvals) - len(params) redchi2 = chi2 / dof print "chi^2 = %g, dof = %i, reduced-chi^2 = %g" % (chi2, dof, redchi2) return (params, chi2, dof) veusz-1.15/utils/vzdbus.py0000644002344000001440000000333211734662204015627 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Wrapper for D-Bus module it.""" # interface name veusz interfaces appear on sessionbus = busname = None try: import dbus import dbus.service as service from dbus.service import method, Object from dbus.mainloop.qt import DBusQtMainLoop import os def setup(): """Initialise dbus.""" DBusQtMainLoop(set_as_default=True) global sessionbus global busname sessionbus = dbus.SessionBus() busname = dbus.service.BusName( 'org.veusz.pid%i' % os.getpid(), sessionbus) except ImportError: # no DBus, so we try to make the interface do nothing def setup(): pass def method(**argsv): def donothing(m): return m return donothing class Object(object): def __init__(self, *args): pass veusz-1.15/utils/pdf.py0000644002344000001440000000533211734662204015065 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import re def scalePDFMediaBox(text, pagewidth, requiredwidth, requiredheight): """Take the PDF file text and adjust the page size. pagewidth: full page width requiredwidth: width we want requiredheight: height we want """ m = re.search(r'^/MediaBox \[([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)\]$', text, re.MULTILINE) box = [float(x) for x in m.groups()] widthfactor = box[2] / pagewidth newbox = '/MediaBox [%i %i %i %i]' % ( 0, int(box[3]-widthfactor*requiredheight), int(widthfactor*requiredwidth), int(box[3])) text = text[:m.start()] + newbox + text[m.end():] return text def fixupPDFIndices(text): """Fixup index table in PDF. Basically, we need to find the start of each obj in the file These indices are then placed in an xref table at the end The index to the xref table is placed after a startxref """ # find occurences of obj in string indices = {} for m in re.finditer('([0-9]+) 0 obj', text): index = int(m.group(1)) indices[index] = m.start(0) # build up xref block (note trailing spaces) xref = ['xref', '0 %i' % (len(indices)+1), '0000000000 65535 f '] for i in xrange(len(indices)): xref.append( '%010i %05i n ' % (indices[i+1], 0) ) xref.append('trailer\n') xref = '\n'.join(xref) # replace current xref with this one xref_match = re.search('^xref\n.*trailer\n', text, re.DOTALL | re.MULTILINE) xref_index = xref_match.start(0) text = text[:xref_index] + xref + text[xref_match.end(0):] # put the correct index to the xref after startxref startxref_re = re.compile('^startxref\n[0-9]+\n', re.DOTALL | re.MULTILINE) text = startxref_re.sub('startxref\n%i\n' % xref_index, text) return text veusz-1.15/utils/utilfuncs.py0000644002344000001440000003353711734662204016340 0ustar jssusers00000000000000# utilfuncs.py # utility functions # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import sys import string import re import os.path import threading import codecs import csv import StringIO import veusz.qtall as qt4 import numpy as N class IgnoreException(Exception): """A special exception class to be ignored by the exception handler.""" def _getVeuszDirectory(): """Get resource and examples directories for Veusz.""" if hasattr(sys, 'frozen'): # for pyinstaller/py2app compatability resdir = os.path.dirname(os.path.abspath(sys.executable)) if sys.platform == 'darwin': # special case for py2app resdir = os.path.join(resdir, '..', 'Resources') else: # standard installation resdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) # override data directory with symlink if os.path.exists( os.path.join(resdir, 'resources') ): resdir = os.path.realpath( os.path.join(resdir, 'resources') ) # override with VEUSZ_RESOURCE_DIR environment variable if necessary resdir = os.environ.get('VEUSZ_RESOURCE_DIR', resdir) # now get example directory (which may be a symlink) examplesdir = os.path.realpath( os.path.join(resdir, 'examples') ) return resdir, examplesdir # get resource and example directories veuszDirectory, exampleDirectory = _getVeuszDirectory() def getLicense(): """Return license text.""" try: f = open(os.path.join(veuszDirectory, 'COPYING'), 'rU') text = f.read() f.close() except EnvironmentError: text = ('Could not open the license file.\n' 'See license at http://www.gnu.org/licenses/gpl-2.0.html') return text id_re = re.compile('^[A-Za-z_][A-Za-z0-9_]*$') def validPythonIdentifier(name): """Is this a valid python identifier?""" return id_re.match(name) is not None def validateDatasetName(name): """Validate dataset name is okay. Dataset names can contain anything except back ticks! """ return len(name.strip()) > 0 and name.find('`') == -1 def validateWidgetName(name): """Validate widget name is okay. Widget names are valid if no surrounding whitespace and do not contain / """ return ( len(name) > 0 and name.strip() == name and name.find('/') == -1 and name != '.' and name != '..' ) def cleanDatasetName(name): """Make string into a valid dataset name.""" # replace backticks and get rid of whitespace at ends return name.replace('`', '_').strip() def relpath(filename, dirname): """Make filename a relative filename relative to dirname.""" # spit up paths into components filename = os.path.abspath(filename) fileparts = filename.split(os.path.sep) dirparts = os.path.abspath(dirname).split(os.path.sep) # if first non empty part is non equal, return original i = 0 while not fileparts[i] and not dirparts[i]: i += 1 if fileparts[i] != dirparts[i]: return filename # remove equal bits at start while fileparts and dirparts and fileparts[0] == dirparts[0]: fileparts.pop(0) dirparts.pop(0) # add on right number of .. to get back up fileparts = [os.path.pardir]*len([d for d in dirparts if d]) + fileparts # join parts back together return os.path.sep.join(fileparts) # handle and extended #RRGGBBAA color extendedcolor_re = re.compile('^#[0-9A-Fa-f]{8}$') def extendedColorToQColor(s): if extendedcolor_re.match(s): col = qt4.QColor(s[:-2]) col.setAlpha( int(s[-2:], 16) ) return col else: return qt4.QColor(s) def extendedColorFromQColor(col): """Make an extended color #RRGGBBAA or #RRGGBB string.""" if col.alpha() == 255: return str(col.name()) else: return '#%02x%02x%02x%02x' % (col.red(), col.green(), col.blue(), col.alpha()) def pixmapAsHtml(pix): """Get QPixmap as html image text.""" ba = qt4.QByteArray() buf = qt4.QBuffer(ba) buf.open(qt4.QIODevice.WriteOnly) pix.toImage().save(buf, "PNG") b64 = str(buf.data().toBase64()) return '' % b64 def BoundCaller(function, *params): """Wrap a function with its initial arguments.""" def wrapped(*args): function( *(params+args) ) return wrapped def pythonise(text): """Turn an expression of the form 'A b c d' into 'A(b,c,d)'. This is for 'pythonising' commands from a command-line interface to make it easier for users. We also have to take account of quotes and backslashes. """ out = '' insingle = False # in a single quote section indouble = False # in a double quote section firstnonws = False # have we reached first non WS char firstpart = True # have we appended the first part of the expr lastbslash = False # was the last character a back-slash # iterate over characters for c in text: # keep leading WS if c in string.whitespace and not firstnonws: out += c continue firstnonws = True # this character isn't escaped if not lastbslash: # quoted section if c == "'": insingle = not insingle elif c == '"': indouble = not indouble elif c == '\\': lastbslash = True continue # spacing between parts if c == ' ' and not insingle and not indouble: if firstpart: out += '(' firstpart = False else: out += ',' else: out += c else: out += '\\' + c lastbslash = False # we're still in the first part if firstpart: out += '(' # we can add a right bracket if not insingle and not indouble: out += ')' return out def validLinePoints(x, y): """Take x and y points and split into sets of points which don't have invalid points. This is a generator. """ xvalid = N.logical_not( N.isfinite(x) ).nonzero()[0] yvalid = N.logical_not( N.isfinite(y) ).nonzero()[0] invalid = N.concatenate((xvalid, yvalid)) invalid.sort() last = 0 for valid in invalid: if valid > last: yield x[last:valid], y[last:valid] last = valid + 1 if last < x.shape[0]-1: yield x[last:], y[last:] class NonBlockingReaderThread(threading.Thread): """A class to read blocking file objects and return the result. Usage: r = ReadThread(myfile) r.start() while True: newdata, done = r.getNewData() print newdata if done: break This is used mainly because windows doesn't properly support non-blocking pipes as files. """ def __init__(self, fileobject): """Create the thread object.""" threading.Thread.__init__(self) self.fileobject = fileobject self.lock = threading.Lock() self.data = '' self.done = False def getNewData(self): """Get any data waiting to be read, and whether the reading is finished. Returns (data, done) """ self.lock.acquire() data = self.data done = self.done self.data = '' self.lock.release() if isinstance(data, Exception): # if the reader errored somewhere raise data else: return data, done def run(self): """Do the reading from the file object.""" while True: try: data = self.fileobject.readline() except Exception, e: # error in reading self.lock.acquire() self.data = e self.lock.release() break # no more data: end of file if len(data) == 0: self.lock.acquire() self.done = True self.lock.release() break self.lock.acquire() self.data += data self.lock.release() # standard python encodings encodings = [ 'ascii', 'big5hkscs', 'big5', 'charmap', 'cp037', 'cp424', 'cp437', 'cp500', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', 'cp857', 'cp860', 'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866', 'cp869', 'cp874', 'cp875', 'cp932', 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1140','cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256', 'cp1257', 'cp1258', 'euc_jis_2004', 'euc_jisx0213', 'euc_jp', 'euc_kr', 'gb18030', 'gb2312', 'gbk', 'hp_roman8', 'hz', 'iso2022_jp_1', 'iso2022_jp_2004', 'iso2022_jp_2', 'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_jp', 'iso2022_kr', 'iso8859_10', 'iso8859_11', 'iso8859_13', 'iso8859_14', 'iso8859_15', 'iso8859_16', 'iso8859_1', 'iso8859_2', 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7', 'iso8859_8', 'iso8859_9', 'johab', 'koi8_r', 'koi8_u', 'latin_1', 'mac_arabic', 'mac_centeuro', 'mac_croatian', 'mac_cyrillic', 'mac_farsi', 'mac_greek', 'mac_iceland', 'mac_latin2', 'mac_romanian', 'mac_roman', 'mac_turkish', 'ptcp154', 'shift_jis_2004', 'shift_jis', 'shift_jisx0213', 'tis_620','utf_16_be', 'utf_16_le', 'utf_16', 'utf_32_be', 'utf_32_le', 'utf_32', 'utf_7', 'utf_8', 'utf_8_sig' ] def openEncoding(filename, encoding, mode='r'): """Convenience function for opening file with encoding given. If filename == '{clipboard}', then load the data from the clipboard instead. """ if filename == '{clipboard}': text = unicode(qt4.QApplication.clipboard().text()) return StringIO.StringIO(text) else: return codecs.open(filename, mode, encoding, 'ignore') # The following two classes are adapted from the Python documentation # they are modified to turn off encoding errors class UTF8Recoder: """ Iterator that reads an encoded stream and reencodes the input to UTF-8 """ def __init__(self, f, encoding): self.reader = codecs.getreader(encoding)(f, errors='ignore') def __iter__(self): return self def next(self): line = self.reader.next() return line.encode("utf-8") class UnicodeCSVReader: """ A CSV reader which will iterate over lines in the CSV file "f", which is encoded in the given encoding. """ def __init__(self, filename, dialect=csv.excel, encoding='utf-8', **kwds): if filename != '{clipboard}': # recode the opened file as utf-8 f = UTF8Recoder(open(filename), encoding) else: # take the unicode clipboard and just put into utf-8 format s = unicode(qt4.QApplication.clipboard().text()) s = s.encode('utf-8') f = StringIO.StringIO(s) # the actual csv reader based on the file above self.reader = csv.reader(f, dialect=dialect, **kwds) def next(self): row = self.reader.next() return [unicode(s, 'utf-8') for s in row] def __iter__(self): return self # End python doc classes def populateCombo(combo, items): """Populate the combo with the list of items given. This also makes sure the currently entered text persists, or if currenttext is set, use this """ # existing setting currenttext = unicode(combo.currentText()) # add to list if not included if currenttext not in items: items = items + [currenttext] # put in new entries for i, val in enumerate(items): if i >= combo.count(): combo.addItem(val) else: if combo.itemText(i) != val: combo.insertItem(i, val) # remove any extra items while combo.count() > len(items): combo.removeItem( combo.count()-1 ) # get index for current value index = combo.findText(currenttext) combo.setCurrentIndex(index) def positionFloatingPopup(popup, widget): """Position a popped up window (popup) to side and below widget given.""" pos = widget.parentWidget().mapToGlobal( widget.pos() ) desktop = qt4.QApplication.desktop() # recalculates out position so that size is correct below popup.adjustSize() # is there room to put this widget besides the widget? if pos.y() + popup.height() + 1 < desktop.height(): # put below y = pos.y() + 1 else: # put above y = pos.y() - popup.height() - 1 # is there room to the left for us? if ( (pos.x() + widget.width() + popup.width() < desktop.width()) or (pos.x() + widget.width() < desktop.width()/2) ): # put left justified with widget x = pos.x() + widget.width() else: # put extending to left x = pos.x() - popup.width() - 1 popup.move(x, y) popup.setFocus() def unique(inlist): """Get unique entries in list.""" inlist = list(inlist) inlist.sort() class X: pass last = X() out = [] for x in inlist: if x != last: out.append(x) last = x return out veusz-1.15/utils/__init__.py0000644002344000001440000000324611734662204016055 0ustar jssusers00000000000000# __init__.py file for utils # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### from version import version from textrender import Renderer, FontMetrics from safe_eval import checkCode from fitlm import fitLM from utilfuncs import * from points import * from action import * from pdf import * from dates import * from formatting import * from colormap import * from extbrushfilling import * try: from veusz.helpers.qtloops import addNumpyToPolygonF, plotPathsToPainter, \ plotLinesToPainter, plotClippedPolyline, polygonClip, \ plotClippedPolygon, plotBoxesToPainter, addNumpyPolygonToPath except ImportError: from slowfuncs import addNumpyToPolygonF, plotPathsToPainter, \ plotLinesToPainter, plotClippedPolyline, polygonClip, \ plotClippedPolygon, plotBoxesToPainter, addNumpyPolygonToPath veusz-1.15/utils/formatting.py0000644002344000001440000001612111734662204016464 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import re import math import dates import veusz.qtall as qt4 _formaterror = 'FormatError' # a format statement in a string _format_re = re.compile(r'%([-#0-9 +.hlL]*?)([diouxXeEfFgGcrs%])') def localeFormat(totfmt, args, locale=None): """Format using fmt statement fmt, qt QLocale object locale and arguments to formatting args. * arguments are not supported in this formatting, nor is using a dict to supply values for statement """ # substitute all format statements with string format statements newfmt = _format_re.sub("%s", totfmt) # do formatting separately for all statements strings = [] i = 0 for f in _format_re.finditer(totfmt): code = f.group(2) if code == '%': s = '%' else: try: s = f.group() % args[i] i += 1 except IndexError: raise TypeError, "Not enough arguments for format string" if locale is not None and code in 'eEfFgG': s = s.replace('.', qt4.QString(locale.decimalPoint())) strings.append(s) if i != len(args): raise TypeError, "Not all arguments converted during string formatting" return newfmt % tuple(strings) def formatSciNotation(num, formatargs, locale=None): """Format number into form X \times 10^{Y}. This function trims trailing zeros and decimal point unless a formatting argument is supplied This is similar to the %e format string formatargs is the standard argument in a format string to control the number of decimal places, etc. locale is a QLocale object """ # create an initial formatting string if formatargs: format = '%' + formatargs + 'e' else: format = '%.10e' # try to format the number # this may be user-supplied data, so don't crash hard by returning # useless output try: text = format % num except: return _formaterror # split around the exponent leader, exponent = text.split('e') # strip off trailing decimal point and zeros if no format args if not formatargs: leader = '%.10g' % float(leader) # trim off leading 1 if leader == '1' and not formatargs: leader = '' else: # the unicode string is a small space, multiply and small space leader += u'\u00d7' # do substitution of decimals if locale is not None: leader = leader.replace('.', qt4.QString(locale.decimalPoint())) return '%s10^{%i}' % (leader, int(exponent)) def formatGeneral(num, fmtarg, locale=None): """General formatting which switches from normal to scientic notation.""" a = abs(num) # manually choose when to switch from normal to scientific # as the default isn't very good if a >= 1e4 or (a < 1e-2 and a > 1e-110): retn = formatSciNotation(num, fmtarg, locale=locale) else: if fmtarg: f = '%' + fmtarg + 'g' else: f = '%.10g' try: retn = f % num except TypeError: retn = _formaterror if locale is not None: retn = retn.replace('.', qt4.QString(locale.decimalPoint())) return retn engsuffixes = ( 'y', 'z', 'a', 'f', 'p', 'n', u'\u03bc', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ) def formatEngineering(num, fmtarg, locale=None): """Engineering suffix format notation using SI suffixes.""" if num != 0.: logindex = math.log10( abs(num) ) / 3. # for numbers < 1 round down suffix if logindex < 0. and (int(logindex)-logindex) > 1e-6: logindex -= 1 # make sure we don't go out of bounds logindex = min( max(logindex, -8), len(engsuffixes) - 9 ) suffix = engsuffixes[ int(logindex) + 8 ] val = num / 10**( int(logindex) *3) else: suffix = '' val = num text = ('%' + fmtarg + 'g%s') % (val, suffix) if locale is not None: text = text.replace('.', qt4.QString(locale.decimalPoint())) return text # catch general veusz formatting expression _formatRE = re.compile(r'%([^A-Za-z]*)(VDVS|VD.|V.|[A-Za-z])') def formatNumber(num, format, locale=None): """ Format a number in different ways. format is a standard C format string, with some additions: %Ve scientific notation X \times 10^{Y} %Vg switches from normal notation to scientific outside 10^-2 to 10^4 %VE engineering suffix option %VDx date formatting, where x is one of the arguments in http://docs.python.org/lib/module-time.html in the function strftime """ while True: # repeatedly try to do string format m = _formatRE.search(format) if not m: break # argument and type of formatting farg, ftype = m.groups() # special veusz formatting if ftype[:1] == 'V': # special veusz formatting if ftype == 'Ve': out = formatSciNotation(num, farg, locale=locale) elif ftype == 'Vg': out = formatGeneral(num, farg, locale=locale) elif ftype == 'VE': out = formatEngineering(num, farg, locale=locale) elif ftype[:2] == 'VD': d = dates.floatToDateTime(num) # date formatting (seconds since start of epoch) if ftype[:4] == 'VDVS': # special seconds operator out = ('%'+ftype[4:]+'g') % (d.second+d.microsecond*1e-6) else: # use date formatting try: out = d.strftime(str('%'+ftype[2:])) except ValueError: out = _formaterror else: out = _formaterror # replace hyphen with true - and small space out = out.replace('-', u'\u2212') else: # standard C formatting try: out = localeFormat('%' + farg + ftype, (num,), locale=locale) except: out = _formaterror format = format[:m.start()] + out + format[m.end():] return format veusz-1.15/utils/colormap.py0000644002344000001440000001542611734662204016135 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import numpy as N import veusz.qtall as qt4 # use fast or slow helpers slowfuncs = False try: from veusz.helpers.qtloops import numpyToQImage, applyImageTransparancy except ImportError: slowfuncs = True from slowfuncs import slowNumpyToQImage # Default colormaps used by widgets. # Each item in this dict is a colormap entry, with the key the name. # The values in the dict are tuples of (B, G, R, alpha). # B, G, R and alpha go from 0 to 255 # Colors are linearly interpolated in this space. defaultcolormaps = { 'blank': ( (0, 0, 0, 0), (0, 0, 0, 0), ), 'heat': ( (0, 0, 0, 255), (0, 0, 186, 255), (50, 139, 255, 255), (19, 239, 248, 255), (255, 255, 255, 255), ), 'spectrum2': ( (0, 0, 255, 255), (0, 255, 255, 255), (0, 255, 0, 255), (255, 255, 0, 255), (255, 0, 0, 255), ), 'spectrum': ( (0, 0, 0, 255), (0, 0, 255, 255), (0, 255, 255, 255), (0, 255, 0, 255), (255, 255, 0, 255), (255, 0, 0, 255), (255, 255, 255, 255), ), 'grey': ( (0, 0, 0, 255), (255, 255, 255, 255), ), 'blue': ( (0, 0, 0, 255), (255, 0, 0, 255), (255, 255, 255, 255), ), 'red': ( (0, 0, 0, 255), (0, 0, 255, 255), (255, 255, 255, 255), ), 'green': ( (0, 0, 0, 255), (0, 255, 0, 255), (255, 255, 255, 255), ), 'bluegreen': ( (0, 0, 0, 255), (255, 123, 0, 255), (255, 226, 72, 255), (161, 255, 0, 255), (255, 255, 255, 255), ), 'transblack': ( (0, 0, 0, 255), (0, 0, 0, 0), ), 'royal': ( (0, 0, 0, 255), (128, 0, 0, 255), (255, 0, 128, 255), (0, 255, 255, 255), (255, 255, 255, 255), ), 'complement': ( (0, 0, 0, 255), (0, 255, 0, 255), (255, 0, 255, 255), (0, 0, 255, 255), (0, 255, 255, 255), (255, 255, 255, 255), ), } def applyScaling(data, mode, minval, maxval): """Apply a scaling transformation on the data. data is a numpy array mode is one of 'linear', 'sqrt', 'log', or 'squared' minval is the minimum value of the scale maxval is the maximum value of the scale returns transformed data, valid between 0 and 1 """ # catch naughty people by hardcoding a range if minval == maxval: minval, maxval = 0., 1. if mode == 'linear': # linear scaling data = (data - minval) / (maxval - minval) elif mode == 'sqrt': # sqrt scaling # translate into fractions of range data = (data - minval) / (maxval - minval) # clip off any bad sqrts data[data < 0.] = 0. # actually do the sqrt transform data = N.sqrt(data) elif mode == 'log': # log scaling of image # clip any values less than lowermin lowermin = data < minval data = N.log(data - (minval - 1)) / N.log(maxval - (minval - 1)) data[lowermin] = 0. elif mode == 'squared': # squared scaling # clip any negative values lowermin = data < minval data = (data-minval)**2 / (maxval-minval)**2 data[lowermin] = 0. else: raise RuntimeError, 'Invalid scaling mode "%s"' % mode return data def applyColorMap(cmap, scaling, datain, minval, maxval, trans, transimg=None): """Apply a colour map to the 2d data given. cmap is the color map (numpy of BGRalpha quads) scaling is scaling mode => 'linear', 'sqrt', 'log' or 'squared' data are the imaging data minval and maxval are the extremes of the data for the colormap trans is a number from 0 to 100 transimg is an optional image to apply transparency from Returns a QImage """ cmap = N.array(cmap, dtype=N.intc) # invert colour map if min and max are swapped if minval > maxval: minval, maxval = maxval, minval cmap = cmap[::-1] # apply transparency if trans != 0: cmap = cmap.copy() cmap[:,3] = (cmap[:,3].astype(N.float32) * (100-trans) / 100.).astype(N.intc) # apply scaling of data fracs = applyScaling(datain, scaling, minval, maxval) if not slowfuncs: img = numpyToQImage(fracs, cmap, transimg is not None) if transimg is not None: applyImageTransparancy(img, transimg) else: img = slowNumpyToQImage(fracs, cmap, transimg) return img def makeColorbarImage(minval, maxval, scaling, cmap, transparency, direction='horz'): """Make a colorbar for the scaling given.""" barsize = 128 if scaling in ('linear', 'sqrt', 'squared'): # do a linear color scaling vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval colorscaling = scaling coloraxisscale = 'linear' else: assert scaling == 'log' # a logarithmic color scaling # we cheat here by actually plotting a linear colorbar # and telling veusz to put a log axis along it # (as we only care about the endpoints) # maybe should do this better... vals = N.arange(barsize)/(barsize-1.0)*(maxval-minval) + minval colorscaling = 'linear' coloraxisscale = 'log' # convert 1d array to 2d image if direction == 'horizontal': vals = vals.reshape(1, barsize) else: assert direction == 'vertical' vals = vals.reshape(barsize, 1) img = applyColorMap(cmap, colorscaling, vals, minval, maxval, transparency) return (minval, maxval, coloraxisscale, img) veusz-1.15/embed_remote.py0000644002344000001440000002251311734662204015603 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import sys import struct import cPickle import socket import veusz.qtall as qt4 from veusz.windows.simplewindow import SimpleWindow import veusz.document as document """Program to be run by embedding interface to run Veusz commands.""" # embed.py module checks this is the same as its version number API_VERSION = 1 class EmbeddedClient(object): """An object for each instance of embedded window with document.""" def __init__(self, title, doc=None): """Construct window with title given.""" self.window = SimpleWindow(title, doc=doc) self.window.show() self.document = self.window.document self.plot = self.window.plot # use time based checking by default self.plot.setTimeout(250) self.ci = document.CommandInterpreter(self.document) self.ci.addCommand('Close', self.cmdClose) self.ci.addCommand('Zoom', self.cmdZoom) self.ci.addCommand('EnableToolbar', self.cmdEnableToolbar) self.ci.addCommand('ForceUpdate', self.cmdForceUpdate) self.ci.addCommand('GetClick', self.cmdGetClick) self.ci.addCommand('ResizeWindow', self.cmdResizeWindow) self.ci.addCommand('SetUpdateInterval', self.cmdSetUpdateInterval) self.ci.addCommand('MoveToPage', self.cmdMoveToPage) self.ci.addCommand('IsClosed', self.cmdIsClosed) self.ci.addCommand('SetAntiAliasing', self.cmdSetAntiAliasing) self.ci.addCommand('_apiVersion', self.cmd_apiVersion) def cmdClose(self): """Close() Close this window.""" self.window.close() self.document = None self.window = None self.plot = None self.ci = None def cmdIsClosed(self): """IsClosed() Return whether window is still open.""" return not self.window.isVisible() def cmd_apiVersion(self): """Get internal API version.""" return API_VERSION def cmdZoom(self, zoom): """Zoom(zoom) Set the plot zoom level: This is a number to for the zoom from 1:1 or 'page': zoom to page 'width': zoom to fit width 'height': zoom to fit height """ self.window.setZoom(zoom) def cmdSetAntiAliasing(self, ison): """SetAntiAliasing(zoom) Enables or disables anti aliasing. """ self.window.setAntiAliasing(ison) def cmdEnableToolbar(self, enable=True): """EnableToolbar(enable=True) Enable the toolbar in this plotwindow. if enable is False, disable it. """ self.window.enableToolbar(enable) def cmdForceUpdate(self): """ForceUpdate() Forces an update of the plot window. """ self.plot.actionForceUpdate() def cmdGetClick(self): """GetClick() Return a clicked point. The user can click a point on the graph This returns a list of tuples containing items for each axis in the clicked region: (axisname, valonaxis) where axisname is the full name of an axis valonaxis is value clicked along the axis [] is returned if no axes span the clicked region """ return self.plot.getClick() def cmdResizeWindow(self, width, height): """ResizeWindow(width, height) Resize the window to be width x height pixels.""" self.window.resize(width, height) def cmdSetUpdateInterval(self, interval): """SetUpdateInterval(interval) Set graph update interval. interval is in milliseconds (ms) set to zero to disable updates set to -1 to update when document changes default interval is 250ms """ self.plot.setTimeout(interval) def cmdMoveToPage(self, pagenum): """MoveToPage(pagenum) Tell window to show specified pagenumber (starting from 1). """ self.plot.setPageNumber(pagenum-1) class EmbedApplication(qt4.QApplication): """Application to run remote end of embed connection. Commands are sent over stdin, with responses sent to stdout """ # lengths of lengths sent to application cmdlenlen = struct.calcsize(' and contributors. Licenced under the GPL (version 2 or greater). Veusz is a Qt4 based scientific plotting package. It is written in Python, using PyQt4 for display and user-interfaces, and numpy for handling the numeric data. Veusz is designed to produce publication-ready Postscript/PDF/SVG output. The user interface aims to be simple, consistent and powerful. Veusz provides a GUI, command line, embedding and scripting interface (based on Python) to its plotting facilities. It also allows for manipulation and editing of datasets. Data can be captured from external sources such as Internet sockets or other programs. Changes in 1.15: * Improved hatching: - More hatch styles - Adjust spacing of hatching - Change hatching line style - Allow hatching background color * Axes will not extend beyond specified min and max values * Add options to extend axes by 2, 5, 10 and 15% of data range * Ctrl+MouseWheel zooms in and out of plot * Full screen graph view mode * New dataset plugins - Linear interpolation - Cumulative value - Rolling average - Subtract mean / minimum * Allow grid widgets to be placed in grid widgets * Catch EnvironmentError exceptions on Windows * Allow multiple datasets to be selected in dataset browser * Allow tagging of datasets and allow datasets be grouped by tags in dataset browser * Allow text to be written as text in SVG, rather than curves * Add DBus interface to program, if DBus is installed * 2D QDP support * Add setup.py options for packagers --veusz-resource-dir : location of data files --disable-install-docs * Add title option for keys Minor changes: * Use / rather than \ for path separator in saved file names for Windows/Unix compatibility * Add diamond fill error bar type * Add \color and \marker commands to text renderer * Support labels on xy datasets if one of x or y datasets missing * Reorganise dataset plugin menu * Fix links in INSTALL/README * Floating point intervals in capture dialog Bug fixes: * Trap case where nan values could be plotted * Fix error if website not accessible in exception dialog * Crash when min and max of axes are too similar * Fix clipping of paths after transform in SVG files * Fix crash in picker * Fix crash if duplication of characters in CSV date format * Fix crash in tool tip in dataset browser * Fix GlobalColor error (on certain dark color sets) * Fix blocked data import if no descriptor * Fix crash if log contours and minimum is zero * Bug fix https://bugzilla.redhat.com/show_bug.cgi?id=800196 Features of package: * X-Y plots (with errorbars) * Line and function plots * Contour plots * Images (with colour mappings and colorbars) * Stepped plots (for histograms) * Bar graphs * Vector field plots * Box plots * Polar plots * Ternary plots * Plotting dates * Fitting functions to data * Stacked plots and arrays of plots * Plot keys * Plot labels * Shapes and arrows on plots * LaTeX-like formatting for text * EPS/PDF/PNG/SVG/EMF export * Scripting interface * Dataset creation/manipulation * Embed Veusz within other programs * Text, CSV, FITS, NPY/NPZ, QDP, binary and user-plugin importing * Data can be captured from external sources * User defined functions, constants and can import external Python functions * Plugin interface to allow user to write or load code to - import data using new formats - make new datasets, optionally linked to existing datasets - arbitrarily manipulate the document * Data picker * Interactive tutorial * Multithreaded rendering Requirements for source install: Python (2.4 or greater required) http://www.python.org/ Qt >= 4.4 (free edition) http://www.trolltech.com/products/qt/ PyQt >= 4.3 (SIP is required to be installed first) http://www.riverbankcomputing.co.uk/software/pyqt/ http://www.riverbankcomputing.co.uk/software/sip/ numpy >= 1.0 http://numpy.scipy.org/ Optional: PyFITS >= 1.1 (optional for FITS import) http://www.stsci.edu/resources/software_hardware/pyfits pyemf >= 2.0.0 (optional for EMF export) http://pyemf.sourceforge.net/ PyMinuit >= 1.1.2 (optional improved fitting) http://code.google.com/p/pyminuit/ For EMF and better SVG export, PyQt >= 4.6 or better is required, to fix a bug in the C++ wrapping dbus-python, for dbus interface http://dbus.freedesktop.org/doc/dbus-python/ For documentation on using Veusz, see the "Documents" directory. The manual is in PDF, HTML and text format (generated from docbook). The examples are also useful documentation. Please also see and contribute to the Veusz wiki: http://barmag.net/veusz-wiki/ Issues with the current version: * Some recent versions of PyQt/SIP will causes crashes when exporting SVG files. Update to 4.7.4 (if released) or a recent snapshot to solve this problem. If you enjoy using Veusz, we would love to hear from you. Please join the mailing lists at https://gna.org/mail/?group=veusz to discuss new features or if you'd like to contribute code. The latest code can always be found in the Git repository at https://github.com/jeremysanders/veusz.git. veusz-1.15/setup.py0000644002344000001440000001707011734662204014316 0ustar jssusers00000000000000#!/usr/bin/env python # Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Veusz distutils setup script see the file INSTALL for details on how to install Veusz """ import glob import os.path import sys import numpy from distutils.command.install_data import install_data from distutils.command.install import install as orig_install import pyqtdistutils # try to get py2app if it exists try: import py2app from setuptools import setup, Extension py2app = True except ImportError: from distutils.core import setup, Extension py2app = False # get version version = open('VERSION').read().strip() class install(orig_install): user_options = orig_install.user_options + [ # tell veusz where to install its data files ('veusz-resource-dir=', None, 'override veusz resource directory location'), ('disable-install-examples', None, 'do not install examples files'), ] boolean_options = orig_install.boolean_options + [ 'disable-install-examples'] def initialize_options(self): orig_install.initialize_options(self) self.veusz_resource_dir = None self.disable_install_examples = False # Pete Shinner's distutils data file fix... from distutils-sig # data installer with improved intelligence over distutils # data files are copied into the project directory instead # of willy-nilly class smart_install_data(install_data): def run(self): install_cmd = self.get_finalized_command('install') if install_cmd.veusz_resource_dir: # override location with veusz-resource-dir option self.install_dir = install_cmd.veusz_resource_dir else: # change self.install_dir to the library dir + veusz by default self.install_dir = os.path.join(install_cmd.install_lib, 'veusz') # disable examples install if requested if install_cmd.disable_install_examples: self.data_files = [f for f in self.data_files if f[0][-8:] != 'examples'] return install_data.run(self) descr = '''Veusz is a scientific plotting package, designed to create publication-ready Postscript, PDF and SVG output. It features GUI, command-line, and scripting interfaces. Graphs are constructed from "widgets", allowing complex layouts to be designed. Veusz supports plotting functions, data with errors, keys, labels, stacked plots, multiple plots, and fitting data.''' if py2app and sys.platform == 'darwin': # extra arguments for mac py2app to associate files plist = { 'CFBundleName': 'Veusz', 'CFBundleShortVersionString': version, 'CFBundleIdentifier': 'org.python.veusz', 'CFBundleDocumentTypes': [{ 'CFBundleTypeExtensions': ['vsz'], 'CFBundleTypeName': 'Veusz document', 'CFBundleTypeRole': 'Editor', }] } extraoptions = { 'setup_requires': ['py2app'], 'app': ['veusz_main.py'], 'options': { 'py2app': {'argv_emulation': True, 'includes': ('veusz.helpers._nc_cntr', 'veusz.helpers.qtloops'), 'plist': plist, 'iconfile': 'windows/icons/veusz.icns', } } } else: # otherwise package scripts extraoptions = { 'scripts': ['scripts/veusz', 'scripts/veusz_listen'] } def findData(dirname, extns): """Return tuple for directory name and list of file extensions for data.""" files = [] for extn in extns: files += glob.glob(os.path.join(dirname, '*.'+extn)) files.sort() return (dirname, files) setup(name = 'veusz', version = version, description = 'A scientific plotting package', long_description = descr, author = 'Jeremy Sanders', author_email = 'jeremy@jeremysanders.net', url = 'http://home.gna.org/veusz/', license = 'GPL', classifiers = [ 'Programming Language :: Python', 'Development Status :: 5 - Production/Stable', 'Environment :: X11 Applications :: Qt', 'Intended Audience :: Science/Research', 'License :: OSI Approved :: ' 'GNU General Public License (GPL)', 'Topic :: Scientific/Engineering :: Visualization' ], package_dir = { 'veusz': '', 'veusz.dialogs': 'dialogs', 'veusz.document': 'document', 'veusz.helpers': 'helpers', 'veusz.plugins': 'plugins', 'veusz.qtwidgets': 'qtwidgets', 'veusz.setting': 'setting', 'veusz.tests': 'tests', 'veusz.utils': 'utils', 'veusz.widgets': 'widgets', 'veusz.windows': 'windows', }, data_files = [ ('', ['VERSION']), findData('dialogs', ('ui',)), findData('windows/icons', ('png', 'svg')), findData('examples', ('vsz', 'py', 'csv', 'dat')), ], packages = [ 'veusz', 'veusz.dialogs', 'veusz.document', 'veusz.helpers', 'veusz.plugins', 'veusz.qtwidgets', 'veusz.setting', 'veusz.utils', 'veusz.widgets', 'veusz.windows', ], ext_modules = [ # contour plotting library Extension('veusz.helpers._nc_cntr', ['helpers/src/_nc_cntr.c'], include_dirs=[numpy.get_include()]), # qt helper module Extension('veusz.helpers.qtloops', ['helpers/src/qtloops.cpp', 'helpers/src/qtloops_helpers.cpp', 'helpers/src/polygonclip.cpp', 'helpers/src/polylineclip.cpp', 'helpers/src/beziers.cpp', 'helpers/src/beziers_qtwrap.cpp', 'helpers/src/numpyfuncs.cpp', 'helpers/src/recordpaintdevice.cpp', 'helpers/src/recordpaintengine.cpp', 'helpers/src/qtloops.sip'], language="c++", include_dirs=['/helpers/src', numpy.get_include()], ), ], cmdclass = {'build_ext': pyqtdistutils.build_ext, 'install_data': smart_install_data, 'install': install}, **extraoptions ) veusz-1.15/scripts/0000755002344000001440000000000011734662466014300 5ustar jssusers00000000000000veusz-1.15/scripts/veusz0000644002344000001440000000177511734662204015377 0ustar jssusers00000000000000#!/usr/bin/env python # Run main veusz python script # # Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.veusz_main veusz.veusz_main.run() veusz-1.15/scripts/veusz_listen0000644002344000001440000000200011734662204016733 0ustar jssusers00000000000000#!/usr/bin/env python # Runs veusz listening script # # Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.veusz_listen veusz.veusz_listen.run() veusz-1.15/document/0000755002344000001440000000000011734662466014427 5ustar jssusers00000000000000veusz-1.15/document/dbusinterface.py0000644002344000001440000002510711734662204017612 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """DBus interface to Veusz document.""" import veusz.utils.vzdbus as vzdbus import commandinterpreter class DBusInterface(vzdbus.Object): """DBus interface to Veusz document command interface.""" _ctr = 1 interface = 'org.veusz.document' def __init__(self, doc): root = '/Windows/%i/Document' % DBusInterface._ctr vzdbus.Object.__init__(self, vzdbus.sessionbus, root) self.index = DBusInterface._ctr DBusInterface._ctr += 1 self.cmdinter = commandinterpreter.CommandInterpreter(doc) self.ci = self.cmdinter.interface @vzdbus.method(dbus_interface=interface, in_signature='s') def RunPython(self, cmdstr): return self.cmdinter.run(cmdstr) @vzdbus.method(dbus_interface=interface, in_signature='sa{sv}') def Action(self, action, optargs): return self.ci.Action(action, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sa{sv}', out_signature='s') def Add(self, wtype, optargs): return self.ci.Add(wtype, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sssa{sv}') def AddCustom(self, thetype, name, val, argsv): self.ci.AddCustom(thetype, name, val, **argsv) @vzdbus.method(dbus_interface=interface, in_signature='s') def AddImportPath(self, dirname): self.ci.AddImportPath(unicode(dirname)) @vzdbus.method(dbus_interface=interface, in_signature='ssa{sv}', out_signature='s') def CloneWidget(self, widget, newparent, optargs): return self.ci.CloneWidget(unicode(widget), unicode(newparent), **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sssa{sv}') def CreateHistogram(self, inexpr, outbinsds, outvalsds, optargs): self.ci.CreateHistogram(unicode(inexpr), unicode(outbinsds), unicode(outvalsds), **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sa{sv}a{sv}') def DatasetPlugin(self, pluginname, fields, datasetnames): self.ci.DatasetPlugin(unicode(pluginname), fields, datasetnames) @vzdbus.method(dbus_interface=interface, in_signature='sa{sv}') def Export(self, filename, optargs): self.ci.Export(filename, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='v') def Get(self, val): return self.ci.Get(val) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='as') def GetChildren(self, where): return self.ci.GetChildren(where=where) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='adadadad') def GetData1D(self, datasetname): """Get a numeric dataset. Returns lists of numeric values for data, symmetric error, negative error and positive error.""" def lornull(l): """Get blank list if None or convert to list otherwise.""" if l is None: return [] return list(l) data, serr, nerr, perr = self.ci.GetData(unicode(datasetname)) return lornull(data), lornull(serr), lornull(nerr), lornull(perr) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='iiddddad') def GetData2D(self, datasetname): """Get a 2D dataset. Returns (X dim, Y dim, rangex min, rangex max, rangey min, rangey max, data (as 1d numeric array)) """ data = self.ci.GetData(unicode(datasetname)) return ( data[0].shape[1], data[0].shape[0], data[1][0], data[1][1], data[2][0], data[2][1], list(data[0].flat) ) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='as') def GetDataText(self, datasetname): """Get a text dataset as an array of strings.""" return self.ci.GetData(unicode(datasetname)) @vzdbus.method(dbus_interface=interface, out_signature='as') def GetDatasets(self): return self.ci.GetDatasets() @vzdbus.method(dbus_interface=interface, in_signature='ssa{sv}') def ImportFile(self, filename, descriptor, optargs): self.ci.ImportFile(filename, descriptor, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sasa{sv}') def ImportFile2D(self, filename, datasetnames, optargs): self.ci.ImportFile2D(filename, datasetnames, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sa{sv}') def ImportFileCSV(self, filename, optargs): self.ci.ImportFileCSV(filename, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sssa{sv}') def ImportFITSFile(self, dsname, filename, hdu, optargs): self.ci.ImportFITSFile(filename, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='ssa{sv}') def ImportFileCSV(self, plugin, filename, optargs): self.ci.ImportFilePlugin(plugin, filename, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='ssa{sv}') def ImportString(self, descriptor, string, optargs): self.ci.ImportString(unicode(descriptor), unicode(string), **optargs) @vzdbus.method(dbus_interface=interface, in_signature='s') def Load(self, filename): self.cmdinter.Load(filename) @vzdbus.method(dbus_interface=interface) def Print(self): self.ci.Print() @vzdbus.method(dbus_interface=interface) def ReloadData(self): self.ci.ReloadData() @vzdbus.method(dbus_interface=interface, in_signature='ss') def Rename(self, widget, newname): self.ci.Rename( unicode(widget), unicode(newname) ) @vzdbus.method(dbus_interface=interface, in_signature='s') def Remove(self, name): self.ci.Remove(unicode(name)) @vzdbus.method(dbus_interface=interface, in_signature='s') def RemoveCustom(self, name): self.ci.RemoveCustom(unicode(name)) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='s') def ResolveReference(self, name): return self.ci.ResolveReference(unicode(name)) @vzdbus.method(dbus_interface=interface, in_signature='s') def Save(self, filename): self.ci.Save(unicode(filename)) @vzdbus.method(dbus_interface=interface, in_signature='sv') def Set(self, name, val): return self.ci.Set(unicode(name), val) @vzdbus.method(dbus_interface=interface, in_signature='ss') def SetToReference(self, name, val): return self.ci.SetToReference(unicode(name), unicode(val)) @vzdbus.method(dbus_interface=interface, in_signature='sadadadad') def SetData(self, name, data, symerr, negerr, poserr): if not symerr: symerr = None if not negerr: negerr = None if not poserr: poserr = None self.ci.SetData(unicode(name), data, symerr, negerr, poserr) @vzdbus.method(dbus_interface=interface, in_signature='ssssa{sv}') def SetData2DExpressionXYZ(self, name, xexpr, yexpr, zexpr, optargs): self.ci.SetData2DExpressionXYZ(unicode(name), xexpr, yexpr, zexpr, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='s(ddd)(ddd)sa{sv}') def SetData2DXYFunc(self, name, xstep, ystep, expr, optargs): self.ci.SetData2DXYFunc(unicode(name), xstep, ystep, expr, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sadii(dd)(dd)') def SetData2D(self, name, data, nx, ny, xrange, yrange): data = N.array(data).reshape(nx, ny) self.ci.SetData2D(unicode(name), data, xrange=xrange, yrange=yrange) @vzdbus.method(dbus_interface=interface, in_signature='ssa{sv}') def SetDataExpression(self, name, val, optargs): self.ci.SetDataExpression(unicode(name), val, **optargs) @vzdbus.method(dbus_interface=interface, in_signature='sas') def SetDataText(self, name, val): val = [unicode(x) for x in val] self.ci.SetDataText(unicode(name), val) @vzdbus.method(dbus_interface=interface, in_signature='ss') def SetToReference(self, var, val): self.ci.SetToReference(var, val) @vzdbus.method(dbus_interface=interface, in_signature='sas') def TagDatasets(self, tag, datasets): self.ci.TagDatasets(unicode(tag), datasets) @vzdbus.method(dbus_interface=interface, in_signature='s') def To(self, path): self.ci.To(path) # node interface @vzdbus.method(dbus_interface=interface, in_signature='ss', out_signature='as') def NodeChildren(self, path, types): return self.ci.NodeChildren(path, types) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='s') def NodeType(self, path): return self.ci.NodeType(path) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='s') def SettingType(self, path): return self.ci.SettingType(path) @vzdbus.method(dbus_interface=interface, in_signature='s', out_signature='s') def WidgetType(self, path): return self.ci.WidgetType(path) veusz-1.15/document/operations.py0000644002344000001440000013763711734662204017173 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Represents atomic operations to take place on a document which can be undone. Rather than the document modified directly, this interface should be used. Operations should be passed to the document to be enacted with applyOperation Each operation provides do(document) and undo(document) methods. Operations store paths to objects to be modified rather than object references because some operations cannot restore references (e.g. add object) """ import os.path from itertools import izip from collections import defaultdict import numpy as N import datasets import widgetfactory import simpleread import readcsv import importparams import datasets import linked import veusz.utils as utils import veusz.plugins as plugins ############################################################################### # Setting operations class OperationSettingSet(object): """Set a variable to a value.""" descr = 'change setting' def __init__(self, setting, value): """Set the setting to value. Setting may be a widget path """ if isinstance(setting, basestring): self.settingpath = setting else: self.settingpath = setting.path self.value = value def do(self, document): """Apply setting variable.""" setting = document.resolveFullSettingPath(self.settingpath) if setting.isReference(): self.oldvalue = setting.getReference() else: self.oldvalue = setting.get() setting.set(self.value) def undo(self, document): """Return old value back...""" setting = document.resolveFullSettingPath(self.settingpath) setting.set(self.oldvalue) class OperationSettingPropagate(object): """Propagate setting to other widgets.""" descr = 'propagate setting' def __init__(self, setting, widgetname = None, root = None, maxlevels = -1): """Take the setting given, and propagate it to other widgets, according to the parameters here. If widgetname is given then only propagate it to widgets with the name given. widgets are located from the widget given (root if not set) Up to maxlevels levels of widgets are changed (<0 means infinite) """ self.val = setting.val self.widgetname = widgetname if root: self.rootpath = root.path else: self.rootpath = None self.maxlevels = maxlevels # work out path of setting relative to widget path = [] s = setting while not s.isWidget(): path.insert(0, s.name) s = s.parent self.setpath = path[1:] self.widgettype = s.typename def do(self, document): """Apply the setting propagation.""" # default is root widget if not self.rootpath: root = document.basewidget else: root = document.resolveFullWidgetPath(self.rootpath) # get a list of matching widgets widgetlist = [] self._recursiveGet(root, self.widgetname, self.widgettype, widgetlist, self.maxlevels) self.restorevals = {} # set the settings for the widgets for w in widgetlist: # lookup the setting s = w.settings for i in self.setpath: s = s.get(i) self.restorevals[s.path] = s.val s.set(self.val) def undo(self, document): """Undo all those changes.""" for setpath, setval in self.restorevals.iteritems(): setting = document.resolveFullSettingPath(setpath) setting.set(setval) def _recursiveGet(root, name, typename, outlist, maxlevels): """Add those widgets in root with name and type to outlist. If name or typename are None, then ignore the criterion. maxlevels is the maximum number of levels to check """ if maxlevels != 0: # if levels is not zero, add the children of this root newmaxlevels = maxlevels - 1 for w in root.children: if ( (w.name == name or name is None) and (w.typename == typename or typename is None) ): outlist.append(w) OperationSettingPropagate._recursiveGet(w, name, typename, outlist, newmaxlevels) _recursiveGet = staticmethod(_recursiveGet) ############################################################################### # Widget operations class OperationWidgetRename(object): """Rename widget.""" descr = 'rename' def __init__(self, widget, newname): """Rename the widget to newname.""" self.widgetpath = widget.path self.newname = newname def do(self, document): """Rename widget.""" widget = document.resolveFullWidgetPath(self.widgetpath) self.oldname = widget.name widget.rename(self.newname) self.newpath = widget.path def undo(self, document): """Undo rename.""" widget = document.resolveFullWidgetPath(self.newpath) widget.rename(self.oldname) class OperationWidgetDelete(object): """Delete widget.""" descr = 'delete' def __init__(self, widget): """Delete the widget.""" self.widgetpath = widget.path def do(self, document): """Delete widget.""" self.oldwidget = document.resolveFullWidgetPath(self.widgetpath) oldparent = self.oldwidget.parent self.oldwidget.parent = None self.oldparentpath = oldparent.path self.oldindex = oldparent.children.index(self.oldwidget) oldparent.removeChild(self.oldwidget.name) def undo(self, document): """Restore deleted widget.""" oldparent = document.resolveFullWidgetPath(self.oldparentpath) self.oldwidget.parent = oldparent oldparent.addChild(self.oldwidget, index=self.oldindex) class OperationWidgetsDelete(object): """Delete mutliple widget.""" descr = 'delete' def __init__(self, widgets): """Delete the widget.""" self.widgetpaths = [w.path for w in widgets] def do(self, document): """Delete widget.""" # ignore widgets which share ancestry # as deleting the parent deletes the child widgetpaths = list(self.widgetpaths) widgetpaths.sort( cmp=lambda a, b: len(a)-len(b) ) i = 0 while i < len(widgetpaths): wp = widgetpaths[i] for j in xrange(i): if wp[:len(widgetpaths[j])+1] == widgetpaths[j]+'/': del widgetpaths[i] break else: i += 1 self.oldwidgets = [] self.oldparentpaths = [] self.oldindexes = [] # delete each widget keeping track of details for path in widgetpaths: self.oldwidgets.append( document.resolveFullWidgetPath(path) ) oldparent = self.oldwidgets[-1].parent self.oldparentpaths.append( oldparent.path ) self.oldindexes.append( oldparent.children.index(self.oldwidgets[-1]) ) oldparent.removeChild(self.oldwidgets[-1].name) def undo(self, document): """Restore deleted widget.""" # put back widgets in reverse order so that indexes are corrent for i in xrange(len(self.oldwidgets)-1,-1,-1): oldparent = document.resolveFullWidgetPath(self.oldparentpaths[i]) oldparent.addChild(self.oldwidgets[i], index=self.oldindexes[i]) class OperationWidgetMoveUpDown(object): """Move a widget up or down in the hierarchy.""" descr = 'move' def __init__(self, widget, direction): """Move the widget specified up or down in the hierarchy. direction is -1 for 'up' or +1 for 'down' """ self.widgetpath = widget.path self.direction = direction def do(self, document): """Move the widget.""" widget = document.resolveFullWidgetPath(self.widgetpath) parent = widget.parent self.suceeded = parent.moveChild(widget, self.direction) self.newpath = widget.path def undo(self, document): """Move it back.""" if self.suceeded: widget = document.resolveFullWidgetPath(self.newpath) parent = widget.parent parent.moveChild(widget, -self.direction) class OperationWidgetMove(object): """Move a widget arbitrarily in the hierarchy.""" descr = 'move' def __init__(self, oldchildpath, newparentpath, newindex): """Move widget with path oldchildpath to be a child of newparentpath and with index newindex.""" self.oldchildpath = oldchildpath self.newparentpath = newparentpath self.newindex = newindex def do(self, document): """Move widget.""" child = document.resolveFullWidgetPath(self.oldchildpath) oldparent = child.parent newparent = document.resolveFullWidgetPath(self.newparentpath) self.oldchildindex = oldparent.children.index(child) self.oldparentpath = oldparent.path self.oldname = None if self.newindex < 0: # convert negative index to normal index self.newindex = len(newparent.children) if oldparent is newparent: # moving within same parent self.movemode = 'sameparent' del oldparent.children[self.oldchildindex] if self.newindex > self.oldchildindex: self.newindex -= 1 oldparent.children.insert(self.newindex, child) else: # moving to different parent self.movemode = 'differentparent' # remove from old parent del oldparent.children[self.oldchildindex] # current names of children childnames = newparent.childnames # record previous parent and position newparent.children.insert(self.newindex, child) child.parent = newparent # set a new name, if required if child.name in childnames: self.oldname = child.name child.name = child.chooseName() self.newchildpath = child.path def undo(self, document): """Undo move.""" newparent = document.resolveFullWidgetPath(self.newparentpath) child = document.resolveFullWidgetPath(self.newchildpath) oldparent = document.resolveFullWidgetPath(self.oldparentpath) # remove from new parent del newparent.children[self.newindex] # restore parent oldparent.children.insert(self.oldchildindex, child) child.parent = oldparent # restore name if self.oldname is not None: child.name = self.oldname class OperationWidgetAdd(object): """Add a widget of specified type to parent.""" descr = 'add' def __init__(self, parent, type, autoadd=True, name=None, index=-1, **defaultvals): """Add a widget of type given parent is the parent widget type is the type to add (string) autoadd adds children automatically for some widgets name is the (optional) name of the new widget index is position in parent to add the widget settings can be passed to the created widgets as optional arguments """ self.parentpath = parent.path self.type = type self.autoadd = autoadd self.name = name self.index = index self.defaultvals = defaultvals def do(self, document): """Create the new widget. Returns the new widget """ parent = document.resolveFullWidgetPath(self.parentpath) w = widgetfactory.thefactory.makeWidget(self.type, parent, autoadd=self.autoadd, name=self.name, index=self.index, **self.defaultvals) self.createdname = w.name return w def undo(self, document): """Remove the added widget.""" parent = document.resolveFullWidgetPath(self.parentpath) parent.removeChild(self.createdname) ############################################################################### # Dataset operations class OperationDatasetSet(object): """Set a dataset to that specified.""" descr = 'set dataset' def __init__(self, datasetname, dataset): self.datasetname = datasetname self.dataset = dataset def do(self, document): """Set dataset, backing up existing one.""" if self.datasetname in document.data: self.olddata = document.data[self.datasetname] else: self.olddata = None document.setData(self.datasetname, self.dataset) def undo(self, document): """Undo the data setting.""" document.deleteData(self.datasetname) if self.olddata is not None: document.setData(self.datasetname, self.olddata) class OperationDatasetDelete(object): """Delete a dateset.""" descr = 'delete dataset' def __init__(self, datasetname): self.datasetname = datasetname def do(self, document): """Remove dataset from document, but preserve for undo.""" self.olddata = document.data[self.datasetname] document.deleteData(self.datasetname) def undo(self, document): """Put dataset back""" document.setData(self.datasetname, self.olddata) class OperationDatasetRename(object): """Rename the dataset. Assumes newname doesn't already exist """ descr = 'rename dataset' def __init__(self, oldname, newname): self.oldname = oldname self.newname = newname def do(self, document): """Rename dataset from oldname to newname.""" document.renameDataset(self.oldname, self.newname) def undo(self, document): """Change name back.""" document.renameDataset(self.newname, self.oldname) class OperationDatasetDuplicate(object): """Duplicate a dataset. Assumes duplicate name doesn't already exist """ descr = 'duplicate dataset' def __init__(self, origname, duplname): self.origname = origname self.duplname = duplname def do(self, document): """Make the duplicate""" self.olddata = document.data.get(self.duplname, None) dataset = document.data[self.origname] duplicate = dataset.returnCopy() document.setData(self.duplname, duplicate) def undo(self, document): """Delete the duplicate""" if self.olddata is None: document.deleteData(self.duplname) else: document.setData(self.duplname, self.olddata) class OperationDatasetUnlinkFile(object): """Remove association between dataset and file.""" descr = 'unlink dataset' def __init__(self, datasetname): self.datasetname = datasetname def do(self, document): dataset = document.data[self.datasetname] self.oldfilelink = dataset.linked dataset.linked = None def undo(self, document): dataset = document.data[self.datasetname] dataset.linked = self.oldfilelink class OperationDatasetUnlinkRelation(object): """Remove association between dataset and another dataset. """ descr = 'unlink dataset' def __init__(self, datasetname): self.datasetname = datasetname def do(self, document): dataset = document.data[self.datasetname] self.olddataset = dataset ds = dataset.returnCopy() document.setData(self.datasetname, ds) def undo(self, document): document.setData(self.datasetname, self.olddataset) class OperationDatasetCreate(object): """Create dataset base class.""" def __init__(self, datasetname): self.datasetname = datasetname self.parts = {} def setPart(self, part, val): self.parts[part] = val def do(self, document): """Record old dataset if it exists.""" self.olddataset = document.data.get(self.datasetname, None) def undo(self, document): """Delete the created dataset.""" document.deleteData(self.datasetname) if self.olddataset is not None: document.setData(self.datasetname, self.olddataset) class OperationDatasetCreateRange(OperationDatasetCreate): """Create a dataset in a specfied range.""" descr = 'create dataset from range' def __init__(self, datasetname, numsteps, parts, linked=False): """Create a dataset with numsteps values. parts is a dict containing keys 'data', 'serr', 'perr' and/or 'nerr'. The values are tuples with (start, stop) values for each range. """ OperationDatasetCreate.__init__(self, datasetname) self.numsteps = numsteps self.parts = parts self.linked = linked def do(self, document): """Create dataset using range.""" OperationDatasetCreate.do(self, document) data = self.parts['data'] serr = self.parts.get('serr', None) perr = self.parts.get('perr', None) nerr = self.parts.get('nerr', None) ds = datasets.DatasetRange(self.numsteps, data, serr=serr, perr=perr, nerr=nerr) if not self.linked: # copy these values if we don't want to link ds = datasets.Dataset(data=ds.data, serr=ds.serr, perr=ds.perr, nerr=ds.nerr) document.setData(self.datasetname, ds) return ds class CreateDatasetException(Exception): """Thrown by dataset creation routines.""" pass class OperationDatasetCreateParameteric(OperationDatasetCreate): """Create a dataset using expressions dependent on t.""" descr = 'create parametric dataset' def __init__(self, datasetname, t0, t1, numsteps, parts, linked=False): """Create a parametric dataset. Variable t goes from t0 to t1 in numsteps. parts is a dict with keys 'data', 'serr', 'perr' and/or 'nerr' The values are expressions for evaluating.""" OperationDatasetCreate.__init__(self, datasetname) self.numsteps = numsteps self.t0 = t0 self.t1 = t1 self.parts = parts self.linked = linked def do(self, document): """Create the dataset.""" OperationDatasetCreate.do(self, document) p = self.parts.copy() p['parametric'] = (self.t0, self.t1, self.numsteps) ds = datasets.DatasetExpression(**p) ds.document = document if not self.linked: # copy these values if we don't want to link ds = datasets.Dataset(data=ds.data, serr=ds.serr, perr=ds.perr, nerr=ds.nerr) document.setData(self.datasetname, ds) return ds class OperationDatasetCreateExpression(OperationDatasetCreate): descr = 'create dataset from expression' def __init__(self, datasetname, parts, link, parametric=None): """Create a dataset from existing dataset using expressions. parts is a dict with keys 'data', 'serr', 'perr' and/or 'nerr' The values are expressions for evaluating. If link is True, then the dataset is linked to the expressions Parametric is a tuple (min, max, numitems) if creating parametric datasets. """ OperationDatasetCreate.__init__(self, datasetname) self.parts = parts self.link = link self.parametric = parametric def validateExpression(self, document): """Validate the expression is okay. A CreateDatasetException is raised if not """ p = self.parts.copy() p['parametric'] = self.parametric ds = datasets.DatasetExpression(**p) ds.document = document try: # we force an evaluation of the dataset for the first time, to # check for errors in the expressions ds.updateEvaluation() except datasets.DatasetExpressionException, e: raise CreateDatasetException(unicode(e)) def do(self, document): """Create the dataset.""" OperationDatasetCreate.do(self, document) p = self.parts.copy() p['parametric'] = self.parametric ds = datasets.DatasetExpression(**p) ds.document = document if not self.link: # copy these values if we don't want to link ds = datasets.Dataset(data=ds.data, serr=ds.serr, perr=ds.perr, nerr=ds.nerr) document.setData(self.datasetname, ds) return ds class OperationDataset2DBase(object): """Operation as base for 2D dataset creation operations.""" def __init__(self, name, link): """Setup operation.""" self.datasetname = name self.link = link def validateExpression(self, document): """Validate expression is okay.""" try: ds = self.makeDSClass() ds.document = document ds.evalDataset() except datasets.DatasetExpressionException, e: raise CreateDatasetException(unicode(e)) def do(self, document): """Make new dataset.""" # keep backup of old if exists self.olddataset = document.data.get(self.datasetname, None) # make new dataset ds = self.makeDSClass() ds.document = document if not self.link: # unlink if necessary ds = datasets.Dataset2D(ds.data, xrange=ds.xrange, yrange=ds.yrange) document.setData(self.datasetname, ds) return ds def undo(self, document): """Undo dataset creation.""" document.deleteData(self.datasetname) if self.olddataset: document.setData(self.datasetname, self.olddataset) class OperationDataset2DCreateExpressionXYZ(OperationDataset2DBase): descr = 'create 2D dataset from x, y and z expressions' def __init__(self, datasetname, xexpr, yexpr, zexpr, link): OperationDataset2DBase.__init__(self, datasetname, link) self.xexpr = xexpr self.yexpr = yexpr self.zexpr = zexpr def makeDSClass(self): return datasets.Dataset2DXYZExpression( self.xexpr, self.yexpr, self.zexpr) class OperationDataset2DCreateExpression(OperationDataset2DBase): descr = 'create 2D dataset from expression' def __init__(self, datasetname, expr, link): OperationDataset2DBase.__init__(self, datasetname, link) self.expr = expr def makeDSClass(self): return datasets.Dataset2DExpression(self.expr) class OperationDataset2DXYFunc(OperationDataset2DBase): descr = 'create 2D dataset from function of x and y' def __init__(self, datasetname, xstep, ystep, expr, link): """Create 2d dataset: xstep: tuple(xmin, xmax, step) ystep: tuple(ymin, ymax, step) expr: expression of x and y link: whether to link to this expression """ OperationDataset2DBase.__init__(self, datasetname, link) self.xstep = xstep self.ystep = ystep self.expr = expr def makeDSClass(self): return datasets.Dataset2DXYFunc(self.xstep, self.ystep, self.expr) class OperationDatasetUnlinkByFile(object): """Unlink all datasets associated with file.""" descr = "unlink datasets" def __init__(self, filename): """Unlink all datasets associated with filename.""" self.filename = filename def do(self, document): """Remove links.""" self.oldlinks = {} for name, ds in document.data.iteritems(): if ds.linked is not None and ds.linked.filename == self.filename: self.oldlinks[name] = ds.linked ds.linked = None def undo(self, document): """Restore links.""" for name, link in self.oldlinks.iteritems(): try: document.data[name].linked = link except KeyError: pass class OperationDatasetDeleteByFile(object): """Delete all datasets associated with file.""" descr = "delete datasets" def __init__(self, filename): """Delete all datasets associated with filename.""" self.filename = filename def do(self, document): """Remove datasets.""" self.olddatasets = {} for name, ds in document.data.items(): if ds.linked is not None and ds.linked.filename == self.filename: self.olddatasets[name] = ds document.deleteData(name) def undo(self, document): """Restore datasets.""" for name, ds in self.olddatasets.iteritems(): document.setData(name, ds) ############################################################################### # Import datasets class OperationDataImportBase(object): """Default useful import class.""" def __init__(self, params): self.params = params # list of returned datasets self.outdatasets = [] # list of returned custom variables self.outcustoms = [] # invalid conversions self.outinvalids = {} def doImport(self, document): """Do import, override this. Return list of names of datasets read """ def addCustoms(self, document, consts): """Optionally, add the customs return by plugins to document.""" if len(consts) > 0: self.oldconst = list(document.customs) cd = document.customDict() for item in consts: if item[1] in cd: idx, ctype, val = cd[item[1]] document.customs[idx] = item else: document.customs.append(item) document.updateEvalContext() def do(self, document): """Do import.""" # remember datasets in document for undo olddatasets = dict(document.data) self.oldconst = None # do actual import self.doImport(document) # only remember the parts we need self.olddatasets = [ (n, olddatasets.get(n)) for n in self.outdatasets ] # apply tags if self.params.tags: for n in self.outdatasets: document.data[n].tags.update(self.params.tags) def undo(self, document): """Undo import.""" # put back old datasets for name, ds in self.olddatasets: if ds is None: document.deleteData(name) else: document.setData(name, ds) # for custom definitions if self.oldconst is not None: document.customs = self.oldconst document.updateEvalContext() class OperationDataImport(OperationDataImportBase): """Import 1D data from text files.""" descr = 'import data' def __init__(self, params): """Setup operation. """ OperationDataImportBase.__init__(self, params) self.simpleread = simpleread.SimpleRead(params.descriptor) def doImport(self, document): """Import data. Returns a list of datasets which were imported. """ p = self.params # open stream to import data from if p.filename is not None: stream = simpleread.FileStream( utils.openEncoding(p.filename, p.encoding)) elif p.datastr is not None: stream = simpleread.StringStream(p.datastr) else: raise RuntimeError, "No filename or string" # do the import self.simpleread.clearState() self.simpleread.readData(stream, useblocks=p.useblocks, ignoretext=p.ignoretext) # associate linked file LF = None if p.linked: assert p.filename LF = linked.LinkedFile(p) # actually set the data in the document self.outdatasets = self.simpleread.setInDocument( document, linkedfile=LF, prefix=p.prefix, suffix=p.suffix) self.outinvalids = self.simpleread.getInvalidConversions() class OperationDataImportCSV(OperationDataImportBase): """Import data from a CSV file.""" descr = 'import CSV data' def doImport(self, document): """Do the data import.""" csvr = readcsv.ReadCSV(self.params) csvr.readData() LF = None if self.params.linked: LF = linked.LinkedFileCSV(self.params) # set the data self.outdatasets = csvr.setData(document, linkedfile=LF) class OperationDataImport2D(OperationDataImportBase): """Import a 2D matrix from a file.""" descr = 'import 2d data' def doImport(self, document): """Import data.""" p = self.params # get stream if p.filename is not None: stream = simpleread.FileStream( utils.openEncoding(p.filename, p.encoding) ) elif p.datastr is not None: stream = simpleread.StringStream(p.datastr) else: assert False # linked file LF = None if p.linked: assert p.filename LF = linked.LinkedFile2D(p) for name in p.datasetnames: sr = simpleread.SimpleRead2D(name, p) sr.readData(stream) self.outdatasets += sr.setInDocument(document, linkedfile=LF) class OperationDataImportFITS(OperationDataImportBase): """Import 1d or 2d data from a fits file.""" descr = 'import FITS file' def _import1d(self, hdu): """Import 1d data from hdu.""" data = hdu.data datav = None symv = None posv = None negv = None # read the columns required p = self.params if p.datacol is not None: datav = data.field(p.datacol) if p.symerrcol is not None: symv = data.field(p.symerrcol) if p.poserrcol is not None: posv = data.field(p.poserrcol) if p.negerrcol is not None: negv = data.field(p.negerrcol) # actually create the dataset return datasets.Dataset(data=datav, serr=symv, perr=posv, nerr=negv) def _import1dimage(self, hdu): """Import 1d image data form hdu.""" return datasets.Dataset(data=hdu.data) def _import2dimage(self, hdu): """Import 2d image data from hdu.""" p = self.params if ( p.datacol is not None or p.symerrcol is not None or p.poserrcol is not None or p.negerrcol is not None ): print "Warning: ignoring columns as import 2D dataset" header = hdu.header data = hdu.data try: # try to read WCS for image, and work out x/yrange wcs = [header[i] for i in ('CRVAL1', 'CRPIX1', 'CDELT1', 'CRVAL2', 'CRPIX2', 'CDELT2')] rangex = ( (data.shape[1]-wcs[1])*wcs[2] + wcs[0], (0-wcs[1])*wcs[2] + wcs[0]) rangey = ( (0-wcs[4])*wcs[5] + wcs[3], (data.shape[0]-wcs[4])*wcs[5] + wcs[3] ) rangex = (rangex[1], rangex[0]) except KeyError: # no / broken wcs rangex = None rangey = None return datasets.Dataset2D(data, xrange=rangex, yrange=rangey) def doImport(self, document): """Do the import.""" try: import pyfits except ImportError: raise RuntimeError, ( 'PyFITS is required to import ' 'data from FITS files' ) p = self.params f = pyfits.open( str(p.filename), 'readonly') hdu = f[p.hdu] data = hdu.data try: # raise an exception if this isn't a table therefore is an image hdu.get_coldefs() ds = self._import1d(hdu) except AttributeError: naxis = hdu.header.get('NAXIS') if naxis == 1: ds = self._import1dimage(hdu) elif naxis == 2: ds = self._import2dimage(hdu) else: raise RuntimeError, "Cannot import images with %i dimensions" % naxis f.close() if p.linked: ds.linked = linked.LinkedFileFITS(self.params) if p.dsname in document.data: self.olddataset = document.data[p.dsname] else: self.olddataset = None document.setData(p.dsname, ds) self.outdatasets.append(p.dsname) class OperationDataImportPlugin(OperationDataImportBase): """Import data using a plugin.""" descr = 'import using plugin' def doImport(self, document): """Do import.""" pluginnames = [p.name for p in plugins.importpluginregistry] plugin = plugins.importpluginregistry[ pluginnames.index(self.params.plugin)] # if the plugin is a class, make an instance # the old API is for the plugin to be instances if isinstance(plugin, type): plugin = plugin() # strip out parameters for plugin itself p = self.params # stick back together the plugin parameter object plugparams = plugins.ImportPluginParams( p.filename, p.encoding, p.pluginpars) results = plugin.doImport(plugparams) # make link for file LF = None if p.linked: LF = linked.LinkedFilePlugin(p) customs = [] # convert results to real datasets names = [] for d in results: if isinstance(d, plugins.Dataset1D): ds = datasets.Dataset(data=d.data, serr=d.serr, perr=d.perr, nerr=d.nerr) elif isinstance(d, plugins.Dataset2D): ds = datasets.Dataset2D(data=d.data, xrange=d.rangex, yrange=d.rangey) elif isinstance(d, plugins.DatasetText): ds = datasets.DatasetText(data=d.data) elif isinstance(d, plugins.DatasetDateTime): ds = datasets.DatasetDateTime(data=d.data) elif isinstance(d, plugins.Constant): customs.append( ['constant', d.name, d.val] ) continue elif isinstance(d, plugins.Function): customs.append( ['function', d.name, d.val] ) continue else: raise RuntimeError("Invalid data set in plugin results") # set any linking if linked: ds.linked = LF # construct name name = p.prefix + d.name + p.suffix # actually make dataset document.setData(name, ds) names.append(name) # add constants, functions to doc, if any self.addCustoms(document, customs) self.outdatasets = names self.outcustoms = list(customs) class OperationDataCaptureSet(object): """An operation for setting the results from a SimpleRead into the docunment's data from a data capture. This is a bit primative, but it is not obvious how to isolate the capturing functionality elsewhere.""" descr = 'data capture' def __init__(self, simplereadobject): """Takes a simpleread object containing the data to be set.""" self.simplereadobject = simplereadobject def do(self, document): """Set the data in the document.""" # before replacing data, get a backup of document's data databackup = dict(document.data) # set the data to the document and keep a list of what's changed self.nameschanged = self.simplereadobject.setInDocument(document) # keep a copy of datasets which have changed from backup self.olddata = {} for name in self.nameschanged: if name in databackup: self.olddata[name] = databackup[name] def undo(self, document): """Undo the results of the capture.""" for name in self.nameschanged: if name in self.olddata: # replace datasets with what was there previously document.setData(name, self.olddata[name]) else: # or delete datasets that weren't there before document.deleteData(name) class OperationDataTag(object): """Add a tag to a list of datasets.""" descr = "add dataset tags" def __init__(self, tag, datasetnames): """Add tag to datasets listed.""" self.tag = tag self.datasetnames = datasetnames def do(self, document): """Add new tags, if required.""" self.removetags = [] for name in self.datasetnames: existing = document.data[name].tags if self.tag not in existing: existing.add(self.tag) self.removetags.append(name) def undo(self, document): """Remove tags, if not previously present.""" for name in self.removetags: document.data[name].tags.remove(self.tag) class OperationDataUntag(object): """Add a tag to a list of datasets.""" descr = "remove dataset tags" def __init__(self, tag, datasetnames): """Remove tag to datasets listed.""" self.tag = tag self.datasetnames = datasetnames def do(self, document): """Add new tags, if required.""" for name in self.datasetnames: document.data[name].tags.remove(self.tag) def undo(self, document): """Remove tags, if not previously present.""" for name in self.datasetnames: document.data[name].tags.add(self.tag) ############################################################################### # Alter dataset class OperationDatasetAddColumn(object): """Add a column to a dataset, blanked to zero.""" descr = 'add dataset column' def __init__(self, datasetname, columnname): """Initialise column columnname in datasetname. columnname can be one of 'data', 'serr', 'perr' or 'nerr' """ self.datasetname = datasetname self.columnname = columnname def do(self, document): """Zero the column.""" ds = document.data[self.datasetname] datacol = ds.data setattr(ds, self.columnname, N.zeros(datacol.shape, dtype='float64')) document.setData(self.datasetname, ds) def undo(self, document): """Remove the column.""" ds = document.data[self.datasetname] setattr(ds, self.columnname, None) document.setData(self.datasetname, ds) class OperationDatasetSetVal(object): """Set a value in the dataset.""" descr = 'change dataset value' def __init__(self, datasetname, columnname, row, val): """Set row in column columnname to val.""" self.datasetname = datasetname self.columnname = columnname self.row = row self.val = val def do(self, document): """Set the value.""" ds = document.data[self.datasetname] datacol = getattr(ds, self.columnname) self.oldval = datacol[self.row] datacol[self.row] = self.val ds.changeValues(self.columnname, datacol) def undo(self, document): """Restore the value.""" ds = document.data[self.datasetname] datacol = getattr(ds, self.columnname) datacol[self.row] = self.oldval ds.changeValues(self.columnname, datacol) class OperationDatasetSetVal2D(object): """Set a value in a 2D dataset.""" descr = 'change 2D dataset value' def __init__(self, datasetname, row, col, val): """Set row in column columnname to val.""" self.datasetname = datasetname self.row = row self.col = col self.val = val def do(self, document): """Set the value.""" ds = document.data[self.datasetname] self.oldval = ds.data[self.row, self.col] ds.data[self.row, self.col] = self.val document.modifiedData(ds) def undo(self, document): """Restore the value.""" ds = document.data[self.datasetname] ds.data[self.row, self.col] = self.oldval document.modifiedData(ds) class OperationDatasetDeleteRow(object): """Delete a row or several in the dataset.""" descr = 'delete dataset row' def __init__(self, datasetname, row, numrows=1): """Delete a row in a dataset.""" self.datasetname = datasetname self.row = row self.numrows = numrows def do(self, document): """Set the value.""" ds = document.data[self.datasetname] self.saveddata = ds.deleteRows(self.row, self.numrows) def undo(self, document): """Restore the value.""" ds = document.data[self.datasetname] ds.insertRows(self.row, self.numrows, self.saveddata) class OperationDatasetInsertRow(object): """Insert a row or several in the dataset.""" descr = 'insert dataset row' def __init__(self, datasetname, row, numrows=1): """Delete a row in a dataset.""" self.datasetname = datasetname self.row = row self.numrows = numrows def do(self, document): """Set the value.""" ds = document.data[self.datasetname] ds.insertRows(self.row, self.numrows, {}) def undo(self, document): """Restore the value.""" ds = document.data[self.datasetname] ds.deleteRows(self.row, self.numrows) ############################################################################### # Custom setting operations class OperationSetCustom(object): """Set custom objects, such as constants.""" descr = 'set a custom definition' def __init__(self, vals): """customtype is the type of custom object to set: eg functions, constants customval is a dict of the values.""" self.customvals = list(vals) def do(self, document): """Set the custom object.""" self.oldval = list(document.customs) document.customs = self.customvals document.updateEvalContext() def undo(self, document): """Restore custom object.""" document.customs = self.oldval document.updateEvalContext() ############################################################################### # Misc operations class OperationMultiple(object): """Multiple operations batched into one.""" def __init__(self, operations, descr='change'): """A batch operation made up of the operations in list. Optional argument descr gives a description of the combined operation """ self.operations = operations if descr: self.descr = descr def addOperation(self, op): """Add an operation to the list of operations.""" self.operations.append(op) def do(self, document): """Do the multiple operations.""" for op in self.operations: op.do(document) def undo(self, document): """Undo the multiple operations.""" # operations need to undone in reverse order for op in self.operations[::-1]: op.undo(document) class OperationLoadStyleSheet(OperationMultiple): """An operation to load a stylesheet.""" descr = 'load stylesheet' def __init__(self, filename): """Load stylesheet with filename.""" OperationMultiple.__init__(self, [], descr=None) self.filename = os.path.abspath(filename) def do(self, document): """Do the import.""" import commandinterpreter # get document to keep track of changes for undo/redo document.batchHistory(self) # fire up interpreter to read file interpreter = commandinterpreter.CommandInterpreter(document) e = None try: interpreter.runFile( open(self.filename) ) except: document.batchHistory(None) raise class OperationLoadCustom(OperationLoadStyleSheet): descr = 'load custom definitions' class OperationToolsPlugin(OperationMultiple): """An operation to represent what a tools plugin does.""" def __init__(self, plugin, fields): """Use tools plugin, passing fields.""" OperationMultiple.__init__(self, [], descr=None) self.plugin = plugin self.fields = fields self.descr = plugin.name def do(self, document): """Use the plugin.""" import commandinterface # get document to keep track of changes for undo/redo document.batchHistory(self) # fire up interpreter to read file ifc = commandinterface.CommandInterface(document) try: self.plugin.apply(ifc, self.fields) except: document.batchHistory(None) raise document.batchHistory(None) class OperationDatasetPlugin(object): """An operation to activate a dataset plugin.""" def __init__(self, plugin, fields, datasetnames={}): """Use dataset plugin, passing fields.""" self.plugin = plugin self.fields = fields self.descr = plugin.name self.names = datasetnames def do(self, document): """Use the plugin. """ self.datasetnames = [] self.olddata = {} manager = self.manager = plugins.DatasetPluginManager( self.plugin, document, self.fields) names = self.datasetnames = list(manager.datasetnames) # rename if requested for i in xrange(len(names)): if names[i] in self.names: names[i] = self.names[names[i]] # preserve old datasets for name in names: if name in document.data: self.olddata[name] = document.data[name] # add new datasets to document for name, ds in izip(names, manager.veuszdatasets): if name is not None: document.setData(name, ds) return names def validate(self): """Check that the plugin works the first time.""" self.manager.update(raiseerrors=True) def undo(self, document): """Undo dataset plugin.""" # delete datasets which were created for name in self.datasetnames: if name is not None: document.deleteData(name) # put back old datasets for name, ds in self.olddata.iteritems(): document.setData(name, ds) veusz-1.15/document/simpleread.py0000644002344000001440000005703711734662204017130 0ustar jssusers00000000000000# Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """SimpleRead: a class for the reading of data formatted in a simple way To read the data it takes a descriptor which takes the form of varname<[repeater]> where <> marks optional arguments, e.g. x+- y+,- z+-[1:5] x(text) y(date) z(number),+- +- means symmetric error bars +,- means asymmetric error bars , is a separator (text) or (date) specifies datatype z+-[1:5] means read z_1+- z_2+- ... z_5+- Indicices can be unspecified, [:], [1:], [:5] The first 3 mean the same thing, the last means read from 1 to 5 Commas are now optional in 1.6, so descriptors can look like x +- y + - """ import re import cStringIO import numpy as N import veusz.utils as utils import datasets # a regular expression for splitting descriptor into tokens descrtokens_split_re = re.compile(r''' ( `[^`]*` | # quoted name [ ,] | # comma or space \([a-z]+?\) | # data type \+- | \+ | - | # error bars \[.*?\] # indices ) ''', re.VERBOSE) range_re = re.compile(r'''^ \[ (-?[0-9]+)? : (-?[0-9]+)? \] $''', re.VERBOSE) def interpretDescriptor(descr): """Get a descriptor and create a set of descriptor objects.""" parts = [] split = descrtokens_split_re.split(descr.strip()) tokens = [x for x in split if x != ''] # make sure that last dataset is added tokens += ['DUMMY'] name = datatype = idxrange = None columns = [] for tokenindex, token in enumerate(tokens): # skip spaces if token == ' ': if tokenindex > 0 and tokens[tokenindex-1] == ',': columns.append(',') continue # ignore column if token == ',': if tokenindex == 0 or ( tokens[tokenindex-1] == ',' or tokens[tokenindex-1] == ' ' ): columns.append(',') continue # does token match datatype name? if ( token[0] == '(' and token[-1] == ')' and token[1:-1] in datatype_name_convert ): datatype = datatype_name_convert[token[1:-1]] continue # match error bars if token in ('+', '-', '+-'): columns.append(token) continue # does token match a range? m = range_re.match(token) if m: if m.group(1): startindex = int(m.group(1)) else: startindex = 1 if m.group(2): stopindex = int(m.group(2)) else: stopindex = 999999999 idxrange = (startindex, stopindex) continue # quoted dataset name, so remove quotes if token[0] == '`' and token[-1] == '`': token = token[1:-1] # add previous entry if name is not None: parts.append( DescriptorPart(name, datatype, columns, idxrange) ) name = datatype = idxrange = None columns = [] columns.append('D') name = token return parts class DescriptorError(ValueError): """Used to indicate an error with the descriptor.""" pass # this is a regular expression to match properly quoted strings # hopefully a matching expression can be passed to eval string_re = re.compile( r''' ^ u?"" | # match empty double-quoted string u?".*?[^\\]" | # match double-quoted string, ignoring escaped quotes u?'' | # match empty single-quoted string u?'.*?[^\\]' # match single-quoted string, ignoring escaped quotes $ ''', re.VERBOSE ) # a line starting with text text_start_re = re.compile( r'^[A-Za-z]' ) # convert data type strings in descriptor to internal datatype datatype_name_convert = { 'float': 'float', 'numeric': 'float', 'number': 'float', 'text': 'string', 'string': 'string', 'date': 'date', 'time': 'date' } def guessDataType(val): """Try to work out data type from sample value (val) Return values are one of float, string, or date """ # if the dataset type is specified # check for identifiers in dataset name # guess the type: # obvious float try: float(val) return 'float' except ValueError: pass # do all libcs check for these? if val.lower() in ('inf', '+inf', '-inf', 'nan'): return 'float' # obvious string if string_re.match(val): return 'string' # date if utils.isDateTime(val): return 'date' # assume string otherwise return 'string' class DescriptorPart(object): """Represents part of a descriptor.""" def __init__(self, name, datatype, columns, idxrange): """Construct DescriptorPart name is dataset name datatype is None or one of the possible options columns is a list of the columns '+', '-', '+-', ',' or 'D' for errors, ignoring a column or a data column idxrange is None or a tuple (minidx, maxidx) """ self.name = name self.datatype = datatype self.columns = tuple(columns) self.errorcount = 0 self.single = idxrange is None if self.single: self.startindex = self.stopindex = 1 else: self.startindex, self.stopindex = idxrange def readFromStream(self, stream, thedatasets, block=None): """Read data from stream, and write to thedatasets.""" # loop over column range for index in xrange(self.startindex, self.stopindex+1): # name for variable if self.single: name = self.name else: name = '%s_%i' % (self.name, index) # if we're reading multiple blocks if block is not None: name += '_%i' % block # loop over columns until we run out, or we don't need any for col in self.columns: # get next column and return if we run out of data val = stream.nextColumn() if val is None: return # append a suffix to specify whether error or value # \0 is used as the user cannot enter it fullname = '%s\0%s' % (name, col) # get dataset (or get new one) try: dataset = thedatasets[fullname] except KeyError: dataset = thedatasets[fullname] = [] if not self.datatype: # try to guess type of data self.datatype = guessDataType(val) # convert according to datatype if self.datatype == 'float': try: # do conversion dat = float(val) except ValueError: dat = N.nan self.errorcount += 1 elif self.datatype == 'string': if string_re.match(val): # possible security issue: # regular expression checks this is safe dat = eval(val) else: dat = val elif self.datatype == 'date': dat = utils.dateStringToDate(val) # add data into dataset dataset.append(dat) def setInDocument(self, thedatasets, document, block=None, linkedfile=None, prefix="", suffix="", tail=None): """Set the read-in data in the document.""" # we didn't read any data if self.datatype is None: return [] names = [] for index in xrange(self.startindex, self.stopindex+1): # name for variable if self.single: name = '%s' % (self.name,) else: name = '%s_%i' % (self.name, index) if block is not None: name += '_%i' % block # does the dataset exist? if name+'\0D' in thedatasets: vals = thedatasets[name+'\0D'] pos = neg = sym = None # retrieve the data for this dataset if name+'\0+' in thedatasets: pos = thedatasets[name+'\0+'] if name+'\0-' in thedatasets: neg = thedatasets[name+'\0-'] if name+'\0+-' in thedatasets: sym = thedatasets[name+'\0+-'] # make sure components are the same length minlength = 99999999999999 for ds in vals, pos, neg, sym: if ds is not None and len(ds) < minlength: minlength = len(ds) for ds in vals, pos, neg, sym: if ds is not None and len(ds) != minlength: del ds[minlength:] # only remember last N values if tail is not None: vals = vals[-tail:] if sym is not None: sym = sym[-tail:] if pos is not None: pos = pos[-tail:] if neg is not None: neg = neg[-tail:] # create the dataset if self.datatype == 'float': ds = datasets.Dataset( data = vals, serr = sym, nerr = neg, perr = pos, linked = linkedfile ) elif self.datatype == 'date': ds = datasets.DatasetDateTime( data=vals, linked=linkedfile ) elif self.datatype == 'string': ds = datasets.DatasetText( data=vals, linked = linkedfile ) else: raise RuntimeError, "Invalid data type" finalname = prefix + name + suffix document.setData( finalname, ds ) names.append(finalname) else: break return names class Stream(object): """This object reads through an input data source (override readLine) and interprets data from the source.""" # this is a regular expression for finding data items in data stream # I'll try to explain this bit-by-bit (these are ORd, and matched in order) find_re = re.compile( r''' `.+?`[^ \t\n\r#!%;]* | # match dataset name quoted in back-ticks # we also need to match following characters to catch # corner cases in the descriptor u?"" | # match empty double-quoted string u?".*?[^\\]" | # match double-quoted string, ignoring escaped quotes u?'' | # match empty single-quoted string u?'.*?[^\\]' | # match single-quoted string, ignoring escaped quotes [#!%;](?=descriptor) | # match separately comment char before descriptor [#!%;].* | # match comment to end of line [^ \t\n\r#!%;]+ # match normal space/tab separated items ''', re.VERBOSE ) def __init__(self): """Initialise stream object.""" self.remainingline = [] def nextColumn(self): """Return value of next column of line.""" try: return self.remainingline.pop(0) except IndexError: return None def allColumns(self): """Get all columns of current line (none are discarded).""" return self.remainingline def flushLine(self): """Forget the rest of the line.""" self.remainingline = [] def readLine(self): """Read the next line of the data source. StopIteration is raised if there is no more data.""" pass def newLine(self): """Read in, and split the next line.""" while True: # get next line from data source try: line = self.readLine() except StopIteration: # end of file return False # break up and append to buffer (removing comments) cmpts = self.find_re.findall(line) self.remainingline += [ x for x in cmpts if x[0] not in '#!%;'] if self.remainingline and self.remainingline[-1] == '\\': # this is a continuation: drop this item and read next line self.remainingline.pop() else: return True class FileStream(Stream): """A stream based on a python-style file (or iterable).""" def __init__(self, file): """File can be any iterator-like object.""" Stream.__init__(self) self.file = file def readLine(self): """Read the next line of the data source. StopIteration is raised if there is no more data.""" return self.file.next() class StringStream(FileStream): '''For reading data from a string.''' def __init__(self, text): """A stream which reads in from a text string.""" FileStream.__init__( self, cStringIO.StringIO(text) ) class SimpleRead(object): '''Class to read in datasets from a stream. The descriptor specifies the format of data to read from the stream Read the docstring for this module for information tail attribute if set says to only use last tail data points when setting ''' def __init__(self, descriptor): # convert descriptor to part objects descriptor = descriptor.strip() self._parseDescriptor(descriptor) # construct data names automatically self.autodescr = (descriptor == '') # get read for reading data self.clearState() def clearState(self): """Start reading from scratch.""" self.datasets = {} self.blocks = None self.tail = None def _parseDescriptor(self, descriptor): """Take a descriptor, and parse it into its individual parts.""" self.parts = interpretDescriptor(descriptor) def readData(self, stream, useblocks=False, ignoretext=False): """Read in the data from the stream. If useblocks is True, data are read as separate blocks. Dataset names are appending with an underscore and a block number if set. """ self.ignoretext = ignoretext if useblocks: self._readDataBlocked(stream, ignoretext) else: self._readDataUnblocked(stream, ignoretext) def _readDataUnblocked(self, stream, ignoretext): """Read in that data from the stream.""" allparts = list(self.parts) # loop over lines while stream.newLine(): if stream.remainingline[:1] == ['descriptor']: # a change descriptor statement descriptor = ' '.join(stream.remainingline[1:]) self._parseDescriptor(descriptor) allparts += self.parts self.autodescr = False elif ( self.ignoretext and len(stream.remainingline) > 0 and text_start_re.match(stream.remainingline[0]) and len(self.parts) > 0 and self.parts[0].datatype != 'string' and stream.remainingline[0] not in ('inf', 'nan') ): # ignore the line if it is text and ignore text is on # and first column is not text pass else: # normal text for p in self.parts: p.readFromStream(stream, self.datasets) # automatically create parts if data are remaining if self.autodescr: while len(stream.remainingline) > 0: p = DescriptorPart( str(len(self.parts)+1), None, 'D', None ) p.readFromStream(stream, self.datasets) self.parts.append(p) allparts.append(p) stream.flushLine() self.parts = allparts self.blocks = None def _readDataBlocked(self, stream, ignoretext): """Read in the data, using blocks.""" allparts = list(self.parts) blocks = {} block = 1 while stream.newLine(): line = stream.remainingline # if this is a blank line, separating data then advance to a new # block if len(line) == 0 or line[0].lower() == 'no': # blank lines separate blocks if block in blocks: block += 1 else: # read in data for p in self.parts: p.readFromStream(stream, self.datasets, block=block) # automatically create parts if data are remaining if self.autodescr: while len(stream.remainingline) > 0: p = DescriptorPart( str(len(self.parts)+1), None, 'D', None ) p.readFromStream(stream, self.datasets, block=block) self.parts.append(p) allparts.append(p) blocks[block] = True # lose remaining data stream.flushLine() self.parts = allparts self.blocks = blocks.keys() def getInvalidConversions(self): """Return the number of invalid conversions after reading data. Returns a dict of dataset, number values.""" out = {} for p in self.parts: out[p.name] = p.errorcount return out def getDatasetCounts(self): """Get a dict of the datasets read (main data part) and number of entries read.""" out = {} for name, data in self.datasets.iteritems(): if name[-2:] == '\0D': out[name[:-2]] = len(data) return out def setInDocument(self, document, linkedfile=None, prefix='', suffix=''): """Set the data in the document. Returns list of variable names read. """ # iterate over blocks used if self.blocks is None: blocks = [None] else: blocks = self.blocks # if automatically making parts, use a prefix/suffix if not set if self.autodescr and prefix == '' and suffix == '': prefix = 'col' names = [] for block in blocks: for part in self.parts: names += part.setInDocument( self.datasets, document, block=block, linkedfile=linkedfile, prefix=prefix, suffix=suffix, tail=self.tail) return names ##################################################################### # 2D data reading class Read2DError(ValueError): pass class SimpleRead2D(object): def __init__(self, name, params): """Read dataset with name given. params is a ImportParams2D object """ self.name = name self.params = params.copy() #################################################################### # Follow functions are for setting parameters during reading of data def _paramXRange(self, cols): try: self.params.xrange = ( float(cols[1]), float(cols[2]) ) except ValueError: raise Read2DError, "Could not interpret xrange" def _paramYRange(self, cols): try: self.params.yrange = ( float(cols[1]), float(cols[2]) ) except ValueError: raise Read2DError, "Could not interpret yrange" def _paramInvertRows(self, cols): self.params.invertrows = True def _paramInvertCols(self, cols): self.params.invertcols = True def _paramTranspose(self, cols): self.params.transpose = True #################################################################### def readData(self, stream): """Read data from stream given stream consists of: optional: xrange A B - set the range of x from A to B yrange A B - set the range of y from A to B invertrows - invert order of the rows invertcols - invert order of the columns transpose - swap rows and columns then: matrix of columns and rows, separated by line endings the rows are in reverse-y order (highest y first) blank line stops reading for further datasets """ settings = { 'xrange': self._paramXRange, 'yrange': self._paramYRange, 'invertrows': self._paramInvertRows, 'invertcols': self._paramInvertCols, 'transpose': self._paramTranspose } rows = [] # loop over lines while stream.newLine(): cols = stream.allColumns() if len(cols) > 0: # check to see whether parameter is set c = cols[0].lower() if c in settings: settings[c](cols) stream.flushLine() continue else: # if there's data and we get to a blank line, finish if len(rows) != 0: break # read columns line = [] while True: v = stream.nextColumn() if v is None: break try: line.append( float(v) ) except ValueError: raise Read2DError, "Could not interpret number '%s'" % v # add row to dataset if len(line) != 0: if self.params.invertcols: line.reverse() rows.insert(0, line) # swap rows if requested if self.params.invertrows: rows.reverse() # dodgy formatting probably... if len(rows) == 0: raise Read2DError, "No data could be imported for dataset" # convert the data to a numpy try: self.data = N.array(rows) except ValueError: raise Read2DError, "Could not convert data to 2D matrix" # obvious check if len(self.data.shape) != 2: raise Read2DError, "Dataset was not 2D" # transpose matrix if requested if self.params.transpose: self.data = N.transpose(self.data).copy() def setInDocument(self, document, linkedfile=None): """Set the data in the document. Returns list containing name of dataset read """ ds = datasets.Dataset2D(self.data, xrange=self.params.xrange, yrange=self.params.yrange) ds.linked = linkedfile fullname = self.params.prefix + self.name + self.params.suffix document.setData(fullname, ds) return [fullname] veusz-1.15/document/selftest_export.py0000644002344000001440000000440511734662204020224 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A paint engine for doing self-tests.""" import svg_export class SelfTestPaintEngine(svg_export.SVGPaintEngine): """Paint engine class for self testing output.""" def __init__(self, width_in, height_in): """Create the class, using width and height as size of canvas in inches.""" svg_export.SVGPaintEngine.__init__(self, width_in, height_in) # ppm images are simple and should be same on all platforms self.imageformat = 'ppm' def drawTextItem(self, pt, textitem): """Write text directly in self test mode.""" text = unicode(textitem.text()).encode('ascii', 'xmlcharrefreplace') svg_export.SVGElement(self.celement, 'text', 'x="%s" y="%s" font-size="%gpt" fill="%s"' % (svg_export.fltStr(pt.x()*svg_export.scale), svg_export.fltStr(pt.y()*svg_export.scale), textitem.font().pointSize(), self.pen.color().name()), text=text) class SelfTestPaintDevice(svg_export.SVGPaintDevice): """Paint device for SVG paint engine.""" def __init__(self, fileobj, width_in, height_in): svg_export.SVGPaintDevice.__init__(self, fileobj, width_in, height_in) self.engine = SelfTestPaintEngine(width_in, height_in) veusz-1.15/document/export.py0000644002344000001440000002430111734662204016310 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Routines to export the document.""" import os.path import random import math import veusz.qtall as qt4 import veusz.utils as utils try: import emf_export hasemf = True except ImportError: hasemf = False import svg_export import selftest_export import painthelper # 1m in inch m_inch = 39.370079 class Export(object): """Class to do the document exporting. This is split from document to make that class cleaner. """ formats = [ (["bmp"], "Windows bitmap"), (["eps"], "Encapsulated Postscript"), (["jpg", "jpeg"], "Jpeg bitmap"), (["pdf"], "Portable Document Format"), #(["pic"], "QT Pic format"), (["png"], "Portable Network Graphics"), (["svg"], "Scalable Vector Graphics"), (["tiff"], "Tagged Image File Format bitmap"), (["xpm"], "X Pixmap"), ] if hasemf: formats.append( (["emf"], "Windows Enhanced Metafile") ) formats.sort() def __init__(self, doc, filename, pagenumber, color=True, bitmapdpi=100, antialias=True, quality=85, backcolor='#ffffff00', pdfdpi=150, svgtextastext=False): """Initialise export class. Parameters are: doc: document to write filename: output filename pagenumber: pagenumber to export color: use color or try to use monochrome bitmapdpi: assume this dpi value when writing images antialias: antialias text and lines when writing bitmaps quality: compression factor for bitmaps backcolor: background color default for bitmaps (default transparent). pdfdpi: dpi for pdf and eps files svgtextastext: write text in SVG as text, rather than curves """ self.doc = doc self.filename = filename self.pagenumber = pagenumber self.color = color self.bitmapdpi = bitmapdpi self.antialias = antialias self.quality = quality self.backcolor = backcolor self.pdfdpi = pdfdpi self.svgtextastext = svgtextastext def export(self): """Export the figure to the filename.""" ext = os.path.splitext(self.filename)[1].lower() if ext in ('.eps', '.pdf'): self.exportPS(ext) elif ext in ('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.xpm'): self.exportBitmap(ext) elif ext == '.svg': self.exportSVG() elif ext == '.selftest': self.exportSelfTest() elif ext == '.pic': self.exportPIC() elif ext == '.emf' and hasemf: self.exportEMF() else: raise RuntimeError, "File type '%s' not supported" % ext def renderPage(self, size, dpi, painter): """Render page using paint helper to painter. This first renders to the helper, then to the painter """ helper = painthelper.PaintHelper(size, dpi=dpi, directpaint=painter) painter.setClipRect( qt4.QRectF( qt4.QPointF(0,0), qt4.QPointF(*size)) ) self.doc.paintTo(helper, self.pagenumber) painter.restore() painter.end() def exportBitmap(self, format): """Export to a bitmap format.""" # get size for bitmap's dpi dpi = self.bitmapdpi size = self.doc.pageSize(self.pagenumber, dpi=(dpi,dpi)) # create real output image backqcolor = utils.extendedColorToQColor(self.backcolor) if format == '.png': # transparent output image = qt4.QImage(size[0], size[1], qt4.QImage.Format_ARGB32_Premultiplied) else: # non transparent output image = qt4.QImage(size[0], size[1], qt4.QImage.Format_RGB32) backqcolor.setAlpha(255) image.setDotsPerMeterX(dpi*m_inch) image.setDotsPerMeterY(dpi*m_inch) if backqcolor.alpha() == 0: image.fill(qt4.qRgba(0,0,0,0)) else: image.fill(backqcolor.rgb()) # paint to the image painter = qt4.QPainter(image) painter.setRenderHint(qt4.QPainter.Antialiasing, self.antialias) painter.setRenderHint(qt4.QPainter.TextAntialiasing, self.antialias) self.renderPage(size, (dpi,dpi), painter) # write image to disk writer = qt4.QImageWriter() # format below takes extension without dot writer.setFormat(qt4.QByteArray(format[1:])) writer.setFileName(self.filename) if format == 'png': # min quality for png as it makes no difference to output # and makes file size smaller writer.setQuality(0) else: writer.setQuality(self.quality) writer.write(image) def exportPS(self, ext): """Export to EPS or PDF format.""" printer = qt4.QPrinter() printer.setFullPage(True) # set printer parameters printer.setColorMode( (qt4.QPrinter.GrayScale, qt4.QPrinter.Color)[ self.color] ) if ext == '.pdf': fmt = qt4.QPrinter.PdfFormat else: fmt = qt4.QPrinter.PostScriptFormat printer.setOutputFormat(fmt) printer.setOutputFileName(self.filename) printer.setCreator('Veusz %s' % utils.version()) printer.setResolution(self.pdfdpi) # setup for printing printer.newPage() painter = qt4.QPainter(printer) # write to printer with correct dpi dpi = (printer.logicalDpiX(), printer.logicalDpiY()) width, height = size = self.doc.pageSize(self.pagenumber, dpi=dpi) self.renderPage(size, dpi, painter) # fixup eps/pdf file - yuck HACK! - hope qt gets fixed # this makes the bounding box correct # copy output to a temporary file tmpfile = "%s.tmp.%i" % (self.filename, random.randint(0,1000000)) fout = open(tmpfile, 'wb') fin = open(self.filename, 'rb') if ext == '.eps': # adjust bounding box for line in fin: if line[:14] == '%%BoundingBox:': # replace bounding box line by calculated one parts = line.split() widthfactor = float(parts[3]) / printer.width() origheight = float(parts[4]) line = "%s %i %i %i %i\n" % ( parts[0], 0, int(math.floor(origheight-widthfactor*height)), int(math.ceil(widthfactor*width)), int(math.ceil(origheight)) ) fout.write(line) elif ext == '.pdf': # change pdf bounding box and correct pdf index text = fin.read() text = utils.scalePDFMediaBox(text, printer.width(), width, height) text = utils.fixupPDFIndices(text) fout.write(text) fout.close() fin.close() os.remove(self.filename) os.rename(tmpfile, self.filename) def exportSVG(self): """Export document as SVG""" if qt4.PYQT_VERSION >= 0x40600: # custom paint devices don't work in old PyQt versions dpi = svg_export.dpi * 1. size = self.doc.pageSize( self.pagenumber, dpi=(dpi,dpi), integer=False) f = open(self.filename, 'w') paintdev = svg_export.SVGPaintDevice( f, size[0]/dpi, size[1]/dpi, writetextastext=self.svgtextastext) painter = qt4.QPainter(paintdev) self.renderPage(size, (dpi,dpi), painter) f.close() else: # use built-in svg generation, which doesn't work very well # (no clipping, font size problems) import PyQt4.QtSvg dpi = 90. size = self.doc.pageSize( self.pagenumber, dpi=(dpi,dpi), integer=False) # actually paint the image gen = PyQt4.QtSvg.QSvgGenerator() gen.setFileName(self.filename) gen.setResolution(dpi) gen.setSize( qt4.QSize(int(size[0]), int(size[1])) ) painter = qt4.QPainter(gen) self.renderPage(size, (dpi,dpi), painter) def exportSelfTest(self): """Export document for testing""" dpi = svg_export.dpi * 1. size = width, height = self.doc.pageSize( self.pagenumber, dpi=(dpi,dpi), integer=False) f = open(self.filename, 'w') paintdev = selftest_export.SelfTestPaintDevice(f, width/dpi, height/dpi) painter = qt4.QPainter(paintdev) self.renderPage(size, (dpi,dpi), painter) f.close() def exportPIC(self): """Export document as Qt PIC""" pic = qt4.QPicture() painter = qt4.QPainter(pic) dpi = (pic.logicalDpiX(), pic.logicalDpiY()) size = self.doc.pageSize(self.pagenumber, dpi=dpi) self.renderPage(size, dpi, painter) pic.save(self.filename) def exportEMF(self): """Export document as EMF.""" dpi = 90. size = self.doc.pageSize(self.pagenumber, dpi=(dpi,dpi), integer=False) paintdev = emf_export.EMFPaintDevice(size[0]/dpi, size[1]/dpi, dpi=dpi) painter = qt4.QPainter(paintdev) self.renderPage(size, (dpi,dpi), painter) paintdev.paintEngine().saveFile(self.filename) veusz-1.15/document/capture.py0000644002344000001440000001700711734662204016437 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import select import subprocess import os import socket import platform import signal import veusz.qtall as qt4 import veusz.utils as utils import simpleread class CaptureFinishException(Exception): """An exception to say when a stream has been finished.""" class CaptureStream(simpleread.Stream): """A special stream for capturing data.""" def __init__(self): """Initialise the stream.""" simpleread.Stream.__init__(self) self.buffer = '' self.continuousreads = 0 self.bytesread = 0 self.linesread = 0 self.maxlines = None self.timedout = False def _setTimeout(self, timeout): """Setter for setting timeout property.""" if timeout: self.timer = qt4.QTimer.singleShot(timeout*1000, self._timedOut) timeout = property(None, _setTimeout, None, "Time interval to stop in (seconds) or None") def _timedOut(self): self.timedout = True def getMoreData(self): """Override this to return more data from the source without blocking.""" return '' def readLine(self): """Return a new line of data. Either returns new line or Raises StopIteration if there is no data, or more than 100 lines have been read.""" while True: # we've reached the limit of lines or a timeout has occurred if self.linesread == self.maxlines: raise CaptureFinishException("Maximum number of lines read") if self.timedout: raise CaptureFinishException("Maximum time period occurred") # stop reading continous data greater than this many lines if self.continuousreads == 100: self.continuousreads = 0 raise StopIteration index = self.buffer.find('\n') if index >= 0: # is there a line in the buffer? retn = self.buffer[:index] self.buffer = self.buffer[index+1:] self.linesread += 1 self.continuousreads += 1 return retn else: # if not, then read some more data data = self.getMoreData() if not data: self.continuousreads = 0 raise StopIteration self.bytesread += len(data) self.buffer += data def close(self): """Close any allocated object.""" pass class FileCaptureStream(CaptureStream): """Capture from a file or named pipe.""" def __init__(self, filename): CaptureStream.__init__(self) # open file self.fileobj = open(filename, 'rU') # make new thread to read file self.readerthread = utils.NonBlockingReaderThread(self.fileobj) self.readerthread.start() self.name = filename def getMoreData(self): """Read data from the file.""" try: data, done = self.readerthread.getNewData() if len(data) == 0 and done: raise CaptureFinishException("End of file") return data except OSError, e: raise CaptureFinishException("OSError: %s" % unicode(e)) def close(self): """Close file.""" self.fileobj.close() class CommandCaptureStream(CaptureStream): """Capture from an external program.""" def __init__(self, commandline): """Capture from commandline - this is passed to the shell.""" CaptureStream.__init__(self) self.name = commandline self.popen = subprocess.Popen(commandline, shell=True, bufsize=0, stdout=subprocess.PIPE, universal_newlines=True) # make new thread to read stdout self.readerthread = utils.NonBlockingReaderThread(self.popen.stdout) self.readerthread.start() def getMoreData(self): """Read data from the command.""" retn, done = self.readerthread.getNewData() if not retn: poll = self.popen.poll() if poll is not None: # process has ended raise CaptureFinishException("Process ended (status code %i)" % poll) return retn def close(self): """Close file.""" if self.popen.poll() is None: # need to kill process if it is still running if platform.system() == 'Windows': # awful code (for windows) # use this to not have a ctypes dependency os.system('TASKKILL /PID %i /F' % self.popen.pid) else: # unix os.kill(self.popen.pid, signal.SIGTERM) try: self.popen.stdout.close() except EnvironmentError: # problems closing stdout for some reason pass class SocketCaptureStream(CaptureStream): """Capture from an internet host.""" def __init__(self, host, port): """Connect to host and port specified.""" CaptureStream.__init__(self) self.name = '%s:%i' % (host, port) try: self.socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) self.socket.connect( (host, port) ) except socket.error, e: self._handleSocketError(e) def _handleSocketError(self, e): """Special function to reraise exceptions because socket exceptions have changed in python 2.6 and behave differently on some platforms. """ # clean up self.socket.close() if isinstance(e, EnvironmentError): # python 2.6 raise e ee = EnvironmentError() if isinstance(e, basestring): # windows? ee.strerror = unicode(e) ee.errno = -1 else: # unix ee.strerror = e[1] ee.errno = e[0] raise ee def getMoreData(self): """Read data from the socket.""" # see whether there is data to be read i, o, e = select.select([self.socket], [], [], 0) if i: try: retn = self.socket.recv(1024) except socket.error, e: self._handleSocketError(e) if len(retn) == 0: raise CaptureFinishException("Remote socket closed") return retn else: return '' def close(self): """Close the socket.""" self.socket.close() veusz-1.15/document/linked.py0000644002344000001440000001764511734662204016252 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Classes for linked files""" import sys from itertools import izip import veusz.utils as utils import operations import simpleread class LinkedFileBase(object): """A base class for linked files containing common routines.""" # operation to recreate dataset create_operation = None def __init__(self, params): """Save parameters.""" self.params = params @property def filename(self): """Get filename.""" return self.params.filename def saveToFile(self, fileobj, relpath=None): """Save the link to the document file.""" pass def _getSaveFilename(self, relpath): """Get filename to write to save file. If relpath is a string, write relative to path given """ if relpath: f = utils.relpath(self.params.filename, relpath) else: f = self.filename # Here we convert backslashes in Windows to forward slashes # This is compatible, but also works on Unix/Mac if sys.platform == 'win32': f = f.replace('\\', '/') return f def reloadLinks(self, document): """Reload datasets linked to this file.""" pass def _deleteLinkedDatasets(self, document): """Delete linked datasets from document linking to self.""" for name, ds in document.data.items(): if ds.linked == self: document.deleteData(name) def _moveReadDatasets(self, tempdoc, document): """Move datasets from tempdoc to document if they do not exist in the destination.""" read = [] for name, ds in tempdoc.data.items(): if name not in document.data: read.append(name) document.setData(name, ds) ds.document = document ds.linked = self return read def reloadLinks(self, document): """Reload links using an operation""" # get the operation for reloading op = self.create_operation(self.params) # load data into a temporary document tempdoc = document.__class__() try: tempdoc.applyOperation(op) except Exception, ex: # if something breaks, record an error and return nothing document.log(unicode(ex)) # find datasets which are linked using this link object # return errors for them errors = dict([(name, 1) for name, ds in document.data.iteritems() if ds.linked is self]) return ([], errors) # delete datasets which are linked and imported here self._deleteLinkedDatasets(document) # move datasets into document read = self._moveReadDatasets(tempdoc, document) # return errors (if any) errors = op.outinvalids return (read, errors) class LinkedFile(LinkedFileBase): """Instead of reading data from a string, data can be read from a "linked file". This means the same document can be reloaded, and the data would be reread from the file. This class is used to store a link filename with the descriptor """ create_operation = operations.OperationDataImport def saveToFile(self, fileobj, relpath=None): """Save the link to the document file. If relpath is set, save links relative to path given """ p = self.params params = [ repr(self._getSaveFilename(relpath)), repr(p.descriptor), "linked=True", "ignoretext=" + repr(p.ignoretext) ] if p.encoding != "utf_8": params.append("encoding=" + repr(p.encoding)) if p.useblocks: params.append("useblocks=True") if p.prefix: params.append("prefix=" + repr(p.prefix)) if p.suffix: params.append("suffix=" + repr(p.suffix)) fileobj.write("ImportFile(%s)\n" % (", ".join(params))) class LinkedFile2D(LinkedFileBase): """Class representing a file linked to a 2d dataset.""" create_operation = operations.OperationDataImport2D def saveToFile(self, fileobj, relpath=None): """Save the link to the document file.""" args = [ repr(self._getSaveFilename(relpath)), repr(self.params.datasetnames) ] for par in ("xrange", "yrange", "invertrows", "invertcols", "transpose", "prefix", "suffix", "encoding"): v = getattr(self.params, par) if v is not None and v != "" and v != self.params.defaults[par]: args.append( "%s=%s" % (par, repr(v)) ) args.append("linked=True") fileobj.write("ImportFile2D(%s)\n" % ", ".join(args)) class LinkedFileFITS(LinkedFileBase): """Links a FITS file to the data.""" create_operation = operations.OperationDataImportFITS def saveToFile(self, fileobj, relpath=None): """Save the link to the document file.""" p = self.params args = [p.dsname, self._getSaveFilename(relpath), p.hdu] args = [repr(i) for i in args] for param, column in ( ("datacol", p.datacol), ("symerrcol", p.symerrcol), ("poserrcol", p.poserrcol), ("negerrcol", p.negerrcol) ): if column is not None: args.append("%s=%s" % (param, repr(column))) args.append("linked=True") fileobj.write("ImportFITSFile(%s)\n" % ", ".join(args)) class LinkedFileCSV(LinkedFileBase): """A CSV file linked to datasets.""" create_operation = operations.OperationDataImportCSV def saveToFile(self, fileobj, relpath=None): """Save the link to the document file.""" paramsout = [ repr(self._getSaveFilename(relpath)) ] # add parameters which aren"t defaults for param, default in sorted(self.params.defaults.items()): v = getattr(self.params, param) if param == 'prefix' or param == 'suffix': param = 'ds' + param if param != 'filename' and param != 'tags' and v != default: paramsout.append("%s=%s" % (param, repr(v))) fileobj.write("ImportFileCSV(%s)\n" % (", ".join(paramsout))) class LinkedFilePlugin(LinkedFileBase): """Represent a file linked using an import plugin.""" create_operation = operations.OperationDataImportPlugin def saveToFile(self, fileobj, relpath=None): """Save the link to the vsz document file.""" p = self.params params = [repr(p.plugin), repr(self._getSaveFilename(relpath)), "linked=True"] if p.encoding != "utf_8": params.append("encoding=" + repr(p.encoding)) if p.prefix: params.append("prefix=" + repr(p.prefix)) if p.suffix: params.append("suffix=" + repr(p.suffix)) for name, val in p.pluginpars.iteritems(): params.append("%s=%s" % (name, repr(val))) fileobj.write("ImportFilePlugin(%s)\n" % (", ".join(params))) veusz-1.15/document/mime.py0000644002344000001440000002126411734662204015723 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### from itertools import izip, count import veusz.qtall as qt4 import doc import operations import widgetfactory import StringIO # mime type for copy and paste widgetmime = 'text/x-vnd.veusz-widget-3' # dataset mime datamime = 'text/x-vnd.veusz-data-1' def generateWidgetsMime(widgets): """Create mime data describing widget and children. format is: numberofwidgets widgettype1 widgetname1 widgetpath1 numberoflines1 ... texttoreproducewidget """ header = [str(len(widgets))] savetext = [] for widget in widgets: header.append(widget.typename) header.append(repr(widget.name)) header.append(repr(widget.path)) save = widget.getSaveText() header.append( str(save.count('\n')) ) savetext.append(save) header.append('') text = '\n'.join(header) + ''.join(savetext) mimedata = qt4.QMimeData() mimedata.setData(widgetmime, qt4.QByteArray(text)) return mimedata def generateDatasetsMime(datasets, document): """Generate mime for the list of dataset names given in the document. Format is: repr of names text to recreate dataset 1 ... """ mimedata = qt4.QMimeData() # just plain text format output = [] for name in datasets: output.append( document.data[name].datasetAsText() ) text = '\n'.join(output) mimedata.setData('text/plain', qt4.QByteArray(text)) textfile = StringIO.StringIO() for name in datasets: # get unlinked copy of dataset ds = document.data[name].returnCopy() # write into a string file ds.saveToFile(textfile, name) mimedata.setData(datamime, textfile.getvalue()) return mimedata def isDataMime(): """Returns whether data available on clipboard.""" mimedata = qt4.QApplication.clipboard().mimeData() return datamime in mimedata.formats() def getClipboardWidgetMime(): """Returns widget mime data if clipboard contains mime data or None.""" mimedata = qt4.QApplication.clipboard().mimeData() if widgetmime in mimedata.formats(): return str(mimedata.data(widgetmime)) else: return None def getMimeWidgetTypes(data): """Get list of widget types in the mime data.""" lines = data.split('\n') try: numwidgets = int(lines[0]) except ValueError: return [] types = lines[1:1+4*numwidgets:4] return types def getMimeWidgetPaths(data): """Get list of widget paths in the mime data.""" lines = data.split('\n') numwidgets = int(lines[0]) paths = [eval(x) for x in lines[3:3+4*numwidgets:4]] return paths def isWidgetMimePastable(parentwidget, mimedata): """Is widget mime data suitable to paste at parentwidget?""" if mimedata is None: return False types = getMimeWidgetTypes(mimedata) for type in types: if doc.getSuitableParent(type, parentwidget) is None: return False return True def isMimeDropable(parentwidget, mimedata): """Can parent have this data pasted directly inside?""" if mimedata is None or parentwidget is None: return False types = getMimeWidgetTypes(mimedata) for type in types: wc = widgetfactory.thefactory.getWidgetClass(type) if not wc.willAllowParent(parentwidget): return False return True def getMimeWidgetCount(mimedata): """Get number of widgets in mimedata.""" return int( mimedata[:mimedata.find('\n')] ) class OperationWidgetPaste(operations.OperationMultiple): """Paste a widget from mime data.""" descr= 'paste widget' def __init__(self, parent, mimedata, index=-1, newnames=None): """Paste widget into parent widget from mimedata. newnames is a list of new names for pasting, if given.""" operations.OperationMultiple.__init__(self, [], descr=None) self.parentpath = parent.path self.mimedata = mimedata self.index = index self.newnames = newnames def do(self, document): """Do the import.""" import commandinterpreter index = self.index # get document to keep track of changes for undo/redo document.batchHistory(self) # fire up interpreter to read file interpreter = commandinterpreter.CommandInterpreter(document) parentwidget = document.resolveFullWidgetPath(self.parentpath) lines = self.mimedata.split('\n') numwidgets = int(lines[0]) # get types, names and number of lines for widgets types = lines[1:1+4*numwidgets:4] names = lines[2:2+4*numwidgets:4] names = [eval(name) for name in names] if self.newnames is not None: names = self.newnames paths = lines[3:3+4*numwidgets:4] # (not required here) widgetslines = lines[4:4+4*numwidgets:4] widgetslines = [int(x) for x in widgetslines] newwidgets = [] widgetline = 1+4*numwidgets try: for wtype, name, numline in izip(types, names, widgetslines): thisparent = doc.getSuitableParent(wtype, parentwidget) if thisparent is None: raise RuntimeError, "Cannot find suitable parent for pasting" # override name if it exists already if name in thisparent.childnames: name = None # make new widget widget = document.applyOperation( operations.OperationWidgetAdd( thisparent, wtype, autoadd=False, name=name, index=index) ) newwidgets.append(widget) # run generating commands interpreter.interface.currentwidget = widget for line in lines[widgetline:widgetline+numline]: interpreter.run(line) if index >= 0: index += 1 # move to next widget widgetline += numline except Exception: document.batchHistory(None) raise # stop batching changes document.batchHistory(None) return newwidgets class OperationWidgetClone(OperationWidgetPaste): """Clone a widget.""" descr = 'clone widget' def __init__(self, widget, newparent, newname): mime = generateWidgetsMime([widget]) mime = str(mime.data(widgetmime)) OperationWidgetPaste.__init__(self, newparent, mime, newnames=[newname]) def do(self, document): """Do the import.""" widgets = OperationWidgetPaste.do(self, document) return widgets[0] class OperationDataPaste(object): """Paste dataset from mime data.""" descr = 'paste data' def __init__(self, mimedata): """Paste datasets into document.""" self.data = str(mimedata.data(datamime)) def do(self, thisdoc): """Do the data paste.""" import commandinterpreter # write data into a temporary document tempdoc = doc.Document() # interpreter to create datasets interpreter = commandinterpreter.CommandInterpreter(tempdoc) interpreter.runFile(self.data) # list of pasted datasets self.newds = [] # now transfer datasets to existing document for name, ds in sorted(tempdoc.data.iteritems()): # get new name if name not in thisdoc.data: newname = name else: for idx in count(2): newname = '%s_%s' % (name, idx) if newname not in thisdoc.data: break thisdoc.setData(newname, ds) self.newds.append(newname) def undo(self, thisdoc): """Undo pasting datasets.""" for n in self.newds: thisdoc.deleteData(n) veusz-1.15/document/commandinterpreter.py0000644002344000001440000002117611734662204020700 0ustar jssusers00000000000000# commandinterpreter.py # this module handles the command line interface interpreter # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ A module for the execution of user 'macro' code inside a special environment. That way the commandline can be used to interact with the app without worrying about the app internals. The way this works is to create an evironment specific to the class consisting of globals & locals. Commands can be run inside the environment. Python errors are trapped and dumped out to stderr. stderr and stdout can be reassigned in the environment to point to an alternative interface. They should point to objects providing a write() interface. This class is modelled on the one described in 'GUI Programming in Python: QT Edition' (Boudewijn Rempt) """ # get globals before things are imported _globals = globals() import sys import traceback import pickle import os.path from commandinterface import CommandInterface import veusz.utils as utils class CommandInterpreter(object): """Class for executing commands in the Veusz command line language.""" def __init__(self, document): """ Initialise object with the document it interfaces.""" self.document = document # set up interface to document self.interface = CommandInterface(document) # initialise environment (make a copy from inital globals) self.globals = _globals.copy() # save the stdout & stderr self.write_stdout = sys.stdout self.write_stderr = sys.stderr # import numpy into the environment exec "from numpy import *" in self.globals # define root object self.globals['Root'] = self.interface.Root # shortcut ifc = self.interface # define commands for interface self.cmds = {} for cmd in CommandInterface.safe_commands: self.cmds[cmd] = getattr(ifc, cmd) for cmd in CommandInterface.unsafe_commands: self.cmds[cmd] = getattr(ifc, cmd) self.cmds['GPL'] = self.GPL self.cmds['Load'] = self.Load self.globals.update( self.cmds ) def addCommand(self, name, command): """Add the given command to the list of available commands.""" self.cmds[name] = command self.globals[name] = command def setOutputs(self, stdout, stderr): """ Assign the environment output files.""" self.write_stdout = stdout self.write_stderr = stderr def _pythonise(self, text): """Internal routine to convert commands in the form Cmd a b c into Cmd(a,b,c).""" out = '' # iterate over lines for l in text.split('\n'): parts = l.split() # turn Cmd a b c into Cmd(a,b,c) if len(parts) != 0 and parts[0] in self.cmds.keys(): l = utils.pythonise(l) out += l + '\n' return out def run(self, inputcmds, filename = None): """ Run a set of commands inside the preserved environment. inputcmds: a string with the commands to run filename: a filename to report if there are errors """ if filename is None: filename = '' # pythonise! inputcmds = self._pythonise(inputcmds) # ignore if blank if len(inputcmds.strip()) == 0: return # preserve output streams temp_stdout = sys.stdout temp_stderr = sys.stderr sys.stdout = self.write_stdout sys.stderr = self.write_stderr # count number of newlines in expression # If it's 2, then execute as a single statement (print out result) if inputcmds.count('\n') == 2: stattype = 'single' else: stattype = 'exec' # first compile the code to check for syntax errors try: c = compile(inputcmds, filename, stattype) except (OverflowError, ValueError, SyntaxError): i = sys.exc_info() backtrace = traceback.format_exception( *i ) for l in backtrace: sys.stderr.write(l) else: # block update signals from document while updating self.document.suspendUpdates() try: # execute the code exec c in self.globals except: # print out the backtrace to stderr i = sys.exc_info() backtrace = traceback.format_exception( *i ) for l in backtrace: sys.stderr.write(l) # reenable documents self.document.enableUpdates() # return output streams sys.stdout = temp_stdout sys.stderr = temp_stderr def Load(self, filename): """Replace the document with a new one from the filename.""" f = open(filename, 'rU') self.document.wipe() self.interface.To('/') oldfile = self.globals['__file__'] self.globals['__file__'] = os.path.abspath(filename) self.interface.importpath.append( os.path.dirname(os.path.abspath(filename))) self.runFile(f) self.interface.importpath.pop() self.globals['__file__'] = oldfile self.document.setModified() self.document.setModified(False) self.document.clearHistory() def runFile(self, fileobject): """ Run a file in the preserved environment.""" # preserve output streams temp_stdout = sys.stdout temp_stderr = sys.stderr sys.stdout = self.write_stdout sys.stderr = self.write_stderr self.document.suspendUpdates() # actually run the code try: exec fileobject in self.globals except Exception: # print out the backtrace to stderr i = sys.exc_info() backtrace = traceback.format_exception( *i ) for l in backtrace: sys.stderr.write(l) self.document.enableUpdates() # return output streams sys.stdout = temp_stdout sys.stderr = temp_stderr # FIXME: need a version of this that can throw exceptions instead def evaluate(self, expression): """ Evaluate an expression in the environment.""" # preserve output streams temp_stdout = sys.stdout temp_stderr = sys.stderr sys.stdout = self.write_stdout sys.stderr = self.write_stderr # actually run the code try: retn = eval(expression, self.globals) except Exception: # print out the backtrace to stderr i = sys.exc_info() backtrace = traceback.format_exception( *i ) for l in backtrace: sys.stderr.write(l) retn = None # return output streams sys.stdout = temp_stdout sys.stderr = temp_stderr return retn def GPL(self): """Write the GPL to the console window.""" sys.stdout.write( utils.getLicense() ) def runPickle(self, command): """Run a pickled command given as arguments. command should consist of following: dumps( (name, args, namedargs) ) name is name of function to execute in environment args are the arguments (list) namedargs are the named arguments (dict). """ name, args, namedargs = pickle.loads(command) self.globals['_tmp_args0'] = args self.globals['_tmp_args1'] = namedargs print name, args, namedargs try: retn = eval('%s(*_tmp_args0, **_tmp_args1)' % name) except Exception, e: # return exception picked if exception retn = e del self.globals['_tmp_args0'] del self.globals['_tmp_args1'] return pickle.dumps(retn) veusz-1.15/document/widgetfactory.py0000644002344000001440000000506511734662204017650 0ustar jssusers00000000000000# widget factory # tools to generate any type of plot widget you want # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## class WidgetFactory(object): """Class to help produce any type of widget you want by name.""" def __init__(self): """Initialise the class.""" self.regwidgets = {} def register(self, classobj): """Register a class with the factory.""" self.regwidgets[classobj.typename] = classobj def makeWidget(self, widgetname, parent, name=None, autoadd=True, index=-1, **optargs): """Make a new widget of the appropriate type.""" # check for / in name of widget if name is not None and name.find('/') != -1: raise ValueError, 'name cannot contain "/"' w = self.regwidgets[widgetname](parent, name=name) # set all the passed default settings for name, val in optargs.iteritems(): # allow subsettings to be set using __ -> syntax name = name.replace('__', '/') w.prefLookup(name).set(val) if autoadd: w.addDefaultSubWidgets() # move around child afterwards, yuck if index != -1: del parent.children[-1] parent.children.insert(index, w) return w def getWidgetClass(self, name): """Get the class for the widget.""" return self.regwidgets[name] def listWidgets(self): """Return an array of the widgets the factory can make.""" names = self.regwidgets.keys() names.sort() return names def listWidgetClasses(self): """Return list of allowed classes.""" return self.regwidgets.values() # singleton thefactory = WidgetFactory() veusz-1.15/document/readcsv.py0000644002344000001440000003053011734662204016417 0ustar jssusers00000000000000# Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """This module contains routines for importing CSV data files in an easy-to-use manner.""" import re import numpy as N import datasets import veusz.utils as utils import veusz.qtall as qt4 class _FileReaderCols(object): """Read a CSV file in rows. This acts as an iterator. This is a very simple wrapper around the csv module """ def __init__(self, csvreader): self.csvreader = csvreader self.maxlen = 0 def next(self): """Return next row.""" row = self.csvreader.next() # add blank columns up to maximum previously read self.maxlen = max(self.maxlen, len(row)) row = row + ['']*(self.maxlen - len(row)) return row class _FileReaderRows(object): """Read a CSV file in columns. This acts as an iterator. This means we have to read the whole file in, then return cols :-( """ def __init__(self, csvreader): self.data = [] self.maxlength = 0 for line in csvreader: self.maxlength = max(self.maxlength, len(line)) self.data.append(line) self.counter = 0 def next(self): """Return the next column.""" if self.counter == self.maxlength: raise StopIteration # probably is a better way to do this retn = [] for row in self.data: if self.counter >= len(row): retn.append('') else: retn.append(row[self.counter]) self.counter += 1 return retn # list of codes which can be added to column descriptors typecodes = ( ('(string)', 'string'), ('(text)', 'string'), ('(date)', 'date'), ('(time)', 'date'), ('(float)', 'float'), ('(numeric)', 'float'), ('(number)', 'float'), ) class _NextValue(Exception): """A class to be raised to move to next value.""" class ReadCSV(object): """A class to import data from CSV files.""" def __init__(self, params): """Initialise the reader. params is a ParamsCSV object """ self.params = params self.numericlocale = qt4.QLocale(params.numericlocale) self.datere = re.compile( utils.dateStrToRegularExpression(params.dateformat)) # created datasets. Each name is associated with a list self.data = {} def _generateName(self, column): """Generate a name for a column.""" if self.params.readrows: prefix = 'row' else: prefix = 'col' name = '%s%s%i%s' % (self.params.prefix, prefix, column+1, self.params.suffix) return name def _getNameAndColType(self, colnum, colval): """Get column name and type.""" name = colval.strip() if name in ('+', '-', '+-'): # loop to find previous valid column prevcol = colnum - 1 while prevcol >= 0: n = self.colnames[prevcol] if len(n) > 0 and n[-1] not in "+-": # we add a \0 here so that there's no chance of the user # using this as a column name name = n + '\0' + name return self.coltypes[prevcol], name prevcol -= 1 else: # did not find anything name = self._generateName(colnum) # examine whether object type is at end of name # convert, and remove, if is type = 'unknown' for codename, codetype in typecodes: if name[-len(codename):] == codename: type = codetype name = name[:-len(codename)].strip() break return type, self.params.prefix + name + self.params.suffix def _setNameAndType(self, colnum, colname, coltype): """Set a name for column number given column name and type.""" while colnum >= len(self.coltypes): self.coltypes.append('') self.coltypes[colnum] = coltype self.nametypes[colname] = coltype self.colnames[colnum] = colname self.colignore[colnum] = self.params.headerignore self.colblanks[colnum] = 0 if colname not in self.data: self.data[colname] = [] def _guessType(self, val): """Guess type for new dataset.""" v, ok = self.numericlocale.toDouble(val) if ok: return 'float' m = self.datere.match(val) try: v = utils.dateREMatchToDate(m) return 'date' except ValueError: return 'string' def _newValueInBlankColumn(self, colnum, col): """Handle occurance of new data value in previously blank column. """ if self.params.headermode == '1st': # just use name of column as title in 1st header mode coltype, name = self._getNameAndColType(colnum, col) self._setNameAndType(colnum, name.strip(), coltype) raise _NextValue() elif self.params.headermode == 'none': # no header, so just start a new data set dtype = self._guessType(col) self._setNameAndType(colnum, self._generateName(colnum), dtype) else: # see whether it looks like data, not a header dtype = self._guessType(col) if dtype == 'string': # use text as dataset name coltype, name = self._getNameAndColType(colnum, col) self._setNameAndType(colnum, name.strip(), coltype) raise _NextValue() else: # use guessed data type and generated name self._setNameAndType(colnum, self._generateName(colnum), dtype) def _newUnknownDataValue(self, colnum, col): """Process data value if data type is unknown. """ # blank value if col.strip() == '': if self.params.blanksaredata: # keep track of blanks above autodetected data self.colblanks[colnum] += 1 # skip back to next value raise _NextValue() # guess type from data value t = self._guessType(col) self.nametypes[self.colnames[colnum]] = t self.coltypes[colnum] = t # add back on blanks if necessary with correct format for i in xrange(self.colblanks[colnum]): d = (N.nan, '')[t == 'string'] self.data[self.colnames[colnum]].append(d) self.colblanks[colnum] = 0 def _handleFailedConversion(self, colnum, col): """If conversion from text to data type fails.""" if col.strip() == '': # skip blanks unless blanksaredata is set if self.params.blanksaredata: # assumes a numeric data type self.data[self.colnames[colnum]].append(N.nan) else: if self.params.headermode == '1st': # no more headers, so fill with invalid number self.data[self.colnames[colnum]].append(N.nan) else: # start a new dataset if conversion failed coltype, name = self._getNameAndColType(colnum, col) self._setNameAndType(colnum, name.strip(), coltype) def _handleVal(self, colnum, col): """Handle a value from the file. colnum: number of column col: data value """ if colnum not in self.colnames: # ignore blanks if col.strip() == '': return # process value self._newValueInBlankColumn(colnum, col) # ignore lines after headers if self.colignore[colnum] > 0: self.colignore[colnum] -= 1 return # process value if data type unknown if self.coltypes[colnum] == 'unknown': self._newUnknownDataValue(colnum, col) ctype = self.coltypes[colnum] try: # convert text to data type of column if ctype == 'float': v, ok = self.numericlocale.toDouble(col) if not ok: raise ValueError elif ctype == 'date': m = self.datere.match(col) v = utils.dateREMatchToDate(m) elif ctype == 'string': v = col else: raise RuntimeError, "Invalid type in CSV reader" except ValueError: self._handleFailedConversion(colnum, col) else: # conversion succeeded - append number to data self.data[self.colnames[colnum]].append(v) def readData(self): """Read the data into the document.""" par = self.params # open the csv file csvf = utils.UnicodeCSVReader( par.filename, delimiter=par.delimiter, quotechar=par.textdelimiter, encoding=par.encoding ) # make in iterator for the file if par.readrows: it = _FileReaderRows(csvf) else: it = _FileReaderCols(csvf) # ignore rows (at top), if requested for i in xrange(par.rowsignore): it.next() # dataset names for each column self.colnames = {} # type of column (float, string or date) self.coltypes = [] # type of names of columns self.nametypes = {} # ignore lines after headers self.colignore = {} # keep track of how many blank values before 1st data for auto # type detection self.colblanks = {} # iterate over each line (or column) while True: try: line = it.next() except StopIteration: break # iterate over items on line for colnum, col in enumerate(line): try: self._handleVal(colnum, col) except _NextValue: pass def setData(self, document, linkedfile=None): """Set the read-in datasets in the document.""" # iterate over each read-in dataset dsnames = [] for name in self.data.iterkeys(): # skip error data here, they are used below # error data name contains \0 if name.find('\0') >= 0: continue dsnames.append(name) # get data and errors (if any) data = [] for k in (name, name+'\0+-', name+'\0+', name+'\0-'): data.append( self.data.get(k, None) ) # make them have a maximum length by adding NaNs maxlen = max([len(x) for x in data if x is not None]) for i in range(len(data)): if data[i] is not None and len(data[i]) < maxlen: data[i] = N.concatenate( ( data[i], N.zeros(maxlen-len(data[i]))*N.nan ) ) # create dataset dstype = self.nametypes[name] if dstype == 'string': ds = datasets.DatasetText(data=data[0], linked=linkedfile) elif dstype == 'date': ds = datasets.DatasetDateTime(data=data[0], linked=linkedfile) else: ds = datasets.Dataset(data=data[0], serr=data[1], perr=data[2], nerr=data[3], linked=linkedfile) document.setData(name, ds) dsnames.sort() return dsnames veusz-1.15/document/datasets.py0000644002344000001440000014526611734662204016615 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2006 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Classes to represent datasets.""" import re from itertools import izip import numpy as N import veusz.qtall as qt4 import veusz.utils as utils import veusz.setting as setting import veusz.plugins as plugins def _convertNumpy(a): """Convert to a numpy double if possible.""" if a is None: # leave as None return None elif not isinstance(a, N.ndarray): # convert to numpy array return N.array(a, dtype=N.float64) else: # make conversion if numpy type is not correct if a.dtype != N.float64: return a.astype(N.float64) else: return a def _convertNumpyAbs(a): """Convert to numpy 64 bit positive values, if possible.""" if a is None: return None if not isinstance(a, N.ndarray): a = N.array(a, dtype=N.float64) elif a.dtype != N.float64: a = a.astype(N.float64) return N.abs(a) def _convertNumpyNegAbs(a): """Convert to numpy 64 bit negative values, if possible.""" if a is None: return None if not isinstance(a, N.ndarray): a = N.array(a, dtype=N.float64) elif a.dtype != N.float64: a = a.astype(N.float64) return -N.abs(a) def _copyOrNone(a): """Return a copy if not None, or None.""" if a is None: return None elif isinstance(a, N.ndarray): return N.array(a) elif isinstance(a, list): return list(a) def generateValidDatasetParts(*datasets): """Generator to return array of valid parts of datasets. Yields new datasets between rows which are invalid """ # find NaNs and INFs in input dataset invalid = datasets[0].invalidDataPoints() minlen = invalid.shape[0] for ds in datasets[1:]: if isinstance(ds, DatasetBase) and not ds.empty(): nextinvalid = ds.invalidDataPoints() minlen = min(nextinvalid.shape[0], minlen) invalid = N.logical_or(invalid[:minlen], nextinvalid[:minlen]) # get indexes of invalid points indexes = invalid.nonzero()[0].tolist() # no bad points: optimisation if not indexes: yield datasets return # add on shortest length of datasets indexes.append( minlen ) lastindex = 0 for index in indexes: if index != lastindex: retn = [] for ds in datasets: if ds is not None and (not isinstance(ds, DatasetBase) or not ds.empty()): retn.append( ds[lastindex:index] ) else: retn.append( None ) yield retn lastindex = index+1 def datasetNameToDescriptorName(name): """Return descriptor name for dataset.""" if re.match('^[0-9A-Za-z_]+$', name): return name else: return '`%s`' % name class DatasetException(Exception): """Raised with dataset errors.""" pass class DatasetBase(object): """A base dataset class.""" # number of dimensions the dataset holds dimensions = 0 # datatype is fundamental type of data # displaytype is formatting suggestion for data datatype = displaytype = 'numeric' # dataset type to show to user dstype = 'Dataset' # list of columns in dataset (if any) columns = () # use descriptions for columns column_descriptions = () # class for representing part of this dataset subsetclass = None # whether this dataset's columns will change without updating the document's # changeset isstable = False def __init__(self, linked=None): """Initialise common members.""" # document member set when this dataset is set in document self.document = None # file this dataset is linked to self.linked = linked # tags applied to dataset self.tags = set() def saveLinksToSavedDoc(self, fileobj, savedlinks, relpath=None): '''Save the link to the saved document, if this dataset is linked. savedlinks is a dict containing any linked files which have already been written relpath is a directory to save linked files relative to ''' # links should only be saved once if self.linked is not None and self.linked not in savedlinks: savedlinks[self.linked] = True self.linked.saveToFile(fileobj, relpath=relpath) def name(self): """Get dataset name.""" for name, ds in self.document.data.iteritems(): if ds == self: return name raise ValueError('Could not find self in document.data') def userSize(self): """Return dimensions of dataset for user.""" return "" def userPreview(self): """Return a small preview of the dataset for the user, e.g. 1, 2, 3, ..., 4, 5, 6.""" return None def description(self, showlinked=True): """Get description of database.""" return "" def uiConvertToDataItem(self, val): """Return a value cast to this dataset data type. We assume here it is a float, so override if not """ if isinstance(val, basestring) or isinstance(val, qt4.QString): val, ok = setting.uilocale.toDouble(val) if ok: return val raise ValueError, "Invalid floating point number" return float(val) def uiDataItemToQVariant(self, val): """Return val converted to QVariant.""" return qt4.QVariant(float(val)) def _getItemHelper(self, key): """Help get arguments to constructor.""" args = {} for col in self.columns: array = getattr(self, col) if array is not None: args[col] = array[key] return args def __getitem__(self, key): """Return a dataset based on this dataset e.g. dataset[5:100] - make a dataset based on items 5 to 99 inclusive """ return type(self)(**self._getItemHelper(key)) def __len__(self): """Return length of dataset.""" return len(self.data) def deleteRows(self, row, numrows): """Delete numrows rows starting from row. Returns deleted rows as a dict of {column:data, ...} """ pass def insertRows(self, row, numrows, rowdata): """Insert numrows rows starting from row. rowdata is a dict of {column: data}. """ pass def canUnlink(self): """Can dataset be unlinked?""" return self.linked is not None def linkedInformation(self): """Return information about any linking for the user.""" if self.linked is not None: name = self.linked.filename else: name = 'None' return 'Linked file: %s' % name def returnCopy(self): """Return an unlinked copy of self.""" pass def renameable(self): """Is it possible to rename this dataset?""" return self.linked is None def datasetAsText(self, fmt='%g', join='\t'): """Return dataset as text (for use by user).""" return '' class Dataset2D(DatasetBase): '''Represents a two-dimensional dataset.''' # number of dimensions the dataset holds dimensions = 2 # dataset type dstype = '2D' # the dataset is recreated if its data changes isstable = True def __init__(self, data, xrange=None, yrange=None): '''Create a two dimensional dataset based on data. data: 2d numpy of imaging data xrange: a tuple of (start, end) coordinates for x yrange: a tuple of (start, end) coordinates for y ''' DatasetBase.__init__(self) # we don't want these set if a inheriting class uses properties instead if not hasattr(self, 'data'): try: self.data = _convertNumpy(data) self.xrange = (0, self.data.shape[1]) self.yrange = (0, self.data.shape[0]) if xrange: self.xrange = xrange if yrange: self.yrange = yrange except AttributeError: # for some reason hasattr doesn't always work pass def indexToPoint(self, xidx, yidx): """Convert a set of indices to pixels in integers to floating point vals using xrange and yrange.""" xfracpix = (xidx+0.5) * (1./self.data.shape[1]) xfloat = xfracpix * (self.xrange[1] - self.xrange[0]) + self.xrange[0] yfracpix = (yidx+0.5) * (1./self.data.shape[0]) yfloat = yfracpix * (self.yrange[1] - self.yrange[0]) + self.yrange[0] return xfloat, yfloat def getDataRanges(self): return self.xrange, self.yrange def saveToFile(self, fileobj, name): """Write the 2d dataset to the file given.""" # return if there is a link if self.linked is not None: return fileobj.write("ImportString2D(%s, '''\n" % repr(name)) fileobj.write("xrange %e %e\n" % self.xrange) fileobj.write("yrange %e %e\n" % self.yrange) fileobj.write(self.datasetAsText(fmt='%e', join=' ')) fileobj.write("''')\n") def datasetAsText(self, fmt='%g', join='\t'): """Return dataset as text. fmt is the format specifier to use join is the string to separate the items """ format = ((fmt+join) * (self.data.shape[1]-1)) + fmt + '\n' # write rows backwards, so lowest y comes first lines = [] for row in self.data[::-1]: line = format % tuple(row) lines.append(line) return ''.join(lines) def userSize(self): """Return dimensions of dataset for user.""" return u'%i×%i' % self.data.shape def userPreview(self): """Return preview of data.""" return dsPreviewHelper(self.data.flatten()) def description(self, showlinked=True): """Get description of dataset.""" text = self.name() text += u' (%i×%i)' % self.data.shape text += ', x=%g->%g' % tuple(self.xrange) text += ', y=%g->%g' % tuple(self.yrange) if self.linked and showlinked: text += ', linked to %s' % self.linked.filename return text def returnCopy(self): return Dataset2D( N.array(self.data), self.xrange, self.yrange) def dsPreviewHelper(d): """Get preview of numpy data d.""" if d.shape[0] <= 6: line1 = ', '.join( ['%.3g' % x for x in d] ) else: line1 = ', '.join( ['%.3g' % x for x in d[:3]] + [ '...' ] + ['%.3g' % x for x in d[-3:]] ) try: line2 = 'mean: %.3g, min: %.3g, max: %.3g' % ( N.nansum(d) / N.isfinite(d).sum(), N.nanmin(d), N.nanmax(d)) except (ValueError, ZeroDivisionError): # nanXXX returns error if no valid data points return line1 return line1 + '\n' + line2 class Dataset(DatasetBase): '''Represents a dataset.''' # number of dimensions the dataset holds dimensions = 1 columns = ('data', 'serr', 'nerr', 'perr') column_descriptions = ('Data', 'Sym. errors', 'Neg. errors', 'Pos. errors') dstype = '1D' # the dataset is recreated if its data changes isstable = True def __init__(self, data = None, serr = None, nerr = None, perr = None, linked = None): '''Initialise dataset with the sets of values given. The values can be given as numpy 1d arrays or lists of numbers linked optionally specifies a LinkedFile to link the dataset to ''' DatasetBase.__init__(self, linked=linked) # convert data to numpy arrays data = _convertNumpy(data) serr = _convertNumpyAbs(serr) perr = _convertNumpyAbs(perr) nerr = _convertNumpyNegAbs(nerr) # check the sizes of things match up s = data.shape for x in (serr, nerr, perr): if x is not None and x.shape != s: raise DatasetException('Lengths of error data do not match data') # finally assign data self._invalidpoints = None try: if not hasattr(self, 'data'): self.data = data self.serr = serr self.perr = perr self.nerr = nerr except AttributeError: # we don't want these set if a inheriting class uses properties instead pass def userSize(self): """Size of dataset.""" return str( self.data.shape[0] ) def userPreview(self): """Preview of data.""" return dsPreviewHelper(self.data) def description(self, showlinked=True): """Get description of dataset.""" text = self.name() if self.serr is not None: text += ',+-' if self.perr is not None: text += ',+' if self.nerr is not None: text += ',-' text += ' (length %i)' % len(self.data) if self.linked and showlinked: text += ' linked to %s' % self.linked.filename return text def invalidDataPoints(self): """Return a numpy bool detailing which datapoints are invalid.""" if self._invalidpoints is None: # recalculate valid points self._invalidpoints = N.logical_not(N.isfinite(self.data)) for error in self.serr, self.perr, self.nerr: if error is not None: self._invalidpoints = N.logical_or(self._invalidpoints, N.logical_not(N.isfinite(error))) return self._invalidpoints def hasErrors(self): '''Whether errors on dataset''' return (self.serr is not None or self.nerr is not None or self.perr is not None) def getPointRanges(self): '''Get range of coordinates for each point in the form (minima, maxima).''' minvals = self.data.copy() maxvals = self.data.copy() if self.serr is not None: minvals -= self.serr maxvals += self.serr if self.nerr is not None: minvals += self.nerr if self.perr is not None: maxvals += self.perr return ( minvals[N.isfinite(minvals)], maxvals[N.isfinite(maxvals)] ) def getRange(self): '''Get total range of coordinates. Returns None if empty.''' minvals, maxvals = self.getPointRanges() if len(minvals) > 0 and len(maxvals) > 0: return ( minvals.min(), maxvals.max() ) else: return None def empty(self): '''Is the data defined?''' return self.data is None or len(self.data) == 0 def changeValues(self, thetype, vals): """Change the requested part of the dataset to vals. thetype == data | serr | perr | nerr """ self._invalidpoints = None if thetype in self.columns: setattr(self, thetype, vals) else: raise ValueError, 'thetype does not contain an allowed value' # just a check... s = self.data.shape for x in (self.serr, self.nerr, self.perr): assert x is None or x.shape == s # tell the document that we've changed self.document.modifiedData(self) def saveToFile(self, fileobj, name): '''Save data to file. ''' # return if there is a link if self.linked is not None: return # build up descriptor descriptor = datasetNameToDescriptorName(name) + '(numeric)' if self.serr is not None: descriptor += ',+-' if self.perr is not None: descriptor += ',+' if self.nerr is not None: descriptor += ',-' fileobj.write( "ImportString(%s,'''\n" % repr(descriptor) ) fileobj.write( self.datasetAsText(fmt='%e', join=' ') ) fileobj.write( "''')\n" ) def datasetAsText(self, fmt='%g', join='\t'): """Return data as text.""" # work out which columns to write cols = [] for c in (self.data, self.serr, self.perr, self.nerr): if c is not None: cols.append(c) # format statement format = (fmt + join) * (len(cols)-1) + fmt + '\n' # do the conversion lines = [] for line in izip(*cols): lines.append( format % line ) return ''.join(lines) def deleteRows(self, row, numrows): """Delete numrows rows starting from row. Returns deleted rows as a dict of {column:data, ...} """ retn = {} for col in self.columns: coldata = getattr(self, col) if coldata is not None: retn[col] = coldata[row:row+numrows] setattr(self, col, N.delete( coldata, N.s_[row:row+numrows] )) self.document.modifiedData(self) return retn def insertRows(self, row, numrows, rowdata): """Insert numrows rows starting from row. rowdata is a dict of {column: data}. """ for col in self.columns: coldata = getattr(self, col) data = N.zeros(numrows) if col in rowdata: data[:len(rowdata[col])] = N.array(rowdata[col]) if coldata is not None: setattr(self, col, N.insert(coldata, [row]*numrows, data)) self.document.modifiedData(self) def returnCopy(self): """Return version of dataset with no linking.""" return Dataset(data = _copyOrNone(self.data), serr = _copyOrNone(self.serr), perr = _copyOrNone(self.perr), nerr = _copyOrNone(self.nerr)) class DatasetDateTime(Dataset): """Dataset holding dates and times.""" columns = ('data',) column_descriptions = ('Data',) isstable = True dstype = 'Date' displaytype = 'date' def __init__(self, data=None, linked=None): Dataset.__init__(self, data=data, linked=linked) def description(self, showlinked=True): text = '%s (%i date/times)' % (self.name(), len(self.data)) if self.linked and showlinked: text += ', linked to %s' % self.linked.filename return text def uiConvertToDataItem(self, val): """Return a value cast to this dataset data type.""" if isinstance(val, basestring) or isinstance(val, qt4.QString): v = utils.dateStringToDate( unicode(val) ) if not N.isfinite(v): try: v = float(val) except ValueError: pass return v else: return N.nan def uiDataItemToQVariant(self, val): """Return val converted to QVariant.""" return qt4.QVariant(utils.dateFloatToString(val)) def saveToFile(self, fileobj, name): '''Save data to file. ''' if self.linked is not None: # do not save if linked to a file return descriptor = datasetNameToDescriptorName(name) + '(date)' fileobj.write( "ImportString(%s,'''\n" % repr(descriptor) ) fileobj.write( self.datasetAsText() ) fileobj.write( "''')\n" ) def datasetAsText(self, fmt=None, join=None): """Return data as text.""" lines = [ utils.dateFloatToString(val) for val in self.data ] lines.append('') return '\n'.join(lines) def returnCopy(self): """Returns version of dataset with no linking.""" return DatasetDateTime(data=N.array(self.data)) class DatasetText(DatasetBase): """Represents a text dataset: holding an array of strings.""" dimensions = 1 datatype = displaytype = 'text' columns = ('data',) column_descriptions = ('Data',) dstype = 'Text' isstable = True def __init__(self, data=None, linked=None): """Initialise dataset with data given. Data are a list of strings.""" DatasetBase.__init__(self, linked=linked) self.data = list(data) def description(self, showlinked=True): text = '%s (%i items)' % (self.name(), len(self.data)) if self.linked and showlinked: text += ', linked to %s' % self.linked.filename return text def userSize(self): """Size of dataset.""" return str( len(self.data) ) def changeValues(self, type, vals): if type == 'data': self.data = list(vals) else: raise ValueError, 'type does not contain an allowed value' self.document.modifiedData(self) def uiConvertToDataItem(self, val): """Return a value cast to this dataset data type.""" return unicode(val) def uiDataItemToQVariant(self, val): """Return val converted to QVariant.""" return qt4.QVariant(unicode(val)) def saveToFile(self, fileobj, name): '''Save data to file. ''' # don't save if a link if self.linked is not None: return descriptor = datasetNameToDescriptorName(name) + '(text)' fileobj.write( "ImportString(%s,r'''\n" % repr(descriptor) ) for line in self.data: # need to "escape" ''' marks in text r = repr(line).replace("'''", "''' \"'''\" r'''") + '\n' fileobj.write(r) fileobj.write( "''')\n" ) def datasetAsText(self, fmt=None, join=None): """Return data as text.""" lines = list(self.data) lines.append('') return '\n'.join(lines) def deleteRows(self, row, numrows): """Delete numrows rows starting from row. Returns deleted rows as a dict of {column:data, ...} """ retn = {'data': self.data[row:row+numrows]} del self.data[row:row+numrows] self.document.modifiedData(self) return retn def insertRows(self, row, numrows, rowdata): """Insert numrows rows starting from row. rowdata is a dict of {column: data}. """ data = rowdata.get('data', []) insdata = data + (['']*(numrows-len(data))) for d in insdata[::-1]: self.data.insert(row, d) self.document.modifiedData(self) def returnCopy(self): """Returns version of dataset with no linking.""" return DatasetText(self.data) class DatasetExpressionException(DatasetException): """Raised if there is an error evaluating a dataset expression.""" pass # split expression on python operators or quoted `DATASET` dataexpr_split_re = re.compile(r'(`.*?`|[\.+\-*/\(\)\[\],<>=!|%^~& ])') # identify whether string is a quoted identifier dataexpr_quote_re = re.compile(r'^`.*`$') dataexpr_columns = {'data':True, 'serr':True, 'perr':True, 'nerr':True} def _substituteDatasets(datasets, expression, thispart): """Substitute the names of datasets with calls to a function which will evaluate them. Returns (new expression, list of substituted datasets) This is horribly hacky, but python-2.3 can't use eval with dict subclass """ # split apart the expression to look for dataset names # re could be compiled if this gets slow bits = dataexpr_split_re.split(expression) dslist = [] for i, bit in enumerate(bits): # test whether there's an _data, _serr or such at the end of the name part = thispart if dataexpr_quote_re.match(bit): # quoted text, so remove backtick-"quotes" bit = bit[1:-1] bitbits = bit.split('_') if len(bitbits) > 1: if bitbits[-1] in dataexpr_columns: part = bitbits.pop(-1) bit = '_'.join(bitbits) if bit in datasets: # replace name with a function to call bits[i] = "_DS_(%s, %s)" % (repr(bit), repr(part)) dslist.append(bit) return ''.join(bits), dslist def _evaluateDataset(datasets, dsname, dspart): """Return the dataset given. dsname is the name of the dataset dspart is the part to get (e.g. data, serr) """ if dspart in dataexpr_columns: val = getattr(datasets[dsname], dspart) if val is None: raise DatasetExpressionException( "Dataset '%s' does not have part '%s'" % (dsname, dspart)) return val else: raise DatasetExpressionException( 'Internal error - invalid dataset part') _safeexpr = set() def simpleEvalExpression(doc, expr, part='data'): """Evaluate expression and return data. part is 'data', 'serr', 'perr' or 'nerr' - these are the dataset parts which are evaluated by the expression """ expr = _substituteDatasets(doc.data, expr, part)[0] if expr not in _safeexpr: if ( not setting.transient_settings['unsafe_mode'] and utils.checkCode(expr, securityonly=True) ): doc.log("Unsafe expression: %s\n" % expr) return N.array([]) # for speed, track safe expressions _safeexpr.add(expr) env = doc.eval_context.copy() def evaluateDataset(dsname, dspart): return _evaluateDataset(doc.data, dsname, dspart) env['_DS_'] = evaluateDataset try: evalout = eval(expr, env) except Exception, ex: doc.log(unicode(ex)) return N.array([]) return evalout class DatasetExpression(Dataset): """A dataset which is linked to another dataset by an expression.""" dstype = 'Expression' def __init__(self, data=None, serr=None, nerr=None, perr=None, parametric=None): """Initialise the dataset with the expressions given. parametric is option and can be (minval, maxval, steps) or None """ Dataset.__init__(self, data=[]) # store the expressions to use to generate the dataset self.expr = {} self.expr['data'] = data self.expr['serr'] = serr self.expr['nerr'] = nerr self.expr['perr'] = perr self.parametric = parametric self.cachedexpr = {} self.docchangeset = -1 self.evaluated = {} def evaluateDataset(self, dsname, dspart): """Return the dataset given. dsname is the name of the dataset dspart is the part to get (e.g. data, serr) """ return _evaluateDataset(self.document.data, dsname, dspart) def _evaluatePart(self, expr, part): """Evaluate expression expr for part part.""" # replace dataset names with calls expr = _substituteDatasets(self.document.data, expr, part)[0] # check expression for nasties if it has changed if self.cachedexpr.get(part) != expr: if ( not setting.transient_settings['unsafe_mode'] and utils.checkCode(expr, securityonly=True) ): raise DatasetExpressionException( "Unsafe expression '%s' in %s part of dataset" % ( self.expr[part], part)) self.cachedexpr[part] = expr # set up environment to evaluate expressions in environment = self.document.eval_context.copy() # create dataset using parametric expression if self.parametric: p = self.parametric if p[2] >= 2: deltat = (p[1]-p[0]) / (p[2]-1) t = N.arange(p[2])*deltat + p[0] else: t = N.array([p[0]]) environment['t'] = t # this fn gets called to return the value of a dataset environment['_DS_'] = self.evaluateDataset # actually evaluate the expression try: result = eval(expr, environment) evalout = N.array(result, N.float64) except Exception, ex: raise DatasetExpressionException( "Error evaluating expression: %s\n" "Error: %s" % (self.expr[part], unicode(ex)) ) # make evaluated error expression have same shape as data if part != 'data': data = self.evaluated['data'] if evalout.shape == (): # zero dimensional - expand to data shape evalout = N.resize(evalout, data.shape) else: # 1-dimensional - make it right size and trim oldsize = evalout.shape[0] evalout = N.resize(evalout, data.shape) evalout[oldsize:] = N.nan else: if evalout.shape == (): # zero dimensional - make a single point evalout = N.resize(evalout, 1) self.evaluated[part] = evalout def updateEvaluation(self): """Update evaluation of parts of dataset. Throws DatasetExpressionException if error """ if self.docchangeset != self.document.changeset: # avoid infinite recursion! self.docchangeset = self.document.changeset # zero out previous values for part in self.columns: self.evaluated[part] = None # update all parts for part in self.columns: expr = self.expr[part] if expr is not None and expr.strip() != '': self._evaluatePart(expr, part) def _propValues(self, part): """Check whether expressions need reevaluating, and recalculate if necessary.""" try: self.updateEvaluation() except DatasetExpressionException, ex: self.document.log(unicode(ex)) # catch case where error in setting data, need to return "real" data if self.evaluated['data'] is None: self.evaluated['data'] = N.array([]) return self.evaluated[part] # expose evaluated data as properties # this allows us to recalculate the expressions on the fly data = property(lambda self: self._propValues('data')) serr = property(lambda self: self._propValues('serr')) perr = property(lambda self: self._propValues('perr')) nerr = property(lambda self: self._propValues('nerr')) def saveToFile(self, fileobj, name): '''Save data to file. ''' parts = [repr(name), repr(self.expr['data'])] if self.expr['serr']: parts.append('symerr=%s' % repr(self.expr['serr'])) if self.expr['nerr']: parts.append('negerr=%s' % repr(self.expr['nerr'])) if self.expr['perr']: parts.append('poserr=%s' % repr(self.expr['perr'])) if self.parametric is not None: parts.append('parametric=%s' % repr(self.parametric)) parts.append('linked=True') s = 'SetDataExpression(%s)\n' % ', '.join(parts) fileobj.write(s) def __getitem__(self, key): """Return a dataset based on this dataset We override this from DatasetBase as it would return a DatsetExpression otherwise, not chopped sets of data. """ return Dataset(**self._getItemHelper(key)) def deleteRows(self, row, numrows): pass def insertRows(self, row, numrows, rowdata): pass def canUnlink(self): """Whether dataset can be unlinked.""" return True def linkedInformation(self): """Return information about linking.""" text = [] if self.parametric: text.append('Linked parametric dataset') else: text.append('Linked expression dataset') for label, part in izip(self.column_descriptions, self.columns): if self.expr[part]: text.append('%s: %s' % (label, self.expr[part])) if self.parametric: text.append("where t goes from %g:%g in %i steps" % self.parametric) return '\n'.join(text) class DatasetRange(Dataset): """Dataset consisting of a range of values e.g. 1 to 10 in 10 steps.""" dstype = 'Range' isstable = True def __init__(self, numsteps, data, serr=None, perr=None, nerr=None): """Construct dataset. numsteps: number of steps in range data, serr, perr and nerr are tuples containing (start, stop) values.""" Dataset.__init__(self, data=[]) self.range_data = data self.range_serr = serr self.range_perr = perr self.range_nerr = nerr self.numsteps = numsteps for name in ('data', 'serr', 'perr', 'nerr'): val = getattr(self, 'range_%s' % name) if val is not None: minval, maxval = val if numsteps == 1: vals = N.array( [minval] ) else: delta = (maxval - minval) / (numsteps-1) vals = N.arange(numsteps)*delta + minval else: vals = None setattr(self, name, vals) def __getitem__(self, key): """Return a dataset based on this dataset We override this from DatasetBase as it would return a DatsetExpression otherwise, not chopped sets of data. """ return Dataset(**self._getItemHelper(key)) def userSize(self): """Size of dataset.""" return str( self.numsteps ) def saveToFile(self, fileobj, name): """Save dataset to file.""" parts = [repr(name), repr(self.numsteps), repr(self.range_data)] if self.range_serr is not None: parts.append('symerr=%s' % repr(self.range_serr)) if self.range_perr is not None: parts.append('poserr=%s' % repr(self.range_perr)) if self.range_nerr is not None: parts.append('negerr=%s' % repr(self.range_nerr)) parts.append('linked=True') s = 'SetDataRange(%s)\n' % ', '.join(parts) fileobj.write(s) def canUnlink(self): return True def linkedInformation(self): """Return information about linking.""" text = ['Linked range dataset'] for label, part in izip(self.column_descriptions, self.columns): val = getattr(self, 'range_%s' % part) if val: text.append('%s: %g:%g' % (label, val[0], val[1])) return '\n'.join(text) def getSpacing(data): """Given a set of values, get minimum, maximum, step size and number of steps. Function allows that values may be missing Function assumes that at least one of the steps is the minimum step size (i.e. steps are not all multiples of some mininimum) """ uniquesorted = N.unique(data) sigfactor = (uniquesorted[-1]-uniquesorted[0])*1e-13 # differences between elements deltas = N.unique( N.ediff1d(uniquesorted) ) mindelta = None for delta in deltas: if delta > sigfactor: if mindelta is None: # first delta mindelta = delta elif N.fabs(mindelta-delta) > sigfactor: # new delta - check is multiple of old delta ratio = delta/mindelta if N.fabs(int(ratio)-ratio) > 1e-3: raise DatasetExpressionException( 'Variable spacings not yet supported ' 'in constructing 2D datasets') return (uniquesorted[0], uniquesorted[-1], mindelta, int((uniquesorted[-1]-uniquesorted[0])/mindelta)+1) class Dataset2DXYZExpression(Dataset2D): '''A 2d dataset with expressions for x, y and z.''' dstype = '2D XYZ' def __init__(self, exprx, expry, exprz): """Initialise dataset. Parameters are mathematical expressions based on datasets.""" Dataset2D.__init__(self, []) self.lastchangeset = -1 self.cacheddata = None # copy parameters self.exprx = exprx self.expry = expry self.exprz = exprz # cache x y and z expressions self.cachedexpr = {} def evaluateDataset(self, dsname, dspart): """Return the dataset given. dsname is the name of the dataset dspart is the part to get (e.g. data, serr) """ return _evaluateDataset(self.document.data, dsname, dspart) def evalDataset(self): """Return the evaluated dataset.""" # return cached data if document unchanged if self.document.changeset == self.lastchangeset: return self.cacheddata evaluated = {} environment = self.document.eval_context.copy() environment['_DS_'] = self.evaluateDataset # evaluate the x, y and z expressions for name in ('exprx', 'expry', 'exprz'): expr = _substituteDatasets(self.document.data, getattr(self, name), 'data')[0] # check expression if not checked before if self.cachedexpr.get(name) != expr: if ( not setting.transient_settings['unsafe_mode'] and utils.checkCode(expr, securityonly=True) ): raise DatasetExpressionException( "Unsafe expression '%s'" % ( expr)) self.cachedexpr[name] = expr try: evaluated[name] = eval(expr, environment) except Exception, e: raise DatasetExpressionException( "Error evaluating expression: %s\n" "Error: %s" % (expr, unicode(e)) ) minx, maxx, stepx, stepsx = getSpacing(evaluated['exprx']) miny, maxy, stepy, stepsy = getSpacing(evaluated['expry']) # update cached x and y ranges self._xrange = (minx-stepx*0.5, maxx+stepx*0.5) self._yrange = (miny-stepy*0.5, maxy+stepy*0.5) self.cacheddata = N.empty( (stepsy, stepsx) ) self.cacheddata[:,:] = N.nan xpts = ((1./stepx)*(evaluated['exprx']-minx)).astype('int32') ypts = ((1./stepy)*(evaluated['expry']-miny)).astype('int32') # this is ugly - is this really the way to do it? try: self.cacheddata.flat [ xpts + ypts*stepsx ] = evaluated['exprz'] except Exception, e: raise DatasetExpressionException( "Shape mismatch when constructing dataset\n" "Error: %s" % unicode(e) ) # update changeset self.lastchangeset = self.document.changeset return self.cacheddata @property def xrange(self): """Get x range of data as a tuple (min, max).""" return self.getDataRanges()[0] @property def yrange(self): """Get y range of data as a tuple (min, max).""" return self.getDataRanges()[1] def getDataRanges(self): """Get both ranges of axis.""" try: self.evalDataset() return (self._xrange, self._yrange) except DatasetExpressionException: return ( (0., 1.), (0., 1.) ) @property def data(self): """Get data, or none if error.""" try: return self.evalDataset() except DatasetExpressionException, ex: self.document.log(unicode(ex)) return N.array( [[]] ) def description(self, showlinked=True): # FIXME: dataeditdialog descriptions should be taken from here somewhere text = self.name() text += ' (%ix%i)' % self.data.shape text += ', x=%g->%g' % tuple(self.xrange) text += ', y=%g->%g' % tuple(self.yrange) def saveToFile(self, fileobj, name): '''Save expressions to file. ''' s = 'SetData2DExpressionXYZ(%s, %s, %s, %s, linked=True)\n' % ( repr(name), repr(self.exprx), repr(self.expry), repr(self.exprz) ) fileobj.write(s) def canUnlink(self): """Can relationship be unlinked?""" return True def linkedInformation(self): """Return linking information.""" return 'Linked 2D function: x=%s, y=%s, z=%s' % ( self.exprx, self.expry, self.exprz) class Dataset2DExpression(Dataset2D): """Evaluate an expression of 2d datasets.""" dstype = '2D Expr' def __init__(self, expr): """Create 2d expression dataset.""" Dataset2D.__init__(self, None) self.expr = expr self.lastchangeset = -1 self.cachedexpr = None if utils.checkCode(expr, securityonly=True) is not None: raise DatasetExpressionException("Unsafe expression '%s'" % expr) @property def data(self): """Return data, or empty array if error.""" try: return self.evalDataset()[0] except DatasetExpressionException, ex: self.document.log(unicode(ex)) return N.array([[]]) @property def xrange(self): """Return x range.""" try: return self.evalDataset()[1] except DatasetExpressionException, ex: self.document.log(unicode(ex)) return [0., 1.] @property def yrange(self): """Return y range.""" try: return self.evalDataset()[2] except DatasetExpressionException, ex: self.document.log(unicode(ex)) return [0., 1.] def evalDataset(self): """Do actual evaluation.""" if self.document.changeset == self.lastchangeset: return self._cacheddata environment = self.document.eval_context.copy() def getdataset(dsname, dspart): """Return the dataset given in document.""" return _evaluateDataset(self.document.data, dsname, dspart) environment['_DS_'] = getdataset # substituted expression expr, datasets = _substituteDatasets(self.document.data, self.expr, 'data') # check expression if not checked before if self.cachedexpr != expr: if ( not setting.transient_settings['unsafe_mode'] and utils.checkCode(expr, securityonly=True) ): raise DatasetExpressionException( "Unsafe expression '%s'" % ( expr)) self.cachedexpr = expr # do evaluation try: evaluated = eval(expr, environment) except Exception, e: raise DatasetExpressionException( "Error evaluating expression: %s\n" "Error: %s" % (expr, str(e)) ) # find 2d dataset dimensions dsdim = None for ds in datasets: d = self.document.data[ds] if d.dimensions == 2: dsdim = d break if dsdim is None: # use default if none found rangex = rangey = [0., 1.] else: # use 1st dataset otherwise rangex = dsdim.xrange rangey = dsdim.yrange self.lastchangeset = self.document.changeset self._cacheddata = evaluated, rangex, rangey return self._cacheddata def saveToFile(self, fileobj, name): '''Save expression to file.''' s = 'SetData2DExpression(%s, %s, linked=True)\n' % ( repr(name), repr(self.expr) ) fileobj.write(s) def canUnlink(self): """Can relationship be unlinked?""" return True def linkedInformation(self): """Return linking information.""" return 'Linked 2D expression: %s' % self.expr class Dataset2DXYFunc(Dataset2D): """Given a range of x and y, this is a dataset which is a function of this. """ dstype = '2D f(x,y)' def __init__(self, xstep, ystep, expr): """Create 2d dataset: xstep: tuple(xmin, xmax, step) ystep: tuple(ymin, ymax, step) expr: expression of x and y """ Dataset2D.__init__(self, []) self.xstep = xstep self.ystep = ystep self.expr = expr if utils.checkCode(expr, securityonly=True) is not None: raise DatasetExpressionException("Unsafe expression '%s'" % expr) self.xrange = (self.xstep[0] - self.xstep[2]*0.5, self.xstep[1] + self.xstep[2]*0.5) self.yrange = (self.ystep[0] - self.ystep[2]*0.5, self.ystep[1] + self.ystep[2]*0.5) self.lastchangeset = -1 @property def data(self): """Return data, or empty array if error.""" try: return self.evalDataset() except DatasetExpressionException, ex: self.document.log(unicode(ex)) return N.array([[]]) def evalDataset(self): """Evaluate the 2d dataset.""" if self.document.changeset == self.lastchangeset: return self.cacheddata env = self.document.eval_context.copy() xarange = N.arange(self.xstep[0], self.xstep[1]+self.xstep[2], self.xstep[2]) yarange = N.arange(self.ystep[0], self.ystep[1]+self.ystep[2], self.ystep[2]) ystep, xstep = N.indices( (len(yarange), len(xarange)) ) xstep = xarange[xstep] ystep = yarange[ystep] env['x'] = xstep env['y'] = ystep try: data = eval(self.expr, env) except Exception, e: raise DatasetExpressionException("Error evaluating expression: %s\n" "Error: %s" % (self.expr, str(e)) ) # ensure we get an array out of this (in case expr is scalar) data = data + xstep*0 self.cacheddata = data self.lastchangeset = self.document.changeset return data def saveToFile(self, fileobj, name): '''Save expressions to file. ''' s = 'SetData2DXYFunc(%s, %s, %s, %s, linked=True)\n' % ( repr(name), repr(self.xstep), repr(self.ystep), repr(self.expr) ) fileobj.write(s) def canUnlink(self): """Can relationship be unlinked?""" return True def linkedInformation(self): """Return linking information.""" return 'Linked 2D function: x=%g:%g:%g, y=%g:%g:%g, z=%s' % tuple( list(self.xstep) + list(self.ystep) + [self.expr]) class _DatasetPlugin(object): """Shared methods for dataset plugins.""" def __init__(self, manager, ds): self.pluginmanager = manager self.pluginds = ds def getPluginData(self, attr): self.pluginmanager.update() return getattr(self.pluginds, attr) def linkedInformation(self): """Return information about how this dataset was created.""" fields = [] for name, val in self.pluginmanager.fields.iteritems(): fields.append('%s: %s' % (unicode(name), unicode(val))) return '%s plugin dataset (fields %s), size %i' % ( self.pluginmanager.plugin.name, ', '.join(fields), self.data.shape[0]) def canUnlink(self): """Can relationship be unlinked?""" return True def deleteRows(self, row, numrows): pass def insertRows(self, row, numrows, rowdata): pass def saveToFile(self, fileobj, name): """Save plugin to file, if this is the first one.""" # only try to save if this is the 1st dataset of this plugin # manager in the document, so that we don't save more than once docdatasets = set( self.document.data.values() ) for ds in self.pluginmanager.veuszdatasets: if ds in docdatasets: if ds is self: # is 1st dataset self.pluginmanager.saveToFile(fileobj) return @property def dstype(self): """Return type of plugin.""" return self.pluginmanager.plugin.name class Dataset1DPlugin(_DatasetPlugin, Dataset): """Return 1D dataset from a plugin.""" def __init__(self, manager, ds): _DatasetPlugin.__init__(self, manager, ds) Dataset.__init__(self, data=[]) def __getitem__(self, key): return Dataset(**self._getItemHelper(key)) def userSize(self): """Size of dataset.""" return str( self.data.shape[0] ) def __getitem__(self, key): """Return a dataset based on this dataset We override this from DatasetBase as it would return a DatsetExpression otherwise, not chopped sets of data. """ return Dataset(**self._getItemHelper(key)) # parent class sets these attributes, so override setattr to do nothing data = property( lambda self: self.getPluginData('data'), lambda self, val: None ) serr = property( lambda self: self.getPluginData('serr'), lambda self, val: None ) nerr = property( lambda self: self.getPluginData('nerr'), lambda self, val: None ) perr = property( lambda self: self.getPluginData('perr'), lambda self, val: None ) class Dataset2DPlugin(_DatasetPlugin, Dataset2D): """Return 2D dataset from a plugin.""" def __init__(self, manager, ds): _DatasetPlugin.__init__(self, manager, ds) Dataset2D.__init__(self, [[]]) def __getitem__(self, key): return Dataset2D(self.data[key], xrange=self.xrange, yrange=self.yrange) data = property( lambda self: self.getPluginData('data'), lambda self, val: None ) xrange = property( lambda self: self.getPluginData('rangex'), lambda self, val: None ) yrange = property( lambda self: self.getPluginData('rangey'), lambda self, val: None ) class DatasetTextPlugin(_DatasetPlugin, DatasetText): """Return text dataset from a plugin.""" def __init__(self, manager, ds): _DatasetPlugin.__init__(self, manager, ds) DatasetText.__init__(self, []) def __getitem__(self, key): return DatasetText(self.data[key]) data = property( lambda self: self.getPluginData('data'), lambda self, val: None ) class DatasetDateTimePlugin(_DatasetPlugin, DatasetDateTime): """Return date dataset from plugin.""" def __init__(self, manager, ds): _DatasetPlugin.__init__(self, manager, ds) DatasetDateTime.__init__(self, []) def __getitem__(self, key): return DatasetDateTime(self.data[key]) data = property( lambda self: self.getPluginData('data'), lambda self, val: None ) veusz-1.15/document/commandinterface.py0000644002344000001440000010762011734662204020274 0ustar jssusers00000000000000# commandinterface.py # this module supplies the command line interface for plotting # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Module supplies the command interface used in the program, and for external programs. """ import os.path import traceback import veusz.qtall as qt4 import veusz.setting as setting import veusz.embed as embed import veusz.plugins as plugins import veusz.utils as utils import linked import importparams import datasets import operations import dataset_histo import mime import export import readcsv class CommandInterface(qt4.QObject): """Class provides command interface.""" # commands which are safe in any script safe_commands = ( 'Action', 'Add', 'AddCustom', 'AddImportPath', 'CloneWidget', 'CreateHistogram', 'DatasetPlugin', 'Get', 'GetChildren', 'GetData', 'GetDataType', 'GetDatasets', 'ImportFITSFile', 'ImportFile', 'ImportFile2D', 'ImportFileCSV', 'ImportFilePlugin', 'ImportString', 'ImportString2D', 'List', 'NodeChildren', 'NodeType', 'ReloadData', 'Remove', 'RemoveCustom', 'Rename', 'ResolveReference', 'Set', 'SetToReference', 'SetData', 'SetData2D', 'SetData2DExpression', 'SetData2DExpressionXYZ', 'SetData2DXYFunc', 'SetDataDateTime', 'SetDataExpression', 'SetDataRange', 'SetDataText', 'SettingType', 'SetVerbose', 'TagDatasets', 'To', 'WidgetType', ) # commands which can modify disk, etc unsafe_commands = ( 'Export', 'Print', 'Save', ) def __init__(self, document): """Initialise the interface.""" qt4.QObject.__init__(self) self.document = document self.currentwidget = self.document.basewidget self.verbose = False self.importpath = [] self.connect( self.document, qt4.SIGNAL("sigWiped"), self.slotWipedDoc ) self.Root = embed.WidgetNode(self, 'widget', '/') def slotWipedDoc(self): """When the document is wiped, we change to the root widget.""" self.To('/') def findFileOnImportPath(self, filename): """Find file on path, returning filename, or original if not found.""" for path in self.importpath: fname = os.path.join(path, filename) try: # try to open file to make sure we have access to it and # it exists opened = open(fname) opened.close() return fname except EnvironmentError: pass return filename def SetVerbose(self, v=True): """Specify whether we want verbose output after operations.""" self.verbose = v def Add(self, widgettype, **args_opt): """Add a widget to the widget with the type given. optional arguments: widget: widget path to place widget in autoadd: if True (default), any subwidgets associated with widget are created automatically setting names, e.g. leftMargin='1cm', Border_color='red' """ at = self.currentwidget if 'widget' in args_opt: at = self.document.resolve(self.currentwidget, args_opt['widget']) del args_opt['widget'] op = operations.OperationWidgetAdd(at, widgettype, **args_opt) w = self.document.applyOperation(op) if self.verbose: print "Added a widget of type '%s' (%s)" % (type, w.userdescription) return w.name def AddCustom(self, ctype, name, val, mode='appendalways'): """Add a custom definition for evaluation of expressions. This can define a constant (can be in terms of other constants), a function of 1 or more variables, or a function imported from an external python module. ctype is "constant", "function" or "import". name is name of constant, or "function(x, y, ...)" or module name. val is definition for constant or function (both are _strings_), or is a list of symbols for a module (comma separated items in a string). if mode is 'appendalways', the custom value is appended to the end of the list even if there is one with the same name. If mode is 'replace', it replaces any existing definition in the same place in the list or is appended otherwise. If mode is 'append', then an existing definition is deleted, and the new one appended to the end. """ if not isinstance(val, basestring): raise RuntimeError, 'Value should be string' if mode not in ('appendalways', 'append', 'replace'): raise RuntimeError, 'Invalid mode' if ctype not in ('constant', 'import', 'function'): raise RuntimeError, 'Invalid type' vals = list( self.document.customs ) item = [ctype, name, val] if mode == 'appendalways': vals.append(item) else: # find any existing item for i, (t, n, v) in enumerate(vals): if n == name: if mode == 'append': del vals[i] vals.append(item) else: # replace vals[i] = item break else: # no existing item, so append vals.append(item) op = operations.OperationSetCustom(vals) self.document.applyOperation(op) def AddImportPath(self, directory): """Add directory to import file path.""" assert isinstance(directory, basestring) self.importpath.append(directory) def CloneWidget(self, widget, newparent, newname=None): """Clone the widget given, placing the copy in newparent and the name given. newname is an optional new name to give it Returns new widget path """ widget = self.document.resolve(self.currentwidget, widget) newparent = self.document.resolve(self.currentwidget, newparent) op = mime.OperationWidgetClone(widget, newparent, newname) w = self.document.applyOperation(op) return w.path def CreateHistogram(self, inexpr, outbinsds, outvalsds, binparams=None, binmanual=None, method='counts', cumulative = 'none', errors=False): """Histogram an input expression. inexpr is input expression outbinds is the name of the dataset to create giving bin positions outvalsds is name of dataset for bin values binparams is None or (numbins, minval, maxval, islogbins) binmanual is None or a list of bin values method is 'counts', 'density', or 'fractions' cumulative is to calculate cumulative distributions which is 'none', 'smalltolarge' or 'largetosmall' errors is to calculate Poisson error bars """ op = dataset_histo.OperationDatasetHistogram( inexpr, outbinsds, outvalsds, binparams=binparams, binmanual=binmanual, method=method, cumulative=cumulative, errors=errors) self.document.applyOperation(op) if self.verbose: print ('Constructed histogram of "%s", creating datasets' ' "%s" and "%s"') % (inexpr, outbinsds, outvalsds) def DatasetPlugin(self, pluginname, fields, datasetnames={}): """Use a dataset plugin. pluginname: name of plugin to use fields: dict of input values to plugin datasetnames: dict mapping old names to new names of datasets if they are renamed. The new name None means dataset is deleted.""" # lookup plugin (urgh) plugin = None for pkls in plugins.datasetpluginregistry: if pkls.name == pluginname: plugin = pkls() break if plugin is None: raise RuntimeError, "Cannot find dataset plugin '%s'" % pluginname # do the work op = operations.OperationDatasetPlugin(plugin, fields, datasetnames=datasetnames) datasets = self.document.applyOperation(op) if self.verbose: print "Used dataset plugin %s to make datasets %s" % ( pluginname, ', '.join(datasets)) def Remove(self, name): """Remove a widget from the dataset.""" w = self.document.resolve(self.currentwidget, name) op = operations.OperationWidgetDelete(w) self.document.applyOperation(op) if self.verbose: print "Removed widget '%s'" % name def RemoveCustom(self, name): """Removes a custom-defined constant, function or import.""" vals = list( self.document.customs ) for i, (t, n, v) in enumerate(vals): if n == name: del vals[i] break else: raise ValueError, 'Custom variable not defined' op = operations.OperationSetCustom(vals) self.document.applyOperation(op) def To(self, where): """Change to a widget within the current widget. where is a path to the widget relative to the current widget """ self.currentwidget = self.document.resolve(self.currentwidget, where) if self.verbose: print "Changed to widget '%s'" % self.currentwidget.path def List(self, where='.'): """List the contents of a widget, by default the current widget.""" widget = self.document.resolve(self.currentwidget, where) children = widget.childnames if len(children) == 0: print '%30s' % 'No children found' else: # output format name, type for name in children: w = widget.getChild(name) print '%10s %10s %30s' % (name, w.typename, w.userdescription) def Get(self, var): """Get the value of a setting.""" return self.currentwidget.prefLookup(var).val def GetChildren(self, where='.'): """Return a list of widgets which are children of the widget of the path given.""" return list( self.document.resolve(self.currentwidget, where).childnames ) def GetDatasets(self): """Return a list of names of datasets.""" ds = self.document.data.keys() ds.sort() return ds def ResolveReference(self, setn): """If the setting is set to a reference, follow the chain of references to return the absolute path to the real setting. If it is not a reference return None. """ pref = self.currentwidget.prefLookup(setn) if pref.isReference(): real = pref.getReference().resolve(pref) return real.path else: return None def Save(self, filename): """Save the state to a file.""" f = open(filename, 'w') self.document.saveToFile(f) def Set(self, var, val): """Set the value of a setting.""" pref = self.currentwidget.prefLookup(var) op = operations.OperationSettingSet(pref, val) self.document.applyOperation(op) if self.verbose: print ( "Set setting '%s' to %s" % (var, repr(pref.get())) ) def SetToReference(self, var, val): """Set setting to a reference value.""" pref = self.currentwidget.prefLookup(var) op = operations.OperationSettingSet(pref, setting.Reference(val)) self.document.applyOperation(op) if self.verbose: print ( "Set setting '%s' to %s" % (var, repr(pref.get())) ) def SetData(self, name, val, symerr=None, negerr=None, poserr=None): """Set dataset with name with values (and optionally errors).""" data = datasets.Dataset(val, symerr, negerr, poserr) op = operations.OperationDatasetSet(name, data) self.document.applyOperation(op) if self.verbose: print "Set dataset '%s':" % name print " Values = %s" % str( data.data ) print " Symmetric errors = %s" % str( data.serr ) print " Negative errors = %s" % str( data.nerr ) print " Positive errors = %s" % str( data.perr ) def SetDataDateTime(self, name, vals): """Set datetime dataset to be values given. vals is a list of python datetime objects """ v = [utils.datetimeToFloat(x) for x in vals] ds = datasets.DatasetDateTime(v) op = operations.OperationDatasetSet(name, ds) self.document.applyOperation(op) if self.verbose: print "Set dataset '%s':" % name print " Values = %s" % str(ds.data) def SetDataExpression(self, name, val, symerr=None, negerr=None, poserr=None, linked=False, parametric=None): """Create a dataset based on text expressions. Expressions are functions of existing datasets. If evaluating the expression 'y*10' in negerr, then the negerrs of dataset y are used, and so on. To access a specific part of the dataset y, the suffixes _data, _serr, _perr, and _nerr can be appended. If linked is True then the expressions are reevaluated if the document is modified parametric: tuple of (minval, maxval, numitems) for creating parametric datasets. t set to this range when evaluating expressions. """ expr = {'data': val, 'serr': symerr, 'nerr': negerr, 'perr': poserr} op = operations.OperationDatasetCreateExpression(name, expr, linked, parametric=parametric) data = self.document.applyOperation(op) if self.verbose: print "Set dataset '%s' based on expression:" % name print " Values = %s" % str( data.data ) print " Symmetric errors = %s" % str( data.serr ) print " Negative errors = %s" % str( data.nerr ) print " Positive errors = %s" % str( data.perr ) if parametric: print " Where t goes form %g:%g in %i steps" % parametric print " linked to expression = %s" % repr(linked) def SetDataRange(self, name, numsteps, val, symerr=None, negerr=None, poserr=None, linked=False): """Create dataset based on ranges of values, e.g. 1 to 10 in 10 steps name: name of dataset numsteps: number of steps to create val: range in form of tuple (minval, maxval) symerr, negerr & poserr: ranges for errors (optional) """ parts = {'data': val, 'serr': symerr, 'nerr': negerr, 'perr': poserr} op = operations.OperationDatasetCreateRange(name, numsteps, parts, linked) self.document.applyOperation(op) if self.verbose: print "Set dataset '%s' based on range:" % name print " Number of steps = %i" % numsteps print " Range of data = %s" % repr(val) print " Range of symmetric error = %s" % repr(symerr) print " Range of positive error = %s" % repr(poserr) print " Range of negative error = %s" % repr(negerr) def SetData2DExpression(self, name, expr, linked=False): """Create a 2D dataset based on expressions name is the new dataset name expr is an expression which should return a 2D array linked specifies whether to permanently link the dataset to the expressions """ op = operations.OperationDataset2DCreateExpression(name, expr, linked) data = self.document.applyOperation(op) if self.verbose: print "Set 2D dataset '%s' based on expressions" % name print " expression = %s" % repr(expr) print " linked to expression = %s" % repr(linked) print " Made a dataset (%i x %i)" % (data.data.shape[0], data.data.shape[1]) def SetData2DExpressionXYZ(self, name, xexpr, yexpr, zexpr, linked=False): """Create a 2D dataset based on expressions in x, y and z xexpr is an expression which expands to an equally-spaced grid of x coordinates yexpr expands to equally spaced y coordinates zexpr expands to z coordinates. linked specifies whether to permanently link the dataset to the expressions """ op = operations.OperationDataset2DCreateExpressionXYZ(name, xexpr, yexpr, zexpr, linked) data = self.document.applyOperation(op) if self.verbose: print "Set 2D dataset '%s' based on expressions" % name print " X expression = %s" % repr(xexpr) print " Y expression = %s" % repr(yexpr) print " Z expression = %s" % repr(zexpr) print " linked to expression = %s" % repr(linked) print " Made a dataset (%i x %i)" % (data.data.shape[0], data.data.shape[1]) def SetData2DXYFunc(self, name, xstep, ystep, expr, linked=False): """Create a 2D dataset based on expressions of a range of x and y xstep is a tuple(min, max, step) ystep is a tuple(min, max, step) expr is an expression of x and y linked specifies whether to permanently link the dataset to the expressions """ op = operations.OperationDataset2DXYFunc(name, xstep, ystep, expr, linked) data = self.document.applyOperation(op) if self.verbose: print "Set 2D dataset '%s' based on function of x and y" % name print " X steps = %s" % repr(xstep) print " Y steps = %s" % repr(ystep) print " Expression = %s" % repr(expr) print " linked to expression = %s" % repr(linked) print " Made a dataset (%i x %i)" % (data.data.shape[0], data.data.shape[1]) def SetData2D(self, name, data, xrange=None, yrange=None): """Create a 2D dataset.""" data = datasets.Dataset2D(data, xrange=xrange, yrange=yrange) op = operations.OperationDatasetSet(name, data) self.document.applyOperation(op) if self.verbose: print "Set 2d dataset '%s'" % name def SetDataText(self, name, val): """Create a text dataset.""" data = datasets.DatasetText(val) op = operations.OperationDatasetSet(name, data) self.document.applyOperation(op) if self.verbose: print "Set text dataset '%s'" % name print " Values = %s" % unicode(data.data) def GetData(self, name): """Return the data with the name. For a 1D dataset, returns a tuple (None if not defined) (data, serr, nerr, perr) For a 2D dataset, returns (data, xrange, yrange) For a text dataset, return a list of text For a date dataset, return a list of python datetime objects Return copies, so that the original data can't be indirectly modified """ d = self.document.getData(name) if d.displaytype == 'text': return d.data[:] elif d.displaytype == 'date': return [utils.floatToDateTime(x) for x in d.data] elif d.dimensions == 2: return (d.data.copy(), d.xrange, d.yrange) else: data = serr = nerr = perr = None if d.data is not None: data = d.data.copy() if d.serr is not None: serr = d.serr.copy() if d.nerr is not None: nerr = d.nerr.copy() if d.perr is not None: perr = d.perr.copy() return (data, serr, nerr, perr) def GetDataType(self, name): """Return the type of dataset. Returns None if no dataset. For a 1D dataset, returns '1d' For a 2D dataset, returns '2d' For a text dataset, returns 'text' For a datetime dataset, returns 'datetime' """ try: d = self.document.getData(name) except KeyError: return None if d.displaytype == 'text': return 'text' elif d.displaytype == 'date': return 'datetime' elif d.dimensions == 2: return '2d' else: return '1d' def ImportString(self, descriptor, dstring, useblocks=False): """Read data from the string using a descriptor. If useblocks is set, then blank lines or the word 'no' are used to split the data into blocks. Dataset names are appended with an underscore and the block number (starting from 1). Returned is a tuple (datasets, errors) where datasets is a list of datasets read errors is a dict of the datasets with the number of errors while converting the data """ params = importparams.ImportParamsSimple( descriptor=descriptor, datastr=dstring, useblocks=useblocks) op = operations.OperationDataImport(params) self.document.applyOperation(op) if self.verbose: print "Imported datasets %s" % (' '.join(op.outdatasets),) for name, num in op.outinvalids.iteritems(): print "%i errors encountered reading dataset %s" % (num, name) return (op.outdatasets, op.outinvalids) def ImportString2D(self, datasetnames, dstring, xrange=None, yrange=None, invertrows=None, invertcols=None, transpose=None): """Read two dimensional data from the string specified. datasetnames is a list of datasets to read from the string or a single dataset name xrange is a tuple containing the range of data in x coordinates yrange is a tuple containing the range of data in y coordinates if invertrows=True, then rows are inverted when read if invertcols=True, then cols are inverted when read if transpose=True, then rows and columns are swapped """ if type(datasetnames) in (str, unicode): datasetnames = [datasetnames] params = importparams.ImportParams2D( datasetnames=datasetnames, datastr=dstring, xrange=xrange, yrange=yrange, invertrows=invertrows, invertcols=invertcols, transpose=transpose) op = operations.OperationDataImport2D(params) self.document.applyOperation(op) if self.verbose: print "Imported datasets %s" % (', '.join(datasetnames)) def ImportFile2D(self, filename, datasetnames, xrange=None, yrange=None, invertrows=None, invertcols=None, transpose=None, prefix="", suffix="", encoding='utf_8', linked=False): """Import two-dimensional data from a file. filename is the name of the file to read datasetnames is a list of datasets to read from the file, or a single dataset name xrange is a tuple containing the range of data in x coordinates yrange is a tuple containing the range of data in y coordinates if invertrows=True, then rows are inverted when read if invertcols=True, then cols are inverted when read if transpose=True, then rows and columns are swapped prefix and suffix are prepended and appended to dataset names encoding is encoding character set if linked=True then the dataset is linked to the file """ # look up filename on path realfilename = self.findFileOnImportPath(filename) if type(datasetnames) in (str, unicode): datasetnames = [datasetnames] params = importparams.ImportParams2D( datasetnames=datasetnames, filename=realfilename, xrange=xrange, yrange=yrange, invertrows=invertrows, invertcols=invertcols, transpose=transpose, prefix=prefix, suffix=suffix, linked=linked) op = operations.OperationDataImport2D(params) self.document.applyOperation(op) if self.verbose: print "Imported datasets %s" % (', '.join(datasetnames)) def ImportFile(self, filename, descriptor, useblocks=False, linked=False, prefix='', suffix='', ignoretext=False, encoding='utf_8'): """Read data from file with filename using descriptor. If linked is True, the data won't be saved in a saved document, the data will be reread from the file. If useblocks is set, then blank lines or the word 'no' are used to split the data into blocks. Dataset names are appended with an underscore and the block number (starting from 1). If prefix is set, prefix is prepended to each dataset name Suffix is added to each dataset name ignoretext ignores lines of text in the file Returned is a tuple (datasets, errors) where datasets is a list of datasets read errors is a dict of the datasets with the number of errors while converting the data """ realfilename = self.findFileOnImportPath(filename) params = importparams.ImportParamsSimple( descriptor=descriptor, filename=realfilename, useblocks=useblocks, linked=linked, prefix=prefix, suffix=suffix, ignoretext=ignoretext) op = operations.OperationDataImport(params) self.document.applyOperation(op) if self.verbose: print "Imported datasets %s" % (' '.join(op.outdatasets),) for name, num in op.outinvalids.iteritems(): print "%i errors encountered reading dataset %s" % (num, name) return (op.outdatasets, op.outinvalids) def ImportFileCSV(self, filename, readrows=False, delimiter=',', textdelimiter='"', encoding='utf_8', headerignore=0, rowsignore=0, blanksaredata=False, numericlocale='en_US', dateformat='YYYY-MM-DD|T|hh:mm:ss', headermode='multi', dsprefix='', dssuffix='', prefix=None, linked=False): """Read data from a comma separated file (CSV). Data are read from filename readrows: if true, data are read across rather than down delimiter: character for delimiting data (usually ',') textdelimiter: character surrounding text (usually '"') encoding: encoding used in file headerignore: number of lines to ignore after header text rowsignore: number of rows to ignore at top of file blanksaredata: treats blank lines in csv files as blank data values numericlocale: format to use for reading numbers dateformat: format for interpreting dates headermode: 'multi': multiple headers allowed in file '1st': first text found are headers 'none': no headers, guess data and use default names Dataset names are prepended and appended, by dsprefix and dssuffix, respectively (prefix is backware compatibility only, it adds an underscore relative to dsprefix) If linked is True the data are linked with the file.""" # backward compatibility if prefix: dsprefix = prefix + '_' # lookup filename realfilename = self.findFileOnImportPath(filename) params = importparams.ImportParamsCSV( filename=realfilename, readrows=readrows, delimiter=delimiter, textdelimiter=textdelimiter, encoding=encoding, headerignore=headerignore, rowsignore=rowsignore, blanksaredata=blanksaredata, numericlocale=numericlocale, dateformat=dateformat, headermode=headermode, prefix=dsprefix, suffix=dssuffix, linked=linked, ) op = operations.OperationDataImportCSV(params) self.document.applyOperation(op) if self.verbose: print "Imported datasets %s" % (' '.join(op.outdatasets),) return op.outdatasets def ImportFITSFile(self, dsname, filename, hdu, datacol = None, symerrcol = None, poserrcol = None, negerrcol = None, linked = False): """Import data from a FITS file dsname is the name of the dataset filename is name of the fits file to open hdu is the number/name of the hdu to access if the hdu is a table, datacol, symerrcol, poserrcol and negerrcol specify the columns containing the data, symmetric error, positive and negative errors. linked specfies that the dataset is linked to the file """ # lookup filename realfilename = self.findFileOnImportPath(filename) params = importparams.ImportParamsFITS( dsname=dsname, filename=realfilename, hdu=hdu, datacol=datacol, symerrcol=symerrcol, poserrcol=poserrcol, negerrcol=negerrcol, linked=linked) op = operations.OperationDataImportFITS(params) self.document.applyOperation(op) def ImportFilePlugin(self, plugin, filename, **args): """Import file using a plugin. optional arguments: prefix: add to start of dataset name (default '') suffix: add to end of dataset name (default '') linked: link import to file (default False) encoding: file encoding (may not be used, default 'utf_8') plus arguments to plugin returns: list of imported datasets, list of imported customs """ realfilename = self.findFileOnImportPath(filename) params = importparams.ImportParamsPlugin( plugin=plugin, filename=realfilename, **args) op = operations.OperationDataImportPlugin(params) try: self.document.applyOperation(op) except: self.document.log("Error in plugin %s" % plugin) exc = ''.join(traceback.format_exc()) self.document.log(exc) return op.outdatasets, op.outcustoms def ReloadData(self): """Reload any linked datasets. Returned is a tuple (datasets, errors) where datasets is a list of datasets read errors is a dict of the datasets with the number of errors while converting the data """ return self.document.reloadLinkedDatasets() def Action(self, action, widget='.'): """Performs action on current widget.""" w = self.document.resolve(self.currentwidget, widget) # run action w.getAction(action).function() def Print(self): """Print document.""" p = qt4.QPrinter() if p.setup(): p.newPage() self.document.printTo( p, range(self.document.getNumberPages()) ) def Export(self, filename, color=True, page=0, dpi=100, antialias=True, quality=85, backcolor='#ffffff00', pdfdpi=150, svgtextastext=False): """Export plot to filename. color is True or False if color is requested in output file page is the pagenumber to export dpi is the number of dots per inch for bitmap output files antialias antialiases output if True quality is a quality parameter for jpeg output backcolor is the background color for bitmap files, which is a name or a #RRGGBBAA value (red, green, blue, alpha) pdfdpi is the dpi to use when exporting eps or pdf files svgtextastext: write text in SVG as text, rather than curves """ e = export.Export(self.document, filename, page, color=color, bitmapdpi=dpi, antialias=antialias, quality=quality, backcolor=backcolor, pdfdpi=pdfdpi, svgtextastext=svgtextastext) e.export() def Rename(self, widget, newname): """Rename the widget with the path given to the new name. eg Rename('graph1/xy1', 'scatter') This function does not move widgets.""" w = self.document.resolve(self.currentwidget, widget) op = operations.OperationWidgetRename(w, newname) self.document.applyOperation(op) def NodeType(self, path): """This function treats the set of objects in the widget and setting tree as a set of nodes. Returns type of node given. Return values are: 'widget', 'settings' or 'setting' """ item = self.document.resolveItem(self.currentwidget, path) if hasattr(item, 'isWidget') and item.isWidget(): return 'widget' elif isinstance(item, setting.Settings): return 'settinggroup' else: return 'setting' def NodeChildren(self, path, types='all'): """This function treats the set of objects in the widget and setting tree as a set of nodes. Returns a list of the names of the children of this node.""" item = self.document.resolveItem(self.currentwidget, path) out = [] if hasattr(item, 'isWidget') and item.isWidget(): if types == 'all' or types == 'widget': out += item.childnames if types == 'all' or types == 'settinggroup': out += [s.name for s in item.settings.getSettingsList()] if types == 'all' or types == 'setting': out += [s.name for s in item.settings.getSettingList()] elif isinstance(item, setting.Settings): if types == 'all' or types == 'settinggroup': out += [s.name for s in item.getSettingsList()] if types == 'all' or types == 'setting': out += [s.name for s in item.getSettingList()] return out def WidgetType(self, path): """Get the Veusz widget type for a widget with path given. Raises a ValueError if the path doesn't point to a widget.""" item = self.document.resolveItem(self.currentwidget, path) if hasattr(item, 'isWidget') and item.isWidget(): return item.typename else: raise ValueError, "Path '%s' is not a widget" % path def SettingType(self, path): """Get the type of setting (a string) for the path given. Raise a ValueError if path is not a setting """ setn = self.currentwidget.prefLookup(path) return setn.typename def TagDatasets(self, tag, datasets): """Apply tag to list of datasets.""" op = operations.OperationDataTag(tag, datasets) self.document.applyOperation(op) if self.verbose: print "Applied tag %s to datasets %s" % ( tag, ' '.join(datasets)) veusz-1.15/document/emf_export.py0000644002344000001440000003456311734662204017152 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A paint engine to produce EMF exports. Requires: PyQt-x11-gpl-4.6-snapshot-20090906.tar.gz sip-4.9-snapshot-20090906.tar.gz pyemf """ import sys import struct import pyemf import veusz.qtall as qt4 inch_mm = 25.4 scale = 100 def isStockObject(obj): """Is this a stock windows object.""" return (obj & 0x80000000) != 0 class _EXTCREATEPEN(pyemf._EMR._EXTCREATEPEN): """Extended pen creation record with custom line style.""" emr_typedef = [ ('i','handle',0), ('i','offBmi',0), ('i','cbBmi',0), ('i','offBits',0), ('i','cbBits',0), ('i','style'), ('i','penwidth'), ('i','brushstyle'), ('i','color'), ('i','brushhatch',0), ('i','numstyleentries')] def __init__(self, style=pyemf.PS_SOLID, width=1, color=0, styleentries=[]): """Create pen. styleentries is a list of dash and space lengths.""" pyemf._EMR._EXTCREATEPEN.__init__(self) self.style = style self.penwidth = width self.color = pyemf._normalizeColor(color) self.brushstyle = 0x0 # solid if style & pyemf.PS_USERSTYLE == 0: self.styleentries = [] else: self.styleentries = styleentries self.numstyleentries = len(self.styleentries) def sizeExtra(self): return struct.calcsize("i")*len(self.styleentries) def serializeExtra(self, fh): self.serializeList(fh, "i", self.styleentries) def hasHandle(self): return True class EMFPaintEngine(qt4.QPaintEngine): """Custom EMF paint engine.""" def __init__(self, width_in, height_in, dpi=75): qt4.QPaintEngine.__init__(self, qt4.QPaintEngine.Antialiasing | qt4.QPaintEngine.PainterPaths | qt4.QPaintEngine.PrimitiveTransform | qt4.QPaintEngine.PaintOutsidePaintEvent | qt4.QPaintEngine.PatternBrush ) self.width = width_in self.height = height_in self.dpi = dpi def begin(self, paintdevice): self.emf = pyemf.EMF(self.width, self.height, self.dpi*scale) self.pen = self.emf.GetStockObject(pyemf.BLACK_PEN) self.pencolor = (0, 0, 0) self.brush = self.emf.GetStockObject(pyemf.NULL_BRUSH) self.paintdevice = paintdevice return True def drawLines(self, lines): """Draw lines to emf output.""" for line in lines: self.emf.Polyline( [(line.x1()*scale, line.y1()*scale), (line.x2()*scale, line.y2()*scale)] ) def drawPolygon(self, points, mode): """Draw polygon on output.""" # print "Polygon" pts = [(p.x()*scale, p.y()*scale) for p in points] if mode == qt4.QPaintEngine.PolylineMode: self.emf.Polyline(pts) else: self.emf.SetPolyFillMode( {qt4.QPaintEngine.WindingMode: pyemf.WINDING, qt4.QPaintEngine.OddEvenMode: pyemf.ALTERNATE, qt4.QPaintEngine.ConvexMode: pyemf.WINDING} ) self.emf.Polygon(pts) def drawEllipse(self, rect): """Draw an ellipse.""" # print "ellipse" args = (rect.left()*scale, rect.top()*scale, rect.right()*scale, rect.bottom()*scale, rect.left()*scale, rect.top()*scale, rect.left()*scale, rect.top()*scale) self.emf.Pie(*args) self.emf.Arc(*args) def drawPoints(self, points): """Draw points.""" # print "points" for pt in points: x, y = (pt.x()-0.5)*scale, (pt.y()-0.5)*scale self.emf.Pie( x, y, (pt.x()+0.5)*scale, (pt.y()+0.5)*scale, x, y, x, y ) def drawPixmap(self, r, pixmap, sr): """Draw pixmap to display.""" # convert pixmap to BMP format bytes = qt4.QByteArray() buffer = qt4.QBuffer(bytes) buffer.open(qt4.QIODevice.WriteOnly) pixmap.save(buffer, "BMP") # chop off bmp header to get DIB bmp = str(buffer.data()) dib = bmp[0xe:] hdrsize, = struct.unpack(' # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import numpy as N from datasets import Dataset, simpleEvalExpression class DatasetHistoGenerator(object): def __init__(self, document, inexpr, binmanual = None, binparams = None, method = 'counts', cumulative = 'none', errors=False): """ inexpr = ds expression binmanual = None / [1,2,3,4,5] binparams = None / (num, minval, maxval, islog) method = ('counts', 'density', or 'fractions') cumulative = ('none', 'smalltolarge', 'largetosmall') errors = True/False """ self.changeset = -1 self.document = document self.inexpr = inexpr self.binmanual = binmanual self.binparams = binparams self.method = method self.cumulative = cumulative self.errors = errors def getData(self): """Get data from input expression, caching result.""" if self.document.changeset != self.changeset: self._cacheddata = simpleEvalExpression(self.document, self.inexpr) self.changeset = self.document.changeset return self._cacheddata def binLocations(self): """Compute locations of bins edges, giving N+1 items.""" if self.binmanual: return N.array(self.binmanual) else: numbins, minval, maxval, islog = self.binparams if minval == 'Auto' or maxval == 'Auto': data = self.getData() if len(data) == 0: return N.array([]) if minval == 'Auto': minval = N.nanmin(data) if maxval == 'Auto': maxval = N.nanmax(data) if not islog: delta = (maxval - minval) / numbins return N.arange(numbins+1)*delta + minval else: if minval <= 0: minval = 1e-99 if maxval <= 0: maxval = 1e99 lmin, lmax = N.log(minval), N.log(maxval) delta = (lmax - lmin) / numbins return N.exp( N.arange(numbins+1)*delta + lmin ) def getBinLocations(self): """Return bin centre, -ve bin width, +ve bin width.""" if len(self.getData()) == 0: return (N.array([]), None, None) binlocs = self.binLocations() if self.binparams and self.binparams[3]: # log bins lbin = N.log(binlocs) data = N.exp( 0.5*(lbin[:-1] + lbin[1:]) ) else: # otherwise linear bins data = 0.5*(binlocs[:-1] + binlocs[1:]) # error bars nerr = binlocs[:-1] - data perr = binlocs[1:] - data return data, nerr, perr def getErrors(self, data, binlocs): """Compute error bars if requried.""" hist, edges = N.histogram(data, bins=binlocs) # calculate scaling values for error bars if self.method == 'density': ratio = 1. / (hist.size*(edges[1]-edges[0])) elif self.method == 'fractions': ratio = 1. / data.size else: ratio = 1. # compute cumulative values (errors correlated) if self.cumulative == 'smalltolarge': hist = N.cumsum(hist) elif self.cumulative == 'largetosmall': hist = N.cumsum(hist[::-1])[::-1] # Gehrels 1986 ApJ 303 336 perr = 1. + N.sqrt(hist + 0.75) nerr = N.where(hist > 0, N.sqrt(hist - 0.25), 0.) return -nerr*ratio, perr*ratio def getBinVals(self): """Return results for each bin.""" data = self.getData() if len(data) == 0: return (N.array([]), None, None) normed = self.method == 'density' binlocs = self.binLocations() hist, edges = N.histogram(data, bins=binlocs, normed=normed) if self.method == 'fractions': hist = hist * (1./data.size) # if cumulative wanted if self.cumulative == 'smalltolarge': hist = N.cumsum(hist) elif self.cumulative == 'largetosmall': hist = N.cumsum(hist[::-1])[::-1] if self.errors: nerr, perr = self.getErrors(data, binlocs) else: nerr, perr = None, None return hist, nerr, perr def getBinDataset(self): self.bindataset = DatasetHistoBins(self, self.document) return self.bindataset def getValueDataset(self): self.valuedataset = DatasetHistoValues(self, self.document) return self.valuedataset def saveToFile(self, fileobj): """Save two datasets to file.""" try: binname = self.bindataset.name() except (ValueError, AttributeError): binname = '' try: valname = self.valuedataset.name() except (ValueError, AttributeError): valname = '' fileobj.write( ("CreateHistogram(%s, %s, %s, binparams=%s, " "binmanual=%s, method=%s, " "cumulative=%s, errors=%s)\n") % (repr(self.inexpr), repr(binname), repr(valname), repr(self.binparams), repr(self.binmanual), repr(self.method), repr(self.cumulative), repr(self.errors)) ) def linkedInformation(self): """Informating about linking.""" if self.binmanual: bins = 'manual bins' else: bins = '%i bins from %s to %s' % (self.binparams[0], self.binparams[1], self.binparams[2]) return "Histogram of '%s' with %s" % (self.inexpr, bins) class DatasetHistoBins(Dataset): """A dataset for getting the bin positions for the histogram.""" def __init__(self, generator, document): self.generator = generator self.document = document self.linked = None self._invalidpoints = None self.changeset = -1 def getData(self): """Get bin positions, caching results.""" if self.changeset != self.generator.document.changeset: self.datacache = self.generator.getBinLocations() self.changeset = self.generator.document.changeset return self.datacache def saveToFile(self, fileobj, name): """Save dataset (counterpart does this).""" pass def linkedInformation(self): """Informating about linking.""" return self.generator.linkedInformation() + " (bin positions)" data = property(lambda self: self.getData()[0]) nerr = property(lambda self: self.getData()[1]) perr = property(lambda self: self.getData()[2]) serr = None class DatasetHistoValues(Dataset): """A dataset for getting the height of the bins in a histogram.""" def __init__(self, generator, document): self.generator = generator self.document = document self.linked = None self._invalidpoints = None self.changeset = -1 def getData(self): """Get bin heights, caching results.""" if self.changeset != self.generator.document.changeset: self.datacache = self.generator.getBinVals() self.changeset = self.generator.document.changeset return self.datacache def saveToFile(self, fileobj, name): """Save dataset and its counterpart to a file.""" self.generator.saveToFile(fileobj) def linkedInformation(self): """Informating about linking.""" return self.generator.linkedInformation() + " (bin values)" data = property(lambda self: self.getData()[0]) nerr = property(lambda self: self.getData()[1]) perr = property(lambda self: self.getData()[2]) serr = None class OperationDatasetHistogram(object): """Operation to make histogram from data.""" descr = 'make histogram' def __init__(self, expr, outposns, outvalues, binparams=None, binmanual=None, method='counts', cumulative = 'none', errors=False): """ inexpr = input dataset expression outposns = name of dataset for bin positions outvalues = name of dataset for bin values binparams = None / (num, minval, maxval, islog) binmanual = None / [1,2,3,4,5] method = ('counts', 'density', or 'fractions') cumulative = ('none', 'smalltolarge', 'largetosmall') errors = True/False """ self.expr = expr self.outposns = outposns self.outvalues = outvalues self.binparams = binparams self.binmanual = binmanual self.method = method self.cumulative = cumulative self.errors = errors def do(self, document): """Create histogram datasets.""" gen = DatasetHistoGenerator( document, self.expr, binparams=self.binparams, binmanual=self.binmanual, method=self.method, cumulative=self.cumulative, errors=self.errors) if self.outvalues != '': self.oldvaluesds = document.data.get(self.outvalues, None) document.setData(self.outvalues, gen.getValueDataset()) if self.outposns != '': self.oldposnsds = document.data.get(self.outposns, None) document.setData(self.outposns, gen.getBinDataset()) def undo(self, document): """Undo creation of datasets.""" if self.oldposnsds is not None: if self.outposns != '': document.setData(self.outposns, self.oldposnsds) else: document.deleteData(self.outposns) if self.oldvaluesds is not None: if self.outvalues != '': document.setData(self.outvalues, self.oldvaluesds) else: document.deleteData(self.outvalues) veusz-1.15/document/painthelper.py0000644002344000001440000002025111734662204017302 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Helper for doing the plotting of the document. """ import veusz.qtall as qt4 import veusz.setting as setting import veusz.utils as utils try: from veusz.helpers.qtloops import RecordPaintDevice except ImportError: # fallback to this if we don't get the native recorded def RecordPaintDevice(width, height, dpix, dpiy): return qt4.QPicture() class DrawState(object): """Each widget plotted has a recorded state in this object.""" def __init__(self, widget, bounds, clip, helper): """Initialise state for widget. bounds: tuple of (x1, y1, x2, y2) clip: if clipping should be done, another tuple.""" self.widget = widget self.record = RecordPaintDevice( helper.pagesize[0], helper.pagesize[1], helper.dpi[0], helper.dpi[1]) self.bounds = bounds self.clip = clip # controlgraphs belonging to widget self.cgis = [] # list of child widgets states self.children = [] class PaintHelper(object): """Helper used when painting widgets. Provides a QPainter to each widget for plotting. Records the controlgraphs for each widget. Holds the scaling, dpi and size of the page. """ def __init__(self, pagesize, scaling=1., dpi=(100, 100), directpaint=None): """Initialise using page size (tuple of pixelw, pixelh). If directpaint is set to a painter, use this directly rather than creating separate layers for rendering later. The user will need to call restore() on the painter before ending, if using this mode, however. """ self.dpi = dpi self.scaling = scaling self.pixperpt = self.dpi[1] / 72. self.pagesize = pagesize # keep track of states of all widgets self.states = {} # axis to plotter mappings self.axisplottermap = {} # whether to directly render to a painter or make new layers self.directpaint = directpaint self.directpainting = False # state for root widget self.rootstate = None @property def maxsize(self): """Return maximum page dimension (using PaintHelper's DPI).""" return max(*self.pagesize) def sizeAtDpi(self, dpi): """Return a tuple size for the page given an output device dpi.""" return ( int(self.pagesize[0]/self.dpi[0] * dpi), int(self.pagesize[1]/self.dpi[1] * dpi) ) def updatePageSize(self, pagew, pageh): """Update page size to value given (in user text units.""" self.pagesize = ( setting.Distance.convertDistance(self, pagew), setting.Distance.convertDistance(self, pageh) ) def painter(self, widget, bounds, clip=None): """Return a painter for use when drawing the widget. widget: widget object bounds: tuple (x1, y1, x2, y2) of widget bounds clip: another tuple, if set clips drawing to this rectangle """ s = self.states[widget] = DrawState(widget, bounds, clip, self) if widget.parent is None: self.rootstate = s else: self.states[widget.parent].children.append(s) if self.directpaint: # only paint to one output painter p = self.directpaint if self.directpainting: p.restore() self.directpainting = True p.save() else: # save to multiple recorded layers p = qt4.QPainter(s.record) p.scaling = self.scaling p.pixperpt = self.pixperpt p.pagesize = self.pagesize p.maxsize = max(*self.pagesize) p.dpi = self.dpi[1] if clip: p.setClipRect(clip) return p def setControlGraph(self, widget, cgis): """Records the control graph list for the widget given.""" self.states[widget].cgis = cgis def renderToPainter(self, painter): """Render saved output to painter. """ self._renderState(self.rootstate, painter) def _renderState(self, state, painter): """Render state to painter.""" painter.save() state.record.play(painter) painter.restore() for child in state.children: self._renderState(child, painter) def identifyWidgetAtPoint(self, x, y, antialias=True): """What widget has drawn at the point x,y? Returns the widget drawn last on the point, or None if it is an empty part of the page. root is the root widget to recurse from if antialias is true, do test for antialiased drawing """ # make a small image filled with a specific color box = 3 specialcolor = qt4.QColor(254, 255, 254) origpix = qt4.QPixmap(2*box+1, 2*box+1) origpix.fill(specialcolor) origimg = origpix.toImage() # store most recent widget here lastwidget = [None] def rendernextstate(state): """Recursively draw painter. Checks whether drawing a widgetchanges the small image around the point given. """ pixmap = qt4.QPixmap(origpix) painter = qt4.QPainter(pixmap) painter.setRenderHint(qt4.QPainter.Antialiasing, antialias) painter.setRenderHint(qt4.QPainter.TextAntialiasing, antialias) # this makes the small image draw from x-box->x+box, y-box->y+box # translate would get overriden by coordinate system playback painter.setWindow(x-box,y-box,box*2+1,box*2+1) state.record.play(painter) painter.end() newimg = pixmap.toImage() if newimg != origimg: lastwidget[0] = state.widget for child in state.children: rendernextstate(child) rendernextstate(self.rootstate) return lastwidget[0] def pointInWidgetBounds(self, x, y, widgettype): """Which graph widget plots at point x,y? Recurse from widget root widgettype is the class of widget to get """ widget = [None] def recursestate(state): if isinstance(state.widget, widgettype): b = state.bounds if x >= b[0] and y >= b[1] and x <= b[2] and y <= b[3]: # most recent widget drawing on point widget[0] = state.widget for child in state.children: recursestate(child) recursestate(self.rootstate) return widget[0] def widgetBounds(self, widget): """Return bounds of widget.""" return self.states[widget].bounds def widgetBoundsIterator(self, widgettype=None): """Returns bounds for each widget. Set widgettype to be a widget type to filter returns Yields (widget, bounds) """ # this is a recursive algorithm turned into an iterative one # which makes creation of a generator easier stack = [self.rootstate] while stack: state = stack[0] if widgettype is None or isinstance(state.widget, widgettype): yield state.widget, state.bounds # remove the widget itself from the stack and insert children stack = state.children + stack[1:] veusz-1.15/document/doc.py0000644002344000001440000006642311734662204015547 0ustar jssusers00000000000000# document.py # A module to handle documents # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A class to represent Veusz documents, with dataset classes.""" import os.path import re import traceback import datetime from collections import defaultdict import numpy as N import veusz.qtall as qt4 import widgetfactory import datasets import painthelper import veusz.utils as utils import veusz.setting as setting # python identifier identifier_re = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$') # for splitting identifier_split_re = re.compile(r'[A-Za-z_][A-Za-z0-9_]*') # python module module_re = re.compile(r'^[A-Za-z_\.]+$') # function(arg1, arg2...) for custom functions # not quite correct as doesn't check for commas in correct places function_re = re.compile(r''' ^([A-Za-z_][A-Za-z0-9_]*)[ ]* # identifier \(( # begin args (?: [ ]* ,? [ ]* [A-Za-z_][A-Za-z0-9_]* )* # args )\)$ # endargs''', re.VERBOSE) def getSuitableParent(widgettype, initialwidget): """Find the nearest relevant parent for the widgettype given.""" # find the parent to add the child to, we go up the tree looking # for possible parents parent = initialwidget wc = widgetfactory.thefactory.getWidgetClass(widgettype) while parent is not None and not wc.willAllowParent(parent): parent = parent.parent return parent class Document( qt4.QObject ): """Document class for holding the graph data. Emits: sigModified when the document has been modified sigWiped when document is wiped """ pluginsloaded = False def __init__(self): """Initialise the document.""" qt4.QObject.__init__( self ) if not Document.pluginsloaded: Document.loadPlugins() Document.pluginsloaded = True # change tracking of document as a whole self.changeset = 0 # increased when the document changes # change tracking of datasets self.datachangeset = 0 # increased whan any dataset changes self.datachangesets = dict() # each ds has an associated change set # map tags to dataset names self.datasettags = defaultdict(list) # if set, do not notify listeners of updates # wait under enableUpdates self.suspendupdates = [] # default document locale self.locale = qt4.QLocale() self.clearHistory() self.wipe() # directories to examine when importing self.importpath = [] # store custom functions and constants # consists of tuples of (name, type, value) # type is constant or function # we use this format to preserve evaluation order self.customs = [] self.updateEvalContext() # copy default colormaps self.colormaps = dict(utils.defaultcolormaps) def wipe(self): """Wipe out any stored data.""" self.data = {} self.basewidget = widgetfactory.thefactory.makeWidget( 'document', None, None) self.basewidget.document = self self.setModified(False) self.emit( qt4.SIGNAL("sigWiped") ) def clearHistory(self): """Clear any history.""" self.historybatch = [] self.historyundo = [] self.historyredo = [] def suspendUpdates(self): """Holds sending update messages. This speeds up modification of the document and prevents the document from being updated on the screen.""" self.suspendupdates.append(self.changeset) def enableUpdates(self): """Reenables document updates.""" changeset = self.suspendupdates.pop() if len(self.suspendupdates) == 0 and changeset != self.changeset: # bump this up as some watchers might ignore this otherwise self.changeset += 1 self.setModified() def makeDefaultDoc(self): """Add default widgets to create document.""" page = widgetfactory.thefactory.makeWidget('page', self.basewidget) graph = widgetfactory.thefactory.makeWidget('graph', page) self.setModified() self.setModified(False) self.changeset = 0 def log(self, message): """Log a message - this is emitted as a signal.""" self.emit( qt4.SIGNAL("sigLog"), message ) def applyOperation(self, operation): """Apply operation to the document. Operations represent atomic actions which can be done to the document and undone. Updates are suspended during the operation. """ self.suspendUpdates() try: retn = operation.do(self) self.changeset += 1 except: self.enableUpdates() raise self.enableUpdates() if self.historybatch: # in batch mode, create an OperationMultiple for all changes self.historybatch[-1].addOperation(operation) else: # standard mode self.historyundo = self.historyundo[-9:] + [operation] self.historyredo = [] return retn def batchHistory(self, batch): """Enable/disable batch history mode. In this mode further operations are added to the OperationMultiple specified, until batchHistory is called with None. The objects are pushed into a list and popped off This allows multiple operations to be batched up for simple undo. """ if batch: self.historybatch.append(batch) else: self.historybatch.pop() def undoOperation(self): """Undo the previous operation.""" operation = self.historyundo.pop() self.suspendUpdates() try: operation.undo(self) self.changeset += 1 except: self.enableUpdates() raise self.enableUpdates() self.historyredo.append(operation) def canUndo(self): """Returns True if previous operation can be removed.""" return len(self.historyundo) != 0 def redoOperation(self): """Redo undone operations.""" operation = self.historyredo.pop() return self.applyOperation(operation) def canRedo(self): """Returns True if previous operation can be redone.""" return len(self.historyredo) != 0 def resolveFullWidgetPath(self, path): """Translate the widget path given into the widget.""" widget = self.basewidget for p in [i for i in path.split('/') if i != '']: for child in widget.children: if p == child.name: widget = child break else: # break wasn't called assert False return widget def resolveFullSettingPath(self, path): """Translate setting path into setting object.""" # find appropriate widget widget = self.basewidget parts = [i for i in path.split('/') if i != ''] while len(parts) > 0: for child in widget.children: if parts[0] == child.name: widget = child del parts[0] break else: # no child with name break # get Setting object s = widget.settings while isinstance(s, setting.Settings) and parts[0] in s.setdict: s = s.get(parts[0]) del parts[0] assert isinstance(s, setting.Setting) return s def isBlank(self): """Does the document contain widgets and no data""" return self.changeset == 0 def setData(self, name, dataset): """Set data to val, with symmetric or negative and positive errors.""" self.data[name] = dataset dataset.document = self # update the change tracking cs = self.datachangesets.get(name, 0) self.datachangesets[name] = cs + 1 self.datachangeset += 1 self.setModified() def deleteData(self, name): """Remove a dataset""" if name in self.data: del self.data[name] # don't remove the changeset tracker, in case this action is later undone self.datachangesets[name] += 1 self.datachangeset += 1 self.setModified() def modifiedData(self, dataset): """The named dataset was modified""" for name, ds in self.data.iteritems(): if ds is dataset: self.datachangesets[name] += 1 self.datachangeset += 1 self.setModified() def getLinkedFiles(self, filenames=None): """Get a list of LinkedFile objects used by the document. if filenames is a set, only get the objects with filenames given """ links = set() for ds in self.data.itervalues(): if ds.linked and (filenames is None or ds.linked.filename in filenames): links.add(ds.linked) return list(links) def reloadLinkedDatasets(self, filenames=None): """Reload linked datasets from their files. If filenames is a set(), only reload from these filenames Returns a tuple of - List of datasets read - Dict of tuples containing dataset names and number of errors """ links = self.getLinkedFiles(filenames=filenames) read = [] errors = {} # load in the files, merging the vars read and errors if links: for lf in links: nread, nerrors = lf.reloadLinks(self) read += nread errors.update(nerrors) self.setModified() read.sort() return (read, errors) def datasetName(self, dataset): """Find name for given dataset, raising ValueError if missing.""" for name, ds in self.data.iteritems(): if ds is dataset: return name raise ValueError, "Cannot find dataset" def deleteDataset(self, name): """Remove the selected dataset.""" del self.data[name] self.setModified() def renameDataset(self, oldname, newname): """Rename the dataset.""" d = self.data[oldname] del self.data[oldname] self.data[newname] = d # transfer change set to new name self.datachangesets[newname] = self.datachangesets[oldname] self.setModified() def getData(self, name): """Get data with name""" return self.data[name] def hasData(self, name): """Whether dataset is defined.""" return name in self.data def setModified(self, ismodified=True): """Set the modified flag on the data, and inform views.""" # useful for tracking back modifications # import traceback # traceback.print_stack() self.modified = ismodified self.changeset += 1 if len(self.suspendupdates) == 0: self.emit( qt4.SIGNAL("sigModified"), ismodified ) def isModified(self): """Return whether modified flag set.""" return self.modified @classmethod def loadPlugins(kls, pluginlist=None): """Load plugins and catch exceptions.""" if pluginlist is None: pluginlist = setting.settingdb.get('plugins', []) for plugin in pluginlist: try: execfile(plugin, dict()) except Exception, ex: err = ('Error loading plugin ' + plugin + '\n\n' + traceback.format_exc()) qt4.QMessageBox.critical(None, "Error loading plugin", err) def printTo(self, printer, pages, scaling = 1., dpi = None, antialias = False): """Print onto printing device.""" dpi = (printer.logicalDpiX(), printer.logicalDpiY()) painter = qt4.QPainter(printer) if antialias: painter.setRenderHint(qt4.QPainter.Antialiasing, True) painter.setRenderHint(qt4.QPainter.TextAntialiasing, True) # This all assumes that only pages can go into the root widget num = len(pages) for count, page in enumerate(pages): size = self.pageSize(page, dpi=dpi) helper = painthelper.PaintHelper(size, dpi=dpi, directpaint=painter) self.paintTo(helper, page) painter.restore() # start new pages between each page if count < num-1: printer.newPage() painter.end() def paintTo(self, painthelper, page): """Paint page specified to the paint helper.""" self.basewidget.draw(painthelper, page) def getNumberPages(self): """Return the number of pages in the document.""" return len(self.basewidget.children) def getPage(self, pagenumber): """Return widget for page.""" return self.basewidget.children[pagenumber] def datasetTags(self): """Get list of all tags in datasets.""" tags = set() for dataset in self.data.itervalues(): tags.update(dataset.tags) return list(sorted(tags)) def _writeFileHeader(self, fileobj, type): """Write a header to a saved file of type.""" fileobj.write('# Veusz %s (version %s)\n' % (type, utils.version())) fileobj.write('# Saved at %s\n\n' % datetime.datetime.utcnow().isoformat()) def saveCustomDefinitions(self, fileobj): """Save custom constants and functions.""" for vals in self.customs: fileobj.write('AddCustom(%s, %s, %s)\n' % tuple([repr(x) for x in vals])) def saveDatasetTags(self, fileobj): """Write dataset tags to output file""" # get a list of all tags and which datasets have them bytag = defaultdict(list) for name, dataset in sorted(self.data.iteritems()): for t in dataset.tags: bytag[t].append(name) # write out tags for tag, val in sorted(bytag.iteritems()): fileobj.write('TagDatasets(%s, %s)\n' % (repr(tag), repr(val))) def saveCustomFile(self, fileobj): """Export the custom settings to a file.""" self._writeFileHeader(fileobj, 'custom definitions') self.saveCustomDefinitions(fileobj) def saveToFile(self, fileobj): """Save the text representing a document to a file.""" self._writeFileHeader(fileobj, 'saved document') # add file directory to import path if we know it reldirname = None if getattr(fileobj, 'name', False): reldirname = os.path.dirname( os.path.abspath(fileobj.name) ) fileobj.write('AddImportPath(%s)\n' % repr(reldirname)) # add custom definitions self.saveCustomDefinitions(fileobj) # save those datasets which are linked # we do this first in case the datasets are overridden below savedlinks = {} for name, dataset in sorted(self.data.items()): dataset.saveLinksToSavedDoc(fileobj, savedlinks, relpath=reldirname) # save the remaining datasets for name, dataset in sorted(self.data.items()): dataset.saveToFile(fileobj, name) # save tags of datasets self.saveDatasetTags(fileobj) # save the actual tree structure fileobj.write(self.basewidget.getSaveText()) self.setModified(False) def exportStyleSheet(self, fileobj): """Export the StyleSheet to a file.""" self._writeFileHeader(fileobj, 'exported stylesheet') stylesheet = self.basewidget.settings.StyleSheet fileobj.write( stylesheet.saveText(True, rootname='') ) def _pagedocsize(self, widget, dpi, scaling, integer): """Helper for page or doc size.""" if dpi is None: p = qt4.QPixmap(1, 1) dpi = (p.logicalDpiX(), p.logicalDpiY()) helper = painthelper.PaintHelper( (1,1), dpi=dpi, scaling=scaling ) w = widget.settings.get('width').convert(helper) h = widget.settings.get('height').convert(helper) if integer: return int(w), int(h) else: return w, h def pageSize(self, pagenum, dpi=None, scaling=1., integer=True): """Get the size of a particular page in pixels. If dpi is None, use the default Qt screen dpi Use dpi if given.""" return self._pagedocsize(self.basewidget.getPage(pagenum), dpi=dpi, scaling=scaling, integer=integer) def docSize(self, dpi=None, scaling=1., integer=True): """Get size for document.""" return self._pagedocsize(self.basewidget, dpi=dpi, scaling=scaling, integer=integer) def resolveItem(self, fromwidget, where): """Resolve item relative to fromwidget. Returns a widget, setting or settings as appropriate. """ parts = where.split('/') if where[:1] == '/': # relative to base directory obj = self.basewidget else: # relative to here obj = fromwidget # iterate over parts in string for p in parts: if p == '..': p = obj.parent if p is None: raise ValueError, "Base graph has no parent" obj = p elif p == '.' or len(p) == 0: pass else: if obj.isWidget(): child = obj.getChild(p) if child is not None: obj = child else: if p in obj.settings: obj = obj.settings[p] else: raise ValueError, "Widget has no child %s" % p else: if isinstance(obj, setting.Settings): try: obj = obj.get(p) except KeyError: raise ValueError, "Settings has no child %s" % p else: raise ValueError, "Item has no children" # return widget return obj def resolve(self, fromwidget, where): """Resolve graph relative to the widget fromwidget Allows unix-style specifiers, e.g. /graph1/x Returns widget """ parts = where.split('/') if where[:1] == '/': # relative to base directory obj = self.basewidget else: # relative to here obj = fromwidget # iterate over parts in string for p in parts: if p == '..': # relative to parent object p = obj.parent if p is None: raise ValueError, "Base graph has no parent" obj = p elif p == '.' or len(p) == 0: # relative to here pass else: # child specified obj = obj.getChild( p ) if obj is None: raise ValueError, "Child '%s' does not exist" % p # return widget return obj def _processSafeImports(self, module, symbols): """Check what symbols are safe to import.""" # empty list if not symbols: return symbols # do import anyway if setting.transient_settings['unsafe_mode']: return symbols # two-pass to ask user whether they want to import symbol for thepass in xrange(2): # remembered during session a = 'import_allowed' if a not in setting.transient_settings: setting.transient_settings[a] = defaultdict(set) allowed = setting.transient_settings[a][module] # not allowed during session a = 'import_notallowed' if a not in setting.transient_settings: setting.transient_settings[a] = defaultdict(set) notallowed = setting.transient_settings[a][module] # remembered in setting file a = 'import_allowed' if a not in setting.settingdb: setting.settingdb[a] = {} if module not in setting.settingdb[a]: setting.settingdb[a][module] = {} allowed_always = setting.settingdb[a][module] # collect up toimport = [] possibleimport = [] for symbol in symbols: if symbol in allowed or symbol in allowed_always: toimport.append(symbol) elif symbol not in notallowed: possibleimport.append(symbol) # nothing to do, so leave if not possibleimport: break # only ask the user the first time if thepass == 0: self.emit(qt4.SIGNAL('check_allowed_imports'), module, possibleimport) return toimport def _updateEvalContextImport(self, module, val): """Add an import statement to the eval function context.""" if module_re.match(module): # work out what is safe to import symbols = identifier_split_re.findall(val) toimport = self._processSafeImports(module, symbols) if toimport: defn = 'from %s import %s' % (module, ', '.join(toimport)) try: exec defn in self.eval_context except Exception: self.log("Failed to import '%s' from " "module '%s'" % (', '.join(toimport), module)) return delta = set(symbols)-set(toimport) if delta: self.log("Did not import '%s' from module '%s'" % (', '.join(list(delta)), module)) else: self.log( "Invalid module name '%s'" % module ) def _updateEvalContextFuncOrConst(self, ctype, name, val): """Update a function or constant in eval function context.""" if ctype == 'constant': if not identifier_re.match(name): self.log( "Invalid constant name '%s'" % name ) return defn = val elif ctype == 'function': m = function_re.match(name) if not m: self.log( "Invalid function specification '%s'" % name ) return name = funcname = m.group(1) args = m.group(2) defn = 'lambda %s: %s' % (args, val) # evaluate, but we ignore any unsafe commands or exceptions checked = utils.checkCode(defn) if checked is not None: self.log( "Expression '%s' failed safe code test" % defn ) return try: self.eval_context[name] = eval(defn, self.eval_context) except Exception, e: self.log( "Error evaluating '%s': '%s'" % (name, unicode(e)) ) def updateEvalContext(self): """To be called after custom constants or functions are changed. This sets up a safe environment where things can be evaluated """ self.eval_context = c = {} # add numpy things # we try to avoid various bits and pieces for safety for name, val in N.__dict__.iteritems(): if ( (callable(val) or type(val)==float) and name not in __builtins__ and name[:1] != '_' and name[-1:] != '_' ): c[name] = val # safe functions c['os_path_join'] = os.path.join c['os_path_dirname'] = os.path.dirname c['veusz_markercodes'] = tuple(utils.MarkerCodes) # custom definitions for ctype, name, val in self.customs: name = name.strip() val = val.strip() if ctype == 'constant' or ctype == 'function': self._updateEvalContextFuncOrConst(ctype, name, val) elif ctype == 'import': self._updateEvalContextImport(name, val) else: raise ValueError, 'Invalid custom type' def customDict(self): """Return a dictionary mapping custom names to (idx, type, value).""" retn = {} for i, (ctype, name, val) in enumerate(self.customs): retn[name] = (i, ctype, val) return retn def evalDatasetExpression(self, expr, part='data'): """Return results of evaluating a 1D dataset expression. part is 'data', 'serr', 'perr' or 'nerr' - these are the dataset parts which are evaluated by the expression """ return datasets.simpleEvalExpression(self, expr, part=part) def walkNodes(self, tocall, root=None, nodetypes=('widget', 'setting', 'settings'), _path=None): """Walk the widget/settings/setting nodes in the document. For each one call tocall(path, node). nodetypes is tuple of possible node types """ if root is None: root = self.basewidget if _path is None: _path = root.path if root.nodetype in nodetypes: tocall(_path, root) if root.nodetype == 'widget': # get rid of // at start of path if _path == '/': _path = '' # do the widget's children for w in root.children: self.walkNodes(tocall, root=w, nodetypes=nodetypes, _path = _path + '/' + w.name) # then do the widget's settings self.walkNodes(tocall, root=root.settings, nodetypes=nodetypes, _path=_path) elif root.nodetype == 'settings': # do the settings of the settings for name, s in sorted(root.setdict.iteritems()): self.walkNodes(tocall, root=s, nodetypes=nodetypes, _path = _path + '/' + s.name) # elif root.nodetype == 'setting': pass def getColormap(self, name, invert): """Get colormap with name given (returning grey if does not exist).""" cmap = self.colormaps.get(name, self.colormaps['grey']) if invert: return cmap[::-1] return cmap veusz-1.15/document/__init__.py0000644002344000001440000000242211734662204016526 0ustar jssusers00000000000000# document __init__.py # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## from widgetfactory import * from doc import * from datasets import * from commandinterface import * from commandinterpreter import * from simpleread import * from operations import * from capture import * from mime import * from dataset_histo import * from painthelper import * from export import Export from dbusinterface import * from importparams import * veusz-1.15/document/importparams.py0000644002344000001440000001303111734662204017503 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Parameters for import routines.""" class ImportParamsBase(object): """Import parameters for the various imports. Parameters: filename: filename to import from linked: whether to link to file encoding: encoding for file prefix: prefix for output dataset names suffix: suffix for output dataset names tags: list of tags to apply to output datasets """ defaults = { 'filename': None, 'linked': False, 'encoding': 'utf_8', 'prefix': '', 'suffix': '', 'tags': None, } def __init__(self, **argsv): """Initialise the reader to import data from filename. """ # set defaults for k, v in self.defaults.iteritems(): setattr(self, k, v) # set parameters for k, v in argsv.iteritems(): if k not in self.defaults: raise ValueError, "Invalid parameter %s" % k setattr(self, k, v) # extra parameters to copy besides defaults self._extras = [] def copy(self): """Make a copy of the parameters object.""" newp = {} for k in self.defaults.keys() + self._extras: newp[k] = getattr(self, k) return self.__class__(**newp) class ImportParamsSimple(ImportParamsBase): """simpleread import parameters. additional parameters: descriptor: data descriptor useblocks: read datasets as blocks datastr: text to read from instead of file ignoretext: whether to ignore lines of text """ defaults = { 'descriptor': '', 'useblocks': False, 'datastr': None, 'ignoretext': False, } defaults.update(ImportParamsBase.defaults) class ImportParamsCSV(ImportParamsBase): """CSV import parameters. additional parameters: readrows: readdata in rows delimiter: CSV delimiter textdelimiter: delimiter for text headerignore: number of lines to ignore after headers rowsignore: number of lines to ignore at top fo file blanksaredata: treat blank entries as nans numericlocale: name of local for numbers dateformat: date format string headermode: 'multi', '1st' or 'none' """ defaults = { 'readrows': False, 'delimiter': ',', 'textdelimiter': '"', 'headerignore': 0, 'rowsignore': 0, 'blanksaredata': False, 'numericlocale': 'en_US', 'dateformat': 'YYYY-MM-DD|T|hh:mm:ss', 'headermode': 'multi', } defaults.update(ImportParamsBase.defaults) def __init__(self, **argsv): ImportParamsBase.__init__(self, **argsv) if self.headermode not in ('multi', '1st', 'none'): raise ValueError, "Invalid headermode" class ImportParams2D(ImportParamsBase): """2D import parameters. additional parameters: datastr: text to read from instead of file xrange: tuple with range of x data coordinates yrange: tuple with range of y data coordinates invertrows: invert rows when reading invertcols: invert columns when reading transpose: swap rows and columns """ defaults = { 'datasetnames': None, 'datastr': None, 'xrange': None, 'yrange': None, 'invertrows': False, 'invertcols': False, 'transpose': False, } defaults.update(ImportParamsBase.defaults) class ImportParamsFITS(ImportParamsBase): """FITS file import parameters. Additional parameters: dsname: name of dataset hdu: name/number of hdu datacol: name of column symerrcol: symmetric error column poserrcol: positive error column negerrcol: negative error column """ defaults = { 'dsname': None, 'hdu': None, 'datacol': None, 'symerrcol': None, 'poserrcol': None, 'negerrcol': None, } defaults.update(ImportParamsBase.defaults) class ImportParamsPlugin(ImportParamsBase): """Parameters for import plugins. Additional parameter: plugin: name of plugin Plugins have their own parameters.""" defaults = { 'plugin': None, } defaults.update(ImportParamsBase.defaults) def __init__(self, **argsv): """Initialise plugin parameters, splitting up default parameters and plugin parameters.""" pluginpars = {} upvars = {} for n, v in argsv.iteritems(): if n in self.defaults: upvars[n] = v else: pluginpars[n] = v ImportParamsBase.__init__(self, **upvars) self.pluginpars = pluginpars self._extras.append('pluginpars') veusz-1.15/document/svg_export.py0000644002344000001440000005015111734662204017171 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A home-brewed SVG paint engine for doing svg with clipping and exporting text as paths for WYSIWYG.""" import sys import re import veusz.qtall as qt4 # dpi runs at many times usual, and results are scaled down # helps fix point issues in font sizes dpi = 900. scale = 0.1 inch_mm = 25.4 inch_pt = 72.0 def printpath(path): """Debugging print path.""" print "Contents of", path for i in xrange(path.elementCount()): el = path.elementAt(i) print " ", el.type, el.x, el.y def fltStr(v, prec=2): """Change a float to a string, using a maximum number of decimal places but removing trailing zeros.""" # this is to get consistent rounding to get the self test correct... yuck # decimal would work, but that drags in loads of code # convert float to string with prec decimal places fmt = '% 10.' + str(prec) + 'f' v1 = fmt % (v-1e-6) v2 = fmt % (v+1e-6) # always round down if v1 < v2: val = v1 else: val = v2 # drop any trailing zeros val = val.rstrip('0').lstrip(' ').rstrip('.') # get rid of -0s (platform differences here) if val == '-0': val = '0' return val def escapeXML(text): """Escape special characters in XML.""" # we have swap & with an unused character, so we can replace it later text = text.replace('&', u'\ue001') text = text.replace('<', '<') text = text.replace('>', '>') text = text.replace('"', '"') text = text.replace("'", ''') text = text.replace(u'\ue001', '&') return text def createPath(path): """Convert qt path to svg path. We use relative coordinates to make the file size smaller and help compression """ p = [] count = path.elementCount() i = 0 ox, oy = 0, 0 while i < count: e = path.elementAt(i) nx, ny = e.x*scale, e.y*scale if e.type == qt4.QPainterPath.MoveToElement: p.append( 'm%s,%s' % (fltStr(nx-ox), fltStr(ny-oy)) ) ox, oy = nx, ny elif e.type == qt4.QPainterPath.LineToElement: p.append( 'l%s,%s' % (fltStr(nx-ox), fltStr(ny-oy)) ) ox, oy = nx, ny elif e.type == qt4.QPainterPath.CurveToElement: e1 = path.elementAt(i+1) e2 = path.elementAt(i+2) p.append( 'c%s,%s,%s,%s,%s,%s' % ( fltStr(nx-ox), fltStr(ny-oy), fltStr(e1.x*scale-ox), fltStr(e1.y*scale-oy), fltStr(e2.x*scale-ox), fltStr(e2.y*scale-oy)) ) ox, oy = e2.x*scale, e2.y*scale i += 2 else: assert False i += 1 return ''.join(p) class SVGElement(object): """SVG element in output. This represents the XML tree in memory """ def __init__(self, parent, eltype, attrb, text=None): """Intialise element. parent: parent element or None eltype: type (e.g. 'polyline') attrb: attribute string appended to output text: text to output between this and closing element. """ self.eltype = eltype self.attrb = attrb self.children = [] self.parent = parent self.text = text if parent: parent.children.append(self) def write(self, fileobj): """Write element and its children to the output file.""" fileobj.write('<%s' % self.eltype) if self.attrb: fileobj.write(' ' + self.attrb) if self.text: fileobj.write('>%s\n' % (self.text, self.eltype)) elif self.children: fileobj.write('>\n') for c in self.children: c.write(fileobj) fileobj.write('\n' % self.eltype) else: # simple close tag if not children or text fileobj.write('/>\n') class SVGPaintEngine(qt4.QPaintEngine): """Paint engine class for writing to svg files.""" def __init__(self, width_in, height_in, writetextastext=False): """Create the class, using width and height as size of canvas in inches.""" qt4.QPaintEngine.__init__(self, qt4.QPaintEngine.Antialiasing | qt4.QPaintEngine.PainterPaths | qt4.QPaintEngine.PrimitiveTransform | qt4.QPaintEngine.PaintOutsidePaintEvent | qt4.QPaintEngine.PixmapTransform | qt4.QPaintEngine.AlphaBlend ) self.width = width_in self.height = height_in self.imageformat = 'png' self.writetextastext = writetextastext def begin(self, paintdevice): """Start painting.""" self.device = paintdevice self.pen = qt4.QPen() self.brush = qt4.QBrush() self.clippath = None self.clipnum = 0 self.existingclips = {} self.matrix = qt4.QMatrix() # svg root element for qt defaults self.rootelement = SVGElement( None, 'svg', ('width="%spx" height="%spx" version="1.1"\n' ' xmlns="http://www.w3.org/2000/svg"\n' ' xmlns:xlink="http://www.w3.org/1999/xlink"') % (fltStr(self.width*dpi*scale), fltStr(self.height*dpi*scale))) SVGElement(self.rootelement, 'desc', '', 'Veusz output document') # definitions, for clips, etc. self.defs = SVGElement(self.rootelement, 'defs', '') # this is where all the drawing goes self.celement = SVGElement( self.rootelement, 'g', 'stroke-linejoin="bevel" stroke-linecap="square" ' 'stroke="#000000" fill-rule="evenodd"') # previous transform, stroke and clip states self.oldstate = [None, None, None] # cache paths to avoid duplication self.pathcache = {} self.pathcacheidx = 0 return True def pruneEmptyGroups(self): """Take the element tree and remove any empty group entries.""" def recursive(root): children = list(root.children) # remove any empty children first for c in children: recursive(c) if root.eltype == 'g' and len(root.children) == 0: # safe to remove index = root.parent.children.index(root) del root.parent.children[index] # merge equal groups last = None i = 0 while i < len(root.children): this = root.children[i] if ( last is not None and last.eltype == this.eltype and last.attrb == this.attrb and last.text == this.text ): last.children += this.children del root.children[i] else: last = this i += 1 recursive(self.rootelement) def end(self): self.pruneEmptyGroups() fileobj = self.device.fileobj fileobj.write('\n' '\n') # write all the elements self.rootelement.write(fileobj) return True def _updateClipPath(self, clippath, clipoperation): """Update clip path given state change.""" clippath = self.matrix.map(clippath) if clipoperation == qt4.Qt.NoClip: self.clippath = None elif clipoperation == qt4.Qt.ReplaceClip: self.clippath = clippath elif clipoperation == qt4.Qt.IntersectClip: self.clippath = self.clippath.intersected(clippath) elif clipoperation == qt4.Qt.UniteClip: self.clippath = self.clippath.united(clippath) else: assert False def updateState(self, state): """Examine what has changed in state and call apropriate function.""" ss = state.state() # state is a list of transform, stroke/fill and clip states statevec = list(self.oldstate) if ss & qt4.QPaintEngine.DirtyTransform: self.matrix = state.matrix() statevec[0] = self.transformState() if ss & qt4.QPaintEngine.DirtyPen: self.pen = state.pen() statevec[1] = self.strokeFillState() if ss & qt4.QPaintEngine.DirtyBrush: self.brush = state.brush() statevec[1] = self.strokeFillState() if ss & qt4.QPaintEngine.DirtyClipPath: self._updateClipPath(state.clipPath(), state.clipOperation()) statevec[2] = self.clipState() if ss & qt4.QPaintEngine.DirtyClipRegion: path = qt4.QPainterPath() path.addRegion(state.clipRegion()) self._updateClipPath(path, state.clipOperation()) statevec[2] = self.clipState() # work out which state differs first pop = 0 for i in xrange(2, -1, -1): if statevec[i] != self.oldstate[i]: pop = i+1 break # go back up the tree the required number of times for i in xrange(pop): if self.oldstate[i]: self.celement = self.celement.parent # create new elements for changed states for i in xrange(pop-1, -1, -1): if statevec[i]: self.celement = SVGElement( self.celement, 'g', ' '.join(statevec[i])) self.oldstate = statevec def clipState(self): """Get SVG clipping state. This is in the form of an svg group""" if self.clippath is None: return () path = createPath(self.clippath) if path in self.existingclips: url = 'url(#c%i)' % self.existingclips[path] else: clippath = SVGElement(self.defs, 'clipPath', 'id="c%i"' % self.clipnum) SVGElement(clippath, 'path', 'd="%s"' % path) url = 'url(#c%i)' % self.clipnum self.existingclips[path] = self.clipnum self.clipnum += 1 return ('clip-path="%s"' % url,) def strokeFillState(self): """Return stroke-fill state.""" vals = {} p = self.pen # - color color = p.color().name() if color != '#000000': vals['stroke'] = p.color().name() # - opacity if p.color().alphaF() != 1.: vals['stroke-opacity'] = '%.3g' % p.color().alphaF() # - join style if p.joinStyle() != qt4.Qt.BevelJoin: vals['stroke-linejoin'] = { qt4.Qt.MiterJoin: 'miter', qt4.Qt.SvgMiterJoin: 'miter', qt4.Qt.RoundJoin: 'round', qt4.Qt.BevelJoin: 'bevel' }[p.joinStyle()] # - cap style if p.capStyle() != qt4.Qt.SquareCap: vals['stroke-linecap'] = { qt4.Qt.FlatCap: 'butt', qt4.Qt.SquareCap: 'square', qt4.Qt.RoundCap: 'round' }[p.capStyle()] # - width w = p.widthF() # width 0 is device width for qt if w == 0.: w = 1./scale vals['stroke-width'] = fltStr(w*scale) # - line style if p.style() == qt4.Qt.NoPen: vals['stroke'] = 'none' elif p.style() not in (qt4.Qt.SolidLine, qt4.Qt.NoPen): # convert from pen width fractions to pts nums = [str(scale*w*x) for x in p.dashPattern()] vals['stroke-dasharray'] = ','.join(nums) # BRUSH STYLES b = self.brush if b.style() == qt4.Qt.NoBrush: vals['fill'] = 'none' else: vals['fill'] = b.color().name() if b.color().alphaF() != 1.0: vals['fill-opacity'] = '%.3g' % b.color().alphaF() items = ['%s="%s"' % x for x in sorted(vals.items())] return tuple(items) def transformState(self): if not self.matrix.isIdentity(): m = self.matrix dx, dy = m.dx(), m.dy() if (m.m11(), m.m12(), m.m21(), m.m22()) == (1., 0., 0., 1): out = ('transform="translate(%s,%s)"' % ( fltStr(dx*scale), fltStr(dy*scale)) ,) else: out = ('transform="matrix(%s %s %s %s %s %s)"' % ( fltStr(m.m11(), 4), fltStr(m.m12(), 4), fltStr(m.m21(), 4), fltStr(m.m22(), 4), fltStr(dx*scale), fltStr(dy*scale) ),) else: out = () return out def drawPath(self, path): """Draw a path on the output.""" p = createPath(path) attrb = 'd="%s"' % p if path.fillRule() == qt4.Qt.WindingFill: attrb += ' fill-rule="nonzero"' if attrb in self.pathcache: element, num = self.pathcache[attrb] if num is None: # this is the first time an element has been referenced again # assign it an id for use below num = self.pathcacheidx self.pathcacheidx += 1 self.pathcache[attrb] = element, num # add an id attribute element.attrb += ' id="p%i"' % num # if the parent is a translation, swallow this into the use element m = re.match('transform="translate\(([-0-9.]+),([-0-9.]+)\)"', self.celement.attrb) if m: SVGElement(self.celement.parent, 'use', 'xlink:href="#p%i" x="%s" y="%s"' % ( num, m.group(1), m.group(2))) else: SVGElement(self.celement, 'use', 'xlink:href="#p%i"' % num) else: pathel = SVGElement(self.celement, 'path', attrb) self.pathcache[attrb] = [pathel, None] def drawTextItem(self, pt, textitem): """Convert text to a path and draw it. """ if self.writetextastext: # size f = textitem.font() if f.pixelSize() > 0: size = f.pixelSize()*scale else: size = f.pointSizeF()*scale*dpi/inch_pt attrb = [ 'x="%s"' % fltStr(pt.x()*scale), 'y="%s"' % fltStr(pt.y()*scale), 'textLength="%s"' % fltStr(textitem.width()*scale), ] grp = SVGElement( self.celement, 'g', 'stroke="none" fill="%s" fill-opacity="%.3g" ' 'font-family="%s" font-size="%s"' % (self.pen.color().name(), self.pen.color().alphaF(), escapeXML(textitem.font().family()), size)) text = escapeXML( unicode(textitem.text()) ) # write as an SVG text element SVGElement( grp, 'text', ' '.join(attrb), text=text.encode('utf-8') ) else: # convert to a path path = qt4.QPainterPath() path.addText(pt, textitem.font(), textitem.text()) p = createPath(path) SVGElement( self.celement, 'path', 'd="%s" fill="%s" stroke="none" fill-opacity="%.3g"' % ( p, self.pen.color().name(), self.pen.color().alphaF()) ) def drawLines(self, lines): """Draw multiple lines.""" paths = [] for line in lines: path = 'M%s,%sl%s,%s' % ( fltStr(line.x1()*scale), fltStr(line.y1()*scale), fltStr((line.x2()-line.x1())*scale), fltStr((line.y2()-line.y1())*scale)) paths.append(path) SVGElement(self.celement, 'path', 'd="%s"' % ''.join(paths)) def drawPolygon(self, points, mode): """Draw polygon on output.""" pts = [] for p in points: pts.append( '%s,%s' % (fltStr(p.x()*scale), fltStr(p.y()*scale)) ) if mode == qt4.QPaintEngine.PolylineMode: SVGElement(self.celement, 'polyline', 'fill="none" points="%s"' % ' '.join(pts)) else: attrb = 'points="%s"' % ' '.join(pts) if mode == qt4.Qt.WindingFill: attrb += ' fill-rule="nonzero"' SVGElement(self.celement, 'polygon', attrb) def drawEllipse(self, rect): """Draw an ellipse to the svg file.""" SVGElement(self.celement, 'ellipse', 'cx="%s" cy="%s" rx="%s" ry="%s"' % (fltStr(rect.center().x()*scale), fltStr(rect.center().y()*scale), fltStr(rect.width()*0.5*scale), fltStr(rect.height()*0.5*scale))) def drawPoints(self, points): """Draw points.""" for pt in points: x, y = fltStr(pt.x()*scale), fltStr(pt.y()*scale) SVGElement(self.celement, 'line', ('x1="%s" y1="%s" x2="%s" y2="%s" ' 'stroke-linecap="round"') % (x, y, x, y)) def drawImage(self, r, img, sr, flags): """Draw image. As the pixmap method uses the same code, just call this.""" self.drawPixmap(r, img, sr) def drawPixmap(self, r, pixmap, sr): """Draw pixmap svg item. This is converted to a bitmap and embedded in the output """ # convert pixmap to textual data data = qt4.QByteArray() buf = qt4.QBuffer(data) buf.open(qt4.QBuffer.ReadWrite) pixmap.save(buf, self.imageformat.upper(), 0) buf.close() attrb = [ 'x="%s" y="%s" ' % (fltStr(r.x()*scale), fltStr(r.y()*scale)), 'width="%s" ' % fltStr(r.width()*scale), 'height="%s" ' % fltStr(r.height()*scale), 'xlink:href="data:image/%s;base64,' % self.imageformat, str(data.toBase64()), '" preserveAspectRatio="none"' ] SVGElement(self.celement, 'image', ''.join(attrb)) class SVGPaintDevice(qt4.QPaintDevice): """Paint device for SVG paint engine.""" def __init__(self, fileobj, width_in, height_in, writetextastext=False): qt4.QPaintDevice.__init__(self) self.engine = SVGPaintEngine(width_in, height_in, writetextastext=writetextastext) self.fileobj = fileobj def paintEngine(self): return self.engine def metric(self, m): """Return the metrics of the painter.""" if m == qt4.QPaintDevice.PdmWidth: return int(self.engine.width * dpi) elif m == qt4.QPaintDevice.PdmHeight: return int(self.engine.height * dpi) elif m == qt4.QPaintDevice.PdmWidthMM: return int(self.engine.width * inch_mm) elif m == qt4.QPaintDevice.PdmHeightMM: return int(self.engine.height * inch_mm) elif m == qt4.QPaintDevice.PdmNumColors: return 2147483647 elif m == qt4.QPaintDevice.PdmDepth: return 24 elif m == qt4.QPaintDevice.PdmDpiX: return int(dpi) elif m == qt4.QPaintDevice.PdmDpiY: return int(dpi) elif m == qt4.QPaintDevice.PdmPhysicalDpiX: return int(dpi) elif m == qt4.QPaintDevice.PdmPhysicalDpiY: return int(dpi) veusz-1.15/tests/0000755002344000001440000000000011734662466013753 5ustar jssusers00000000000000veusz-1.15/tests/selftests/0000755002344000001440000000000011734662466015767 5ustar jssusers00000000000000veusz-1.15/tests/selftests/csv_missing.vsz0000644002344000001440000000177011734662204021052 0ustar jssusers00000000000000# Veusz saved document (version 1.13) # Saved at 2011-10-29T18:48:52.635479 ImportFileCSV(u'csv_missing.csv', linked=True, blanksaredata=True, dateformat=u'DD/MM/YY| |hh:mm:ss', headermode='1st', numericlocale='en_GB') SetDataExpression(u'cdiv', u'c/1000.', linked=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'a') Set('yData', []) Set('labels', u'b') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'd') Set('yData', []) Set('marker', u'diamond') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', u'e') Set('yData', []) Set('markerSize', u'30pt') Set('MarkerFill/hide', True) To('..') Add('xy', name='xy4', autoadd=False) To('xy4') Set('xData', []) Set('yData', u'cdiv') Set('marker', u'cross') To('..') To('..') To('..') veusz-1.15/tests/selftests/qdp_1d.vsz0000644002344000001440000000155511734662204017677 0ustar jssusers00000000000000# Veusz saved document (version 1.14) # Saved at 2011-12-10T15:40:10.998898 ImportFilePlugin(u'QDP import', u'qdp_1d_2.qdp', linked=True, names=(u'a', u'b')) ImportFilePlugin(u'QDP import', u'qdp_1d_1.qdp', linked=True, names=('',)) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'vec1') Set('yData', u'vec2') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'vec1') Set('yData', u'vec3') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', u'a_1') Set('yData', u'b_1') To('..') Add('xy', name='xy4', autoadd=False) To('xy4') Set('xData', u'a_2') Set('yData', u'b_2') To('..') To('..') To('..') veusz-1.15/tests/selftests/sizetest.vsz0000644002344000001440000000311411734662204020372 0ustar jssusers00000000000000# Veusz saved document (version 1.12.999) # Saved at 2011-08-17T21:59:29.985354 AddImportPath(u'/home/jss/code/veusz-git/veusz/tests/selftests') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name=u'pt', autoadd=False) To(u'pt') Set('xData', [1.0, 2.1, 2.5]) Set('yData', [3.0, 2.0, 1.0]) Set('markerSize', u'5pt') Set('MarkerFill/color', u'blue') To('..') Add('xy', name=u'perc', autoadd=False) To(u'perc') Set('xData', [1.0, 2.0, 3.0]) Set('yData', [1.0, 3.0, 2.0]) Set('markerSize', u'10%') Set('MarkerFill/color', u'magenta') To('..') Add('xy', name=u'ratio', autoadd=False) To(u'ratio') Set('xData', [0.5, 1.5, 2.5]) Set('yData', [2.5, 0.8, 4.0]) Set('markerSize', u'1/20') To('..') Add('xy', name=u'frac', autoadd=False) To(u'frac') Set('xData', [1.4, 1.7, 2.5]) Set('yData', [3.2, 1.5, 3.0]) Set('markerSize', u'0.04') Set('MarkerFill/color', u'cyan') To('..') Add('xy', name=u'cm', autoadd=False) To(u'cm') Set('xData', [0.4, 1.5, 3.0]) Set('yData', [3.0, 2.5, 1.0]) Set('markerSize', u'0.5cm') Set('MarkerFill/color', u'yellow') To('..') Add('xy', name=u'mm', autoadd=False) To(u'mm') Set('xData', [1.9, 1.5, 1.7]) Set('yData', [1.3, 1.7, 1.2]) Set('markerSize', u'3mm') Set('MarkerFill/color', u'red') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', [1.9, 1.5, 1.7]) Set('yData', [1.5, 2.7, 2.2]) Set('markerSize', u'0.2in') Set('MarkerFill/color', u'#aaaaff') To('..') To('..') To('..') veusz-1.15/tests/selftests/test_npy_npz.vsz0000644002344000001440000000176211734662204021263 0ustar jssusers00000000000000# Veusz saved document (version 1.13) # Saved at 2011-09-09T18:43:18.107056 ImportFilePlugin(u'Numpy NPZ import', u'testdat.npz', linked=True, errorsin2d=True) ImportFilePlugin(u'Numpy NPY import', u'testdat.npy', linked=True, errorsin2d=True, name=u'c') Set('StyleSheet/xy/marker', u'none') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'a') Set('yData', u'b') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'a') Set('yData', u'c') Set('PlotLine/color', u'green') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', u'a') Set('yData', u'd') Set('PlotLine/color', u'blue') To('..') Add('xy', name='xy4', autoadd=False) To('xy4') Set('xData', u'a') Set('yData', u'e') Set('PlotLine/color', u'cyan') To('..') To('..') To('..') veusz-1.15/tests/selftests/csv_locale.vsz0000644002344000001440000000144311734662204020635 0ustar jssusers00000000000000# Veusz saved document (version 1.13) # Saved at 2011-10-29T17:11:42.391345 ImportFileCSV(u'csv_locale.csv', linked=True, delimiter='\t', dsprefix=u'a_') ImportFileCSV(u'csv_locale.csv', linked=True, dateformat=u'DD/MM/YY| |hh:mm:ss', delimiter='\t', dsprefix=u'b_', numericlocale='de_DE') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('mode', u'datetime') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'a_b') Set('yData', u'a_d') Set('labels', u'a_a') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'b_a') Set('yData', u'b_c') Set('labels', u'b_d') To('..') To('..') To('..') veusz-1.15/tests/selftests/noheader.vsz0000644002344000001440000000112311734662204020303 0ustar jssusers00000000000000# Veusz saved document (version 1.13) # Saved at 2011-10-29T19:22:19.911232 ImportFileCSV(u'noheader.csv', linked=True, blanksaredata=True, headermode='none', numericlocale='en_GB') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) To('x') Set('mode', u'datetime') To('..') Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'col3') Set('yData', u'col1') Set('labels', u'col2') To('..') To('..') To('..') veusz-1.15/tests/selftests/rangeds.vsz0000644002344000001440000000173211734662204020147 0ustar jssusers00000000000000# Veusz saved document (version 1.11) # User: jss # Date: Fri, 10 Jun 2011 19:21:55 +0000 AddImportPath(u'/home/jss/code/veusz/veusz/tests/selftests') SetDataExpression(u'para', u't**2', parametric=(0.0, 1.0, 10), linked=True) SetDataRange(u'x', 10, (0.0, 10.0), linked=True) SetDataRange(u'x2', 10, (2.0, 5.0), poserr=(0.1, 0.1), negerr=(-0.1, -0.1), linked=True) SetDataExpression(u'y', u'(para**2 + x**3)/100', symerr=u'2', linked=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('yData', u'para') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('marker', u'plus') Set('errorStyle', u'linevertbar') To('..') Add('xy', name='xy3', autoadd=False) To('xy3') Set('xData', u'x2') Set('errorStyle', u'fillvert') To('..') To('..') To('..') veusz-1.15/tests/selftests/csv1.csv0000644002344000001440000000015511734662204017347 0ustar jssusers00000000000000"test","foo","+-","aerg" 1,7,0.1,"fdfd" 2,4,0.1,"dsfh" 3,3,0.1,"bgtr" 4,2,0.1,"RR" 5,3,0.2,"ZZ" 6,4,0.2,"AA" veusz-1.15/tests/selftests/testdat.npz0000644002344000001440000000233611734662204020162 0ustar jssusers00000000000000PK[)?ÖÔ\C  a.npy“NUMPYF{'descr': '©–­õEB‹?ÛUHùIµ·?ã¯ÉõÓ?çoB!æ?D½Œb9õ?ï1%’(@ßJvl¢ @5$î±ôA@…ˆ)‘$@PK[)?1‚Ýl@@d.npy“NUMPYF{'descr': '@Zd;ß @ôýÔxéf$@veusz-1.15/tests/selftests/qdp_1d_1.qdp0000644002344000001440000000010211734662204020044 0ustar jssusers00000000000000! example qdp file (no skipping) 1 2 3 2 6 4 no no no 5 6 8 7 2 1 veusz-1.15/tests/selftests/blockeddata.vsz0000644002344000001440000000113511734662204020756 0ustar jssusers00000000000000# Veusz saved document (version 1.13) # Saved at 2011-11-06T14:18:14.450714 ImportFile(u'blockeddata.dat', u'x y', linked=True, ignoretext=True, useblocks=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'x_1') Set('yData', u'y_1') To('..') Add('xy', name='xy2', autoadd=False) To('xy2') Set('xData', u'x_2') Set('yData', u'y_2') To('..') To('..') To('..') veusz-1.15/tests/selftests/testcsverr.vsz0000644002344000001440000000267411734662204020736 0ustar jssusers00000000000000# Veusz saved document (version 1.12.99) # Saved at 2011-08-14T14:43:33.721297 AddImportPath(u'/home/jss/code/veusz-git/veusz') ImportFileCSV(u'testcsverr.csv', linked=True, blanksaredata=True) Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name=u'footest', autoadd=False) To(u'footest') Set('xData', u'foo') Set('yData', u'test') To('..') Add('xy', name=u'foo2foo', autoadd=False) To(u'foo2foo') Set('xData', u'foo2\xf0\xf0\xf0') Set('yData', u'foo') Set('marker', u'diamond') Set('MarkerFill/color', u'red') To('..') Add('xy', name=u'testplusminus', autoadd=False) To(u'testplusminus') Set('xData', u'test+') Set('yData', u'test-') Set('marker', u'lineplus') Set('MarkerFill/color', u'blue') To('..') Add('xy', name=u'testplustest', autoadd=False) To(u'testplustest') Set('xData', u'test+') Set('yData', u'test') Set('marker', u'barvert') Set('MarkerFill/color', u'cyan') To('..') Add('xy', name=u'ab', autoadd=False) To(u'ab') Set('xData', u'a') Set('yData', u'b') Set('marker', u'pentagon') Set('errorStyle', u'barends') Set('MarkerFill/color', u'darkred') To('..') Add('xy', name=u'ac', autoadd=False) To(u'ac') Set('xData', u'c') Set('yData', u'a') Set('PlotLine/steps', u'centre') Set('PlotLine/color', u'red') Set('PlotLine/width', u'1pt') To('..') To('..') To('..') veusz-1.15/tests/selftests/testcontour.vsz0000644002344000001440000000436311734662204021120 0ustar jssusers00000000000000# Veusz saved document (version 0.99.0) # User: jss # Date: Fri, 21 Sep 2007 19:00:30 +0000 # A test to make sure 2d arrays are working with different dimensions # in x and y ImportString2D(u'test', ''' xrange 0.000000e+00 1.000000e+01 yrange 0.000000e+00 1.200000e+01 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 0.000000e+00 2.000000e+00 4.000000e+00 6.000000e+00 8.000000e+00 1.000000e+01 1.200000e+01 1.400000e+01 1.600000e+01 1.800000e+01 0.000000e+00 3.000000e+00 6.000000e+00 9.000000e+00 1.200000e+01 1.500000e+01 1.800000e+01 2.100000e+01 2.400000e+01 2.700000e+01 0.000000e+00 4.000000e+00 8.000000e+00 1.200000e+01 1.600000e+01 2.000000e+01 2.400000e+01 2.800000e+01 3.200000e+01 3.600000e+01 0.000000e+00 5.000000e+00 1.000000e+01 1.500000e+01 2.000000e+01 2.500000e+01 3.000000e+01 3.500000e+01 4.000000e+01 4.500000e+01 0.000000e+00 6.000000e+00 1.200000e+01 1.800000e+01 2.400000e+01 3.000000e+01 3.600000e+01 4.200000e+01 4.800000e+01 5.400000e+01 0.000000e+00 7.000000e+00 1.400000e+01 2.100000e+01 2.800000e+01 3.500000e+01 4.200000e+01 4.900000e+01 5.600000e+01 6.300000e+01 0.000000e+00 8.000000e+00 1.600000e+01 2.400000e+01 3.200000e+01 4.000000e+01 4.800000e+01 5.600000e+01 6.400000e+01 7.200000e+01 0.000000e+00 9.000000e+00 1.800000e+01 2.700000e+01 3.600000e+01 4.500000e+01 5.400000e+01 6.300000e+01 7.200000e+01 8.100000e+01 0.000000e+00 1.000000e+01 2.000000e+01 3.000000e+01 4.000000e+01 5.000000e+01 6.000000e+01 7.000000e+01 8.000000e+01 9.000000e+01 0.000000e+00 1.100000e+01 2.200000e+01 3.300000e+01 4.400000e+01 5.500000e+01 6.600000e+01 7.700000e+01 8.800000e+01 9.900000e+01 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('contour', name='contour1', autoadd=False) To('contour1') Set('data', u'test') To('..') Add('image', name='image1', autoadd=False) To('image1') Set('data', u'test') Set('colorMap', u'spectrum2') To('..') To('..') To('..') veusz-1.15/tests/selftests/qdp_2d.qdp0000644002344000001440000003061211734662204017636 0ustar jssusers00000000000000@qdp_2d.pco ! 404.87271 408.81204 412.70694 416.54901 420.33163 424.05014 427.70126 431.28256 434.79248 438.23038 441.59604 444.88965 448.11179 451.26321 454.34506 457.35864 460.30533 463.18628 466.00327 468.75772 471.45129 474.08554 476.66196 479.18222 481.64774- 484.06024 486.42102 488.73157 490.99344 493.20789 495.37631 497.50003 499.58035 501.61856 503.61566 505.57288 507.49146 509.37241 511.21674 513.02557 514.7998 377.28552 381.07175 384.8439 388.58951 392.29904 395.96451 399.5798 403.14056 406.64307 410.0849 413.46423 416.78003 420.03162 423.21893 426.34204 429.4014 432.39771 435.33182 438.20459 441.01733 443.77103 446.46701 449.10648 451.69077 454.22116 456.69894- 459.12546 461.50201 463.82983 466.11044 468.34473 470.53415 472.67978 474.78281 476.84445 478.86581 480.84796 482.79187 484.69861 486.56918 488.40454 350.43729 354.01654 357.61945 361.22925 364.83194 368.41635 371.97327 375.4953 378.97647 382.41214 385.79858 389.13306 392.41327 395.63809 398.80615 401.91714 404.97064 407.96689 410.90585 413.78833 416.61469 419.3858 422.10245 424.76563 427.37619- 429.93527 432.44388 434.90314 437.31393 439.67755 441.99509 444.26761 446.49615 448.68188 450.82565 452.92871 454.99194 457.01633 459.00293 460.95261 462.86646 324.55194 327.85257 331.22235 334.63977 338.08643 341.54721 345.00925 348.46191 351.8963 355.30527 358.68268 362.02371 365.32437 368.58157 371.79279 374.95609 378.07013 381.13385 384.14667 387.10806 390.01807 392.87671 395.68408 398.4408 401.14734- 403.80423 406.41211 408.97192 411.48428 413.9502 416.37039 418.74576 421.07721 423.36575 425.61218 427.81754 429.98254 432.10825 434.19547 436.24515 438.25803 300.03836 302.96985 306.02402 309.1741 312.39731 315.67401 318.98773 322.3241 325.67099 329.01831 332.35715 335.6803 338.98154 342.25574 345.49854 348.70651 351.87665 355.00674 358.09476 361.13934 364.1394 367.09415 370.00293 372.86563 375.68185 378.45193- 381.17578 383.85388 386.48642 389.0741 391.61731 394.11667 396.57278 398.98639 401.35809 403.68869 405.97885 408.22949 410.44113 412.61484 414.75104 277.55585 280.0137 282.65405 285.44534 288.36029 291.37512 294.46927 297.62476 300.82608 304.05981 307.31421 310.57935 313.84647 317.10815 320.358 323.59058 326.80109 329.9856 333.14084 336.26407 339.35278 342.40524 345.41968 348.39505 351.33026 354.22473- 357.07773 359.8891 362.65839 365.38599 368.0715 370.71545 373.31781 375.87918 378.39954 380.87985 383.32013 385.72122 388.08334 390.40753 392.69394 258.08423 259.96222 262.08591 264.41977 266.93253 269.59656 272.3876 275.28424 278.26758 281.32114 284.43015 287.58191 290.76505 293.96973 297.18726 300.40994 303.63135 306.84549 310.04749 313.23282 316.39789 319.53918 322.65414 325.74023 328.79553- 331.81833 334.80722 337.76093 340.67877 343.55963 346.40335 349.20917 351.97711 354.70679 357.39822 360.05142 362.6666 365.24377 367.78336 370.28543 372.75061 244.31047 245.58493 247.15504 248.98303 251.0349 253.28079 255.69385 258.25052 260.92938 263.71185 266.58124 269.52255 272.52258 275.56961 278.65326 281.76413 284.89423 288.0361 291.18335 294.33032 297.47214 300.60422 303.72272 306.82428 309.90598- 312.96515 315.99957 319.00735 321.98694 324.93671 327.85571 330.74268 333.59717 336.41815 339.20532 341.95816 344.67661 347.36011 350.00897 352.62289 355.20212 234.93307 235.64696 236.68747 238.01555 239.59642 241.39894 243.39522 245.56036 247.87213 250.31059 252.85776 255.49774 258.21637 261.00082 263.83957 266.72253 269.64047 272.58533 275.5498 278.5275 281.51251 284.49969 287.48462 290.46317 293.43167- 296.38721 299.32675 302.24799 305.14871 308.02701 310.88129 313.71024 316.51248 319.28711 322.0332 324.75012 327.43716 330.09387 332.71979 335.31485 337.87866 227.08977 227.2114 227.68927 228.48367 229.55899 230.88309 232.42722 234.16544 236.07452 238.13351 240.32355 242.62759 245.03055 247.51849 250.07942 252.70174 255.37578 258.09259 260.8439 263.62262 266.42209 269.23663 272.06088 274.89014 277.72021- 280.54749 283.36841 286.18011 288.97989 291.76538 294.53464 297.28561 300.01682 302.72684 305.41431 308.07831 310.7179 313.33215 315.92053 318.48245 321.01746 220.87743 220.37907 220.26509 220.49522 221.03323 221.84642 222.90514 224.18274 225.65504 227.30037 229.09888 231.03282 233.08603 235.24397 237.49339 239.82231 242.22012 244.67677 247.18365 249.73271 252.31676 254.92917 257.56409 260.21613 262.88065- 265.55319 268.22986 270.9071 273.58182 276.25119 278.9126 281.56378 284.20267 286.82758 289.43677 292.02884 294.6026 297.15674 299.69055 302.20291 304.6933 216.40108 215.26001 214.52939 214.16876 214.14156 214.41452 214.95758 215.74342 216.74724 217.94662 219.32103 220.8519 222.52243 224.31728 226.22241 228.2253 230.31425 232.47891 234.70958 236.99765 239.33548 241.71541 244.13129 246.57707 249.04738- 251.53737 254.04243 256.55862 259.08234 261.6102 264.13931 266.66678 269.19022 271.70743 274.21637 276.71536 279.20264 281.67685 284.1366 286.58093 289.00858 213.77354 211.97282 210.60623 209.63336 209.01759 208.72542 208.72647 208.993 209.4998 210.22382 211.144 212.24127 213.49808 214.89848 216.42786 218.07295 219.82153 221.66251 223.58568 225.58176 227.64226 229.75941 231.92609 234.13574 236.38252 238.66107- 240.96625 243.29367 245.63914 247.99889 250.36942 252.74757 255.13062 257.51584 259.90085 262.28357 264.66187 267.03415 269.39865 271.75391 274.09882 213.11516 210.64449 208.62891 207.02837 205.80632 204.92937 204.36696 204.09123 204.07664 204.29977 204.73924 205.37547 206.19043 207.16769 208.29213 209.54988 210.92827 212.41559 214.0011 215.675 217.42822 219.25252 221.14018 223.08423 225.0782 227.11635- 229.19298 231.30318 233.44247 235.60649 237.79147 239.99379 242.2103 244.43782 246.67371 248.91551 251.16074 253.40746 255.65361 257.89755 260.13745 214.55322 211.41003 208.73979 206.50308 204.66376 203.18864 202.04738 201.21205 200.65701 200.35878 200.29565 200.44777 200.79683 201.32594 202.01967 202.86372 203.84491 204.95114 206.17123 207.4949 208.9126 210.41562 211.9958 213.64577 215.3586 217.1279- 218.94781 220.81296 222.71828 224.65918 226.63142 228.63095 230.65431 232.69789 234.75888 236.83429 238.92143 241.01797 243.12161 245.23022 247.34198 218.22166 214.41199 211.08978 208.21642 205.75648 203.67734 201.94887 200.54338 199.43544 198.60144 198.01967 197.6702 197.53445 197.5954 197.8373 198.24551 198.80663 199.50813 200.33852 201.28711 202.34404 203.50015 204.74693 206.0766 207.48186 208.9559- 210.49255 212.08601 213.73083 215.42213 217.15521 218.9258 220.72993 222.56392 224.42433 226.30791 228.21187 230.1333 232.06989 234.01897 235.97865 224.26042 219.79982 215.83759 212.33632 209.26134 206.58076 204.26503 202.28691 200.62108 199.24437 198.13509 197.27324 196.64037 196.2193 195.99413 195.95012 196.07355 196.35178 196.77298 197.32628 198.00142 198.78902 199.68025 200.66698 201.74161- 202.89705 204.12675 205.42459 206.78487 208.20229 209.67192 211.18915 212.74966 214.34956 215.98505 217.65266 219.3492 221.07169 222.8172 224.58328 226.36737 232.81479 227.72945 223.14964 219.03925 215.3647 212.09506 209.20148 206.6573 204.43782 202.52007 200.88269 199.50597 198.37148 197.46213 196.76198 196.2563 195.93134 195.7742 195.77303 195.91667 196.19479 196.59772 197.11644 197.74257 198.46826 199.28618- 200.18953 201.17189 202.22734 203.35027 204.53554 205.77824 207.07382 208.41805 209.80693 211.23683 212.70415 214.20569 215.73842 217.29948 218.88618 244.03487 238.36243 233.19887 228.50943 224.26195 220.42645 216.97511 213.88208 211.12326 208.67619 206.5201 204.63542 203.00414 201.60925 200.43509 199.46692 198.69093 198.09445 197.66542 197.39268 197.26578 197.27492 197.41093 197.66533 198.03008- 198.49765 199.06104 199.7137 200.44942 201.26247 202.14742 203.09917 204.11298 205.18439 206.30922 207.48352 208.7036 209.96597 211.26746 212.6049 213.97551 258.07462 251.86534 246.1642 240.93823 236.15657 231.7906 227.81363 224.20068 220.92848 217.97531 215.32095 212.94632 210.83382 208.96681 207.32982 205.90834 204.68883 203.65854 202.8056 202.11888 201.58784 201.20276 200.95442 200.83417 200.83389 200.94609- 201.16353 201.47952 201.88777 202.38239 202.95775 203.60866 204.33017 205.11766 205.96677 206.87341 207.83368 208.84395 209.90082 211.00104 212.1416 274.36423 267.69235 261.52457 255.82928 250.57751 245.74174 241.29663 237.21802 233.48387 230.07294 226.96585 224.1441 221.59059 219.28911 217.22458 215.38274 213.75041 212.31493 211.06468 209.98851 209.07614 208.31779 207.70433 207.2271 206.87802- 206.64944 206.53418 206.52542 206.61691 206.80251 207.07663 207.43385 207.86929 208.37807 208.95576 209.59811 210.30113 211.06108 211.87439 212.73772 213.64787 281.60223 274.6676 268.2366 262.27872 256.76532 251.66939 246.96606 242.63179 238.64453 234.9836 231.62979 228.5648 225.77176 223.23456 220.9382 218.8687 217.01273 215.35785 213.89238 212.60527 211.48624 210.52553 209.71388 209.04276 208.504 208.08987- 207.79326 207.60722 207.52542 207.54179 207.6506 207.84644 208.12419 208.47913 208.90659 209.40236 209.96239 210.58279 211.25995 211.99048 212.77109 289.55817 282.36911 275.68277 269.46921 263.70047 258.35034 253.39409 248.80872 244.57259 240.66531 237.06796 233.76248 230.7321 227.96109 225.43449 223.13838 221.05959 219.18576 217.50529 216.00714 214.68102 213.51724 212.50664 211.64049 210.91071- 210.3096 209.8299 209.46477 209.2077 209.05269 208.99387 209.02586 209.14349 209.34184 209.61641 209.96277 210.37683 210.85471 211.39267 211.98727 212.63518 298.22845 290.7944 283.86142 277.40033 271.38376 265.78613 260.58328 255.75243 251.27252 247.12354 243.2867 239.74432 236.47992 233.47775 230.72331 228.20255 225.90256 223.81104 221.91649 220.20796 218.67514 217.30841 216.09862 215.03703 214.11565- 213.32668 212.66287 212.11739 211.68372 211.3557 211.12758 210.99385 210.94928 210.98907 211.10844 211.30302 211.56871 211.90149 212.29764 212.75362 213.26607 307.60962 299.94101 292.77115 286.07166 279.81573 273.97836 268.53601 263.4664 258.74878 254.3636 250.29228 246.51764 243.02324 239.7937 236.81448 234.07196 231.55325 229.24614 227.13931 225.22179 223.4834 221.91458 220.5061 219.24951 218.13654 217.15953- 216.31126 215.58485 214.97375 214.47182 214.07324 213.77252 213.56438 213.44392 213.40645 213.44748 213.56281 213.74847 214.00066 214.31578 214.69038 317.698 309.80637 302.41043 295.4826 288.99667 282.92844 277.25476 271.95407 267.0058 262.39084 258.09122 254.08969 250.3703 246.91789 243.71814 240.75752 238.02342 235.50372 233.18713 231.06291 229.12094 227.35156 225.74586 224.29518 222.99146 221.827- 220.7946 219.88731 219.09872 218.42258 217.85304 217.38469 217.01216 216.73048 216.53502 216.42117 216.38483 216.42184 216.5284 216.70094 216.93593 328.49002 320.38782 312.77756 305.63242 298.92712 292.63776 286.74203 281.21869 276.04785 271.21069 266.68958 262.46765 258.5293 254.85947 251.44426 248.2702 245.32486 242.59639 240.07347 237.7457 235.6028 233.63547 231.83455 230.19176 228.69882 227.34824- 226.13274 225.04546 224.0799 223.22995 222.4897 221.85365 221.31654 220.87334 220.51932 220.24998 220.06105 219.94852 219.90842 219.93713 220.03119 339.98206 331.68268 323.87088 316.52054 309.60721 303.1076 296.99994 291.26367 285.87924 280.82834 276.09366 271.65878 267.5083 263.62759 260.00281 256.62088 253.46951 250.53693 247.81216 245.28468 242.94456 240.78244 238.78941 236.95706 235.27734- 233.74266 232.3459 231.08018 229.93906 228.9164 228.0063 227.20328 226.50203 225.89761 225.38519 224.96034 224.6187 224.35619 224.16895 224.05327 224.00563 352.17007 343.6879 335.68826 328.14587 321.03693 314.3389 308.03058 302.09186 296.50388 291.24872 286.30936 281.66995 277.31528 273.23099 269.40366 265.82034 262.46896 259.33804 256.4166 253.69437 251.16151 248.80884 246.62744 244.60902 242.74576 241.02994- 239.45454 238.01276 236.69817 235.50464 234.42636 233.45779 232.59367 231.82899 231.15904 230.57927 230.08539 229.67325 229.33904 229.07893 228.88948 365.05023 356.40073 348.22797 340.50757 333.21637 326.33267 319.83585 313.70636 307.92587 302.47678 297.34286 292.50818 287.95825 283.67886 279.65689 275.87964 272.3353 269.01254 265.9006 262.98944 260.26929 257.73108 255.3661 253.16609 251.1232 249.23003- 247.47951 245.86485 244.37975 243.01804 241.77396 240.64201 239.61696 238.69388 237.8679 237.1346 236.48959 235.92886 235.44844 235.04462 234.71384 378.61856 369.81802 361.48779 353.60434 346.14545 339.08981 332.41766 326.10995 320.14902 314.51752 309.1998 304.18039 299.4451 294.97995 290.77228 286.80951 283.08026 279.57318 276.27802 273.1846 270.28351 267.5658 265.02283 262.64648 260.42911 258.36313- 256.44186 254.65836 253.00652 251.48013 250.07356 248.78122 247.59804 246.51897 245.53941 244.65463 243.86052 243.15283 242.5278 241.9816 241.51074 veusz-1.15/tests/selftests/autodetect.csv0000644002344000001440000000030311734662204020627 0ustar jssusers00000000000000a,b,c,,d,e (text) 1,01/01/10,hello,,1.00E+001,01/01/10 2,01/02/10,foo,,1.00E+002,02/01/11 3,20/02/10,bar,,1.00E+003,03/02/12 4,15/03/10,xxx,,1.00E+004,01/11/11 5,10/04/10,aaa,,1.00E+003,11/11/11 veusz-1.15/tests/test_badpy_expr_link.vsz0000644002344000001440000000121411734662204020714 0ustar jssusers00000000000000# Veusz saved document (version 0.99.0) # User: jss # Date: Tue, 09 Oct 2007 11:04:05 +0000 SetDataExpression(u'y', u'x_0.5**2', linked=True) ImportString(u'x_0.5(numeric)',''' 0.000000e+00 1.000000e+00 2.000000e+00 3.000000e+00 4.000000e+00 5.000000e+00 6.000000e+00 7.000000e+00 8.000000e+00 9.000000e+00 1.000000e+01 ''') Add('page', name='page1', autoadd=False) To('page1') Add('graph', name='graph1', autoadd=False) To('graph1') Add('axis', name='x', autoadd=False) Add('axis', name='y', autoadd=False) To('y') Set('direction', 'vertical') To('..') Add('xy', name='xy1', autoadd=False) To('xy1') Set('xData', u'x_0.5') To('..') To('..') To('..') veusz-1.15/tests/runselftest.py0000755002344000001440000001463111734662204016701 0ustar jssusers00000000000000#!/usr/bin/env python # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A program to self test the Veusz installation. This code compares the output of example + self test input files with expected output. It returns 0 if the tests succeeded, otherwise the number of tests failed. If you use an argument "regenerate" to the program, the comparison files will be recreated. This program requires the veusz module to be on the PYTHONPATH. On Unix/Linux, Qt requires the DISPLAY environment to be set to an X11 server for the self test to run. In a non graphical environment Xvfb can be used to create a hidden X11 server. The comparison files are close to being SVG files, but use XPM for any images and use a fixed (hacked) font metric to give the same results on each platform. In addition Unicode characters are expanded to their Unicode code to work around different font handling on platforms. """ import glob import os.path import sys import veusz.qtall as qt4 import veusz.utils.textrender import veusz.document as document import veusz.setting as setting import veusz.windows.mainwindow import veusz.document.svg_export as svg_export # these tests fail for some reason which haven't been debugged # it appears the failures aren't important however excluded_tests = set([ # fails on Windows 'histo.vsz', # duplicate in long list of values 'spectrum.vsz', # angstrom is split into two on linux # fails on Mac OS X 'histo.vsz', # somewhere in long list of values 'spectrum.vsz', # symbol issue 'labels.vsz' # symbol issue ]) class StupidFontMetrics(object): """This is a fake font metrics device which should return the same results on all systems with any font.""" def __init__(self, font, device): self.font = font self.device = device def height(self): return self.device.logicalDpiY() * (self.font.pointSizeF()/72.) def width(self, text): return len(text)*self.height()*0.5 def ascent(self): return 0.1*self.height() def descent(self): return 0.1*self.height() def leading(self): return 0.1*self.height() def boundingRect(self, c): return qt4.QRectF(0, 0, self.height()*0.5, self.height()) _pt = veusz.utils.textrender.PartText class PartTextAscii(_pt): """Text renderer which converts text to ascii.""" def __init__(self, text): text = unicode(text).encode('ascii', 'xmlcharrefreplace') _pt.__init__(self, text) def renderTest(invsz, outfile): """Render vsz document to create outfile.""" d = document.Document() ifc = document.CommandInterface(d) # this lot looks a bit of a mess cmds = d.eval_context for cmd in document.CommandInterface.safe_commands: cmds[cmd] = getattr(ifc, cmd) for cmd in document.CommandInterface.unsafe_commands: cmds[cmd] = getattr(ifc, cmd) exec "from numpy import *" in cmds ifc.AddImportPath( os.path.dirname(invsz) ) exec open(invsz) in cmds ifc.Export(outfile) class Dirs(object): """Directories and files object.""" def __init__(self): self.thisdir = os.path.dirname(__file__) self.exampledir = os.path.join(self.thisdir, '..', 'examples') self.testdir = os.path.join(self.thisdir, 'selftests') self.comparisondir = os.path.join(self.thisdir, 'comparison') files = ( glob.glob( os.path.join(self.exampledir, '*.vsz') ) + glob.glob( os.path.join(self.testdir, '*.vsz') ) ) self.invszfiles = [ f for f in files if os.path.basename(f) not in excluded_tests ] def renderAllTests(): """Check documents produce same output as in comparison directory.""" print "Regenerating all test output" d = Dirs() for vsz in d.invszfiles: base = os.path.basename(vsz) print base outfile = os.path.join(d.comparisondir, base + '.selftest') renderTest(vsz, outfile) def runTests(): print "Testing output" fails = 0 passes = 0 d = Dirs() for vsz in sorted(d.invszfiles): base = os.path.basename(vsz) print base outfile = os.path.join(d.thisdir, base + '.temp.selftest') renderTest(vsz, outfile) comparfile = os.path.join(d.thisdir, 'comparison', base + '.selftest') t1 = open(outfile, 'rU').read() t2 = open(comparfile, 'rU').read() if t1 != t2: print " FAIL: results differed" fails += 1 else: print " PASS" passes += 1 os.unlink(outfile) print if fails == 0: print "All tests %i/%i PASSED" % (passes, passes) sys.exit(0) else: print "%i/%i tests FAILED" % (fails, passes+fails) sys.exit(fails) if __name__ == '__main__': app = qt4.QApplication([]) veusz.setting.transient_settings['unsafe_mode'] = True # hack metrics object to always return same metrics # and replace text renderer with one that encodes unicode symbols veusz.utils.textrender.FontMetrics = StupidFontMetrics veusz.utils.FontMetrics = StupidFontMetrics veusz.utils.textrender.PartText = PartTextAscii # nasty hack to remove underlining del veusz.utils.textrender.part_commands[r'\underline'] # dpi (use old values) svg_export.dpi = 90. svg_export.scale = 1. if len(sys.argv) == 1: runTests() else: if len(sys.argv) != 2 or sys.argv[1] != 'regenerate': print >>sys.stderr, "Usage: %s [regenerate]" % sys.argv[0] sys.exit(1) renderAllTests() veusz-1.15/tests/test_all_examples.vsz0000644002344000001440000000305111734662204020211 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## # load all the example documents in this directory # generate an image for each example # run with veusz --unsafe-mode testallexamples.vsz import glob import os import os.path thisdir = os.path.abspath(os.path.dirname(__file__)) exampledir = os.path.join(thisdir, '..', 'examples') examples = glob.glob(os.path.join(exampledir, '*.vsz')) for filename in examples: To('/') for child in GetChildren(): Remove(child) Set('width', '15cm') Set('height', '15cm') if filename == 'testall.vsz': continue __file__ = filename execfile(filename) Export(os.path.join(thisdir, 'test_%s.png' % os.path.basename(filename))) veusz-1.15/tests/comparison/0000755002344000001440000000000011734662466016125 5ustar jssusers00000000000000veusz-1.15/tests/comparison/rangeds.vsz.selftest0000644002344000001440000002122211734662204022131 0ustar jssusers00000000000000 Veusz output document −2.5 0 2.5 5 7.5 10 12.5 0 2 4 6 8 10 veusz-1.15/tests/comparison/qdp_2d.vsz.selftest0000644002344000001440000001434311734662204021665 0ustar jssusers00000000000000 Veusz output document 0.8 1 1.2 1.4 1.6 1.5 1.75 2 2.25 2.5 veusz-1.15/tests/comparison/isolatedaxes.vsz.selftest0000644002344000001440000000747411734662204023210 0ustar jssusers00000000000000 Veusz output document y -axis (cm -3 ) 0 0.2 0.4 0.6 0.8 1 x -axis (erg) 0.01 0.1 1 veusz-1.15/tests/comparison/histo.vsz.selftest0000644002344000001440000005360611734662204021647 0ustar jssusers00000000000000 Veusz output document Example histogram Fit to histogram Histogram Dragons 0 100 200 300 400 500 Wingspan (m) 0 50 100 150 200 veusz-1.15/tests/comparison/contour.vsz.selftest0000644002344000001440000024401511734662204022206 0ustar jssusers00000000000000 Veusz output document 0 20 40 60 80 100 0 20 40 60 80 100 veusz-1.15/tests/comparison/markerspolygon.vsz.selftest0000644002344000001440000007730711734662204023601 0ustar jssusers00000000000000 Veusz output document Outward ticks on this y axis −2 0 1 2 Outward ticks on this x axis −2 −1 0 1 2 veusz-1.15/tests/comparison/custom.vsz.selftest0000644002344000001440000001313611734662204022025 0ustar jssusers00000000000000 Veusz output document 0 10 20 30 40 0 0.2 0.4 0.6 0.8 1 veusz-1.15/tests/comparison/dataset_operations.vsz.selftest0000644002344000001440000005074711734662204024414 0ustar jssusers00000000000000 Veusz output document data add plus mean sub scale extremes thin Using data operations to combine datasets −2.5 0 2.5 5 0 5 10 15 20 veusz-1.15/tests/comparison/1dto2d.vsz.selftest0000644002344000001440000002233511734662204021611 0ustar jssusers00000000000000 Veusz output document 0 2 4 6 8 0 2 4 6 8 veusz-1.15/tests/comparison/spectrum.vsz.selftest0000644002344000001440000076125211734662204022366 0ustar jssusers00000000000000 Veusz output document Fe XXI Fe XXIII - XXIV Fe XXIV Mg XII Ne X Fe XXIV Fe XXII - XXIII Ne X , Fe XVII - XVIII Fe XVIII Fe XVIII Fe XVII Fe XVII Fe XX - XXII Fe XIX Si XIII Si XIV 99% PSF 90% PSF 99% - 90% Mg XII N VII Fe XVII Fe XVII O VIII Fe XVIII O VIII Flux (10 -3 photon cm -2 s -1 -1 ) 0 0.5 1 1.5 2 2.5 0.5 keV model spectrum 0.7 keV model spectrum 1.0 keV model spectrum Arbitrary units 0 0.1 0.2 0.3 0.4 0.5 0.6 Wavelength ( ) 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 veusz-1.15/tests/comparison/testcontour.vsz.selftest0000644002344000001440000001357711734662204023115 0ustar jssusers00000000000000 Veusz output document 0 2.5 5 7.5 10 12.5 0 2 4 6 8 10 veusz-1.15/tests/comparison/ternary.vsz.selftest0000644002344000001440000004540711734662204022205 0ustar jssusers00000000000000 Veusz output document 0 10 20 30 40 50 60 70 80 90 100 0 10 20 30 40 50 60 70 80 90 100 0 10 20 30 40 50 60 70 80 90 100 Earth Air Fire Nougat Chocolate veusz-1.15/tests/comparison/test_npy_npz.vsz.selftest0000644002344000001440000001414311734662204023246 0ustar jssusers00000000000000 Veusz output document 0 25 50 75 100 125 0 2 4 6 8 10 veusz-1.15/tests/comparison/example_csv.vsz.selftest0000644002344000001440000001750311734662204023023 0ustar jssusers00000000000000 Veusz output document Line plot Histogram Data read into from CSV 0 1 2 3 4 5 6 7 Imported CSV file example 0 2 4 6 8 veusz-1.15/tests/comparison/datebar.vsz.selftest0000644002344000001440000001557511734662204022126 0ustar jssusers00000000000000 Veusz output document A graph title Look! Crazy bar value 0 1 2 3 4 Date 2009-03-10 2009-03-11 2009-03-12 2009-03-13 2009-03-14 2009-03-15 2009-03-16 veusz-1.15/tests/comparison/multixy.vsz.selftest0000644002344000001440000003034211734662204022224 0ustar jssusers00000000000000 Veusz output document The joy of plots Datasets Valkyries Swindon Discworld Model Death rate −5 −2.5 0 2.5 5 7.5 Winged warriors 0 5 10 15 20 veusz-1.15/tests/comparison/mandelbrot.vsz.selftest0000644002344000001440000072754511734662204022662 0ustar jssusers00000000000000 Veusz output document 1 10 The Mandelbrot Set 0.5 0.75 1 1.25 1.5 1.75 −2 −1 0 0.5 1 veusz-1.15/tests/comparison/labels.vsz.selftest0000644002344000001440000001544311734662204021760 0ustar jssusers00000000000000 Veusz output document dataset 2 dataset 2 dataset 2 A test test2 A 200 α β γ Y axis 0 2 4 6 8 X axis 0 1 2 3 4 5 veusz-1.15/tests/comparison/example_import.vsz.selftest0000644002344000001440000006551211734662204023545 0ustar jssusers00000000000000 Veusz output document y 1 0 1 2 3 4 5 6 This is an x-axis 0 2.5 5 7.5 10 12.5 15 y 2 −1 0 1 2 3 Another x-axis 0 2.5 5 7.5 10 12.5 15 y 3 0 0.5 1 1.5 2 2.5 Final \underline x axis 0 5 10 15 20 veusz-1.15/tests/comparison/functions.vsz.selftest0000644002344000001440000002430511734662204022523 0ustar jssusers00000000000000 Veusz output document Another axis 10 −2 10 0 δ Left axis 0 5 10 15 20 25 30 x axis −1.5 −0.5 0 0.5 1 1.5 veusz-1.15/tests/comparison/profile.vsz.selftest0000644002344000001440000006360111734662204022155 0ustar jssusers00000000000000 Veusz output document Chandra deprojected Chandra projected XMM projected Temperature (keV) 10 5 Density Cooling time Cooling time (yr) 10 8 10 9 10 10 10 11 10 12 Electron density (cm -3 ) 0.01 0.1 Radius (kpc) 10 100 1000 veusz-1.15/tests/comparison/barplots.vsz.selftest0000644002344000001440000005734011734662204022346 0ustar jssusers00000000000000 Veusz output document a b stacked mode −1.5 −1 −0.5 0 0.5 1 1.5 0 5 10 15 20 grouped mode −1 −0.5 0 0.5 1 0 5 10 15 20 error bars −1 −0.5 0 0.5 1 0 5 10 15 20 horizontal with values 1 1000 10 6 10 9 −1 0 0.5 1 veusz-1.15/tests/comparison/qdp_1d.vsz.selftest0000644002344000001440000001324611734662204021665 0ustar jssusers00000000000000 Veusz output document 0 2 4 6 8 1 2 3 4 5 6 7 veusz-1.15/tests/comparison/blockeddata.vsz.selftest0000644002344000001440000001134011734662204022743 0ustar jssusers00000000000000 Veusz output document 2 4 6 8 10 0 2 4 6 8 10 veusz-1.15/tests/comparison/csv_locale.vsz.selftest0000644002344000001440000001521411734662204022624 0ustar jssusers00000000000000 Veusz output document 2012-12-06 2011-03-01 2011-04-05 2012-01-01 1,23 100,3 1.001,2 10 2008-09 2009-03 2009-09 2010-03 2010-09 2011-03 2011-09 2012-03 2012-09 2013-03 0 250 500 750 1000 1250 1500 veusz-1.15/tests/comparison/starchart.vsz.selftest0000644002344000001440000011327611734662204022514 0ustar jssusers00000000000000 Veusz output document veusz-1.15/tests/comparison/csv1.vsz.selftest0000644002344000001440000001351511734662204021370 0ustar jssusers00000000000000 Veusz output document fdfd dsfh bgtr RR ZZ AA 1 2 3 4 5 6 2 3 4 5 6 7 veusz-1.15/tests/comparison/vectorfield.vsz.selftest0000644002344000001440000060223311734662204023023 0ustar jssusers00000000000000 Veusz output document −1 −0.5 0 0.5 1 −1 −0.5 0 0.5 1 veusz-1.15/tests/comparison/fit.vsz.selftest0000644002344000001440000001546011734662204021277 0ustar jssusers00000000000000 Veusz output document data fit A dubious y axis 0 5 10 15 20 A wonderful x axis 1 10 veusz-1.15/tests/comparison/sin_byhand.vsz.selftest0000644002344000001440000001430411734662204022627 0ustar jssusers00000000000000 Veusz output document sin x −1 −0.5 0 0.5 1 x 0 1 2 3 4 5 6 7 veusz-1.15/tests/comparison/noheader.vsz.selftest0000644002344000001440000001241211734662204022274 0ustar jssusers00000000000000 Veusz output document hello 5 2 foo 1 2 3 4 5 2009-01-01 00:00 2009-01-01 06:00 2009-01-01 12:00 2009-01-01 18:00 veusz-1.15/tests/comparison/hatching.vsz.selftest0000644002344000001440000005575711734662204022317 0ustar jssusers00000000000000 Veusz output document Hatch ing 0 2 4 6 8 10 12 1 2 3 4 5 6 7 veusz-1.15/tests/comparison/polar.vsz.selftest0000644002344000001440000002723511734662204021635 0ustar jssusers00000000000000 Veusz output document 0.5 1 1.5 2 2.5 3 330° 300° 270° 240° 210° 180° 150° 120° 90° 60° 30° 0 100 200 300 2 2.2 2.4 2.6 2.8 veusz-1.15/tests/comparison/multiaxes.vsz.selftest0000644002344000001440000003206311734662204022526 0ustar jssusers00000000000000 Veusz output document Optical surface brightness Electron density Iron Chandra Western Iron Chandra Eastern Iron XMM Electron density (cm -3 ) 10 −3 0.01 Iron metallicity (solar units) 0.4 0.6 0.8 1 1.2 1.4 1.6 1.8 Optical surface brightness (mag arcsec -2 ) 22 24 26 28 30 Radius (kpc) 10 100 veusz-1.15/tests/comparison/shapes.vsz.selftest0000644002344000001440000001742111734662204021777 0ustar jssusers00000000000000 Veusz output document Some shapes α 0 0.25 0.5 0.75 1 1.25 1.5 0 0.2 0.4 0.6 0.8 1 veusz-1.15/tests/comparison/coloredpoints.vsz.selftest0000644002344000001440000005373111734662204023404 0ustar jssusers00000000000000 Veusz output document Offset (m) −3 −2 −1 0 1 2 3 Time (yr) 0 0.2 0.4 0.6 0.8 1 Power (W) 10 100 1k veusz-1.15/tests/comparison/autodetect.vsz.selftest0000644002344000001440000002056011734662204022653 0ustar jssusers00000000000000 Veusz output document hello foo bar xxx aaa 2010-01-01 2010-01-15 2010-01-29 2010-02-12 2010-02-26 2010-03-12 2010-03-26 2010-04-09 1 2 3 4 5 xxx aaa 4 4.2 4.4 4.6 4.8 5 1000 10 4 veusz-1.15/tests/comparison/sin.vsz.selftest0000644002344000001440000001430411734662204021302 0ustar jssusers00000000000000 Veusz output document sin x −1 −0.5 0 0.5 1 x 0 1 2 3 4 5 6 7 veusz-1.15/tests/comparison/bar_labels.vsz.selftest0000644002344000001440000002333111734662204022577 0ustar jssusers00000000000000 Veusz output document Spring Summer Number of balloons 0 1 2 3 4 5 6 Colour Red Green Blue Red Green Blue Summer 2.5 3 3.5 4 4.5 Spring 2 3 4 5 veusz-1.15/tests/comparison/sizetest.vsz.selftest0000644002344000001440000001722111734662204022364 0ustar jssusers00000000000000 Veusz output document 0.5 1 1.5 2 2.5 3 3.5 4 0.5 1 1.5 2 2.5 3 veusz-1.15/tests/comparison/inside.vsz.selftest0000644002344000001440000007547311734662204022002 0ustar jssusers00000000000000 Veusz output document All the cheese in the world −50 −25 0 25 50 75 100 Random axis, maybe something interesting 2 ... 0 50 100 150 200 −8 −4 0 −1 0 1 2 0 2 4 6 8 10 sin 2 π x −1 −0.5 0 0.5 1 an x-axis 0 0.2 0.4 0.6 0.8 1 veusz-1.15/tests/comparison/stackedxy.vsz.selftest0000644002344000001440000003677711734662204022532 0ustar jssusers00000000000000 Veusz output document Valkyries −2 0 1 2 3 Swindon −1 1 2 3 4 Discworld −5 0 2.5 5 7.5 Traffic police 0 5 10 15 20 veusz-1.15/tests/comparison/csv_missing.vsz.selftest0000644002344000001440000001271211734662204023036 0ustar jssusers00000000000000 Veusz output document hello foo 0 1 2 3 4 5 6 0 2 4 6 8 veusz-1.15/tests/comparison/testcsverr.vsz.selftest0000644002344000001440000001745011734662204022722 0ustar jssusers00000000000000 Veusz output document 1 2 3 4 5 6 0 2 4 6 8 10 veusz-1.15/tests/comparison/linked_datasets.vsz.selftest0000644002344000001440000007111411734662204023651 0ustar jssusers00000000000000 Veusz output document Another axis −0.8 −0.6 −0.4 0 0.2 0.4 0.6 0.8 Experiments with linked datasets −0.8 −0.4 0 0.2 0.4 0.6 0.8 veusz-1.15/tests/comparison/boxplot.vsz.selftest0000644002344000001440000001525211734662204022203 0ustar jssusers00000000000000 Veusz output document Number of insects 0 5 10 15 20 Bees Butterflys veusz-1.15/tests/pychecker_all.sh0000755002344000001440000000202211734662204017101 0ustar jssusers00000000000000#!/bin/bash # Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## # run pychecker on all script files for f in `find . -name "*.py"`; do pychecker $f >> pychecker-out.txt done veusz-1.15/COPYING0000644002344000001440000004310311734662204013633 0ustar jssusers00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. veusz-1.15/veusz_main.py0000755002344000001440000001712311734662204015340 0ustar jssusers00000000000000#!/usr/bin/env python # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Main Veusz executable. """ import sys import os.path import signal import optparse # Allow veusz to be run even if not installed into PYTHONPATH try: import veusz except ImportError: # load in the veusz module, but change its path to # the veusz directory, and insert it into sys.modules import __init__ as veusz thisdir = os.path.dirname( os.path.abspath(__file__) ) veusz.__path__ = [thisdir] veusz.__name__ = 'veusz' sys.modules['veusz'] = veusz import veusz.qtall as qt4 import veusz.utils as utils copyr='''Veusz %s Copyright (C) Jeremy Sanders 2003-2012 and contributors Licenced under the GNU General Public Licence (version 2 or greater) ''' splashcopyr='''Veusz %s
    Copyright (C) Jeremy Sanders 2003-2012 and contributors
    Licenced under the GPL (version 2 or greater) ''' def handleIntSignal(signum, frame): '''Ask windows to close if Ctrl+C pressed.''' qt4.qApp.closeAllWindows() def makeSplashLogo(): """Make a splash screen logo.""" border = 16 xw, yw = 520, 240 pix = qt4.QPixmap(xw, yw) pix.fill() p = qt4.QPainter(pix) # draw logo on pixmap logo = utils.getPixmap('logo.png') p.drawPixmap( xw/2 - logo.width()/2, border, logo ) # add copyright text doc = qt4.QTextDocument() doc.setPageSize( qt4.QSizeF(xw, yw - 3*border - logo.height()) ) f = qt4.qApp.font() f.setPointSize(14) doc.setDefaultFont(f) doc.setDefaultTextOption( qt4.QTextOption(qt4.Qt.AlignCenter) ) doc.setHtml(splashcopyr % utils.version()) p.translate(0, 2*border + logo.height()) doc.drawContents(p) p.end() return pix def excepthook(excepttype, exceptvalue, tracebackobj): """Show exception dialog if an exception occurs.""" from veusz.dialogs.exceptiondialog import ExceptionDialog if not isinstance(exceptvalue, utils.IgnoreException): # next exception is ignored to clear out the stack frame of the # previous exception - yuck d = ExceptionDialog((excepttype, exceptvalue, tracebackobj), None) d.exec_() def listen(args, quiet): '''For running with --listen option.''' from veusz.veusz_listen import openWindow openWindow(args, quiet=quiet) def export(exports, args): '''A shortcut to load a set of files and export them.''' import veusz.document as document for expfn, vsz in zip(exports, args[1:]): doc = document.Document() ci = document.CommandInterpreter(doc) ci.Load(vsz) ci.run('Export(%s)' % repr(expfn)) def mainwindow(args): '''Open the main window with any loaded files.''' from veusz.windows.mainwindow import MainWindow if len(args) > 1: # load in filenames given for filename in args[1:]: MainWindow.CreateWindow(filename) else: # create blank window MainWindow.CreateWindow() def convertArgsUnicode(args): '''Convert set of arguments to unicode. Arguments in argv use current file system encoding ''' enc = sys.getfilesystemencoding() # bail out if not supported if enc is None: return args out = [] for a in args: if isinstance(a, str): out.append( a.decode(enc) ) else: out.append(a) return out def run(): '''Run the main application.''' # jump to the embedding client entry point if required if len(sys.argv) == 2 and sys.argv[1] == '--embed-remote': from veusz.embed_remote import runremote runremote() return # this function is spaghetti-like and has nasty code paths. # the idea is to postpone the imports until the splash screen # is shown app = qt4.QApplication(sys.argv) app.connect(app, qt4.SIGNAL("lastWindowClosed()"), app, qt4.SLOT("quit()")) sys.excepthook = excepthook # register a signal handler to catch ctrl+C signal.signal(signal.SIGINT, handleIntSignal) # parse command line options parser = optparse.OptionParser( usage="%prog [options] filename.vsz ...", version=copyr % utils.version()) parser.add_option('--unsafe-mode', action='store_true', help='disable safety checks when running documents' ' or scripts') parser.add_option('--listen', action='store_true', help='read and execute Veusz commands from stdin,' ' replacing veusz_listen') parser.add_option('--quiet', action='store_true', help='if in listening mode, do not open a window but' ' execute commands quietly') parser.add_option('--export', action='append', metavar='FILE', help='export the next document to this' ' output image file, exiting when finished') parser.add_option('--embed-remote', action='store_true', help=optparse.SUPPRESS_HELP) parser.add_option('--plugin', action='append', metavar='FILE', help='load the plugin from the file given for ' 'the session') options, args = parser.parse_args( app.argv() ) # convert args to unicode from filesystem strings args = convertArgsUnicode(args) splash = None if not (options.listen or options.export): # show the splash screen on normal start splash = qt4.QSplashScreen(makeSplashLogo()) splash.show() app.processEvents() # import these after showing splash screen so we don't # have too long a wait before it shows import veusz.setting import veusz.widgets import veusz.utils.vzdbus as vzdbus vzdbus.setup() # for people who want to run any old script veusz.setting.transient_settings['unsafe_mode'] = bool( options.unsafe_mode) # load any requested plugins if options.plugin: import veusz.document veusz.document.Document.loadPlugins(pluginlist=options.plugin) # different modes if options.listen: # listen to incoming commands listen(args, quiet=options.quiet) elif options.export: # export files to make images if len(options.export) != len(args)-1: parser.error( 'export option needs same number of documents and output files') export(options.export, args) return else: # standard start main window mainwindow(args) # clear splash when startup done if splash is not None: splash.finish(app.topLevelWidgets()[0]) # wait for application to exit app.exec_() # if ran as a program if __name__ == '__main__': #import cProfile #cProfile.run('run()', 'outprofile.dat') run() veusz-1.15/widgets/0000755002344000001440000000000011734662466014257 5ustar jssusers00000000000000veusz-1.15/widgets/key.py0000644002344000001440000004231211734662204015411 0ustar jssusers00000000000000# key symbol plotting # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils import widget import graph import controlgraph import math ############################################################################# # classes for controlling key position interactively class ControlKey(object): """Control the position of a key on a plot.""" def __init__( self, widget, parentposn, boxposn, boxdims, textheight ): """widget is widget to adjust parentposn: posn of parent on plot xpos, ypos: position of key width. height: size of key textheight: """ self.widget = widget self.parentposn = tuple(parentposn) self.posn = tuple(boxposn) self.dims = tuple(boxdims) self.textheight = textheight def createGraphicsItem(self): return _GraphControlKey(self) class _GraphControlKey(qt4.QGraphicsRectItem): """The graphical rectangle which is dragged around to reposition the key.""" def __init__(self, params): qt4.QGraphicsRectItem.__init__(self, params.posn[0], params.posn[1], params.dims[0], params.dims[1]) self.params = params self.setCursor(qt4.Qt.SizeAllCursor) self.setZValue(1.) self.setFlag(qt4.QGraphicsItem.ItemIsMovable) self.highlightpen = qt4.QPen(qt4.Qt.red, 2, qt4.Qt.DotLine) pposn, dims = params.parentposn, params.dims th = params.textheight # special places on the plot xposn = { 'left': pposn[0] + th, 'centre': pposn[0] + 0.5*(pposn[2]-pposn[0]-dims[0]), 'right': pposn[2] - th - dims[0] } yposn = { 'top': pposn[1] + th, 'centre': pposn[1] + 0.5*(pposn[3]-pposn[1]-dims[1]), 'bottom': pposn[3] - th - dims[1] } # these are special places where the key is aligned self.highlightpoints = {} for xname, xval in xposn.iteritems(): for yname, yval in yposn.iteritems(): self.highlightpoints[(xname, yname)] = qt4.QPointF(xval, yval) self.updatePen() def checkHighlight(self): """Check to see whether box is over hightlight area. Returns (x, y) name or None if not.""" rect = self.rect() rect.translate(self.pos()) highlight = None highlightrect = qt4.QRectF(rect.left()-10, rect.top()-10, 20, 20) for name, point in self.highlightpoints.iteritems(): if highlightrect.contains(point): highlight = name break return highlight def updatePen(self): """Update color of rectangle if it is over a hightlight area.""" if self.checkHighlight(): self.setPen(self.highlightpen) else: self.setPen(controlgraph.controlLinePen()) def mouseMoveEvent(self, event): """Set correct pen for box.""" qt4.QGraphicsRectItem.mouseMoveEvent(self, event) self.updatePen() def mouseReleaseEvent(self, event): """Update widget with position.""" qt4.QGraphicsRectItem.mouseReleaseEvent(self, event) highlight = self.checkHighlight() if highlight: # in a highlight zone so use highlight zone name to set position hp, vp = highlight hm, vm = 0., 0. else: # calculate the position of the box to work out Manual fractions rect = self.rect() rect.translate(self.pos()) pposn = self.params.parentposn hp, vp = 'manual', 'manual' hm = (rect.left() - pposn[0]) / (pposn[2] - pposn[0]) vm = (pposn[3] - rect.bottom()) / (pposn[3] - pposn[1]) # update widget with positions s = self.params.widget.settings operations = ( document.OperationSettingSet(s.get('horzPosn'), hp), document.OperationSettingSet(s.get('vertPosn'), vp), document.OperationSettingSet(s.get('horzManual'), hm), document.OperationSettingSet(s.get('vertManual'), vm), ) self.params.widget.document.applyOperation( document.OperationMultiple(operations, descr='move key')) ############################################################################ class Key(widget.Widget): """Key on graph.""" typename = 'key' description = "Plot key" allowedparenttypes = [graph.Graph] allowusercreation = True def __init__(self, parent, name=None): widget.Widget.__init__(self, parent, name=name) if type(self) == Key: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add( setting.Text('Text', descr = 'Text settings', usertext='Text'), pixmap = 'settings_axislabel' ) s.add( setting.KeyBrush('Background', descr = 'Key background fill', usertext='Background'), pixmap = 'settings_bgfill' ) s.add( setting.Line('Border', descr = 'Key border line', usertext='Border'), pixmap = 'settings_border' ) s.add( setting.Str('title', '', descr='Key title text', usertext='Title') ) s.add( setting.AlignHorzWManual( 'horzPosn', 'right', descr = 'Horizontal key position', usertext='Horz posn', formatting=True) ) s.add( setting.AlignVertWManual( 'vertPosn', 'bottom', descr = 'Vertical key position', usertext='Vert posn', formatting=True) ) s.add( setting.Distance('keyLength', '1cm', descr = 'Length of line to show in sample', usertext='Key length', formatting=True) ) s.add( setting.AlignVert( 'keyAlign', 'top', descr = 'Alignment of key symbols relative to text', usertext = 'Key alignment', formatting = True) ) s.add( setting.Float( 'horzManual', 0., descr = 'Manual horizontal fractional position', usertext='Horz manual', formatting=True) ) s.add( setting.Float( 'vertManual', 0., descr = 'Manual vertical fractional position', usertext='Vert manual', formatting=True) ) s.add( setting.Float( 'marginSize', 1., minval = 0., descr = 'Width of margin in characters', usertext='Margin size', formatting=True) ) s.add( setting.Int( 'columns', 1, descr = 'Number of columns in key', usertext = 'Columns', minval = 1, maxval = 100, formatting = True) ) @staticmethod def _layoutChunk(entries, start, dims): """Layout the entries into the given box, starting at start""" row, col = start numrows, numcols = dims colstats = [0] * numcols layout = [] for (plotter, num, lines) in entries: if row+lines > numrows: # this item doesn't fit in this column, so move to the next col += 1 row = 0 if col >= numcols: # this layout failed, suggest expanding the box by 1 row return ([], [], numrows+1) if lines > numrows: # this layout failed, suggest expanding the box to |lines| return ([], [], lines) # col -> yp, row -> xp layout.append( (plotter, num, col, row, lines) ) row += lines colstats[col] += 1 return (layout, colstats, numrows) def _layout(self, entries, totallines): """Layout the items, trying to keep the box as small as possible while still filling the columns""" maxcols = self.settings.columns numcols = min(maxcols, max(len(entries), 1)) if not entries: return (list(), (0, 0)) # start with evenly-sized rows and expand to fit numrows = totallines / numcols layout = [] while not layout: # try to do a first cut of the layout, and expand the box until # everything fits (layout, colstats, newrows) = self._layoutChunk(entries, (0, 0), (numrows, numcols)) if not layout: numrows = newrows # ok, we've got a layout where everything fits, now pull items right # to fill the remaining columns, if need be while colstats[-1] == 0: # shift 1 item to the right, up to the first column that has # excess items meanoccupation = max(1, sum(colstats)/float(numcols)) # loop until we find a victim item which can be safely moved victimcol = numcols while True: # find the right-most column with excess occupation number for i in reversed(xrange(victimcol)): if colstats[i] > meanoccupation: victimcol = i break # find the last item in the victim column victim = 0 for i in reversed(xrange(len(layout))): if layout[i][2] == victimcol: victim = i break # try to relayout with the victim item shoved to the next column (newlayout, newcolstats, newrows) = self._layoutChunk(entries[victim:], (0, victimcol+1), (numrows, numcols)) if newlayout: # the relayout worked, so accept it layout = layout[0:victim] + newlayout colstats[victimcol] -= 1 del colstats[victimcol+1:] colstats += newcolstats[victimcol+1:] break # if we've run out of potential victims, just return what we have if victimcol == 0: return (layout, (numrows, numcols)) return (layout, (numrows, numcols)) def draw(self, parentposn, phelper, outerbounds = None): """Plot the key on a plotter.""" s = self.settings if s.hide: return painter = phelper.painter(self, parentposn) font = s.get('Text').makeQFont(painter) painter.setFont(font) height = utils.FontMetrics(font, painter.device()).height() margin = s.marginSize * height showtext = not s.Text.hide # maximum width of text required maxwidth = 1 # total number of layout lines required totallines = 0 # reserve space for the title titlewidth, titleheight = 0, 0 if s.title != '': titlefont = qt4.QFont(font) titlefont.setPointSize(max(font.pointSize() * 1.2, font.pointSize() + 2)) titlewidth, titleheight = utils.Renderer(painter, titlefont, 0, 0, s.title).getDimensions() titleheight += 0.5*margin maxwidth = titlewidth entries = [] # iterate over children and find widgets which are suitable for c in self.parent.children: try: num = c.getNumberKeys() except AttributeError: continue if not c.settings.hide: # add an entry for each key entry for each widget for i in xrange(num): lines = 1 if showtext: w, h = utils.Renderer(painter, font, 0, 0, c.getKeyText(i)).getDimensions() maxwidth = max(maxwidth, w) lines = max(1, math.ceil(float(h)/float(height))) totallines += lines entries.append( (c, i, lines) ) # layout the box layout, (numrows, numcols) = self._layout(entries, totallines) # total size of box symbolwidth = s.get('keyLength').convert(painter) totalwidth = ( (maxwidth + height + symbolwidth)*numcols + height*(numcols-1) ) totalheight = numrows * height + titleheight if not s.Border.hide: totalwidth += 2*margin totalheight += margin # work out horizontal position h = s.horzPosn if h == 'left': x = parentposn[0] + height elif h == 'right': x = parentposn[2] - height - totalwidth elif h == 'centre': x = ( parentposn[0] + 0.5*(parentposn[2] - parentposn[0] - totalwidth) ) elif h == 'manual': x = parentposn[0] + (parentposn[2]-parentposn[0])*s.horzManual # work out vertical position v = s.vertPosn if v == 'top': y = parentposn[1] + height elif v == 'bottom': y = parentposn[3] - totalheight - height elif v == 'centre': y = ( parentposn[1] + 0.5*(parentposn[3] - parentposn[1] - totalheight) ) elif v == 'manual': y = ( parentposn[3] - (parentposn[3]-parentposn[1])*s.vertManual - totalheight ) # for controlgraph boxposn = (x, y) boxdims = (totalwidth, totalheight) # draw surrounding box boxpath = qt4.QPainterPath() boxpath.addRect(qt4.QRectF(x, y, totalwidth, totalheight)) if not s.Background.hide: utils.brushExtFillPath(painter, s.Background, boxpath) if not s.Border.hide: painter.strokePath(boxpath, s.get('Border').makeQPen(painter) ) x += margin y += margin*0.5 # center and draw the title if s.title: xpos = x + 0.5*(totalwidth - (0 if s.Border.hide else 2*margin) - titlewidth) utils.Renderer(painter, titlefont, xpos, y, s.title, alignvert=1).render() y += titleheight textpen = s.get('Text').makeQPen() # plot dataset entries for (plotter, num, xp, yp, lines) in layout: xpos = x + xp*(maxwidth+2*height+symbolwidth) ypos = y + yp*height # plot key symbol painter.save() keyoffset = 0 if s.keyAlign == 'centre': keyoffset = (lines-1)*height/2.0 elif s.keyAlign == 'bottom': keyoffset = (lines-1)*height plotter.drawKeySymbol(num, painter, xpos, ypos+keyoffset, symbolwidth, height) painter.restore() # write key text if showtext: painter.setPen(textpen) utils.Renderer(painter, font, xpos + height + symbolwidth, ypos, plotter.getKeyText(num), -1, 1).render() phelper.setControlGraph( self, [ControlKey(self, parentposn, boxposn, boxdims, height)] ) document.thefactory.register( Key ) veusz-1.15/widgets/axis.py0000644002344000001440000012152411734662204015570 0ustar jssusers00000000000000# Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Widget to plot axes, and to handle conversion of coordinates to plot positions.""" from itertools import izip import numpy as N import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils import widget import axisticks import graph import grid import controlgraph ############################################################################### class MajorTick(setting.Line): '''Major tick settings.''' def __init__(self, name, **args): setting.Line.__init__(self, name, **args) self.add( setting.DistancePt( 'length', '6pt', descr = 'Length of major ticks', usertext='Length') ) self.add( setting.Int( 'number', 6, descr = 'Number of major ticks to aim for', usertext='Number') ) self.add( setting.FloatList('manualTicks', [], descr = 'List of tick values' ' overriding defaults', usertext='Manual ticks') ) def getLength(self, painter): '''Return tick length in painter coordinates''' return self.get('length').convert(painter) class MinorTick(setting.Line): '''Minor tick settings.''' def __init__(self, name, **args): setting.Line.__init__(self, name, **args) self.add( setting.DistancePt( 'length', '3pt', descr = 'Length of minor ticks', usertext='Length') ) self.add( setting.Int( 'number', 20, descr = 'Number of minor ticks to aim for', usertext='Number') ) def getLength(self, painter): '''Return tick length in painter coordinates''' return self.get('length').convert(painter) class GridLine(setting.Line): '''Grid line settings.''' def __init__(self, name, **args): setting.Line.__init__(self, name, **args) self.get('color').newDefault( 'grey' ) self.get('hide').newDefault( True ) self.get('style').newDefault( 'dotted' ) class MinorGridLine(setting.Line): '''Minor tick grid line settings.''' def __init__(self, name, **args): setting.Line.__init__(self, name, **args) self.get('color').newDefault( 'lightgrey' ) self.get('hide').newDefault( True ) self.get('style').newDefault( 'dotted' ) class AxisLabel(setting.Text): """For axis labels.""" def __init__(self, name, **args): setting.Text.__init__(self, name, **args) self.add( setting.Bool( 'atEdge', False, descr = 'Place axis label close to edge' ' of graph', usertext='At edge') ) self.add( setting.RotateInterval( 'rotate', '0', descr = 'Angle by which to rotate label by', usertext='Rotate') ) self.add( setting.DistancePt( 'offset', '0pt', descr = 'Additional offset of axis label' ' from axis tick labels', usertext='Label offset') ) class TickLabel(setting.Text): """For tick labels on axes.""" formatchoices = ('Auto', '%Vg', '%Ve', '%VE', '%g', '%e', '%.2f') descriptions = ('Automatic', 'General numerical format', 'Scientific notation', 'Engineering suffix notation', 'C-style general format', 'C-style scientific notation', '2 decimal places always shown') def __init__(self, name, **args): setting.Text.__init__(self, name, **args) self.add( setting.RotateInterval( 'rotate', '0', descr = 'Angle by which to rotate label by', usertext='Rotate') ) self.add( setting.ChoiceOrMore( 'format', TickLabel.formatchoices, 'Auto', descr = 'Format of the tick labels', descriptions=TickLabel.descriptions, usertext='Format') ) self.add( setting.Float('scale', 1., descr='A scale factor to apply to the values ' 'of the tick labels', usertext='Scale') ) self.add( setting.DistancePt( 'offset', '0pt', descr = 'Additional offset of axis tick ' 'labels from axis', usertext='Tick offset') ) ############################################################################### class Axis(widget.Widget): """Manages and draws an axis.""" typename = 'axis' allowedparenttypes = [graph.Graph, grid.Grid] allowusercreation = True description = 'Axis to a plot or shared in a grid' isaxis = True def __init__(self, parent, name=None): """Initialise axis.""" widget.Widget.__init__(self, parent, name=name) s = self.settings if type(self) == Axis: self.readDefaults() if self.name == 'y' and s.direction != 'vertical': s.direction = 'vertical' elif self.name == 'x' and s.direction != 'horizontal': s.direction = 'horizontal' self.minorticks = None self.majorticks = None # automatic range self.setAutoRange(None) # document updates change set variable when things need recalculating self.docchangeset = -1 @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add( setting.Str('label', '', descr='Axis label text', usertext='Label') ) s.add( setting.AxisBound('min', 'Auto', descr='Minimum value of axis', usertext='Min') ) s.add( setting.AxisBound('max', 'Auto', descr='Maximum value of axis', usertext='Max') ) s.add( setting.Bool('log', False, descr = 'Whether axis is logarithmic', usertext='Log') ) s.add( setting.Choice( 'autoRange', ('exact', 'next-tick', '+2%', '+5%', '+10%', '+15%'), 'next-tick', descr = 'If axis range not specified, use range of ' 'data and this setting', descriptions = ('Use exact data range', 'Use data range, rounding up to tick marks', 'Use data range, adding 2% of range', 'Use data range, adding 5% of range', 'Use data range, adding 10% of range', 'Use data range, adding 15% of range', ), formatting = True, usertext = 'Auto range' ) ) s.add( setting.Choice('mode', ('numeric', 'datetime', 'labels'), 'numeric', descr = 'Type of ticks to show on on axis', usertext='Mode') ) s.add( setting.SettingBackwardCompat( 'autoExtend', 'autoRange', True, translatefn = lambda x: ('exact', 'next-tick')[x], formatting=True ) ) # this setting no longer used s.add( setting.Bool('autoExtendZero', True, descr = 'Extend axis to zero if close (UNUSED)', usertext='Zero extend', hidden=True, formatting=True) ) s.add( setting.Bool('autoMirror', True, descr = 'Place axis on opposite side of graph ' 'if none', usertext='Auto mirror', formatting=True) ) s.add( setting.Bool('reflect', False, descr = 'Place axis text and ticks on other side' ' of axis', usertext='Reflect', formatting=True) ) s.add( setting.Bool('outerticks', False, descr = 'Place ticks on outside of graph', usertext='Outer ticks', formatting=True) ) s.add( setting.Float('datascale', 1., descr='Scale data plotted by this factor', usertext='Scale') ) s.add( setting.Choice('direction', ['horizontal', 'vertical'], 'horizontal', descr = 'Direction of axis', usertext='Direction') ) s.add( setting.Float('lowerPosition', 0., descr='Fractional position of lower end of ' 'axis on graph', usertext='Min position') ) s.add( setting.Float('upperPosition', 1., descr='Fractional position of upper end of ' 'axis on graph', usertext='Max position') ) s.add( setting.Float('otherPosition', 0., descr='Fractional position of axis ' 'in its perpendicular direction', usertext='Axis position') ) s.add( setting.WidgetPath('match', '', descr = 'Match the scale of this axis to the ' 'axis specified', usertext='Match', allowedwidgets = [Axis] )) s.add( setting.Line('Line', descr = 'Axis line settings', usertext = 'Axis line'), pixmap='settings_axisline' ) s.add( AxisLabel('Label', descr = 'Axis label settings', usertext = 'Axis label'), pixmap='settings_axislabel' ) s.add( TickLabel('TickLabels', descr = 'Tick label settings', usertext = 'Tick labels'), pixmap='settings_axisticklabels' ) s.add( MajorTick('MajorTicks', descr = 'Major tick line settings', usertext = 'Major ticks'), pixmap='settings_axismajorticks' ) s.add( MinorTick('MinorTicks', descr = 'Minor tick line settings', usertext = 'Minor ticks'), pixmap='settings_axisminorticks' ) s.add( GridLine('GridLines', descr = 'Grid line settings', usertext = 'Grid lines'), pixmap='settings_axisgridlines' ) s.add( MinorGridLine('MinorGridLines', descr = 'Minor grid line settings', usertext = 'Grid lines for minor ticks'), pixmap='settings_axisminorgridlines' ) def _getUserDescription(self): """User friendly description.""" s = self.settings return "range %s to %s%s" % ( str(s.min), str(s.max), ['',' (log)'][s.log]) userdescription = property(_getUserDescription) def setAutoRange(self, autorange): """Set the automatic range of this axis (called from page helper).""" if autorange: scale = self.settings.datascale self.autorange = ar = [x*scale for x in autorange] if self.settings.log: ar[0] = max(1e-99, ar[0]) else: if self.settings.log: self.autorange = [1e-2, 1.] else: self.autorange = [0., 1.] def _computePlottedRange(self): """Convert the range requested into a plotted range.""" s = self.settings self.plottedrange = [s.min, s.max] # match the scale of this axis to another matched = False if s.match != '': # locate widget we're matching # this is ensured to be an Axis try: widget = s.get('match').getReferredWidget() except setting.InvalidType: widget = None # this looks valid + sanity checks if (widget is not None and widget != self and widget.settings.match == ''): # update if out of date if widget.docchangeset != self.document.changeset: widget._computePlottedRange() # copy the range self.plottedrange = list(widget.plottedrange) matched = True # automatic lookup of minimum if s.min == 'Auto' and not matched: self.plottedrange[0] = self.autorange[0] if s.max == 'Auto' and not matched: self.plottedrange[1] = self.autorange[1] # yuck, but sometimes it's true # tweak range to make sure things don't blow up further down the # line if ( abs(self.plottedrange[0] - self.plottedrange[1]) < ( abs(self.plottedrange[0]) + abs(self.plottedrange[1]) )*1e-8 ): self.plottedrange[1] = ( self.plottedrange[0] + max(1., self.plottedrange[0]*0.1) ) # handle axis values round the wrong way invertaxis = self.plottedrange[0] > self.plottedrange[1] if invertaxis: self.plottedrange.reverse() # make sure log axes don't blow up if s.log: if self.plottedrange[0] < 1e-99: self.plottedrange[0] = 1e-99 if self.plottedrange[1] < 1e-99: self.plottedrange[1] = 1e-99 if self.plottedrange[0] == self.plottedrange[1]: self.plottedrange[1] = self.plottedrange[0]*2 # work out tick values and expand axes if necessary if s.mode in ('numeric', 'labels'): tickclass = axisticks.AxisTicks else: tickclass = axisticks.DateTicks extendmin = extendmax = False r = s.autoRange if r == 'exact': pass elif r == 'next-tick': if s.min == 'Auto': extendmin = True if s.max == 'Auto': extendmax = True else: val = {'+2%': 0.02, '+5%': 0.05, '+10%': 0.1, '+15%': 0.15}[r] if s.log: # logarithmic logrng = abs( N.log(self.plottedrange[1]) - N.log(self.plottedrange[0]) ) if s.min == 'Auto': self.plottedrange[0] /= N.exp(logrng * val) if s.max == 'Auto': self.plottedrange[1] *= N.exp(logrng * val) else: # linear rng = self.plottedrange[1] - self.plottedrange[0] if s.min == 'Auto': self.plottedrange[0] -= rng*val if s.max == 'Auto': self.plottedrange[1] += rng*val axs = tickclass(self.plottedrange[0], self.plottedrange[1], s.MajorTicks.number, s.MinorTicks.number, extendmin = extendmin, extendmax = extendmax, logaxis = s.log ) axs.getTicks() self.plottedrange[0] = axs.minval self.plottedrange[1] = axs.maxval self.majortickscalc = axs.tickvals self.minortickscalc = axs.minorticks self.autoformat = axs.autoformat # override values if requested if len(s.MajorTicks.manualTicks) > 0: ticks = [] for i in s.MajorTicks.manualTicks: if i >= self.plottedrange[0] and i <= self.plottedrange[1]: ticks.append(i) self.majortickscalc = N.array(ticks) # invert bounds if axis was inverted if invertaxis: self.plottedrange.reverse() if self.majorticks is not None: self.majortickscalc = N.array(self.majorticks) if self.minorticks is not None: self.minortickscalc = N.array(self.minorticks) self.docchangeset = self.document.changeset def getPlottedRange(self): """Return the range plotted by the axes.""" if self.docchangeset != self.document.changeset: self._computePlottedRange() return (self.plottedrange[0], self.plottedrange[1]) def _updatePlotRange(self, bounds, otherposition=None): """Calculate coordinates on plotter of axis.""" s = self.settings x1, y1, x2, y2 = bounds dx = x2 - x1 dy = y2 - y1 p1, p2 = s.lowerPosition, s.upperPosition if otherposition is None: otherposition = s.otherPosition if s.direction == 'horizontal': # horizontal self.coordParr1 = x1 + dx*p1 self.coordParr2 = x1 + dx*p2 # other axis coordinates self.coordPerp = y2 - dy*otherposition self.coordPerp1 = y1 self.coordPerp2 = y2 else: # vertical self.coordParr1 = y2 - dy*p1 self.coordParr2 = y2 - dy*p2 # other axis coordinates self.coordPerp = x1 + dx*otherposition self.coordPerp1 = x1 self.coordPerp2 = x2 # is this axis reflected if otherposition > 0.5: self.coordReflected = not s.reflect else: self.coordReflected = s.reflect def graphToPlotterCoords(self, bounds, vals): """Convert graph coordinates to plotter coordinates on this axis. bounds specifies the plot bounds vals is numpy of coordinates Returns positions as a numpy """ # if the doc was modified, recompute the range if self.docchangeset != self.document.changeset: self._computePlottedRange() self._updatePlotRange(bounds) return self._graphToPlotter(vals) def _graphToPlotter(self, vals): """Convert the coordinates assuming the machinery is in place.""" # work out fractional posistions, then convert to pixels if self.settings.log: fracposns = self.logConvertToPlotter(vals) else: fracposns = self.linearConvertToPlotter(vals) return self.coordParr1 + fracposns*(self.coordParr2-self.coordParr1) def dataToPlotterCoords(self, posn, data): """Convert data values to plotter coordinates, scaling if necessary.""" # if the doc was modified, recompute the range if self.docchangeset != self.document.changeset: self._computePlottedRange() self._updatePlotRange(posn) return self._graphToPlotter(data*self.settings.datascale) def plotterToGraphCoords(self, bounds, vals): """Convert plotter coordinates on this axis to graph coordinates. bounds specifies the plot bounds vals is a numpy of coordinates returns a numpy of floats """ # if the doc was modified, recompute the range if self.docchangeset != self.document.changeset: self._computePlottedRange() self._updatePlotRange( bounds ) # work out fractional positions of the plotter coords frac = ( (vals.astype(N.float64) - self.coordParr1) / (self.coordParr2 - self.coordParr1) ) # convert from fractional to graph if self.settings.log: return self.logConvertFromPlotter(frac) else: return self.linearConvertFromPlotter(frac) def plotterToDataCoords(self, bounds, vals): """Convert plotter coordinates to data, removing scaling.""" try: scale = 1./self.settings.datascale except ZeroDivisionError: scale = 0. return scale * self.plotterToGraphCoords(bounds, vals) def linearConvertToPlotter(self, v): """Convert graph coordinates to fractional plotter units for linear scale. """ return ( (v - self.plottedrange[0]) / (self.plottedrange[1] - self.plottedrange[0]) ) def linearConvertFromPlotter(self, v): """Convert from (fractional) plotter coords to graph coords. """ return ( self.plottedrange[0] + v * (self.plottedrange[1]-self.plottedrange[0]) ) def logConvertToPlotter(self, v): """Convert graph coordinates to fractional plotter units for log10 scale. """ log1 = N.log(self.plottedrange[0]) log2 = N.log(self.plottedrange[1]) return ( N.log( N.clip(v, 1e-99, 1e99) ) - log1 )/(log2 - log1) def logConvertFromPlotter(self, v): """Convert from fraction plotter coords to graph coords with log scale. """ return ( self.plottedrange[0] * ( self.plottedrange[1]/self.plottedrange[0] )**v ) def againstWhichEdge(self): """Returns edge this axis is against, if any. Returns 0-3,None for (left, top, right, bottom, None) """ s = self.settings op = abs(s.otherPosition) if op > 1e-3 and op < 0.999: return None else: if s.direction == 'vertical': if op <= 1e-3: return 0 else: return 2 else: if op <= 1e-3: return 3 else: return 1 def swapline(self, painter, a1, b1, a2, b2): """Draw line, but swap x & y coordinates if vertical axis.""" if self.settings.direction == 'horizontal': painter.drawLine(qt4.QPointF(a1, b1), qt4.QPointF(a2, b2)) else: painter.drawLine(qt4.QPointF(b1, a1), qt4.QPointF(b2, a2)) def swaplines(self, painter, a1, b1, a2, b2): """Multiline version of swapline where a1, b1, a2, b2 are arrays.""" if self.settings.direction == 'horizontal': a = (a1, b1, a2, b2) else: a = (b1, a1, b2, a2) utils.plotLinesToPainter(painter, a[0], a[1], a[2], a[3]) def _drawGridLines(self, subset, painter, coordticks, parentposn): """Draw grid lines on the plot.""" painter.setPen( self.settings.get(subset).makeQPen(painter) ) # drop points which overlap with graph box (if used) if self.parent.typename == 'graph': if not self.parent.settings.Border.hide: if self.settings.direction == 'horizontal': ok = ( (N.abs(coordticks-parentposn[0]) > 1e-3) & (N.abs(coordticks-parentposn[2]) > 1e-3) ) else: ok = ( (N.abs(coordticks-parentposn[1]) > 1e-3) & (N.abs(coordticks-parentposn[3]) > 1e-3) ) coordticks = coordticks[ok] self.swaplines(painter, coordticks, coordticks*0.+self.coordPerp1, coordticks, coordticks*0.+self.coordPerp2) def _drawAxisLine(self, painter): """Draw the line of the axis.""" pen = self.settings.get('Line').makeQPen(painter) pen.setCapStyle(qt4.Qt.FlatCap) painter.setPen(pen) self.swapline( painter, self.coordParr1, self.coordPerp, self.coordParr2, self.coordPerp ) def _drawMinorTicks(self, painter, coordminorticks): """Draw minor ticks on plot.""" s = self.settings mt = s.get('MinorTicks') pen = mt.makeQPen(painter) pen.setCapStyle(qt4.Qt.FlatCap) painter.setPen(pen) delta = mt.getLength(painter) if s.direction == 'vertical': delta *= -1 if self.coordReflected: delta *= -1 if s.outerticks: delta *= -1 y = coordminorticks*0.+self.coordPerp self.swaplines( painter, coordminorticks, y, coordminorticks, y-delta ) def _drawMajorTicks(self, painter, tickcoords): """Draw major ticks on the plot.""" s = self.settings mt = s.get('MajorTicks') pen = mt.makeQPen(painter) pen.setCapStyle(qt4.Qt.FlatCap) painter.setPen(pen) startdelta = mt.getLength(painter) delta = startdelta if s.direction == 'vertical': delta *= -1 if self.coordReflected: delta *= -1 if s.outerticks: delta *= -1 y = tickcoords*0.+self.coordPerp self.swaplines( painter, tickcoords, y, tickcoords, y-delta ) # account for ticks if they are in the direction of the label if s.outerticks and not self.coordReflected: self._delta_axis += abs(delta) def generateLabelLabels(self, phelper): """Generate list of positions and labels from widgets using this axis.""" try: plotters = phelper.axisplottermap[self] except (AttributeError, KeyError): return dir = self.settings.direction minval, maxval = self.plottedrange for plotter in plotters: # get label and label coordinates from plotter (if any) labels, coords = plotter.getAxisLabels(dir) if None not in (labels, coords): # convert coordinates to plotter coordinates pcoords = self._graphToPlotter(coords) for coord, pcoord, lab in izip(coords, pcoords, labels): # return labels that are within the plotted range # of coordinates if N.isfinite(coord) and (minval <= coord <= maxval): yield pcoord, lab def _drawTickLabels(self, phelper, painter, coordticks, sign, outerbounds, texttorender): """Draw tick labels on the plot. texttorender is a list which contains text for the axis to render after checking for collisions """ s = self.settings vertical = s.direction == 'vertical' font = s.get('TickLabels').makeQFont(painter) painter.setFont(font) fm = utils.FontMetrics(font, painter.device()) tl_spacing = fm.leading() + fm.descent() # work out font alignment angle = int(s.TickLabels.rotate) if not self.coordReflected and angle != 0: angle = 360-angle if vertical: # limit tick labels to be directly below/besides axis bounds = { 'miny': min(self.coordParr1, self.coordParr2), 'maxy': max(self.coordParr1, self.coordParr2) } ax, ay = 1, 0 else: bounds = { 'minx': min(self.coordParr1, self.coordParr2), 'maxx': max(self.coordParr1, self.coordParr2) } ax, ay = 0, 1 if self.coordReflected: ax, ay = -ax, -ay # get information about text scales tl = s.get('TickLabels') scale = tl.scale pen = tl.makeQPen() # an extra offset if required self._delta_axis += tl.get('offset').convert(painter) def generateTickLabels(): """Return plotter position of labels and label text.""" # get format for labels format = s.TickLabels.format if format.lower() == 'auto': format = self.autoformat # generate positions and labels for posn, tickval in izip(coordticks, self.majortickscalc): text = utils.formatNumber(tickval*scale, format, locale=self.document.locale) yield posn, text # position of label perpendicular to axis perpposn = self.coordPerp + sign*(self._delta_axis+tl_spacing) # use generator function to get labels and positions if s.mode == 'labels': ticklabels = self.generateLabelLabels(phelper) else: ticklabels = generateTickLabels() # iterate over each label maxdim = 0 for parlposn, text in ticklabels: # x and y round other way if vertical if vertical: x, y = perpposn, parlposn else: x, y = parlposn, perpposn r = utils.Renderer(painter, font, x, y, text, alignhorz=ax, alignvert=ay, angle=angle) if outerbounds is not None: # make sure ticks are within plot if vertical: r.ensureInBox(miny=outerbounds[1], maxy=outerbounds[3], extraspace=True) else: r.ensureInBox(minx=outerbounds[0], maxx=outerbounds[2], extraspace=True) bnd = r.getBounds() texttorender.append( (r, pen) ) # keep track of maximum extent of label perpendicular to axis if vertical: maxdim = max(maxdim, bnd[2] - bnd[0]) else: maxdim = max(maxdim, bnd[3] - bnd[1]) # keep track of where we are self._delta_axis += 2*tl_spacing + maxdim def _drawAxisLabel(self, painter, sign, outerbounds, texttorender): """Draw an axis label on the plot. texttorender is a list which contains text for the axis to render after checking for collisions """ s = self.settings sl = s.Label label = s.get('Label') font = label.makeQFont(painter) painter.setFont(font) fm = utils.FontMetrics(font, painter.device()) al_spacing = fm.leading() + fm.descent() # an extra offset if required self._delta_axis += label.get('offset').convert(painter) text = s.label # avoid adding blank text to plot if not text: return horz = s.direction == 'horizontal' if not horz: ax, ay = 1, 0 else: ax, ay = 0, 1 reflected = self.coordReflected if reflected: ax, ay = -ax, -ay # angle of text (logic is slightly complex) angle = int(sl.rotate) if horz: if not reflected: angle = 360-angle else: angle = angle+270 if reflected: angle = 360-angle angle = angle % 360 x = 0.5*(self.coordParr1 + self.coordParr2) y = self.coordPerp + sign*(self._delta_axis+al_spacing) if not horz: x, y = y, x # make axis label flush with edge of plot if # it's appropriate if outerbounds is not None and sl.atEdge: if abs(s.otherPosition) < 1e-4 and not reflected: if horz: y = outerbounds[3] ay = -ay else: x = outerbounds[0] ax = -ax elif abs(s.otherPosition-1.) < 1e-4 and reflected: if horz: y = outerbounds[1] ay = -ay else: x = outerbounds[2] ax = -ax r = utils.Renderer(painter, font, x, y, text, ax, ay, angle, usefullheight = True) # make sure text is in plot rectangle if outerbounds is not None: r.ensureInBox( minx=outerbounds[0], maxx=outerbounds[2], miny=outerbounds[1], maxy=outerbounds[3] ) texttorender.insert(0, (r, s.get('Label').makeQPen()) ) def _autoMirrorDraw(self, posn, painter, coordticks, coordminorticks): """Mirror axis to opposite side of graph if there isn't an axis there already.""" # This is a nasty hack: must think of a better way to do this s = self.settings countaxis = 0 for c in self.parent.children: try: # don't allow descendents of axis to look like an axis # to this function (e.g. colorbar) if c.typename == 'axis' and s.direction == c.settings.direction: countaxis += 1 except AttributeError: # if it's not an axis we get here pass # another axis in the same direction, so we don't mirror it if countaxis > 1: return # swap axis to other side if s.otherPosition < 0.5: otheredge = 1. else: otheredge = 0. # temporarily change position of axis to other side for drawing self._updatePlotRange(posn, otherposition=otheredge) if not s.Line.hide: self._drawAxisLine(painter) if not s.MinorTicks.hide: self._drawMinorTicks(painter, coordminorticks) if not s.MajorTicks.hide: self._drawMajorTicks(painter, coordticks) def chooseName(self): """Get default name for axis. Make x and y axes, then axisN.""" try: widgets = set(self.parent.childnames) except AttributeError: widgets = set() for name in ('x', 'y'): if name not in widgets: return name return widget.Widget.chooseName(self) def _suppressText(self, painter, parentposn, outerbounds): """Whether to suppress drawing text on this axis because it is too close to the edge of its parent bounding box. If the edge of the plot is within textheight then suppress text """ s = self.settings height = utils.FontMetrics( s.get('Label').makeQFont(painter), painter.device()).height() otherposition = s.otherPosition if s.direction == 'vertical': if ( ( otherposition < 0.01 and abs(parentposn[0]-outerbounds[0]) < height) or ( otherposition > 0.99 and abs(parentposn[2]-outerbounds[2]) < height) ): return True else: if ( ( otherposition < 0.01 and abs(parentposn[3]-outerbounds[3]) < height) or ( otherposition > 0.99 and abs(parentposn[1]-outerbounds[1]) < height) ): return True return False def draw(self, parentposn, phelper, outerbounds=None, useexistingpainter=None): """Plot the axis on the painter. useexistingpainter is a hack so that a colorbar can reuse the drawing code here. If set to a painter, it will use this rather than opening a new one. """ s = self.settings # recompute if document modified if self.docchangeset != self.document.changeset: self._computePlottedRange() posn = widget.Widget.draw(self, parentposn, phelper, outerbounds) self._updatePlotRange(posn) # get ready to draw if useexistingpainter is not None: painter = useexistingpainter else: painter = phelper.painter(self, posn) # make control item for axis phelper.setControlGraph(self, [ controlgraph.ControlAxisLine( self, s.direction, self.coordParr1, self.coordParr2, self.coordPerp, posn) ]) # get tick vals coordticks = self._graphToPlotter(self.majortickscalc) coordminorticks = self._graphToPlotter(self.minortickscalc) # exit if axis is hidden if s.hide: return texttorender = [] # multiplication factor if reflection on the axis is requested sign = 1 if s.direction == 'vertical': sign *= -1 if self.coordReflected: sign *= -1 # plot gridlines if not s.MinorGridLines.hide: self._drawGridLines('MinorGridLines', painter, coordminorticks, parentposn) if not s.GridLines.hide: self._drawGridLines('GridLines', painter, coordticks, parentposn) # plot the line along the axis if not s.Line.hide: self._drawAxisLine(painter) # plot minor ticks if not s.MinorTicks.hide: self._drawMinorTicks(painter, coordminorticks) # keep track of distance from axis self._delta_axis = 0 # plot major ticks if not s.MajorTicks.hide: self._drawMajorTicks(painter, coordticks) # plot tick labels suppresstext = self._suppressText(painter, parentposn, outerbounds) if not s.TickLabels.hide and not suppresstext: self._drawTickLabels(phelper, painter, coordticks, sign, outerbounds, texttorender) # draw an axis label if not s.Label.hide and not suppresstext: self._drawAxisLabel(painter, sign, outerbounds, texttorender) # mirror axis at other side of plot if s.autoMirror: self._autoMirrorDraw(posn, painter, coordticks, coordminorticks) # all the text is drawn at the end so that # we can check it doesn't overlap drawntext = qt4.QPainterPath() for r, pen in texttorender: bounds = r.getBounds() rect = qt4.QRectF(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]) if not drawntext.intersects(rect): painter.setPen(pen) box = r.render() drawntext.addRect(rect) def updateControlItem(self, cgi): """Update axis position from control item.""" s = self.settings p = cgi.maxposn if cgi.zoomed(): # zoom axis scale c1, c2 = self.plotterToGraphCoords( cgi.maxposn, N.array([cgi.minzoom, cgi.maxzoom])) if c1 > c2: c1, c2 = c2, c1 operations = ( document.OperationSettingSet(s.get('min'), float(c1)), document.OperationSettingSet(s.get('max'), float(c2)), ) self.document.applyOperation( document.OperationMultiple(operations, descr='zoom axis')) elif cgi.moved(): # move axis # convert positions to fractions pt1, pt2, ppt1, ppt2 = ( (3, 1, 0, 2), (0, 2, 3, 1) ) [s.direction == 'horizontal'] minfrac = abs((cgi.minpos - p[pt1]) / (p[pt2] - p[pt1])) maxfrac = abs((cgi.maxpos - p[pt1]) / (p[pt2] - p[pt1])) axisfrac = abs((cgi.axispos - p[ppt1]) / (p[ppt2] - p[ppt1])) # swap if wrong way around if minfrac > maxfrac: minfrac, maxfrac = maxfrac, minfrac # update doc operations = ( document.OperationSettingSet(s.get('lowerPosition'), minfrac), document.OperationSettingSet(s.get('upperPosition'), maxfrac), document.OperationSettingSet(s.get('otherPosition'), axisfrac), ) self.document.applyOperation( document.OperationMultiple(operations, descr='adjust axis')) # allow the factory to instantiate an axis document.thefactory.register( Axis ) veusz-1.15/widgets/colorbar.py0000644002344000001440000002025511734662204016426 0ustar jssusers00000000000000# Copyright (C) 2007 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A colorbar widget for the image widget. Should show the scale of the image.""" import veusz.qtall as qt4 import numpy as N import veusz.document as document import veusz.setting as setting import veusz.utils as utils import graph import grid import widget import axis class ColorBar(axis.Axis): """Color bar for showing scale of image. This naturally is descended from an axis """ typename='colorbar' allowedparenttypes = [graph.Graph, grid.Grid] allowusercreation = True description = 'Image color bar' def __init__(self, parent, name=None): """Initialise object and create axes.""" axis.Axis.__init__(self, parent, name=name) if type(self) == ColorBar: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" axis.Axis.addSettings(s) s.add( setting.WidgetChoice('widgetName', '', descr='Corresponding widget', widgettypes=('image', 'xy'), usertext = 'Widget'), 0 ) s.get('log').readonly = True s.get('datascale').readonly = True s.add( setting.AlignHorzWManual( 'horzPosn', 'right', descr = 'Horizontal position', usertext='Horz posn', formatting=True) ) s.add( setting.AlignVertWManual( 'vertPosn', 'bottom', descr = 'Vertical position', usertext='Vert posn', formatting=True) ) s.add( setting.DistanceOrAuto('width', 'Auto', descr = 'Width of colorbar', usertext='Width', formatting=True) ) s.add( setting.DistanceOrAuto('height', 'Auto', descr = 'Height of colorbar', usertext='Height', formatting=True) ) s.add( setting.Float( 'horzManual', 0., descr = 'Manual horizontal fractional position', usertext='Horz manual', formatting=True) ) s.add( setting.Float( 'vertManual', 0., descr = 'Manual vertical fractional position', usertext='Vert manual', formatting=True) ) s.add( setting.Line('Border', descr = 'Colorbar border line', usertext='Border'), pixmap='settings_border') s.add( setting.SettingBackwardCompat('image', 'widgetName', None) ) def chooseName(self): """Get name of widget.""" # override axis naming of x and y return widget.Widget.chooseName(self) def draw(self, parentposn, phelper, outerbounds = None): '''Update the margins before drawing.''' s = self.settings # exit if hidden if s.hide: return # get height of label font bounds = self.computeBounds(parentposn, phelper) painter = phelper.painter(self, parentposn) font = s.get('Label').makeQFont(phelper) painter.setFont(font) fontheight = utils.FontMetrics(font, painter.device()).height() horz = s.direction == 'horizontal' # use above to estimate width and height if necessary w = s.get('width') if w.isAuto(): if horz: totalwidth = bounds[2] - bounds[0] - 2*fontheight else: totalwidth = fontheight else: totalwidth = w.convert(painter) h = s.get('height') if h.isAuto(): if horz: totalheight = fontheight else: totalheight = bounds[3] - bounds[1] - 2*fontheight else: totalheight = h.convert(painter) # work out horizontal position h = s.horzPosn if h == 'left': bounds[0] += fontheight bounds[2] = bounds[0] + totalwidth elif h == 'right': bounds[2] -= fontheight bounds[0] = bounds[2] - totalwidth elif h == 'centre': delta = (bounds[2]-bounds[0]-totalwidth)/2. bounds[0] += delta bounds[2] -= delta elif h == 'manual': bounds[0] += (bounds[2]-bounds[0])*s.horzManual bounds[2] = bounds[0] + totalwidth # work out vertical position v = s.vertPosn if v == 'top': bounds[1] += fontheight bounds[3] = bounds[1] + totalheight elif v == 'bottom': bounds[3] -= fontheight bounds[1] = bounds[3] - totalheight elif v == 'centre': delta = (bounds[3]-bounds[1]-totalheight)/2. bounds[1] += delta bounds[3] -= delta elif v == 'manual': bounds[1] += (bounds[3]-bounds[1])*s.vertManual bounds[3] = bounds[1] + totalheight # this is ugly - update bounds in helper state phelper.states[self].bounds = bounds # do no painting if hidden or no image imgwidget = s.get('widgetName').findWidget() if s.hide: return bounds # update image if necessary with new settings if imgwidget is not None: # could find widget (minval, maxval, axisscale, img) = imgwidget.makeColorbarImage(s.direction) else: # couldn't find widget minval, maxval, axisscale = 0., 1., 'linear' img = None self.setAutoRange([minval, maxval]) s.get('log').setSilent(axisscale == 'log') # now draw image on axis... minpix, maxpix = self.graphToPlotterCoords( bounds, N.array([minval, maxval]) ) if s.direction == 'horizontal': c = [ minpix, bounds[1], maxpix, bounds[3] ] else: c = [ bounds[0], maxpix, bounds[2], minpix ] r = qt4.QRectF(c[0], c[1], c[2]-c[0], c[3]-c[1]) # really draw the img if img is not None: painter.drawImage(r, img) # if there's a border if not s.Border.hide: painter.setPen( s.get('Border').makeQPen(painter) ) painter.setBrush( qt4.QBrush() ) painter.drawRect( qt4.QRectF(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]) ) # actually draw axis # we have to force position to full, as otherwise computeBounds # will mess up range if called twice savedposition = self.position self.position = (0., 0., 1., 1.) axis.Axis.draw(self, bounds, phelper, outerbounds=outerbounds, useexistingpainter=painter) self.position = savedposition # allow the factory to instantiate a colorbar document.thefactory.register( ColorBar ) veusz-1.15/widgets/plotters.py0000644002344000001440000001715311734662204016502 0ustar jssusers00000000000000# plotters.py # plotting classes # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """A generic plotter widget which is inherited by function and point.""" import veusz.qtall as qt4 import numpy as N import veusz.setting as setting import widget import graph import page class GenericPlotter(widget.Widget): """Generic plotter.""" typename='genericplotter' allowedparenttypes=[graph.Graph] isplotter = True def __init__(self, parent, name=None): """Initialise object, setting axes.""" widget.Widget.__init__(self, parent, name=name) @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add( setting.Str('key', '', descr = 'Description of the plotted data to appear in key', usertext='Key text') ) s.add( setting.Axis('xAxis', 'x', 'horizontal', descr = 'Name of X-axis to use', usertext='X axis') ) s.add( setting.Axis('yAxis', 'y', 'vertical', descr = 'Name of Y-axis to use', usertext='Y axis') ) def getAxesNames(self): """Returns names of axes used.""" s = self.settings return (s.xAxis, s.yAxis) def lookupAxis(self, axisname): """Find widget associated with axisname.""" w = self.parent while w: for c in w.children: if c.name == axisname and hasattr(c, 'isaxis'): return c w = w.parent return None def providesAxesDependency(self): """Returns information on the following axes. format is ( ('x', 'sx'), ('y', 'sy') ) where key is the axis and value is a provided bound """ return () def requiresAxesDependency(self): """Requires information about the axis given before providing information. Format (('sx': 'x'), ('sy': 'y')) """ return () def updateAxisRange(self, axis, depname, range): """Update range variable for axis with dependency name given.""" pass def getNumberKeys(self): """Return number of key entries.""" if self.settings.key: return 1 else: return 0 def getKeyText(self, number): """Get key entry.""" return self.settings.key def drawKeySymbol(self, number, painter, x, y, width, height): """Draw the plot symbol and/or line at (x,y) in a box width*height. This is used to plot a key """ pass def clipAxesBounds(self, axes, bounds): """Returns clipping rectange for start and stop values of axis.""" # update cached coordinates of axes axes[0].plotterToDataCoords(bounds, N.array([])) axes[1].plotterToDataCoords(bounds, N.array([])) # get range x1, x2 = axes[0].coordParr1, axes[0].coordParr2 if x1 > x2: x1, x2 = x2, x1 y1, y2 = axes[1].coordParr2, axes[1].coordParr1 if y1 > y2: y1, y2 = y2, y1 # actually clip the data cliprect = qt4.QRectF(qt4.QPointF(x1, y1), qt4.QPointF(x2, y2)) return cliprect def getAxisLabels(self, direction): """Get labels for datapoints and coordinates, or None if none. direction is 'horizontal' or 'vertical' return (labels, coordinates) """ return (None, None) class FreePlotter(widget.Widget): """A plotter which can be plotted on the page or in a graph.""" allowedparenttypes = [graph.Graph, page.Page] def __init__(self, parent, name=None): """Initialise object, setting axes.""" widget.Widget.__init__(self, parent, name=name) @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add( setting.DatasetOrFloatList('xPos', [0.5], descr='List of fractional X ' 'coordinates or dataset', usertext='X positions', formatting=False) ) s.add( setting.DatasetOrFloatList('yPos', [0.5], descr='List of fractional Y ' 'coordinates or dataset', usertext='Y positions', formatting=False) ) s.add( setting.Choice('positioning', ['axes', 'relative'], 'relative', descr='Use axes or fractional ' 'position to place label', usertext='Position mode', formatting=False) ) s.add( setting.Axis('xAxis', 'x', 'horizontal', descr = 'Name of X-axis to use', usertext='X axis') ) s.add( setting.Axis('yAxis', 'y', 'vertical', descr = 'Name of Y-axis to use', usertext='Y axis') ) def _getPlotterCoords(self, posn): """Calculate coordinates from relative or axis positioning.""" s = self.settings xpos = s.get('xPos').getFloatArray(self.document) ypos = s.get('yPos').getFloatArray(self.document) if xpos is None or ypos is None: return None, None if s.positioning == 'axes': if hasattr(self.parent, 'getAxes'): axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) else: return None, None if None in axes: return None, None xpos = axes[0].dataToPlotterCoords(posn, xpos) ypos = axes[1].dataToPlotterCoords(posn, ypos) else: xpos = posn[0] + (posn[2]-posn[0])*xpos ypos = posn[3] - (posn[3]-posn[1])*ypos return xpos, ypos def _getGraphCoords(self, posn, xplt, yplt): """Calculate graph coodinates given plot coordinates xplt, yplt.""" s = self.settings if s.positioning == 'axes': if hasattr(self.parent, 'getAxes'): axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) else: return None, None if None in axes: return None, None xpos = axes[0].plotterToDataCoords(posn, N.array(xplt)) ypos = axes[1].plotterToDataCoords(posn, N.array(yplt)) else: xpos = (xplt - posn[0]) / (posn[2]-posn[0]) ypos = (yplt - posn[3]) / (posn[1]-posn[3]) return xpos, ypos veusz-1.15/widgets/vectorfield.py0000644002344000001440000002137511734662204017135 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import itertools import numpy as N import veusz.setting as setting import veusz.document as document import veusz.utils as utils import veusz.qtall as qt4 import plotters class VectorField(plotters.GenericPlotter): '''A plotter for plotting a vector field.''' typename = 'vectorfield' allowusercreation = True description = 'Plot a vector field' def __init__(self, parent, name=None): """Initialse vector field plotter.""" plotters.GenericPlotter.__init__(self, parent, name=name) if type(self) == VectorField: self.readDefaults() @classmethod def addSettings(klass, s): '''Construct list of settings.''' plotters.GenericPlotter.addSettings(s) # datasets s.add( setting.Dataset('data1', '', dimensions = 2, descr = 'X coordinate length or vector magnitude', usertext = u'δx or r'), 0 ) s.add( setting.Dataset('data2', '', dimensions = 2, descr = 'Y coordinate length or vector angle', usertext = u'δy or θ'), 1 ) s.add( setting.Choice('mode', ['cartesian', 'polar'], 'cartesian', descr = u'Cartesian (δx,δy) or polar (r,θ)', usertext = 'Mode'), 2 ) # formatting s.add( setting.DistancePt('baselength', '10pt', descr = "Base length of unit vector", usertext = "Base length", formatting=True), 0 ) s.add( setting.DistancePt('arrowsize', '2pt', descr = "Size of any arrows", usertext = "Arrow size", formatting=True), 1 ) s.add( setting.Bool('scalearrow', True, descr = 'Scale arrow head by length', usertext = 'Scale arrow', formatting=True), 2 ) s.add( setting.Arrow('arrowfront', 'none', descr = 'Arrow in front direction', usertext='Arrow front', formatting=True), 3) s.add( setting.Arrow('arrowback', 'none', descr = 'Arrow in back direction', usertext='Arrow back', formatting=True), 4) s.add( setting.Line('Line', descr = 'Line style', usertext = 'Line'), pixmap = 'settings_plotline' ) s.add( setting.ArrowFill('Fill', descr = 'Arrow fill settings', usertext = 'Arrow fill'), pixmap = 'settings_plotmarkerfill' ) def providesAxesDependency(self): """Range information provided by widget.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def updateAxisRange(self, axis, depname, axrange): """Automatically determine the ranges of variable on the axes.""" for name in (self.settings.data1, self.settings.data2): try: data = self.document.data[name] except KeyError: continue if data.dimensions == 2: if depname == 'sx': dxrange = data.xrange axrange[0] = min( axrange[0], dxrange[0] ) axrange[1] = max( axrange[1], dxrange[1] ) elif depname == 'sy': dyrange = data.yrange axrange[0] = min( axrange[0], dyrange[0] ) axrange[1] = max( axrange[1], dyrange[1] ) def drawKeySymbol(self, number, painter, x, y, width, height): """Draw the plot symbol and/or line.""" painter.save() s = self.settings painter.setPen( s.Line.makeQPenWHide(painter) ) painter.setBrush( s.get('Fill').makeQBrushWHide() ) utils.plotLineArrow(painter, x+width, y+height*0.5, width, 180, height*0.25, arrowleft=s.arrowfront, arrowright=s.arrowback) painter.restore() def draw(self, parentposn, phelper, outerbounds = None): """Draw the widget.""" posn = plotters.GenericPlotter.draw(self, parentposn, phelper, outerbounds = outerbounds) x1, y1, x2, y2 = posn s = self.settings d = self.document # hide if hidden! if s.hide: return # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there's no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return # ignore non existing datasets try: data1 = d.data[s.data1] data2 = d.data[s.data2] except KeyError: return # require 2d datasets if data1.dimensions != 2 or data2.dimensions != 2: return # clip data within bounds of plotter cliprect = self.clipAxesBounds(axes, posn) painter = phelper.painter(self, posn, clip=cliprect) baselength = s.get('baselength').convert(painter) # try to be nice if the datasets don't match data1st, data2nd = data1.data, data2.data xw = min(data1st.shape[1], data2nd.shape[1]) yw = min(data1st.shape[0], data2nd.shape[0]) # construct indices into datasets yvals, xvals = N.mgrid[0:yw, 0:xw] # convert using 1st dataset to axes values xdsvals, ydsvals = data1.indexToPoint(xvals.ravel(), yvals.ravel()) # convert using axes to plotter values xplotter = axes[0].dataToPlotterCoords(posn, xdsvals) yplotter = axes[1].dataToPlotterCoords(posn, ydsvals) pen = s.Line.makeQPenWHide(painter) painter.setPen(pen) if s.mode == 'cartesian': dx = (data1st[:yw, :xw] * baselength).ravel() dy = (data2nd[:yw, :xw] * baselength).ravel() elif s.mode == 'polar': r = data1st[:yw, :xw].ravel() * baselength theta = data2nd[:yw, :xw].ravel() dx = r * N.cos(theta) dy = r * N.sin(theta) x1, x2 = xplotter-dx, xplotter+dx y1, y2 = yplotter+dy, yplotter-dy if s.arrowfront == 'none' and s.arrowback == 'none': utils.plotLinesToPainter(painter, x1, y1, x2, y2, cliprect) else: arrowsize = s.get('arrowsize').convert(painter) painter.setBrush( s.get('Fill').makeQBrushWHide() ) # this is backward - have to convert from dx, dy to angle, length angles = 180 - N.arctan2(dy, dx) * (180./N.pi) lengths = N.sqrt(dx**2+dy**2) * 2 # scale arrow heads by arrow length if requested if s.scalearrow: arrowsizes = (arrowsize/baselength/2) * lengths else: arrowsizes = N.zeros(lengths.shape) + arrowsize for x, y, l, a, asize in itertools.izip(x2, y2, lengths, angles, arrowsizes): utils.plotLineArrow(painter, x, y, l, a, asize, arrowleft=s.arrowfront, arrowright=s.arrowback) # allow the factory to instantiate a vector field document.thefactory.register( VectorField ) veusz-1.15/widgets/shape.py0000644002344000001440000003246611734662204015732 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For plotting shapes.""" import itertools import os import veusz.qtall as qt4 import veusz.setting as setting import veusz.document as document import veusz.utils as utils import widget import controlgraph import plotters class Shape(plotters.FreePlotter): """A shape on a page/graph.""" def __init__(self, parent, name=None): plotters.FreePlotter.__init__(self, parent, name=name) @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.FreePlotter.addSettings(s) s.add( setting.ShapeFill('Fill', descr = 'Shape fill', usertext='Fill'), pixmap = 'settings_bgfill' ) s.add( setting.Line('Border', descr = 'Shape border', usertext='Border'), pixmap = 'settings_border' ) class BoxShape(Shape): """For drawing box-like shapes.""" def __init__(self, parent, name=None): Shape.__init__(self, parent, name=name) @classmethod def addSettings(klass, s): """Construct list of settings.""" Shape.addSettings(s) s.add( setting.DatasetOrFloatList('width', [0.1], descr='List of fractional ' 'widths or dataset', usertext='Widths', formatting=False), 3 ) s.add( setting.DatasetOrFloatList('height', [0.1], descr='List of fractional ' 'heights or dataset', usertext='Heights', formatting=False), 4 ) s.add( setting.DatasetOrFloatList('rotate', [0.], descr='Rotation angles of ' 'shape or dataset', usertext='Rotate', formatting=False), 5 ) def drawShape(self, painter, rect): pass def draw(self, posn, phelper, outerbounds = None): """Plot the key on a plotter.""" s = self.settings d = self.document if s.hide: return # get positions of shapes width = s.get('width').getFloatArray(d) height = s.get('height').getFloatArray(d) rotate = s.get('rotate').getFloatArray(d) if width is None or height is None or rotate is None: return # translate coordinates from axes or relative values xpos, ypos = self._getPlotterCoords(posn) if xpos is None or ypos is None: # we can't calculate coordinates return # if a dataset is used, we can't use control items isnotdataset = ( not s.get('xPos').isDataset(d) and not s.get('yPos').isDataset(d) and not s.get('width').isDataset(d) and not s.get('height').isDataset(d) and not s.get('rotate').isDataset(d) ) controlgraphitems = [] painter = phelper.painter(self, posn) # drawing settings for shape if not s.Border.hide: painter.setPen( s.get('Border').makeQPen(painter) ) else: painter.setPen( qt4.QPen(qt4.Qt.NoPen) ) # iterate over positions index = 0 dx, dy = posn[2]-posn[0], posn[3]-posn[1] for x, y, w, h, r in itertools.izip(xpos, ypos, itertools.cycle(width), itertools.cycle(height), itertools.cycle(rotate)): wp, hp = dx*w, dy*h painter.save() painter.translate(x, y) if r != 0: painter.rotate(r) self.drawShape(painter, qt4.QRectF(-wp*0.5, -hp*0.5, wp, hp)) painter.restore() if isnotdataset: cgi = controlgraph.ControlResizableBox( self, [x, y], [wp, hp], r, allowrotate=True) cgi.index = index cgi.widgetposn = posn index += 1 controlgraphitems.append(cgi) phelper.setControlGraph(self, controlgraphitems) def updateControlItem(self, cgi): """If control item is moved or resized, this is called.""" s = self.settings # calculate new position coordinate for item xpos, ypos = self._getGraphCoords(cgi.widgetposn, cgi.posn[0], cgi.posn[1]) if xpos is None or ypos is None: return xw = abs(cgi.dims[0] / (cgi.widgetposn[2]-cgi.widgetposn[0])) yw = abs(cgi.dims[1] / (cgi.widgetposn[1]-cgi.widgetposn[3])) # actually do the adjustment on the document xp, yp = list(s.xPos), list(s.yPos) w, h, r = list(s.width), list(s.height), list(s.rotate) xp[cgi.index] = xpos yp[cgi.index] = ypos w[min(cgi.index, len(w)-1)] = xw h[min(cgi.index, len(h)-1)] = yw r[min(cgi.index, len(r)-1)] = cgi.angle operations = ( document.OperationSettingSet(s.get('xPos'), xp), document.OperationSettingSet(s.get('yPos'), yp), document.OperationSettingSet(s.get('width'), w), document.OperationSettingSet(s.get('height'), h), document.OperationSettingSet(s.get('rotate'), r) ) self.document.applyOperation( document.OperationMultiple(operations, descr='adjust shape') ) class Rectangle(BoxShape): """Draw a rectangle, or rounded rectangle.""" typename = 'rect' description = 'Rectangle' allowusercreation = True def __init__(self, parent, name=None): BoxShape.__init__(self, parent, name=name) if type(self) == Rectangle: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" BoxShape.addSettings(s) s.add( setting.Int('rounding', 0, minval=0, maxval=100, descr='Round corners with this percentage', usertext='Rounding corners', formatting=True) ) def drawShape(self, painter, rect): s = self.settings path = qt4.QPainterPath() if s.rounding == 0: path.addRect(rect) else: path.addRoundedRect(rect, s.rounding, s.rounding) utils.brushExtFillPath(painter, s.Fill, path, stroke=painter.pen()) class Ellipse(BoxShape): """Draw an ellipse.""" typename = 'ellipse' description = 'Ellipse' allowusercreation = True def __init__(self, parent, name=None): BoxShape.__init__(self, parent, name=name) if type(self) == Ellipse: self.readDefaults() def drawShape(self, painter, rect): s = self.settings path = qt4.QPainterPath() path.addEllipse(rect) utils.brushExtFillPath(painter, s.Fill, path, stroke=painter.pen()) class ImageFile(BoxShape): """Draw an image.""" typename = 'imagefile' description = 'Image file' allowusercreation = True def __init__(self, parent, name=None): BoxShape.__init__(self, parent, name=name) if type(self) == ImageFile: self.readDefaults() self.cacheimage = None self.cachefilename = None self.cachestat = None self.cacheembeddata = None self.addAction( widget.Action('embed', self.actionEmbed, descr = 'Embed image in Veusz document ' 'to remove dependency on external file', usertext = 'Embed image') ) @classmethod def addSettings(klass, s): """Construct list of settings.""" BoxShape.addSettings(s) s.add( setting.ImageFilename('filename', '', descr='Image filename', usertext='Filename', formatting=False), posn=0 ) s.add( setting.Str('embeddedImageData', '', descr='Embedded base 64-encoded image data, ' 'used if filename set to {embedded}', usertext='Embedded data', hidden=True) ) s.add( setting.Bool('aspect', True, descr='Preserve aspect ratio', usertext='Preserve aspect', formatting=True), posn=0 ) s.Border.get('hide').newDefault(True) def actionEmbed(self): """Embed external image into veusz document.""" s = self.settings if s.filename == '{embedded}': print "Data already embedded" return # get data from external file try: f = open(s.filename, 'rb') data = f.read() f.close() except EnvironmentError: print "Could not find file. Not embedding." return # convert to base 64 to make it nicer in the saved file encoded = str(qt4.QByteArray(data).toBase64()) # now put embedded data in hidden setting ops = [ document.OperationSettingSet(s.get('filename'), '{embedded}'), document.OperationSettingSet(s.get('embeddedImageData'), encoded) ] self.document.applyOperation( document.OperationMultiple(ops, descr='embed image') ) def updateCachedImage(self): """Update cache.""" s = self.settings self.cachestat = os.stat(s.filename) self.cacheimage = qt4.QImage(s.filename) self.cachefilename = s.filename def updateCachedEmbedded(self): """Update cached image from embedded data.""" s = self.settings self.cacheimage = qt4.QImage() # convert the embedded data from base64 and load into the image decoded = qt4.QByteArray.fromBase64(s.embeddedImageData) self.cacheimage.loadFromData(decoded) # we cache the data we have decoded self.cacheembeddata = s.embeddedImageData def drawShape(self, painter, rect): """Draw image.""" s = self.settings # draw border and fill painter.drawRect(rect) # check to see whether image needs reloading image = None if s.filename != '' and os.path.isfile(s.filename): if (self.cachefilename != s.filename or os.stat(s.filename) != self.cachestat): # update the image cache self.updateCachedImage() # clear any embedded image data self.settings.get('embeddedImageData').set('') image = self.cacheimage # or needs recreating from embedded data if s.filename == '{embedded}': if s.embeddedImageData is not self.cacheembeddata: self.updateCachedEmbedded() image = self.cacheimage # if no image, then use default image if ( not image or image.isNull() or image.width() == 0 or image.height() == 0 ): # load replacement image fname = os.path.join(utils.imagedir, 'button_imagefile.svg') r = qt4.QSvgRenderer(fname) r.render(painter, rect) else: # image rectangle irect = qt4.QRectF(image.rect()) # preserve aspect ratio if s.aspect: xr = rect.width() / irect.width() yr = rect.height() / irect.height() if xr > yr: rect = qt4.QRectF( rect.left()+(rect.width()-irect.width()*yr)*0.5, rect.top(), irect.width()*yr, rect.height()) else: rect = qt4.QRectF( rect.left(), rect.top()+(rect.height()-irect.height()*xr)*0.5, rect.width(), irect.height()*xr) # finally draw image painter.drawImage(rect, image, irect) document.thefactory.register( Ellipse ) document.thefactory.register( Rectangle ) document.thefactory.register( ImageFile ) veusz-1.15/widgets/contour.py0000644002344000001440000005527311734662204016324 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Contour plotting from 2d datasets. Contour plotting requires that the veusz_helpers package is installed, as a C routine (taken from matplotlib) is used to trace the contours. """ from itertools import izip import sys import veusz.qtall as qt4 import numpy as N import veusz.setting as setting import veusz.document as document import veusz.utils as utils import plotters def finitePoly(poly): """Remove non-finite coordinates from numpy arrays of coordinates.""" out = [] for line in poly: finite = N.isfinite(line) validrows = N.logical_and(finite[:,0], finite[:,1]) out.append( line[validrows] ) return out class ContourFills(setting.Settings): """Settings for contour fills.""" def __init__(self, name, **args): setting.Settings.__init__(self, name, **args) self.add( setting.FillSet( 'fills', [], descr = 'Fill styles to plot between contours', usertext='Fill styles', formatting=True) ) self.add( setting.Bool('hide', False, descr = 'Hide fills', usertext = 'Hide', formatting = True) ) class ContourLines(setting.Settings): """Settings for contour lines.""" def __init__(self, name, **args): setting.Settings.__init__(self, name, **args) self.add( setting.LineSet( 'lines', [('solid', '1pt', 'black', False)], descr = 'Line styles to plot the contours ' 'using', usertext='Line styles', formatting=True) ) self.add( setting.Bool('hide', False, descr = 'Hide lines', usertext = 'Hide', formatting = True) ) class SubContourLines(setting.Settings): """Sub-dividing contour line settings.""" def __init__(self, name, **args): setting.Settings.__init__(self, name, **args) self.add( setting.LineSet( 'lines', [('dot1', '1pt', 'black', False)], descr = 'Line styles used for sub-contours', usertext='Line styles', formatting=True) ) self.add( setting.Int('numLevels', 5, minval=2, descr='Number of sub-levels to plot between ' 'each contour', usertext='Levels') ) self.add( setting.Bool('hide', True, descr='Hide lines', usertext='Hide', formatting=True) ) class ContourLabel(setting.Text): """For tick labels on axes.""" def __init__(self, name, **args): setting.Text.__init__(self, name, **args) self.add( setting.Str( 'format', '%.3Vg', descr = 'Format of the tick labels', usertext='Format') ) self.add( setting.Float('scale', 1., descr='A scale factor to apply to the values ' 'of the tick labels', usertext='Scale') ) self.get('hide').newDefault(True) class Contour(plotters.GenericPlotter): """A class which plots contours on a graph with a specified coordinate system.""" typename='contour' allowusercreation=True description='Plot a 2d dataset as contours' def __init__(self, parent, name=None): """Initialise plotter with axes.""" plotters.GenericPlotter.__init__(self, parent, name=name) # try to import contour helpers here Cntr = None try: from veusz.helpers._nc_cntr import Cntr except ImportError: print >>sys.stderr,('WARNING: Veusz cannot import contour module\n' 'Please run python setup.py build\n' 'Contour support is disabled') self.Cntr = Cntr # keep track of settings so we recalculate when necessary self.lastdataset = None self.contsettings = None # cached traced contours self._cachedcontours = None self._cachedpolygons = None self._cachedsubcontours = None if type(self) == Contour: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.GenericPlotter.addSettings(s) s.add( setting.Dataset('data', '', dimensions = 2, descr = 'Dataset to plot', usertext='Dataset'), 0 ) s.add( setting.FloatOrAuto('min', 'Auto', descr = 'Minimum value of contour scale', usertext='Min. value'), 1 ) s.add( setting.FloatOrAuto('max', 'Auto', descr = 'Maximum value of contour scale', usertext='Max. value'), 2 ) s.add( setting.Int('numLevels', 5, minval = 1, descr = 'Number of contour levels to plot', usertext='Number levels'), 3 ) s.add( setting.Choice('scaling', ['linear', 'sqrt', 'log', 'squared', 'manual'], 'linear', descr = 'Scaling between contour levels', usertext='Scaling'), 4 ) s.add( setting.FloatList('manualLevels', [], descr = 'Levels to use for manual scaling', usertext='Manual levels'), 5 ) s.add( setting.Bool('keyLevels', False, descr='Show levels in key', usertext='Levels in key'), 6 ) s.add( setting.FloatList('levelsOut', [], descr = 'Levels used in the plot', usertext='Output levels'), 7, readonly=True ) s.add( ContourLabel('ContourLabels', descr = 'Contour label settings', usertext = 'Contour labels'), pixmap = 'settings_axisticklabels' ) s.add( ContourLines('Lines', descr='Contour lines', usertext='Contour lines'), pixmap = 'settings_contourline' ) s.add( ContourFills('Fills', descr='Fill within contours', usertext='Contour fills'), pixmap = 'settings_contourfill' ) s.add( SubContourLines('SubLines', descr='Sub-contour lines', usertext='Sub-contour lines'), pixmap = 'settings_subcontourline' ) s.add( setting.SettingBackwardCompat('lines', 'Lines/lines', None) ) s.add( setting.SettingBackwardCompat('fills', 'Fills/fills', None) ) s.remove('key') def _getUserDescription(self): """User friendly description.""" s = self.settings out = [] if s.data: out.append( s.data ) if s.scaling == 'manual': out.append('manual levels (%s)' % ( ', '.join([str(i) for i in s.manualLevels]))) else: out.append('%(numLevels)i %(scaling)s levels (%(min)s to %(max)s)' % s) return ', '.join(out) userdescription = property(_getUserDescription) def calculateLevels(self): """Calculate contour levels from data and settings. Returns levels as 1d numpy """ # get dataset s = self.settings d = self.document minval, maxval = 0., 1. if s.data in d.data: # scan data data = d.data[s.data].data minval, maxval = N.nanmin(data), N.nanmax(data) if not N.isfinite(minval): minval = 0. if not N.isfinite(maxval): maxval = 1. # override if not auto if s.min != 'Auto': minval = s.min if s.max != 'Auto': maxval = s.max numlevels = s.numLevels scaling = s.scaling if numlevels == 1 and scaling != 'manual': # calculations below assume numlevels > 1 levels = N.array([minval,]) else: # trap out silly cases if minval == maxval: minval = 0. maxval = 1. # calculate levels for each scaling if scaling == 'linear': delta = (maxval - minval) / (numlevels-1) levels = minval + N.arange(numlevels)*delta elif scaling == 'sqrt': delta = N.sqrt(maxval - minval) / (numlevels-1) levels = minval + (N.arange(numlevels)*delta)**2 elif scaling == 'log': if minval == 0.: minval = 1. if minval == maxval: maxval = minval + 1 delta = N.log(maxval/minval) / (numlevels-1) levels = N.exp(N.arange(numlevels)*delta)*minval elif scaling == 'squared': delta = (maxval - minval)**2 / (numlevels-1) levels = minval + N.sqrt(N.arange(numlevels)*delta) else: # manual levels = N.array(s.manualLevels) # for the user later # we do this to convert array to list of floats s.levelsOut = [float(i) for i in levels] return minval, maxval, levels def calculateSubLevels(self, minval, maxval, levels): """Calculate sublevels between contours.""" s = self.settings num = s.SubLines.numLevels if s.SubLines.hide or len(s.SubLines.lines) == 0 or len(levels) <= 1: return N.array([]) # indices where contour levels should be placed numcont = (len(levels)-1) * num indices = N.arange(numcont) indices = indices[indices % num != 0] scaling = s.scaling if scaling == 'linear': delta = (maxval-minval) / numcont slev = indices*delta + minval elif scaling == 'log': delta = N.log( maxval/minval ) / numcont slev = N.exp(indices*delta) * minval elif scaling == 'sqrt': delta = N.sqrt( maxval-minval ) / numcont slev = minval + (indices*delta)**2 elif scaling == 'squared': delta = (maxval-minval)**2 / numcont slev = minval + N.sqrt(indices*delta) elif scaling == 'manual': drange = N.arange(1, num) out = [[]] for conmin, conmax in izip(levels[:-1], levels[1:]): delta = (conmax-conmin) / num out.append( conmin+drange*delta ) slev = N.hstack(out) return slev def providesAxesDependency(self): """Range information provided by widget.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def updateAxisRange(self, axis, depname, axrange): """Automatically determine the ranges of variable on the axes.""" # this is copied from Image, probably should combine s = self.settings d = self.document # return if no data or if the dataset isn't two dimensional data = d.data.get(s.data, None) if data is None or data.dimensions != 2: return if depname == 'sx': dxrange = data.xrange axrange[0] = min( axrange[0], dxrange[0] ) axrange[1] = max( axrange[1], dxrange[1] ) elif depname == 'sy': dyrange = data.yrange axrange[0] = min( axrange[0], dyrange[0] ) axrange[1] = max( axrange[1], dyrange[1] ) def getNumberKeys(self): """How many keys to show.""" self.checkContoursUpToDate() if self.settings.keyLevels: return len( self.settings.levelsOut ) else: return 0 def getKeyText(self, number): """Get key entry.""" s = self.settings if s.keyLevels: cl = s.get('ContourLabels') return utils.formatNumber( s.levelsOut[number] * cl.scale, cl.format, locale=self.document.locale ) else: return '' def drawKeySymbol(self, number, painter, x, y, width, height): """Draw key for contour level.""" painter.setPen( self.settings.Lines.get('lines').makePen(painter, number)) painter.drawLine(x, y+height/2, x+width, y+height/2) def checkContoursUpToDate(self): """Update contours if necessary. Returns True if okay to plot contours, False if error """ s = self.settings d = self.document # return if no data or if the dataset isn't two dimensional data = d.data.get(s.data, None) if data is None or data.dimensions != 2 or data.data.size == 0: self.contsettings = self.lastdataset = None s.levelsOut = [] return False contsettings = ( s.min, s.max, s.numLevels, s.scaling, s.SubLines.numLevels, len(s.Fills.fills) == 0 or s.Fills.hide, len(s.SubLines.lines) == 0 or s.SubLines.hide, tuple(s.manualLevels) ) if data is not self.lastdataset or contsettings != self.contsettings: self.updateContours() self.lastdataset = data self.contsettings = contsettings return True def draw(self, parentposn, phelper, outerbounds = None): """Draw the contours.""" posn = plotters.GenericPlotter.draw(self, parentposn, phelper, outerbounds = outerbounds) s = self.settings # do not paint if hidden if s.hide: return # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there's no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return # update contours if necessary if not self.checkContoursUpToDate(): return # plot the precalculated contours clip = self.clipAxesBounds(axes, posn) painter = phelper.painter(self, posn, clip=clip) self.plotContourFills(painter, posn, axes, clip) self.plotContours(painter, posn, axes, clip) self.plotSubContours(painter, posn, axes, clip) def updateContours(self): """Update calculated contours.""" s = self.settings d = self.document minval, maxval, levels = self.calculateLevels() sublevels = self.calculateSubLevels(minval, maxval, levels) # find coordinates of image coordinate bounds data = d.data[s.data] rangex, rangey = data.getDataRanges() yw, xw = data.data.shape if xw == 0 or yw == 0: return # arrays containing coordinates of pixels in x and y xpts = N.fromfunction(lambda y,x: (x+0.5)*((rangex[1]-rangex[0])/xw) + rangex[0], (yw, xw)) ypts = N.fromfunction(lambda y,x: (y+0.5)*((rangey[1]-rangey[0])/yw) + rangey[0], (yw, xw)) # only keep finite data points mask = N.logical_not(N.isfinite(data.data)) # iterate over the levels and trace the contours self._cachedcontours = None self._cachedpolygons = None self._cachedsubcontours = None if self.Cntr is not None: c = self.Cntr(xpts, ypts, data.data, mask) # trace the contour levels if len(s.Lines.lines) != 0: self._cachedcontours = [] for level in levels: linelist = c.trace(level) self._cachedcontours.append( finitePoly(linelist) ) # trace the polygons between the contours if len(s.Fills.fills) != 0 and len(levels) > 1 and not s.Fills.hide: self._cachedpolygons = [] for level1, level2 in izip(levels[:-1], levels[1:]): linelist = c.trace(level1, level2) self._cachedpolygons.append( finitePoly(linelist) ) # trace sub-levels if len(sublevels) > 0: self._cachedsubcontours = [] for level in sublevels: linelist = c.trace(level) self._cachedsubcontours.append( finitePoly(linelist) ) def plotContourLabel(self, painter, number, xplt, yplt, showline): """Draw a label on a contour. This clips when drawing the line, plotting the label on top. """ s = self.settings cl = s.get('ContourLabels') painter.save() # get text and font text = utils.formatNumber(number * cl.scale, cl.format, locale=self.document.locale) font = cl.makeQFont(painter) descent = utils.FontMetrics(font, painter.device()).descent() # work out where text lies half = len(xplt)/2 hx, hy = xplt[half], yplt[half] r = utils.Renderer(painter, font, hx, hy, text, alignhorz=0, alignvert=0, angle=0) bounds = r.getBounds() # heuristics of when to plot label # we try to only plot label if underlying line is long enough height = bounds[3]-bounds[1] showtext = ( height*1.5 < (yplt.max() - yplt.min()) or (height*4 < (xplt.max() - xplt.min())) ) if showtext: # clip region containing text oldclip = painter.clipRegion() cr = oldclip - qt4.QRegion( bounds[0]-descent, bounds[1]-descent, bounds[2]-bounds[0]+descent*2, bounds[3]-bounds[1]+descent*2 ) painter.setClipRegion(cr) # draw lines if showline: pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, xplt, yplt) painter.drawPolyline(pts) # actually plot the label if showtext: painter.setClipRegion(oldclip) painter.setPen( cl.makeQPen() ) r.render() painter.restore() def _plotContours(self, painter, posn, axes, linestyles, contours, showlabels, hidelines, clip): """Plot a set of contours. """ s = self.settings # no lines cached as no line styles if contours is None: return # iterate over each level, and list of lines for num, linelist in enumerate(contours): # move to the next line style painter.setPen(linestyles.makePen(painter, num)) # iterate over each complete line of the contour for curve in linelist: # convert coordinates from graph to plotter xplt = axes[0].dataToPlotterCoords(posn, curve[:,0]) yplt = axes[1].dataToPlotterCoords(posn, curve[:,1]) pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, xplt, yplt) if showlabels: self.plotContourLabel(painter, s.levelsOut[num], xplt, yplt, not hidelines) else: # actually draw the curve to the plotter if not hidelines: utils.plotClippedPolyline(painter, clip, pts) def plotContours(self, painter, posn, axes, clip): """Plot the traced contours on the painter.""" s = self.settings self._plotContours(painter, posn, axes, s.Lines.get('lines'), self._cachedcontours, not s.ContourLabels.hide, s.Lines.hide, clip) def plotSubContours(self, painter, posn, axes, clip): """Plot sub contours on painter.""" s = self.settings self._plotContours(painter, posn, axes, s.SubLines.get('lines'), self._cachedsubcontours, False, s.SubLines.hide, clip) def plotContourFills(self, painter, posn, axes, clip): """Plot the traced contours on the painter.""" s = self.settings # don't draw if there are no cached polygons if self._cachedpolygons is None or s.Fills.hide: return # iterate over each level, and list of lines for num, polylist in enumerate(self._cachedpolygons): # iterate over each complete line of the contour path = qt4.QPainterPath() for poly in polylist: # convert coordinates from graph to plotter xplt = axes[0].dataToPlotterCoords(posn, poly[:,0]) yplt = axes[1].dataToPlotterCoords(posn, poly[:,1]) pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, xplt, yplt) clippedpoly = qt4.QPolygonF() utils.polygonClip(pts, clip, clippedpoly) path.addPolygon(clippedpoly) # fill polygons brush = s.Fills.get('fills').returnBrushExtended(num) utils.brushExtFillPath(painter, brush, path) # allow the factory to instantiate a contour document.thefactory.register( Contour ) veusz-1.15/widgets/page.py0000644002344000001440000002205111734662204015533 0ustar jssusers00000000000000# Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Widget that represents a page in the document.""" import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import widget import root import controlgraph # x, y, fplot, xyplot # x-> xyplot(x) # y-> xyplot(y) # fplot(y) -> x # y -> fplot(y) # x -> xyplot(x) # y -> (xyplot(y), fplot(y) -> x) defaultrange = [1e99, -1e99] class _AxisDependHelper(object): """A class to work out the dependency of widgets on axes and vice versa, in terms of ranges of the axes. It then works out the ranges for each of the axes from the plotters. """ def __init__(self, root): self.root = root self.nodes = {} self.axes = [] self.axis_plotter_map = {} def recursivePlotterSearch(self, widget): """Find a list of plotters below widget. Builds up a dict of "nodes" representing each widget: plotter/axis Each node is a list of tuples saying which widgets need evaling first The tuples are (widget, depname), where depname is a name for the part of the plotter, e.g. "sx" or "sy" for x or y. """ if hasattr(widget, 'isplotter'): nodes = self.nodes # keep track of which widgets depend on which axes widgetaxes = {} for axname in widget.getAxesNames(): axis = widget.lookupAxis(axname) widgetaxes[axname] = axis if axis not in self.axis_plotter_map: self.axis_plotter_map[axis] = [] self.axis_plotter_map[axis].append(widget) # if the widget is a plotter, find which axes the plotter can # provide range information about for axname, depname in widget.providesAxesDependency(): axis = widgetaxes[axname] axdep = (axis, None) if axdep not in nodes: nodes[axdep] = [] nodes[axdep].append( (widget, depname) ) # find which axes the plotter needs information from for depname, axname in widget.requiresAxesDependency(): axis = widgetaxes[axname] widdep = (widget, depname) if widdep not in nodes: nodes[widdep] = [] nodes[widdep].append( (axis, None) ) elif hasattr(widget, 'isaxis'): # it is an axis, so keep track of it if hasattr(widget, 'isaxis'): self.axes.append(widget) else: # otherwise search children for c in widget.children: self.recursivePlotterSearch(c) def findPlotters(self): """Construct a list of plotters associated with each axis. Returns nodes: {axisobject: [plotterobject, plotter2...]), ...} """ self.recursivePlotterSearch(self.root) self.ranges = dict( [(a, list(defaultrange)) for a in self.axes] ) def processDepends(self, widget, depends): """Go through dependencies of widget. If the dependency has no dependency itself, then update the axis with the widget or vice versa """ modified = False i = 0 while i < len(depends): dep = depends[i] if dep not in self.nodes: dwidget, dwidget_dep = dep if hasattr(dwidget, 'isplotter'): # update range of axis with (dwidget, dwidget_dep) # do not do this if the widget is hidden if ( not dwidget.settings.isSetting('hide') or not dwidget.settings.hide ): dwidget.updateAxisRange(widget, dwidget_dep, self.ranges[widget]) elif hasattr(dwidget, 'isaxis'): # set actual range on axis, as axis no longer has a # dependency if dwidget in self.ranges: axrange = self.ranges[dwidget] if axrange == defaultrange: axrange = None dwidget.setAutoRange(axrange) del self.ranges[dwidget] del depends[i] modified = True continue i += 1 return modified def findAxisRanges(self): """Find the ranges from the plotters and set the axis ranges. Follows the dependencies calculated above. """ # probaby horribly inefficient nodes = self.nodes while nodes: # iterate over dependencies for each widget inloop = True for (widget, widget_depname), depends in nodes.iteritems(): # go through dependencies of widget if widget and self.processDepends(widget, depends): # if modified, we keep looping inloop = False # delete dependencies for widget if none remaining if not depends: del nodes[(widget, widget_depname)] break # prevent infinite loops, break out if we do nothing in an # iteration if inloop: break for axis, axrange in self.ranges.iteritems(): if axrange == defaultrange: axrange = None axis.setAutoRange(axrange) class Page(widget.Widget): """A class for representing a page of plotting.""" typename='page' allowusercreation = True allowedparenttypes = [root.Root] description='Blank page' def __init__(self, parent, name=None): """Initialise object.""" widget.Widget.__init__(self, parent, name=name) if type(self) == Page: self.readDefaults() @classmethod def addSettings(klass, s): widget.Widget.addSettings(s) # page sizes are initially linked to the document page size s.add( setting.Distance('width', setting.Reference('/width'), descr='Width of page', usertext='Page width', formatting=True) ) s.add( setting.Distance('height', setting.Reference('/height'), descr='Height of page', usertext='Page height', formatting=True) ) def draw(self, parentposn, painthelper, outerbounds=None): """Draw the plotter. Clip graph inside bounds.""" # document should pass us the page bounds x1, y1, x2, y2 = parentposn # find ranges of axes axisdependhelper = _AxisDependHelper(self) axisdependhelper.findPlotters() axisdependhelper.findAxisRanges() # store axis->plotter mappings in painter too (is this nasty?) painthelper.axisplottermap.update(axisdependhelper.axis_plotter_map) if self.settings.hide: bounds = self.computeBounds(parentposn, painthelper) return bounds clip = qt4.QRectF( qt4.QPointF(parentposn[0], parentposn[1]), qt4.QPointF(parentposn[2], parentposn[3]) ) painter = painthelper.painter(self, parentposn, clip=clip) # clip to page bounds = widget.Widget.draw(self, parentposn, painthelper, parentposn) # w and h are non integer w = self.settings.get('width').convert(painter) h = self.settings.get('height').convert(painter) painthelper.setControlGraph(self, [ controlgraph.ControlMarginBox(self, [0, 0, w, h], [-10000, -10000, 10000, 10000], painthelper, ismovable = False) ] ) return bounds def updateControlItem(self, cgi): """Call helper to set page size.""" cgi.setPageSize() # allow the factory to instantiate this document.thefactory.register( Page ) veusz-1.15/widgets/function.py0000644002344000001440000003710711734662204016454 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For plotting numerical functions.""" import veusz.qtall as qt4 import itertools import numpy as N import veusz.document as document import veusz.setting as setting import veusz.utils as utils import pickable from plotters import GenericPlotter class FunctionChecker(object): """Help check function is valid.""" def __init__(self): self.cachedfunc = None self.cachedvar = None self.compiled = None def check(self, fn, var): """check function doesn't contain dangerous code. fn: function var: function is a variable of this raises a RuntimeError(msg) if a problem """ fn = fn.strip() if self.cachedfunc != fn or self.cachedvar != var: checked = utils.checkCode(fn) if checked is not None: try: msg = checked[0][0] except Exception: msg = '' raise RuntimeError(msg) self.cachedfunc = fn self.cachedvar = var try: # compile code self.compiled = compile(fn, '', 'eval') except Exception, e: raise RuntimeError(e) class FunctionPlotter(GenericPlotter): """Function plotting class.""" typename='function' allowusercreation=True description='Plot a function' def __init__(self, parent, name=None): """Initialise plotter.""" GenericPlotter.__init__(self, parent, name=name) if type(self) == FunctionPlotter: self.readDefaults() self.checker = FunctionChecker() @classmethod def addSettings(klass, s): """Construct list of settings.""" GenericPlotter.addSettings(s) s.add( setting.Int('steps', 50, minval = 3, descr = 'Number of steps to evaluate the function' ' over', usertext='Steps', formatting=True), 0 ) s.add( setting.Choice('variable', ['x', 'y'], 'x', descr='Variable the function is a function of', usertext='Variable'), 0 ) s.add( setting.Str('function', 'x', descr='Function expression', usertext='Function'), 0 ) s.add(setting.FloatOrAuto('min', 'Auto', descr='Minimum value at which to plot function', usertext='Min')) s.add(setting.FloatOrAuto('max', 'Auto', descr='Maximum value at which to plot function', usertext='Max')) s.add( setting.Line('Line', descr = 'Function line settings', usertext = 'Plot line'), pixmap = 'settings_plotline' ) s.add( setting.PlotterFill('FillBelow', descr = 'Fill below function', usertext = 'Fill below'), pixmap = 'settings_plotfillbelow' ) s.add( setting.PlotterFill('FillAbove', descr = 'Fill above function', usertext = 'Fill above'), pixmap = 'settings_plotfillabove' ) @property def userdescription(self): """User-friendly description.""" return "%(variable)s = %(function)s" % self.settings def logEvalError(self, ex): """Write error message to document log for exception ex.""" self.document.log( "Error evaluating expression in function widget '%s': '%s'" % ( self.name, unicode(ex))) def providesAxesDependency(self): s = self.settings if s.variable == 'x': return ((s.yAxis, 'both'),) else: return ((s.xAxis, 'both'),) def requiresAxesDependency(self): s = self.settings if s.variable == 'x': return (('both', s.xAxis),) else: return (('both', s.yAxis),) def updateAxisRange(self, axis, depname, axrange): """Adjust the range of the axis depending on the values plotted.""" s = self.settings # ignore empty function if s.function.strip() == '': return # ignore if function isn't sensible try: self.checker.check(s.function, s.variable) except RuntimeError, e: self.logEvalError(e) return # find axis to find variable range over axis = self.lookupAxis( {'x': s.xAxis, 'y': s.yAxis}[s.variable] ) if not axis: return # get range of that axis varaxrange = list(axis.getPlottedRange()) if varaxrange[0] == varaxrange[1]: return # trim to range if s.min != 'Auto': varaxrange[0] = max(s.min, varaxrange[0]) if s.max != 'Auto': varaxrange[1] = min(s.max, varaxrange[1]) # work out function in steps try: if axis.settings.log: # log spaced steps l1, l2 = N.log(varaxrange[1]), N.log(varaxrange[0]) delta = (l2-l1)/20. points = N.exp(N.arange(l1, l2+delta, delta)) else: # linear spaced steps delta = (varaxrange[1] - varaxrange[0])/20. points = N.arange(varaxrange[0], varaxrange[1]+delta, delta) except ZeroDivisionError: # delta is zero return env = self.initEnviron() env[s.variable] = points try: vals = eval(self.checker.compiled, env) + points*0. except: # something wrong in the evaluation return # get values which are finite: excluding nan and inf finitevals = vals[N.isfinite(vals)] # update the automatic range axrange[0] = min(N.min(finitevals), axrange[0]) axrange[1] = max(N.max(finitevals), axrange[1]) def _plotLine(self, painter, xpts, ypts, bounds, clip): """ Plot the points in xpts, ypts.""" x1, y1, x2, y2 = bounds maxdeltax = (x2-x1)*3/4 maxdeltay = (y2-y1)*3/4 # idea is to collect points until we go out of the bounds # or reach the end, then plot them pts = qt4.QPolygonF() lastx = lasty = -65536 for x, y in itertools.izip(xpts, ypts): # ignore point if it outside sensible bounds if x < -32767 or y < -32767 or x > 32767 or y > 32767: if len(pts) >= 2: utils.plotClippedPolyline(painter, clip, pts) pts.clear() else: # if the jump wasn't too large, add the point to the points if abs(x-lastx) < maxdeltax and abs(y-lasty) < maxdeltay: pts.append( qt4.QPointF(x, y) ) else: # draw what we have until now, and start a new line if len(pts) >= 2: utils.plotClippedPolyline(painter, clip, pts) pts.clear() pts.append( qt4.QPointF(x, y) ) lastx = x lasty = y # draw remaining points if len(pts) >= 2: utils.plotClippedPolyline(painter, clip, pts) def _fillRegion(self, painter, pxpts, pypts, bounds, belowleft, clip, brush): """Fill the region above/below or left/right of the points. belowleft fills below if the variable is 'x', or left if 'y' otherwise it fills above/right.""" # find starting and ending points for the filled region x1, y1, x2, y2 = bounds s = self.settings pts = qt4.QPolygonF() if self.settings.variable == 'x': if belowleft: pts.append(qt4.QPointF(pxpts[0], y2)) endpt = qt4.QPointF(pxpts[-1], y2) else: pts.append(qt4.QPointF(pxpts[0], y1)) endpt = qt4.QPointF(pxpts[-1], y1) else: if belowleft: pts.append(qt4.QPointF(x1, pypts[0])) endpt = qt4.QPointF(x1, pypts[-1]) else: pts.append(qt4.QPointF(x2, pypts[0])) endpt = qt4.QPointF(x2, pypts[-1]) # add the points between utils.addNumpyToPolygonF(pts, pxpts, pypts) # stick on the ending point pts.append(endpt) # draw the clipped polygon clipped = qt4.QPolygonF() utils.polygonClip(pts, clip, clipped) path = qt4.QPainterPath() path.addPolygon(clipped) utils.brushExtFillPath(painter, brush, path) def drawKeySymbol(self, number, painter, x, y, width, height): """Draw the plot symbol and/or line.""" s = self.settings yp = y + height/2 # draw line if not s.Line.hide: painter.setBrush( qt4.QBrush() ) painter.setPen( s.Line.makeQPen(painter) ) painter.drawLine( qt4.QPointF(x, yp), qt4.QPointF(x+width, yp) ) def initEnviron(self): """Set up function environment.""" return self.document.eval_context.copy() def getIndependentPoints(self, axes, posn): """Calculate the real and screen points to plot for the independent axis""" s = self.settings if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return None, None # get axes function is plotted along and on and # plot coordinates along axis function plotted along if s.variable == 'x': axis1, axis2 = axes[0], axes[1] minval, maxval = posn[0], posn[2] else: axis1, axis2 = axes[1], axes[0] minval, maxval = posn[1], posn[3] # get equally spaced coordinates along axis in plotter coords plotpts = N.arange(s.steps) * ((maxval-minval) / (s.steps-1)) + minval # convert to axis coordinates axispts = axis1.plotterToDataCoords(posn, plotpts) # trim according to min and max. have to convert back to plotter too. if s.min != 'Auto': axispts = axispts[ axispts >= s.min ] plotpts = axis1.dataToPlotterCoords(posn, axispts) if s.max != 'Auto': axispts = axispts[ axispts <= s.max ] plotpts = axis1.dataToPlotterCoords(posn, axispts) return axispts, plotpts def calcDependentPoints(self, axispts, axes, posn): """Calculate the real and screen points to plot for the dependent axis""" s = self.settings if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return None, None if axispts is None: return None, None try: self.checker.check(s.function, s.variable) except RuntimeError, e: self.logEvalError(e) return None, None axis2 = axes[1] if s.variable == 'x' else axes[0] # evaluate function env = self.initEnviron() env[s.variable] = axispts try: results = eval(self.checker.compiled, env) + N.zeros(axispts.shape) resultpts = axis2.dataToPlotterCoords(posn, results) except Exception, e: self.logEvalError(e) results = None resultpts = None return results, resultpts def calcFunctionPoints(self, axes, posn): ipts, pipts = self.getIndependentPoints(axes, posn) dpts, pdpts = self.calcDependentPoints(ipts, axes, posn) if self.settings.variable == 'x': return (ipts, dpts), (pipts, pdpts) else: return (dpts, ipts), (pdpts, pipts) def _pickable(self, posn): s = self.settings axisnames = [s.xAxis, s.yAxis] axes = self.parent.getAxes(axisnames) if s.variable == 'x': axisnames[1] = axisnames[1] + '(' + axisnames[0] + ')' else: axisnames[0] = axisnames[0] + '(' + axisnames[1] + ')' (xpts, ypts), (pxpts, pypts) = self.calcFunctionPoints(axes, posn) return pickable.GenericPickable( self, axisnames, (xpts, ypts), (pxpts, pypts) ) def pickPoint(self, x0, y0, bounds, distance='radial'): return self._pickable(bounds).pickPoint(x0, y0, bounds, distance) def pickIndex(self, oldindex, direction, bounds): return self._pickable(bounds).pickIndex(oldindex, direction, bounds) def draw(self, parentposn, painthelper, outerbounds = None): """Draw the function.""" posn = GenericPlotter.draw(self, parentposn, painthelper, outerbounds = outerbounds) x1, y1, x2, y2 = posn s = self.settings # exit if hidden or function blank if s.hide or s.function.strip() == '': return # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there's no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return # clip data within bounds of plotter cliprect = self.clipAxesBounds(axes, posn) painter = painthelper.painter(self, posn, clip=cliprect) # get the points to plot by evaluating the function (xpts, ypts), (pxpts, pypts) = self.calcFunctionPoints(axes, posn) # draw the function line if pxpts is None or pypts is None: # not sure how to deal with errors here painter.setPen( setting.settingdb.color('error') ) f = qt4.QFont() f.setPointSize(20) painter.setFont(f) painter.drawText( qt4.QRectF(x1, y1, x2-x1, y2-y1), qt4.Qt.AlignCenter, "Cannot evaluate '%s'" % s.function ) else: if not s.FillBelow.hide: self._fillRegion(painter, pxpts, pypts, posn, True, cliprect, s.FillBelow) if not s.FillAbove.hide: self._fillRegion(painter, pxpts, pypts, posn, False, cliprect, s.FillAbove) if not s.Line.hide: painter.setBrush( qt4.QBrush() ) painter.setPen( s.Line.makeQPen(painter) ) self._plotLine(painter, pxpts, pypts, posn, cliprect) # allow the factory to instantiate an function plotter document.thefactory.register( FunctionPlotter ) veusz-1.15/widgets/nonorthpoint.py0000644002344000001440000002167111734662204017367 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Non orthogonal point plotting.""" import itertools import numpy as N import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils import pickable from nonorthgraph import NonOrthGraph, FillBrush from widget import Widget from point import MarkerFillBrush class NonOrthPoint(Widget): '''Widget for plotting points in a non-orthogonal plot.''' typename = 'nonorthpoint' allowusercreation = True description = 'Plot points on a graph with non-orthogonal axes' allowedparenttypes = [NonOrthGraph] def __init__(self, parent, name=None): """Initialise plotter.""" Widget.__init__(self, parent, name=name) if type(self) == NonOrthPoint: self.readDefaults() @classmethod def addSettings(klass, s): '''Settings for widget.''' Widget.addSettings(s) s.add( setting.DatasetOrFloatList( 'data1', 'x', descr='Dataset containing 1st dataset or list of values', usertext='Dataset 1') ) s.add( setting.DatasetOrFloatList( 'data2', 'y', descr='Dataset containing 2nd dataset or list of values', usertext='Dataset 2') ) s.add( setting.DatasetOrFloatList( 'scalePoints', '', descr = 'Scale size of plotted markers by this dataset or' ' list of values', usertext='Scale markers') ) s.add( setting.DatasetOrStr('labels', '', descr='Dataset or string to label points', usertext='Labels', datatype='text') ) s.add( setting.DistancePt('markerSize', '3pt', descr = 'Size of marker to plot', usertext='Marker size', formatting=True), 0 ) s.add( setting.Marker('marker', 'circle', descr = 'Type of marker to plot', usertext='Marker', formatting=True), 0 ) s.add( setting.Line('PlotLine', descr = 'Plot line settings', usertext = 'Plot line'), pixmap = 'settings_plotline' ) s.add( setting.Line('MarkerLine', descr = 'Line around the marker settings', usertext = 'Marker border'), pixmap = 'settings_plotmarkerline' ) s.add( MarkerFillBrush('MarkerFill', descr = 'Marker fill settings', usertext = 'Marker fill'), pixmap = 'settings_plotmarkerfill' ) s.add( FillBrush('Fill1', descr = 'Fill settings (1)', usertext = 'Area fill 1'), pixmap = 'settings_plotfillbelow' ) s.add( FillBrush('Fill2', descr = 'Fill settings (2)', usertext = 'Area fill 2'), pixmap = 'settings_plotfillbelow' ) s.add( setting.PointLabel('Label', descr = 'Label settings', usertext='Label'), pixmap = 'settings_axislabel' ) def updateDataRanges(self, inrange): '''Extend inrange to range of data.''' d1 = self.settings.get('data1').getData(self.document) if d1: inrange[0] = min( N.nanmin(d1.data), inrange[0] ) inrange[1] = max( N.nanmax(d1.data), inrange[1] ) d2 = self.settings.get('data2').getData(self.document) if d2: inrange[2] = min( N.nanmin(d2.data), inrange[2] ) inrange[3] = max( N.nanmax(d2.data), inrange[3] ) def pickPoint(self, x0, y0, bounds, distance = 'radial'): p = pickable.DiscretePickable(self, 'data1', 'data2', lambda v1, v2: self.parent.graphToPlotCoords(v1, v2)) return p.pickPoint(x0, y0, bounds, distance) def pickIndex(self, oldindex, direction, bounds): p = pickable.DiscretePickable(self, 'data1', 'data2', lambda v1, v2: self.parent.graphToPlotCoords(v1, v2)) return p.pickIndex(oldindex, direction, bounds) def plotMarkers(self, painter, plta, pltb, scaling, markersize, clip): '''Draw markers in widget.''' s = self.settings if not s.MarkerLine.hide or not s.MarkerFill.hide: painter.setBrush( s.MarkerFill.makeQBrushWHide() ) painter.setPen( s.MarkerLine.makeQPenWHide(painter) ) utils.plotMarkers(painter, plta, pltb, s.marker, markersize, scaling=scaling, clip=clip) def drawLabels(self, painter, xplotter, yplotter, textvals, markersize): """Draw labels for the points. This is copied from the xy (point) widget class, so it probably should be somehow be shared. FIXME: sane automatic placement of labels """ s = self.settings lab = s.get('Label') # work out offset an alignment deltax = markersize*1.5*{'left':-1, 'centre':0, 'right':1}[lab.posnHorz] deltay = markersize*1.5*{'top':-1, 'centre':0, 'bottom':1}[lab.posnVert] alignhorz = {'left':1, 'centre':0, 'right':-1}[lab.posnHorz] alignvert = {'top':-1, 'centre':0, 'bottom':1}[lab.posnVert] # make font and len textpen = lab.makeQPen() painter.setPen(textpen) font = lab.makeQFont(painter) angle = lab.angle # iterate over each point and plot each label for x, y, t in itertools.izip(xplotter+deltax, yplotter+deltay, textvals): utils.Renderer( painter, font, x, y, t, alignhorz, alignvert, angle ).render() def draw(self, parentposn, phelper, outerbounds=None): '''Plot the data on a plotter.''' posn = Widget.draw(self, parentposn, phelper, outerbounds=outerbounds) s = self.settings d = self.document # exit if hidden if s.hide: return d1 = s.get('data1').getData(d) d2 = s.get('data2').getData(d) dscale = s.get('scalePoints').getData(d) text = s.get('labels').getData(d, checknull=True) if not d1 or not d2: return x1, y1, x2, y2 = posn cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) ) painter = phelper.painter(self, posn) self.parent.setClip(painter, posn) # split parts separated by NaNs for v1, v2, scalings, textitems in document.generateValidDatasetParts( d1, d2, dscale, text): # convert data (chopping down length) v1d, v2d = v1.data, v2.data minlen = min(v1d.shape[0], v2d.shape[0]) v1d, v2d = v1d[:minlen], v2d[:minlen] px, py = self.parent.graphToPlotCoords(v1d, v2d) # do fill1 (if any) if not s.Fill1.hide: self.parent.drawFillPts(painter, s.Fill1, cliprect, px, py) # do fill2 if not s.Fill2.hide: self.parent.drawFillPts(painter, s.Fill2, cliprect, px, py) # plot line if not s.PlotLine.hide: painter.setBrush( qt4.QBrush() ) painter.setPen(s.PlotLine.makeQPen(painter)) pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, px, py) utils.plotClippedPolyline(painter, cliprect, pts) # markers markersize = s.get('markerSize').convert(painter) pscale = None if scalings: pscale = scalings.data self.plotMarkers(painter, px, py, pscale, markersize, cliprect) # finally plot any labels if textitems and not s.Label.hide: self.drawLabels(painter, px, py, textitems, markersize) # allow the factory to instantiate plotter document.thefactory.register( NonOrthPoint ) veusz-1.15/widgets/ternary.py0000644002344000001440000004531011734662204016306 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Ternary plot widget.""" import numpy as N import math from itertools import izip from nonorthgraph import NonOrthGraph from axisticks import AxisTicks from axis import MajorTick, MinorTick, GridLine, MinorGridLine, AxisLabel, \ TickLabel import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils def rotatePts(x, y, theta): '''Rotate points by theta degrees.''' s = math.sin(theta*math.pi/180.) c = math.cos(theta*math.pi/180.) return x*c-y*s, x*s+y*c # translate coordinates a,b,c from user to plot # user can select different coordinate systems coord_lookup = { 'bottom-left': (0, 1, 2), 'bottom-right': (0, 2, 1), 'left-bottom': (1, 0, 2), 'left-right': (2, 0, 1), 'right-bottom': (1, 2, 0), 'right-left': (2, 1, 0) } # useful trigonometric identities sin30 = 0.5 sin60 = cos30 = 0.86602540378 tan30 = 0.5773502691 class Ternary(NonOrthGraph): '''Ternary plotter.''' typename='ternary' allowusercreation = True description = 'Ternary graph' def __init__(self, parent, name=None): '''Initialise ternary plot.''' NonOrthGraph.__init__(self, parent, name=name) if type(self) == NonOrthGraph: self.readDefaults() @classmethod def addSettings(klass, s): '''Construct list of settings.''' NonOrthGraph.addSettings(s) s.add( setting.Choice('mode', ('percentage', 'fraction'), 'percentage', descr='Show percentages or fractions', usertext='Mode') ) s.add( setting.Choice('coords', ('bottom-left', 'bottom-right', 'left-bottom', 'left-right', 'right-left', 'right-bottom'), 'bottom-left', descr='Axes to use for plotting coordinates', usertext='Coord system') ) s.add( setting.Str('labelbottom', '', descr='Bottom axis label text', usertext='Label bottom') ) s.add( setting.Str('labelleft', '', descr='Left axis label text', usertext='Label left') ) s.add( setting.Str('labelright', '', descr='Right axis label text', usertext='Label right') ) s.add( setting.Float('originleft', 0., descr='Fractional origin of left axis at its top', usertext='Left origin') ) s.add( setting.Float('originbottom', 0., descr='Fractional origin of bottom axis at its ' 'left', usertext='Bottom origin') ) s.add( setting.Float('fracsize', 1., descr='Fractional size of plot', usertext='Size') ) s.add( AxisLabel('Label', descr = 'Axis label settings', usertext = 'Axis label'), pixmap='settings_axislabel' ) s.add( TickLabel('TickLabels', descr = 'Tick label settings', usertext = 'Tick labels'), pixmap='settings_axisticklabels' ) s.add( MajorTick('MajorTicks', descr = 'Major tick line settings', usertext = 'Major ticks'), pixmap='settings_axismajorticks' ) s.add( MinorTick('MinorTicks', descr = 'Minor tick line settings', usertext = 'Minor ticks'), pixmap='settings_axisminorticks' ) s.add( GridLine('GridLines', descr = 'Grid line settings', usertext = 'Grid lines'), pixmap='settings_axisgridlines' ) s.add( MinorGridLine('MinorGridLines', descr = 'Minor grid line settings', usertext = 'Grid lines for minor ticks'), pixmap='settings_axisminorgridlines' ) s.get('leftMargin').newDefault('1cm') s.get('rightMargin').newDefault('1cm') s.get('topMargin').newDefault('1cm') s.get('bottomMargin').newDefault('1cm') s.MajorTicks.get('number').newDefault(10) s.MinorTicks.get('number').newDefault(50) s.GridLines.get('hide').newDefault(False) s.TickLabels.remove('rotate') def _maxVal(self): '''Get maximum value on axis.''' if self.settings.mode == 'percentage': return 100. else: return 1. def coordRanges(self): '''Get ranges of coordinates.''' mv = self._maxVal() # ranges for each coordinate ra = [self._orgbot*mv, (self._orgbot+self._size)*mv] rb = [self._orgleft*mv, (self._orgleft+self._size)*mv] rc = [self._orgright*mv, (self._orgright+self._size)*mv] ranges = [ra, rb, rc] lookup = coord_lookup[self.settings.coords] return ranges[lookup.index(0)], ranges[lookup.index(1)] def graphToPlotCoords(self, coorda, coordb): '''Convert coordinates in r, theta to x, y.''' s = self.settings # normalize coordinates maxval = self._maxVal() coordan = coorda / maxval coordbn = coordb / maxval # the three coordinates on the plot clist = (coordan, coordbn, 1.-coordan-coordbn) # select the right coordinates for a, b and c given the system # requested by the user # normalise by origins and plot size lookup = coord_lookup[s.coords] cbot = ( clist[ lookup[0] ] - self._orgbot ) / self._size cleft = ( clist[ lookup[1] ] - self._orgleft ) / self._size cright = ( clist[ lookup[2] ] - self._orgright ) / self._size # from Ingram, 1984, Area, 16, 175 # remember that y goes in the opposite direction here x = (0.5*cright + cbot)*self._width + self._box[0] y = self._box[3] - cright * sin60 * self._width return x, y def drawFillPts(self, painter, brushext, cliprect, ptsx, ptsy): '''Draw points for plotting a fill.''' pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, ptsx, ptsy) filltype = brushext.filltype # this is broken: FIXME if filltype == 'left': dyend = ptsy[-1]-self._box[1] pts.append( qt4.QPointF(ptsx[-1]-dyend*tan30, self._box[1]) ) dystart = ptsy[0]-self._box[1] pts.append( qt4.QPointF(ptsx[0]-dystart*tan30, self._box[1]) ) elif filltype == 'right': pts.append( qt4.QPointF(self._box[2], ptsy[-1]) ) pts.append( qt4.QPointF(self._box[2], ptsy[0]) ) elif filltype == 'bottom': dyend = self._box[3]-ptsy[-1] pts.append( qt4.QPointF(ptsx[-1]-dyend*tan30, self._box[3]) ) dystart = self._box[3]-ptsy[0] pts.append( qt4.QPointF(ptsx[0]-dystart*tan30, self._box[3]) ) elif filltype == 'polygon': pass else: pts = None if pts is not None: utils.brushExtFillPolygon(painter, brushext, cliprect, pts) def drawGraph(self, painter, bounds, datarange, outerbounds=None): '''Plot graph area and axes.''' s = self.settings xw, yw = bounds[2]-bounds[0], bounds[3]-bounds[1] d60 = 60./180.*math.pi ang = math.atan2(yw, xw/2.) if ang > d60: # taller than wider widthh = xw/2 height = math.tan(d60) * widthh else: # wider than taller height = yw widthh = height / math.tan(d60) # box for equilateral triangle self._box = ( (bounds[2]+bounds[0])/2 - widthh, (bounds[1]+bounds[3])/2 - height/2, (bounds[2]+bounds[0])/2 + widthh, (bounds[1]+bounds[3])/2 + height/2 ) self._width = widthh*2 self._height = height # triangle shaped polygon for graph self._tripoly = p = qt4.QPolygonF() p.append( qt4.QPointF(self._box[0], self._box[3]) ) p.append( qt4.QPointF(self._box[0]+widthh, self._box[1]) ) p.append( qt4.QPointF(self._box[2], self._box[3]) ) path = qt4.QPainterPath() path.addPolygon(p) path.closeSubpath() utils.brushExtFillPath(painter, s.Background, path, stroke=s.Border.makeQPenWHide(painter)) # work out origins and size self._size = max(min(s.fracsize, 1.), 0.) # make sure we don't go past the ends of the allowed range # value of origin of left axis at top self._orgleft = min(s.originleft, 1.-self._size) # value of origin of bottom axis at left self._orgbot = min(s.originbottom, 1.-self._size) # origin of right axis at bottom self._orgright = 1. - self._orgleft - (self._orgbot + self._size) def _computeTickVals(self): """Compute tick values.""" s = self.settings # this is a hack as we lose ends off the axis otherwise d = 1e-6 # get ticks along left axis atickleft = AxisTicks(self._orgleft-d, self._orgleft+self._size+d, s.MajorTicks.number, s.MinorTicks.number, extendmin=False, extendmax=False) atickleft.getTicks() # use the interval from above to calculate ticks for right atickright = AxisTicks(self._orgright-d, self._orgright+self._size+d, s.MajorTicks.number, s.MinorTicks.number, extendmin=False, extendmax=False, forceinterval = atickleft.interval) atickright.getTicks() # then calculate for bottom atickbot = AxisTicks(self._orgbot-d, self._orgbot+self._size+d, s.MajorTicks.number, s.MinorTicks.number, extendmin=False, extendmax=False, forceinterval = atickleft.interval) atickbot.getTicks() return atickbot, atickleft, atickright def setClip(self, painter, bounds): '''Set clipping for graph.''' p = qt4.QPainterPath() p.addPolygon( self._tripoly ) painter.setClipPath(p) def _getLabels(self, ticks, autoformat): """Return tick labels.""" labels = [] tl = self.settings.TickLabels format = tl.format scale = tl.scale if format.lower() == 'auto': format = autoformat for v in ticks: l = utils.formatNumber(v*scale, format, locale=self.document.locale) labels.append(l) return labels def _drawTickSet(self, painter, tickSetn, gridSetn, tickbot, tickleft, tickright, tickLabelSetn=None, labelSetn=None): '''Draw a set of ticks (major or minor). tickSetn: tick setting to get line details gridSetn: setting for grid line (if any) tickXXX: tick arrays for each axis tickLabelSetn: setting used to label ticks, or None if minor ticks labelSetn: setting for labels, if any ''' # this is mostly a lot of annoying trigonometry # compute line ends for ticks and grid lines tl = tickSetn.get('length').convert(painter) mv = self._maxVal() # bottom ticks x1 = (tickbot - self._orgbot)/self._size*self._width + self._box[0] x2 = x1 - tl * sin30 y1 = self._box[3] + N.zeros(x1.shape) y2 = y1 + tl * cos30 tickbotline = (x1, y1, x2, y2) # bottom grid (removing lines at edge of plot) scaletick = 1 - (tickbot-self._orgbot)/self._size gx = x1 + scaletick*self._width*sin30 gy = y1 - scaletick*self._width*cos30 ne = (scaletick > 1e-6) & (scaletick < (1-1e-6)) gridbotline = (x1[ne], y1[ne], gx[ne], gy[ne]) # left ticks x1 = -(tickleft - self._orgleft)/self._size*self._width*sin30 + ( self._box[0] + self._box[2])*0.5 x2 = x1 - tl * sin30 y1 = (tickleft - self._orgleft)/self._size*self._width*cos30 + self._box[1] y2 = y1 - tl * cos30 tickleftline = (x1, y1, x2, y2) # left grid scaletick = 1 - (tickleft-self._orgleft)/self._size gx = x1 + scaletick*self._width*sin30 gy = self._box[3] + N.zeros(y1.shape) ne = (scaletick > 1e-6) & (scaletick < (1-1e-6)) gridleftline = (x1[ne], y1[ne], gx[ne], gy[ne]) # right ticks x1 = -(tickright - self._orgright)/self._size*self._width*sin30+self._box[2] x2 = x1 + tl y1 = -(tickright - self._orgright)/self._size*self._width*cos30+self._box[3] y2 = y1 tickrightline = (x1, y1, x2, y2) # right grid scaletick = 1 - (tickright-self._orgright)/self._size gx = x1 - scaletick*self._width gy = y1 gridrightline = (x1[ne], y1[ne], gx[ne], gy[ne]) if not gridSetn.hide: # draw the grid pen = gridSetn.makeQPen(painter) painter.setPen(pen) utils.plotLinesToPainter(painter, *gridbotline) utils.plotLinesToPainter(painter, *gridleftline) utils.plotLinesToPainter(painter, *gridrightline) # calculate deltas for ticks bdelta = ldelta = rdelta = 0 if not tickSetn.hide: # draw ticks themselves pen = tickSetn.makeQPen(painter) pen.setCapStyle(qt4.Qt.FlatCap) painter.setPen(pen) utils.plotLinesToPainter(painter, *tickbotline) utils.plotLinesToPainter(painter, *tickleftline) utils.plotLinesToPainter(painter, *tickrightline) ldelta += tl*sin30 bdelta += tl*cos30 rdelta += tl if tickLabelSetn is not None and not tickLabelSetn.hide: # compute the labels for the ticks tleftlabels = self._getLabels(tickleft*mv, '%Vg') trightlabels = self._getLabels(tickright*mv, '%Vg') tbotlabels = self._getLabels(tickbot*mv, '%Vg') painter.setPen( tickLabelSetn.makeQPen() ) font = tickLabelSetn.makeQFont(painter) painter.setFont(font) fm = utils.FontMetrics(font, painter.device()) sp = fm.leading() + fm.descent() off = tickLabelSetn.get('offset').convert(painter) # draw tick labels in each direction hlabbot = wlableft = wlabright = 0 for l, x, y in izip(tbotlabels, tickbotline[2], tickbotline[3]+off): r = utils.Renderer(painter, font, x, y, l, 0, 1, 0) bounds = r.render() hlabbot = max(hlabbot, bounds[3]-bounds[1]) for l, x, y in izip(tleftlabels, tickleftline[2]-off-sp, tickleftline[3]): r = utils.Renderer(painter, font, x, y, l, 1, 0, 0) bounds = r.render() wlableft = max(wlableft, bounds[2]-bounds[0]) for l, x, y in izip(trightlabels,tickrightline[2]+off+sp, tickrightline[3]): r = utils.Renderer(painter, font, x, y, l, -1, 0, 0) bounds = r.render() wlabright = max(wlabright, bounds[2]-bounds[0]) bdelta += hlabbot+off+sp ldelta += wlableft+off+sp rdelta += wlabright+off+sp if labelSetn is not None and not labelSetn.hide: # draw label on edges (if requested) painter.setPen( labelSetn.makeQPen() ) font = labelSetn.makeQFont(painter) painter.setFont(font) fm = utils.FontMetrics(font, painter.device()) sp = fm.leading() + fm.descent() off = labelSetn.get('offset').convert(painter) # bottom label r = utils.Renderer(painter, font, self._box[0]+self._width/2, self._box[3] + bdelta + off, self.settings.labelbottom, 0, 1) r.render() # left label - rotate frame before drawing so we can get # the bounds correct r = utils.Renderer(painter, font, 0, -sp, self.settings.labelleft, 0, -1) painter.save() painter.translate(self._box[0]+self._width*0.25 - ldelta - off, 0.5*(self._box[1]+self._box[3])) painter.rotate(-60) r.render() painter.restore() # right label r = utils.Renderer(painter, font, 0, -sp, self.settings.labelright, 0, -1) painter.save() painter.translate(self._box[0]+self._width*0.75 + ldelta + off, 0.5*(self._box[1]+self._box[3])) painter.rotate(60) r.render() painter.restore() def drawAxes(self, painter, bounds, datarange, outerbounds=None): '''Draw plot axes.''' s = self.settings # compute tick values for later when plotting axes tbot, tleft, tright = self._computeTickVals() # draw the major ticks self._drawTickSet(painter, s.MajorTicks, s.GridLines, tbot.tickvals, tleft.tickvals, tright.tickvals, tickLabelSetn=s.TickLabels, labelSetn=s.Label) # now draw the minor ones self._drawTickSet(painter, s.MinorTicks, s.MinorGridLines, tbot.minorticks, tleft.minorticks, tright.minorticks) document.thefactory.register(Ternary) veusz-1.15/widgets/pickable.py0000644002344000001440000002112711734662204016374 0ustar jssusers00000000000000# pickable.py # stuff related to the Picker (aka Read Data) tool # Copyright (C) 2011 Benjamin K. Stuhl # Email: Benjamin K. Stuhl # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import numpy as N import veusz.document as document class PickInfo: """Encapsulates the results of a Pick operation. screenpos and coords are numeric (x,y) tuples, labels are the textual labels for the x and y datasets, and index is some object that the picker can use to figure out what the 'next' and 'previous' points are. index must implement __str__(); return '' if it has no user-visible meaning.""" def __init__(self, widget=None, screenpos=None, labels=None, coords=None, index=None): self.widget = widget self.screenpos = screenpos self.labels = labels self.coords = coords self.index = index self.distance = float('inf') self.displaytype = ('numeric', 'numeric') def __nonzero__(self): if self.widget and self.screenpos and self.labels and self.coords: return True return False class Index: """A class containing all the state a GenericPickable needs to find the next or previous point""" def __init__(self, ivar, index, sign): self.ivar = ivar self.index = index self.sign = sign # default to not trusting the actual index to be meaningful self.useindex = False def __str__(self): if not self.useindex: return '' else: return str(self.index) def _chooseOrderingSign(m, c, p): """Figures out whether p or m is visually right of c""" assert c is not None if p is not None and m is not None: if p[0] > m[0] or (p[0] == m[0] and p[1] < m[1]): # p is visually to the right of or above m return 1 else: return -1 elif p is not None: if p[0] > c[0]: # p is visually right of c return 1 else: return -1 elif m is not None: if m[0] < c[0]: # m is visually left of c return 1 else: return -1 else: assert m is not None or p is not None class GenericPickable: """Utility class which abstracts the math of picking the closest point out of a list of points""" def __init__(self, widget, labels, vals, screenvals): self.widget = widget self.labels = labels self.xvals, self.yvals = vals self.xscreen, self.yscreen = screenvals def _pickSign(self, i): if len(self.xscreen) <= 1: # we only have one element, so it doesn't matter anyways return 1 if i == 0: m = None else: m = self.xscreen[i-1], self.yscreen[i-1] c = self.xscreen[i], self.yscreen[i] if i+1 == len(self.xscreen): p = None else: p = self.xscreen[i+1], self.yscreen[i+1] return _chooseOrderingSign(m, c, p) def pickPoint(self, x0, y0, bounds, distance_direction): info = PickInfo(self.widget, labels=self.labels) if self.widget.settings.hide: return info if None in (self.xvals, self.yvals): return info if len(self.xscreen) == 0 or len(self.yscreen) == 0: return info # calculate distances if distance_direction == 'vertical': # measure distance along y dist = N.abs(self.yscreen - y0) elif distance_direction == 'horizontal': # measure distance along x dist = N.abs(self.xscreen - x0) elif distance_direction == 'radial': # measure radial distance dist = N.sqrt((self.xscreen - x0)**2 + (self.yscreen - y0)**2) else: # programming error assert (distance_direction == 'radial' or distance_direction == 'vertical' or distance_direction == 'horizontal') # ignore points which are offscreen outofbounds = ( (self.xscreen < bounds[0]) | (self.xscreen > bounds[2]) | (self.yscreen < bounds[1]) | (self.yscreen > bounds[3]) ) dist[outofbounds] = float('inf') m = N.min(dist) # if there are multiple equidistant points, arbitrarily take # the first one i = N.nonzero(dist == m)[0][0] info.screenpos = self.xscreen[i], self.yscreen[i] info.coords = self.xvals[i], self.yvals[i] info.distance = m info.index = Index(self.xvals[i], i, self._pickSign(i)) return info def pickIndex(self, oldindex, direction, bounds): info = PickInfo(self.widget, labels=self.labels) if self.widget.settings.hide: return info if None in (self.xvals, self.yvals): return info if oldindex.index is None: # no explicit index, so find the closest location to the previous # independent variable value i = N.logical_not( N.logical_or( self.xvals < oldindex.ivar, self.xvals > oldindex.ivar) ) # and pick the next if oldindex.sign == 1: i = max(N.nonzero(i)[0]) else: i = min(N.nonzero(i)[0]) else: i = oldindex.index if direction == 'right': incr = oldindex.sign elif direction == 'left': incr = -oldindex.sign else: assert direction == 'right' or direction == 'left' i += incr # skip points that are outside of the bounds while ( i >= 0 and i < len(self.xscreen) and (self.xscreen[i] < bounds[0] or self.xscreen[i] > bounds[2] or self.yscreen[i] < bounds[1] or self.yscreen[i] > bounds[3]) ): i += incr if i < 0 or i >= len(self.xscreen): return info info.screenpos = self.xscreen[i], self.yscreen[i] info.coords = self.xvals[i], self.yvals[i] info.index = Index(self.xvals[i], i, oldindex.sign) return info class DiscretePickable(GenericPickable): """A specialization of GenericPickable that knows how to deal with widgets with axes and data sets""" def __init__(self, widget, xdata_propname, ydata_propname, mapdata_fn): s = widget.settings doc = widget.document self.xdata = xdata = s.get(xdata_propname).getData(doc) self.ydata = ydata = s.get(ydata_propname).getData(doc) labels = s.__getattr__(xdata_propname), s.__getattr__(ydata_propname) if not xdata or not ydata or not mapdata_fn: GenericPickable.__init__( self, widget, labels, (None, None), (None, None) ) return # map all the valid data x, y = N.array([]), N.array([]) xs, ys = N.array([]), N.array([]) for xvals, yvals in document.generateValidDatasetParts(xdata, ydata): chunklen = min(len(xvals.data), len(yvals.data)) x = N.append(x, xvals.data[:chunklen]) y = N.append(y, yvals.data[:chunklen]) xs, ys = mapdata_fn(x, y) # and set us up with the mapped data GenericPickable.__init__( self, widget, labels, (x, y), (xs, ys) ) def pickPoint(self, x0, y0, bounds, distance_direction): info = GenericPickable.pickPoint(self, x0, y0, bounds, distance_direction) info.displaytype = (self.xdata.displaytype, self.ydata.displaytype) if not info: return info # indicies are persistent info.index.useindex = True return info def pickIndex(self, oldindex, direction, bounds): info = GenericPickable.pickIndex(self, oldindex, direction, bounds) if not info: return info # indicies are persistent info.index.useindex = True return info veusz-1.15/widgets/root.py0000644002344000001440000001233411734662204015605 0ustar jssusers00000000000000# root.py # Represents the root widget for plotting the document # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import widget import controlgraph class Root(widget.Widget): """Root widget class for plotting the document.""" typename='document' allowusercreation = False allowedparenttypes = [None] def __init__(self, parent, name=None, document=None): """Initialise object.""" widget.Widget.__init__(self, parent, name=name) s = self.settings self.document = document # don't want user to be able to hide entire document stylesheet = setting.StyleSheet(descr='Master settings for document', usertext='Style sheet') s.add(stylesheet) self.fillStylesheet(stylesheet) if type(self) == Root: self.readDefaults() s.get('englishlocale').setOnModified(self.changeLocale) @classmethod def addSettings(klass, s): widget.Widget.addSettings(s) s.remove('hide') s.add( setting.Distance('width', '15cm', descr='Width of the pages', usertext='Page width', formatting=True) ) s.add( setting.Distance('height', '15cm', descr='Height of the pages', usertext='Page height', formatting=True) ) s.add( setting.Bool('englishlocale', False, descr='Use US/English number formatting for ' 'document', usertext='English locale', formatting=True) ) def changeLocale(self): """Update locale of document if changed by user.""" if self.settings.englishlocale: self.document.locale = qt4.QLocale.c() else: self.document.locale = qt4.QLocale() self.document.locale.setNumberOptions(qt4.QLocale.OmitGroupSeparator) def getPage(self, pagenum): """Get page widget.""" return self.children[pagenum] def draw(self, painthelper, pagenum): """Draw the page requested on the painter.""" xw, yw = painthelper.pagesize posn = [0, 0, xw, yw] painter = painthelper.painter(self, posn) page = self.children[pagenum] page.draw( posn, painthelper ) # w and h are non integer w = self.settings.get('width').convert(painter) h = self.settings.get('height').convert(painter) painthelper.setControlGraph(self, [ controlgraph.ControlMarginBox(self, [0, 0, w, h], [-10000, -10000, 10000, 10000], painthelper, ismovable = False) ] ) def updateControlItem(self, cgi): """Call helper to set page size.""" cgi.setPageSize() def fillStylesheet(self, stylesheet): """Register widgets with stylesheet.""" for widgetname in document.thefactory.listWidgets(): klass = document.thefactory.getWidgetClass(widgetname) if klass.allowusercreation or klass == Root: newsett = setting.Settings(name=klass.typename, usertext = klass.typename, pixmap="button_%s" % klass.typename) classset = setting.Settings('temp') klass.addSettings(classset) # copy formatting settings to stylesheet for name in classset.setnames: # might become recursive if name == 'StyleSheet': continue sett = classset.setdict[name] # skip non formatting settings #if hasattr(sett, 'formatting') and not sett.formatting: # continue newsett.add( sett.copy() ) stylesheet.add(newsett) # allow the factory to instantiate this document.thefactory.register( Root ) veusz-1.15/widgets/bar.py0000644002344000001440000004514611734662204015375 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For plotting bar graphs.""" from itertools import izip, repeat import numpy as N import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils from plotters import GenericPlotter class BarFill(setting.Settings): '''Filling of bars.''' def __init__(self, name, **args): setting.Settings.__init__(self, name, **args) self.add( setting.FillSet('fills', [('solid', 'grey', False)], descr = 'Fill styles for dataset bars', usertext='Fill styles') ) class BarLine(setting.Settings): '''Edges of bars.''' def __init__(self, name, **args): setting.Settings.__init__(self, name, **args) self.add( setting.LineSet('lines', [('solid', '0.5pt', 'black', False)], descr = 'Line styles for dataset bars', usertext='Line styles') ) def extend1DArray(array, length, missing=0.): """Return array with length given (original if appropriate. Values are extended with value given.""" if len(array) == length: return array retn = N.resize(array, length) retn[len(array):] = missing return retn class BarPlotter(GenericPlotter): """Plot bar charts.""" typename='bar' allowusercreation=True description='Plot bar charts' def __init__(self, parent, name=None): """Initialise bar chart.""" GenericPlotter.__init__(self, parent, name=name) if type(self) == BarPlotter: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" GenericPlotter.addSettings(s) # get rid of default key setting s.remove('key') s.add( setting.Strings('keys', ('',), descr='Key text for each dataset', usertext='Key text'), 0) s.add( setting.DatasetOrStr('labels', '', descr='Dataset or string to label bars', usertext='Labels', datatype='text'), 5 ) s.add( setting.Choice('mode', ('grouped', 'stacked'), 'grouped', descr='Show datasets grouped ' 'together or as a single bar', usertext='Mode'), 0) s.add( setting.Choice('direction', ('horizontal', 'vertical'), 'vertical', descr = 'Horizontal or vertical bar chart', usertext='Direction'), 0 ) s.add( setting.Dataset('posn', '', descr = 'Dataset containing position of bars' ' (optional)', usertext='Positions'), 0 ) s.add( setting.Datasets('lengths', ('y',), descr = 'Datasets containing lengths of bars', usertext='Lengths'), 0 ) s.add( setting.Float('barfill', 0.75, minval = 0., maxval = 1., descr = 'Filling fraction of bars' ' (between 0 and 1)', usertext='Bar fill', formatting=True) ) s.add( setting.Float('groupfill', 0.9, minval = 0., maxval = 1., descr = 'Filling fraction of groups of bars' ' (between 0 and 1)', usertext='Group fill', formatting=True) ) s.add( setting.Choice('errorstyle', ('none', 'bar', 'barends'), 'bar', descr='Error bar style to show', usertext='Error style', formatting=True) ) s.add(BarFill('BarFill', descr='Bar fill', usertext='Fill'), pixmap = 'settings_bgfill') s.add(BarLine('BarLine', descr='Bar line', usertext='Line'), pixmap = 'settings_border') s.add( setting.ErrorBarLine('ErrorBarLine', descr = 'Error bar line settings', usertext = 'Error bar line'), pixmap = 'settings_ploterrorline' ) @property def userdescription(self): """User-friendly description.""" s = self.settings return "lengths='%s', position='%s'" % (', '.join(s.lengths), s.posn) def providesAxesDependency(self): """This widget provides range information about these axes.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def getAxisLabels(self, direction): """Get labels for bar for appropriate axis.""" s = self.settings if s.direction != direction: # if horizontal bars, want labels on vertical axis and vice versa doc = self.document labels = s.get('labels').getData(doc, checknull=True) positions = s.get('posn').getData(doc) if positions is None: lengths = s.get('lengths').getData(doc) if not lengths: return (None, None) p = N.arange( max([len(d.data) for d in lengths]) )+1. else: p = positions.data return (labels, p) else: return (None, None) def singleBarDataRange(self, datasets): """For single bars where multiple datasets are added, compute maximum range.""" minv, maxv = 0., 0. for data in izip(*[ds.data for ds in datasets]): totpos = sum( [d for d in data if d > 0] ) totneg = sum( [d for d in data if d < 0] ) minv = min(minv, totneg) maxv = max(maxv, totpos) return minv, maxv def updateAxisRange(self, axis, depname, axrange): """Update axis range from data.""" s = self.settings if ((s.direction == 'horizontal' and depname == 'sx') or (s.direction == 'vertical' and depname == 'sy')): # update from lengths data = s.get('lengths').getData(self.document) if s.mode == 'grouped': # update range from individual datasets for d in data: drange = d.getRange() if drange is not None: axrange[0] = min(axrange[0], drange[0]) axrange[1] = max(axrange[1], drange[1]) else: # update range from sum of datasets minv, maxv = self.singleBarDataRange(data) axrange[0] = min(axrange[0], minv) axrange[1] = max(axrange[1], maxv) else: if s.posn: # use given positions data = s.get('posn').getData(self.document) if data: drange = data.getRange() if drange is not None: axrange[0] = min(axrange[0], drange[0]) axrange[1] = max(axrange[1], drange[1]) else: # count bars data = s.get('lengths').getData(self.document) if data: maxlen = max([len(d) for d in data]) axrange[0] = min(1-0.5, axrange[0]) axrange[1] = max(maxlen+0.5, axrange[1]) def findBarPositions(self, lengths, positions, axes, posn): """Work out centres of bar / bar groups and maximum width.""" ishorz = self.settings.direction == 'horizontal' if positions is None: p = N.arange( max([len(d.data) for d in lengths]) )+1. else: p = positions.data # work out positions of bars # get vertical axis if horz, and vice-versa axis = axes[ishorz] posns = axis.dataToPlotterCoords(posn, p) if len(posns) <= 1: if ishorz: maxwidth = posn[2]-posn[0] else: maxwidth = posn[3]-posn[1] else: maxwidth = N.nanmin(N.abs(posns[1:]-posns[:-1])) return posns, maxwidth def calculateErrorBars(self, dataset, vals): """Get values for error bars.""" minval = None maxval = None length = len(vals) if 'serr' in dataset: s = N.nan_to_num(dataset['serr']) minval = vals - s maxval = vals + s else: if 'nerr' in dataset: minval = vals + N.nan_to_num(dataset['nerr']) if 'perr' in dataset: maxval = vals + N.nan_to_num(dataset['perr']) return minval, maxval def drawErrorBars(self, painter, posns, barwidth, yvals, dataset, axes, widgetposn): """Draw (optional) error bars on bars.""" s = self.settings if s.errorstyle == 'none': return minval, maxval = self.calculateErrorBars(dataset, yvals) if minval is None and maxval is None: return # handle one sided errors if minval is None: minval = yvals if maxval is None: maxval = yvals # convert errors to coordinates ishorz = s.direction == 'horizontal' mincoord = axes[not ishorz].dataToPlotterCoords(widgetposn, minval) mincoord = N.clip(mincoord, -32767, 32767) maxcoord = axes[not ishorz].dataToPlotterCoords(widgetposn, maxval) maxcoord = N.clip(maxcoord, -32767, 32767) # draw error bars painter.setPen( self.settings.ErrorBarLine.makeQPenWHide(painter) ) w = barwidth*0.25 if ishorz: utils.plotLinesToPainter(painter, mincoord, posns, maxcoord, posns) if s.errorstyle == 'barends': utils.plotLinesToPainter(painter, mincoord, posns-w, mincoord, posns+w) utils.plotLinesToPainter(painter, maxcoord, posns-w, maxcoord, posns+w) else: utils.plotLinesToPainter(painter, posns, mincoord, posns, maxcoord) if s.errorstyle == 'barends': utils.plotLinesToPainter(painter, posns-w, mincoord, posns+w, mincoord) utils.plotLinesToPainter(painter, posns-w, maxcoord, posns+w, maxcoord) def plotBars(self, painter, s, dsnum, clip, corners): """Plot a set of boxes.""" # get style brush = s.BarFill.get('fills').returnBrushExtended(dsnum) pen = s.BarLine.get('lines').makePen(painter, dsnum) lw = pen.widthF() * 2 # make clip box bigger to avoid lines showing extclip = qt4.QRectF(qt4.QPointF(clip.left()-lw, clip.top()-lw), qt4.QPointF(clip.right()+lw, clip.bottom()+lw)) # plot bars path = qt4.QPainterPath() utils.addNumpyPolygonToPath( path, extclip, corners[0], corners[1], corners[2], corners[1], corners[2], corners[3], corners[0], corners[3]) utils.brushExtFillPath(painter, brush, path, stroke=pen) def barDrawGroup(self, painter, posns, maxwidth, dsvals, axes, widgetposn, clip): """Draw groups of bars.""" s = self.settings # calculate bar and group widths numgroups = len(dsvals) groupwidth = maxwidth usablewidth = groupwidth * s.groupfill bardelta = usablewidth / float(numgroups) barwidth = bardelta * s.barfill ishorz = s.direction == 'horizontal' # bar extends from these coordinates zeropt = axes[not ishorz].dataToPlotterCoords(widgetposn, N.array([0.])) for dsnum, dataset in enumerate(dsvals): # convert bar length to plotter coords lengthcoord = axes[not ishorz].dataToPlotterCoords( widgetposn, dataset['data']) # these are the coordinates perpendicular to the bar posns1 = posns + (-usablewidth*0.5 + bardelta*dsnum + (bardelta-barwidth)*0.5) posns2 = posns1 + barwidth if ishorz: p = (zeropt + N.zeros(posns1.shape), posns1, lengthcoord, posns2) else: p = (posns1, zeropt + N.zeros(posns2.shape), posns2, lengthcoord) self.plotBars(painter, s, dsnum, clip, p) # draw error bars self.drawErrorBars(painter, posns2-barwidth*0.5, barwidth, dataset['data'], dataset, axes, widgetposn) def barDrawStacked(self, painter, posns, maxwidth, dsvals, axes, widgetposn, clip): """Draw each dataset in a single bar.""" s = self.settings # get positions of groups of bars barwidth = maxwidth * s.barfill ishorz = s.direction == 'horizontal' # keep track of last most negative or most positive values in bars poslen = len(posns) lastneg = N.zeros(poslen) lastpos = N.zeros(poslen) # keep track of bars for error bars barvals = [] for dsnum, data in enumerate(dsvals): # add on value to last value in correct direction data = data['data'] last = N.where(data < 0., lastneg, lastpos) new = N.where(data < 0., lastneg+data, lastpos+data) # work out maximum extents for next time lastneg = N.min( N.vstack((lastneg, new)), axis=0 ) lastpos = N.max( N.vstack((lastpos, new)), axis=0 ) # convert values to plotter coordinates lastplt = axes[not ishorz].dataToPlotterCoords( widgetposn, last) newplt = axes[not ishorz].dataToPlotterCoords( widgetposn, new) # positions of bar perpendicular to bar direction posns1 = posns - barwidth*0.5 posns2 = posns1 + barwidth # we iterate over each of these coordinates if ishorz: p = (lastplt, posns1, newplt, posns2) else: p = (posns1, lastplt, posns2, newplt) self.plotBars(painter, s, dsnum, clip, p) barvals.append(new) for barval, dsval in izip(barvals, dsvals): # draw error bars self.drawErrorBars(painter, posns, barwidth, barval, dsval, axes, widgetposn) def getNumberKeys(self): """Return maximum number of keys.""" lengths = self.settings.get('lengths').getData(self.document) if not lengths: return 0 return min( len([k for k in self.settings.keys if k]), len(lengths) ) def getKeyText(self, number): """Get key entry.""" return [k for k in self.settings.keys if k][number] def drawKeySymbol(self, number, painter, x, y, width, height): """Draw a fill rectangle for key entry.""" self.plotBars(painter, self.settings, number, qt4.QRectF(0,0,32767,32767), ([x], [y+height*0.1], [x+width], [y+height*0.8])) def draw(self, parentposn, phelper, outerbounds=None): """Plot the data on a plotter.""" widgetposn = GenericPlotter.draw(self, parentposn, phelper, outerbounds=outerbounds) s = self.settings # exit if hidden if s.hide: return # get data doc = self.document positions = s.get('posn').getData(doc) lengths = s.get('lengths').getData(doc) if not lengths: return # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there are no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return # where the bars are to be placed horizontally barposns, maxwidth = self.findBarPositions(lengths, positions, axes, widgetposn) # only use finite positions origposnlen = len(barposns) validposn = N.isfinite(barposns) barposns = barposns[validposn] # this is a bit rubbish - we take the datasets and # make sure they have the same lengths as posns and remove NaNs # Datasets are stored as dicts dsvals = [] for dataset in lengths: vals = {} for key in ('data', 'serr', 'nerr', 'perr'): v = getattr(dataset, key) if v is not None: vals[key] = extend1DArray(N.nan_to_num(v), origposnlen)[validposn] dsvals.append(vals) # clip data within bounds of plotter clip = self.clipAxesBounds(axes, widgetposn) painter = phelper.painter(self, widgetposn, clip=clip) # actually do the drawing fn = {'stacked': self.barDrawStacked, 'grouped': self.barDrawGroup}[s.mode] fn(painter, barposns, maxwidth, dsvals, axes, widgetposn, clip) # allow the factory to instantiate a bar plotter document.thefactory.register( BarPlotter ) veusz-1.15/widgets/graph.py0000644002344000001440000001473011734662204015725 0ustar jssusers00000000000000# graph widget for containing other sorts of widget # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting import veusz.utils as utils import veusz.document as document import widget import axis import page import grid import controlgraph class Graph(widget.Widget): """Graph for containing other sorts of widgets""" typename='graph' allowedparenttypes = [page.Page, grid.Grid] allowusercreation = True description = 'Base graph' def __init__(self, parent, name=None): """Initialise object and create axes.""" widget.Widget.__init__(self, parent, name=name) s = self.settings self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add( setting.Distance( 'leftMargin', '1.7cm', descr='Distance from left of graph to edge', usertext='Left margin', formatting=True) ) s.add( setting.Distance( 'rightMargin', '0.2cm', descr='Distance from right of graph to edge', usertext='Right margin', formatting=True) ) s.add( setting.Distance( 'topMargin', '0.2cm', descr='Distance from top of graph to edge', usertext='Top margin', formatting=True) ) s.add( setting.Distance( 'bottomMargin', '1.7cm', descr='Distance from bottom of graph to edge', usertext='Bottom margin', formatting=True) ) s.add( setting.GraphBrush( 'Background', descr = 'Background plot fill', usertext='Background'), pixmap='settings_bgfill' ) s.add( setting.Line('Border', descr = 'Graph border line', usertext='Border'), pixmap='settings_border') def addDefaultSubWidgets(self): """Add axes automatically.""" if self.parent.getChild('x') is None: axis.Axis(self, name='x') if self.parent.getChild('y') is None: axis.Axis(self, name='y') def getAxes(self, axesnames): """Get the axes for widgets to plot against. names is a list of names to find.""" widgets = {} # recursively go back up the tree to find axes w = self while w is not None and len(widgets) < len(axesnames): for c in w.children: name = c.name if ( name in axesnames and name not in widgets and isinstance(c, axis.Axis) ): widgets[name] = c w = w.parent # didn't find everything... if w is None: for name in axesnames: if name not in widgets: widgets[name] = None # return list of found widgets return [widgets[n] for n in axesnames] def draw(self, parentposn, painthelper, outerbounds = None): '''Update the margins before drawing.''' s = self.settings margins = ( s.get('leftMargin').convert(painthelper), s.get('topMargin').convert(painthelper), s.get('rightMargin').convert(painthelper), s.get('bottomMargin').convert(painthelper) ) bounds = self.computeBounds(parentposn, painthelper, margins=margins) maxbounds = self.computeBounds(parentposn, painthelper) # controls for adjusting graph margins painter = painthelper.painter(self, bounds) painthelper.setControlGraph(self, [ controlgraph.ControlMarginBox(self, bounds, maxbounds, painthelper) ]) # do no painting if hidden if s.hide: return bounds # set graph rectangle attributes path = qt4.QPainterPath() path.addRect( qt4.QRectF(qt4.QPointF(bounds[0], bounds[1]), qt4.QPointF(bounds[2], bounds[3])) ) utils.brushExtFillPath(painter, s.Background, path, stroke=s.Border.makeQPenWHide(painter)) # do normal drawing of children # iterate over children in reverse order for c in reversed(self.children): c.draw(bounds, painthelper, outerbounds=outerbounds) # now need to find axes which aren't children, and draw those again axestodraw = set() childrennames = set() for c in self.children: childrennames.add(c.name) try: for axis in c.getAxesNames(): axestodraw.add(axis) except AttributeError: pass axestodraw = axestodraw - childrennames if axestodraw: # now redraw all these axes if they aren't children of us axeswidgets = self.getAxes(axestodraw) for w in axeswidgets: if w is not None: w.draw(bounds, painthelper, outerbounds=outerbounds) return bounds def updateControlItem(self, cgi): """Graph resized or moved - call helper routine to move self.""" cgi.setWidgetMargins() # allow users to make Graph objects document.thefactory.register( Graph ) veusz-1.15/widgets/axisticks.py0000644002344000001440000004776511734662204016644 0ustar jssusers00000000000000# axisticks.py # algorithm to work out what tick-marks to put on an axis # Copyright (C) 2003 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import math import numpy as N import veusz.utils as utils """Algorithms for working with axis ticks. These algorithms were designed by me (Jeremy Sanders), so there may well be bugs. Please report them. The idea is to try to achieve a set number of major and minor ticks by looking though a list of allowable interval values (after taking account of what power of 10 the coordinates are in). """ class AxisTicksBase(object): """Base class of axis ticks classes.""" def __init__( self, minval, maxval, numticks, numminorticks, logaxis = False, prefermore = True, extendmin = False, extendmax = False, forceinterval = None ): """Initialise the class. minval and maxval are the range of the data to be plotted numticks number of major ticks to aim for logaxis: axis logarithmic? prefermore: prefer more ticks rather than fewer extendbounds: extend minval and maxval to nearest tick if okay forceinterval: force interval to one given (if allowed). interval is tuple as returned in self.interval after calling getTicks() """ self.minval = minval self.maxval = maxval self.numticks = numticks self.numminorticks = numminorticks self.logaxis = logaxis self.prefermore = prefermore self.extendmin = extendmin self.extendmax = extendmax self.forceinterval = forceinterval def getTicks( self ): """Calculate and return the position of the major ticks. Results are returned as attributes of this object in interval, minval, maxval, tickvals, minorticks, autoformat """ class AxisTicks(AxisTicksBase): """Class to work out at what values axis major ticks should appear.""" # the allowed values we allow ticks to increase by # first values are the major tick intervals, followed by a list # of allowed minors allowed_minorintervals_linear = { 1.: (0.1, 0.2, 0.5), 2.: (0.2, 0.5, 1.), 5.: (0.5, 1., 2.5), 2.5: (0.5,) } # just get the allowable majors allowed_intervals_linear = allowed_minorintervals_linear.keys() # the allowed values we can increase by in log space # by default we increase by 10^3 # if the first value is chosen we can use the "special" log minor ticks allowed_intervals_log = (1., 3., 6., 9., 12., 15., 19.) # positions we're allowed to put minor intervals allowed_minorintervals_log = (1., 3., 6., 9., 12., 15., 19.) # how much we should allow axes to extend to a tick max_extend_factor = 0.15 def _calcTickValues( self, minval, maxval, delta ): """Compute the tick values, given minval, maxval and delta.""" startmult = int( math.ceil( minval / delta ) ) stopmult = int( math.floor( maxval / delta ) ) return N.arange(startmult, stopmult+1) * delta def _tickNums(self, minval, maxval, delta): """Calculate number of ticks between minval and maxval with delta.""" startmult = int( math.ceil( minval / delta ) ) stopmult = int( math.floor( maxval / delta ) ) return (stopmult-startmult)+1 def _calcNoTicks( self, interval, logdelta ): """Return the number of ticks with spacing interval*10^logdelta. Returns a tuple (noticks, minval, maxval). """ # store these for modification (if we extend bounds) minval = self.minval maxval = self.maxval # calculate tick spacing and maximum extension factor delta = interval * (10**logdelta) maxextend = (maxval - minval) * AxisTicks.max_extend_factor # should we try to extend to nearest interval*10^logdelta? if self.extendmin: # extend minval if possible if math.fabs( math.modf( minval / delta )[0] ) > 1e-8: d = minval - ( math.floor( minval / delta ) * delta ) if d <= maxextend: minval -= d if self.extendmax: # extend maxval if possible if math.fabs( math.modf( maxval / delta)[0] ) > 1e-8: d = ( (math.floor(maxval / delta)+1.) * delta) - maxval if d <= maxextend: maxval += d # return (noticks, minbound, maxbound) return ( self._tickNums(minval, maxval, delta), minval, maxval ) def _calcLinearMinorTickValues(self, minval, maxval, interval, logstep, allowedintervals): """Get the best values for minor ticks on a linear axis Algorithm tries to look for best match to nominorticks Pass routine major ticks from minval to maxval with steps of interval*(10**logstep) """ # iterate over allowed minor intervals best = -1 best_numticks = -1 best_delta = 1000000 mult = 10.**logstep # iterate over allowed minor intervals for minint in allowedintervals: numticks = self._tickNums(minval, maxval, minint*mult) d = abs( self.numminorticks - numticks ) # if this is a better match to the number of ticks # we want, choose this if ((d < best_delta ) or (d == best_delta and (self.prefermore and numticks > best_numticks) or (not self.prefermore and numticks < best_numticks)) ): best = minint best_delta = d best_numticks = numticks # use best value to return tick values return self._calcTickValues(minval, maxval, best*mult) def _calcLogMinorTickValues( self, minval, maxval ): """Calculate minor tick values with a log scale.""" # this is a scale going e.g. 1,2,3,...8,9,10,20,30...90,100,200... # round down to nearest power of 10 for each alpha = int( math.floor( N.log10(minval) ) ) beta = int( math.floor( N.log10(maxval) ) ) ticks = [] # iterate over range in log space for i in xrange(alpha, beta+1): power = 10.**i # add ticks for values in correct range for j in xrange(2, 10): v = power*j # blah log conversions mean we have to use 'fuzzy logic' if ( math.fabs(v - minval)/v < 1e-6 or v > minval ) and \ ( math.fabs(v - maxval)/v < 1e-6 or v < maxval ) : ticks.append(v) return N.array( ticks ) def _selectBestTickFromSelection(self, selection): """Choose best tick from selection given.""" # we now try to find the best matching value minabsdelta = 1e99 mindelta = 1e99 bestsel = () # find the best set of tick labels for s in selection: # difference between what we want and what we have delta = s[0] - self.numticks absdelta = abs(delta) # if it matches better choose this if absdelta < minabsdelta: minabsdelta = absdelta mindelta = delta bestsel = s # if we find two closest matching label sets, we # test whether we prefer too few to too many labels if absdelta == minabsdelta: if (self.prefermore and (delta > mindelta)) or \ (not self.prefermore and (delta < mindelta)): minabsdelta = absdelta mindelta = delta bestsel = s return bestsel def _getBestTickSelection(self, allowed_intervals): """Go through allowed tick intervals and find one best matching requested parameters.""" # work out range and log range therange = self.maxval - self.minval intlogrange = int( N.log10( therange ) ) # we step variable to move through log space to find best ticks logstep = intlogrange + 1 # we iterate down in log spacing, until we have more than twice # the number of ticks requested. # Maybe a better algorithm is required selection = [] # keep track of largest number of ticks calculated largestno = 0 while True: for interval in allowed_intervals: no, minval, maxval = self._calcNoTicks( interval, logstep ) selection.append( (no, interval, logstep, minval, maxval ) ) largestno = max(largestno, no) if largestno > self.numticks*2: break logstep -= 1 # necessary as we don't want 10**x on axis if |x|<1 # :-( if logstep < 0 and self.logaxis: break return selection def _tickSelector(self, allowed_intervals): """With minval and maxval find best tick positions.""" if self.forceinterval is None: # get selection of closely matching ticks selection = self._getBestTickSelection(allowed_intervals) # now we have the best, we work out the ticks and return bestsel = self._selectBestTickFromSelection(selection) dummy, interval, loginterval, minval, maxval = bestsel else: # forced specific interval requested interval, loginterval = self.forceinterval no, minval, maxval = self._calcNoTicks(interval, loginterval) # calculate the positions of the ticks from parameters tickdelta = interval * 10.**loginterval ticks = self._calcTickValues( minval, maxval, tickdelta ) return (minval, maxval, ticks, interval, loginterval) def getTicks(self): """Calculate and return the position of the major ticks. """ if self.logaxis: # which intervals we'll accept for major ticks intervals = AxisTicks.allowed_intervals_log # transform range into log space self.minval = N.log10( self.minval ) self.maxval = N.log10( self.maxval ) else: # which linear intervals we'll allow intervals = AxisTicks.allowed_intervals_linear minval, maxval, tickvals, interval, loginterval = self._tickSelector( intervals ) # work out the most appropriate minor tick intervals if not self.logaxis: # just plain minor ticks # try to achieve no of minors close to value requested minorticks = self._calcLinearMinorTickValues( minval, maxval, interval, loginterval, AxisTicks.allowed_minorintervals_linear[interval] ) else: # log axis if interval == 1.: # calculate minor ticks # here we use 'conventional' minor log tick spacing # e.g. 0.9, 1, 2, .., 8, 9, 10, 20, 30 ... minorticks = self._calcLogMinorTickValues( 10.**minval, 10.**maxval) # Here we test whether more log major tick values are needed... # often we might only have one tick value, and so we add 2, then 5 # this is a bit of a hack: better ideas please!! if len(tickvals) < 2: # get lower power of 10 low10 = int( math.floor(minval) ) # could use numpy here for i in (2., 5., 20., 50.): n = low10 + math.log10(i) if n >= minval and n <= maxval: tickvals = N.concatenate( (tickvals, N.array([n]) )) else: # if we increase by more than one power of 10 on the # axis, we can't do the above, so we do linear ticks # in log space # aim is to choose powers of 3 for majors and minors # to make it easy to read the axis. comments? minorticks = self._calcLinearMinorTickValues( minval, maxval, interval, loginterval, AxisTicks.allowed_minorintervals_log) minorticks = 10.**minorticks # transform normal ticks back to real space minval = 10.**minval maxval = 10.**maxval tickvals = 10.**tickvals self.interval = (interval, loginterval) self.minorticks = minorticks self.minval = minval self.maxval = maxval self.tickvals = tickvals self.autoformat = '%Vg' class DateTicks(AxisTicksBase): """For formatting dates. We want something that chooses appropriate intervals So we want to choose most apropriate interval depending on number of ticks requested """ # possible intervals for a time/date axis # tuples of ((y, m, d, h, m, s, msec), autoformat) intervals = ( ((200, 0, 0, 0, 0, 0, 0), '%VDY'), ((100, 0, 0, 0, 0, 0, 0), '%VDY'), ((50, 0, 0, 0, 0, 0, 0), '%VDY'), ((20, 0, 0, 0, 0, 0, 0), '%VDY'), ((10, 0, 0, 0, 0, 0, 0), '%VDY'), ((5, 0, 0, 0, 0, 0, 0), '%VDY'), ((2, 0, 0, 0, 0, 0, 0), '%VDY'), ((1, 0, 0, 0, 0, 0, 0), '%VDY'), ((0, 6, 0, 0, 0, 0, 0), '%VDY-%VDm'), ((0, 4, 0, 0, 0, 0, 0), '%VDY-%VDm'), ((0, 3, 0, 0, 0, 0, 0), '%VDY-%VDm'), ((0, 2, 0, 0, 0, 0, 0), '%VDY-%VDm'), ((0, 1, 0, 0, 0, 0, 0), '%VDY-%VDm'), ((0, 0, 28, 0, 0, 0, 0), '%VDY-%VDm-%VDd'), ((0, 0, 14, 0, 0, 0, 0), '%VDY-%VDm-%VDd'), ((0, 0, 7, 0, 0, 0, 0), '%VDY-%VDm-%VDd'), ((0, 0, 2, 0, 0, 0, 0), '%VDY-%VDm-%VDd'), ((0, 0, 1, 0, 0, 0, 0), '%VDY-%VDm-%VDd'), ((0, 0, 0, 12, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'), ((0, 0, 0, 6, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'), ((0, 0, 0, 4, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'), ((0, 0, 0, 3, 0, 0, 0), '%VDY-%VDm-%VDd\\\\%VDH:%VDM'), ((0, 0, 0, 2, 0, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 1, 0, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 30, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 15, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 10, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 5, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 2, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 1, 0, 0), '%VDH:%VDM'), ((0, 0, 0, 0, 0, 30, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 15, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 10, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 5, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 2, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 1, 0), '%VDH:%VDM:%VDS'), ((0, 0, 0, 0, 0, 0, 500000), '%VDH:%VDM:%VDVS'), ((0, 0, 0, 0, 0, 0, 200000), '%VDVS'), ((0, 0, 0, 0, 0, 0, 100000), '%VDVS'), ((0, 0, 0, 0, 0, 0, 50000), '%VDVS'), ((0, 0, 0, 0, 0, 0, 10000), '%VDVS'), ) intervals_sec = N.array([(ms*1e-6+s+mi*60+hr*60*60+dy*24*60*60+ mn*(365/12.)*24*60*60+ yr*365*24*60*60) for (yr, mn, dy, hr, mi, s, ms), fmt in intervals]) def bestTickFinder(self, minval, maxval, numticks, extendmin, extendmax, intervals, intervals_sec): """Try to find best choice of numticks ticks between minval and maxval intervals is an array similar to self.intervals intervals_sec is an array similar to self.intervals_sec Returns a tuple (minval, maxval, estimatedsize, ticks, textformat)""" delta = maxval - minval # iterate over different intervals and find one closest to what we want estimated = delta / intervals_sec tick1 = max(estimated.searchsorted(numticks)-1, 0) tick2 = min(tick1+1, len(estimated)-1) del1 = abs(estimated[tick1] - numticks) del2 = abs(estimated[tick2] - numticks) if del1 < del2: best = tick1 else: best = tick2 besttt, format = intervals[best] mindate = utils.floatToDateTime(minval) maxdate = utils.floatToDateTime(maxval) # round min and max to nearest minround = utils.tupleToDateTime(utils.roundDownToTimeTuple(mindate, besttt)) maxround = utils.tupleToDateTime(utils.roundDownToTimeTuple(maxdate, besttt)) if minround == mindate: mintick = minround else: # rounded down, so move on to next tick mintick = utils.addTimeTupleToDateTime(minround, besttt) maxtick = maxround # extend bounds if requested deltamin = utils.datetimeToFloat(mindate)-utils.datetimeToFloat(mintick) if extendmin and (deltamin != 0. and deltamin < delta*0.15): mindate = utils.addTimeTupleToDateTime(minround, [-x for x in besttt]) mintick = mindate deltamax = utils.datetimeToFloat(maxdate)-utils.datetimeToFloat(maxtick) if extendmax and (deltamax != 0. and deltamax < delta*0.15): maxdate = utils.addTimeTupleToDateTime(maxtick, besttt) maxtick = maxdate # make ticks ticks = [] dt = mintick while dt <= maxtick: ticks.append( utils.datetimeToFloat(dt)) dt = utils.addTimeTupleToDateTime(dt, besttt) return ( utils.datetimeToFloat(mindate), utils.datetimeToFloat(maxdate), intervals_sec[best], N.array(ticks), format ) def filterIntervals(self, estint): """Filter intervals and intervals_sec to be multiples of estint seconds.""" intervals = [] intervals_sec = [] for i, inter in enumerate(self.intervals_sec): ratio = estint / inter if abs(ratio-int(ratio)) < ratio*.01: intervals.append(self.intervals[i]) intervals_sec.append(inter) return intervals, N.array(intervals_sec) def getTicks(self): """Calculate and return the position of the major ticks. """ # find minor ticks mindate, maxdate, est, ticks, format = self.bestTickFinder( self.minval, self.maxval, self.numticks, self.extendmin, self.extendmax, self.intervals, self.intervals_sec) # try to make minor ticks divide evenly into major ticks intervals, intervals_sec = self.filterIntervals(est) # get minor ticks ig, ig, ig, minorticks, ig = self.bestTickFinder( mindate, maxdate, self.numminorticks, False, False, intervals, intervals_sec) self.interval = (intervals, intervals_sec) self.minval = mindate self.maxval = maxdate self.minorticks = minorticks self.tickvals = ticks self.autoformat = format veusz-1.15/widgets/fit.py0000644002344000001440000003341711734662204015411 0ustar jssusers00000000000000# fit.py # fitting plotter # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import re import sys import numpy as N import veusz.document as document import veusz.setting as setting import veusz.utils as utils from function import FunctionPlotter import widget try: import minuit except ImportError: minuit = None def minuitFit(evalfunc, params, names, values, xvals, yvals, yserr): """Do fitting with minuit (if installed).""" def chi2(params): """generate a lambda function to impedance-match between PyMinuit's use of multiple parameters versus our use of a single numpy vector.""" c = ((evalfunc(params, xvals) - yvals)**2 / yserr**2).sum() if chi2.runningFit: chi2.iters += 1 p = [chi2.iters, c] + params.tolist() str = ("%5i " + "%8g " * (len(params)+1)) % tuple(p) print str return c namestr = ', '.join(names) fnstr = 'lambda %s: chi2(N.array([%s]))' % (namestr, namestr) # this is safe because the only user-controlled variable is len(names) fn = eval(fnstr, {'chi2' : chi2, 'N' : N}) print 'Fitting via Minuit:' m = minuit.Minuit(fn, fix_x=True, **values) # run the fit chi2.runningFit = True chi2.iters = 0 m.migrad() # do some error analysis have_symerr, have_err = False, False try: chi2.runningFit = False m.hesse() have_symerr = True m.minos() have_err = True except minuit.MinuitError, e: print e if str(e).startswith('Discovered a new minimum'): # the initial fit really failed raise # print the results retchi2 = m.fval dof = len(yvals) - len(params) redchi2 = retchi2 / dof if have_err: print 'Fit results:\n', "\n".join([ u" %s = %g \u00b1 %g (+%g / %g)" % (n, m.values[n], m.errors[n], m.merrors[(n, 1.0)], m.merrors[(n, -1.0)]) for n in names]) elif have_symerr: print 'Fit results:\n', "\n".join([ u" %s = %g \u00b1 %g" % (n, m.values[n], m.errors[n]) for n in names]) print 'MINOS error estimate not available.' else: print 'Fit results:\n', "\n".join([' %s = %g' % (n, m.values[n]) for n in names]) print 'No error analysis available: fit quality uncertain' print "chi^2 = %g, dof = %i, reduced-chi^2 = %g" % (retchi2, dof, redchi2) vals = m.values return vals, retchi2, dof class Fit(FunctionPlotter): """A plotter to fit a function to data.""" typename='fit' allowusercreation=True description='Fit a function to data' def __init__(self, parent, name=None): FunctionPlotter.__init__(self, parent, name=name) if type(self) == Fit: self.readDefaults() self.addAction( widget.Action('fit', self.actionFit, descr = 'Fit function', usertext = 'Fit function') ) @classmethod def addSettings(klass, s): """Construct list of settings.""" FunctionPlotter.addSettings(s) s.add( setting.FloatDict('values', {'a': 0.0, 'b': 1.0}, descr = 'Variables and fit values', usertext='Parameters'), 1 ) s.add( setting.Dataset('xData', 'x', descr = 'Variable containing x data to fit', usertext='X dataset'), 2 ) s.add( setting.Dataset('yData', 'y', descr = 'Variable containing y data to fit', usertext='Y dataset'), 3 ) s.add( setting.Bool('fitRange', False, descr = 'Fit only the data between the ' 'minimum and maximum of the axis for ' 'the function variable', usertext='Fit only range'), 4 ) s.add( setting.WidgetChoice( 'outLabel', '', descr='Write best fit parameters to this text label ' 'after fitting', widgettypes=('label',), usertext='Output label'), 5 ) s.add( setting.Str('outExpr', '', descr = 'Output best fitting expression', usertext='Output expression'), 6, readonly=True ) s.add( setting.Float('chi2', -1, descr = 'Output chi^2 from fitting', usertext='Fit χ2'), 7, readonly=True ) s.add( setting.Int('dof', -1, descr = 'Output degrees of freedom from fitting', usertext='Fit d.o.f.'), 8, readonly=True ) s.add( setting.Float('redchi2', -1, descr = 'Output reduced-chi-squared from fitting', usertext='Fit reduced χ2'), 9, readonly=True ) f = s.get('function') f.newDefault('a + b*x') f.descr = 'Function to fit' # modify description s.get('min').usertext='Min. fit range' s.get('max').usertext='Max. fit range' def providesAxesDependency(self): """This widget provides range information about these axes.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def updateAxisRange(self, axis, depname, axrange): """Update range with range of data.""" dataname = {'sx': 'xData', 'sy': 'yData'}[depname] data = self.settings.get(dataname).getData(self.document) if data: drange = data.getRange() if drange: axrange[0] = min(axrange[0], drange[0]) axrange[1] = max(axrange[1], drange[1]) def initEnviron(self): """Copy data into environment.""" env = self.document.eval_context.copy() env.update( self.settings.values ) return env def updateOutputLabel(self, ops, vals, chi2, dof): """Use best fit parameters to update text label.""" s = self.settings labelwidget = s.get('outLabel').findWidget() if labelwidget is not None: # build up a set of X=Y values loc = self.document.locale txt = [] for l, v in sorted(vals.iteritems()): val = utils.formatNumber(v, '%.4Vg', locale=loc) txt.append( '%s = %s' % (l, val) ) # add chi2 output txt.append( r'\chi^{2}_{\nu} = %s/%i = %s' % ( utils.formatNumber(chi2, '%.4Vg', locale=loc), dof, utils.formatNumber(chi2/dof, '%.4Vg', locale=loc) )) # update label with text text = r'\\'.join(txt) ops.append( document.OperationSettingSet( labelwidget.settings.get('label') , text ) ) def actionFit(self): """Fit the data.""" s = self.settings # update function for fitting try: self.checker.check(s.function, s.variable) except RuntimeError, e: self.logEvalError(e) return # populate the input parameters names = s.values.keys() names.sort() params = N.array( [s.values[i] for i in names] ) # FIXME: loads of error handling!! d = self.document # choose dataset depending on fit variable if s.variable == 'x': xvals = d.getData(s.xData).data ydata = d.getData(s.yData) yvals = ydata.data yserr = ydata.serr else: xvals = d.getData(s.yData).data ydata = d.getData(s.xData) yvals = ydata.data yserr = ydata.serr # if there are no errors on data if yserr is None: if ydata.perr is not None and ydata.nerr is not None: print "Warning: Symmeterising positive and negative errors" yserr = N.sqrt( 0.5*(ydata.perr**2 + ydata.nerr**2) ) else: print "Warning: No errors on y values. Assuming 5% errors." yserr = yvals*0.05 yserr[yserr < 1e-8] = 1e-8 # if the fitRange parameter is on, we chop out data outside the # range of the axis if s.fitRange: # get ranges for axes if s.variable == 'x': drange = self.parent.getAxes((s.xAxis,))[0].getPlottedRange() mask = N.logical_and(xvals >= drange[0], xvals <= drange[1]) else: drange = self.parent.getAxes((s.yAxis,))[0].getPlottedRange() mask = N.logical_and(yvals >= drange[0], yvals <= drange[1]) xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask] print "Fitting %s from %g to %g" % (s.variable, drange[0], drange[1]) # minimum set for fitting if s.min != 'Auto': if s.variable == 'x': mask = xvals >= s.min else: mask = yvals >= s.min xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask] # maximum set for fitting if s.max != 'Auto': if s.variable == 'x': mask = xvals <= s.max else: mask = yvals <= s.max xvals, yvals, yserr = xvals[mask], yvals[mask], yserr[mask] if s.min != 'Auto' or s.max != 'Auto': print "Fitting %s between %s and %s" % (s.variable, s.min, s.max) # various error checks if len(xvals) == 0: sys.stderr.write('No data values. Not fitting.\n') return if len(xvals) != len(yvals) or len(xvals) != len(yserr): sys.stderr.write('Fit data not equal in length. Not fitting.\n') return if len(params) > len(xvals): sys.stderr.write('No degrees of freedom for fit. Not fitting\n') return # actually do the fit, either via Minuit or our own LM fitter chi2 = 1 dof = 1 if minuit is not None: vals, chi2, dof = minuitFit(self.evalfunc, params, names, s.values, xvals, yvals, yserr) else: print 'Minuit not available, falling back to simple L-M fitting:' retn, chi2, dof = utils.fitLM(self.evalfunc, params, xvals, yvals, yserr) vals = {} for i, v in zip(names, retn): vals[i] = float(v) # list of operations do we can undo the changes operations = [] # populate the return parameters operations.append( document.OperationSettingSet(s.get('values'), vals) ) # populate the read-only fit quality params operations.append( document.OperationSettingSet(s.get('chi2'), float(chi2)) ) operations.append( document.OperationSettingSet(s.get('dof'), int(dof)) ) if dof <= 0: print 'No degrees of freedom in fit.\n' redchi2 = -1. else: redchi2 = float(chi2/dof) operations.append( document.OperationSettingSet(s.get('redchi2'), redchi2) ) # expression for fit expr = self.generateOutputExpr(vals) operations.append( document.OperationSettingSet(s.get('outExpr'), expr) ) self.updateOutputLabel(operations, vals, chi2, dof) # actually change all the settings d.applyOperation( document.OperationMultiple(operations, descr='fit') ) def evalfunc(self, params, xvals): # make an environment env = self.initEnviron() s = self.settings env[s.variable] = xvals # set values for real function names = s.values.keys() names.sort() for name, val in zip(names, params): env[name] = val try: return eval(self.checker.compiled, env) + xvals*0. except: return N.nan def generateOutputExpr(self, vals): """Try to generate text form of output expression. vals is a dict of variable: value pairs returns the expression """ paramvals = vals.copy() s = self.settings # also substitute in data name for variable if s.variable == 'x': paramvals['x'] = s.xData else: paramvals['y'] = s.yData # split expression up into parts of text and nums, separated # by non-text/nums parts = re.split('([^A-Za-z0-9.])', s.function) # replace part by things in paramvals, if they exist for i, p in enumerate(parts): if p in paramvals: parts[i] = str( paramvals[p] ) return ''.join(parts) # allow the factory to instantiate an x,y plotter document.thefactory.register( Fit ) veusz-1.15/widgets/line.py0000644002344000001440000001560211734662204015552 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Plotting a line with arrowheads or labels.""" import itertools import math import numpy as N import veusz.qtall as qt4 import veusz.setting as setting import veusz.document as document import veusz.utils as utils import controlgraph import plotters class Line(plotters.FreePlotter): """A line on the plot/graph.""" typename='line' description='Line or arrow' allowusercreation = True def __init__(self, parent, name=None): """Construct plotter.""" plotters.FreePlotter.__init__(self, parent, name=name) if type(self) == Line: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.FreePlotter.addSettings(s) s.add( setting.DatasetOrFloatList('length', [0.2], descr='List of fractional ' 'lengths or dataset', usertext='Lengths', formatting=False), 3 ) s.add( setting.DatasetOrFloatList('angle', [0.], descr='Angle of lines or ' 'dataset', usertext='Angles', formatting=False), 4 ) s.add( setting.Line('Line', descr = 'Line style', usertext = 'Line'), pixmap = 'settings_plotline' ) s.add( setting.ArrowFill('Fill', descr = 'Arrow fill settings', usertext = 'Arrow fill'), pixmap = 'settings_plotmarkerfill' ) s.add( setting.DistancePt('arrowSize', '5pt', descr = 'Size of arrow to plot', usertext='Arrow size', formatting=True), 0) s.add( setting.Arrow('arrowright', 'none', descr = 'Arrow to plot on right side', usertext='Arrow right', formatting=True), 0) s.add( setting.Arrow('arrowleft', 'none', descr = 'Arrow to plot on left side', usertext='Arrow left', formatting=True), 0) def draw(self, posn, phelper, outerbounds = None): """Plot the key on a plotter.""" s = self.settings d = self.document if s.hide: return # get lengths and angles of lines length = s.get('length').getFloatArray(d) angle = s.get('angle').getFloatArray(d) if length is None or angle is None: return # translate coordinates from axes or relative values xpos, ypos = self._getPlotterCoords(posn) if xpos is None or ypos is None: # we can't calculate coordinates return # if a dataset is used, we can't use control items isnotdataset = ( not s.get('xPos').isDataset(d) and not s.get('yPos').isDataset(d) and not s.get('length').isDataset(d) and not s.get('angle').isDataset(d) ) # now do the drawing painter = phelper.painter(self, posn) # adjustable positions for the lines controlgraphitems = [] arrowsize = s.get('arrowSize').convert(painter) # drawing settings for line if not s.Line.hide: painter.setPen( s.get('Line').makeQPen(painter) ) else: painter.setPen( qt4.QPen(qt4.Qt.NoPen) ) # settings for fill if not s.Fill.hide: painter.setBrush( s.get('Fill').makeQBrush() ) else: painter.setBrush( qt4.QBrush() ) # iterate over positions index = 0 dx, dy = posn[2]-posn[0], posn[3]-posn[1] for x, y, l, a in itertools.izip(xpos, ypos, itertools.cycle(length), itertools.cycle(angle)): if N.isfinite(x) and N.isfinite(y) and N.isfinite(a): utils.plotLineArrow(painter, x, y, l*dx, a, arrowsize=arrowsize, arrowleft=s.arrowleft, arrowright=s.arrowright) if isnotdataset: cgi = controlgraph.ControlLine( self, x, y, x + l*dx*math.cos(a/180.*math.pi), y + l*dx*math.sin(a/180.*math.pi)) cgi.index = index cgi.widgetposn = posn index += 1 controlgraphitems.append(cgi) phelper.setControlGraph(self, controlgraphitems) def updateControlItem(self, cgi, pt1, pt2): """If control items are moved, update line.""" s = self.settings # calculate new position coordinate for item xpos, ypos = self._getGraphCoords(cgi.widgetposn, pt1[0], pt1[1]) if xpos is None or ypos is None: return length = ( math.sqrt( (pt2[0]-pt1[0])**2 + (pt2[1]-pt1[1])**2 ) / (cgi.widgetposn[2]-cgi.widgetposn[0]) ) angle = ( (math.atan2( pt2[1]-pt1[1], pt2[0]-pt1[0] ) * 180. / math.pi) % 360. ) x, y = list(s.xPos), list(s.yPos) l, a = list(s.length), list(s.angle) x[cgi.index] = xpos y[cgi.index] = ypos l[min(cgi.index, len(l)-1)] = length a[min(cgi.index, len(a)-1)] = angle operations = ( document.OperationSettingSet(s.get('xPos'), x), document.OperationSettingSet(s.get('yPos'), y), document.OperationSettingSet(s.get('length'), l), document.OperationSettingSet(s.get('angle'), a), ) self.document.applyOperation( document.OperationMultiple(operations, descr='adjust shape') ) document.thefactory.register( Line ) veusz-1.15/widgets/controlgraph.py0000644002344000001440000005766511734662204017344 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Classes for moving widgets around Control items have a createGraphicsItem method which returns a graphics item to control the object """ import math import itertools import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting ############################################################################## class _ShapeCorner(qt4.QGraphicsRectItem): """Representing the corners of the rectangle.""" def __init__(self, parent, rotator=False): qt4.QGraphicsRectItem.__init__(self, parent) if rotator: self.setBrush( qt4.QBrush(setting.settingdb.color('cntrlline')) ) self.setRect(-3, -3, 6, 6) else: self.setBrush(qt4.QBrush(setting.settingdb.color('cntrlcorner')) ) self.setRect(-5, -5, 10, 10) self.setPen(qt4.QPen(qt4.Qt.NoPen)) self.setFlag(qt4.QGraphicsItem.ItemIsMovable) self.setZValue(3.) def mouseMoveEvent(self, event): """Notify parent on move.""" qt4.QGraphicsRectItem.mouseMoveEvent(self, event) self.parentItem().updateFromCorner(self, event) def mouseReleaseEvent(self, event): """Notify parent on unclicking.""" qt4.QGraphicsRectItem.mouseReleaseEvent(self, event) self.parentItem().updateWidget() ############################################################################## def controlLinePen(): """Get pen for lines around shapes.""" return qt4.QPen(setting.settingdb.color('cntrlline'), 2, qt4.Qt.DotLine) class _EdgeLine(qt4.QGraphicsLineItem): """Line used for edges of resizing box.""" def __init__(self, parent, ismovable = True): qt4.QGraphicsLineItem.__init__(self, parent) self.setPen(controlLinePen()) self.setZValue(2.) if ismovable: self.setFlag(qt4.QGraphicsItem.ItemIsMovable) self.setCursor(qt4.Qt.SizeAllCursor) def mouseMoveEvent(self, event): """Notify parent on move.""" qt4.QGraphicsLineItem.mouseMoveEvent(self, event) self.parentItem().updateFromLine(self, self.pos()) def mouseReleaseEvent(self, event): """Notify parent on unclicking.""" qt4.QGraphicsLineItem.mouseReleaseEvent(self, event) self.parentItem().updateWidget() ############################################################################## class ControlMarginBox(object): def __init__(self, widget, posn, maxposn, painthelper, ismovable = True, isresizable = True): """Create control box item. widget: widget this is controllng posn: coordinates of box [x1, y1, x2, y2] maxposn: coordinates of biggest possibe box painthelper: painterhelper to get scaling from ismovable: box can be moved isresizable: box can be resized """ # save values self.posn = posn self.maxposn = maxposn self.widget = widget self.ismovable = ismovable self.isresizable = isresizable # we need these later to convert back to original units self.pagesize = painthelper.pagesize self.scaling = painthelper.scaling self.dpi = painthelper.dpi def createGraphicsItem(self): return _GraphMarginBox(self) def setWidgetMargins(self): """A helpful routine for setting widget margins after moving or resizing. This is called by the widget after receiving updateControlItem """ s = self.widget.settings # get margins in pixels left = self.posn[0] - self.maxposn[0] right = self.maxposn[2] - self.posn[2] top = self.posn[1] - self.maxposn[1] bottom = self.maxposn[3] - self.posn[3] # set up fake painthelper containing veusz scalings helper = document.PaintHelper(self.pagesize, scaling=self.scaling, dpi=self.dpi) # convert to physical units left = s.get('leftMargin').convertInverse(left, helper) right = s.get('rightMargin').convertInverse(right, helper) top = s.get('topMargin').convertInverse(top, helper) bottom = s.get('bottomMargin').convertInverse(bottom, helper) # modify widget margins operations = ( document.OperationSettingSet(s.get('leftMargin'), left), document.OperationSettingSet(s.get('rightMargin'), right), document.OperationSettingSet(s.get('topMargin'), top), document.OperationSettingSet(s.get('bottomMargin'), bottom) ) self.widget.document.applyOperation( document.OperationMultiple(operations, descr='resize margins')) def setPageSize(self): """Helper for setting document/page widget size. This is called by the widget after receiving updateControlItem """ s = self.widget.settings # get margins in pixels width = self.posn[2] - self.posn[0] height = self.posn[3] - self.posn[1] # set up fake painter containing veusz scalings helper = document.PaintHelper(self.pagesize, scaling=self.scaling, dpi=self.dpi) # convert to physical units width = s.get('width').convertInverse(width, helper) height = s.get('height').convertInverse(height, helper) # modify widget margins operations = ( document.OperationSettingSet(s.get('width'), width), document.OperationSettingSet(s.get('height'), height), ) self.widget.document.applyOperation( document.OperationMultiple(operations, descr='change page size')) class _GraphMarginBox(qt4.QGraphicsItem): """A box which can be moved or resized. Can automatically set margins or widget """ # posn coords of each corner mapcornertoposn = ( (0, 1), (2, 1), (0, 3), (2, 3) ) def __init__(self, params): """Create control box item.""" qt4.QGraphicsItem.__init__(self) self.params = params self.setZValue(2.) # create corners of box self.corners = [_ShapeCorner(self) for i in xrange(4)] # lines connecting corners self.lines = [_EdgeLine(self, ismovable=params.ismovable) for i in xrange(4)] # hide corners if box is not resizable if not params.isresizable: for c in self.corners: c.hide() self.updateCornerPosns() def updateCornerPosns(self): """Update all corners from updated box.""" par = self.params pos = par.posn # update cursors self.corners[0].setCursor(qt4.Qt.SizeFDiagCursor) self.corners[1].setCursor(qt4.Qt.SizeBDiagCursor) self.corners[2].setCursor(qt4.Qt.SizeBDiagCursor) self.corners[3].setCursor(qt4.Qt.SizeFDiagCursor) # trim box to maximum size pos[0] = max(pos[0], par.maxposn[0]) pos[1] = max(pos[1], par.maxposn[1]) pos[2] = min(pos[2], par.maxposn[2]) pos[3] = min(pos[3], par.maxposn[3]) # move corners for corner, (xindex, yindex) in itertools.izip(self.corners, self.mapcornertoposn): corner.setPos( qt4.QPointF( pos[xindex], pos[yindex] ) ) # move lines w, h = pos[2]-pos[0], pos[3]-pos[1] self.lines[0].setPos(pos[0], pos[1]) self.lines[0].setLine(0, 0, w, 0) self.lines[1].setPos(pos[2], pos[1]) self.lines[1].setLine(0, 0, 0, h) self.lines[2].setPos(pos[2], pos[3]) self.lines[2].setLine(0, 0, -w, 0) self.lines[3].setPos(pos[0], pos[3]) self.lines[3].setLine(0, 0, 0, -h) def updateFromLine(self, line, thispos): """Edge line of box was moved - update bounding box.""" par = self.params # need old coordinate to work out how far line has moved try: li = self.lines.index(line) except ValueError: return ox = par.posn[ (0, 2, 2, 0)[li] ] oy = par.posn[ (1, 1, 3, 3)[li] ] # add on deltas to box coordinates dx, dy = thispos.x()-ox, thispos.y()-oy # make sure box can't be moved outside the allowed region if dx > 0: dx = min(dx, par.maxposn[2]-par.posn[2]) else: dx = -min(abs(dx), abs(par.maxposn[0]-par.posn[0])) if dy > 0: dy = min(dy, par.maxposn[3]-par.posn[3]) else: dy = -min(abs(dy), abs(par.maxposn[1]-par.posn[1])) # move the box par.posn[0] += dx par.posn[1] += dy par.posn[2] += dx par.posn[3] += dy # update corner coords and other line coordinates self.updateCornerPosns() def updateFromCorner(self, corner, event): """Move corner of box to new position.""" try: index = self.corners.index(corner) except ValueError: return pos = self.params.posn pos[ self.mapcornertoposn[index][0] ] = corner.x() pos[ self.mapcornertoposn[index][1] ] = corner.y() # this is needed if the corners move past each other if pos[0] > pos[2]: # swap x pos[0], pos[2] = pos[2], pos[0] self.corners[0], self.corners[1] = self.corners[1], self.corners[0] self.corners[2], self.corners[3] = self.corners[3], self.corners[2] if pos[1] > pos[3]: # swap y pos[1], pos[3] = pos[3], pos[1] self.corners[0], self.corners[2] = self.corners[2], self.corners[0] self.corners[1], self.corners[3] = self.corners[3], self.corners[1] self.updateCornerPosns() def boundingRect(self): return qt4.QRectF(0, 0, 0, 0) def paint(self, painter, option, widget): """Intentionally empty painter.""" def updateWidget(self): """Update widget margins.""" self.params.widget.updateControlItem(self.params) ############################################################################## class ControlResizableBox(object): """Control a resizable box. Item resizes centred around a position """ def __init__(self, widget, posn, dims, angle, allowrotate=False): """Initialise with widget and boxbounds shape. Rotation is allowed if allowrotate is set """ self.widget = widget self.posn = posn self.dims = dims self.angle = angle self.allowrotate = allowrotate def createGraphicsItem(self): return _GraphResizableBox(self) class _GraphResizableBox(qt4.QGraphicsRectItem): """Control a resizable box. Item resizes centred around a position """ def __init__(self, params): """Initialise with widget and boxbounds shape. Rotation is allowed if allowrotate is set """ qt4.QGraphicsRectItem.__init__(self, params.posn[0], params.posn[1], params.dims[0], params.dims[1]) self.params = params self.rotate(params.angle) # initial setup self.setCursor(qt4.Qt.SizeAllCursor) self.setZValue(1.) self.setFlag(qt4.QGraphicsItem.ItemIsMovable) self.setPen(controlLinePen()) self.setBrush( qt4.QBrush() ) # create child graphicsitem for each corner self.corners = [_ShapeCorner(self) for i in xrange(4)] self.corners[0].setCursor(qt4.Qt.SizeFDiagCursor) self.corners[1].setCursor(qt4.Qt.SizeBDiagCursor) self.corners[2].setCursor(qt4.Qt.SizeBDiagCursor) self.corners[3].setCursor(qt4.Qt.SizeFDiagCursor) # whether box is allowed to be rotated self.rotator = None if params.allowrotate: self.rotator = _ShapeCorner(self, rotator=True) self.rotator.setCursor(qt4.Qt.CrossCursor) self.updateCorners() self.rotator.setPos( 0, -abs(params.dims[1]*0.5) ) def updateFromCorner(self, corner, event): """Take position and update corners.""" par = self.params if corner in self.corners: # compute size from corner position par.dims[0] = abs(corner.pos().x()*2) par.dims[1] = abs(corner.pos().y()*2) elif corner == self.rotator: # work out angle relative to centre of widget delta = event.scenePos() - self.scenePos() angle = math.atan2( delta.y(), delta.x() ) # change to degrees from correct direction par.angle = (angle*(180/math.pi) + 90.) % 360 # apply rotation selfpt = self.pos() self.resetTransform() self.setPos(selfpt) self.rotate(par.angle) self.updateCorners() def updateCorners(self): """Update corners on size.""" size = 5 par = self.params # update position and size self.setPos( par.posn[0], par.posn[1] ) self.setRect( -par.dims[0]*0.5, -par.dims[1]*0.5, par.dims[0], par.dims[1] ) # update corners self.corners[0].setPos(-par.dims[0]*0.5, -par.dims[1]*0.5) self.corners[1].setPos( par.dims[0]*0.5, -par.dims[1]*0.5) self.corners[2].setPos(-par.dims[0]*0.5, par.dims[1]*0.5) self.corners[3].setPos( par.dims[0]*0.5, par.dims[1]*0.5) if self.rotator: # set rotator position (constant distance) self.rotator.setPos( 0, -abs(par.dims[1]*0.5) ) def mouseReleaseEvent(self, event): """If the item has been moved, do and update.""" qt4.QGraphicsRectItem.mouseReleaseEvent(self, event) self.updateWidget() def mouseMoveEvent(self, event): """Keep track of movement.""" qt4.QGraphicsRectItem.mouseMoveEvent(self, event) self.params.posn = [self.pos().x(), self.pos().y()] def updateWidget(self): """Tell the user the graphicsitem has been moved or resized.""" self.params.widget.updateControlItem(self.params) ############################################################################## class ControlMovableBox(ControlMarginBox): """Item for user display for controlling widget. This is a dotted movable box with an optional "cross" where the real position of the widget is """ def __init__(self, widget, posn, painthelper, crosspos=None): ControlMarginBox.__init__(self, widget, posn, [-10000, -10000, 10000, 10000], painthelper, isresizable=False) self.deltacrosspos = (crosspos[0] - self.posn[0], crosspos[1] - self.posn[1]) def createGraphicsItem(self): return _GraphMovableBox(self) class _GraphMovableBox(_GraphMarginBox): def __init__(self, params): _GraphMarginBox.__init__(self, params) self.cross = _ShapeCorner(self) self.cross.setCursor(qt4.Qt.SizeAllCursor) self.updateCornerPosns() def updateCornerPosns(self): _GraphMarginBox.updateCornerPosns(self) par = self.params if hasattr(self, 'cross'): # this fails if called before self.cross is initialised! self.cross.setPos( par.deltacrosspos[0] + par.posn[0], par.deltacrosspos[1] + par.posn[1] ) def updateFromCorner(self, corner, event): if corner == self.cross: # if cross moves, move whole box par = self.params cx, cy = self.cross.pos().x(), self.cross.pos().y() dx = cx - (par.deltacrosspos[0] + par.posn[0]) dy = cy - (par.deltacrosspos[1] + par.posn[1]) par.posn[0] += dx par.posn[1] += dy par.posn[2] += dx par.posn[3] += dy self.updateCornerPosns() else: _GraphMarginBox.updateFromCorner(self, corner, event) ############################################################################## class ControlLine(object): """For controlling the position and ends of a line.""" def __init__(self, widget, x1, y1, x2, y2): self.widget = widget self.line = x1, y1, x2, y2 def createGraphicsItem(self): return _GraphLine(self) class _GraphLine(qt4.QGraphicsLineItem): """Represents the line as a graphics item.""" def __init__(self, params): qt4.QGraphicsLineItem.__init__(self, *params.line) self.params = params self.setCursor(qt4.Qt.SizeAllCursor) self.setFlag(qt4.QGraphicsItem.ItemIsMovable) self.setZValue(1.) self.setPen(controlLinePen()) self.pts = [_ShapeCorner(self, rotator=True), _ShapeCorner(self, rotator=True)] self.pts[0].setPos(params.line[0], params.line[1]) self.pts[1].setPos(params.line[2], params.line[3]) self.pts[0].setCursor(qt4.Qt.CrossCursor) self.pts[1].setCursor(qt4.Qt.CrossCursor) def updateFromCorner(self, corner, event): """Take position and update ends of line.""" line = (self.pts[0].x(), self.pts[0].y(), self.pts[1].x(), self.pts[1].y()) self.setLine(*line) def mouseReleaseEvent(self, event): """If widget has moved, tell it.""" qt4.QGraphicsItem.mouseReleaseEvent(self, event) self.updateWidget() def updateWidget(self): """Update caller with position and line positions.""" pt1 = ( self.pts[0].x() + self.pos().x(), self.pts[0].y() + self.pos().y() ) pt2 = ( self.pts[1].x() + self.pos().x(), self.pts[1].y() + self.pos().y() ) self.params.widget.updateControlItem(self.params, pt1, pt2) ############################################################################# class _AxisGraphicsLineItem(qt4.QGraphicsLineItem): def __init__(self, parent): qt4.QGraphicsLineItem.__init__(self, parent) self.parent = parent self.setPen(controlLinePen()) self.setZValue(2.) self.setFlag(qt4.QGraphicsItem.ItemIsMovable) def mouseReleaseEvent(self, event): """Notify finished.""" qt4.QGraphicsLineItem.mouseReleaseEvent(self, event) self.parent.updateWidget() def mouseMoveEvent(self, event): """Move the axis.""" qt4.QGraphicsLineItem.mouseMoveEvent(self, event) self.parent.doLineUpdate() class ControlAxisLine(object): """Controlling position of an axis.""" def __init__(self, widget, direction, minpos, maxpos, axispos, maxposn): self.widget = widget self.direction = direction if minpos > maxpos: minpos, maxpos = maxpos, minpos self.minpos = self.minzoom = self.minorig = minpos self.maxpos = self.maxzoom = self.maxorig = maxpos self.axisorigpos = self.axispos = axispos self.maxposn = maxposn def zoomed(self): """Is this a zoom?""" return self.minzoom != self.minorig or self.maxzoom != self.maxorig def moved(self): """Has axis moved?""" return ( self.minpos != self.minorig or self.maxpos != self.maxorig or self.axisorigpos != self.axispos ) def createGraphicsItem(self): return _GraphAxisLine(self) class _GraphAxisLine(qt4.QGraphicsItem): curs = {True: qt4.Qt.SizeVerCursor, False: qt4.Qt.SizeHorCursor} curs_zoom = {True: qt4.Qt.SplitVCursor, False: qt4.Qt.SplitHCursor} def __init__(self, params): """Line is about to be shown.""" qt4.QGraphicsItem.__init__(self) self.params = params self.pts = [ _ShapeCorner(self), _ShapeCorner(self), _ShapeCorner(self), _ShapeCorner(self) ] self.line = _AxisGraphicsLineItem(self) # set cursors and tooltips for items self.horz = (params.direction == 'horizontal') for p in self.pts[0:2]: p.setCursor(self.curs[not self.horz]) p.setToolTip("Move axis ends") for p in self.pts[2:]: p.setCursor(self.curs_zoom[not self.horz]) p.setToolTip("Change axis scale") self.line.setCursor( self.curs[self.horz] ) self.line.setToolTip("Move axis position") self.setZValue(2.) self.updatePos() def updatePos(self): """Set ends of line and line positions from stored values.""" par = self.params mxp = par.maxposn def _clip(*args): """Clip positions to bounds of box given coords.""" par.minpos = max(par.minpos, mxp[args[0]]) par.maxpos = min(par.maxpos, mxp[args[1]]) par.axispos = max(par.axispos, mxp[args[2]]) par.axispos = min(par.axispos, mxp[args[3]]) if self.horz: _clip(0, 2, 1, 3) # set positions if par.zoomed(): self.line.setPos(par.minzoom, par.axispos) self.line.setLine(0, 0, par.maxzoom-par.minzoom, 0) else: self.line.setPos(par.minpos, par.axispos) self.line.setLine(0, 0, par.maxpos-par.minpos, 0) self.pts[0].setPos(par.minpos, par.axispos) self.pts[1].setPos(par.maxpos, par.axispos) self.pts[2].setPos(par.minzoom, par.axispos-15) self.pts[3].setPos(par.maxzoom, par.axispos-15) else: _clip(1, 3, 0, 2) # set positions if par.zoomed(): self.line.setPos(par.axispos, par.minzoom) self.line.setLine(0, 0, 0, par.maxzoom-par.minzoom) else: self.line.setPos(par.axispos, par.minpos) self.line.setLine(0, 0, 0, par.maxpos-par.minpos) self.pts[0].setPos(par.axispos, par.minpos) self.pts[1].setPos(par.axispos, par.maxpos) self.pts[2].setPos(par.axispos+15, par.minzoom) self.pts[3].setPos(par.axispos+15, par.maxzoom) def updateFromCorner(self, corner, event): """Ends of axis have moved, so update values.""" par = self.params pt = (corner.y(), corner.x())[self.horz] # which end has moved? if corner is self.pts[0]: # horizonal or vertical axis? par.minpos = pt elif corner is self.pts[1]: par.maxpos = pt elif corner is self.pts[2]: par.minzoom = pt elif corner is self.pts[3]: par.maxzoom = pt # swap round end points if min > max if par.minpos > par.maxpos: par.minpos, par.maxpos = par.maxpos, par.minpos self.pts[0], self.pts[1] = self.pts[1], self.pts[0] self.updatePos() def doLineUpdate(self): """Line has moved, so update position.""" pos = self.line.pos() if self.horz: self.params.axispos = pos.y() else: self.params.axispos = pos.x() self.updatePos() def updateWidget(self): """Tell widget to update.""" self.params.widget.updateControlItem(self.params) def boundingRect(self): """Intentionally zero bounding rect.""" return qt4.QRectF(0, 0, 0, 0) def paint(self, painter, option, widget): """Intentionally empty painter.""" veusz-1.15/widgets/boxplot.py0000644002344000001440000004322311734662204016312 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For making box plots.""" import math from itertools import izip import numpy as N import veusz.qtall as qt4 import veusz.setting as setting import veusz.document as document import veusz.utils as utils from plotters import GenericPlotter def percentile(sortedds, perc): """Given a sorted dataset, get the percentile perc. Interpolates between data points.""" index = perc * 0.01 * (sortedds.shape[0]-1) # interpolate between indices frac, index = math.modf(index) index = int(index) indexplus1 = min(index+1, sortedds.shape[0]-1) interpol = (1-frac)*sortedds[index] + frac*sortedds[indexplus1] return interpol def swapline(painter, x1, y1, x2, y2, swap): """Draw line, swapping x and y coordinates if swap is True.""" if swap: painter.drawLine( qt4.QPointF(y1, x1), qt4.QPointF(y2, x2) ) else: painter.drawLine( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) ) def swapbox(painter, x1, y1, x2, y2, swap): """Return box, swapping x and y coordinates if swap is True.""" if swap: return qt4.QRectF(qt4.QPointF(y1, x1), qt4.QPointF(y2, x2)) else: return qt4.QRectF(qt4.QPointF(x1, y1), qt4.QPointF(x2, y2)) class _Stats(object): """Store statistics about box.""" def calculate(self, data, whiskermode): """Calculate statistics for data.""" cleaned = data[ N.isfinite(data) ] cleaned.sort() self.median = percentile(cleaned, 50) self.botquart = percentile(cleaned, 25) self.topquart = percentile(cleaned, 75) self.mean = N.mean(cleaned) if whiskermode == 'min/max': self.botwhisker = cleaned.min() self.topwhisker = cleaned.max() elif whiskermode == '1.5IQR': iqr = self.topquart - self.botquart eltop = N.searchsorted(cleaned, self.topquart+1.5*iqr)-1 self.topwhisker = cleaned[eltop] elbot = max(N.searchsorted(cleaned, self.botquart-1.5*iqr)-1, 0) self.botwhisker = cleaned[elbot] elif whiskermode == '1 stddev': stddev = N.std(cleaned) self.topwhisker = self.mean+stddev self.botwhisker = self.mean-stddev elif whiskermode == '9/91 percentile': self.topwhisker = percentile(cleaned, 91) self.botwhisker = percentile(cleaned, 9) elif whiskermode == '2/98 percentile': self.topwhisker = percentile(cleaned, 98) self.botwhisker = percentile(cleaned, 2) else: raise RuntimeError, "Invalid whisker mode" self.outliers = cleaned[ (cleaned < self.botwhisker) | (cleaned > self.topwhisker) ] class BoxPlot(GenericPlotter): """Plot bar charts.""" typename='boxplot' allowusercreation=True description='Plot box plots' def __init__(self, parent, name=None): """Initialise box plot.""" GenericPlotter.__init__(self, parent, name=name) if type(self) == BoxPlot: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" GenericPlotter.addSettings(s) s.remove('key') s.add( setting.Choice('whiskermode', ('min/max', '1.5IQR', '1 stddev', '9/91 percentile', '2/98 percentile'), '1.5IQR', descr = 'Whisker mode', usertext='Whisker mode'), 0 ) s.add( setting.Choice('direction', ('horizontal', 'vertical'), 'vertical', descr = 'Horizontal or vertical boxes', usertext='Direction'), 0 ) s.add( setting.DatasetOrStr('labels', '', descr='Dataset or string to label bars', usertext='Labels', datatype='text'), 0 ) s.add( setting.DatasetOrFloatList( 'posn', '', descr = 'Dataset or list of values giving ' 'positions of boxes (optional)', usertext='Positions'), 0 ) # calculate statistics from these datasets s.add( setting.Datasets('values', ('data',), descr = 'Datasets containing values to ' 'calculate statistics for', usertext='Datasets'), 0 ) # alternate mode where data are provided for boxes s.add( setting.DatasetOrFloatList( 'whiskermax', '', descr='Dataset with whisker maxima or list of values', usertext='Whisker max'), 0 ) s.add( setting.DatasetOrFloatList( 'whiskermin', '', descr='Dataset with whisker minima or list of values', usertext='Whisker min'), 0 ) s.add( setting.DatasetOrFloatList( 'boxmax', '', descr='Dataset with box maxima or list of values', usertext='Box max'), 0 ) s.add( setting.DatasetOrFloatList( 'boxmin', '', descr='Dataset with box minima or list of values', usertext='Box min'), 0 ) s.add( setting.DatasetOrFloatList( 'median', '', descr='Dataset with medians or list of values', usertext='Median'), 0 ) s.add( setting.DatasetOrFloatList( 'mean', '', descr='Dataset with means or list of values', usertext='Mean'), 0 ) # switch between different modes s.add( setting.BoolSwitch('calculate', True, descr = 'Calculate statistics from datasets' ' rather than given manually', usertext = 'Calculate', settingstrue=('whiskermode', 'values'), settingsfalse=('boxmin', 'whiskermin', 'boxmax', 'whiskermax', 'mean', 'median')), 0 ) # formatting options s.add( setting.Float('fillfraction', 0.75, descr = 'Fill fraction of boxes', usertext='Fill fraction', formatting=True) ) s.add( setting.Marker('outliersmarker', 'circle', descr = 'Marker for outliers', usertext='Outliers', formatting=True) ) s.add( setting.Marker('meanmarker', 'linecross', descr = 'Marker for mean', usertext='Mean', formatting=True) ) s.add( setting.DistancePt('markerSize', '3pt', descr = 'Size of markers to plot', usertext='Markers size', formatting=True) ) s.add( setting.GraphBrush( 'Fill', descr = 'Box fill', usertext='Box fill'), pixmap='settings_bgfill' ) s.add( setting.Line('Border', descr = 'Box border line', usertext='Box border'), pixmap='settings_border') s.add( setting.Line('Whisker', descr = 'Whisker line', usertext='Whisker line'), pixmap='settings_whisker') s.add( setting.Line('MarkersLine', descr = 'Line around markers', usertext = 'Markers border'), pixmap = 'settings_plotmarkerline' ) s.add( setting.BoxPlotMarkerFillBrush('MarkersFill', descr = 'Markers fill', usertext = 'Markers fill'), pixmap = 'settings_plotmarkerfill' ) @property def userdescription(self): """Friendly description for user.""" s = self.settings return "values='%s', position='%s'" % ( ', '.join(s.values), s.posn) def providesAxesDependency(self): """This widget provides range information about these axes.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def rangeManual(self): """For updating range in manual mode.""" s = self.settings ds = [] for name in ('whiskermin', 'whiskermax', 'boxmin', 'boxmax', 'mean', 'median'): ds.append( s.get(name).getData(self.document) ) r = [N.inf, -N.inf] if None not in ds: concat = N.concatenate([d.data for d in ds]) r[0] = N.nanmin(concat) r[1] = N.nanmax(concat) return r def getPosns(self): """Get values of positions of bars.""" s = self.settings doc = self.document posns = s.get('posn').getData(doc) if posns is not None: # manual positions return posns.data else: if s.calculate: # number of datasets vals = s.get('values').getData(doc) else: # length of mean array vals = s.get('mean').getData(doc) if vals: vals = vals.data if vals is None: return N.array([]) else: return N.arange(1, len(vals)+1, dtype=N.float64) def updateAxisRange(self, axis, depname, axrange): """Update axis range from data.""" s = self.settings doc = self.document if ( (depname == 'sx' and s.direction == 'horizontal') or (depname == 'sy' and s.direction == 'vertical') ): # update axis in direction of data if s.calculate: # update from values values = s.get('values').getData(doc) if values: for v in values: axrange[0] = min(axrange[0], N.nanmin(v.data)) axrange[1] = max(axrange[1], N.nanmax(v.data)) else: # update from manual entries drange = self.rangeManual() axrange[0] = min(axrange[0], drange[0]) axrange[1] = max(axrange[1], drange[1]) else: # update axis in direction of datasets posns = self.getPosns() if len(posns) > 0: axrange[0] = min(axrange[0], N.nanmin(posns)-0.5) axrange[1] = max(axrange[1], N.nanmax(posns)+0.5) def getAxisLabels(self, direction): """Get labels for axis if using a label axis.""" s = self.settings doc = self.document text = s.get('labels').getData(doc, checknull=True) values = s.get('values').getData(doc) if text is None or values is None: return (None, None) positions = self.getPosns() return (text, positions) def calcStats(self, data): """Calculate statistics for data.""" stats = _Stats() def plotBox(self, painter, axes, boxposn, posn, width, clip, stats): """Draw box for dataset.""" s = self.settings horz = (s.direction == 'horizontal') # convert quartiles, top and bottom whiskers to plotter medplt, botplt, topplt, botwhisplt, topwhisplt = tuple( axes[not horz].dataToPlotterCoords( posn, N.array([ stats.median, stats.botquart, stats.topquart, stats.botwhisker, stats.topwhisker ])) ) # draw whisker top to bottom p = s.Whisker.makeQPenWHide(painter) p.setCapStyle(qt4.Qt.FlatCap) painter.setPen(p) swapline(painter, boxposn, topwhisplt, boxposn, botwhisplt, horz) # draw ends of whiskers endsize = width/2 swapline(painter, boxposn-endsize/2, topwhisplt, boxposn+endsize/2, topwhisplt, horz) swapline(painter, boxposn-endsize/2, botwhisplt, boxposn+endsize/2, botwhisplt, horz) # draw box fill boxpath = qt4.QPainterPath() boxpath.addRect( swapbox(painter, boxposn-width/2, botplt, boxposn+width/2, topplt, horz) ) utils.brushExtFillPath(painter, s.Fill, boxpath) # draw line across box p = s.Whisker.makeQPenWHide(painter) p.setCapStyle(qt4.Qt.FlatCap) painter.setPen(p) swapline(painter, boxposn-width/2, medplt, boxposn+width/2, medplt, horz) # draw box painter.strokePath(boxpath, s.Border.makeQPenWHide(painter) ) # draw outliers painter.setPen( s.MarkersLine.makeQPenWHide(painter) ) painter.setBrush( s.MarkersFill.makeQBrushWHide() ) markersize = s.get('markerSize').convert(painter) if stats.outliers.shape[0] != 0: pltvals = axes[not horz].dataToPlotterCoords(posn, stats.outliers) otherpos = N.zeros(pltvals.shape) + boxposn if horz: x, y = pltvals, otherpos else: x, y = otherpos, pltvals utils.plotMarkers( painter, x, y, s.outliersmarker, markersize, clip=clip ) # draw mean meanplt = axes[not horz].dataToPlotterCoords( posn, N.array([stats.mean]))[0] if horz: x, y = meanplt, boxposn else: x, y = boxposn, meanplt utils.plotMarker( painter, x, y, s.meanmarker, markersize ) def draw(self, parentposn, phelper, outerbounds=None): """Plot the data on a plotter.""" widgetposn = GenericPlotter.draw(self, parentposn, phelper, outerbounds=outerbounds) s = self.settings # exit if hidden if s.hide: return # get data doc = self.document positions = self.getPosns() if s.calculate: # calculate from data values = s.get('values').getData(doc) if values is None: return else: # use manual datasets datasets = [ s.get(x).getData(doc) for x in ('whiskermin', 'whiskermax', 'boxmin', 'boxmax', 'mean', 'median') ] if None in datasets: return # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there are no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return clip = self.clipAxesBounds(axes, widgetposn) painter = phelper.painter(self, widgetposn, clip=clip) # get boxes visible along direction of boxes to work out width horz = (s.direction == 'horizontal') plotposns = axes[horz].dataToPlotterCoords(widgetposn, positions) if horz: inplot = (plotposns > widgetposn[1]) & (plotposns < widgetposn[3]) else: inplot = (plotposns > widgetposn[0]) & (plotposns < widgetposn[2]) inplotposn = plotposns[inplot] if inplotposn.shape[0] < 2: if horz: width = (widgetposn[3]-widgetposn[1])*0.5 else: width = (widgetposn[2]-widgetposn[0])*0.5 else: # use minimum different between points to get width inplotposn.sort() width = N.nanmin(inplotposn[1:] - inplotposn[:-1]) # adjust width width = width * s.fillfraction if s.calculate: # calculated boxes for vals, plotpos in izip(values, plotposns): stats = _Stats() stats.calculate(vals.data, s.whiskermode) self.plotBox(painter, axes, plotpos, widgetposn, width, clip, stats) else: # manually given boxes vals = [d.data for d in datasets] + [plotposns] lens = [len(d) for d in vals] for i in xrange(min(lens)): stats = _Stats() stats.topwhisker = vals[0][i] stats.botwhisker = vals[1][i] stats.botquart = vals[2][i] stats.topquart = vals[3][i] stats.mean = vals[4][i] stats.median = vals[5][i] stats.outliers = N.array([]) self.plotBox(painter, axes, vals[6][i], widgetposn, width, clip, stats) # allow the factory to instantiate a boxplot document.thefactory.register( BoxPlot ) veusz-1.15/widgets/widget.py0000644002344000001440000003071211734662204016105 0ustar jssusers00000000000000# widget.py # fundamental graph plotting widget # Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import itertools import veusz.document as document import veusz.utils as utils import veusz.setting as setting class Action(object): """A class to wrap functions operating on widgets. Attributes: name: name of action function: function to call with no arguments descr: description of action usertext: name of action to display to user """ def __init__(self, name, function, descr='', usertext=''): """Initialise Action Name of action is name Calls function function() on invocation Action has description descr Usertext is short form of name to display to user.""" self.name = name self.function = function self.descr = descr self.usertext = usertext class Widget(object): """ Fundamental plotting widget interface.""" # differentiate widgets, settings and setting nodetype = 'widget' typename = 'generic' allowusercreation = False # list of allowed types this can have as a parent allowedparenttypes = [] def __init__(self, parent, name=None): """Initialise a blank widget.""" # save parent widget for later self.parent = parent self.document = None if not self.isAllowedParent(parent): raise RuntimeError, "Widget parent is of incorrect type" if name is None: name = self.chooseName() self.name = name # propagate document if parent is not None: self.document = parent.document parent.addChild(self) # store child widgets self.children = [] # position of this widget on its parent self.position = (0., 0., 1., 1.) # settings for widget self.settings = setting.Settings( 'Widget_' + self.typename, setnsmode='widgetsettings' ) self.settings.parent = self self.addSettings(self.settings) # actions for widget self.actions = [] @classmethod def addSettings(klass, s): """Add items to settings s.""" s.add( setting.Bool('hide', False, descr = 'Hide object', usertext = 'Hide', formatting = True) ) def isWidget(self): """Is this object a widget?""" return True def getDocument(self): """Return document. Unfortunately we need this as document is shadowed in StyleSheet, sigh.""" return self.document def rename(self, name): """Change name of self.""" if self.parent is None: raise ValueError, 'Cannot rename root widget' if name.find('/') != -1: raise ValueError, 'Names cannot contain "/"' # check whether name already exists in siblings for i in self.parent.children: if i != self and i.name == name: raise ValueError, 'New name "%s" already exists' % name self.name = name def addDefaultSubWidgets(self): '''Add default sub widgets to widget, if any''' pass def addAction(self, action): """Assign name to operation. action is action class above """ self.actions.append( action ) def getAction(self, name): """Get action associated with name.""" for a in self.actions: if a.name == name: return a return None def isAllowedParent(self, parent): """Is the parent a suitable type?""" ap = self.allowedparenttypes if parent is None:# and len(ap)>0 and ap[0] is None: return True for p in ap: if isinstance(parent, p): return True return False def willAllowParent(cls, parent): """Is the parent of an allowed type to have this type as a child?""" # allow base widget to have no parent ap = cls.allowedparenttypes if parent is None and len(ap) > 0 and ap[0] is None: return True for p in ap: if isinstance(parent, p): return True return False willAllowParent = classmethod(willAllowParent) def addChild(self, child, index=9999999): """Add child to list. index is a position to place the new child """ self.children.insert(index, child) def createUniqueName(self, prefix): """Create a name using the prefix which hasn't been used before.""" names = self.childnames i = 1 while "%s%i" % (prefix, i) in names: i += 1 return "%s%i" % (prefix, i) def chooseName(self): """Make a name for widget if not specified.""" if self.parent is None: return '/' else: return self.parent.createUniqueName(self.typename) def _getUserDescription(self): """Return a user-friendly description of what this is (e.g. function).""" return '' userdescription = property(_getUserDescription) def prefLookup(self, name): """Get the value of a preference in the form foo/bar/baz""" if len(name) > 0 and name[0] == '/': obj = self.document.basewidget name = name[1:] else: obj = self parts = name.split('/') noparts = len(parts) # this could be recursive, but why bother # loop while we iterate through the family i = 0 while i < noparts and obj.hasChild( parts[i] ): obj = obj.getChild( parts[i] ) i += 1 if i == noparts: raise ValueError, "Specified a widget, not a setting" else: return obj.settings.getFromPath( parts[i:] ) def getChild(self, name): """Return a child with a name.""" for i in self.children: if i.name == name: return i return None def hasChild(self, name): """Return whether there is a child with a name.""" return self.getChild(name) is not None def _getChildNames(self): """Return the child names.""" return [i.name for i in self.children] childnames = property(_getChildNames) def removeChild(self, name): """Remove a child.""" i = 0 nc = len(self.children) while i < nc and self.children[i].name != name: i += 1 if i < nc: self.children.pop(i) else: raise ValueError, \ "Cannot remove graph '%s' - does not exist" % name def widgetSiblingIndex(self): """Get index of widget in its siblings.""" if self.parent is None: return 0 else: return self.parent.children.index(self) def _getPath(self): """Returns a path for the object, e.g. /plot1/x.""" obj = self build = '' while obj.parent is not None: build = '/' + obj.name + build obj = obj.parent if len(build) == 0: build = '/' return build path = property(_getPath) def computeBounds(self, parentposn, painthelper, margins = (0., 0., 0., 0.)): """Compute a bounds array, giving the bounding box for the widget.""" # get parent's position x1, y1, x2, y2 = parentposn dx, dy = x2-x1, y2-y1 # get our position px1, py1, px2, py2 = self.position x1, y1, x2, y2 = ( x1+dx*px1, y1+dy*py1, x1+dx*px2, y1+dy*py2 ) dx1, dy1, dx2, dy2 = margins return [ x1+dx1, y1+dy1, x2-dx2, y2-dy2 ] def draw(self, parentposn, painthelper, outerbounds = None): """Draw the widget and its children in posn (a tuple with x1,y1,x2,y2). painter is the widget.Painter to draw on outerbounds contains "ultimate" bounds we don't go outside """ bounds = self.computeBounds(parentposn, painthelper) if not self.settings.hide: # iterate over children in reverse order for c in reversed(self.children): c.draw(bounds, painthelper, outerbounds=outerbounds) # return our final bounds return bounds def getSaveText(self, saveall = False): """Return text to restore object If saveall is true, save everything, including defaults.""" # set everything first text = self.settings.saveText(saveall) # now go throught the subwidgets for c in self.children: text += ( "Add('%s', name=%s, autoadd=False)\n" % (c.typename, repr(c.name)) ) # if we need to go to the child, go there ctext = c.getSaveText(saveall) if ctext != '': text += ("To(%s)\n" "%s" "To('..')\n") % (repr(c.name), ctext) return text def readDefaults(self): """Read the default settings. Also set settings to stylesheet """ self.settings.readDefaults('', self.name) self.settings.linkToStylesheet() def buildFlatWidgetList(self, thelist): """Return a built up list of the widgets in the tree.""" thelist.append(self) for child in self.children: child.buildFlatWidgetList(thelist) def _recursiveBuildSlots(self, slots): """Build up a flat representation of the places where widgets can be placed The list consists of (parent, index) tuples """ slots.append( (self, 0) ) for child, index in itertools.izip(self.children, itertools.count(1)): child._recursiveBuildSlots(slots) slots.append( (self, index) ) def moveChild(self, w, direction): """Move the child widget w up in the hierarchy in the direction. direction is -1 for 'up' or +1 for 'down' Returns True if succeeded """ # find position of child in self c = self.children oldindex = c.index(w) # remove the widget from its current location c.pop(oldindex) # build a list of places widgets can be placed (slots) slots = [] self.document.basewidget._recursiveBuildSlots(slots) # find self list - must be a better way to do this - # probably doesn't matter too much, however ourslot = (self, oldindex) ourindex = 0 while ourindex < len(slots) and slots[ourindex] != ourslot: ourindex += 1 # should never happen assert ourindex < len(slots) # move up or down the list until we find a suitable parent ourindex += direction while ( ourindex >= 0 and ourindex < len(slots) and not w.isAllowedParent(slots[ourindex][0]) ): ourindex += direction # we failed to find a new parent if ourindex < 0 or ourindex >= len(slots): c.insert(oldindex, w) return False else: newparent, newindex = slots[ourindex] existingname = w.name in newparent.childnames newparent.children.insert(newindex, w) w.parent = newparent # require a new name because of a clash if existingname: w.name = w.chooseName() return True def updateControlItem(self, controlitem, pos): """Update the widget's control point. controlitem is the control item in question.""" pass # allow the factory to instantiate a generic widget document.thefactory.register( Widget ) veusz-1.15/widgets/polar.py0000644002344000001440000002632611734662204015745 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Polar plot widget.""" import numpy as N from nonorthgraph import NonOrthGraph from axisticks import AxisTicks import axis import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils class Tick(setting.Line): '''Polar tick settings.''' def __init__(self, name, **args): setting.Line.__init__(self, name, **args) self.add( setting.DistancePt( 'length', '6pt', descr = 'Length of major ticks', usertext='Length') ) self.add( setting.Int( 'number', 6, descr = 'Number of major ticks to aim for', usertext='Number') ) self.add( setting.Bool('hidespokes', False, descr = 'Hide radial spokes', usertext = 'Hide spokes') ) self.add( setting.Bool('hideannuli', False, descr = 'Hide annuli', usertext = 'Hide annuli') ) self.get('color').newDefault('grey') def getLength(self, painter): '''Return tick length in painter coordinates''' return self.get('length').convert(painter) class TickLabel(axis.TickLabel): """For tick label.""" def __init__(self, *args, **argsv): axis.TickLabel.__init__(self, *args, **argsv) self.remove('offset') self.remove('rotate') self.remove('hide') self.add( setting.Bool('hideradial', False, descr = 'Hide radial labels', usertext='Hide radial') ) self.add( setting.Bool('hidetangential', False, descr = 'Hide tangential labels', usertext='Hide tangent') ) class Polar(NonOrthGraph): '''Polar plotter.''' typename='polar' allowusercreation = True description = 'Polar graph' def __init__(self, parent, name=None): '''Initialise polar plot.''' NonOrthGraph.__init__(self, parent, name=name) if type(self) == NonOrthGraph: self.readDefaults() @classmethod def addSettings(klass, s): '''Construct list of settings.''' NonOrthGraph.addSettings(s) s.add( setting.FloatOrAuto('maxradius', 'Auto', descr='Maximum value of radius', usertext='Max radius') ) s.add( setting.Choice('units', ('degrees', 'radians'), 'degrees', descr = 'Angular units', usertext='Units') ) s.add( setting.Choice('direction', ('clockwise', 'anticlockwise'), 'anticlockwise', descr = 'Angle direction', usertext = 'Direction') ) s.add( setting.Choice('position0', ('right', 'top', 'left', 'bottom'), 'right', descr = 'Direction of 0 angle', usertext = u'Position of 0°') ) s.add( TickLabel('TickLabels', descr = 'Tick labels', usertext='Tick labels'), pixmap='settings_axisticklabels' ) s.add( Tick('Tick', descr = 'Tick line', usertext='Tick'), pixmap='settings_axismajorticks' ) s.get('leftMargin').newDefault('1cm') s.get('rightMargin').newDefault('1cm') s.get('topMargin').newDefault('1cm') s.get('bottomMargin').newDefault('1cm') def coordRanges(self): '''Get ranges of coordinates.''' if self.settings.units == 'degrees': return [[0., self._maxradius], [0., 360.]] return [[0., self._maxradius], [0., 2.*N.pi]] def toPlotAngle(self, angles): """Convert one or more angles to angle on plot.""" s = self.settings # unit conversion if s.units == 'degrees': angles = angles * (N.pi/180.) # change direction if self.settings.direction == 'anticlockwise': angles = -angles # add offset angles -= {'right': 0, 'top': 0.5*N.pi, 'left': N.pi, 'bottom': 1.5*N.pi}[self.settings.position0] return angles def graphToPlotCoords(self, coorda, coordb): '''Convert coordinates in r, theta to x, y.''' s = self.settings ca = coorda / self._maxradius cb = self.toPlotAngle(coordb) x = self._xc + ca * N.cos(cb) * self._xscale y = self._yc + ca * N.sin(cb) * self._yscale return x, y def drawFillPts(self, painter, extfill, cliprect, ptsx, ptsy): '''Draw points for plotting a fill.''' pts = qt4.QPolygonF() utils.addNumpyToPolygonF(pts, ptsx, ptsy) filltype = extfill.filltype if filltype == 'center': pts.append( qt4.QPointF(self._xc, self._yc) ) utils.brushExtFillPolygon(painter, extfill, cliprect, pts) elif filltype == 'outside': pp = qt4.QPainterPath() pp.moveTo(self._xc, self._yc) pp.arcTo(cliprect, 0, 360) pp.addPolygon(pts) utils.brushExtFillPath(painter, extfill, pp) elif filltype == 'polygon': utils.brushExtFillPolygon(painter, extfill, cliprect, pts) def drawGraph(self, painter, bounds, datarange, outerbounds=None): '''Plot graph area and axes.''' s = self.settings if s.maxradius == 'Auto': self._maxradius = datarange[1] else: self._maxradius = s.maxradius self._xscale = (bounds[2]-bounds[0])*0.5 self._yscale = (bounds[3]-bounds[1])*0.5 self._xc = 0.5*(bounds[0]+bounds[2]) self._yc = 0.5*(bounds[3]+bounds[1]) path = qt4.QPainterPath() path.addEllipse( qt4.QRectF( qt4.QPointF(bounds[0], bounds[1]), qt4.QPointF(bounds[2], bounds[3]) ) ) utils.brushExtFillPath(painter, s.Background, path, stroke=s.Border.makeQPenWHide(painter)) def setClip(self, painter, bounds): '''Set clipping for graph.''' p = qt4.QPainterPath() p.addEllipse( qt4.QRectF( qt4.QPointF(bounds[0], bounds[1]), qt4.QPointF(bounds[2], bounds[3]) ) ) painter.setClipPath(p) def drawAxes(self, painter, bounds, datarange, outerbounds=None): '''Plot axes.''' s = self.settings t = s.Tick if self._maxradius <= 0.: self._maxradius = 1. atick = AxisTicks(0, self._maxradius, t.number, t.number*4, extendmin=False, extendmax=False) atick.getTicks() majtick = atick.tickvals # draw ticks as circles if not t.hideannuli: painter.setPen( s.Tick.makeQPenWHide(painter) ) painter.setBrush( qt4.QBrush() ) for tick in majtick[1:]: radius = tick / self._maxradius painter.drawEllipse(qt4.QRectF( qt4.QPointF( self._xc - radius*self._xscale, self._yc - radius*self._yscale ), qt4.QPointF( self._xc + radius*self._xscale, self._yc + radius*self._yscale ) )) # setup axes plot tl = s.TickLabels scale, format = tl.scale, tl.format if format == 'Auto': format = atick.autoformat painter.setPen( tl.makeQPen() ) font = tl.makeQFont(painter) # draw radial axis if not s.TickLabels.hideradial: for tick in majtick[1:]: num = utils.formatNumber(tick*scale, format, locale=self.document.locale) x = tick / self._maxradius * self._xscale + self._xc r = utils.Renderer(painter, font, x, self._yc, num, alignhorz=-1, alignvert=-1, usefullheight=True) r.render() if s.units == 'degrees': angles = [ u'0°', u'30°', u'60°', u'90°', u'120°', u'150°', u'180°', u'210°', u'240°', u'270°', u'300°', u'330°' ] else: angles = [ '0', u'Ï€/6', u'Ï€/3', u'Ï€/2', u'2Ï€/3', u'5Ï€/6', u'Ï€', u'7Ï€/6', u'4Ï€/3', u'3Ï€/2', u'5Ï€/3', u'11Ï€/6' ] align = [ (-1, 1), (-1, 1), (-1, 1), (0, 1), (1, 1), (1, 1), (1, 0), (1, -1), (1, -1), (0, -1), (-1, -1), (-1, -1) ] if s.direction == 'anticlockwise': angles = angles[0:1] + angles[1:][::-1] # rotate labels if zero not at right if s.position0 == 'top': angles = angles[3:] + angles[:4] elif s.position0 == 'left': angles = angles[6:] + angles[:7] elif s.position0 == 'bottom': angles = angles[9:] + angles[:10] # draw labels around plot if not s.TickLabels.hidetangential: for i in xrange(12): angle = 2 * N.pi / 12 x = self._xc + N.cos(angle*i) * self._xscale y = self._yc + N.sin(angle*i) * self._yscale r = utils.Renderer(painter, font, x, y, angles[i], alignhorz=align[i][0], alignvert=align[i][1], usefullheight=True) r.render() # draw spokes if not t.hidespokes: painter.setPen( s.Tick.makeQPenWHide(painter) ) painter.setBrush( qt4.QBrush() ) angle = 2 * N.pi / 12 lines = [] for i in xrange(12): x = self._xc + N.cos(angle*i) * self._xscale y = self._yc + N.sin(angle*i) * self._yscale lines.append( qt4.QLineF(qt4.QPointF(self._xc, self._yc), qt4.QPointF(x, y)) ) painter.drawLines(lines) document.thefactory.register(Polar) veusz-1.15/widgets/__init__.py0000644002344000001440000000332111734662204016355 0ustar jssusers00000000000000# Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Widgets are defined in this module.""" from widget import Widget, Action from axis import Axis from graph import Graph from grid import Grid from plotters import GenericPlotter, FreePlotter from pickable import PickInfo from point import PointPlotter from function import FunctionPlotter from textlabel import TextLabel from page import Page from root import Root from key import Key from fit import Fit from image import Image from contour import Contour from colorbar import ColorBar from shape import Shape, BoxShape, Rectangle, Ellipse, ImageFile from line import Line from bar import BarPlotter from polygon import Polygon from vectorfield import VectorField from boxplot import BoxPlot from polar import Polar from ternary import Ternary from nonorthpoint import NonOrthPoint from nonorthfunction import NonOrthFunction veusz-1.15/widgets/image.py0000644002344000001440000002722711734662204015713 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Image plotting from 2d datasets.""" import veusz.qtall as qt4 import numpy as N import veusz.setting as setting import veusz.document as document import veusz.utils as utils import plotters class Image(plotters.GenericPlotter): """A class which plots an image on a graph with a specified coordinate system.""" typename='image' allowusercreation=True description='Plot a 2d dataset as an image' def __init__(self, parent, name=None): """Initialise plotter with axes.""" plotters.GenericPlotter.__init__(self, parent, name=name) self.lastcolormap = None self.lastdataset = None self.schangeset = -1 # this is the range of data plotted, computed when plot is changed # the ColorBar object needs this later self.cacheddatarange = (0, 1) if type(self) == Image: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.GenericPlotter.addSettings(s) s.add( setting.Dataset('data', '', dimensions = 2, descr = 'Dataset to plot', usertext='Dataset'), 0 ) s.add( setting.FloatOrAuto('min', 'Auto', descr = 'Minimum value of image scale', usertext='Min. value'), 1 ) s.add( setting.FloatOrAuto('max', 'Auto', descr = 'Maximum value of image scale', usertext='Max. value'), 2 ) s.add( setting.Choice('colorScaling', ['linear', 'sqrt', 'log', 'squared'], 'linear', descr = 'Scaling to transform numbers to color', usertext='Scaling'), 3 ) s.add( setting.Dataset('transparencyData', '', dimensions = 2, descr = 'Dataset to use for transparency ' '(0 to 1)', usertext='Transparent data'), 4 ) s.add( setting.Colormap('colorMap', 'grey', descr = 'Set of colors to plot data with', usertext='Colormap', formatting=True), 5 ) s.add( setting.Bool('colorInvert', False, descr = 'Invert color map', usertext='Invert colormap', formatting=True), 6 ) s.add( setting.Int( 'transparency', 0, descr = 'Transparency percentage', usertext = 'Transparency', minval = 0, maxval = 100, formatting=True), 7 ) s.add( setting.Bool( 'smooth', False, descr = 'Smooth image to display resolution', usertext = 'Smooth', formatting = True ) ) def _getUserDescription(self): """User friendly description.""" s = self.settings out = [] if s.data: out.append(s.data) out += [s.colorScaling, s.colorMap] return ', '.join(out) userdescription = property(_getUserDescription) def updateImage(self): """Update the image with new contents.""" s = self.settings d = self.document data = d.data[s.data] transimg = None if s.transparencyData in d.data: transimg = d.data[s.transparencyData].data minval = s.min if minval == 'Auto': minval = N.nanmin(data.data) maxval = s.max if maxval == 'Auto': maxval = N.nanmax(data.data) # this is used currently by colorbar objects self.cacheddatarange = (minval, maxval) # get color map cmap = self.document.getColormap(s.colorMap, s.colorInvert) self.image = utils.applyColorMap( cmap, s.colorScaling, data.data, minval, maxval, s.transparency, transimg=transimg) def providesAxesDependency(self): """Range information provided by widget.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def updateAxisRange(self, axis, depname, axrange): """Automatically determine the ranges of variable on the axes.""" # this is copied from Image, probably should combine s = self.settings d = self.document # return if no data if s.data not in d.data: return # return if the dataset isn't two dimensional data = d.data[s.data] if data.dimensions != 2: return if depname == 'sx': dxrange = data.xrange axrange[0] = min( axrange[0], dxrange[0] ) axrange[1] = max( axrange[1], dxrange[1] ) elif depname == 'sy': dyrange = data.yrange axrange[0] = min( axrange[0], dyrange[0] ) axrange[1] = max( axrange[1], dyrange[1] ) def cutImageToFit(self, pltx, plty, posn): x1, y1, x2, y2 = posn pltx1, pltx2 = pltx pltw = pltx2-pltx1 plty2, plty1 = plty plth = plty2-plty1 imw = self.image.width() imh = self.image.height() pixw = pltw / float(imw) pixh = plth / float(imh) cutr = [0, 0, imw-1, imh-1] # work out where image intercepts posn, and make sure image # fills at least that area # need to chop left if pltx1 < x1: d = int((x1-pltx1) / pixw) cutr[0] += d pltx[0] += d*pixw # need to chop right if pltx2 > x2: d = max(0, int((pltx2-x2) / pixw) - 1) cutr[2] -= d pltx[1] -= d*pixw # chop top if plty1 < y1: d = int((y1-plty1) / pixh) cutr[1] += d plty[1] += d*pixh # chop bottom if plty2 > y2: d = max(0, int((plty2-y2) / pixh) - 1) cutr[3] -= d plty[0] -= d*pixh # create chopped-down image newimage = self.image.copy(cutr[0], cutr[1], cutr[2]-cutr[0]+1, cutr[3]-cutr[1]+1) # return new image coordinates and image return pltx, plty, newimage def makeColorbarImage(self, direction='horz'): """Make a QImage colorbar for the current plot. direction is 'horizontal' or 'vertical' to draw horizontal or vertical bars Returns a tuple (minval, maxval, scale, qimage) minval is the minimum value which should be plotted on the axis maxval is the maximum " " scale is 'linear' or 'log', depending on how numbers should be scaled qimage is a QImage of 1 x barsize """ self.recomputeInternals() minval, maxval = self.cacheddatarange s = self.settings # get colormap cmap = self.document.getColormap(s.colorMap, s.colorInvert) return utils.makeColorbarImage( minval, maxval, s.colorScaling, cmap, s.transparency, direction=direction) def recomputeInternals(self): """Recompute the internals if required. This is used by colorbar as it needs to know data range when plotting """ s = self.settings d = self.document # return if the dataset isn't two dimensional try: data = d.data[s.data] except KeyError: return None # recompute data if data.dimensions == 2: if data != self.lastdataset or self.schangeset != d.changeset: self.updateImage() self.lastdataset = data self.schangeset = d.changeset return data else: return None def draw(self, parentposn, phelper, outerbounds = None): """Draw the image.""" posn = plotters.GenericPlotter.draw(self, parentposn, phelper, outerbounds = outerbounds) x1, y1, x2, y2 = posn s = self.settings d = self.document # get axes widgets axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # return if there's no proper axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return # get data and update internal computations data = self.recomputeInternals() if not data or s.hide: return # find coordinates of image coordinate bounds rangex, rangey = data.getDataRanges() # translate coordinates to plotter coordinates coordsx = axes[0].dataToPlotterCoords(posn, N.array(rangex)) coordsy = axes[1].dataToPlotterCoords(posn, N.array(rangey)) # truncate image down if necessary # This assumes linear pixels! if ( coordsx[0] < x1 or coordsx[1] > x2 or coordsy[0] < y1 or coordsy[1] > y2 ): coordsx, coordsy, image = self.cutImageToFit(coordsx, coordsy, posn) else: image = self.image # clip data within bounds of plotter clip = self.clipAxesBounds(axes, posn) painter = phelper.painter(self, posn, clip=clip) # optionally smooth images before displaying if s.smooth: image = image.scaled( coordsx[1]-coordsx[0], coordsy[0]-coordsy[1], qt4.Qt.IgnoreAspectRatio, qt4.Qt.SmoothTransformation ) # get position and size of output image xp, yp = coordsx[0], coordsy[1] xw = coordsx[1]-coordsx[0] yw = coordsy[0]-coordsy[1] # invert output drawing if axes go from positive->negative # we only translate the coordinate system if this is the case xscale = yscale = 1 if xw < 0: xscale = -1 if yw < 0: yscale = -1 if xscale != 1 or yscale != 1: painter.save() painter.translate(xp, yp) xp = yp = 0 painter.scale(xscale, yscale) # draw image painter.drawImage(qt4.QRectF(xp, yp, abs(xw), abs(yw)), image) # restore painter if image was inverted if xscale != 1 or yscale != 1: painter.restore() # allow the factory to instantiate an image document.thefactory.register( Image ) veusz-1.15/widgets/nonorthfunction.py0000644002344000001440000001540711734662204020063 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## '''Non orthogonal function plotting.''' import numpy as N import veusz.qtall as qt4 import veusz.document as document import veusz.setting as setting import veusz.utils as utils import pickable from nonorthgraph import NonOrthGraph, FillBrush from widget import Widget from function import FunctionChecker class NonOrthFunction(Widget): '''Widget for plotting a function on a non-orthogonal plot.''' typename = 'nonorthfunc' allowusercreation = True description = 'Plot a function on graphs with non-orthogonal axes' allowedparenttypes = [NonOrthGraph] def __init__(self, parent, name=None): '''Initialise plotter.''' Widget.__init__(self, parent, name=name) if type(self) == NonOrthFunction: self.readDefaults() self.checker = FunctionChecker() @classmethod def addSettings(klass, s): '''Settings for widget.''' Widget.addSettings(s) s.add( setting.Str('function', 'a', descr='Function expression', usertext='Function') ) s.add( setting.Choice('variable', ['a', 'b'], 'a', descr='Variable the function is a function of', usertext='Variable') ) s.add(setting.FloatOrAuto('min', 'Auto', descr='Minimum value at which to plot function', usertext='Min')) s.add(setting.FloatOrAuto('max', 'Auto', descr='Maximum value at which to plot function', usertext='Max')) s.add( setting.Line('PlotLine', descr = 'Plot line settings', usertext = 'Plot line'), pixmap = 'settings_plotline' ) s.add( FillBrush('Fill1', descr = 'Fill settings (1)', usertext = 'Area fill 1'), pixmap = 'settings_plotfillbelow' ) s.add( FillBrush('Fill2', descr = 'Fill settings (2)', usertext = 'Area fill 2'), pixmap = 'settings_plotfillbelow' ) s.add( setting.Int('steps', 50, descr = 'Number of steps to evaluate the function' ' over', usertext='Steps', formatting=True), 0 ) def initEnviron(self): '''Set up function environment.''' return self.document.eval_context.copy() def logEvalError(self, ex): '''Write error message to document log for exception ex.''' self.document.log( "Error evaluating expression in function widget '%s': '%s'" % ( self.name, unicode(ex))) def getFunctionPoints(self): '''Get points for plotting function. Return (apts, bpts) ''' # get range of variable in expression s = self.settings crange = self.parent.coordRanges()[ {'a': 0, 'b': 1}[s.variable] ] if s.min != 'Auto': crange[0] = s.min if s.max != 'Auto': crange[1] = s.max steps = max(2, s.steps) # input values for function invals = ( N.arange(steps)*(1./(steps-1))*(crange[1]-crange[0]) + crange[0] ) # do evaluation env = self.initEnviron() env[s.variable] = invals try: vals = eval(self.checker.compiled, env) + invals*0. except Exception, e: self.logEvalError(e) vals = invals = N.array([]) # return points if s.variable == 'a': return invals, vals else: return vals, invals def updateDataRanges(self, inrange): '''Update ranges of data given function.''' def _pickable(self): apts, bpts = self.getFunctionPoints() px, py = self.parent.graphToPlotCoords(apts, bpts) if self.settings.variable == 'a': labels = ('a', 'b(a)') else: labels = ('a(b)', 'b') return pickable.GenericPickable( self, labels, (apts, bpts), (px, py) ) def pickPoint(self, x0, y0, bounds, distance='radial'): return self._pickable().pickPoint(x0, y0, bounds, distance) def pickIndex(self, oldindex, direction, bounds): return self._pickable().pickIndex(oldindex, direction, bounds) def draw(self, parentposn, phelper, outerbounds=None): '''Plot the function on a plotter.''' posn = Widget.draw(self, parentposn, phelper, outerbounds=outerbounds) s = self.settings d = self.document # exit if hidden if s.hide: return # ignore if function isn't sensible try: self.checker.check(s.function, s.variable) except RuntimeError, e: self.logEvalError(e) return x1, y1, x2, y2 = posn cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) ) painter = phelper.painter(self, posn) self.parent.setClip(painter, posn) apts, bpts = self.getFunctionPoints() px, py = self.parent.graphToPlotCoords(apts, bpts) # plot line painter.setBrush(qt4.QBrush()) painter.setPen( s.PlotLine.makeQPenWHide(painter) ) for x, y in utils.validLinePoints(px, py): if not s.Fill1.hide: self.parent.drawFillPts(painter, s.Fill1, cliprect, x, y) if not s.Fill2.hide: self.parent.drawFillPts(painter, s.Fill2, cliprect, x, y) if not s.PlotLine.hide: p = qt4.QPolygonF() utils.addNumpyToPolygonF(p, x, y) painter.setBrush(qt4.QBrush()) painter.setPen( s.PlotLine.makeQPen(painter) ) utils.plotClippedPolyline(painter, cliprect, p) document.thefactory.register( NonOrthFunction ) veusz-1.15/widgets/point.py0000644002344000001440000010105711734662204015754 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For plotting xy points.""" import veusz.qtall as qt4 import itertools import numpy as N import veusz.document as document import veusz.setting as setting import veusz.utils as utils import pickable from plotters import GenericPlotter try: import veusz.helpers.qtloops as qtloops hasqtloops = True except ImportError: hasqtloops = False # functions for plotting error bars # different styles are made up of combinations of these functions # each function takes the same arguments def _errorBarsBar(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw bar style error lines.""" # vertical error bars if ymin is not None and ymax is not None and not s.ErrorBarLine.hideVert: utils.plotLinesToPainter(painter, xplotter, ymin, xplotter, ymax, clip) # horizontal error bars if xmin is not None and xmax is not None and not s.ErrorBarLine.hideHorz: utils.plotLinesToPainter(painter, xmin, yplotter, xmax, yplotter, clip) def _errorBarsEnds(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw perpendiclar ends on error bars.""" size = ( s.get('markerSize').convert(painter) * s.ErrorBarLine.endsize ) if ymin is not None and ymax is not None and not s.ErrorBarLine.hideVert: utils.plotLinesToPainter(painter, xplotter-size, ymin, xplotter+size, ymin, clip) utils.plotLinesToPainter(painter, xplotter-size, ymax, xplotter+size, ymax, clip) if xmin is not None and xmax is not None and not s.ErrorBarLine.hideHorz: utils.plotLinesToPainter(painter, xmin, yplotter-size, xmin, yplotter+size, clip) utils.plotLinesToPainter(painter, xmax, yplotter-size, xmax, yplotter+size, clip) def _errorBarsBox(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw box around error region.""" if None not in (xmin, xmax, ymin, ymax): painter.setBrush( qt4.QBrush() ) utils.plotBoxesToPainter(painter, xmin, ymin, xmax, ymax, clip) def _errorBarsBoxFilled(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw box filled region inside error bars.""" if None not in (xmin, xmax, ymin, ymax): # filled region below if not s.FillBelow.hideerror: path = qt4.QPainterPath() utils.addNumpyPolygonToPath(path, clip, xmin, ymin, xmin, yplotter, xmax, yplotter, xmax, ymin) utils.brushExtFillPath(painter, s.FillBelow, path, ignorehide=True) # filled region above if not s.FillAbove.hideerror: path = qt4.QPainterPath() utils.addNumpyPolygonToPath(path, clip, xmin, yplotter, xmax, yplotter, xmax, ymax, xmin, ymax) utils.brushExtFillPath(painter, s.FillAbove, path, ignorehide=True) def _errorBarsDiamond(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw diamond around error region.""" if None not in (xmin, xmax, ymin, ymax): # expand clip by pen width (urgh) pw = painter.pen().widthF()*2 clip = qt4.QRectF(qt4.QPointF(clip.left()-pw,clip.top()-pw), qt4.QPointF(clip.right()+pw,clip.bottom()+pw)) path = qt4.QPainterPath() utils.addNumpyPolygonToPath(path, clip, xmin, yplotter, xplotter, ymax, xmax, yplotter, xplotter, ymin) painter.setBrush( qt4.QBrush() ) painter.drawPath(path) def _errorBarsDiamondFilled(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw diamond filled region inside error bars.""" if None not in (xmin, xmax, ymin, ymax): if not s.FillBelow.hideerror: path = qt4.QPainterPath() utils.addNumpyPolygonToPath(path, clip, xmin, yplotter, xplotter, ymin, xmax, yplotter) utils.brushExtFillPath(painter, s.FillBelow, path, ignorehide=True) if not s.FillAbove.hideerror: path = qt4.QPainterPath() utils.addNumpyPolygonToPath(path, clip, xmin, yplotter, xplotter, ymax, xmax, yplotter) utils.brushExtFillPath(painter, s.FillAbove, path, ignorehide=True) def _errorBarsCurve(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw curve around error region.""" if None not in (xmin, xmax, ymin, ymax): # non-filling brush painter.setBrush( qt4.QBrush() ) for xp, yp, xmn, ymn, xmx, ymx in itertools.izip( xplotter, yplotter, xmin, ymin, xmax, ymax): # break up curve into four arcs (for asym error bars) # qt geometry means we have to calculate lots # the big numbers are in 1/16 degrees painter.drawArc(qt4.QRectF(xp - (xmx-xp), yp - (yp-ymx), (xmx-xp)*2, (yp-ymx)*2), 0, 1440) painter.drawArc(qt4.QRectF(xp - (xp-xmn), yp - (yp-ymx), (xp-xmn)*2, (yp-ymx)*2), 1440, 1440) painter.drawArc(qt4.QRectF(xp - (xp-xmn), yp - (ymn-yp), (xp-xmn)*2, (ymn-yp)*2), 2880, 1440) painter.drawArc(qt4.QRectF(xp - (xmx-xp), yp - (ymn-yp), (xmx-xp)*2, (ymn-yp)*2), 4320, 1440) def _errorBarsFilled(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, clip): """Draw filled region as error region.""" ptsabove = qt4.QPolygonF() ptsbelow = qt4.QPolygonF() hidevert = True # keep track of what's shown hidehorz = True if ( 'vert' in style and (ymin is not None and ymax is not None) and not s.ErrorBarLine.hideVert ): hidevert = False # lines above/below points utils.addNumpyToPolygonF(ptsbelow, xplotter, ymin) utils.addNumpyToPolygonF(ptsabove, xplotter, ymax) elif ( 'horz' in style and (xmin is not None and xmax is not None) and not s.ErrorBarLine.hideHorz ): hidehorz = False # lines left/right points utils.addNumpyToPolygonF(ptsbelow, xmin, yplotter) utils.addNumpyToPolygonF(ptsabove, xmax, yplotter) # draw filled regions above/left and below/right if 'fill' in style and not (hidehorz and hidevert): # construct points for error bar regions retnpts = qt4.QPolygonF() utils.addNumpyToPolygonF(retnpts, xplotter[::-1], yplotter[::-1]) # polygons consist of lines joining the points and continuing # back along the plot line (retnpts) if not s.FillBelow.hideerror: utils.brushExtFillPolygon(painter, s.FillBelow, clip, ptsbelow+retnpts, ignorehide=True) if not s.FillAbove.hideerror: utils.brushExtFillPolygon(painter, s.FillAbove, clip, ptsabove+retnpts, ignorehide=True) # draw optional line (on top of fill) utils.plotClippedPolyline(painter, clip, ptsabove) utils.plotClippedPolyline(painter, clip, ptsbelow) # map error bar names to lists of functions (above) _errorBarFunctionMap = { 'none': (), 'bar': (_errorBarsBar,), 'bardiamond': (_errorBarsBar, _errorBarsDiamond,), 'barcurve': (_errorBarsBar, _errorBarsCurve,), 'barbox': (_errorBarsBar, _errorBarsBox,), 'barends': (_errorBarsBar, _errorBarsEnds,), 'box': (_errorBarsBox,), 'boxfill': (_errorBarsBoxFilled, _errorBarsBox,), 'diamond': (_errorBarsDiamond,), 'diamondfill': (_errorBarsDiamond, _errorBarsDiamondFilled), 'curve': (_errorBarsCurve,), 'fillhorz': (_errorBarsFilled,), 'fillvert': (_errorBarsFilled,), 'linehorz': (_errorBarsFilled,), 'linevert': (_errorBarsFilled,), 'linehorzbar': (_errorBarsBar, _errorBarsFilled), 'linevertbar': (_errorBarsBar, _errorBarsFilled), } class MarkerFillBrush(setting.Brush): def __init__(self, name, **args): setting.Brush.__init__(self, name, **args) self.get('color').newDefault( setting.Reference( '../PlotLine/color') ) self.add( setting.Colormap( 'colorMap', 'grey', descr = 'If color markers dataset is given, use this colormap ' 'instead of the fill color', usertext='Color map', formatting=True) ) self.add( setting.Bool( 'colorMapInvert', False, descr = 'Invert color map', usertext = 'Invert map', formatting=True) ) class ColorSettings(setting.Settings): """Settings for a coloring points using data values.""" def __init__(self, name): setting.Settings.__init__(self, name, setnsmode='groupedsetting') self.add( setting.DatasetOrFloatList( 'points', '', descr = 'Use color value (0-1) in dataset to paint points', usertext='Color markers'), 7 ) self.add( setting.Float( 'min', 0., descr = 'Minimum value of color dataset', usertext = 'Min val' )) self.add( setting.Float( 'max', 1., descr = 'Maximum value of color dataset', usertext = 'Max val' )) self.add( setting.Choice( 'scaling', ['linear', 'sqrt', 'log', 'squared'], 'linear', descr = 'Scaling to transform numbers to color', usertext='Scaling')) class PointPlotter(GenericPlotter): """A class for plotting points and their errors.""" typename='xy' allowusercreation=True description='Plot points with lines and errorbars' def __init__(self, parent, name=None): """Initialise XY plotter plotting (xdata, ydata). xdata and ydata are strings specifying the data in the document""" GenericPlotter.__init__(self, parent, name=name) if type(self) == PointPlotter: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" GenericPlotter.addSettings(s) s.add( setting.Int('thinfactor', 1, minval=1, descr='Thin number of markers plotted' ' for each datapoint by this factor', usertext='Thin markers', formatting=True), 0 ) s.add( setting.DistancePt('markerSize', '3pt', descr = 'Size of marker to plot', usertext='Marker size', formatting=True), 0 ) s.add( setting.Marker('marker', 'circle', descr = 'Type of marker to plot', usertext='Marker', formatting=True), 0 ) s.add( setting.DatasetOrStr('labels', '', descr='Dataset or string to label points', usertext='Labels', datatype='text'), 5 ) s.add( setting.DatasetOrFloatList( 'scalePoints', '', descr = 'Scale size of plotted markers by this dataset or' ' list of values', usertext='Scale markers'), 6 ) s.add( ColorSettings('Color') ) s.add( setting.DatasetOrFloatList( 'yData', 'y', descr='Dataset containing y data or list of values', usertext='Y data'), 0 ) s.add( setting.DatasetOrFloatList( 'xData', 'x', descr='Dataset containing x data or list of values', usertext='X data'), 0 ) s.add( setting.ErrorStyle('errorStyle', 'bar', descr='Style of error bars to plot', usertext='Error style', formatting=True) ) s.add( setting.XYPlotLine('PlotLine', descr = 'Plot line settings', usertext = 'Plot line'), pixmap = 'settings_plotline' ) s.add( setting.Line('MarkerLine', descr = 'Line around the marker settings', usertext = 'Marker border'), pixmap = 'settings_plotmarkerline' ) s.add( MarkerFillBrush('MarkerFill', descr = 'Marker fill settings', usertext = 'Marker fill'), pixmap = 'settings_plotmarkerfill' ) s.add( setting.ErrorBarLine('ErrorBarLine', descr = 'Error bar line settings', usertext = 'Error bar line'), pixmap = 'settings_ploterrorline' ) s.add( setting.PointFill('FillBelow', descr = 'Fill below plot line', usertext = 'Fill below'), pixmap = 'settings_plotfillbelow' ) s.add( setting.PointFill('FillAbove', descr = 'Fill above plot line', usertext = 'Fill above'), pixmap = 'settings_plotfillabove' ) s.add( setting.PointLabel('Label', descr = 'Label settings', usertext='Label'), pixmap = 'settings_axislabel' ) def _getUserDescription(self): """User-friendly description.""" s = self.settings return "x='%s', y='%s', marker='%s'" % (s.xData, s.yData, s.marker) userdescription = property(_getUserDescription) def _plotErrors(self, posn, painter, xplotter, yplotter, axes, xdata, ydata, cliprect): """Plot error bars (horizontal and vertical). """ s = self.settings style = s.errorStyle if style == 'none': return # default is no error bars xmin = xmax = ymin = ymax = None # draw horizontal error bars if xdata.hasErrors(): xmin, xmax = xdata.getPointRanges() # convert xmin and xmax to graph coordinates xmin = axes[0].dataToPlotterCoords(posn, xmin) xmax = axes[0].dataToPlotterCoords(posn, xmax) # draw vertical error bars if ydata.hasErrors(): ymin, ymax = ydata.getPointRanges() # convert ymin and ymax to graph coordinates ymin = axes[1].dataToPlotterCoords(posn, ymin) ymax = axes[1].dataToPlotterCoords(posn, ymax) # no error bars - break out of processing below if ymin is None and ymax is None and xmin is None and xmax is None: return # iterate to call the error bars functions required to draw style pen = s.ErrorBarLine.makeQPenWHide(painter) pen.setCapStyle(qt4.Qt.FlatCap) painter.setPen(pen) for function in _errorBarFunctionMap[style]: function(style, xmin, xmax, ymin, ymax, xplotter, yplotter, s, painter, cliprect) def providesAxesDependency(self): """This widget provides range information about these axes.""" s = self.settings return ( (s.xAxis, 'sx'), (s.yAxis, 'sy') ) def updateAxisRange(self, axis, depname, axrange): """Compute the effect of data on the axis range.""" dataname = {'sx': 'xData', 'sy': 'yData'}[depname] dsetn = self.settings.get(dataname) data = dsetn.getData(self.document) if data: drange = data.getRange() if drange: axrange[0] = min(axrange[0], drange[0]) axrange[1] = max(axrange[1], drange[1]) elif dsetn.isEmpty(): # no valid dataset. # check if there a valid dataset for the other axis. # if there is, treat this as a row number dataname = {'sy': 'xData', 'sx': 'yData'}[depname] data = self.settings.get(dataname).getData(self.document) if data: length = data.data.shape[0] axrange[0] = min(axrange[0], 1) axrange[1] = max(axrange[1], length) def _getLinePoints( self, xvals, yvals, posn, xdata, ydata ): """Get the points corresponding to the line connecting the points.""" pts = qt4.QPolygonF() s = self.settings steps = s.PlotLine.steps # simple continuous line if steps == 'off': utils.addNumpyToPolygonF(pts, xvals, yvals) # stepped line, with points on left elif steps == 'left': x1 = xvals[:-1] x2 = xvals[1:] y1 = yvals[:-1] y2 = yvals[1:] utils.addNumpyToPolygonF(pts, x1, y1, x2, y1, x2, y2) # stepped line, with points on right elif steps == 'right': x1 = xvals[:-1] x2 = xvals[1:] y1 = yvals[:-1] y2 = yvals[1:] utils.addNumpyToPolygonF(pts, x1, y1, x1, y2, x2, y2) # stepped line, with points in centre # this is complex as we can't use the mean of the plotter coords, # as the axis could be log elif steps == 'centre': axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) if xdata.hasErrors(): # Special case if error bars on x points: # here we use the error bars to define the steps xmin, xmax = xdata.getPointRanges() # this is duplicated from drawing error bars: bad # convert xmin and xmax to graph coordinates xmin = axes[0].dataToPlotterCoords(posn, xmin) xmax = axes[0].dataToPlotterCoords(posn, xmax) utils.addNumpyToPolygonF(pts, xmin, yvals, xmax, yvals) else: # we put the bin edges half way between the points # we assume this is the correct thing to do even in log space x1 = xvals[:-1] x2 = xvals[1:] y1 = yvals[:-1] y2 = yvals[1:] xc = 0.5*(x1+x2) utils.addNumpyToPolygonF(pts, x1, y1, xc, y1, xc, y2) if len(xvals) > 0: pts.append( qt4.QPointF(xvals[-1], yvals[-1]) ) else: assert False return pts def _getBezierLine(self, poly): """Try to draw a bezier line connecting the points.""" npts = qtloops.bezier_fit_cubic_multi(poly, 0.1, len(poly)+1) i = 0 path = qt4.QPainterPath() lastpt = qt4.QPointF(-999999,-999999) while i < len(npts): if lastpt != npts[i]: path.moveTo(npts[i]) path.cubicTo(npts[i+1], npts[i+2], npts[i+3]) lastpt = npts[i+3] i += 4 return path painter.strokePath(p, painter.pen()) def _drawBezierLine( self, painter, xvals, yvals, posn, xdata, ydata): """Handle bezier lines and fills.""" pts = self._getLinePoints(xvals, yvals, posn, xdata, ydata) if len(pts) < 2: return path = self._getBezierLine(pts) s = self.settings if not s.FillBelow.hide: temppath = qt4.QPainterPath(path) temppath.lineTo(pts[-1].x(), posn[3]) temppath.lineTo(pts[0].x(), posn[3]) utils.brushExtFillPath(painter, s.FillBelow, temppath) if not s.FillAbove.hide: temppath = qt4.QPainterPath(path) temppath.lineTo(pts[-1].x(), posn[1]) temppath.lineTo(pts[0].x(), posn[1]) utils.brushExtFillPath(painter, s.FillAbove, temppath) if not s.PlotLine.hide: painter.strokePath(path, s.PlotLine.makeQPen(painter)) def _drawPlotLine( self, painter, xvals, yvals, posn, xdata, ydata, cliprect ): """Draw the line connecting the points.""" pts = self._getLinePoints(xvals, yvals, posn, xdata, ydata) if len(pts) < 2: return s = self.settings if not s.FillBelow.hide: # construct polygon to draw filled region polypts = qt4.QPolygonF([qt4.QPointF(pts[0].x(), posn[3])]) polypts += pts polypts.append(qt4.QPointF(pts[len(pts)-1].x(), posn[3])) utils.brushExtFillPolygon(painter, s.FillBelow, cliprect, polypts) if not s.FillAbove.hide: polypts = qt4.QPolygonF([qt4.QPointF(pts[0].x(), posn[1])]) polypts += pts polypts.append(qt4.QPointF(pts[len(pts)-1].x(), posn[1])) utils.brushExtFillPolygon(painter, s.FillAbove, cliprect, polypts) # draw line between points if not s.PlotLine.hide: painter.setPen( s.PlotLine.makeQPen(painter) ) utils.plotClippedPolyline(painter, cliprect, pts) def drawKeySymbol(self, number, painter, x, y, width, height): """Draw the plot symbol and/or line.""" painter.save() cliprect = qt4.QRectF(qt4.QPointF(x,y), qt4.QPointF(x+width,y+height)) painter.setClipRect(cliprect) # draw sample error bar s = self.settings size = s.get('markerSize').convert(painter) style = s.errorStyle # make some fake error bar data to plot xv = s.get('xData').getData(self.document) yv = s.get('yData').getData(self.document) yp = y + height/2 xpts = N.array([x-width, x+width/2, x+2*width]) ypts = N.array([yp, yp, yp]) # size of error bars in key errorsize = height*0.4 # make points for error bars (if any) if xv and xv.hasErrors(): xneg = N.array([x-width, x+width/2-errorsize, x+2*width]) xpos = N.array([x-width, x+width/2+errorsize, x+2*width]) else: xneg = xpos = xpts if yv and yv.hasErrors(): yneg = N.array([yp-errorsize, yp-errorsize, yp-errorsize]) ypos = N.array([yp+errorsize, yp+errorsize, yp+errorsize]) else: yneg = ypos = ypts # plot error bar painter.setPen( s.ErrorBarLine.makeQPenWHide(painter) ) for function in _errorBarFunctionMap[style]: function(style, xneg, xpos, yneg, ypos, xpts, ypts, s, painter, cliprect) # draw line if not s.PlotLine.hide: painter.setPen( s.PlotLine.makeQPen(painter) ) painter.drawLine( qt4.QPointF(x, yp), qt4.QPointF(x+width, yp) ) # draw marker if not s.MarkerLine.hide or not s.MarkerFill.hide: if not s.MarkerFill.hide: painter.setBrush( s.MarkerFill.makeQBrush() ) if not s.MarkerLine.hide: painter.setPen( s.MarkerLine.makeQPen(painter) ) else: painter.setPen( qt4.QPen( qt4.Qt.NoPen ) ) utils.plotMarker(painter, x+width/2, yp, s.marker, size) painter.restore() def drawLabels(self, painter, xplotter, yplotter, textvals, markersize): """Draw labels for the points.""" s = self.settings lab = s.get('Label') # work out offset an alignment deltax = markersize*1.5*{'left':-1, 'centre':0, 'right':1}[lab.posnHorz] deltay = markersize*1.5*{'top':-1, 'centre':0, 'bottom':1}[lab.posnVert] alignhorz = {'left':1, 'centre':0, 'right':-1}[lab.posnHorz] alignvert = {'top':-1, 'centre':0, 'bottom':1}[lab.posnVert] # make font and len textpen = lab.makeQPen() painter.setPen(textpen) font = lab.makeQFont(painter) angle = lab.angle # iterate over each point and plot each label for x, y, t in itertools.izip(xplotter+deltax, yplotter+deltay, textvals): utils.Renderer( painter, font, x, y, t, alignhorz, alignvert, angle ).render() def getAxisLabels(self, direction): """Get labels for axis if using a label axis.""" s = self.settings doc = self.document text = s.get('labels').getData(doc, checknull=True) xv = s.get('xData').getData(doc) yv = s.get('yData').getData(doc) # handle missing dataset if yv and not xv and s.get('xData').isEmpty(): length = yv.data.shape[0] xv = document.DatasetRange(length, (1,length)) elif xv and not yv and s.get('yData').isEmpty(): length = xv.data.shape[0] yv = document.DatasetRange(length, (1,length)) if None in (text, xv, yv): return (None, None) if direction == 'horizontal': return (text, xv.data) else: return (text, yv.data) def _fetchAxes(self): """Returns the axes for this widget""" s = self.settings axes = self.parent.getAxes( (s.xAxis, s.yAxis) ) # fail if we don't have good axes if ( None in axes or axes[0].settings.direction != 'horizontal' or axes[1].settings.direction != 'vertical' ): return None return axes def _pickable(self, bounds): axes = self._fetchAxes() if axes is None: map_fn = None else: map_fn = lambda x, y: ( axes[0].dataToPlotterCoords(bounds, x), axes[1].dataToPlotterCoords(bounds, y) ) return pickable.DiscretePickable(self, 'xData', 'yData', map_fn) def pickPoint(self, x0, y0, bounds, distance = 'radial'): return self._pickable(bounds).pickPoint(x0, y0, bounds, distance) def pickIndex(self, oldindex, direction, bounds): return self._pickable(bounds).pickIndex(oldindex, direction, bounds) def makeColorbarImage(self, direction='horz'): """Make a QImage colorbar for the current plot.""" s = self.settings c = s.Color cmap = self.document.getColormap( s.MarkerFill.colorMap, s.MarkerFill.colorMapInvert) return utils.makeColorbarImage( c.min, c.max, c.scaling, cmap, 0, direction=direction) def draw(self, parentposn, phelper, outerbounds=None): """Plot the data on a plotter.""" posn = GenericPlotter.draw(self, parentposn, phelper, outerbounds=outerbounds) x1, y1, x2, y2 = posn s = self.settings # exit if hidden if s.hide: return # get data doc = self.document xv = s.get('xData').getData(doc) yv = s.get('yData').getData(doc) text = s.get('labels').getData(doc, checknull=True) scalepoints = s.get('scalePoints').getData(doc) colorpoints = s.Color.get('points').getData(doc) # if a missing dataset, make a fake dataset for the second one # based on a row number if xv and not yv and s.get('yData').isEmpty(): # use index for y data length = xv.data.shape[0] yv = document.DatasetRange(length, (1,length)) elif yv and not xv and s.get('xData').isEmpty(): # use index for x data length = yv.data.shape[0] xv = document.DatasetRange(length, (1,length)) if not xv or not yv: # no valid dataset, so exit return # if text entered, then multiply up to get same number of values # as datapoints if text: length = min( len(xv.data), len(yv.data) ) text = text*(length / len(text)) + text[:length % len(text)] # get axes widgets axes = self._fetchAxes() if not axes: # no valid axes, so exit return # clip data within bounds of plotter cliprect = self.clipAxesBounds(axes, posn) painter = phelper.painter(self, posn, clip=cliprect) # loop over chopped up values for xvals, yvals, tvals, ptvals, cvals in ( document.generateValidDatasetParts( xv, yv, text, scalepoints, colorpoints)): #print "Calculating coordinates" # calc plotter coords of x and y points xplotter = axes[0].dataToPlotterCoords(posn, xvals.data) yplotter = axes[1].dataToPlotterCoords(posn, yvals.data) #print "Painting error bars" # plot errors bars self._plotErrors(posn, painter, xplotter, yplotter, axes, xvals, yvals, cliprect) #print "Painting plot line" # plot data line (and/or filling above or below) if not s.PlotLine.hide or not s.FillAbove.hide or not s.FillBelow.hide: if s.PlotLine.bezierJoin and hasqtloops: self._drawBezierLine( painter, xplotter, yplotter, posn, xvals, yvals ) else: self._drawPlotLine( painter, xplotter, yplotter, posn, xvals, yvals, cliprect ) # plot the points (we do this last so they are on top) markersize = s.get('markerSize').convert(painter) if not s.MarkerLine.hide or not s.MarkerFill.hide: #print "Painting marker fill" if not s.MarkerFill.hide: # filling for markers painter.setBrush( s.MarkerFill.makeQBrush() ) else: # no-filling brush painter.setBrush( qt4.QBrush() ) #print "Painting marker lines" if not s.MarkerLine.hide: # edges of markers painter.setPen( s.MarkerLine.makeQPen(painter) ) else: # invisible pen painter.setPen( qt4.QPen(qt4.Qt.NoPen) ) # thin datapoints as required if s.thinfactor <= 1: xplt, yplt = xplotter, yplotter else: xplt, yplt = (xplotter[::s.thinfactor], yplotter[::s.thinfactor]) # whether to scale markers scaling = colorvals = cmap = None if ptvals: scaling = ptvals.data # color point individually if cvals: colorvals = utils.applyScaling( cvals.data, s.Color.scaling, s.Color.min, s.Color.max) cmap = self.document.getColormap( s.MarkerFill.colorMap, s.MarkerFill.colorMapInvert) # actually plot datapoints utils.plotMarkers(painter, xplt, yplt, s.marker, markersize, scaling=scaling, clip=cliprect, cmap=cmap, colorvals=colorvals) # finally plot any labels if tvals and not s.Label.hide: self.drawLabels(painter, xplotter, yplotter, tvals, markersize) # allow the factory to instantiate an x,y plotter document.thefactory.register( PointPlotter ) veusz-1.15/widgets/grid.py0000644002344000001440000003350611734662204015553 0ustar jssusers00000000000000# Copyright (C) 2004 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """The grid class allows graphs to be arranged in a regular grid. The graphs may share axes if they are stored in the grid widget. """ import veusz.document as document import veusz.setting as setting import veusz.qtall as qt4 import widget import page import graph import controlgraph class _gridengine: """Internal class to build up grid of widgets.""" def __init__(self, columns, rows): """Initialise allocater, with N columns or rows (other set to None).""" # we don't allocate space until it is used self.alloced = [] self.rows = rows self.columns = columns # starting position self.row = 0 self.col = 0 def isAlloced(self, c, r): """Returns whether element (c,r) allocated.""" if r >= len(self.alloced): return False row = self.alloced[r] if c >= len( row ): return False else: return row[c] def isAllocedBlock(self, c, r, w, h): """Is the block (c,r) -> (c+w,r+h) allocated?""" for y in xrange(h): for x in xrange(w): if self.isAlloced(c+x, y+r): return True return False def setAlloced(self, c, r): """Set element (c,r) as allocated.""" while r >= len(self.alloced): self.alloced.append( [] ) row = self.alloced[r] while c >= len(row): row.append( False ) row[c] = True def setAllocedBlock(self, c, r, w, h): """Set block (c,r)->(c+w,r+h) as allocated.""" for y in xrange(h): for x in xrange(w): self.setAlloced(x+c, y+r) def add(self, width, height): """Add a block of width x height, returning position as tuple.""" if self.columns is not None: # wrap around if item too wide # (providing we didn't request more columns than we have - # in that case we ignore the request) if ((self.col + width) > self.columns) and (width <= self.columns): self.col = 0 self.row += 1 # increase column until we can allocate the block # if we run out of columns, move to the next row while self.isAllocedBlock(self.col, self.row, width, height): self.col += 1 if (self.col + width > self.columns) and \ (width <= self.columns): self.col = 0 self.row += 1 # save position c = self.col r = self.row self.col += width else: # work in row based layout now if ((self.row + height) > self.rows) and (height <= self.rows): self.row = 0 self.col += 1 # increase row until we can allocate the next block # if we run out of rows, move to the next column while self.isAllocedBlock(self.col, self.row, width, height): self.row += 1 if (self.row + height > self.rows) and (height <= self.rows): self.row = 0 self.col += 1 # save position c = self.col r = self.row self.row += height # allocate and return block position self.setAllocedBlock(c, r, width, height) return (c, r) def getAllocedDimensions(self): """Return the columns x rows allocated.""" # assumes blocks don't get unset h = len(self.alloced) w = 0 for l in self.alloced: w = max(w, len(l)) return (w, h) class Grid(widget.Widget): """Class to hold plots in a grid arrangement. The idea is we either specify how many rows or columns to use. If we specify no of rows, then we fill vertically until we exceed rows, then we add another column. The same is true if cols is specified. """ typename='grid' allowusercreation=True description='Arrange graphs in a grid' allowedparenttypes=[page.Page] def __init__(self, parent, name=None): """Initialise the grid. """ widget.Widget.__init__(self, parent, name=name) # we're not descended from if type(self) == Grid: self.readDefaults() self.addAction( widget.Action('zeroMargins', self.actionZeroMargins, descr = 'Zero margins of graphs in grid', usertext = 'Zero margins') ) # calculated positions for children self.childpositions = {} # watch for changes to these variables to decide whether to # recalculate positions self.lastdimensions = None self.lastscalings = None self.lastchildren = None @classmethod def addSettings(klass, s): """Construct list of settings.""" widget.Widget.addSettings(s) s.add(setting.Int('rows', 2, descr = 'Number of rows in grid', usertext='Number of rows') ) s.add(setting.Int('columns', 2, descr = 'Number of columns in grid', usertext='Number of columns') ) s.add( setting.FloatList('scaleRows', [], descr = 'Row scaling factors. A sequence' ' of values\nby which to scale rows ' 'relative to each other.', usertext='Row scalings') ) s.add( setting.FloatList('scaleCols', [], descr = 'Column scaling factors. A sequence' ' of values\nby which to scale columns' ' relative to each other.', usertext='Column scalings') ) s.add( setting.Distance( 'leftMargin', '1.7cm', descr= 'Distance from left of grid to ' 'edge of page', usertext='Left margin', formatting=True) ) s.add( setting.Distance( 'rightMargin', '0.2cm', descr= 'Distance from right of grid to ' 'edge of page', usertext='Right margin', formatting=True) ) s.add( setting.Distance( 'topMargin', '0.2cm', descr= 'Distance from top of grid to ' 'edge of page', usertext='Top margin', formatting=True) ) s.add( setting.Distance( 'bottomMargin', '1.7cm', descr= 'Distance from bottom of grid' 'to edge of page', usertext='Bottom margin', formatting=True) ) def _getUserDescription(self): """User friendly description.""" s = self.settings return "%(rows)i rows, %(columns)i columns" % s userdescription = property(_getUserDescription) def _recalcPositions(self): """(internal) recalculate the positions of the children.""" # class to handle management ge = _gridengine(self.settings.columns, self.settings.rows) # copy children, and remove any which are axes children = [c for c in self.children if c.typename != 'axis'] child_dimensions = {} child_posns = {} for c in children: dims = (1, 1) child_dimensions[c] = dims child_posns[c] = ge.add(*dims) nocols, norows = ge.getAllocedDimensions() # exit if there aren't any children if nocols == 0 or norows == 0: return # get total scaling factors for cols scalecols = list(self.settings.scaleCols[:nocols]) scalecols += [1.]*(nocols-len(scalecols)) totscalecols = sum(scalecols) if totscalecols == 0.: totscalecols = 1. # fractional starting positions of columns last = 0. startcols = [last] for scale in scalecols: last += scale/totscalecols startcols.append(last) # similarly get total scaling factors for rows scalerows = list(self.settings.scaleRows[:norows]) scalerows += [1.]*(norows-len(scalerows)) totscalerows = sum(scalerows) if totscalerows == 0.: totscalerows = 1. # fractional starting positions of rows last = 0. startrows = [last] for scale in scalerows: last += scale/totscalerows startrows.append(last) # iterate over children, and modify positions self.childpositions.clear() for child in children: dims = child_dimensions[child] pos = child_posns[child] self.childpositions[child] = ( startcols[pos[0]], startrows[pos[1]], startcols[pos[0]+dims[0]], startrows[pos[1]+dims[1]] ) def actionZeroMargins(self): """Zero margins of plots inside this grid.""" operations = [] for c in self.children: if isinstance(c, graph.Graph): s = c.settings for v in ('leftMargin', 'topMargin', 'rightMargin', 'bottomMargin'): operations.append( document.OperationSettingSet(s.get(v), '0cm') ) self.document.applyOperation( document.OperationMultiple(operations, descr='zero margins') ) def _drawChild(self, phelper, child, bounds, parentposn): """Draw child at correct position, with correct bounds.""" # save old position, then update with calculated oldposn = child.position coutbound = parentposn if child in self.childpositions: cpos = self.childpositions[child] child.position = cpos # outer bounds for child dx, dy = bounds[2]-bounds[0], bounds[3]-bounds[1] coutbound = [ bounds[0]+dx*cpos[0], bounds[1]+dy*cpos[1], bounds[0]+dx*cpos[2], bounds[1]+dy*cpos[3] ] # work out bounds for graph in box # FIXME: should consider case if no graphs to side if abs(cpos[0]) < 1e-3: coutbound[0] = parentposn[0] if abs(cpos[1]) < 1e-3: coutbound[1] = parentposn[1] if abs(cpos[2]-1) < 1e-3: coutbound[2] = parentposn[2] if abs(cpos[3]-1) < 1e-3: coutbound[3] = parentposn[3] # draw widget child.draw(bounds, phelper, outerbounds=coutbound) # debugging #painter.setPen(qt4.QPen(qt4.Qt.red)) #painter.drawRect( qt4.QRectF(qt4.QPointF(coutbound[0], coutbound[1]), # qt4.QPointF(coutbound[2], coutbound[3]))) # restore position child.position = oldposn def draw(self, parentposn, phelper, outerbounds=None): """Draws the widget's children.""" s = self.settings # if the contents have been modified, recalculate the positions dimensions = (s.columns, s.rows) scalings = (s.scaleRows, s.scaleCols) if ( self.children != self.lastchildren or self.lastdimensions != dimensions or self.lastscalings != scalings ): self._recalcPositions() self.lastchildren = list(self.children) self.lastdimensions = dimensions self.lastscalings = scalings margins = ( s.get('leftMargin').convert(phelper), s.get('topMargin').convert(phelper), s.get('rightMargin').convert(phelper), s.get('bottomMargin').convert(phelper) ) bounds = self.computeBounds(parentposn, phelper, margins=margins) maxbounds = self.computeBounds(parentposn, phelper) painter = phelper.painter(self, bounds) # controls for adjusting grid margins phelper.setControlGraph(self,[ controlgraph.ControlMarginBox(self, bounds, maxbounds, phelper)]) for child in self.children: if child.typename != 'axis': self._drawChild(phelper, child, bounds, parentposn) # do not call widget.Widget.draw, do not collect 200 pounds pass def updateControlItem(self, cgi): """Grid resized or moved - call helper routine to move self.""" cgi.setWidgetMargins() # can't refer to Grid in Grid definition Grid.allowedparenttypes.append( Grid ) # allow the factory to instantiate a grid document.thefactory.register( Grid ) veusz-1.15/widgets/textlabel.py0000644002344000001440000001266311734662204016613 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """For plotting one or more text labels on a graph.""" import itertools import veusz.document as document import veusz.setting as setting import veusz.utils as utils import plotters import controlgraph class TextLabel(plotters.FreePlotter): """Add a text label to a graph.""" typename = 'label' description = "Text label" allowusercreation = True def __init__(self, parent, name=None): plotters.FreePlotter.__init__(self, parent, name=name) if type(self) == TextLabel: self.readDefaults() @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.FreePlotter.addSettings(s) s.add( setting.DatasetOrStr('label', '', descr='Text to show or text dataset', usertext='Label', datatype='text'), 0 ) s.add( setting.AlignHorz('alignHorz', 'left', descr="Horizontal alignment of label", usertext='Horz alignment', formatting=True), 7) s.add( setting.AlignVert('alignVert', 'bottom', descr='Vertical alignment of label', usertext='Vert alignment', formatting=True), 8) s.add( setting.Float('angle', 0., descr='Angle of the label in degrees', usertext='Angle', formatting=True), 9 ) s.add( setting.Text('Text', descr = 'Text settings', usertext='Text'), pixmap = 'settings_axislabel' ) # convert text to alignments used by Renderer cnvtalignhorz = { 'left': -1, 'centre': 0, 'right': 1 } cnvtalignvert = { 'top': 1, 'centre': 0, 'bottom': -1 } def draw(self, posn, phelper, outerbounds = None): """Draw the text label.""" s = self.settings d = self.document # exit if hidden if s.hide or s.Text.hide: return text = s.get('label').getData(d) xp, yp = self._getPlotterCoords(posn) if xp is None or yp is None: # we can't calculate coordinates return painter = phelper.painter(self, posn) textpen = s.get('Text').makeQPen() painter.setPen(textpen) font = s.get('Text').makeQFont(painter) # we should only be able to move non-dataset labels isnotdataset = ( not s.get('xPos').isDataset(d) and not s.get('yPos').isDataset(d) ) controlgraphitems = [] for index, (x, y, t) in enumerate(itertools.izip( xp, yp, itertools.cycle(text))): # render the text tbounds = utils.Renderer( painter, font, x, y, t, TextLabel.cnvtalignhorz[s.alignHorz], TextLabel.cnvtalignvert[s.alignVert], s.angle ).render() # add cgi for adjustable positions if isnotdataset: cgi = controlgraph.ControlMovableBox(self, tbounds, phelper, crosspos = (x, y)) cgi.labelpt = (x, y) cgi.widgetposn = posn cgi.index = index controlgraphitems.append(cgi) phelper.setControlGraph(self, controlgraphitems) def updateControlItem(self, cgi): """Update position of point given new name and vals.""" s = self.settings pointsX = list(s.xPos) # make a copy here so original is not modifed pointsY = list(s.yPos) ind = cgi.index # calculate new position coordinate for item xpos, ypos = self._getGraphCoords(cgi.widgetposn, cgi.deltacrosspos[0]+cgi.posn[0], cgi.deltacrosspos[1]+cgi.posn[1]) if xpos is None or ypos is None: return pointsX[ind], pointsY[ind] = xpos, ypos operations = ( document.OperationSettingSet(s.get('xPos'), pointsX), document.OperationSettingSet(s.get('yPos'), pointsY) ) self.document.applyOperation( document.OperationMultiple(operations, descr='move label') ) # allow the factory to instantiate a text label document.thefactory.register( TextLabel ) veusz-1.15/widgets/nonorthgraph.py0000644002344000001440000001304311734662204017331 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Non orthogonal graph root.""" import controlgraph from widget import Widget from page import Page from grid import Grid import veusz.setting as setting filloptions = ('center', 'outside', 'top', 'bottom', 'left', 'right', 'polygon') class FillBrush(setting.BrushExtended): '''Brush for filling point region.''' def __init__(self, *args, **argsv): setting.BrushExtended.__init__(self, *args, **argsv) self.add( setting.Choice('filltype', filloptions, 'center', descr='Fill to this edge/position', usertext='Fill type') ) self.get('hide').newDefault(True) class NonOrthGraph(Widget): '''Non-orthogonal graph base widget.''' allowedparenttypes = [Page, Grid] @classmethod def addSettings(klass, s): '''Construct list of settings.''' Widget.addSettings(s) s.add( setting.Distance( 'leftMargin', '1.7cm', descr='Distance from left of graph to edge', usertext='Left margin', formatting=True) ) s.add( setting.Distance( 'rightMargin', '0.2cm', descr='Distance from right of graph to edge', usertext='Right margin', formatting=True) ) s.add( setting.Distance( 'topMargin', '0.2cm', descr='Distance from top of graph to edge', usertext='Top margin', formatting=True) ) s.add( setting.Distance( 'bottomMargin', '1.7cm', descr='Distance from bottom of graph to edge', usertext='Bottom margin', formatting=True) ) s.add( setting.GraphBrush( 'Background', descr = 'Background plot fill', usertext='Background'), pixmap='settings_bgfill' ) s.add( setting.Line('Border', descr = 'Graph border line', usertext='Border'), pixmap='settings_border') def graphToPlotCoords(self, coorda, coordb): '''Convert graph to plotting coordinates. Returns (plta, pltb) coordinates ''' def coordRanges(self): '''Return coordinate ranges of plot. This is in the form [[mina, maxa], [minb, maxb]].''' def drawFillPts(self, painter, extfill, bounds, ptsx, ptsy): '''Draw set of points for filling. extfill: extended fill brush bounds: usual tuple (minx, miny, maxx, maxy) ptsx, ptsy: translated plotter coordinates ''' def drawGraph(self, painter, bounds, datarange, outerbounds=None): '''Plot graph area.''' def drawAxes(self, painter, bounds, datarange, outerbounds=None): '''Plot axes.''' def setClip(self, painter, bounds): '''Set clipping for graph.''' def getDataRange(self): """Get automatic data range.""" drange = [1e199, -1e199, 1e199, -1e199] for c in self.children: c.updateDataRanges(drange) return drange def draw(self, parentposn, phelper, outerbounds=None): '''Update the margins before drawing.''' s = self.settings margins = ( s.get('leftMargin').convert(phelper), s.get('topMargin').convert(phelper), s.get('rightMargin').convert(phelper), s.get('bottomMargin').convert(phelper) ) bounds = self.computeBounds(parentposn, phelper, margins=margins) maxbounds = self.computeBounds(parentposn, phelper) painter = phelper.painter(self, bounds) # controls for adjusting margins phelper.setControlGraph(self, [ controlgraph.ControlMarginBox(self, bounds, maxbounds, phelper)]) # do no painting if hidden if s.hide: return bounds # plot graph datarange = self.getDataRange() self.drawGraph(painter, bounds, datarange, outerbounds=outerbounds) self.drawAxes(painter, bounds, datarange, outerbounds=outerbounds) # paint children for c in reversed(self.children): c.draw(bounds, phelper, outerbounds=outerbounds) return bounds def updateControlItem(self, cgi): """Graph resized or moved - call helper routine to move self.""" cgi.setWidgetMargins() veusz-1.15/widgets/polygon.py0000644002344000001440000000637111734662204016315 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.document as document import veusz.setting as setting import veusz.qtall as qt4 import veusz.utils as utils import plotters class Polygon(plotters.FreePlotter): """For plotting polygons.""" typename = 'polygon' allowusercreeation = True description = 'Plot a polygon' def __init__(self, parent, name=None): """Initialise object, setting axes.""" plotters.FreePlotter.__init__(self, parent, name=name) @classmethod def addSettings(klass, s): """Construct list of settings.""" plotters.FreePlotter.addSettings(s) s.add( setting.Line('Line', descr = 'Line around polygon', usertext = 'Line'), pixmap = 'settings_plotline' ) s.add( setting.BrushExtended('Fill', descr = 'Fill within polygon', usertext = 'Fill'), pixmap = 'settings_plotfillbelow' ) def draw(self, posn, phelper, outerbounds=None): """Plot the data on a plotter.""" s = self.settings d = self.document # exit if hidden if s.hide: return # get points in plotter coordinates xp, yp = self._getPlotterCoords(posn) if xp is None or yp is None: # we can't calculate coordinates return x1, y1, x2, y2 = posn cliprect = qt4.QRectF( qt4.QPointF(x1, y1), qt4.QPointF(x2, y2) ) painter = phelper.painter(self, posn, clip=cliprect) pen = s.Line.makeQPenWHide(painter) pw = pen.widthF()*2 lineclip = qt4.QRectF( qt4.QPointF(x1-pw, y1-pw), qt4.QPointF(x2+pw, y2+pw) ) # this is a hack as we generate temporary fake datasets path = qt4.QPainterPath() for xvals, yvals in document.generateValidDatasetParts( document.Dataset(xp), document.Dataset(yp)): poly = qt4.QPolygonF() utils.addNumpyToPolygonF(poly, xvals.data, yvals.data) clippedpoly = qt4.QPolygonF() utils.polygonClip(poly, lineclip, clippedpoly) path.addPolygon(clippedpoly) path.closeSubpath() utils.brushExtFillPath(painter, s.Fill, path, stroke=pen) # allow the factory to instantiate this document.thefactory.register( Polygon ) veusz-1.15/pyqtdistutils.py0000644002344000001440000001047011734662204016115 0ustar jssusers00000000000000# Subclasses disutils.command.build_ext, # replacing it with a SIP version that compiles .sip -> .cpp # before calling the original build_ext command. # Written by Giovanni Bajo # Based on Pyrex.Distutils, written by Graham Fawcett and Darrel Gallion. import distutils.command.build_ext from distutils.dep_util import newer, newer_group import os import sys import PyQt4.pyqtconfig def replace_suffix(path, new_suffix): return os.path.splitext(path)[0] + new_suffix class build_ext (distutils.command.build_ext.build_ext): description = ('Compile SIP descriptions, then build C/C++ extensions ' '(compile/link to build directory)') def _get_sip_output_list(self, sbf): ''' Parse the sbf file specified to extract the name of the generated source files. Make them absolute assuming they reside in the temp directory. ''' for L in file(sbf): key, value = L.split('=', 1) if key.strip() == 'sources': out = [] for o in value.split(): out.append(os.path.join(self.build_temp, o)) return out raise RuntimeError, 'cannot parse SIP-generated "%s"' % sbf def _find_sip(self): cfg = PyQt4.pyqtconfig.Configuration() return cfg.sip_bin def _sip_inc_dir(self): cfg = PyQt4.pyqtconfig.Configuration() return cfg.sip_inc_dir def get_includes(self, cfg): incdirs = [] for mod in ('QtCore', 'QtGui'): if cfg.qt_framework: incdirs.append( os.path.join(cfg.qt_lib_dir, mod + '.framework', 'Headers') ) else: incdirs.append( os.path.join(cfg.qt_inc_dir, mod) ) return incdirs def swig_sources (self, sources, extension=None): if not self.extensions: return cfg = PyQt4.pyqtconfig.Configuration() # add directory of input files as include path indirs = list(set([os.path.dirname(x) for x in sources])) # Add the SIP and Qt include directories to the include path extension.include_dirs += [ cfg.sip_inc_dir, cfg.qt_inc_dir, ] + self.get_includes(cfg) + indirs # link against libraries if cfg.qt_framework: extension.extra_link_args = ['-framework', 'QtGui', '-framework', 'QtCore'] elif sys.platform == 'win32': extension.libraries = ['QtGui4', 'QtCore4'] else: extension.libraries = ['QtGui', 'QtCore'] extension.library_dirs = [cfg.qt_lib_dir] depends = extension.depends # Filter dependencies list: we are interested only in .sip files, # since the main .sip files can only depend on additional .sip # files. For instance, if a .h changes, there is no need to # run sip again. depends = [f for f in depends if os.path.splitext(f)[1] == '.sip'] # Create the temporary directory if it does not exist already if not os.path.isdir(self.build_temp): os.makedirs(self.build_temp) # Collect the names of the source (.sip) files sip_sources = [] sip_sources = [source for source in sources if source.endswith('.sip')] other_sources = [source for source in sources if not source.endswith('.sip')] generated_sources = [] sip_bin = self._find_sip() for sip in sip_sources: # Use the sbf file as dependency check sipbasename = os.path.basename(sip) sbf = os.path.join(self.build_temp, replace_suffix(sipbasename, '.sbf')) if newer_group([sip]+depends, sbf) or self.force: self._sip_compile(sip_bin, sip, sbf) out = self._get_sip_output_list(sbf) generated_sources.extend(out) return generated_sources + other_sources def _sip_compile(self, sip_bin, source, sbf): cfg = PyQt4.pyqtconfig.Configuration() self.spawn([sip_bin, '-c', self.build_temp, ] + cfg.pyqt_sip_flags.split() + [ '-I', cfg.pyqt_sip_dir, '-b', sbf, source]) veusz-1.15/Documents/0000755002344000001440000000000011734662466014552 5ustar jssusers00000000000000veusz-1.15/Documents/document_api.py0000644002344000001440000001065411734662204017567 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Document veusz widget types and settings Creates an xml file designed to be processed into a web page using xsl """ import re import veusz.widgets as widgets import veusz.document as document import veusz.setting as setting #import cElementTree as ET #import elementtree.ElementTree as ET import xml.etree.ElementTree as ET def processSetting(parent, setn): """Convert setting to xml element.""" setnxml = ET.SubElement(parent, "setting") ET.SubElement(setnxml, "apiname").text = setn.name ET.SubElement(setnxml, "displayname").text = setn.usertext ET.SubElement(setnxml, "description").text = setn.descr ET.SubElement(setnxml, "formatting").text = str(setn.formatting) typename = str(type(setn)) typename = re.match(r"^$", typename).group(1) typename = typename.split('.')[-1] ET.SubElement(setnxml, "type").text = typename # show list of possible choices if there is one if isinstance(setn, setting.Choice): for choice in setn.vallist: ET.SubElement(setnxml, "choice").text = choice if not isinstance(setn.default, setting.Reference): ET.SubElement(setnxml, "default").text = setn.toText() else: ET.SubElement(setnxml, "default").text = "to reference" def processSettings(parent, setns): """Convert setting to xml element.""" setnsxml = ET.SubElement(parent, "settings") ET.SubElement(setnsxml, "apiname").text = setns.name ET.SubElement(setnsxml, "displayname").text = setns.usertext ET.SubElement(setnsxml, "description").text = setns.descr for s in setns.getSettingList(): processSetting(setnsxml, s) def processWidgetType(root, name): """Produce documentation for a widget type.""" widgetxml = ET.SubElement(root, "widget") klass = document.thefactory.getWidgetClass(name) print klass ET.SubElement(widgetxml, "apiname").text = name try: ET.SubElement(widgetxml, "description").text = klass.description except AttributeError: pass for parent in [k for k in klass.allowedparenttypes if k is not None]: ET.SubElement(widgetxml, "allowedparent").text = parent.typename ET.SubElement(widgetxml, "usercreation").text = str(klass.allowusercreation) thesettings = setting.Settings('') klass.addSettings(thesettings) #for s in thesettings.getSettingList(): processSettings(widgetxml, thesettings) for s in thesettings.getSettingsList(): processSettings(widgetxml, s) def indent(elem, level=0): """Indent output, from elementtree manual.""" i = "\n" + level*" " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: indent(elem, level+1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i def addXSL(filename): f = open(filename) l = f.readlines() f.close() l.insert(1, '\n') f = open(filename, 'w') f.writelines(l) f.close() def main(): widgettypes = document.thefactory.listWidgets() root = ET.Element("widgets") for wt in widgettypes: processWidgetType(root, wt) tree = ET.ElementTree(root) indent(root) tree.write('widget_doc.xml', encoding="utf8") addXSL('widget_doc.xml') if __name__ == '__main__': main() veusz-1.15/Documents/manual.txt0000644002344000001440000021136011734662402016561 0ustar jssusers00000000000000Veusz - a scientific plotting package Jeremy Sanders Copyright © 2011 This document is licensed under the GNU General Public License, version 2 or greater. Please see the file COPYING for details, or see http://www.gnu.org/licenses/gpl-2.0.html. __________________________________________________________ Table of Contents Introduction Veusz Terminology Widget Measurements Settings Text Axis numbers Installation The main window My first plot Reading data Descriptors Reading CSV files Reading FITS files Reading other data formats Manipulating datasets Using dataset plugins Using expressions to create new datasets Linking datasets to expressions Splitting data Defining new constants or functions Dataset plugins Command line interface Introduction Commands Action Add AddCustom AddImportPath CloneWidget Close CreateHistogram DatasetPlugin EnableToolbar Export ForceUpdate Get GetChildren GetClick GetData GetDataType GetDatasets GPL ImportFile ImportFile2D ImportFileCSV ImportFilePlugin ImportFITSFile ImportString ImportString2D IsClosed List Load MoveToPage ReloadData Rename Remove ResizeWindow Save Set SetAntiAliasing SetData SetDataExpression SetDataRange SetData2D SetData2DExpression SetData2DExpressionXYZ SetData2DXYFunc SetDataDateTime SetDataText SetToReference SetUpdateInterval SetVerbose StartSecondView TagDatasets To Quit WaitForClose Zoom Security Using Veusz from other programs Non-Qt Python programs PyQt4 programs Non Python programs C, C++ and Fortran __________________________________________________________ Introduction Veusz Veusz is a scientific plotting package. It was written as I was dissatisfied with existing plotting packages as they were either old and unmaintained (like pgplot and qdp, which does not support postscript fonts), not free (like IDL or Matlab), or had user interfaces I do not appreciate (like gnuplot). Veusz is designed to be easily extensible, and is written in Python, a high level language (in the future it may be necessary to write some parts in another language for speed, but this will be kept to an absolute minimum). It is also designed in an object oriented fashion, where a document is built up by a number of parts in a hierarchy. The advantage of using Python is that is is easy to allow the user to script Veusz using Python as a very powerful scripting language. Indeed, the saved file format is a Python script. The technologies behind Veusz include PyQt (a very easy to use Python interface to Qt, which is used for rendering and the graphical user interface, GUI) and numpy (a package for Python which makes the handling of large datasets easy). Veusz has two user interfaces: a graphical one which gives the user a relatively shallow learning curve, and a command line interface. The command line interface is also used by scripting and in the saved file format. Furthermore, Veusz can be embedded within other Python programs, with plots in their own windows, or it can be controlled from any other application. __________________________________________________________ Terminology Here I define some terminology for future use. __________________________________________________________ Widget A document and its graphs are built up from widgets. These widgets can often by placed within each other, depending on the type of the widget. A widget has children, those widgets placed within it, and its parent. The widgets have a number of different settings which modify their behaviour. These include the font to be used, the line thickness, and whether an axis is logarithmic. In addition they have actions, which perform some sort of activity on the widget or its children, like "fit" for a fit widget. Widgets are specified with a "path", like a file in Unix or Windows. These can be relative to the current widget (do not start with a slash), or absolute (do not start with a slash). Examples of paths include, "/page1/graph1/x", "x" and ".". The widget types include 1. document - representing a complete document. A document can contain pages. In addition it contains a setting giving the page size for the document. 2. page - representing a page in a document. One or more graphs can be placed on a page, or a grid. 3. graph - defining an actual graph. A graph can be placed on a page or within a grid. Contained within the graph are its axes and plotters. A graph can be given a background fill and a border if required. It also has a margin, which specifies how far away from the edge of its parent widget to plot the body of the graph. A graph can contain several axes, at any position on the plot. In addition a graph can use axes defined in parent widgets, shared with other graphs. More than one graph can be placed within in a page. The margins can be adjusted so that they lie within or besides each other. 4. grid - containing one or more graphs. A grid plots graphs in a gridlike fashion. You can specify the number of rows and columns, and the plots are automatically replotted in the chosen arrangement. A grid can contain graphs or axes. If an axis is placed in a grid, it can be shared by the graphs in the grid. 5. axis - giving the scale for plotting data. An axis translates the coordinates of the data to the screen. An axis can be linear or logarithmic, it can have fixed endpoints, or can automatically get them from the plotted data. It also has settings for the axis labels and lines, tick labels, and major and minor tick marks. An axis may be "horizontal" or "vertical" and can appear anywhere on its parent graph or grid. If an axis appears within a grid, then it can be shared by all the graphs which are contained within the grid. 6. plotters - types of widgets which plot data or add other things on a graph. There is no actual plotter widget which can be added, but several types of plotters listed below. Plotters typically take an axis as a setting, which is the axis used to plot the data on the graph (default x and y). a. function - a plotter which plots a function on the graph. Functions can be functions of x or y (parametric functions are not done yet!), and are defined in Python expression syntax, which is very close to most other languages. For example "3*x**2 + 2*x - 4". A number of functions are available (e.g. sin, cos, tan, exp, log...). Technically, Veusz imports the numpy package when evaluating, so numpy functions are available. As well as the function setting, also settable is the line type to plot the function, and the number of steps to evaluate the function when plotting. Filling is supported above/below/left/right of the function. b. xy - a plotter which plots scatter, line, or stepped plots. This versatile plotter takes an x and y dataset, and plots (optional) points, in a chosen marker and colour, connecting them with (optional) lines, and plotting (optional) error bars. An xy plotter can also plot a stepped line, allowing histograms to be plotted (note that it doesn't yet do the binning of the data). The settings for the xy widget are the various attibutes for the points, line and error bars, the datasets to plot, and the axes to plot on. The xy plotter can plot a label next to each dataset, which is either the same for each point or taken from a text dataset. If you wish to leave gaps in a plot, the input value "nan" can be specified in the numeric dataset. c. fit - fit a function to data. This plotter is a like the function plotter, but allows fitting of the function to data. This is achived by clicking on a "fit" button, or using the "fit" action of the widget. The fitter takes a function to fit containing the unknowns, e.g. "a*x**2 + b*x + c", and initial values for the variables (here a, b and c). It then fits the data (note that at the moment, the fit plotter fits all the data, not just the data that can be seen on the graph) by minimising the chi-squared. In order to fit properly, the y data (or x, if fitting as a function of x) must have a properly defined, preferably symmetric error. If there is none, Veusz assumes the same fractional error everywhere, or symmetrises asymmetric errors. Note that more work is required in this widget, as if a parameter is not well defined by the data, the matrix inversion in the fit will fail. In addition Veusz does not supply estimates for the errors or the final chi-squared in a machine readable way. If the fitting parameters vary significantly from 1, then it is worth "normalizing" them by adding in a factor in the fit equation to bring them to of the order of 1. d. bar - a bar chart which plots sets of data as horizontal or vertical bars. Multiple datasets are supported. In "grouped" mode the bars are placed side-by-side for each dataset. In "stacked" mode the bars are placed on top of each other (in the appropriate direction according to the sign of the dataset). Bars are placed on coordinates given, or in integer values from 1 upward if none are given. Error bars are plotted for each of the datasets. Different fill styles can be given for each dataset given. A separate key value can be given for each dataset. e. key - a box which describes the data plotted. If a key is added to a plot, the key looks for "key" settings of the other data plotted within a graph. If there any it builds up a box containing the symbol and line for the plotter, and the text in the "key" setting of the widget. This allows a key to be very easily added to a plot. The key may be placed in any of the corners of the plot, in the centre, or manually placed. Depending on the ordering of the widgets, the key will be placed behind or on top of the widget. The key can be filled and surrounded by a box, or not filled or surrounded. f. label - a text label places on a graph. The alignment can be adjusted and the font changed. The position of the label can be specified in fractional terms of the current graph, or using axis coordinates. g. rect, ellipse - these draw a rectangle or ellipse, respectively, of size and rotation given. These widgets can be placed directly on the page or on a graph. The centre can be given in axis coordinates or fractional coordinates. h. imagefile - draw an external graphs file on the graph or page, with size and rotation given. The centre can be given in axis coordinates or fractional coordinates. i. line - draw a line with optional arrowheads on the graph or page. One end can be given in axis coordinates or fractional coordinates. j. contour - plot contours of a 2D dataset on the graph. Contours are automatically calculated between the minimum and maximum values of the graph or chosen manually. The line style of the contours can be chosen individually and the region between contours can be filled with shading or color. 2D datasets currently consist of a regular grid of values between minimum and maximum positions in x and y. They can be constructed from three 1D datasets of x, y and z if they form a regular x, y grid. k. image - plot a 2D dataset as a colored image. Different color schemes can be chosen. The scaling between the values and the image can be specified as linear, logarithmic, square-root or square. l. polygon - plot x and y points from datasets as a polygon. The polygon can be placed directly on the page or within a graph. Coordinates are either plotted using the axis or as fractions of the width and height of the containing widget. m. boxplot - plot distribution of points in a dataset. n. polar - plot polar data or functions. This is a non-orthogonal plot and is placed directly on the page rather than in a graph. o. ternary - plot data of three variables which add up to 100 per cent.This is a non-orthogonal plot and is placed directly on the page rather than in a graph. __________________________________________________________ Measurements Distances, widths and lengths in Veusz can be specified in a number of different ways. These include absolute distances specified in physical units, e.g. 1cm, 0.05m, 10mm, 5in and 10pt, and relative units, which are relative to the largest dimension of the page, including 5%, 1/20, 0.05. __________________________________________________________ Settings The various settings of the widgets come in a number of types, including integers (e.g. 10), floats (e.g. 3.14), text ("hi there!"), distances (see above), options ("horizontal" or "vertical" for axes). Veusz performs type checks on these parameters. If they are in the wrong format the control to edit the setting will turn red. In the command line, a TypeError exception is thrown. In the GUI, the current page is replotted if a setting is changed when enter is pressed or the user moves to another setting. The settings are split up into formatting settings, controlling the appearance of the plot, or properties, controlling what is plotted and how it is plotted. Default settings, including the default font and line style, and the default settings for any graph widget, can be modified in the "Default styles" dialog box under the "Edit" menu. Default settings are set on a per-document basis, but can be saved into a separate file and loaded. A default default settings file can be given to use for new documents (set in the preferences dialog). __________________________________________________________ Text Veusz understands a limited set of LaTeX-like formatting for text. There are some differences (for example, "10^23" puts the 2 and 3 into superscript), but it is fairly similar. You should also leave out the dollar signs. Veusz supports superscripts ("^"), subscripts ("_"), brackets for grouping attributes are "{" and "}". Supported LaTeX symbols include: \AA, \Alpha, \Beta, \Chi, \Delta, \Epsilon, \Eta, \Gamma, \Iota, \Kappa, \Lambda, \Mu, \Nu, \Omega, \Omicron, \Phi, \Pi, \Psi, \Rho, \Sigma, \Tau, \Theta, \Upsilon, \Xi, \Zeta, \alpha, \approx, \ast, \asymp, \beta, \bowtie, \bullet, \cap, \chi, \circ, \cup, \dagger, \dashv, \ddagger, \deg, \delta, \diamond, \divide, \doteq, \downarrow, \epsilon, \equiv, \eta, \gamma, \ge, \gg, \in, \infty, \int, \iota, \kappa, \lambda, \le, \leftarrow, \lhd, \ll, \models, \mp, \mu, \neq, \ni, \nu, \odot, \omega, \omicron, \ominus, \oplus, \oslash, \otimes, \parallel, \perp, \phi, \pi, \pm, \prec, \preceq, \propto, \psi, \rhd, \rho, \rightarrow, \sigma, \sim, \simeq, \sqrt, \sqsubset, \sqsubseteq, \sqsupset, \sqsupseteq, \star, \stigma, \subset, \subseteq, \succ, \succeq, \supset, \supseteq, \tau, \theta, \times, \umid, \unlhd, \unrhd, \uparrow, \uplus, \upsilon, \vdash, \vee, \wedge, \xi, \zeta. Please request additional characters if they are required (and exist in the unicode character set). Special symbols can be included directly from a character map. Other LaTeX commands are supported. "\\" breaks a line. This can be used for simple tables. For example "{a\\b} {c\\d}" shows "a c" over "b d". The command "\frac{a}{b}" shows a vertical fraction a/b. Also supported are commands to change font. The command "\font{name}{text}" changes the font text is written in to name. This may be useful if a symbol is missing from the current font, e.g. "\font{symbol}{g}" should produce a gamma. You can increase, decrease, or set the size of the font with "\size{+2}{text}", "\size{-2}{text}", or "\size{20}{text}". Numbers are in points. Various font attributes can be changed: for example, "\italic{some italic text}" (or use "\textit" or "\emph"), "\bold{some bold text}" (or use "\textbf") and "\underline{some underlined text}". Example text could include "Area / \pi (10^{-23} cm^{-2})", or "\pi\bold{g}". Veusz plots these symbols with Qt's unicode support. If your current font does not contain these symbols then you may get messy results. If you find this is the case, I highly recommend you to down load Microsoft's Corefonts (see http://corefonts.sourceforge.net/). __________________________________________________________ Axis numbers The way numbers are shown on axes is chosen automatically. For standard numerical axes, values are shown with the "%Vg" formatting (see below). For date axes, an appropriate date formatting is used so that the interval shown is correct. A format can be given for an axis in the axis number formatting panel can be given to explicitly choose a format. Some examples are given in the drop down axis menu. Hold the mouse over the example for detail. C-style number formatting is used with a few Veusz specific extensions. Text can be mixed with format specifiers, which start with a "%" sign. Examples of C-style formatting include: "%.2f" (decimal number with two decimal places, e.g. 2.01), "%.3e" (scientific formatting with three decimal places, e.g. 2.123e-02), "%g" (general formatting, switching between "%f" and "%e" as appropriate). See http://opengroup.org/onlinepubs/007908799/xsh/fprintf.html for details. Veusz extensions include "%Ve", which is like "%e" except it displays scientific notation as written, e.g. 1.2x10^23, rather than 1.2e+23. "%Vg" switches between standard numbers and Veusz scientific notation for large and small numbers. "%VE" using engineering SI suffixes to represent large or small numbers (e.g. 1000 is 1k). Veusz allows dates and times to be formatted using "%VDX" where "X" is one of the formatting characters for strftime (see http://opengroup.org/onlinepubs/007908799/xsh/strftime.html for details). These include "a" for an abbreviated weekday name, "A" for full weekday name, "b" for abbreviated month name, "B" for full month name, "c" date and time representaiton, "d" day of month 01..31, "H" hour as 00..23, "I" hour as 01..12, "j" as day of year 001..366, "m" as month 01..12, "M" minute as 00..59, "p" AM/PM, "S" second 00..61, "U" week number of year 00..53 (Sunday as first day of week), "w" weekday as decimal number 0..6, "W" week number of year (Monday as first day of week), "x" date representation, "X" time representation, "y" year without century 00..99 and "Y" year. "%VDVS" is a special Veusz addon format which shows seconds and fractions of seconds (e.g. 12.2). __________________________________________________________ Installation Please look at the Installation notes (INSTALL) for details on installing Veusz. __________________________________________________________ The main window You should see the main window when you run Veusz (you can just type the veusz command in Unix). [mainwindow.png] The Veusz window is split into several sections. At the top is the menu bar and tool bar. These work in the usual way to other applications. Sometimes options are disabled (greyed out) if they do not make sense to be used. If you hold your mouse over a button for a few seconds, you will usually get an explanation for what it does called a "tool tip". Below the main toolbar is a second toolbar for constructing the graph by adding widgets (on the left), and some editing buttons. The add widget buttons add the request widget to the currently selected widget in the selection window. The widgets are arranged in a tree-like structure. Below these toolbars and to the right is the plot window. This is where the current page of the current document is shown. You can adjust the size of the plot on the screen (the zoom factor) using the "View" menu or the zoom tool bar button (the magnifying glass). Initially you will not see a plot in the plot window, but you will see the Veusz logo. At the moment you cannot do much else with the window. In the future you will be able to click on items in the plot to modify them. To the left of the plot window is the selection window, and the properties and formatting windows. The properties window lets you edit various aspects of the selected widget (such as the minimum and maximum values on an axis). Changing these values should update the plot. The formatting lets you modify the appearance of the selected widget. There are a series of tabs for choosing what aspect to modify. The various windows can be "dragged" from the main window to "float" by themselves on the screen. To the bottom of the window is the console. This window is not shown by default, but can be enabled in the View menu. The console is a Veusz and Python command line console. To read about the commands available see Commands. As this is a Python console, you can enter mathematical expressions (e.g. "1+2.0*cos(pi/4)") here and they will be evaluated when you press Enter. The usual special functions and the operators are supported. You can also assign results to variables (e.g. "a=1+2") for use later. The console also supports command history like many Unix shells. Press the up and down cursor keys to browse through the history. Command line completion is not available yet! In recent versions there also exists a dataset browsing window, by default to the right of the screen. This window allows you to view the datasets currently loaded, their dimensions and type. Hovering a mouse over the size of the dataset will give you a preview of the data. __________________________________________________________ My first plot After opening Veusz, on the left of the main window, you will see a Document, containing a Page, which contains a Graph with its axes. The Graph is selected in the selection window. The toolbar above adds a new widget to the selected widget. If a widget cannot be added to a selected widget it is disabled. On opening a new document Veusz automatically adds a new Page and Graph (with axes) to the document. You will see something like this: [winwithgraph.png] Select the x axis which has been added to the document (click on "x" in the selection window). In the properties window you will see a variety of different properties you can modify. For instance you can enter a label for the axis by writing "Area (cm^{2})" in the box next to label and pressing enter. Veusz supports text in LaTeX-like form (without the dollar signs). Other important parameters is the "log" switch which switches between linear and logarithmic axes, and "min" and "max" which allow the user to specify the minimum and maximum values on the axes. The formatting dialog lets you edit various aspects of the graph appearance. For instance the "Line" tab allows you to edit the line of the axis. Click on "Line", then you can then modify its colour. Enter "green" instead of "black" and press enter. Try making the axis label bold. Now you can try plotting a function on the graph. If the graph, or its children are selected, you will then be able to click the "function" button at the top (a red curve on a graph). You will see a straight line (y=x) added to the plot. If you select "function1", you will be able to edit the functional form plotted and the style of its line. Change the function to "x**2" (x-squared). We will now try plotting data on the graph. Go to your favourite text editor and save the following data as test.dat: 1 0.1 -0.12 1.1 0.1 2.05 0.12 -0.14 4.08 0.12 2.98 0.08 -0.1 2.9 0.11 4.02 0.04 -0.1 15.3 1.0 The first three columns are the x data to plot plus its asymmetric errors. The final two columns are the y data plus its symmetric errors. In Veusz, go to the "Data" menu and select "Import". Type the filename into the filename box, or use the "Browse..." button to search for the file. You will see a preview of the data pop up in the box below. Enter "x,+,- y,+-" into the descriptors edit box (note that commas and spaces in the descriptor are almost interchangeable in Veusz 1.6 or newer). This describes the format of the data which describes dataset "x" plus its asymmetric errors, and "y" with its symmetric errors. If you now click "Import", you will see it has imported datasets "x" and "y". To plot the data you should now click on "graph1" in the tree window. You are now able to click on the "xy" button (which looks like points plotted on a graph). You will see your data plotted on the graph. Veusz plots datasets "x" and "y" by default, but you can change these in the properties of the "xy" plotter. You are able to choose from a variety of markers to plot. You can remove the plot line by choosing the "Plot Line" subsetting, and clicking on the "hide" option. You can change the colour of the marker by going to the "Marker Fill" subsetting, and entering a new colour (e.g. red), into the colour property. __________________________________________________________ Reading data Currently Veusz supports reading data from a text file, FITS format files, CSV files, QDP files, binary files and NPY/NPZ files. Reading data is supported using the "Data, Import" dialog, or using the ImportFile and ImportString commands which read data from files or an existing Python string (allowing data to be embedded in a Python script). In addition, the user can load or write plugins in Python which load data into Veusz in an arbitrary format. At the moment QDP, binary and NPY/NPZ files are supported with this method. [importdialog.png] CSV files are intuitive to use and are described below. In addition data may also be read in from FITS files if the PyFITS Python module is installed. FITS is a widespread astronomical data format. FITS files are read using the FITS tab on the import dialog or using the ImportFITSFile command. Two dimensional data are also supported using the 2D tab on the Import dialog box, ImportFile2D and ImportString2D commands. __________________________________________________________ Descriptors The "Data, Import" dialog box, ImportFile and ImportString commands use a "Descriptor", or list of dataset names, to describe how the data are formatted in the import file. The descriptor at its simplest is a list of the names of the datasets to import (which are columns in the file). Additionally modifiers are added if error bars are also read in. Examples of descriptors are below: 1. x y two columns are present in the file, they will be read in as datasets "x" and "y". 2. x,+- y,+,- or x +- y + - two datasets are in the file. Dataset "x" consists of the first two columns. The first column are the values and the second are the symmetric errors. "y" consists of three columns (note the comma between + and -). The first column are the values, the second positive asymmetric errors, and the third negative asymmetric errors. Suppose the input file contains: 1.0 0.3 2 0.1 -0.2 1.5 0.2 2.3 2e-2 -0.3E0 2.19 0.02 5 0.1 -0.1 Then x will contain "1+-0.3", "1.5+-0.2", "2.19+-0.02". y will contain "2 +0.1 -0.2", "2.3 +0.02 -0.3", "5 +0.1 -0.1". 3. x[1:2] y[:] the first column is the data "x_1", the second "x_2". Subsequent columns are read as "y[1]" to "y[n]". 4. y[:]+- read each pair of columns as a dataset and its symmetric error, calling them "y[1]" to "y[n]". 5. foo,,+- read the first column as the foo dataset, skip a column, and read the third column as its symmetric error. The dataset names given here x and y, are just representative. Dataset names can contain virtually any character, even unicode characters. If the name contains non alpha-numeric characters (characters outside of A-Z, a-z and 0-9), then the dataset name should be contained within back-tick characters. An example descriptor is `length data (m)`,+- `speed (mps)`,+,-, for two datasets with spaces and brackets in their names. The special names +-, + or - specify that the datasets before are followed by columns containing symmetric, positive or negative errors. The signs on positive or negative errors are automatically set to be correct. If a descriptor is left blank, Veusz will automatically create dataset names. If the prefix and suffix settings are blank, they are assigned names col1, col2, etc. If prefix and suffix are not blank, the datasets are called prefix+number+suffix. When reading in data, Veusz treats any whitespace as separating columns. The columns do not actually need to be in columns! Furthermore a "\" symbol can be placed at the end of a line to mark a continuation. Veusz will read the next line as if it were placed at the end of the current line. In addition comments and blank lines are ignored. Comments start with a "#", ";", "!" or "%", and continue until the end of the line. The special value "nan" can be used to specify a break in a dataset. Veusz supports reading in other types of data. The type of data can be added in round brackets after the name. Veusz will try to guess the type of data based on the first value, so you should specify it if there is any form of ambiguity (e.g. is 3 text or a number). Supported types are numbers (use numeric in brackets) and text (use text in brackets). An example descriptor would be "x(numeric) +- y(numeric) + - label(text)" for an x dataset followed by its symmetric errors, a y dataset followed by two columns of asymmetric errors, and a final column of text for the label dataset. A text column does not need quotation unless it contains space characters or escape characters. However make sure you deselect the "ignore text" option in the import dialog. This ignores lines of text to ease the import of data from other applications. Quotation marks are recommended around text if you wish to avoid ambiguity. Text is quoted according to the Python rules for text. Double or single quotation marks can be used, e.g. "A 'test'", 'A second "test"'. Quotes can be escaped by prefixing them with a backslash, e.g. "A new \"test\"". If the data are generated from a Python script, the repr function provides the text in a suitable form. Dates and times are also supported with the syntax "dataset(date)". Dates must be in ISO format YYYY-MM-DD. Times are in 24 hour format hh:mm:ss.ss. Dates with times are written YYYY-MM-DDThh:mm:ss.ss (this is a standard ISO format, see http://www.w3.org/TR/NOTE-datetime). Dates are stored within Veusz as a number which is the number of seconds since the start of January 1st 2009. Veusz also supports dates and times in the local format, though take note that the same file and data may not work on a system in a different location. Data may be optionally split into "blocks" of data separated by blank lines (or the word "no" on a line, for obscure reasons). The name of each read in dataset has an underscore and the number of the block (starting from 1) added. This is specified by clicking the blocks checkbox in the import dialog, or by using the useblocks=True option on the ImportFile or ImportString commands. Instead of specifying the descriptor in the import dialog, the descriptor can be placed in the data file using a descriptor statement on a separate line, consisting of "descriptor" followed by the descriptor. Multiple descriptors can be placed in a single file, for example: # here is one section descriptor x,+- y,+,- 1 0.5 2 0.1 -0.1 2 0.3 4 0.2 -0.1 # my next block descriptor alpha beta gamma 1 2 3 4 5 6 7 8 9 # etc... If data are imported from a file, Veusz will normally save the data in its saved document format. If the data are changing, quite often one wants to reread the data from the input file. This can be achieved using the "linked=True" option on the ImportFile command, or by clicking the "Link" checkbox in the import dialog. __________________________________________________________ Reading CSV files CVS (comma separated variable) files are often written from other programs, such as spreadsheets, including Excel and Gnumeric. Veusz supports reading from these files. In the import dialog choose "CSV", then choose a filename to import from. In the CSV file the user should place the data in either rows or columns. Veusz will use a name above a column or to the left of a row to specify what the dataset name should be. The user can use new names further down in columns or right in rows to specify a different dataset name. Names do not have to be used, and Veusz will assign default "col" and "row" names if not given. You can also specify a prefix which is prepended to each dataset name read from the file. To specify symmetric errors for a column, put "+-" as the dataset name in the next column or row. Asymmetric errors can be stated with "+" and "-" in the columns. The data can be linked with the CSV file so that it can be updated when the file changes. See the example CSV import for details. The data type in CSV files are automatically detected unless specified. The data type can be given in brackets after the column name, e.g. "name (text)", where the data type is "date", "numeric" or "text". Explicit data types are needed if the data look like a different data type (e.g. a text item of "1.23"). The date format in CSV files can be specified in the import dialog box - see the examples given. In addition CSV files support numbers in European format (e.g. 2,34 rather than 2.34), depending on the setting in the dialog box. __________________________________________________________ Reading FITS files 1D or 2D data can be read from FITS files. 1D data, with optional errors bars, can be read from table extensions, and 2D data from image or primary extensions. __________________________________________________________ Reading other data formats As mentioned above, a user may write some Python code to read a data file or set of data files. To write a plugin which is incorportated into Veusz, see http://barmag.net/veusz-wiki/ImportPlugins You can also include Python code in an input file to read data, which we describe here. Suppose an input file "in.dat" contains the following data: 1 2 2 4 3 9 4 16 Of course this data could be read using the ImportFile command. However, you could also read it with the following Veusz script (which could be saved to a file and loaded with execfile or Load. The script also places symmetric errors of 0.1 on the x dataset. x = [] y = [] for line in open("in.dat"): parts = [float(i) for i in line.split()] x.append(parts[0]) y.append(parts[1]) SetData('x', x, symerr=0.1) SetData('y', y) __________________________________________________________ Manipulating datasets Imported datasets can easily be modified in the Edit data dialog box, by clicking on a value and entering a new one. What is probably more interesting is using the Create dialog box to make new datasets from scratch or based on other datasets. New datasets can be made by entering a name, and choosing whether to make a dataset consisting of a single value or over a range, from expressions based on a parametric expression, or from expressions based on existing datasets. __________________________________________________________ Using dataset plugins Dataset plugins can be used to perform arbitrary manipulation of datasets. Veusz includes several plugins for mathematical operation of data and other dataset manipulations, such as concatenation or splitting. If you wish to write your own plugins look at http://barmag.net/veusz-wiki/DatasetPlugins. __________________________________________________________ Using expressions to create new datasets For instance, if the user has already imported dataset d, then they can create d2 which consists of d**2. Expressions are in Python numpy syntax and can include the usual mathematical functions. [createdataset.png] Expressions for error bars can also be given. By appending _data, _serr, _perr or _nerr to the name of the dataset in the expression, the user can base their expression on particular parts of the given dataset (the main data, symmetric errors, positive errors or negative errors). Otherwise the program uses the same parts as is currently being specified. If a dataset name contains non alphanumeric characters, its name should be quoted in the expression in back-tick characters, e.g. `length (cm)`*2. __________________________________________________________ Linking datasets to expressions A particularly useful feature is to be able to link a dataset to an expression, so if the expression changes the dataset changes with it, like in a spreadsheet. __________________________________________________________ Splitting data Data can also be chopped in this method, for example using the expression x[10:20], which makes a dataset based on the 11th to 20th item in the x dataset (the ranges are Python syntax, and are zero-based). Negative indices count backwards from the end of the dataset. Data can be skipped using expressions such as data[::2], which skips every other element __________________________________________________________ Defining new constants or functions User defined constants or functions can be defined in the "Custom definitions" dialog box under the edit menu. Functions can also be imported from external python modules. [customdefinition.png] Custom definitions are defined on a per-document basis, but can be saved or loaded into a file. A default custom definitions file can be set in the preferences dialog box. __________________________________________________________ Dataset plugins In addition to creating datasets based on expressions, a variety of dataset plugins exist, which make certain operations on datasets much more convenient. See the Data, Operations menu for a list of the default plugins. The user can easily create new plugins. See http://barmag.net/veusz-wiki/DatasetPlugins for details. __________________________________________________________ Command line interface Introduction An alternative way to control Veusz is via its command line interface. As Veusz is a a Python application it uses Python as its scripting language. Therefore you can freely mix Veusz and Python commands on the command line. Veusz can also read in Python scripts from files (see the Load command). When commands are entered in the command prompt in the Veusz window, Veusz supports a simplified command syntax, where brackets following commands names, and commas, can replaced by spaces in Veusz commands (not Python commands). For example, Add('graph', name='foo'), may be entered as Add 'graph' name='foo'. The numpy package is already imported into the command line interface (as "*"), so you do not need to import it first. The command prompt supports history (use the up and down cursor keys to recall previous commands). Most of the commands listed below can be used in the in-program command line interface, using the embedding interface or using veusz_listen. Commands specific to particular modes are documented as such. Veusz also includes a new object-oriented version of the interface, which is documented at http://barmag.net/veusz-wiki/EmbeddingPython. __________________________________________________________ Commands We list the allowed set of commands below __________________________________________________________ Action Action('actionname', componentpath='.') Initiates the specified action on the widget (component) given the action name. Actions perform certain automated routines. These include "fit" on a fit widget, and "zeroMargins" on grids. __________________________________________________________ Add Add('widgettype', name='nameforwidget', autoadd=True, optionalargs) The Add command adds a graph into the current widget (See the To command to change the current position). The first argument is the type of widget to add. These include "graph", "page", "axis", "xy" and "grid". name is the name of the new widget (if not given, it will be generated from the type of the widget plus a number). The autoadd parameter if set, constructs the default sub-widgets this widget has (for example, axes in a graph). Optionally, default values for the graph settings may be given, for example Add('axis', name='y', direction='vertical'). Subsettings may be set by using double underscores, for example Add('xy', MarkerFill__color='red', ErrorBarLine__hide=True). Returns: Name of widget added. __________________________________________________________ AddCustom AddCustom(type, name, value) Add a custom definition for evaluation of expressions. This can define a constant (can be in terms of other constants), a function of 1 or more variables, or a function imported from an external python module. ctype is "constant", "function" or "import". name is name of constant, or "function(x, y, ...)" or module name. val is definition for constant or function (both are _strings_), or is a list of symbols for a module (comma separated items in a string). If mode is 'appendalways', the custom value is appended to the end of the list even if there is one with the same name. If mode is 'replace', it replaces any existing definition in the same place in the list or is appended otherwise. If mode is 'append', then an existing definition is deleted, and the new one appended to the end. __________________________________________________________ AddImportPath AddImportPath(directory) Add a directory to the list of directories to try to import data from. __________________________________________________________ CloneWidget CloneWidget(widget, newparent, newname=None) Clone the widget given, placing the copy in newparent and the name given. newname is an optional new name to give it Returns new widget path. __________________________________________________________ Close Close() Closes the plotwindow. This is only supported in embedded mode. __________________________________________________________ CreateHistogram CreateHistogram(inexpr, outbinsds, outvalsds, binparams=None, binmanual=None, method='counts', cumulative = 'none', errors=False) Histogram an input expression. inexpr is input expression. outbinds is the name of the dataset to create giving bin positions. outvalsds is name of dataset for bin values. binparams is None or (numbins, minval, maxval, islogbins). binmanual is None or a list of bin values. method is 'counts', 'density', or 'fractions'. cumulative is to calculate cumulative distributions which is 'none', 'smalltolarge' or 'largetosmall'. errors is to calculate Poisson error bars. __________________________________________________________ DatasetPlugin DatasetPlugin(pluginname, fields, datasetnames={})> Use a dataset plugin. pluginname: name of plugin to use fields: dict of input values to plugin datasetnames: dict mapping old names to new names of datasets if they are renamed. The new name None means dataset is deleted __________________________________________________________ EnableToolbar EnableToolbar(enable=True) Enable/disable the zooming toolbar in the plotwindow. This command is only supported in embedded mode or from veusz_listen. __________________________________________________________ Export Export(filename, color=True, page=0 dpi=100, antialias=True, quality=85, backcolor='#ffffff00', pdfdpi=150, svgtextastext=False) Export the page given to the filename given. The filename must end with the correct extension to get the right sort of output file. Currrenly supported extensions are '.eps', '.pdf', '.svg', '.jpg', '.jpeg', '.bmp' and '.png'. If color is True, then the output is in colour, else greyscale. page is the page number of the document to export (starting from 0 for the first page!). dpi is the number of dots per inch for bitmap output files. antialias - antialiases output if True. quality is a quality parameter for jpeg output. backcolor is the background color for bitmap files, which is a name or a #RRGGBBAA value (red, green, blue, alpha). pdfdpi is the dpi to use when exporting EPS or PDF files. svgtextastext says whether to export SVG text as text, rather than curves. __________________________________________________________ ForceUpdate ForceUpdate() Force the window to be updated to reflect the current state of the document. Often used when periodic updates have been disabled (see SetUpdateInterval). This command is only supported in embedded mode or from veusz_listen. __________________________________________________________ Get Get('settingpath') Returns: The value of the setting given by the path. >>> Get('/page1/graph1/x/min') 'Auto' __________________________________________________________ GetChildren GetChildren(where='.') Returns: The names of the widgets which are children of the path given __________________________________________________________ GetClick GetClick() Waits for the user to click on a graph and returns the position of the click on appropriate axes. Command only works in embedded mode. Returns: A list containing tuples of the form (axispath, val) for each axis for which the click was in range. The value is the value on the axis for the click. __________________________________________________________ GetData GetData(name) Returns: For a 1D dataset, a tuple containing the dataset with the name given. The value is (data, symerr, negerr, poserr), with each a numpy array of the same size or None. data are the values of the dataset, symerr are the symmetric errors (if set), negerr and poserr and negative and positive asymmetric errors (if set). If a text dataset, return a list of text elements. If the dataset is a date-time dataset, return a list of Python datetime objects. If the dataset is a 2D dataset return the tuple (data, rangex, rangey), where data is a 2D numpy array and rangex/y are tuples giving the range of the x and y coordinates of the data. data = GetData('x') SetData('x', data[0]*0.1, *data[1:]) __________________________________________________________ GetDataType GetDataType(name) Get type of dataset with name given. Returns '1d' for a 1d dataset, '2d' for a 2d dataset, 'text' for a text dataset and 'datetime' for a datetime dataset. __________________________________________________________ GetDatasets GetDatasets() Returns: The names of the datasets in the current document. __________________________________________________________ GPL GPL() Print out the GNU Public Licence, which Veusz is licenced under. __________________________________________________________ ImportFile ImportFile('filename', 'descriptor', linked=False, prefix='', suffix='', encoding='utf_8') Imports data from a file. The arguments are the filename to load data from and the descriptor. The format of the descriptor is a list of variable names representing the columns of the data. For more information see Descriptors. If the linked parameter is set to True, if the document is saved, the data imported will not be saved with the document, but will be reread from the filename given the next time the document is opened. The linked parameter is optional. If prefix and/or suffix are set, then the prefix and suffix are added to each dataset name. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset. Changed in version 0.5: A tuple is returned rather than just the number of imported variables. __________________________________________________________ ImportFile2D ImportFile2D('filename', datasets, xrange=(a,b), yrange=(c,d), invertrows=True/False, invertcols=True/False, transpose=True/False, prefix='', suffix='', linked=False, encoding='utf8') Imports two-dimensional data from a file. The required arguments are the filename to load data from and the dataset name, or a list of names to use. filename is a string which contains the filename to use. datasets is either a string (for a single dataset), or a list of strings (for multiple datasets). The xrange parameter is a tuple which contains the range of the X-axis along the two-dimensional dataset, for example (-1., 1.) represents an inclusive range of -1 to 1. The yrange parameter specifies the range of the Y-axis similarly. If they are not specified, (0, N) is the default, where N is the number of datapoints along a particular axis. invertrows and invertcols if set to True, invert the rows and columns respectively after they are read by Veusz. transpose swaps the rows and columns. If prefix and/or suffix are set, they are prepended or appended to imported dataset names. If the linked parameter is True, then the datasets are linked to the imported file, and are not saved within a saved document. The file format this command accepts is a two-dimensional matrix of numbers, with the columns separated by spaces or tabs, and the rows separated by new lines. The X-coordinate is taken to be in the direction of the columns. Comments are supported (use "#", "!" or "%"), as are continuation characters ("\"). Separate datasets are deliminated by using blank lines. In addition to the matrix of numbers, the various optional parameters this command takes can also be specified in the data file. These commands should be given on separate lines before the matrix of numbers. They are: 1. xrange A B 2. yrange C D 3. invertrows 4. invertcols 5. transpose __________________________________________________________ ImportFileCSV ImportFileCSV('filename', readrows=False, dsprefix='', dssuffix='', linked=False, encoding='utf_8') This command imports data from a CSV format file. Data are read from the file using the dataset names given at the top of the files in columns. Please see the reading data section of this manual for more information. dsprefix is prepended to each dataset name and dssuffix is added (the prefix option is deprecated and also addeds an underscore to the dataset name). linked specifies whether the data will be linked to the file. __________________________________________________________ ImportFilePlugin ImportFilePlugin('pluginname', 'filename', **pluginargs, linked=False, encoding='utf_8', prefix='', suffix='') Import data from file using import plugin 'pluginname'. The arguments to the plugin are given, plus optionally a text encoding, and prefix and suffix to prepend or append to dataset names. __________________________________________________________ ImportFITSFile ImportFITSFile(datasetname, filename, hdu, datacol='A', symerrcol='B', poserrcol='C', negerrcol='D', linked=True/False) This command does a simple import from a FITS file. The FITS format is used within the astronomical community to transport binary data. For a more powerful FITS interface, you can use PyFITS within your scripts. The datasetname is the name of the dataset to import, the filename is the name of the FITS file to import from. The hdu parameter specifies the HDU to import data from (numerical or a name). If the HDU specified is a primary HDU or image extension, then a two-dimensional dataset is loaded from the file. The optional parameters (other than linked) are ignored. Any WCS information within the HDU are used to provide a suitable xrange and yrange. If the HDU is a table, then the datacol parameter must be specified (and optionally symerrcol, poserrcol and negerrcol). The dataset is read in from the named column in the table. Any errors are read in from the other specified columns. If linked is True, then the dataset is not saved with a saved document, but is reread from the data file each time the document is loaded. __________________________________________________________ ImportString ImportString('descriptor', 'data') Like, ImportFile, but loads the data from the specfied string rather than a file. This allows data to be easily embedded within a document. The data string is usually a multi-line Python string. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset. Changed in version 0.5: A tuple is returned rather than just the number of imported variables. ImportString('x y', ''' 1 2 2 5 3 10 ''') __________________________________________________________ ImportString2D ImportString2D(datasets, string) Imports a two-dimensional dataset from the string given. This is similar to the ImportFile2D command, with the same dataset format within the string. This command, however, does not currently take any optional parameters. The various controlling parameters can be set within the string. See the ImportFile2D section for details. __________________________________________________________ IsClosed IsClosed() Returns a boolean value telling the caller whether the plotting window has been closed. Note: this command is only supported in the embedding interface. __________________________________________________________ List List(where='.') List the widgets which are contained within the widget with the path given, the type of widgets, and a brief description. __________________________________________________________ Load Load('filename.vsz') Loads the veusz script file given. The script file can be any Python code. The code is executed using the Veusz interpreter. Note: this command is only supported at the command line and not in a script. Scripts may use the python execfile function instead. __________________________________________________________ MoveToPage MoveToPage(pagenum) Updates window to show the page number given of the document. Note: this command is only supported in the embedding interface or veusz_listen. __________________________________________________________ ReloadData ReloadData() Reload any datasets which have been linked to files. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset. __________________________________________________________ Rename Remove('widgetpath', 'newname') Rename the widget at the path given to a new name. This command does not move widgets. See To for a description of the path syntax. '.' can be used to select the current widget. __________________________________________________________ Remove Remove('widgetpath') Remove the widget selected using the path. See To for a description of the path syntax. __________________________________________________________ ResizeWindow ResizeWindow(width, height) Resizes window to be width by height pixels. Note: this command is only supported in the embedding interface or veusz_listen. __________________________________________________________ Save Save('filename.vsz') Save the current document under the filename given. __________________________________________________________ Set Set('settingpath', val) Set the setting given by the path to the value given. If the type of val is incorrect, an InvalidType exception is thrown. The path to the setting is the optional path to the widget the setting is contained within, an optional subsetting specifier, and the setting itself. Set('page1/graph1/x/min', -10.) __________________________________________________________ SetAntiAliasing SetAntiAliasing(on) Enable or disable anti aliasing in the plot window, replotting the image. __________________________________________________________ SetData SetData(name, val, symerr=None, negerr=None, poserr=None) Set the dataset name with the values given. If None is given for an item, it will be left blank. val is the actual data, symerr are the symmetric errors, negerr and poserr and the getative and positive asymmetric errors. The data can be given as lists or numpys. __________________________________________________________ SetDataExpression SetDataExpression(name, val, symerr=None, negerr=None, poserr=None, linked=False, parametric=None) Create a new dataset based on the expressions given. The expressions are Python syntax expressions based on existing datasets. If linked is True, the dataset will change as the datasets in the expressions change. Parametric can be set to a tuple of (minval, maxval, numitems). t in the expression will iterate from minval to maxval in numitems values. __________________________________________________________ SetDataRange SetDataRange(name, numsteps, val, symerr=None, negerr=None, poserr=None, linked=False) Set dataset to be a range of values with numsteps steps. val is tuple made up of (minimum value, maximum value). symerr, negerr and poserr are optional tuples for the error bars. If linked is True, the dataset can be saved in a document as a SetDataRange, otherwise it is expanded to the values which would make it up. __________________________________________________________ SetData2D SetData2D('name', val, xrange=(A,B), yrange=(C,D)) Creates a two-dimensional dataset with the name given. val is either a two-dimensional numpy array, or is a list of lists, with each list in the list representing a row. xrange and yrange are optional tuples giving the inclusive range of the X and Y coordinates of the data. __________________________________________________________ SetData2DExpression SetData2D('name', expr, linked=False) Create a 2D dataset based on expressions. name is the new dataset name expr is an expression which should return a 2D array linked specifies whether to permanently link the dataset to the expressions. __________________________________________________________ SetData2DExpressionXYZ SetData2DExpressionXYZ('name', 'xexpr', 'yexpr', 'zexpr', linked=False) Create a 2D dataset based on three 1D expressions. The x, y expressions need to evaluate to a grid of x, y points, with the z expression as the 2D value at that point. Currently only linear fixed grids are supported. This function is intended to convert calculations or measurements at fixed points into a 2D dataset easily. Missing values are filled with NaN. __________________________________________________________ SetData2DXYFunc SetData2DXYFunc('name', xstep, ystep, 'expr', linked=False) Construct a 2D dataset using a mathematical expression of "x" and "y". The x values are specified as (min, max, step) in xstep as a tuple, the y values similarly. If linked remains as False, then a real 2D dataset is created, where values can be modified and the data are stored in the saved file. __________________________________________________________ SetDataDateTime SetDataDateTime('name', vals) Creates a datetime dataset of name given. vals is a list of Python datetime objects. __________________________________________________________ SetDataText SetDataText(name, val) Set the text dataset name with the values given. val must be a type that can be converted into a Python list. SetDataText('mylabel', ['oranges', 'apples', 'pears', 'spam']) __________________________________________________________ SetToReference SetToReference(setting, refval) Set setting to match other setting refval always.. __________________________________________________________ SetUpdateInterval SetUpdateInterval(interval) Tells window to update every interval milliseconds at most. The value 0 disables updates until this function is called with a non-zero. The value -1 tells Veusz to update the window every time the document has changed. This will make things slow if repeated changes are made to the document. Disabling updates and using the ForceUpdate command will allow the user to control updates directly. Note: this command is only supported in the embedding interface or veusz_listen. __________________________________________________________ SetVerbose SetVerbose(v=True) If v is True, then extra information is printed out by commands. __________________________________________________________ StartSecondView StartSecondView(name = 'window title') In the embedding interface, this method will open a new Embedding interface onto the same document, returning the object. This new window provides a second view onto the document. It can, for instance, show a different page to the primary view. name is a window title for the new window. Note: this command is only supported in the embedding interface. __________________________________________________________ TagDatasets TagDatasets('tag', ['ds1', 'ds2'...]) Adds the tag to the list of datasets given.. __________________________________________________________ To To('widgetpath') The To command takes a path to a widget and moves to that widget. For example, this may be "/", the root widget, "graph1", "/page1/graph1/x", "../x". The syntax is designed to mimic Unix paths for files. "/" represents the base widget (where the pages reside), and ".." represents the widget next up the tree. __________________________________________________________ Quit Quit() Quits Veusz. This is only supported in veusz_listen. __________________________________________________________ WaitForClose WaitForClose() Wait until the plotting window has been closed. Note: this command is only supported in the embedding interface. __________________________________________________________ Zoom Zoom(factor) Sets the plot zoom factor, relative to a 1:1 scaling. factor can also be "width", "height" or "page", to zoom to the page width, height or page, respectively. This is only supported in embedded mode or veusz_listen. __________________________________________________________ Security With the 1.0 release of Veusz, input scripts and expressions are checked for possible security risks. Only a limited subset of Python functionality is allowed, or a dialog box is opened allowing the user to cancel the operation. Specifically you cannot import modules, get attributes of Python objects, access globals() or locals() or do any sort of file reading or manipulation. Basically anything which might break in Veusz or modify a system is not supported. In addition internal Veusz functions which can modify a system are also warned against, specifically Print(), Save() and Export(). If you are running your own scripts and do not want to be bothered by these dialogs, you can run veusz with the --unsafe-mode option. __________________________________________________________ Using Veusz from other programs Non-Qt Python programs Veusz supports being embedded within other Python programs. The calling program can open up any number of plot windows, and manipulate the graphs using the Veusz scripting commands, which are exposed as methods of graph objects. Using the embedding interface, a Python program can create multiple Veusz plot windows showing the same or different documents. The standard Veusz operations are supported with the addition of a few specific commands. The embedding interface runs Veusz in a second process, sending the commands over a pipe. Veusz must be installed in the PYTHONPATH for embedding to work. This can be done with the setup.py distutils script. An example embedding program is in examples/embedexample.py. An example Python program embedding Veusz is below: import time import numpy import veusz.embed as veusz g = veusz.Embedded('new window title') g.To( g.Add('page') ) g.To( g.Add('graph') ) g.SetData('x', numpy.arange(20)) g.SetData('y', numpy.arange(20)**2) g.Add('xy') g.Zoom(0.5) # wait 20 seconds time.sleep(20) win2 = veusz.Embedded('second window example') win2.To( win2.Add('page') ) win2.To( win2.Add('graph') ) win2.Add('function', function='x**2') win2.Set('x/label', 'An example axis \\emph{label}') time.sleep(20) g.Close() The supported commands are the same as in Commands, with the addition of: Close, EnableToolbar, MoveToPage, ResizeWindow, SetUpdateInterval, StartSecondView and Zoom. __________________________________________________________ PyQt4 programs There is no direct PyQt4 interface. The standard embedding interface should work, however. __________________________________________________________ Non Python programs Support for non Python programs is available in a limited form. External programs may execute the veusz_listen executable or veusz_listen.py Python module. Veusz will read its input from the standard input, and write output to standard output. This is a full Python execution environment, and supports all the scripting commands mentioned in Commands, a Quit() command, the EnableToolbar() and the Zoom(factor) command listed above. Only one window is supported at once, but many veusz_listen programs may be started. veusz_listen may be used from the shell command line by doing something like: veusz_listen < in.vsz where in.vsz contains: To(Add('page') ) To(Add('graph') ) SetData('x', arange(20)) SetData('y', arange(20)**2) Add('xy') Zoom(0.5) Export("foo.eps") Quit() A program may interface with Veusz in this way by using the popen C Unix function, which allows a program to be started having control of its standard input and output. Veusz can then be controlled by writing commands to an input pipe. __________________________________________________________ C, C++ and Fortran A callable library interface to Veusz is on my todo list for C, C++ and Fortran. Please tell me if you would be interested in this option. veusz-1.15/Documents/veusz_listen.10000644002344000001440000001206611734662403017362 0ustar jssusers00000000000000.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .ie \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . nr % 0 . rr F .\} .el \{\ . de IX .. .\} .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "VEUSZ_LISTEN 1" .TH VEUSZ_LISTEN 1 "2012-03-28" "1.15" "Veusz" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" veusz_listen \- command\-line interface to the Veusz plotting application. .SH "SYNOPSIS" .IX Header "SYNOPSIS" veusz_listen [\fIWindowTitle\fR]... .SH "DESCRIPTION" .IX Header "DESCRIPTION" \&\fBVeusz\fR is a scientific plotting and graphing package. \fBveusz_listen\fR provides a command line interface to its scripting interface. .PP \&\fBveusz_listen\fR opens a new window (with an optional window title) and listens to stdin. It executes Veusz scripting commands, writing any output to stdout. .PP \&\fBveusz_listen\fR is now deprecated. Please use \fBveusz \-\-listen\fR instead. .SH "SEE ALSO" .IX Header "SEE ALSO" \&\fIveusz\fR\|(1) .SH "BUGS" .IX Header "BUGS" Please report bugs at https://gna.org/bugs/?group=veusz .SH "AUTHORS" .IX Header "AUTHORS" \&\fBVeusz\fR was written by Jeremy Sanders . .PP This manual page was written by Jeremy Sanders . .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright (C) 2003\-2012 Jeremy Sanders . .PP This program is free software; you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. .PP On Debian GNU/Linux systems, the complete text of the \s-1GNU\s0 General Public License can be found in `/usr/share/common\-licenses/GPL'. veusz-1.15/Documents/veusz.10000644002344000001440000001527011734662403016004 0ustar jssusers00000000000000.\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is turned on, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .ie \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . nr % 0 . rr F .\} .el \{\ . de IX .. .\} .\" .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). .\" Fear. Run. Save yourself. No user-serviceable parts. . \" fudge factors for nroff and troff .if n \{\ . ds #H 0 . ds #V .8m . ds #F .3m . ds #[ \f1 . ds #] \fP .\} .if t \{\ . ds #H ((1u-(\\\\n(.fu%2u))*.13m) . ds #V .6m . ds #F 0 . ds #[ \& . ds #] \& .\} . \" simple accents for nroff and troff .if n \{\ . ds ' \& . ds ` \& . ds ^ \& . ds , \& . ds ~ ~ . ds / .\} .if t \{\ . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' .\} . \" troff and (daisy-wheel) nroff accents .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' .ds 8 \h'\*(#H'\(*b\h'-\*(#H' .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] .ds ae a\h'-(\w'a'u*4/10)'e .ds Ae A\h'-(\w'A'u*4/10)'E . \" corrections for vroff .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' . \" for low resolution devices (crt and lpr) .if \n(.H>23 .if \n(.V>19 \ \{\ . ds : e . ds 8 ss . ds o a . ds d- d\h'-1'\(ga . ds D- D\h'-1'\(hy . ds th \o'bp' . ds Th \o'LP' . ds ae ae . ds Ae AE .\} .rm #[ #] #H #V #F C .\" ======================================================================== .\" .IX Title "VEUSZ 1" .TH VEUSZ 1 "2012-03-28" "1.15" "Veusz" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Veusz \- a scientific plotting and graphing application. .SH "SYNOPSIS" .IX Header "SYNOPSIS" veusz [\fIoptions\fR] [\fIdocument.vsz\fR]... .SH "DESCRIPTION" .IX Header "DESCRIPTION" \&\fBVeusz\fR is a scientific plotting and graphing package. It is designed to create publication-ready output in a variety of different output formats. Graphs are built-up combining plotting widgets. Veusz has a \&\s-1GUI\s0 user interface (started with the \f(CW\*(C`veusz\*(C'\fR command), a Python module interface and a scripting interface. .PP If started without command line arguments, \fBVeusz\fR will open up with a new empty document. The program will otherwise open the listed documents. .SH "OPTIONS" .IX Header "OPTIONS" .IP "\fB\-\-unsafe\-mode\fR" 8 .IX Item "--unsafe-mode" Do not check opened scripts for the presence of unsafe Python commands. This allows you to create or open complete Python scripts with Veusz commands if they come from a trusted source. .IP "\fB\-\-listen\fR" 8 .IX Item "--listen" Read Veusz commands from stdin, executing them, then writing the results to stdout. This option is intended to replace the veusz_listen standalone program. .Sp In this mode Veusz does not read any input documents, but will use the first argument to the program as the window title, if given. .IP "\fB\-\-quiet\fR" 8 .IX Item "--quiet" If in listening mode, do not open a window before running commands, but execute them quietly. .IP "\fB\-\-export\fR=\fI\s-1FILE\s0\fR" 8 .IX Item "--export=FILE" Export the next Veusz document file on the command line to the graphics file \fI\s-1FILE\s0\fR. Supported file types include \s-1EPS\s0, \s-1PDF\s0, \s-1SVG\s0, \&\s-1PNG\s0, \s-1BMP\s0, \s-1JPG\s0 and \s-1XPM\s0. The extension of the output file is used to determine the output file format. There should be as many export options specified as input Veusz documents on the command line. .IP "\fB\-\-plugin\fR=\fI\s-1FILE\s0\fR" 8 .IX Item "--plugin=FILE" Loads the Veusz plugin \fI\s-1FILE\s0\fR when starting Veusz. This option provides a per-session alternative to adding the plugin in the preferences dialog box. .IP "\fB\-\-help\fR" 8 .IX Item "--help" Displays the options to the program and exits. .IP "\fB\-\-version\fR" 8 .IX Item "--version" Displays information about the currently installed version and exits. .SH "BUGS" .IX Header "BUGS" Please report bugs at https://gna.org/bugs/?group=veusz .SH "AUTHORS" .IX Header "AUTHORS" \&\fBVeusz\fR was written by Jeremy Sanders . .PP This manual page was written by Jeremy Sanders . .SH "COPYRIGHT" .IX Header "COPYRIGHT" Copyright (C) 2003\-2012 Jeremy Sanders . .PP This program is free software; you can redistribute it and/or modify it under the terms of the \s-1GNU\s0 General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. .PP On Debian GNU/Linux systems, the complete text of the \s-1GNU\s0 General Public License can be found in `/usr/share/common\-licenses/GPL'. veusz-1.15/Documents/manual.xml0000644002344000001440000023253711734662204016553 0ustar jssusers00000000000000 Veusz - a scientific plotting package Jeremy Sanders jeremy@jeremysanders.net 2011 This document is licensed under the GNU General Public License, version 2 or greater. Please see the file COPYING for details, or see . Introduction
    Veusz Veusz is a scientific plotting package. It was written as I was dissatisfied with existing plotting packages as they were either old and unmaintained (like pgplot and qdp, which does not support postscript fonts), not free (like IDL or Matlab), or had user interfaces I do not appreciate (like gnuplot). Veusz is designed to be easily extensible, and is written in Python, a high level language (in the future it may be necessary to write some parts in another language for speed, but this will be kept to an absolute minimum). It is also designed in an object oriented fashion, where a document is built up by a number of parts in a hierarchy. The advantage of using Python is that is is easy to allow the user to script Veusz using Python as a very powerful scripting language. Indeed, the saved file format is a Python script. The technologies behind Veusz include PyQt (a very easy to use Python interface to Qt, which is used for rendering and the graphical user interface, GUI) and numpy (a package for Python which makes the handling of large datasets easy). Veusz has two user interfaces: a graphical one which gives the user a relatively shallow learning curve, and a command line interface. The command line interface is also used by scripting and in the saved file format. Furthermore, Veusz can be embedded within other Python programs, with plots in their own windows, or it can be controlled from any other application.
    Terminology Here I define some terminology for future use.
    Widget A document and its graphs are built up from widgets. These widgets can often by placed within each other, depending on the type of the widget. A widget has children, those widgets placed within it, and its parent. The widgets have a number of different settings which modify their behaviour. These include the font to be used, the line thickness, and whether an axis is logarithmic. In addition they have actions, which perform some sort of activity on the widget or its children, like "fit" for a fit widget. Widgets are specified with a "path", like a file in Unix or Windows. These can be relative to the current widget (do not start with a slash), or absolute (do not start with a slash). Examples of paths include, "/page1/graph1/x", "x" and ".". The widget types include document - representing a complete document. A document can contain pages. In addition it contains a setting giving the page size for the document. page - representing a page in a document. One or more graphs can be placed on a page, or a grid. graph - defining an actual graph. A graph can be placed on a page or within a grid. Contained within the graph are its axes and plotters. A graph can be given a background fill and a border if required. It also has a margin, which specifies how far away from the edge of its parent widget to plot the body of the graph. A graph can contain several axes, at any position on the plot. In addition a graph can use axes defined in parent widgets, shared with other graphs. More than one graph can be placed within in a page. The margins can be adjusted so that they lie within or besides each other. grid - containing one or more graphs. A grid plots graphs in a gridlike fashion. You can specify the number of rows and columns, and the plots are automatically replotted in the chosen arrangement. A grid can contain graphs or axes. If an axis is placed in a grid, it can be shared by the graphs in the grid. axis - giving the scale for plotting data. An axis translates the coordinates of the data to the screen. An axis can be linear or logarithmic, it can have fixed endpoints, or can automatically get them from the plotted data. It also has settings for the axis labels and lines, tick labels, and major and minor tick marks. An axis may be "horizontal" or "vertical" and can appear anywhere on its parent graph or grid. If an axis appears within a grid, then it can be shared by all the graphs which are contained within the grid. plotters - types of widgets which plot data or add other things on a graph. There is no actual plotter widget which can be added, but several types of plotters listed below. Plotters typically take an axis as a setting, which is the axis used to plot the data on the graph (default x and y). function - a plotter which plots a function on the graph. Functions can be functions of x or y (parametric functions are not done yet!), and are defined in Python expression syntax, which is very close to most other languages. For example "3*x**2 + 2*x - 4". A number of functions are available (e.g. sin, cos, tan, exp, log...). Technically, Veusz imports the numpy package when evaluating, so numpy functions are available. As well as the function setting, also settable is the line type to plot the function, and the number of steps to evaluate the function when plotting. Filling is supported above/below/left/right of the function. xy - a plotter which plots scatter, line, or stepped plots. This versatile plotter takes an x and y dataset, and plots (optional) points, in a chosen marker and colour, connecting them with (optional) lines, and plotting (optional) error bars. An xy plotter can also plot a stepped line, allowing histograms to be plotted (note that it doesn't yet do the binning of the data). The settings for the xy widget are the various attibutes for the points, line and error bars, the datasets to plot, and the axes to plot on. The xy plotter can plot a label next to each dataset, which is either the same for each point or taken from a text dataset. If you wish to leave gaps in a plot, the input value "nan" can be specified in the numeric dataset. fit - fit a function to data. This plotter is a like the function plotter, but allows fitting of the function to data. This is achived by clicking on a "fit" button, or using the "fit" action of the widget. The fitter takes a function to fit containing the unknowns, e.g. "a*x**2 + b*x + c", and initial values for the variables (here a, b and c). It then fits the data (note that at the moment, the fit plotter fits all the data, not just the data that can be seen on the graph) by minimising the chi-squared. In order to fit properly, the y data (or x, if fitting as a function of x) must have a properly defined, preferably symmetric error. If there is none, Veusz assumes the same fractional error everywhere, or symmetrises asymmetric errors. Note that more work is required in this widget, as if a parameter is not well defined by the data, the matrix inversion in the fit will fail. In addition Veusz does not supply estimates for the errors or the final chi-squared in a machine readable way. If the fitting parameters vary significantly from 1, then it is worth "normalizing" them by adding in a factor in the fit equation to bring them to of the order of 1. bar - a bar chart which plots sets of data as horizontal or vertical bars. Multiple datasets are supported. In "grouped" mode the bars are placed side-by-side for each dataset. In "stacked" mode the bars are placed on top of each other (in the appropriate direction according to the sign of the dataset). Bars are placed on coordinates given, or in integer values from 1 upward if none are given. Error bars are plotted for each of the datasets. Different fill styles can be given for each dataset given. A separate key value can be given for each dataset. key - a box which describes the data plotted. If a key is added to a plot, the key looks for "key" settings of the other data plotted within a graph. If there any it builds up a box containing the symbol and line for the plotter, and the text in the "key" setting of the widget. This allows a key to be very easily added to a plot. The key may be placed in any of the corners of the plot, in the centre, or manually placed. Depending on the ordering of the widgets, the key will be placed behind or on top of the widget. The key can be filled and surrounded by a box, or not filled or surrounded. label - a text label places on a graph. The alignment can be adjusted and the font changed. The position of the label can be specified in fractional terms of the current graph, or using axis coordinates. rect, ellipse - these draw a rectangle or ellipse, respectively, of size and rotation given. These widgets can be placed directly on the page or on a graph. The centre can be given in axis coordinates or fractional coordinates. imagefile - draw an external graphs file on the graph or page, with size and rotation given. The centre can be given in axis coordinates or fractional coordinates. line - draw a line with optional arrowheads on the graph or page. One end can be given in axis coordinates or fractional coordinates. contour - plot contours of a 2D dataset on the graph. Contours are automatically calculated between the minimum and maximum values of the graph or chosen manually. The line style of the contours can be chosen individually and the region between contours can be filled with shading or color. 2D datasets currently consist of a regular grid of values between minimum and maximum positions in x and y. They can be constructed from three 1D datasets of x, y and z if they form a regular x, y grid. image - plot a 2D dataset as a colored image. Different color schemes can be chosen. The scaling between the values and the image can be specified as linear, logarithmic, square-root or square. polygon - plot x and y points from datasets as a polygon. The polygon can be placed directly on the page or within a graph. Coordinates are either plotted using the axis or as fractions of the width and height of the containing widget. boxplot - plot distribution of points in a dataset. polar - plot polar data or functions. This is a non-orthogonal plot and is placed directly on the page rather than in a graph. ternary - plot data of three variables which add up to 100 per cent.This is a non-orthogonal plot and is placed directly on the page rather than in a graph.
    Measurements Distances, widths and lengths in Veusz can be specified in a number of different ways. These include absolute distances specified in physical units, e.g. 1cm, 0.05m, 10mm, 5in and 10pt, and relative units, which are relative to the largest dimension of the page, including 5%, 1/20, 0.05.
    Settings The various settings of the widgets come in a number of types, including integers (e.g. 10), floats (e.g. 3.14), text ("hi there!"), distances (see above), options ("horizontal" or "vertical" for axes). Veusz performs type checks on these parameters. If they are in the wrong format the control to edit the setting will turn red. In the command line, a TypeError exception is thrown. In the GUI, the current page is replotted if a setting is changed when enter is pressed or the user moves to another setting. The settings are split up into formatting settings, controlling the appearance of the plot, or properties, controlling what is plotted and how it is plotted. Default settings, including the default font and line style, and the default settings for any graph widget, can be modified in the "Default styles" dialog box under the "Edit" menu. Default settings are set on a per-document basis, but can be saved into a separate file and loaded. A default default settings file can be given to use for new documents (set in the preferences dialog).
    <anchor id="TextFonts" />Text Veusz understands a limited set of LaTeX-like formatting for text. There are some differences (for example, "10^23" puts the 2 and 3 into superscript), but it is fairly similar. You should also leave out the dollar signs. Veusz supports superscripts ("^"), subscripts ("_"), brackets for grouping attributes are "{" and "}". Supported LaTeX symbols include: \AA, \Alpha, \Beta, \Chi, \Delta, \Epsilon, \Eta, \Gamma, \Iota, \Kappa, \Lambda, \Mu, \Nu, \Omega, \Omicron, \Phi, \Pi, \Psi, \Rho, \Sigma, \Tau, \Theta, \Upsilon, \Xi, \Zeta, \alpha, \approx, \ast, \asymp, \beta, \bowtie, \bullet, \cap, \chi, \circ, \cup, \dagger, \dashv, \ddagger, \deg, \delta, \diamond, \divide, \doteq, \downarrow, \epsilon, \equiv, \eta, \gamma, \ge, \gg, \in, \infty, \int, \iota, \kappa, \lambda, \le, \leftarrow, \lhd, \ll, \models, \mp, \mu, \neq, \ni, \nu, \odot, \omega, \omicron, \ominus, \oplus, \oslash, \otimes, \parallel, \perp, \phi, \pi, \pm, \prec, \preceq, \propto, \psi, \rhd, \rho, \rightarrow, \sigma, \sim, \simeq, \sqrt, \sqsubset, \sqsubseteq, \sqsupset, \sqsupseteq, \star, \stigma, \subset, \subseteq, \succ, \succeq, \supset, \supseteq, \tau, \theta, \times, \umid, \unlhd, \unrhd, \uparrow, \uplus, \upsilon, \vdash, \vee, \wedge, \xi, \zeta. Please request additional characters if they are required (and exist in the unicode character set). Special symbols can be included directly from a character map. Other LaTeX commands are supported. "\\" breaks a line. This can be used for simple tables. For example "{a\\b} {c\\d}" shows "a c" over "b d". The command "\frac{a}{b}" shows a vertical fraction a/b. Also supported are commands to change font. The command "\font{name}{text}" changes the font text is written in to name. This may be useful if a symbol is missing from the current font, e.g. "\font{symbol}{g}" should produce a gamma. You can increase, decrease, or set the size of the font with "\size{+2}{text}", "\size{-2}{text}", or "\size{20}{text}". Numbers are in points. Various font attributes can be changed: for example, "\italic{some italic text}" (or use "\textit" or "\emph"), "\bold{some bold text}" (or use "\textbf") and "\underline{some underlined text}". Example text could include "Area / \pi (10^{-23} cm^{-2})", or "\pi\bold{g}". Veusz plots these symbols with Qt's unicode support. If your current font does not contain these symbols then you may get messy results. If you find this is the case, I highly recommend you to down load Microsoft's Corefonts (see ).
    Axis numbers The way numbers are shown on axes is chosen automatically. For standard numerical axes, values are shown with the "%Vg" formatting (see below). For date axes, an appropriate date formatting is used so that the interval shown is correct. A format can be given for an axis in the axis number formatting panel can be given to explicitly choose a format. Some examples are given in the drop down axis menu. Hold the mouse over the example for detail. C-style number formatting is used with a few Veusz specific extensions. Text can be mixed with format specifiers, which start with a "%" sign. Examples of C-style formatting include: "%.2f" (decimal number with two decimal places, e.g. 2.01), "%.3e" (scientific formatting with three decimal places, e.g. 2.123e-02), "%g" (general formatting, switching between "%f" and "%e" as appropriate). See for details. Veusz extensions include "%Ve", which is like "%e" except it displays scientific notation as written, e.g. 1.2x10^23, rather than 1.2e+23. "%Vg" switches between standard numbers and Veusz scientific notation for large and small numbers. "%VE" using engineering SI suffixes to represent large or small numbers (e.g. 1000 is 1k). Veusz allows dates and times to be formatted using "%VDX" where "X" is one of the formatting characters for strftime (see for details). These include "a" for an abbreviated weekday name, "A" for full weekday name, "b" for abbreviated month name, "B" for full month name, "c" date and time representaiton, "d" day of month 01..31, "H" hour as 00..23, "I" hour as 01..12, "j" as day of year 001..366, "m" as month 01..12, "M" minute as 00..59, "p" AM/PM, "S" second 00..61, "U" week number of year 00..53 (Sunday as first day of week), "w" weekday as decimal number 0..6, "W" week number of year (Monday as first day of week), "x" date representation, "X" time representation, "y" year without century 00..99 and "Y" year. "%VDVS" is a special Veusz addon format which shows seconds and fractions of seconds (e.g. 12.2).
    Installation Please look at the Installation notes (INSTALL) for details on installing Veusz.
    The main window You should see the main window when you run Veusz (you can just type the veusz command in Unix). The Veusz window is split into several sections. At the top is the menu bar and tool bar. These work in the usual way to other applications. Sometimes options are disabled (greyed out) if they do not make sense to be used. If you hold your mouse over a button for a few seconds, you will usually get an explanation for what it does called a "tool tip". Below the main toolbar is a second toolbar for constructing the graph by adding widgets (on the left), and some editing buttons. The add widget buttons add the request widget to the currently selected widget in the selection window. The widgets are arranged in a tree-like structure. Below these toolbars and to the right is the plot window. This is where the current page of the current document is shown. You can adjust the size of the plot on the screen (the zoom factor) using the "View" menu or the zoom tool bar button (the magnifying glass). Initially you will not see a plot in the plot window, but you will see the Veusz logo. At the moment you cannot do much else with the window. In the future you will be able to click on items in the plot to modify them. To the left of the plot window is the selection window, and the properties and formatting windows. The properties window lets you edit various aspects of the selected widget (such as the minimum and maximum values on an axis). Changing these values should update the plot. The formatting lets you modify the appearance of the selected widget. There are a series of tabs for choosing what aspect to modify. The various windows can be "dragged" from the main window to "float" by themselves on the screen. To the bottom of the window is the console. This window is not shown by default, but can be enabled in the View menu. The console is a Veusz and Python command line console. To read about the commands available see Commands. As this is a Python console, you can enter mathematical expressions (e.g. "1+2.0*cos(pi/4)") here and they will be evaluated when you press Enter. The usual special functions and the operators are supported. You can also assign results to variables (e.g. "a=1+2") for use later. The console also supports command history like many Unix shells. Press the up and down cursor keys to browse through the history. Command line completion is not available yet! In recent versions there also exists a dataset browsing window, by default to the right of the screen. This window allows you to view the datasets currently loaded, their dimensions and type. Hovering a mouse over the size of the dataset will give you a preview of the data.
    My first plot After opening Veusz, on the left of the main window, you will see a Document, containing a Page, which contains a Graph with its axes. The Graph is selected in the selection window. The toolbar above adds a new widget to the selected widget. If a widget cannot be added to a selected widget it is disabled. On opening a new document Veusz automatically adds a new Page and Graph (with axes) to the document. You will see something like this: Select the x axis which has been added to the document (click on "x" in the selection window). In the properties window you will see a variety of different properties you can modify. For instance you can enter a label for the axis by writing "Area (cm^{2})" in the box next to label and pressing enter. Veusz supports text in LaTeX-like form (without the dollar signs). Other important parameters is the "log" switch which switches between linear and logarithmic axes, and "min" and "max" which allow the user to specify the minimum and maximum values on the axes. The formatting dialog lets you edit various aspects of the graph appearance. For instance the "Line" tab allows you to edit the line of the axis. Click on "Line", then you can then modify its colour. Enter "green" instead of "black" and press enter. Try making the axis label bold. Now you can try plotting a function on the graph. If the graph, or its children are selected, you will then be able to click the "function" button at the top (a red curve on a graph). You will see a straight line (y=x) added to the plot. If you select "function1", you will be able to edit the functional form plotted and the style of its line. Change the function to "x**2" (x-squared). We will now try plotting data on the graph. Go to your favourite text editor and save the following data as test.dat: 1 0.1 -0.12 1.1 0.1 2.05 0.12 -0.14 4.08 0.12 2.98 0.08 -0.1 2.9 0.11 4.02 0.04 -0.1 15.3 1.0 The first three columns are the x data to plot plus its asymmetric errors. The final two columns are the y data plus its symmetric errors. In Veusz, go to the "Data" menu and select "Import". Type the filename into the filename box, or use the "Browse..." button to search for the file. You will see a preview of the data pop up in the box below. Enter "x,+,- y,+-" into the descriptors edit box (note that commas and spaces in the descriptor are almost interchangeable in Veusz 1.6 or newer). This describes the format of the data which describes dataset "x" plus its asymmetric errors, and "y" with its symmetric errors. If you now click "Import", you will see it has imported datasets "x" and "y". To plot the data you should now click on "graph1" in the tree window. You are now able to click on the "xy" button (which looks like points plotted on a graph). You will see your data plotted on the graph. Veusz plots datasets "x" and "y" by default, but you can change these in the properties of the "xy" plotter. You are able to choose from a variety of markers to plot. You can remove the plot line by choosing the "Plot Line" subsetting, and clicking on the "hide" option. You can change the colour of the marker by going to the "Marker Fill" subsetting, and entering a new colour (e.g. red), into the colour property.
    Reading data Currently Veusz supports reading data from a text file, FITS format files, CSV files, QDP files, binary files and NPY/NPZ files. Reading data is supported using the "Data, Import" dialog, or using the ImportFile and ImportString commands which read data from files or an existing Python string (allowing data to be embedded in a Python script). In addition, the user can load or write plugins in Python which load data into Veusz in an arbitrary format. At the moment QDP, binary and NPY/NPZ files are supported with this method. CSV files are intuitive to use and are described below. In addition data may also be read in from FITS files if the PyFITS Python module is installed. FITS is a widespread astronomical data format. FITS files are read using the FITS tab on the import dialog or using the ImportFITSFile command. Two dimensional data are also supported using the 2D tab on the Import dialog box, ImportFile2D and ImportString2D commands.
    <anchor id="Descriptors" />Descriptors The "Data, Import" dialog box, ImportFile and ImportString commands use a "Descriptor", or list of dataset names, to describe how the data are formatted in the import file. The descriptor at its simplest is a list of the names of the datasets to import (which are columns in the file). Additionally modifiers are added if error bars are also read in. Examples of descriptors are below: x y two columns are present in the file, they will be read in as datasets "x" and "y". x,+- y,+,- or x +- y + - two datasets are in the file. Dataset "x" consists of the first two columns. The first column are the values and the second are the symmetric errors. "y" consists of three columns (note the comma between + and -). The first column are the values, the second positive asymmetric errors, and the third negative asymmetric errors. Suppose the input file contains: 1.0 0.3 2 0.1 -0.2 1.5 0.2 2.3 2e-2 -0.3E0 2.19 0.02 5 0.1 -0.1 Then x will contain "1+-0.3", "1.5+-0.2", "2.19+-0.02". y will contain "2 +0.1 -0.2", "2.3 +0.02 -0.3", "5 +0.1 -0.1". x[1:2] y[:] the first column is the data "x_1", the second "x_2". Subsequent columns are read as "y[1]" to "y[n]". y[:]+- read each pair of columns as a dataset and its symmetric error, calling them "y[1]" to "y[n]". foo,,+- read the first column as the foo dataset, skip a column, and read the third column as its symmetric error. The dataset names given here x and y, are just representative. Dataset names can contain virtually any character, even unicode characters. If the name contains non alpha-numeric characters (characters outside of A-Z, a-z and 0-9), then the dataset name should be contained within back-tick characters. An example descriptor is `length data (m)`,+- `speed (mps)`,+,-, for two datasets with spaces and brackets in their names. The special names +-, + or - specify that the datasets before are followed by columns containing symmetric, positive or negative errors. The signs on positive or negative errors are automatically set to be correct. If a descriptor is left blank, Veusz will automatically create dataset names. If the prefix and suffix settings are blank, they are assigned names col1, col2, etc. If prefix and suffix are not blank, the datasets are called prefix+number+suffix. When reading in data, Veusz treats any whitespace as separating columns. The columns do not actually need to be in columns! Furthermore a "\" symbol can be placed at the end of a line to mark a continuation. Veusz will read the next line as if it were placed at the end of the current line. In addition comments and blank lines are ignored. Comments start with a "#", ";", "!" or "%", and continue until the end of the line. The special value "nan" can be used to specify a break in a dataset. Veusz supports reading in other types of data. The type of data can be added in round brackets after the name. Veusz will try to guess the type of data based on the first value, so you should specify it if there is any form of ambiguity (e.g. is 3 text or a number). Supported types are numbers (use numeric in brackets) and text (use text in brackets). An example descriptor would be "x(numeric) +- y(numeric) + - label(text)" for an x dataset followed by its symmetric errors, a y dataset followed by two columns of asymmetric errors, and a final column of text for the label dataset. A text column does not need quotation unless it contains space characters or escape characters. However make sure you deselect the "ignore text" option in the import dialog. This ignores lines of text to ease the import of data from other applications. Quotation marks are recommended around text if you wish to avoid ambiguity. Text is quoted according to the Python rules for text. Double or single quotation marks can be used, e.g. "A 'test'", 'A second "test"'. Quotes can be escaped by prefixing them with a backslash, e.g. "A new \"test\"". If the data are generated from a Python script, the repr function provides the text in a suitable form. Dates and times are also supported with the syntax "dataset(date)". Dates must be in ISO format YYYY-MM-DD. Times are in 24 hour format hh:mm:ss.ss. Dates with times are written YYYY-MM-DDThh:mm:ss.ss (this is a standard ISO format, see ). Dates are stored within Veusz as a number which is the number of seconds since the start of January 1st 2009. Veusz also supports dates and times in the local format, though take note that the same file and data may not work on a system in a different location. Data may be optionally split into "blocks" of data separated by blank lines (or the word "no" on a line, for obscure reasons). The name of each read in dataset has an underscore and the number of the block (starting from 1) added. This is specified by clicking the blocks checkbox in the import dialog, or by using the useblocks=True option on the ImportFile or ImportString commands. Instead of specifying the descriptor in the import dialog, the descriptor can be placed in the data file using a descriptor statement on a separate line, consisting of "descriptor" followed by the descriptor. Multiple descriptors can be placed in a single file, for example: # here is one section descriptor x,+- y,+,- 1 0.5 2 0.1 -0.1 2 0.3 4 0.2 -0.1 # my next block descriptor alpha beta gamma 1 2 3 4 5 6 7 8 9 # etc... If data are imported from a file, Veusz will normally save the data in its saved document format. If the data are changing, quite often one wants to reread the data from the input file. This can be achieved using the "linked=True" option on the ImportFile command, or by clicking the "Link" checkbox in the import dialog.
    Reading CSV files CVS (comma separated variable) files are often written from other programs, such as spreadsheets, including Excel and Gnumeric. Veusz supports reading from these files. In the import dialog choose "CSV", then choose a filename to import from. In the CSV file the user should place the data in either rows or columns. Veusz will use a name above a column or to the left of a row to specify what the dataset name should be. The user can use new names further down in columns or right in rows to specify a different dataset name. Names do not have to be used, and Veusz will assign default "col" and "row" names if not given. You can also specify a prefix which is prepended to each dataset name read from the file. To specify symmetric errors for a column, put "+-" as the dataset name in the next column or row. Asymmetric errors can be stated with "+" and "-" in the columns. The data can be linked with the CSV file so that it can be updated when the file changes. See the example CSV import for details. The data type in CSV files are automatically detected unless specified. The data type can be given in brackets after the column name, e.g. "name (text)", where the data type is "date", "numeric" or "text". Explicit data types are needed if the data look like a different data type (e.g. a text item of "1.23"). The date format in CSV files can be specified in the import dialog box - see the examples given. In addition CSV files support numbers in European format (e.g. 2,34 rather than 2.34), depending on the setting in the dialog box.
    Reading FITS files 1D or 2D data can be read from FITS files. 1D data, with optional errors bars, can be read from table extensions, and 2D data from image or primary extensions.
    Reading other data formats As mentioned above, a user may write some Python code to read a data file or set of data files. To write a plugin which is incorportated into Veusz, see You can also include Python code in an input file to read data, which we describe here. Suppose an input file "in.dat" contains the following data: 1 2 2 4 3 9 4 16 Of course this data could be read using the ImportFile command. However, you could also read it with the following Veusz script (which could be saved to a file and loaded with execfile or Load. The script also places symmetric errors of 0.1 on the x dataset. x = [] y = [] for line in open("in.dat"): parts = [float(i) for i in line.split()] x.append(parts[0]) y.append(parts[1]) SetData('x', x, symerr=0.1) SetData('y', y)
    Manipulating datasets Imported datasets can easily be modified in the Edit data dialog box, by clicking on a value and entering a new one. What is probably more interesting is using the Create dialog box to make new datasets from scratch or based on other datasets. New datasets can be made by entering a name, and choosing whether to make a dataset consisting of a single value or over a range, from expressions based on a parametric expression, or from expressions based on existing datasets.
    Using dataset plugins Dataset plugins can be used to perform arbitrary manipulation of datasets. Veusz includes several plugins for mathematical operation of data and other dataset manipulations, such as concatenation or splitting. If you wish to write your own plugins look at .
    Using expressions to create new datasets For instance, if the user has already imported dataset d, then they can create d2 which consists of d**2. Expressions are in Python numpy syntax and can include the usual mathematical functions. Expressions for error bars can also be given. By appending _data, _serr, _perr or _nerr to the name of the dataset in the expression, the user can base their expression on particular parts of the given dataset (the main data, symmetric errors, positive errors or negative errors). Otherwise the program uses the same parts as is currently being specified. If a dataset name contains non alphanumeric characters, its name should be quoted in the expression in back-tick characters, e.g. `length (cm)`*2.
    Linking datasets to expressions A particularly useful feature is to be able to link a dataset to an expression, so if the expression changes the dataset changes with it, like in a spreadsheet.
    Splitting data Data can also be chopped in this method, for example using the expression x[10:20], which makes a dataset based on the 11th to 20th item in the x dataset (the ranges are Python syntax, and are zero-based). Negative indices count backwards from the end of the dataset. Data can be skipped using expressions such as data[::2], which skips every other element
    Defining new constants or functions User defined constants or functions can be defined in the "Custom definitions" dialog box under the edit menu. Functions can also be imported from external python modules. Custom definitions are defined on a per-document basis, but can be saved or loaded into a file. A default custom definitions file can be set in the preferences dialog box.
    Dataset plugins In addition to creating datasets based on expressions, a variety of dataset plugins exist, which make certain operations on datasets much more convenient. See the Data, Operations menu for a list of the default plugins. The user can easily create new plugins. See for details.
    <anchor id="Commands" />Command line interface
    Introduction An alternative way to control Veusz is via its command line interface. As Veusz is a a Python application it uses Python as its scripting language. Therefore you can freely mix Veusz and Python commands on the command line. Veusz can also read in Python scripts from files (see the Load command). When commands are entered in the command prompt in the Veusz window, Veusz supports a simplified command syntax, where brackets following commands names, and commas, can replaced by spaces in Veusz commands (not Python commands). For example, Add('graph', name='foo'), may be entered as Add 'graph' name='foo'. The numpy package is already imported into the command line interface (as "*"), so you do not need to import it first. The command prompt supports history (use the up and down cursor keys to recall previous commands). Most of the commands listed below can be used in the in-program command line interface, using the embedding interface or using veusz_listen. Commands specific to particular modes are documented as such. Veusz also includes a new object-oriented version of the interface, which is documented at .
    Commands We list the allowed set of commands below
    <anchor id="Command.Action" />Action Action('actionname', componentpath='.') Initiates the specified action on the widget (component) given the action name. Actions perform certain automated routines. These include "fit" on a fit widget, and "zeroMargins" on grids.
    <anchor id="Command.Add" />Add Add('widgettype', name='nameforwidget', autoadd=True, optionalargs) The Add command adds a graph into the current widget (See the To command to change the current position). The first argument is the type of widget to add. These include "graph", "page", "axis", "xy" and "grid". name is the name of the new widget (if not given, it will be generated from the type of the widget plus a number). The autoadd parameter if set, constructs the default sub-widgets this widget has (for example, axes in a graph). Optionally, default values for the graph settings may be given, for example Add('axis', name='y', direction='vertical'). Subsettings may be set by using double underscores, for example Add('xy', MarkerFill__color='red', ErrorBarLine__hide=True). Returns: Name of widget added.
    <anchor id="Command.AddCustom" />AddCustom AddCustom(type, name, value) Add a custom definition for evaluation of expressions. This can define a constant (can be in terms of other constants), a function of 1 or more variables, or a function imported from an external python module. ctype is "constant", "function" or "import". name is name of constant, or "function(x, y, ...)" or module name. val is definition for constant or function (both are _strings_), or is a list of symbols for a module (comma separated items in a string). If mode is 'appendalways', the custom value is appended to the end of the list even if there is one with the same name. If mode is 'replace', it replaces any existing definition in the same place in the list or is appended otherwise. If mode is 'append', then an existing definition is deleted, and the new one appended to the end.
    <anchor id="Command.AddImportPath" />AddImportPath AddImportPath(directory) Add a directory to the list of directories to try to import data from.
    <anchor id="Command.CloneWidget" />CloneWidget CloneWidget(widget, newparent, newname=None) Clone the widget given, placing the copy in newparent and the name given. newname is an optional new name to give it Returns new widget path.
    <anchor id="Command.Close" />Close Close() Closes the plotwindow. This is only supported in embedded mode.
    <anchor id="Command.CreateHistogram" />CreateHistogram CreateHistogram(inexpr, outbinsds, outvalsds, binparams=None, binmanual=None, method='counts', cumulative = 'none', errors=False) Histogram an input expression. inexpr is input expression. outbinds is the name of the dataset to create giving bin positions. outvalsds is name of dataset for bin values. binparams is None or (numbins, minval, maxval, islogbins). binmanual is None or a list of bin values. method is 'counts', 'density', or 'fractions'. cumulative is to calculate cumulative distributions which is 'none', 'smalltolarge' or 'largetosmall'. errors is to calculate Poisson error bars.
    <anchor id="Command.DatasetPlugin" />DatasetPlugin DatasetPlugin(pluginname, fields, datasetnames={})> Use a dataset plugin. pluginname: name of plugin to use fields: dict of input values to plugin datasetnames: dict mapping old names to new names of datasets if they are renamed. The new name None means dataset is deleted
    <anchor id="Command.EnableToolbar" />EnableToolbar EnableToolbar(enable=True) Enable/disable the zooming toolbar in the plotwindow. This command is only supported in embedded mode or from veusz_listen.
    <anchor id="Command.Export" />Export Export(filename, color=True, page=0 dpi=100, antialias=True, quality=85, backcolor='#ffffff00', pdfdpi=150, svgtextastext=False) Export the page given to the filename given. The filename must end with the correct extension to get the right sort of output file. Currrenly supported extensions are '.eps', '.pdf', '.svg', '.jpg', '.jpeg', '.bmp' and '.png'. If color is True, then the output is in colour, else greyscale. page is the page number of the document to export (starting from 0 for the first page!). dpi is the number of dots per inch for bitmap output files. antialias - antialiases output if True. quality is a quality parameter for jpeg output. backcolor is the background color for bitmap files, which is a name or a #RRGGBBAA value (red, green, blue, alpha). pdfdpi is the dpi to use when exporting EPS or PDF files. svgtextastext says whether to export SVG text as text, rather than curves.
    <anchor id="Command.ForceUpdate" />ForceUpdate ForceUpdate() Force the window to be updated to reflect the current state of the document. Often used when periodic updates have been disabled (see SetUpdateInterval). This command is only supported in embedded mode or from veusz_listen.
    <anchor id="Command.Get" />Get Get('settingpath') Returns: The value of the setting given by the path. >>> Get('/page1/graph1/x/min') 'Auto'
    <anchor id="Command.GetChildren" />GetChildren GetChildren(where='.') Returns: The names of the widgets which are children of the path given
    <anchor id="Command.GetClick" />GetClick GetClick() Waits for the user to click on a graph and returns the position of the click on appropriate axes. Command only works in embedded mode. Returns: A list containing tuples of the form (axispath, val) for each axis for which the click was in range. The value is the value on the axis for the click.
    <anchor id="Command.GetData" />GetData GetData(name) Returns: For a 1D dataset, a tuple containing the dataset with the name given. The value is (data, symerr, negerr, poserr), with each a numpy array of the same size or None. data are the values of the dataset, symerr are the symmetric errors (if set), negerr and poserr and negative and positive asymmetric errors (if set). If a text dataset, return a list of text elements. If the dataset is a date-time dataset, return a list of Python datetime objects. If the dataset is a 2D dataset return the tuple (data, rangex, rangey), where data is a 2D numpy array and rangex/y are tuples giving the range of the x and y coordinates of the data. data = GetData('x') SetData('x', data[0]*0.1, *data[1:])
    <anchor id="Command.GetDataType" />GetDataType GetDataType(name) Get type of dataset with name given. Returns '1d' for a 1d dataset, '2d' for a 2d dataset, 'text' for a text dataset and 'datetime' for a datetime dataset.
    <anchor id="Command.GetDatasets" />GetDatasets GetDatasets() Returns: The names of the datasets in the current document.
    <anchor id="Command.GPL" />GPL GPL() Print out the GNU Public Licence, which Veusz is licenced under.
    <anchor id="Command.ImportFile" />ImportFile ImportFile('filename', 'descriptor', linked=False, prefix='', suffix='', encoding='utf_8') Imports data from a file. The arguments are the filename to load data from and the descriptor. The format of the descriptor is a list of variable names representing the columns of the data. For more information see Descriptors. If the linked parameter is set to True, if the document is saved, the data imported will not be saved with the document, but will be reread from the filename given the next time the document is opened. The linked parameter is optional. If prefix and/or suffix are set, then the prefix and suffix are added to each dataset name. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset. Changed in version 0.5: A tuple is returned rather than just the number of imported variables.
    <anchor id="Command.ImportFile2D" />ImportFile2D ImportFile2D('filename', datasets, xrange=(a,b), yrange=(c,d), invertrows=True/False, invertcols=True/False, transpose=True/False, prefix='', suffix='', linked=False, encoding='utf8') Imports two-dimensional data from a file. The required arguments are the filename to load data from and the dataset name, or a list of names to use. filename is a string which contains the filename to use. datasets is either a string (for a single dataset), or a list of strings (for multiple datasets). The xrange parameter is a tuple which contains the range of the X-axis along the two-dimensional dataset, for example (-1., 1.) represents an inclusive range of -1 to 1. The yrange parameter specifies the range of the Y-axis similarly. If they are not specified, (0, N) is the default, where N is the number of datapoints along a particular axis. invertrows and invertcols if set to True, invert the rows and columns respectively after they are read by Veusz. transpose swaps the rows and columns. If prefix and/or suffix are set, they are prepended or appended to imported dataset names. If the linked parameter is True, then the datasets are linked to the imported file, and are not saved within a saved document. The file format this command accepts is a two-dimensional matrix of numbers, with the columns separated by spaces or tabs, and the rows separated by new lines. The X-coordinate is taken to be in the direction of the columns. Comments are supported (use "#", "!" or "%"), as are continuation characters ("\"). Separate datasets are deliminated by using blank lines. In addition to the matrix of numbers, the various optional parameters this command takes can also be specified in the data file. These commands should be given on separate lines before the matrix of numbers. They are: xrange A B yrange C D invertrows invertcols transpose
    <anchor id="Command.ImportFileCSV" />ImportFileCSV ImportFileCSV('filename', readrows=False, dsprefix='', dssuffix='', linked=False, encoding='utf_8') This command imports data from a CSV format file. Data are read from the file using the dataset names given at the top of the files in columns. Please see the reading data section of this manual for more information. dsprefix is prepended to each dataset name and dssuffix is added (the prefix option is deprecated and also addeds an underscore to the dataset name). linked specifies whether the data will be linked to the file.
    <anchor id="Command.ImportFilePlugin" />ImportFilePlugin ImportFilePlugin('pluginname', 'filename', **pluginargs, linked=False, encoding='utf_8', prefix='', suffix='') Import data from file using import plugin 'pluginname'. The arguments to the plugin are given, plus optionally a text encoding, and prefix and suffix to prepend or append to dataset names.
    <anchor id="Command.ImportFITSFile" /> ImportFITSFile ImportFITSFile(datasetname, filename, hdu, datacol='A', symerrcol='B', poserrcol='C', negerrcol='D', linked=True/False) This command does a simple import from a FITS file. The FITS format is used within the astronomical community to transport binary data. For a more powerful FITS interface, you can use PyFITS within your scripts. The datasetname is the name of the dataset to import, the filename is the name of the FITS file to import from. The hdu parameter specifies the HDU to import data from (numerical or a name). If the HDU specified is a primary HDU or image extension, then a two-dimensional dataset is loaded from the file. The optional parameters (other than linked) are ignored. Any WCS information within the HDU are used to provide a suitable xrange and yrange. If the HDU is a table, then the datacol parameter must be specified (and optionally symerrcol, poserrcol and negerrcol). The dataset is read in from the named column in the table. Any errors are read in from the other specified columns. If linked is True, then the dataset is not saved with a saved document, but is reread from the data file each time the document is loaded.
    <anchor id="Command.ImportString" />ImportString ImportString('descriptor', 'data') Like, ImportFile, but loads the data from the specfied string rather than a file. This allows data to be easily embedded within a document. The data string is usually a multi-line Python string. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset. Changed in version 0.5: A tuple is returned rather than just the number of imported variables. ImportString('x y', ''' 1 2 2 5 3 10 ''')
    <anchor id="Command.ImportString2D" />ImportString2D ImportString2D(datasets, string) Imports a two-dimensional dataset from the string given. This is similar to the ImportFile2D command, with the same dataset format within the string. This command, however, does not currently take any optional parameters. The various controlling parameters can be set within the string. See the ImportFile2D section for details.
    <anchor id="Command.IsClosed" />IsClosed IsClosed() Returns a boolean value telling the caller whether the plotting window has been closed. Note: this command is only supported in the embedding interface.
    <anchor id="Command.List" />List List(where='.') List the widgets which are contained within the widget with the path given, the type of widgets, and a brief description.
    <anchor id="Command.Load" />Load Load('filename.vsz') Loads the veusz script file given. The script file can be any Python code. The code is executed using the Veusz interpreter. Note: this command is only supported at the command line and not in a script. Scripts may use the python execfile function instead.
    <anchor id="Command.MoveToPage" />MoveToPage MoveToPage(pagenum) Updates window to show the page number given of the document. Note: this command is only supported in the embedding interface or veusz_listen.
    <anchor id="Command.ReloadData" />ReloadData ReloadData() Reload any datasets which have been linked to files. Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset.
    <anchor id="Command.Rename" />Rename Remove('widgetpath', 'newname') Rename the widget at the path given to a new name. This command does not move widgets. See To for a description of the path syntax. '.' can be used to select the current widget.
    <anchor id="Command.Remove" />Remove Remove('widgetpath') Remove the widget selected using the path. See To for a description of the path syntax.
    <anchor id="Command.ResizeWindow" />ResizeWindow ResizeWindow(width, height) Resizes window to be width by height pixels. Note: this command is only supported in the embedding interface or veusz_listen.
    <anchor id="Command.Save" />Save Save('filename.vsz') Save the current document under the filename given.
    <anchor id="Command.Set" />Set Set('settingpath', val) Set the setting given by the path to the value given. If the type of val is incorrect, an InvalidType exception is thrown. The path to the setting is the optional path to the widget the setting is contained within, an optional subsetting specifier, and the setting itself. Set('page1/graph1/x/min', -10.)
    <anchor id="Command.SetAntiAliasing" />SetAntiAliasing SetAntiAliasing(on) Enable or disable anti aliasing in the plot window, replotting the image.
    <anchor id="Command.SetData" />SetData SetData(name, val, symerr=None, negerr=None, poserr=None) Set the dataset name with the values given. If None is given for an item, it will be left blank. val is the actual data, symerr are the symmetric errors, negerr and poserr and the getative and positive asymmetric errors. The data can be given as lists or numpys.
    <anchor id="Command.SetDataExpression" />SetDataExpression SetDataExpression(name, val, symerr=None, negerr=None, poserr=None, linked=False, parametric=None) Create a new dataset based on the expressions given. The expressions are Python syntax expressions based on existing datasets. If linked is True, the dataset will change as the datasets in the expressions change. Parametric can be set to a tuple of (minval, maxval, numitems). t in the expression will iterate from minval to maxval in numitems values.
    <anchor id="Command.SetDataRange" />SetDataRange SetDataRange(name, numsteps, val, symerr=None, negerr=None, poserr=None, linked=False) Set dataset to be a range of values with numsteps steps. val is tuple made up of (minimum value, maximum value). symerr, negerr and poserr are optional tuples for the error bars. If linked is True, the dataset can be saved in a document as a SetDataRange, otherwise it is expanded to the values which would make it up.
    <anchor id="Command.SetData2D" />SetData2D SetData2D('name', val, xrange=(A,B), yrange=(C,D)) Creates a two-dimensional dataset with the name given. val is either a two-dimensional numpy array, or is a list of lists, with each list in the list representing a row. xrange and yrange are optional tuples giving the inclusive range of the X and Y coordinates of the data.
    <anchor id="Command.SetData2DExpression" />SetData2DExpression SetData2D('name', expr, linked=False) Create a 2D dataset based on expressions. name is the new dataset name expr is an expression which should return a 2D array linked specifies whether to permanently link the dataset to the expressions.
    <anchor id="Command.SetData2DExpressionXYZ" />SetData2DExpressionXYZ SetData2DExpressionXYZ('name', 'xexpr', 'yexpr', 'zexpr', linked=False) Create a 2D dataset based on three 1D expressions. The x, y expressions need to evaluate to a grid of x, y points, with the z expression as the 2D value at that point. Currently only linear fixed grids are supported. This function is intended to convert calculations or measurements at fixed points into a 2D dataset easily. Missing values are filled with NaN.
    <anchor id="Command.SetData2DXYFunc" />SetData2DXYFunc SetData2DXYFunc('name', xstep, ystep, 'expr', linked=False) Construct a 2D dataset using a mathematical expression of "x" and "y". The x values are specified as (min, max, step) in xstep as a tuple, the y values similarly. If linked remains as False, then a real 2D dataset is created, where values can be modified and the data are stored in the saved file.
    <anchor id="Command.SetDataDateTime" />SetDataDateTime SetDataDateTime('name', vals) Creates a datetime dataset of name given. vals is a list of Python datetime objects.
    <anchor id="Command.SetDataText" />SetDataText SetDataText(name, val) Set the text dataset name with the values given. val must be a type that can be converted into a Python list. SetDataText('mylabel', ['oranges', 'apples', 'pears', 'spam'])
    <anchor id="Command.SetToReference" />SetToReference SetToReference(setting, refval) Set setting to match other setting refval always..
    <anchor id="Command.SetUpdateInterval" />SetUpdateInterval SetUpdateInterval(interval) Tells window to update every interval milliseconds at most. The value 0 disables updates until this function is called with a non-zero. The value -1 tells Veusz to update the window every time the document has changed. This will make things slow if repeated changes are made to the document. Disabling updates and using the ForceUpdate command will allow the user to control updates directly. Note: this command is only supported in the embedding interface or veusz_listen.
    <anchor id="Command.SetVerbose" />SetVerbose SetVerbose(v=True) If v is True, then extra information is printed out by commands.
    <anchor id="Command.StartSecondView" />StartSecondView StartSecondView(name = 'window title') In the embedding interface, this method will open a new Embedding interface onto the same document, returning the object. This new window provides a second view onto the document. It can, for instance, show a different page to the primary view. name is a window title for the new window. Note: this command is only supported in the embedding interface.
    <anchor id="Command.TagDatasets" />TagDatasets TagDatasets('tag', ['ds1', 'ds2'...]) Adds the tag to the list of datasets given..
    <anchor id="Command.To" />To To('widgetpath') The To command takes a path to a widget and moves to that widget. For example, this may be "/", the root widget, "graph1", "/page1/graph1/x", "../x". The syntax is designed to mimic Unix paths for files. "/" represents the base widget (where the pages reside), and ".." represents the widget next up the tree.
    <anchor id="Command.Quit" />Quit Quit() Quits Veusz. This is only supported in veusz_listen.
    <anchor id="Command.WaitForClose" />WaitForClose WaitForClose() Wait until the plotting window has been closed. Note: this command is only supported in the embedding interface.
    <anchor id="Command.Zoom" />Zoom Zoom(factor) Sets the plot zoom factor, relative to a 1:1 scaling. factor can also be "width", "height" or "page", to zoom to the page width, height or page, respectively. This is only supported in embedded mode or veusz_listen.
    Security With the 1.0 release of Veusz, input scripts and expressions are checked for possible security risks. Only a limited subset of Python functionality is allowed, or a dialog box is opened allowing the user to cancel the operation. Specifically you cannot import modules, get attributes of Python objects, access globals() or locals() or do any sort of file reading or manipulation. Basically anything which might break in Veusz or modify a system is not supported. In addition internal Veusz functions which can modify a system are also warned against, specifically Print(), Save() and Export(). If you are running your own scripts and do not want to be bothered by these dialogs, you can run veusz with the --unsafe-mode option.
    Using Veusz from other programs
    Non-Qt Python programs Veusz supports being embedded within other Python programs. The calling program can open up any number of plot windows, and manipulate the graphs using the Veusz scripting commands, which are exposed as methods of graph objects. Using the embedding interface, a Python program can create multiple Veusz plot windows showing the same or different documents. The standard Veusz operations are supported with the addition of a few specific commands. The embedding interface runs Veusz in a second process, sending the commands over a pipe. Veusz must be installed in the PYTHONPATH for embedding to work. This can be done with the setup.py distutils script. An example embedding program is in examples/embedexample.py. An example Python program embedding Veusz is below: import time import numpy import veusz.embed as veusz g = veusz.Embedded('new window title') g.To( g.Add('page') ) g.To( g.Add('graph') ) g.SetData('x', numpy.arange(20)) g.SetData('y', numpy.arange(20)**2) g.Add('xy') g.Zoom(0.5) # wait 20 seconds time.sleep(20) win2 = veusz.Embedded('second window example') win2.To( win2.Add('page') ) win2.To( win2.Add('graph') ) win2.Add('function', function='x**2') win2.Set('x/label', 'An example axis \\emph{label}') time.sleep(20) g.Close() The supported commands are the same as in Commands, with the addition of: Close, EnableToolbar, MoveToPage, ResizeWindow, SetUpdateInterval, StartSecondView and Zoom.
    PyQt4 programs There is no direct PyQt4 interface. The standard embedding interface should work, however.
    Non Python programs Support for non Python programs is available in a limited form. External programs may execute the veusz_listen executable or veusz_listen.py Python module. Veusz will read its input from the standard input, and write output to standard output. This is a full Python execution environment, and supports all the scripting commands mentioned in Commands, a Quit() command, the EnableToolbar() and the Zoom(factor) command listed above. Only one window is supported at once, but many veusz_listen programs may be started. veusz_listen may be used from the shell command line by doing something like: veusz_listen < in.vsz where in.vsz contains: To(Add('page') ) To(Add('graph') ) SetData('x', arange(20)) SetData('y', arange(20)**2) Add('xy') Zoom(0.5) Export("foo.eps") Quit() A program may interface with Veusz in this way by using the popen C Unix function, which allows a program to be started having control of its standard input and output. Veusz can then be controlled by writing commands to an input pipe.
    C, C++ and Fortran A callable library interface to Veusz is on my todo list for C, C++ and Fortran. Please tell me if you would be interested in this option.
    veusz-1.15/Documents/generate_manual.sh0000755002344000001440000000241111734662204020224 0ustar jssusers00000000000000#!/usr/bin/env bash # Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## # generate the output manual files from the input docbook file # DO NOT EDIT THE OUTPUT FILES! infile=manual.xml docbook2pdf $infile docbook2html -u $infile docbook2txt $infile release=$(cat ../VERSION) pod2man --release=${release} --center="Veusz" veusz.pod > veusz.1 pod2man --release=${release} --center="Veusz" veusz_listen.pod > veusz_listen.1 veusz-1.15/Documents/manual.html0000644002344000001440000025263011734662402016713 0ustar jssusers00000000000000 Veusz - a scientific plotting package

    Veusz - a scientific plotting package

    Jeremy Sanders

    This document is licensed under the GNU General Public License, version 2 or greater. Please see the file COPYING for details, or see http://www.gnu.org/licenses/gpl-2.0.html.


    Table of Contents
    Introduction
    Veusz
    Terminology
    Widget
    Measurements
    Settings
    Text
    Axis numbers
    Installation
    The main window
    My first plot
    Reading data
    Descriptors
    Reading CSV files
    Reading FITS files
    Reading other data formats
    Manipulating datasets
    Using dataset plugins
    Using expressions to create new datasets
    Linking datasets to expressions
    Splitting data
    Defining new constants or functions
    Dataset plugins
    Command line interface
    Introduction
    Commands
    Action
    Add
    AddCustom
    AddImportPath
    CloneWidget
    Close
    CreateHistogram
    DatasetPlugin
    EnableToolbar
    Export
    ForceUpdate
    Get
    GetChildren
    GetClick
    GetData
    GetDataType
    GetDatasets
    GPL
    ImportFile
    ImportFile2D
    ImportFileCSV
    ImportFilePlugin
    ImportFITSFile
    ImportString
    ImportString2D
    IsClosed
    List
    Load
    MoveToPage
    ReloadData
    Rename
    Remove
    ResizeWindow
    Save
    Set
    SetAntiAliasing
    SetData
    SetDataExpression
    SetDataRange
    SetData2D
    SetData2DExpression
    SetData2DExpressionXYZ
    SetData2DXYFunc
    SetDataDateTime
    SetDataText
    SetToReference
    SetUpdateInterval
    SetVerbose
    StartSecondView
    TagDatasets
    To
    Quit
    WaitForClose
    Zoom
    Security
    Using Veusz from other programs
    Non-Qt Python programs
    PyQt4 programs
    Non Python programs
    C, C++ and Fortran

    Introduction

    Veusz

    Veusz is a scientific plotting package. It was written as I was dissatisfied with existing plotting packages as they were either old and unmaintained (like pgplot and qdp, which does not support postscript fonts), not free (like IDL or Matlab), or had user interfaces I do not appreciate (like gnuplot).

    Veusz is designed to be easily extensible, and is written in Python, a high level language (in the future it may be necessary to write some parts in another language for speed, but this will be kept to an absolute minimum). It is also designed in an object oriented fashion, where a document is built up by a number of parts in a hierarchy. The advantage of using Python is that is is easy to allow the user to script Veusz using Python as a very powerful scripting language. Indeed, the saved file format is a Python script.

    The technologies behind Veusz include PyQt (a very easy to use Python interface to Qt, which is used for rendering and the graphical user interface, GUI) and numpy (a package for Python which makes the handling of large datasets easy).

    Veusz has two user interfaces: a graphical one which gives the user a relatively shallow learning curve, and a command line interface. The command line interface is also used by scripting and in the saved file format.

    Furthermore, Veusz can be embedded within other Python programs, with plots in their own windows, or it can be controlled from any other application.


    Terminology

    Here I define some terminology for future use.


    Widget

    A document and its graphs are built up from widgets. These widgets can often by placed within each other, depending on the type of the widget. A widget has children, those widgets placed within it, and its parent. The widgets have a number of different settings which modify their behaviour. These include the font to be used, the line thickness, and whether an axis is logarithmic. In addition they have actions, which perform some sort of activity on the widget or its children, like "fit" for a fit widget.

    Widgets are specified with a "path", like a file in Unix or Windows. These can be relative to the current widget (do not start with a slash), or absolute (do not start with a slash). Examples of paths include, "/page1/graph1/x", "x" and ".".

    The widget types include

    1. document - representing a complete document. A document can contain pages. In addition it contains a setting giving the page size for the document.

    2. page - representing a page in a document. One or more graphs can be placed on a page, or a grid.

    3. graph - defining an actual graph. A graph can be placed on a page or within a grid. Contained within the graph are its axes and plotters. A graph can be given a background fill and a border if required. It also has a margin, which specifies how far away from the edge of its parent widget to plot the body of the graph.

      A graph can contain several axes, at any position on the plot. In addition a graph can use axes defined in parent widgets, shared with other graphs.

      More than one graph can be placed within in a page. The margins can be adjusted so that they lie within or besides each other.

    4. grid - containing one or more graphs. A grid plots graphs in a gridlike fashion. You can specify the number of rows and columns, and the plots are automatically replotted in the chosen arrangement. A grid can contain graphs or axes. If an axis is placed in a grid, it can be shared by the graphs in the grid.

    5. axis - giving the scale for plotting data. An axis translates the coordinates of the data to the screen. An axis can be linear or logarithmic, it can have fixed endpoints, or can automatically get them from the plotted data. It also has settings for the axis labels and lines, tick labels, and major and minor tick marks.

      An axis may be "horizontal" or "vertical" and can appear anywhere on its parent graph or grid.

      If an axis appears within a grid, then it can be shared by all the graphs which are contained within the grid.

    6. plotters - types of widgets which plot data or add other things on a graph. There is no actual plotter widget which can be added, but several types of plotters listed below. Plotters typically take an axis as a setting, which is the axis used to plot the data on the graph (default x and y).

      1. function - a plotter which plots a function on the graph. Functions can be functions of x or y (parametric functions are not done yet!), and are defined in Python expression syntax, which is very close to most other languages. For example "3*x**2 + 2*x - 4". A number of functions are available (e.g. sin, cos, tan, exp, log...). Technically, Veusz imports the numpy package when evaluating, so numpy functions are available.

        As well as the function setting, also settable is the line type to plot the function, and the number of steps to evaluate the function when plotting. Filling is supported above/below/left/right of the function.

      2. xy - a plotter which plots scatter, line, or stepped plots. This versatile plotter takes an x and y dataset, and plots (optional) points, in a chosen marker and colour, connecting them with (optional) lines, and plotting (optional) error bars. An xy plotter can also plot a stepped line, allowing histograms to be plotted (note that it doesn't yet do the binning of the data).

        The settings for the xy widget are the various attibutes for the points, line and error bars, the datasets to plot, and the axes to plot on.

        The xy plotter can plot a label next to each dataset, which is either the same for each point or taken from a text dataset.

        If you wish to leave gaps in a plot, the input value "nan" can be specified in the numeric dataset.

      3. fit - fit a function to data. This plotter is a like the function plotter, but allows fitting of the function to data. This is achived by clicking on a "fit" button, or using the "fit" action of the widget. The fitter takes a function to fit containing the unknowns, e.g. "a*x**2 + b*x + c", and initial values for the variables (here a, b and c). It then fits the data (note that at the moment, the fit plotter fits all the data, not just the data that can be seen on the graph) by minimising the chi-squared.

        In order to fit properly, the y data (or x, if fitting as a function of x) must have a properly defined, preferably symmetric error. If there is none, Veusz assumes the same fractional error everywhere, or symmetrises asymmetric errors.

        Note that more work is required in this widget, as if a parameter is not well defined by the data, the matrix inversion in the fit will fail. In addition Veusz does not supply estimates for the errors or the final chi-squared in a machine readable way.

        If the fitting parameters vary significantly from 1, then it is worth "normalizing" them by adding in a factor in the fit equation to bring them to of the order of 1.

      4. bar - a bar chart which plots sets of data as horizontal or vertical bars. Multiple datasets are supported. In "grouped" mode the bars are placed side-by-side for each dataset. In "stacked" mode the bars are placed on top of each other (in the appropriate direction according to the sign of the dataset). Bars are placed on coordinates given, or in integer values from 1 upward if none are given. Error bars are plotted for each of the datasets.

        Different fill styles can be given for each dataset given. A separate key value can be given for each dataset.

      5. key - a box which describes the data plotted. If a key is added to a plot, the key looks for "key" settings of the other data plotted within a graph. If there any it builds up a box containing the symbol and line for the plotter, and the text in the "key" setting of the widget. This allows a key to be very easily added to a plot.

        The key may be placed in any of the corners of the plot, in the centre, or manually placed. Depending on the ordering of the widgets, the key will be placed behind or on top of the widget. The key can be filled and surrounded by a box, or not filled or surrounded.

      6. label - a text label places on a graph. The alignment can be adjusted and the font changed. The position of the label can be specified in fractional terms of the current graph, or using axis coordinates.

      7. rect, ellipse - these draw a rectangle or ellipse, respectively, of size and rotation given. These widgets can be placed directly on the page or on a graph. The centre can be given in axis coordinates or fractional coordinates.

      8. imagefile - draw an external graphs file on the graph or page, with size and rotation given. The centre can be given in axis coordinates or fractional coordinates.

      9. line - draw a line with optional arrowheads on the graph or page. One end can be given in axis coordinates or fractional coordinates.

      10. contour - plot contours of a 2D dataset on the graph. Contours are automatically calculated between the minimum and maximum values of the graph or chosen manually. The line style of the contours can be chosen individually and the region between contours can be filled with shading or color.

        2D datasets currently consist of a regular grid of values between minimum and maximum positions in x and y. They can be constructed from three 1D datasets of x, y and z if they form a regular x, y grid.

      11. image - plot a 2D dataset as a colored image. Different color schemes can be chosen. The scaling between the values and the image can be specified as linear, logarithmic, square-root or square.

      12. polygon - plot x and y points from datasets as a polygon. The polygon can be placed directly on the page or within a graph. Coordinates are either plotted using the axis or as fractions of the width and height of the containing widget.

      13. boxplot - plot distribution of points in a dataset.

      14. polar - plot polar data or functions. This is a non-orthogonal plot and is placed directly on the page rather than in a graph.

      15. ternary - plot data of three variables which add up to 100 per cent.This is a non-orthogonal plot and is placed directly on the page rather than in a graph.


    Measurements

    Distances, widths and lengths in Veusz can be specified in a number of different ways. These include absolute distances specified in physical units, e.g. 1cm, 0.05m, 10mm, 5in and 10pt, and relative units, which are relative to the largest dimension of the page, including 5%, 1/20, 0.05.


    Settings

    The various settings of the widgets come in a number of types, including integers (e.g. 10), floats (e.g. 3.14), text ("hi there!"), distances (see above), options ("horizontal" or "vertical" for axes).

    Veusz performs type checks on these parameters. If they are in the wrong format the control to edit the setting will turn red. In the command line, a TypeError exception is thrown.

    In the GUI, the current page is replotted if a setting is changed when enter is pressed or the user moves to another setting.

    The settings are split up into formatting settings, controlling the appearance of the plot, or properties, controlling what is plotted and how it is plotted.

    Default settings, including the default font and line style, and the default settings for any graph widget, can be modified in the "Default styles" dialog box under the "Edit" menu. Default settings are set on a per-document basis, but can be saved into a separate file and loaded. A default default settings file can be given to use for new documents (set in the preferences dialog).


    Text

    Veusz understands a limited set of LaTeX-like formatting for text. There are some differences (for example, "10^23" puts the 2 and 3 into superscript), but it is fairly similar. You should also leave out the dollar signs. Veusz supports superscripts ("^"), subscripts ("_"), brackets for grouping attributes are "{" and "}".

    Supported LaTeX symbols include: \AA, \Alpha, \Beta, \Chi, \Delta, \Epsilon, \Eta, \Gamma, \Iota, \Kappa, \Lambda, \Mu, \Nu, \Omega, \Omicron, \Phi, \Pi, \Psi, \Rho, \Sigma, \Tau, \Theta, \Upsilon, \Xi, \Zeta, \alpha, \approx, \ast, \asymp, \beta, \bowtie, \bullet, \cap, \chi, \circ, \cup, \dagger, \dashv, \ddagger, \deg, \delta, \diamond, \divide, \doteq, \downarrow, \epsilon, \equiv, \eta, \gamma, \ge, \gg, \in, \infty, \int, \iota, \kappa, \lambda, \le, \leftarrow, \lhd, \ll, \models, \mp, \mu, \neq, \ni, \nu, \odot, \omega, \omicron, \ominus, \oplus, \oslash, \otimes, \parallel, \perp, \phi, \pi, \pm, \prec, \preceq, \propto, \psi, \rhd, \rho, \rightarrow, \sigma, \sim, \simeq, \sqrt, \sqsubset, \sqsubseteq, \sqsupset, \sqsupseteq, \star, \stigma, \subset, \subseteq, \succ, \succeq, \supset, \supseteq, \tau, \theta, \times, \umid, \unlhd, \unrhd, \uparrow, \uplus, \upsilon, \vdash, \vee, \wedge, \xi, \zeta. Please request additional characters if they are required (and exist in the unicode character set). Special symbols can be included directly from a character map.

    Other LaTeX commands are supported. "\\" breaks a line. This can be used for simple tables. For example "{a\\b} {c\\d}" shows "a c" over "b d". The command "\frac{a}{b}" shows a vertical fraction a/b.

    Also supported are commands to change font. The command "\font{name}{text}" changes the font text is written in to name. This may be useful if a symbol is missing from the current font, e.g. "\font{symbol}{g}" should produce a gamma. You can increase, decrease, or set the size of the font with "\size{+2}{text}", "\size{-2}{text}", or "\size{20}{text}". Numbers are in points.

    Various font attributes can be changed: for example, "\italic{some italic text}" (or use "\textit" or "\emph"), "\bold{some bold text}" (or use "\textbf") and "\underline{some underlined text}".

    Example text could include "Area / \pi (10^{-23} cm^{-2})", or "\pi\bold{g}".

    Veusz plots these symbols with Qt's unicode support. If your current font does not contain these symbols then you may get messy results. If you find this is the case, I highly recommend you to down load Microsoft's Corefonts (see http://corefonts.sourceforge.net/).


    Axis numbers

    The way numbers are shown on axes is chosen automatically. For standard numerical axes, values are shown with the "%Vg" formatting (see below). For date axes, an appropriate date formatting is used so that the interval shown is correct. A format can be given for an axis in the axis number formatting panel can be given to explicitly choose a format. Some examples are given in the drop down axis menu. Hold the mouse over the example for detail.

    C-style number formatting is used with a few Veusz specific extensions. Text can be mixed with format specifiers, which start with a "%" sign. Examples of C-style formatting include: "%.2f" (decimal number with two decimal places, e.g. 2.01), "%.3e" (scientific formatting with three decimal places, e.g. 2.123e-02), "%g" (general formatting, switching between "%f" and "%e" as appropriate). See http://opengroup.org/onlinepubs/007908799/xsh/fprintf.html for details.

    Veusz extensions include "%Ve", which is like "%e" except it displays scientific notation as written, e.g. 1.2x10^23, rather than 1.2e+23. "%Vg" switches between standard numbers and Veusz scientific notation for large and small numbers. "%VE" using engineering SI suffixes to represent large or small numbers (e.g. 1000 is 1k).

    Veusz allows dates and times to be formatted using "%VDX" where "X" is one of the formatting characters for strftime (see http://opengroup.org/onlinepubs/007908799/xsh/strftime.html for details). These include "a" for an abbreviated weekday name, "A" for full weekday name, "b" for abbreviated month name, "B" for full month name, "c" date and time representaiton, "d" day of month 01..31, "H" hour as 00..23, "I" hour as 01..12, "j" as day of year 001..366, "m" as month 01..12, "M" minute as 00..59, "p" AM/PM, "S" second 00..61, "U" week number of year 00..53 (Sunday as first day of week), "w" weekday as decimal number 0..6, "W" week number of year (Monday as first day of week), "x" date representation, "X" time representation, "y" year without century 00..99 and "Y" year. "%VDVS" is a special Veusz addon format which shows seconds and fractions of seconds (e.g. 12.2).


    Installation

    Please look at the Installation notes (INSTALL) for details on installing Veusz.


    The main window

    You should see the main window when you run Veusz (you can just type the veusz command in Unix).

    The Veusz window is split into several sections. At the top is the menu bar and tool bar. These work in the usual way to other applications. Sometimes options are disabled (greyed out) if they do not make sense to be used. If you hold your mouse over a button for a few seconds, you will usually get an explanation for what it does called a "tool tip".

    Below the main toolbar is a second toolbar for constructing the graph by adding widgets (on the left), and some editing buttons. The add widget buttons add the request widget to the currently selected widget in the selection window. The widgets are arranged in a tree-like structure.

    Below these toolbars and to the right is the plot window. This is where the current page of the current document is shown. You can adjust the size of the plot on the screen (the zoom factor) using the "View" menu or the zoom tool bar button (the magnifying glass). Initially you will not see a plot in the plot window, but you will see the Veusz logo. At the moment you cannot do much else with the window. In the future you will be able to click on items in the plot to modify them.

    To the left of the plot window is the selection window, and the properties and formatting windows. The properties window lets you edit various aspects of the selected widget (such as the minimum and maximum values on an axis). Changing these values should update the plot. The formatting lets you modify the appearance of the selected widget. There are a series of tabs for choosing what aspect to modify.

    The various windows can be "dragged" from the main window to "float" by themselves on the screen.

    To the bottom of the window is the console. This window is not shown by default, but can be enabled in the View menu. The console is a Veusz and Python command line console. To read about the commands available see Commands. As this is a Python console, you can enter mathematical expressions (e.g. "1+2.0*cos(pi/4)") here and they will be evaluated when you press Enter. The usual special functions and the operators are supported. You can also assign results to variables (e.g. "a=1+2") for use later. The console also supports command history like many Unix shells. Press the up and down cursor keys to browse through the history. Command line completion is not available yet!

    In recent versions there also exists a dataset browsing window, by default to the right of the screen. This window allows you to view the datasets currently loaded, their dimensions and type. Hovering a mouse over the size of the dataset will give you a preview of the data.


    My first plot

    After opening Veusz, on the left of the main window, you will see a Document, containing a Page, which contains a Graph with its axes. The Graph is selected in the selection window. The toolbar above adds a new widget to the selected widget. If a widget cannot be added to a selected widget it is disabled. On opening a new document Veusz automatically adds a new Page and Graph (with axes) to the document.

    You will see something like this:

    Select the x axis which has been added to the document (click on "x" in the selection window). In the properties window you will see a variety of different properties you can modify. For instance you can enter a label for the axis by writing "Area (cm^{2})" in the box next to label and pressing enter. Veusz supports text in LaTeX-like form (without the dollar signs). Other important parameters is the "log" switch which switches between linear and logarithmic axes, and "min" and "max" which allow the user to specify the minimum and maximum values on the axes.

    The formatting dialog lets you edit various aspects of the graph appearance. For instance the "Line" tab allows you to edit the line of the axis. Click on "Line", then you can then modify its colour. Enter "green" instead of "black" and press enter. Try making the axis label bold.

    Now you can try plotting a function on the graph. If the graph, or its children are selected, you will then be able to click the "function" button at the top (a red curve on a graph). You will see a straight line (y=x) added to the plot. If you select "function1", you will be able to edit the functional form plotted and the style of its line. Change the function to "x**2" (x-squared).

    We will now try plotting data on the graph. Go to your favourite text editor and save the following data as test.dat:

    
1     0.1   -0.12   1.1    0.1
    2.05  0.12  -0.14   4.08   0.12
    2.98  0.08  -0.1    2.9    0.11
    4.02  0.04  -0.1    15.3   1.0   
    

    The first three columns are the x data to plot plus its asymmetric errors. The final two columns are the y data plus its symmetric errors. In Veusz, go to the "Data" menu and select "Import". Type the filename into the filename box, or use the "Browse..." button to search for the file. You will see a preview of the data pop up in the box below. Enter "x,+,- y,+-" into the descriptors edit box (note that commas and spaces in the descriptor are almost interchangeable in Veusz 1.6 or newer). This describes the format of the data which describes dataset "x" plus its asymmetric errors, and "y" with its symmetric errors. If you now click "Import", you will see it has imported datasets "x" and "y".

    To plot the data you should now click on "graph1" in the tree window. You are now able to click on the "xy" button (which looks like points plotted on a graph). You will see your data plotted on the graph. Veusz plots datasets "x" and "y" by default, but you can change these in the properties of the "xy" plotter.

    You are able to choose from a variety of markers to plot. You can remove the plot line by choosing the "Plot Line" subsetting, and clicking on the "hide" option. You can change the colour of the marker by going to the "Marker Fill" subsetting, and entering a new colour (e.g. red), into the colour property.


    Reading data

    Currently Veusz supports reading data from a text file, FITS format files, CSV files, QDP files, binary files and NPY/NPZ files. Reading data is supported using the "Data, Import" dialog, or using the ImportFile and ImportString commands which read data from files or an existing Python string (allowing data to be embedded in a Python script). In addition, the user can load or write plugins in Python which load data into Veusz in an arbitrary format. At the moment QDP, binary and NPY/NPZ files are supported with this method.

    CSV files are intuitive to use and are described below.

    In addition data may also be read in from FITS files if the PyFITS Python module is installed. FITS is a widespread astronomical data format. FITS files are read using the FITS tab on the import dialog or using the ImportFITSFile command.

    Two dimensional data are also supported using the 2D tab on the Import dialog box, ImportFile2D and ImportString2D commands.


    Descriptors

    The "Data, Import" dialog box, ImportFile and ImportString commands use a "Descriptor", or list of dataset names, to describe how the data are formatted in the import file. The descriptor at its simplest is a list of the names of the datasets to import (which are columns in the file). Additionally modifiers are added if error bars are also read in. Examples of descriptors are below:

    1. x y two columns are present in the file, they will be read in as datasets "x" and "y".

    2. x,+- y,+,- or x +- y + - two datasets are in the file. Dataset "x" consists of the first two columns. The first column are the values and the second are the symmetric errors. "y" consists of three columns (note the comma between + and -). The first column are the values, the second positive asymmetric errors, and the third negative asymmetric errors.

      Suppose the input file contains:

      
1.0  0.3  2   0.1  -0.2
      1.5  0.2  2.3 2e-2 -0.3E0
      2.19 0.02 5    0.1 -0.1 
      

      Then x will contain "1+-0.3", "1.5+-0.2", "2.19+-0.02". y will contain "2 +0.1 -0.2", "2.3 +0.02 -0.3", "5 +0.1 -0.1".

    3. x[1:2] y[:] the first column is the data "x_1", the second "x_2". Subsequent columns are read as "y[1]" to "y[n]".

    4. y[:]+- read each pair of columns as a dataset and its symmetric error, calling them "y[1]" to "y[n]".

    5. foo,,+- read the first column as the foo dataset, skip a column, and read the third column as its symmetric error.

    The dataset names given here x and y, are just representative. Dataset names can contain virtually any character, even unicode characters. If the name contains non alpha-numeric characters (characters outside of A-Z, a-z and 0-9), then the dataset name should be contained within back-tick characters. An example descriptor is `length data (m)`,+- `speed (mps)`,+,-, for two datasets with spaces and brackets in their names.

    The special names +-, + or - specify that the datasets before are followed by columns containing symmetric, positive or negative errors. The signs on positive or negative errors are automatically set to be correct.

    If a descriptor is left blank, Veusz will automatically create dataset names. If the prefix and suffix settings are blank, they are assigned names col1, col2, etc. If prefix and suffix are not blank, the datasets are called prefix+number+suffix.

    When reading in data, Veusz treats any whitespace as separating columns. The columns do not actually need to be in columns! Furthermore a "\" symbol can be placed at the end of a line to mark a continuation. Veusz will read the next line as if it were placed at the end of the current line. In addition comments and blank lines are ignored. Comments start with a "#", ";", "!" or "%", and continue until the end of the line. The special value "nan" can be used to specify a break in a dataset.

    Veusz supports reading in other types of data. The type of data can be added in round brackets after the name. Veusz will try to guess the type of data based on the first value, so you should specify it if there is any form of ambiguity (e.g. is 3 text or a number). Supported types are numbers (use numeric in brackets) and text (use text in brackets). An example descriptor would be "x(numeric) +- y(numeric) + - label(text)" for an x dataset followed by its symmetric errors, a y dataset followed by two columns of asymmetric errors, and a final column of text for the label dataset.

    A text column does not need quotation unless it contains space characters or escape characters. However make sure you deselect the "ignore text" option in the import dialog. This ignores lines of text to ease the import of data from other applications. Quotation marks are recommended around text if you wish to avoid ambiguity. Text is quoted according to the Python rules for text. Double or single quotation marks can be used, e.g. "A 'test'", 'A second "test"'. Quotes can be escaped by prefixing them with a backslash, e.g. "A new \"test\"". If the data are generated from a Python script, the repr function provides the text in a suitable form.

    Dates and times are also supported with the syntax "dataset(date)". Dates must be in ISO format YYYY-MM-DD. Times are in 24 hour format hh:mm:ss.ss. Dates with times are written YYYY-MM-DDThh:mm:ss.ss (this is a standard ISO format, see http://www.w3.org/TR/NOTE-datetime). Dates are stored within Veusz as a number which is the number of seconds since the start of January 1st 2009. Veusz also supports dates and times in the local format, though take note that the same file and data may not work on a system in a different location.

    Data may be optionally split into "blocks" of data separated by blank lines (or the word "no" on a line, for obscure reasons). The name of each read in dataset has an underscore and the number of the block (starting from 1) added. This is specified by clicking the blocks checkbox in the import dialog, or by using the useblocks=True option on the ImportFile or ImportString commands.

    Instead of specifying the descriptor in the import dialog, the descriptor can be placed in the data file using a descriptor statement on a separate line, consisting of "descriptor" followed by the descriptor. Multiple descriptors can be placed in a single file, for example:

    
# here is one section
    descriptor x,+- y,+,-
    1 0.5  2 0.1 -0.1
    2 0.3  4 0.2 -0.1
    
    # my next block
    descriptor alpha beta gamma
    1 2 3
    4 5 6
    7 8 9
    
    # etc...
    

    If data are imported from a file, Veusz will normally save the data in its saved document format. If the data are changing, quite often one wants to reread the data from the input file. This can be achieved using the "linked=True" option on the ImportFile command, or by clicking the "Link" checkbox in the import dialog.


    Reading CSV files

    CVS (comma separated variable) files are often written from other programs, such as spreadsheets, including Excel and Gnumeric. Veusz supports reading from these files.

    In the import dialog choose "CSV", then choose a filename to import from. In the CSV file the user should place the data in either rows or columns. Veusz will use a name above a column or to the left of a row to specify what the dataset name should be. The user can use new names further down in columns or right in rows to specify a different dataset name. Names do not have to be used, and Veusz will assign default "col" and "row" names if not given. You can also specify a prefix which is prepended to each dataset name read from the file.

    To specify symmetric errors for a column, put "+-" as the dataset name in the next column or row. Asymmetric errors can be stated with "+" and "-" in the columns.

    The data can be linked with the CSV file so that it can be updated when the file changes. See the example CSV import for details.

    The data type in CSV files are automatically detected unless specified. The data type can be given in brackets after the column name, e.g. "name (text)", where the data type is "date", "numeric" or "text". Explicit data types are needed if the data look like a different data type (e.g. a text item of "1.23"). The date format in CSV files can be specified in the import dialog box - see the examples given. In addition CSV files support numbers in European format (e.g. 2,34 rather than 2.34), depending on the setting in the dialog box.


    Reading FITS files

    1D or 2D data can be read from FITS files. 1D data, with optional errors bars, can be read from table extensions, and 2D data from image or primary extensions.


    Reading other data formats

    As mentioned above, a user may write some Python code to read a data file or set of data files. To write a plugin which is incorportated into Veusz, see http://barmag.net/veusz-wiki/ImportPlugins

    You can also include Python code in an input file to read data, which we describe here. Suppose an input file "in.dat" contains the following data:

    
1   2
    2   4
    3   9
    4   16
    

    Of course this data could be read using the ImportFile command. However, you could also read it with the following Veusz script (which could be saved to a file and loaded with execfile or Load. The script also places symmetric errors of 0.1 on the x dataset.

    
x = []
    y = []
    for line in open("in.dat"):
        parts = [float(i) for i in line.split()]
        x.append(parts[0])
        y.append(parts[1])
    
    SetData('x', x, symerr=0.1)
    SetData('y', y)
    


    Manipulating datasets

    Imported datasets can easily be modified in the Edit data dialog box, by clicking on a value and entering a new one. What is probably more interesting is using the Create dialog box to make new datasets from scratch or based on other datasets.

    New datasets can be made by entering a name, and choosing whether to make a dataset consisting of a single value or over a range, from expressions based on a parametric expression, or from expressions based on existing datasets.


    Using dataset plugins

    Dataset plugins can be used to perform arbitrary manipulation of datasets. Veusz includes several plugins for mathematical operation of data and other dataset manipulations, such as concatenation or splitting. If you wish to write your own plugins look at http://barmag.net/veusz-wiki/DatasetPlugins.


    Using expressions to create new datasets

    For instance, if the user has already imported dataset d, then they can create d2 which consists of d**2. Expressions are in Python numpy syntax and can include the usual mathematical functions.

    Expressions for error bars can also be given. By appending _data, _serr, _perr or _nerr to the name of the dataset in the expression, the user can base their expression on particular parts of the given dataset (the main data, symmetric errors, positive errors or negative errors). Otherwise the program uses the same parts as is currently being specified.

    If a dataset name contains non alphanumeric characters, its name should be quoted in the expression in back-tick characters, e.g. `length (cm)`*2.


    Linking datasets to expressions

    A particularly useful feature is to be able to link a dataset to an expression, so if the expression changes the dataset changes with it, like in a spreadsheet.


    Splitting data

    Data can also be chopped in this method, for example using the expression x[10:20], which makes a dataset based on the 11th to 20th item in the x dataset (the ranges are Python syntax, and are zero-based). Negative indices count backwards from the end of the dataset. Data can be skipped using expressions such as data[::2], which skips every other element


    Defining new constants or functions

    User defined constants or functions can be defined in the "Custom definitions" dialog box under the edit menu. Functions can also be imported from external python modules.

    Custom definitions are defined on a per-document basis, but can be saved or loaded into a file. A default custom definitions file can be set in the preferences dialog box.


    Dataset plugins

    In addition to creating datasets based on expressions, a variety of dataset plugins exist, which make certain operations on datasets much more convenient. See the Data, Operations menu for a list of the default plugins. The user can easily create new plugins. See http://barmag.net/veusz-wiki/DatasetPlugins for details.


    Command line interface

    Introduction

    An alternative way to control Veusz is via its command line interface. As Veusz is a a Python application it uses Python as its scripting language. Therefore you can freely mix Veusz and Python commands on the command line. Veusz can also read in Python scripts from files (see the Load command).

    When commands are entered in the command prompt in the Veusz window, Veusz supports a simplified command syntax, where brackets following commands names, and commas, can replaced by spaces in Veusz commands (not Python commands). For example, Add('graph', name='foo'), may be entered as Add 'graph' name='foo'.

    The numpy package is already imported into the command line interface (as "*"), so you do not need to import it first.

    The command prompt supports history (use the up and down cursor keys to recall previous commands).

    Most of the commands listed below can be used in the in-program command line interface, using the embedding interface or using veusz_listen. Commands specific to particular modes are documented as such.

    Veusz also includes a new object-oriented version of the interface, which is documented at http://barmag.net/veusz-wiki/EmbeddingPython.


    Commands

    We list the allowed set of commands below


    Action

    Action('actionname', componentpath='.')

    Initiates the specified action on the widget (component) given the action name. Actions perform certain automated routines. These include "fit" on a fit widget, and "zeroMargins" on grids.


    Add

    Add('widgettype', name='nameforwidget', autoadd=True, optionalargs)

    The Add command adds a graph into the current widget (See the To command to change the current position).

    The first argument is the type of widget to add. These include "graph", "page", "axis", "xy" and "grid". name is the name of the new widget (if not given, it will be generated from the type of the widget plus a number). The autoadd parameter if set, constructs the default sub-widgets this widget has (for example, axes in a graph).

    Optionally, default values for the graph settings may be given, for example Add('axis', name='y', direction='vertical').

    Subsettings may be set by using double underscores, for example Add('xy', MarkerFill__color='red', ErrorBarLine__hide=True).

    Returns: Name of widget added.


    AddCustom

    AddCustom(type, name, value)

    Add a custom definition for evaluation of expressions. This can define a constant (can be in terms of other constants), a function of 1 or more variables, or a function imported from an external python module.

    ctype is "constant", "function" or "import".

    name is name of constant, or "function(x, y, ...)" or module name.

    val is definition for constant or function (both are _strings_), or is a list of symbols for a module (comma separated items in a string).

    If mode is 'appendalways', the custom value is appended to the end of the list even if there is one with the same name. If mode is 'replace', it replaces any existing definition in the same place in the list or is appended otherwise. If mode is 'append', then an existing definition is deleted, and the new one appended to the end.


    AddImportPath

    AddImportPath(directory)

    Add a directory to the list of directories to try to import data from.


    CloneWidget

    CloneWidget(widget, newparent, newname=None)

    Clone the widget given, placing the copy in newparent and the name given. newname is an optional new name to give it Returns new widget path.


    Close

    Close()

    Closes the plotwindow. This is only supported in embedded mode.


    CreateHistogram

    CreateHistogram(inexpr, outbinsds, outvalsds, binparams=None, binmanual=None, method='counts', cumulative = 'none', errors=False)

    Histogram an input expression. inexpr is input expression. outbinds is the name of the dataset to create giving bin positions. outvalsds is name of dataset for bin values. binparams is None or (numbins, minval, maxval, islogbins). binmanual is None or a list of bin values. method is 'counts', 'density', or 'fractions'. cumulative is to calculate cumulative distributions which is 'none', 'smalltolarge' or 'largetosmall'. errors is to calculate Poisson error bars.


    DatasetPlugin

    DatasetPlugin(pluginname, fields, datasetnames={})>

    Use a dataset plugin. pluginname: name of plugin to use fields: dict of input values to plugin datasetnames: dict mapping old names to new names of datasets if they are renamed. The new name None means dataset is deleted


    EnableToolbar

    EnableToolbar(enable=True)

    Enable/disable the zooming toolbar in the plotwindow. This command is only supported in embedded mode or from veusz_listen.


    Export

    Export(filename, color=True, page=0 dpi=100, antialias=True, quality=85, backcolor='#ffffff00', pdfdpi=150, svgtextastext=False)

    Export the page given to the filename given. The filename must end with the correct extension to get the right sort of output file. Currrenly supported extensions are '.eps', '.pdf', '.svg', '.jpg', '.jpeg', '.bmp' and '.png'. If color is True, then the output is in colour, else greyscale. page is the page number of the document to export (starting from 0 for the first page!). dpi is the number of dots per inch for bitmap output files. antialias - antialiases output if True. quality is a quality parameter for jpeg output. backcolor is the background color for bitmap files, which is a name or a #RRGGBBAA value (red, green, blue, alpha). pdfdpi is the dpi to use when exporting EPS or PDF files. svgtextastext says whether to export SVG text as text, rather than curves.


    ForceUpdate

    ForceUpdate()

    Force the window to be updated to reflect the current state of the document. Often used when periodic updates have been disabled (see SetUpdateInterval). This command is only supported in embedded mode or from veusz_listen.


    Get

    Get('settingpath')

    Returns: The value of the setting given by the path.

    
>>> Get('/page1/graph1/x/min')
    'Auto'
    


    GetChildren

    GetChildren(where='.')

    Returns: The names of the widgets which are children of the path given


    GetClick

    GetClick()

    Waits for the user to click on a graph and returns the position of the click on appropriate axes. Command only works in embedded mode.

    Returns: A list containing tuples of the form (axispath, val) for each axis for which the click was in range. The value is the value on the axis for the click.


    GetData

    GetData(name)

    Returns: For a 1D dataset, a tuple containing the dataset with the name given. The value is (data, symerr, negerr, poserr), with each a numpy array of the same size or None. data are the values of the dataset, symerr are the symmetric errors (if set), negerr and poserr and negative and positive asymmetric errors (if set). If a text dataset, return a list of text elements. If the dataset is a date-time dataset, return a list of Python datetime objects. If the dataset is a 2D dataset return the tuple (data, rangex, rangey), where data is a 2D numpy array and rangex/y are tuples giving the range of the x and y coordinates of the data.

    
data = GetData('x')
    SetData('x', data[0]*0.1, *data[1:])
    


    GetDataType

    GetDataType(name)

    Get type of dataset with name given. Returns '1d' for a 1d dataset, '2d' for a 2d dataset, 'text' for a text dataset and 'datetime' for a datetime dataset.


    GetDatasets

    GetDatasets()

    Returns: The names of the datasets in the current document.


    GPL

    GPL()

    Print out the GNU Public Licence, which Veusz is licenced under.


    ImportFile

    ImportFile('filename', 'descriptor', linked=False, prefix='', suffix='', encoding='utf_8')

    Imports data from a file. The arguments are the filename to load data from and the descriptor.

    The format of the descriptor is a list of variable names representing the columns of the data. For more information see Descriptors.

    If the linked parameter is set to True, if the document is saved, the data imported will not be saved with the document, but will be reread from the filename given the next time the document is opened. The linked parameter is optional.

    If prefix and/or suffix are set, then the prefix and suffix are added to each dataset name.

    Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset.

    Changed in version 0.5: A tuple is returned rather than just the number of imported variables.


    ImportFile2D

    ImportFile2D('filename', datasets, xrange=(a,b), yrange=(c,d), invertrows=True/False, invertcols=True/False, transpose=True/False, prefix='', suffix='', linked=False, encoding='utf8')

    Imports two-dimensional data from a file. The required arguments are the filename to load data from and the dataset name, or a list of names to use.

    filename is a string which contains the filename to use. datasets is either a string (for a single dataset), or a list of strings (for multiple datasets).

    The xrange parameter is a tuple which contains the range of the X-axis along the two-dimensional dataset, for example (-1., 1.) represents an inclusive range of -1 to 1. The yrange parameter specifies the range of the Y-axis similarly. If they are not specified, (0, N) is the default, where N is the number of datapoints along a particular axis.

    invertrows and invertcols if set to True, invert the rows and columns respectively after they are read by Veusz. transpose swaps the rows and columns.

    If prefix and/or suffix are set, they are prepended or appended to imported dataset names.

    If the linked parameter is True, then the datasets are linked to the imported file, and are not saved within a saved document.

    The file format this command accepts is a two-dimensional matrix of numbers, with the columns separated by spaces or tabs, and the rows separated by new lines. The X-coordinate is taken to be in the direction of the columns. Comments are supported (use "#", "!" or "%"), as are continuation characters ("\"). Separate datasets are deliminated by using blank lines.

    In addition to the matrix of numbers, the various optional parameters this command takes can also be specified in the data file. These commands should be given on separate lines before the matrix of numbers. They are:

    1. xrange A B

    2. yrange C D

    3. invertrows

    4. invertcols

    5. transpose


    ImportFileCSV

    ImportFileCSV('filename', readrows=False, dsprefix='', dssuffix='', linked=False, encoding='utf_8')

    This command imports data from a CSV format file. Data are read from the file using the dataset names given at the top of the files in columns. Please see the reading data section of this manual for more information. dsprefix is prepended to each dataset name and dssuffix is added (the prefix option is deprecated and also addeds an underscore to the dataset name). linked specifies whether the data will be linked to the file.


    ImportFilePlugin

    ImportFilePlugin('pluginname', 'filename', **pluginargs, linked=False, encoding='utf_8', prefix='', suffix='')

    Import data from file using import plugin 'pluginname'. The arguments to the plugin are given, plus optionally a text encoding, and prefix and suffix to prepend or append to dataset names.


    ImportFITSFile

    ImportFITSFile(datasetname, filename, hdu, datacol='A', symerrcol='B', poserrcol='C', negerrcol='D', linked=True/False)

    This command does a simple import from a FITS file. The FITS format is used within the astronomical community to transport binary data. For a more powerful FITS interface, you can use PyFITS within your scripts.

    The datasetname is the name of the dataset to import, the filename is the name of the FITS file to import from. The hdu parameter specifies the HDU to import data from (numerical or a name).

    If the HDU specified is a primary HDU or image extension, then a two-dimensional dataset is loaded from the file. The optional parameters (other than linked) are ignored. Any WCS information within the HDU are used to provide a suitable xrange and yrange.

    If the HDU is a table, then the datacol parameter must be specified (and optionally symerrcol, poserrcol and negerrcol). The dataset is read in from the named column in the table. Any errors are read in from the other specified columns.

    If linked is True, then the dataset is not saved with a saved document, but is reread from the data file each time the document is loaded.


    ImportString

    ImportString('descriptor', 'data')

    Like, ImportFile, but loads the data from the specfied string rather than a file. This allows data to be easily embedded within a document. The data string is usually a multi-line Python string.

    Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset.

    Changed in version 0.5: A tuple is returned rather than just the number of imported variables.

    
ImportString('x y', '''
    1   2
    2   5
    3   10
    ''')
    


    ImportString2D

    ImportString2D(datasets, string)

    Imports a two-dimensional dataset from the string given. This is similar to the ImportFile2D command, with the same dataset format within the string. This command, however, does not currently take any optional parameters. The various controlling parameters can be set within the string. See the ImportFile2D section for details.


    IsClosed

    IsClosed()

    Returns a boolean value telling the caller whether the plotting window has been closed.

    Note: this command is only supported in the embedding interface.


    List

    List(where='.')

    List the widgets which are contained within the widget with the path given, the type of widgets, and a brief description.


    Load

    Load('filename.vsz')

    Loads the veusz script file given. The script file can be any Python code. The code is executed using the Veusz interpreter.

    Note: this command is only supported at the command line and not in a script. Scripts may use the python execfile function instead.


    MoveToPage

    MoveToPage(pagenum)

    Updates window to show the page number given of the document.

    Note: this command is only supported in the embedding interface or veusz_listen.


    ReloadData

    ReloadData()

    Reload any datasets which have been linked to files.

    Returns: A tuple containing a list of the imported datasets and the number of conversions which failed for a dataset.


    Rename

    Remove('widgetpath', 'newname')

    Rename the widget at the path given to a new name. This command does not move widgets. See To for a description of the path syntax. '.' can be used to select the current widget.


    Remove

    Remove('widgetpath')

    Remove the widget selected using the path. See To for a description of the path syntax.


    ResizeWindow

    ResizeWindow(width, height)

    Resizes window to be width by height pixels.

    Note: this command is only supported in the embedding interface or veusz_listen.


    Save

    Save('filename.vsz')

    Save the current document under the filename given.


    Set

    Set('settingpath', val)

    Set the setting given by the path to the value given. If the type of val is incorrect, an InvalidType exception is thrown. The path to the setting is the optional path to the widget the setting is contained within, an optional subsetting specifier, and the setting itself.

    
Set('page1/graph1/x/min', -10.)
    


    SetAntiAliasing

    SetAntiAliasing(on)

    Enable or disable anti aliasing in the plot window, replotting the image.


    SetData

    SetData(name, val, symerr=None, negerr=None, poserr=None)

    Set the dataset name with the values given. If None is given for an item, it will be left blank. val is the actual data, symerr are the symmetric errors, negerr and poserr and the getative and positive asymmetric errors. The data can be given as lists or numpys.


    SetDataExpression

    SetDataExpression(name, val, symerr=None, negerr=None, poserr=None, linked=False, parametric=None)

    Create a new dataset based on the expressions given. The expressions are Python syntax expressions based on existing datasets.

    If linked is True, the dataset will change as the datasets in the expressions change.

    Parametric can be set to a tuple of (minval, maxval, numitems). t in the expression will iterate from minval to maxval in numitems values.


    SetDataRange

    SetDataRange(name, numsteps, val, symerr=None, negerr=None, poserr=None, linked=False)

    Set dataset to be a range of values with numsteps steps. val is tuple made up of (minimum value, maximum value). symerr, negerr and poserr are optional tuples for the error bars.

    If linked is True, the dataset can be saved in a document as a SetDataRange, otherwise it is expanded to the values which would make it up.


    SetData2D

    SetData2D('name', val, xrange=(A,B), yrange=(C,D))

    Creates a two-dimensional dataset with the name given. val is either a two-dimensional numpy array, or is a list of lists, with each list in the list representing a row.

    xrange and yrange are optional tuples giving the inclusive range of the X and Y coordinates of the data.


    SetData2DExpression

    SetData2D('name', expr, linked=False)

    Create a 2D dataset based on expressions. name is the new dataset name expr is an expression which should return a 2D array linked specifies whether to permanently link the dataset to the expressions.


    SetData2DExpressionXYZ

    SetData2DExpressionXYZ('name', 'xexpr', 'yexpr', 'zexpr', linked=False)

    Create a 2D dataset based on three 1D expressions. The x, y expressions need to evaluate to a grid of x, y points, with the z expression as the 2D value at that point. Currently only linear fixed grids are supported. This function is intended to convert calculations or measurements at fixed points into a 2D dataset easily. Missing values are filled with NaN.


    SetData2DXYFunc

    SetData2DXYFunc('name', xstep, ystep, 'expr', linked=False)

    Construct a 2D dataset using a mathematical expression of "x" and "y". The x values are specified as (min, max, step) in xstep as a tuple, the y values similarly. If linked remains as False, then a real 2D dataset is created, where values can be modified and the data are stored in the saved file.


    SetDataDateTime

    SetDataDateTime('name', vals)

    Creates a datetime dataset of name given. vals is a list of Python datetime objects.


    SetDataText

    SetDataText(name, val)

    Set the text dataset name with the values given. val must be a type that can be converted into a Python list.

    
SetDataText('mylabel', ['oranges', 'apples', 'pears', 'spam'])
    


    SetToReference

    SetToReference(setting, refval)

    Set setting to match other setting refval always..


    SetUpdateInterval

    SetUpdateInterval(interval)

    Tells window to update every interval milliseconds at most. The value 0 disables updates until this function is called with a non-zero. The value -1 tells Veusz to update the window every time the document has changed. This will make things slow if repeated changes are made to the document. Disabling updates and using the ForceUpdate command will allow the user to control updates directly.

    Note: this command is only supported in the embedding interface or veusz_listen.


    SetVerbose

    SetVerbose(v=True)

    If v is True, then extra information is printed out by commands.


    StartSecondView

    StartSecondView(name = 'window title')

    In the embedding interface, this method will open a new Embedding interface onto the same document, returning the object. This new window provides a second view onto the document. It can, for instance, show a different page to the primary view. name is a window title for the new window.

    Note: this command is only supported in the embedding interface.


    TagDatasets

    TagDatasets('tag', ['ds1', 'ds2'...])

    Adds the tag to the list of datasets given..


    To

    To('widgetpath')

    The To command takes a path to a widget and moves to that widget. For example, this may be "/", the root widget, "graph1", "/page1/graph1/x", "../x". The syntax is designed to mimic Unix paths for files. "/" represents the base widget (where the pages reside), and ".." represents the widget next up the tree.


    Quit

    Quit()

    Quits Veusz. This is only supported in veusz_listen.


    WaitForClose

    WaitForClose()

    Wait until the plotting window has been closed.

    Note: this command is only supported in the embedding interface.


    Zoom

    Zoom(factor)

    Sets the plot zoom factor, relative to a 1:1 scaling. factor can also be "width", "height" or "page", to zoom to the page width, height or page, respectively.

    This is only supported in embedded mode or veusz_listen.


    Security

    With the 1.0 release of Veusz, input scripts and expressions are checked for possible security risks. Only a limited subset of Python functionality is allowed, or a dialog box is opened allowing the user to cancel the operation. Specifically you cannot import modules, get attributes of Python objects, access globals() or locals() or do any sort of file reading or manipulation. Basically anything which might break in Veusz or modify a system is not supported. In addition internal Veusz functions which can modify a system are also warned against, specifically Print(), Save() and Export().

    If you are running your own scripts and do not want to be bothered by these dialogs, you can run veusz with the --unsafe-mode option.


    Using Veusz from other programs

    Non-Qt Python programs

    Veusz supports being embedded within other Python programs. The calling program can open up any number of plot windows, and manipulate the graphs using the Veusz scripting commands, which are exposed as methods of graph objects.

    Using the embedding interface, a Python program can create multiple Veusz plot windows showing the same or different documents. The standard Veusz operations are supported with the addition of a few specific commands.

    The embedding interface runs Veusz in a second process, sending the commands over a pipe.

    Veusz must be installed in the PYTHONPATH for embedding to work. This can be done with the setup.py distutils script. An example embedding program is in examples/embedexample.py.

    An example Python program embedding Veusz is below:

    
import time
    import numpy
    import veusz.embed as veusz
     
    g = veusz.Embedded('new window title')
    g.To( g.Add('page') )
    g.To( g.Add('graph') )
    g.SetData('x', numpy.arange(20))
    g.SetData('y', numpy.arange(20)**2)
    g.Add('xy')
    g.Zoom(0.5)
     
    # wait 20 seconds
    time.sleep(20)
    
    win2 = veusz.Embedded('second window example')
    win2.To( win2.Add('page') )
    win2.To( win2.Add('graph') )
    win2.Add('function', function='x**2')
    win2.Set('x/label', 'An example axis \\emph{label}')
    
    time.sleep(20)
    
    g.Close()
    

    The supported commands are the same as in Commands, with the addition of: Close, EnableToolbar, MoveToPage, ResizeWindow, SetUpdateInterval, StartSecondView and Zoom.


    PyQt4 programs

    There is no direct PyQt4 interface. The standard embedding interface should work, however.


    Non Python programs

    Support for non Python programs is available in a limited form. External programs may execute the veusz_listen executable or veusz_listen.py Python module. Veusz will read its input from the standard input, and write output to standard output. This is a full Python execution environment, and supports all the scripting commands mentioned in Commands, a Quit() command, the EnableToolbar() and the Zoom(factor) command listed above. Only one window is supported at once, but many veusz_listen programs may be started.

    veusz_listen may be used from the shell command line by doing something like:

    
veusz_listen < in.vsz
    

    where in.vsz contains:

    
To(Add('page') )
    To(Add('graph') )
    SetData('x', arange(20))
    SetData('y', arange(20)**2)
    Add('xy')
    Zoom(0.5)
    Export("foo.eps")
    Quit()
    

    A program may interface with Veusz in this way by using the popen C Unix function, which allows a program to be started having control of its standard input and output. Veusz can then be controlled by writing commands to an input pipe.


    C, C++ and Fortran

    A callable library interface to Veusz is on my todo list for C, C++ and Fortran. Please tell me if you would be interested in this option.

    veusz-1.15/Documents/Interface.txt0000644002344000001440000000116411734662204017203 0ustar jssusers00000000000000Just some notes on the interface design... ------------------------------------------ AddGraph('function', 'x**2', name='fn') AddGraph('xy', 'x', 'y', name='data') (alias AG) AddContainer('grid', columns=3) (alias AC) List() - list graphs in current container (alias L) Save('filename.rap') Load('filename.rap') WriteFile('eps', 'filename.eps') (alias WF) Change('fn') (alias C) GraphUp() (alias U) ReadFile('descriptor', 'filename.dat') (alias RF) SetData('x', numarray, symerr=var1, negerr=var2, poserr=var2) Attribute('AxisLabel.text', 'axis') (alias A) AttributeAxis('x', 'AxisLabel.text', 'axis') (alias AA) veusz-1.15/Documents/manual.pdf0000644002344000001440000141513511734662401016521 0ustar jssusers00000000000000%PDF-1.5 %ÐÔÅØ 2 0 obj << /Type /ObjStm /N 100 /First 798 /Length 1054 /Filter /FlateDecode >> stream xÚ…•]oÚJ†ïùsÙÞï÷®TUŠ I#µRtB{®Xˆc#Û(Iýyj—ÞTZ‡ñú™×óå Œ IM–¬¤@ޒȰð£°`jwŽ„—¸H„Œ¤À²)I‘0ž¤År$–!ì+()…åIi¬@Êaá†פ–œhIZ:ÒK‘¶Xžë É :! ûN|9,Üy²dU6AôVãÎuY¤ƒ8m ',aÃáÕN‘³¸4¹ qxçž¼V0É;9AŠ>Xò†²B1bñBp¸.JPP\ŸŒžÈ ‚GÔF ‘ ÎLD†‚‰Ž]É%äºJÃ;†ÏØê%õ–ì]…º vP– (+'YýÈäD0§3Ïb0PÖæ ƒ•Í ÜHH~®¸—šAÙ86<øƒ^›ù‰@7„=ŠÁàΣ#ÂòÛÑá8'~ã 8§Ù€²3Už ìz®ú#¼¨²áX†eÊÞ3å ËÃàäÐ+ PìŠJ‹ØÀ¨e\HxÊLÙ ¦Ì8(ôMfœ;:ƒL£À§–kƒ~HaØÐ0òZ™±eXp…²4ÙäÓ'ºz «ÛzQÓÕœ>ˆiö‘>ž|ø÷í/Ê©]±êŠu±¤]Yw]Qmh—/ŸóMüxá-{ïEþXFª×4««þí%«zvö”ïºØ˜Ò]Õ5õj¿ìŠºóSqÝ!{b›mQÕe½yKpSy$ÿ-V›ØC²‡¾Ç¼Ý7q›ÊENU>ÄC•˜î±E|M¼ÔôÈõkÑRµß>ÆfTMõÉÞUm——ežª›ªòi›½Õª~cMÏ~£uѴݡ>Ò?1_ñ€¬ò.ó°½ö<¶Ë¦ØuõXbzêz®œ=üD0e¥ý9}s·xHã᯻'„Ï1Óºn¶y7ê&²¡0yUìö\íß©¶1ébû^þhOh”t¿)ª”“û¿S|Ý5±mÑÛ–ºš–MÌ»HU|ùÛË}¯ó­¨žOƒe™Õ„{&zW]÷^c¹8YÏ㺨˜æ—5Ï&¾ªZï«Ã·,ððQÎÿV(s>~jŠÃf»Í«•E©ÀÁÓ¬óeóâϧóÞ‘r8Q~«·)j8+®ßΉëÕ*Åèf¶o»z›"Í y·ÝÕMwŸwO)z˜ÆYYW1uðYw¶1E #6;Lå×Ánš<o8kðý¡¿ Z#õ¥â(‹º.ó&EÃóå• ‘†>ÝÔÍ2þØa¦SéÉ¡Y·ÉBI}ÂÌžŠrÕÄdBæ”-‹ås ´'à|ô£;rîŒ[¼í’Éø3vüä8²CŸnï¿%5tç8x78pS¨¸Då<ËKçŠV—t?Tÿ6Ûá endstream endobj 358 0 obj << /Length 168 /Filter /FlateDecode >> stream xÚ=1‚0…w~Åí@½^)-«‰š°Ú¸¨C• à ¿^HÑå¾¼7¼/‡ÐÂ.Á…k—¬¶d@æ³Ì‚»€Ô…PdÁèL(Iàj8²×ÈÂkøð”Œei„ª6tc{B¤*6ÏÛcÛ®Y’¯8!»z.‘MÝt?»2š• Õl¶¹ CÚBÒQ\†>ܹBöŽ[{ßÕ¡çR³ažø?òãÆ%_äi6P endstream endobj 366 0 obj << /Length 350 /Filter /FlateDecode >> stream xÚ]’QOƒ0Çß÷)úX’­´Ý«.nq1ÓD41·:hd@hq™ŸÞâ•eúÂÝÿ¸»ßÝE9¢h=¢ÞÞ&£pÅc$ˆ˜ó9JHDd>c(Žæ„F3”dè¿Ū3ßÁ„Ç <#Á˜T«Êê¥<…HSÖÖê*÷J¦§øSŒbsO|$›p5^q)roˆˆ ÷gWí¨ÕlÕÑëgYeª5}ƒ¾„3"ØJ–usnu^XÈÜñˆÇ)cbLDL™'rGä Ê“BÈÏê´;º@ ÑR§ª2*Õõc€k ÎzûâU©V– žº½+ÿzŒA}¹Et]ù!ÁÔ¾ikKë(ñ ß«TÒxšQê¿ÿ¥Ns}ÍåãÓÛýv i‡‘)+uiÆÉ—¾…µÍMžN§@pLòª#õïTyèoa¼)'œPRØcIzòåÏì]2úb¾¢à endstream endobj 495 0 obj << /Length 1756 /Filter /FlateDecode >> stream xÚíÝASÙཿ"ËdAìÎÒ‹Ã\ni%]̽‹–4±k’n*iF_?B"Wƒ¨ól ©§h«Uáå;ç|_Ôš¶¢Ö¯¢õë¿Æï%ÃVÜï¦Ù ×·FiwÐ[Ã4íö†ƒÖxÒú½=îdQ;݉£ö¬èì$ì]¯^wëª)ªfÙùßø?}¼×Z£îh ÎÙv³$níÄY7I{«GÆÝߟFíýªYÔ“Ó£¦¬«KÏøøÚú}'öÚÝð`çC´Ž¯µÇ{½Þ'Ù:éuGgѺ×í¥«dý²Çq»8]þÌÓ™< À}ˆÿþÙ‰£î(},Í’v±˜—U=«§ïƒ‘?í[fpw1ö¼n_β¯:iÚ.'Ó¢ ÆØa×2€ïc‡_ªÆf«û¼È—§‹Nœµ‹ùg§¢?ͱýÌ:|)‚õ¶Ú?,𦬦¢Wl}À·‹©ýœÛ|ÜéN€P½9T?yW.WmŪÓùëb,'‰s¤_ÎÒëmçº;>ûÕ²Ég³|S¿WwÕÀ}»öžnw àM±ªWÎó²Z}õ¶¬&õÛð(…K€Ÿ/Ö=¿Êrÿ¢d±lVoNfuð´gOÅÜiІO]ìºóùô©d=×õE‘OÊjºz3É›ÜpWððcôèëf¸>-–G‹ò¤©7\aŠô±÷)_=5q©d¼{øòÿg(fE¸k¤QÀ÷˜âß<Âíí·Êp='î Âe×D¸ºyS,>9ÃyþÕq½˜çf[ŽlL²Ðó<¯Ê“Ó³6ŽŸÞñX¹‹¸7¹'¹¯ëaýÛòjêùØáåtZVÁ”Zzàaç†(Šw'‹Nœµ‹å²¬«õ¼²¦^½­(oÖ}«âí–å•r€ŸÿÃðYYýñyÝøÒçàÕÉÐÿ9ð“ð¥ßN½­~;žÌʦٲwÐ ðÅiÁ[ư§ÅÙ­Áê"‡]TBê³ÂÕÇŠA½>Ÿ~|Z5›jñ ò€ô·ôÓI×Àð0ÿн:C§?ìfÉåöß½õÝz>Ï«Éêͬ¬Ö‡‚ʪ)ÇùQ¾ke_÷¤ª”~ݸ”ýª9?-XONÏ÷Â%¦‘•ß*§^Ýý\—ï6œHI,0¸Ã{©ÛÕº!ø“%Õ8sj|ËS”ÛÕZŸL&Áˆšºøl Þ=]6õ<†¸Ó¸¶??©ÍAÞ¼ ÎR¾À}ÏFƒínÏÌêªxÕIÓv9™Mè™±š?"š-ƒÍ“(¶ºà>ÄÚÕÈé¼)þ].›zºÈƒÇè’¡¥ÀOÕÞe»¼³žÎvp>—-ÜxHkзҸÏ) ¤PB:le5 æ£ÞÐB|uõ*‰n°U· fŽ˜ÇÚØà’Ž–»³zYL´‘·Í–ÏÊe¼QØw2üÓrOÂg÷:î>¯ÿ,ÆQÒ®òiøqª2 ðÝ£Ù‹bVç“§y“7ì#¸~h‹·çð¢¨òy°¶–ºÍ¾:r^ÔçõŸÁÈÙ‹¬.¸uä\–¯:iÚ.«IýÖZÜ$Mæáò][[ð0cpìV9´‰ ê+øx¯·FÝÑ ´ÆÇ­d4ìfqÖÚI¢n­CvY–—¾÷—ñ£¿ƒ*§Ò endstream endobj 203 0 obj << /Type /ObjStm /N 100 /First 846 /Length 1665 /Filter /FlateDecode >> stream xÚ½˜ooÔFÆßß§Ø— VöÎîì? !ÑÐk‘ r¥¥/ÜÄÀ©‰Ý9úéûŒ3gÒäL«¶É‹hûÙßìÌì¬÷ì¬3Ö8“ 'ãÈš$†Lq0lȉ †|͆¢Øb(cÐøÌ.œó°æ¢qBqø lœ€³X2®àžgã噯3žùl|[`ÁegØ–…co¾GÜG&GøÖpK&X;úâ#XøÙ„(¶˜ ~£3‘xá06:ù?š(óŠÉÄ ßÉŠ%““ýÄbƒIsNÙ¤"¶˜Lð›Éì.{“#æ•£ÉEl2…à·XSX,ÒÁ-lJ‹üY !Äd½\ ƒÓö(ÙÞzC¸…;’!”„á{…(Éᢠ`—…‘ Ré¥4—ÚHÅ!oAFuÈ“Ã=»±ðŒGòÈg¹ ©0ʃ;ÂÈì!Æt‰|¡J,AÁå…G(h4dÉ£Rd†(Exö¨EÆ|xŠ b„U$0*IAÖ“ÃðˆQ9ù…Gö©`IÁAÁµLÖYYª1È’Å(ËIö’h‡ä²dÚ%Ñ€$ÏemyFò}’\ñŸ0%Ÿà?ËŒQs¶VÄ‹Nò;qNà1;È=X)Á`õsÂPŸ Ž­_Y.×§íýyø«üpج»wsâ¸OìÏÉÓ$ßœöÛödN˜w§ëí0'*“¨oæHlw¢gý‡vÕ?oÞÍEδ“¾lOA|Ü ÍœÔ}–vÍÙ,Ñ–ÁýœŒ?˶ë?ÚŸÖÝIÿûœxªÏa3OœêrØÎ¥ÓÍ£nX?:]7ÛùZs¾¢ÿRnÊ5Ý·Ï7ív»î»™Á^ñ²éf«èšxvµwCù÷Sñ_óóë_æ†ña?¿^^tÇsúpM¿vµž]H!^Ó¯Úsu Wëºê_¶oÛMÛÏ¢¯–õÇóLäI7´›Íé܈«~Õn~E#ÏHãçÊ ¶‡ö¸ïN^­Û¹Å§â®šwç¶¶sÚ©¼«~N2UóÅÅz._qªÝOÍzXö›qgšO…û¥ïÏöŠ|E»ì_lÖ盪XÙKÉÁûæ¹6\™¥õÌ«öbû‡y»éÏL?¼Ç“óMÿnÓœm÷Aˆw®~軃yþixßw_vCžz1ð—µñ þÁÓnÀÁ×æà«¯LÓ$tØ47ûíHÞ¼Ö¼4õr=¼‘Q,êÕ§óÖÔ²I/êƒë°¶xñfQ.jlýÅæ¸ÝŽïâñÖ³ödÝ|Ó4G7Bñ•Å›5ã’˜ß,€Ââðâõ£^Ý\u®Æ+Ëð` Î ÝÅéé›9m £6ET¯ãÿMËÞW Î69)ýE¼D.Œdp‰cNã0<«Ÿoúc´¡9BÖ/M-ƒ™†íOgŒ7Ò‰óËL'ÎDÿ({#©—G¡˜ÿKXrƺ–œÓÿMXõ£®ë<q2'9Å]ZRëÔzµ¬6¨j“Ú¬VyYyYyYyYyYyYyYyYyYyYyEyEyEyEyEyEyEyEyEyå’‡Ó¨ZRëÔzµ¬6¨j“Ú¬Vy¤_Ô‡¿ãÿO×Ýo‹ú›~sÒnƭ¾©¿¯ŸÔG4þ#›Ë1v%ü:­$%ÉËÆ. ¨*òƒ§sO ºGæúû‘.ÿ‹.TÉÝùç’+iÁ»òOx¡É—œœMå÷1W²Ô“§*&ÞïŸo!~õ?ÅGþ§øm©,¶8çgׄùêQöúwt ñ«ÿ)þ;òO{´\„_¼$|ù~–ÈV6î÷~ ÕWïSðwã}Š=—*fùЗ*ùŠ&³Ie¦÷lº…ðu»øïlSÎü˜ˆsøÉ‰a̩™jÿÈÞBtSîjSbª’|„ÅғﻘONeÆÿT?;'= endstream endobj 506 0 obj << /Type /ObjStm /N 100 /First 912 /Length 1972 /Filter /FlateDecode >> stream xÚ½šË®¹ †÷ýZ&¶xÓ0˜$@ «ÌìŒl&ñ"`g‘·ÏÏî*zSµŠ%À]§|šüô‹¢HUéR‹-,ŸVÄ>??­8·×u³¸7Jï†ÏYfÕ¢³®*¸àÂÜø¡SÂLN˜4†Íi…Ý`tzáÖau¶Â£Âìì…§ÂîEj ;³úÕZDyà‚‹˜õ‡U[o¸£Ezu\€v¨á‹̦¸€Ñ «p#¸€cõŠ  X‡e\Ãmc.Ú–Yb¤0È1$ðÇÀ ã®8 BӃ܋9lXÀ5 ;0Ún îŽø:{~˜ô ï°åÞÅŠ¬woAãÖ`^zñ>Ãá(>%ìÌÒªCkiŒ©2åÒ”a¢73XV-Í,CˆÖ ¤AÐ6`Ì´•6=À{ésŠ”.géj0Ý:Æe\z«õa¸Û;<›iéÑ`f¥Ïø:B`phc­ ÁÿcbÊÐvFŽI3›e4ŒÀ0ìÑý¥Vc´‡AÇYCQ i2¬š[™Òaǽ̘aîtŒÒðÍÙ ‚`öt3æ¡EÔUÀXCØUîö0"¡9ÁUèc ‘Wc˜\® aišëˆ)ÃŒq³8¼‘iБ‘ˆ …Fä”cnðq2;Û€ÌÜ1»†ÕÀ<0DëðÁsÔ¬ŽI†MѰ¸{ð!a‚¿eif,–îòøðáñüù¿ÿþTž?~þüåëãùÓ~ùúúý/ÿüü¯Çów_~ýǧ_?V,Úú·çŸž~þþ#¿~y<ÿúéï_ËG›L¯èh:FfsP¬‹Ö>ñw?–Êó§òüã—Ÿ¿”çÊo0–ß–~xàßÿÀ³’ q *i`Õ‰+E8"ÆIÄ®d|?€TàHv0)‚)“"4ѹ@NÒü@Û€T`@* FùñNŠ”EZÛ À\ À ìH„I‘‘;€ðiýÀu@*° €M(Ò-öZìÂöþ'ÍqÐVÄÀ ì8cÀ§QT Ǩ€Ôb5\Œ1p¤»R¡Ô°ñaPrjãz3ùž10Erœ lHºPDˆXÄLø0êzíþ»îçøßîsø›Üçè£ÁN8&UTëÞ•zç€ó¤»R¯(F(Ë*Ú)oBƒåÀe@*° à¬]' ô 2)² ;£8¹ B_±€T`@* ƒZlíHËhТ8¹NÃÒê€T`@*À&:_ÕŠ<@ØŠõº E_¸@ Ø `ëE_‹®Á‡^ØÑÌÞnV¬‚ Øp*`³aî{Q(€ÞÞQ¡…¿ö?øû púOv¤Ã©Š!ìIg][‚€±@àT`ÀÙXG6 yTæQÍ›jl.Ø O€T`ÀYX«4ÑG[ˆjÀz%õ;ÿ¾@€Ã ° °hÂüÕyœQú¤ëHëŠ%p¸ÏñïqŸ)¥HìhÎG4fh X¯sòŠ$xäøw¤(Ej‹S!©gÔdCnú€T`@*€R$‰Å°!är]Žª®H‚@*° ¨ŠµÛ€b~=m!o7ËÐV(p¤»Nt ùDDs&@F­ú À‚Ç¿ Çq{¤Á(;6l‰S¯«« Jâ Øp* (>ÙG°T‡×ňñ‚%pøÏñïòŸãŽ 0² –ža_º^‚& JàT`@*Ð…j<,FA¬@JýæŒÞdÁ>p¤»R4C-ÞÃ3P¿9£7]PŸ©À.€TÀãYe ñûˆ¨Å!ÉÍ2´«àHv¤:‰;¿?]^ xÜø_PŸþS€MþsüÈÂïqCîËMòYàHv¤ÜI\ãm\jŠ, “°0¯Æ‚ŽàHv¤µ‘¼ž RÖ×KtZoÒЊ§$'@*° àT€§S¼úJ?q@€òLí& Í;Á ìH†½DOÔâ…òxtwsFëu…À©À6€T +Å+çÒâLÆäÄ7g´Î ©À.€T 9Õ8$FS ŠÄk„×›‘Ë‚¦àHv¤nTã¸êÚ¤$7g´® ê¡ Ø ˜k{½B:â{h~çA=túO6ùÏñcóõñ>2”ælñæÀMÚŠ> stream xÚíÝKOÛ@†á=¿ÂK[ÔÆãk¼¤@ªv¸ÊE]¸É$X"dO(ðë 1´Ü&J m¹¼ Ä ¼8›ù8s<ñ­¡å[Ÿü{ß?æ KÝ0´2/K‚ÄÊ–È„tB+Ï#+ï[‡ö¶ÔË•.—Ë¢)«¡ó-ÿòàQÖ¡+Bßö ðâÁMS;p\a‹ÇÁ·\á{YœýŠH«….L ¢`žÁ<¡síì¤vDÇ–MSªÊØÕà‰9¥3gNÙ*ª¡4Æ“NL‘þ¨¬3V–Pa€7•üf; ÌhµÀ»<Œ»ŸööŒq!¢âÛì|ÛìÞ~w\õŒG2—ÿäSgžåµõpzJéÜI)—_2wâØ.GSty…€ÿ’mÂyº/¹“¶<ÓÆLt¨3À‹Ls“$§¶ä@¶3+UoJ£*¤Ü¼ãƒ¾ßjç¤_hù¹Ò²>-ŽM9åàoŽRÏœ]v!„-ëïª1ö}hû¼‘Ó?]Ôz[öTÕß̨Ëæy.ºW¼Æ~×dÖ©^ °7R7Æ’uþi÷*š=Ì)Î,àÙÃöæ¸4ßõq9ð6sð×ÉA©»ª^9žòÊŒH3 ðÔ výÑ JŒÇþ”x‰™Ö B/âþ}<½q]êsóÌ‚2ï÷µÿÇÃõR7ò­ÌË’ ±òÁuÔ¾LÚ¡ÆmÐŽ<Ç bßÞiÊjØ.¯/Â7íσZÚ•ÒG²n—'µÖÅÈxs@ÂÍÛslØžÃðÖöüpw^W•»©Ûwã\©êf÷½ú„²é;°H8ÂÇôþóMÍ/‚(¡Þ¯(î­ßô’æî+ .à5ÆŸ•mâYY\lEÕo]U뺨Ì÷Y¤Tà&{%¦)dq{ 9^ Ë"áEaÔ²òôÎß®å ?Îèt endstream endobj 641 0 obj << /Length 2649 /Filter /FlateDecode >> stream xÚYK“ã¶¾ûW¨æ¤©šáò!QbnÇëL*®ØUãøç‰/EÒ|ŒFþõéî¯AR³g*_`£»Ñ(\áâûoBmÿúü͇Oñf­‚õ6MχE¶ÒU´Ø¬×A²IÏùâ?ËoO¦îlsÿo¶Ë(@ûTvÍ}.«¼ßw®*ïÿûül–qÊfáâ1É‚UºÁ>ÿ¾_‡KÛ·`n’,² Kã”§®6Á6ŽцXÉüü(аà1I“¥k¹—ÃvïlÙ¹_Ã0ÞƒPU×¹òzmöŸÍÑ =u@/Fw¹4®ël ÔƒOo&å®mMçZþˆÍ•躟€…A¶Vví«kñõxMx‘xiyË×íNöŠÞ…Äm—#K_yÓܪÈšR;}y6®ìègù5\‡…ûl±¢>ò·ç8Äiºü=¯¸·Y^Nn˜WÂ%-¨íëºj: uÕví¾qµUÙ‘`ÖÑƺj³<à,ð„9¢=ýퟀ«À¦+ÌṴ̂,ÓÈlN&G§o­B$ÛÌ^¸¦ñš¼B v¨cê íé,° S4:–=‹Œ™&ÖA¶š±Ã(ƒ’éç¶uGQ£]tg1¶¦uÅUû¯di­Ûö“DÓ½FkdPۯݩ*ý`'w<(ì‹-ædV˜òØ“µñ´XŽ*Æ ÀCßõÞâxì:´gsÅÌJKòmMsŇÄ屘ÙVg[›¦ku¿­!-ˆ)óLâëqŽ_e6WdS :mmmΆ®—»¾ØX\Ü»¸¢@o§+?ÛÚϪ°Ì”›][}§óήtçþ ] ò¤ëüÞ¦hu‡AÁ3Ló ãuНP[í~³{±¸5-{&¹D9˜öäD<ºœÆ«Î˱"¯öý™ÖN¨Ýõ®P¨¯ºbÁ°ìÏ;Üâà€jxåm"k©99ÛáeºŠ‰ <³•Ä[Ò^þbÈшr—PÛ·ðkÔ……¢/\oÙÈL7EâBWâªs*e¥(ª‹_fýöt¢ö±f³|õ§W3Þ¦#k› k8\nÑ¼ØæŠùuEž÷ÐÓ‡ü= 0|*sµI0·3<¶æÅæÞÅ„q¡†ÌúlÔ9uWÍÀ&õñý9•$é²³ûSYÕщÛ#dgONüz²¾Ñ\¹/ú\—þxý©C]‚AWEAkU9ü -)¯TeKõºï©(!þÔ= £á%QcH Þ=¹ìÜÁ… ñ6Þl¥N‚ˢα15ífŠa]=p¤ßüþç'¾Ýã>3ŒÒ½©¯c(P]h”tö¥†|¸¤îÙ|ö‘¦+QªÌ Øp!ÉQã–ùsÓ™Övº–Åþu'¢íY†}ðRQ)DÓhø ð©Ø­J Ük†°£{±~c‘öÍÆtTP2DQç:kû§á6¯)8Ý M©—i½Ü÷Í‹}@yÈv…ûH´ê|0’Ÿ<)EýÒŸÏg6g£ŒØìí „£GR¡·vw^ÅA•øš$oTþ§7~N©Ÿú†C⹂D%,d·77 —{Ä• ñZKŽ>Ïñ©LrPa…¦j„ex4Õl‰¤§"ýŸÛ‡qÖpºó~ŒÀùœÚuñÆO~¦º`³pÈÌœú5å8TŽ ¨¸D`ŠÂ‹©auöRUiGl­ så’"˜«)}¥À•E–¨{¼§3‘L¸ÆëWÖŸDâ1}´¬ÆÒkW’Èdû[ÿ0Í¥Ô˜&œÇA¸^m}5Ó9Ô~q9]0ÛÍðËjIƒl«%ØG¾„ñ$MH"ï##ÎÞZtä²kß ùMÕL‚qÎ$¸UÁ.Ä ù¤4ºi­EW ˆŽyzuàduÆ~äþl¸£ëÆJß$£±nøOB¡óÍjùJnk ¸qLÖ¸ht×Ú¿ºñÃÆóý8űߜáurE®±‡™ˆ)‰?UôâíxpƇãÄ“»G“\§ á ¨ã°dËi¶ƈ÷% ;o‰·…  M³þ yÞV¿¨6=ðÖd -ì·4õò‹ºçŠV\±Ö_o‚)0/®êE0Õ?ÏSˆ­Wu¸öS¨šg–¨ÙV+K(ÛèZÂÔa äöŸKª/@3®J­zAšWqÜ„ ¥­:*DNg·@yòsóÜÉ«„ RgÏp Ù'!¥áûÎÝstdVðᑺ5!òæ­ÂIp“dêÓf‚ö6HljüŠ©NõI§å@©dª4²C’™ Å ~ïÝpD1^e)4¹‡ ~Üó¬On>èûìÝÁ•%q¤¤VJmv¦AÇ\øõV ±ÌòÎ…a›µ'¡$Ž}¶šL²U† ¾ÜÊß ÃŽs²®òëm¨Êd5Ò/C§ë«Á$â•þ„yo,ƒÖ¾Ø†Mž§°}<—g5n¥–\ñ“^ëS¿²0FÁ“éhOJ›$‹<š}¢Vö¦u.žÇØS¼úøè‹Ç77¢«/¤Û“’sOõYÁ¤,†Ï›àƒoDJíÓå Yö(Úl£¼Sg§Ó—˜¥c]C¸oNÓWPš…°ÑtÛ/7Ÿûó)ÿ­o;Hÿò¢Å05žÂÙQ^†Þ-ïlër;<žíoD%ÌGM|lGA¸‰È—…A”Äš«Ü¬øîù›ÿßË endstream endobj 666 0 obj << /Length 2933 /Filter /FlateDecode >> stream xÚZKs㸾ϯp|‰<+sø–tœ¤2©Í!µWªR»9À$qM‘Z‚²¥ýõéî¯Á‡LϦrð¡6~IáÝþ.¼ûû§¿<}úò-î6Á&ó»§ÝÝ& rVë8Ȳôîi{÷óâ¯sêlûðgá" ÐþXwíC´^4ÛsÑ•MýðŸ§| u_ß~ù–$£Íó,XѦqdØ: &눙pÌ ÷hBH÷m¹eâ›=ã8ÈÃ'z|xLÖá¢hêΔuYïy-šÚb¢iÑaÞbzßšÓÁ˜úŠF>'³§ªéœ™pY3}U¾Xf‘N®£0Ødð¶3î@‚âÏÄùâß›xÑœy°Z¦êN¶(wW ºƒÅt}>>³ülvh!ü7‡‘©·ºSSµ[NàÑV8 óN¹3^ q-̹kަ+ SUW†B|ÏòòÎnAÅàV6gšâÐ8« i[SïíÑÖ]ä+ˆ XäÔÒÁuͱå%Î*׈ ÒÖë¼îª16—Ò)uÑ©2…0œ)Ù\™“%²C –hîÙpŒnñ|Ž3g^wæX÷_SRœ|ªïwPñÇ( 6úÚ²ÿýIÈigžDdi:zñ:[ìËWyÜGÔqtÁÚ݉t©#WÜ“nMgt¿ÖhUƼ]±«Lgeœû Þ×¶¬eolMdÄÚo6ºr³–ï)ÔÑ© 1‹›°ü„†ˆB"×Gг®©ÊÚš˜±foÚ²;Ëb DnŸ(tùfq0¯vîJ ÃøÂªmò…­·§¦¬;~l<æý¹•]¸sóŠÚÛ:Õ½^ò±Ç¹Cõ!ZøÇÆc½¦ùQ÷0•kæ¸<GºpVnRFþ† V‰ö7IPežm¥tb4$á¹%ÀŽØ)‹ @½•’g‹£ùÕaÀÊš°;lF¯âhÚô4ÃCÀõ\ò®ØÿYphÚòw¶Õ=p@3¯¶ÉßßJï˜ÓIUƒg¯oÛ[@ÞG©ÊykyR«Pwþi“[©Ñk¿9›¬$ÏÅd%¹?ž ‘üF*.†'©½b„®P—³î2RøýX6 Œ-—àWݬªÀ'ò± {;”…kðYï_aÞ×ÿÃÚå7$?«ÑfÍoÅ„ÇbÁÈÒvדU¤Q³ÿVnéE)Ø3ËzP¨=á5-Z³USÞt«³¿WÇ!7ÏthD8¸GÏýH@O#­ÉÕéäñ¢nÔ ÝÙTèë™0ã ö|=¢(x.ÌZñQŒ;tœ%åöÛz©ä"•9Uíe™$9½i'&…ûôˆ›7C8$I²ÅOJÚØ[.š‹ž(·P\Zht‰A£fg‰‘»å¬’õãw¸8;¯ZâT#nÌ_m¨W6ѽþ9þfáÖî̹ÒõýX­_¸Et£…Ñ*ØK+ªùC·œx»;×}H|ãšÓ(X­&Ñj±…Ù  Œy©)îF”ùð™Gëc¼0ó*+à7%U¬ßŸÈ–#‚Óävׯ¡á'Á4BS{Å,Ë—¬¡9Ú®-‹™ ÖƒéàAÍ÷È-‚rÚâj»?ñ%,gãR aÙ§ô»YÞZvÅb€xN %µ?]»C£}{9a…s-sW²[—%¼ˆy½CKïê Ê¢¢PvV[I!Sò±q÷roG¬(ä=›½„§<þÖ´ ±sbœÞë_S ð8ÇŽæzKI8‘=ÁƒÔhμš’|we1ÇWgƒ}€‘+ë%ÈŠùÅ`Æc$MÅ(| ‚F¦žØ„ØâP—³†DQ´Ð8é_2°g÷;ɃOMË>Š2>ðIƒ¦“)^H¨¼‰çc ûjª³QKÃS I‹¥3¬ŒdóÎ¥õ²™ ED“É)[qž‘š¼È;ÑÈ¿ÉøFVé9V“ãz4*§ûdt)D–[qðÕ +vðC#š¦ýÙi¤>îI³À'“ Š%Œ|ÂÉ)]H¥koV¦‡a¹Œ|^RjzeUi.®'æÜã|â{—'ÌYÅsój¿ˆKúRÙ]÷¥-÷‡s¢ÛkÈAä´†¼§ú¶ö‘%çØÓ¸ƒæÓM¤WéËù®A×j¨/×[ÅÁ*ÌÆiÙ2ƒfpöqØ;xź”{ É*å‡Á_ýK$Xa"º˜“øB¿8@ÿéPê>d³½‚J+ÊÕ\À[ÇâȺmP{ñCþPœ°iç1;XÒÖåd6îÏ@]¶ ͉%n*¶Jà³"”ú ƒ­‹C3{kR7H’¤–î¹ÅæŸ:ES5g/­$Ys ZÛ™* £âG½çç}^ã÷ÿŽ#/œ9&¡¶Eæ¦éå3…îº>1½\Ñò’̵š?¯í3œ©’$iÄ‘|ó†óÓˆ¢kÈõÆü¹¯žúøs‹Ÿˆ<±NuÓ¡Wj»m¬«ÿ,ƒ˜ý³G¿¿=—5Jn“ífµ>Ôº±¯OB&ãŒ5L5c%¸óó"QšéãgÂ3>¦|¥¿9ëFôÒJ ŸEïiîvß´WÙ~2ŽožÏM7¿œ9­ÓÔdÅ.o6…¿Ö}ôªI›[­D,Þ; „Œ|Q1Zé6Ü3 “üHm/:'úB­5Ùðþ÷áuá´ˆ´”Ðç{:á(0DoçÅ…ÈùXêÓ¬™­“FóCa¤¯ÙѼð;’ðT.Ñ(Ǧwt•ëfCúâ€ä˜ÔV–k<ÒÝ›“âHy†‰¾Ë¥.Q ɉó0ÆØCê÷µ©ïх؉òy6~” /¯>Ãy—8“ƒ¶Jßžr’H§bä](þ ?IWÅ_ïæZ¤ÑzìÐÂX‰¥kÐŒB‰®ÒSò…B‚Ô7…ñÈðÑÀƒº˱ÅÁÎí쫃¤op 'N§‰[CþD’€iµßI [”øçžL¢† þ騇–LHq(_åΔ*0EU/ú™i(“ÎÝû½ðtB)¤Cˆ¶FÝÛ³Óªk_H]MWÏó?Œ(sµˆO… ¬~Á»þõê†ÇõMâJ$j…V›^n~Wá)ý:%ìõKݼÉïŒ#«à‰{£¹Ã?{æ\g4.î—è|àŒéƒ]ÉõŽ„,µ<:I‡ÕŽ3¨©/¬>×8»¹¡BÈY‚ðY‡^P§ð Íq}Uw­ˆÜä[P—ù"±÷­ñz£¾•{¾U¡Q¨Óð¯#KE5¶å~/ñq éq§»q&2ÙŽùÑÍ$ÉæÎ¯g7úê ³CE~ ‡A¬·Éó•‰x…þí8æšý­àŸˆeW},K½Ño¼ôõ·¦}.ÑËJïÀþv.‡2ùJÖ]II˜îÉéb1®KP]Ïz#cÐhl>’o¦‰V¿(ÖJAãRV ×Â-ÌLšúg=ÂJ’H0Ì—õ«äKZåJµ6&×'#ߥT|¶4bÊ*@î)ïZ³Ý–ªñ4šhHŒ 306ÔáŒ^t•úÖuåQŒ}ð{Cïs”ñÆÑGvJd$ÚöÞˆLã(ãV"*¹ƒÐlQw‘Ÿ, ,Æ»¨iügâ ò(FÝ4‰ÁJ> stream xÚ½ZÑŽ· }¿_¡ÇöEW¤DRŒI 'ZÀˆýÐÆðƒã\AƒÝb½Ò¿ïá\Ö›Ì$“dg€ÅR3W"$Š’¦T)”šq…,¥@JÈÕßk¤4gÈLý} ïJK¡AÓ@Tèdh\š+Ì)(Q Ì^…š ¨i ¢85C!f`>þ¨.€&ª•ÒIÔš Zrš·ÀÍ›[ ™HBvøbröæ–C@+!kI(À3CS1…‹­žÄàGššƒÀ0 dP!NA¡Œ…" ¡´(ì+æÖ«ûÆ^-“xè¢J'C’ FA›ÞÄ=ð{s]î;h‘&Ðê49øV‚²ƒoö)T5o-F°] èz-¨Sa®¡ pÐk…רV[öº†Bu½Z`Þ’ôÖЈœ—†ƦJ á¡BScèTtJãæ?¡UÎþSAݬèîVØß( è M(ŸT YÄß@³“¨ÐÞ¤*4«+Änæ áG3óY‚Ê•ý'hnä?aN¤Dø êQR>={v:¿þß/áüùÍÍíýéüêãw÷Óó߸ùÏéüÅíÝ÷—»7 S3½=}þÛùË74=œÎß\Þ߇7˜™±ÀA¢Y|VÕX’O³¡ÚçáÙ³p~Î_ݾ¾ ççá/ðå¯á³ÏNøûóö©Á.zƒ9LÞ3ú•@-[Wy:ë³÷“õáúQÖgß«{M˜5úÀ²h¢ËÖŸÞ÷n}öþ(ó³ófÑ#,ÇŠæ Z^1ßžÞûn~öþ(ó³÷ {Aœ$ ·`¹,›¯;Œûn~ö~»ùçÐe%…oÂùŸÿú37jñÞËÑ×°›?þøvµ.Y”äÎ BŒ>®üâöæ~2þÁ9+_›½ÀQ°V]@d„ÀþË4t§¨8¿¼»}ÿêÃùåóáüúòÓ}xû˜µ—ïþ}9¿„¥ËÍý,C“çæÃíÇ»÷—ÓÊ4½úÇåûÞ}qûS˜ØÄ’“O™\£'êå»;èœ|­?uÈX÷4Å1y–âRt–¹ËÒ¥t©]Z—µËv•–º¤.»>ëú¬ë³®Ïº>ëú¬ë³®¯v}µë«]_íúj×W»¾ÚõÕ®¯v}µëk]_÷[f¿[××G€ç WÙõõ^•Þ‡ž¸ôôã*©Kî2wYº”.µKë²vyÕ÷ö‰£–pDb‰œYƒiÁòY—§NÛaæÎÆÔ= À` äèù³šð´&‹öч;Ðí?Ä®ƒ rŠˆDl%&•Ž)ëŠ}Û€«ýáÿQö‡ÿS†èiK‰Ø‡ÅdË)£’î@@08 À0ˆ8ùEBÚjœ"v­Ë8ïÁÀÀ`à(#}A¶ˆý,6Òè K`be æ´CþÒí²?üŸòE 9ÙdæÈ‰Vì0f3‡ X”i ƒ0 @†u‰—”¶/3€ÁÀQRx‹„lŒc¯âu^±_÷Èá¯öG VÂRbõ! Èëyy¡Rv`  `0P$úy!‹?R+Qµ-ЂÀ`à(ƒ¬½~°Šìz6m%雸«õáüAևˆÁû‚g?ùËMVØ‹@08 À`€™˜ï†{}ü3ú£­ÄàºGèG˜Àö5V?È)~¦ÑüÁt–´¶C3€ÁÀQH±hà„€=7öñQÖfa³=˜ìÏþføo«]2YýΧF ËE–vXfƒ£ ´F¿ÛbÁäS™5±;,3€ÁÀQbÑИ@½éEmËiˆí±!žÍÿ@0ä'¹‚ 0qC(Ó¹²ˆDËËg2¶Ç†x08 FôÁœbËuÚZAÀ{µ¶`‡ÝÀ `0p€1È|c T°!9Y°Ç™À `0p€Á@r‰T€°1¿ÂÉ–%¬ì1 ®ö‡ÿGÙŸý/MüþtÊ³Ï ?aZ°Cœ Ž0¨•ët4Ê#¦+K±îÁ@03ð;<oü[›M7{—u¯ä>½¬{‚û¹B¿¸ŸËíÝÏ}âcI>b¼¸0é/n/Õe,)•xùörMqñ[æòëu)cŒdÊŸ7©õÓNÊ›ê*rÿ~eK]1`mTS¢muÒ¤² o±<} òë”™çþiÕÔf“^ì“n¬‹]äo⥚±ÎQÈ ë]­›g´I¶àœÈ/ö6ÕeLÒ²­¦ _ÙH?do¿Õ³`ÕóÕ¶ØVžQ ˆ3É6Å„Îðo Ÿ®rGAØRÓFÅZâVÞ¶Ô1`‡Ùt[?c=H*Û"D‰ôó¾Xùc×hýâQ´¿~+ñg¢}ÿÊâå& GxÜTùÿPË» endstream endobj 695 0 obj << /Length 2809 /Filter /FlateDecode >> stream xÚÉ’ã¶î>_ÑÕ'uU[Ѿ“™$5¯êÕ»ô-yZ¢mfdÉÑÒnÏ×?lÔ6ê%ïDI±PòîŽwÞÝïŸ~yúôÓo‘—»y$wO‡»ÈóÝ<ÌïÒ,pã8º{*ïþp>ŸÔ¥×íÃ.ˆ=Çw¹ýZ÷탟9M9½iê‡ÿ>ýë“' ÿô[ÎVÝ^à&Y@âÆ¼è×ÃÃ.Œ}§?içOÏ úÞÔGÆ_T«Î¶íxøYµ7é̱6H]¨º¯y`fÎLì?Ž‹×Œ1=cŒ,wmÚþ„<¿;ßsó8g¾îë¦=«Ê|Nîù¨ýƒïœô™;û·ª,‰× ÊS Ž»Uôˆ‹†GÇD“Aý÷ HtLÑÈ-®»ÁY?ò`I›ƒôíâ I¡´º²ã ³¹v`Yßõ’ÚØÍEÉ¥»Ò`äÍ4(<¦Ý«‰WZö37ñ3K³9'9ŠœA@qýëÉ'/UÓw vÚBxlKÕÓB™£däÔ´æ{S÷ªJYÿY·½)‹Ü K 1+Øpο‡ y1—J3÷‘Ý¡§Hš4V7\.`3º”¹_kÆßÙò†‹.ïyèÜ”2‰P¸é–NÇ-‚E  ]"œ‘—z·¿í°åÑ™Z¡ÌHØua{ßõªøFÜ sƒl&iþnâdâ&N²QÀõÍEF/ˆÕ[FþéÅž±3Ø4SG].,¨KkT/{”†w,ä ]Qˆï¶Xä;—Ä|°¥ ’„Ü£ˆ¹i(¶r¾È{ñ/(›Î ñÌ¸Š¬V4©ïŽçͳ®·d-|ñ©íõQ òYU-ð䱂Ôw|F—+3TÊì·uSk†f0.ÿ¶¼ lµçÎfl0‹—®g%{b`è—D©Æ^w\mò _€YØè y¿ïx‘ëð–©Óõ· ÏxðÞŒÜkFÐa$VpŒYA”ìÌh{nø™›NcÄè7ùM‹Ó&ù3HÛ“¿„lÿŠ$Æ«¶t¤¹›B”œKA¿éG™Þ›ØÚp£¹›Ds/êEäE½ØÙ7/Ü·~p¥î pb­Ù“`=1{¤¯éÅVÏ.ã)ôN«’t°O±ñe‰6Ak5–”ý)ñöƒ =b³÷-’¨šæ[Ç Ë€{½gŠ_(ÈØæËX—%rDN& «éO¦Þteb(­ºœ\†éØÐÒªâØ¡«êFŒk?˜ªì.B$C¤ =¦æl…× ¿÷MeW.·X«L­%i!3‡lD’ _N×>¤‘#) .²L”zýb“™z5„²c¸çžˆ—išu¾u5åQ zÍáÓ M2šªj®Ý”Ý@3^¦1a‘ ¡÷f¯Mgª)O²ÆN…ð¬[~ä‰ôï'²“Ÿ:guc mˆÑOŽ­/zDÙ‘/:A”šòÈÍQkÀ€ã¥lÐD¡%ËÕ¯ûú ù«îŸeœËC_ôE×’)fH†w@„ÍØ„$c³t"aEuÓÌ×\úÁ«ag›‰_Ò ÆÁ}˜}lNJJ›ˆfCð±Mg8#C4™Ã|¾œ†PB´—±î¢ *δìºíž­¢œOUât{î^É9 QªÈÀc9­ cèø¡ñ¼XïµJÝ~¨‚<7&iMq|» â ¢ìÒVtÒ°VUæÒé óˆc7‰—áœ.V§,[ueˆ¢qîØ„S ]ÔiÙ“ QÞ=d.à||ßwdŒTæañ¯äNøøÑ2_ölz[ŒfL,~bnWÄ6E?¯<«_ž2{r‹>»™ °zEruÔ¯yÕ0¿r ÍÊÃÜZj>sÐÒW2i/›T†\¥ÓzdD½Níi[ÙMt³™Ûíû–f ;½g`éXA›3ˆüÜ–aE‘›/ªm lM‘n2(Ð^àj1»™¤Cò2QqXšÅ"#Ñ ºmD’·“p±Ý.È%wA`e`€ `öu̵I}%Ô{=›… TFç('¬úZÌÆåÞÑdêfKEš7IäÞ,ßÛÀT˜Î=C[wprIAB'\s±Œ¡„¼ëI+N[c é‘à±Õ'µ<†út%j0+ 9ý§÷«mÀ~«b_À^ó½Koý…úáBú½#}¤ÉbªÞ [VaêNñà‡” "ÙyãÉ£úªq0øÂÝ©*Ešš[–º?tD~^,7¯ÉwÌÇÒ5}sVô’EþÊx‹¡R\û`¯û«&ù§¡}Ú œ3%çá,•-´gЋ`C,‚7ýÚ ¥þãè1+$­õxb=€*NMGŽpcú‹HjLöÐÞhÀ€¢â_V‘ ´·T£°7øb»K%ûOÓqoÄA6kžMiÓoÀÈ¡mô,ë#ûZg”\2S3õx¯ôC©,ßHÊ;OªókäES5TÉm9h@‘—Î^#/™§:x$þ:ÓõÜA¹a«¸‘ƒU´<ÿØšrIi_œ¶çÞ8Øh6Q²£pf6ˆµIaÇcxãý²š3ZbÁ nŒ&Á" •ˆ-œkÓ ;ˆ™3lç@8{' ÑÐùК»þn'1bRÈ0r¨z€þ¶ ¾sCk´¦ (ÈeÅ+X C°!!˪|ßlnjÊä™ßþAžIyÀVÉ!Y˜ÀÄ!¤¸ACÃvôT¨Gu ¶T ÌꜲ¤œo¹eeó2…—D(tWʾ¿¢9ª.úÙ‚éþÄ}ïØ4mM:V¡#Á»YDõá,âÒT·#f[g‚žd‹4‰¬ô"ïÜ$-èß„ 15Ý€gW,‰æw ç ²Óˆ —{¬:Aï$%dvâ>RhŒ$£C€òóÈ›'·¬c§qœˆ|®h""ÀÈ ÁŠ›1ìüyÌ,6½Íû“ùמÄw´á·G_q@êLÄ3S€“‚PüÊ—¯TÁ˜çØáËLzƒÛþ 9¾’œ´9žú×*ãÙ+$¤ñIoî~B7]U!ç·ÝO°ûš2¦Íü)LOžµ;|1‡¨Õšý`?íLü¦7Oß|f2×_>³×¯£À67¿W©%‹<Élþ‡M#púüØæ0Ô¬X—ûübŠmeB a?57GÉØíì,™…mÕÛLzúç­Ëjj½2 ¾"µÊ~“ÃaUoJ\.Ë+ZE¬æ=GSÄ¢’³½m›LÎD幈!–3Bh*±çÍÃ=¢ŸÁ«=}B®|AP•%øb->6cë{/u®Û1ãNÂUŒ‚ÈæÊÃwÄßB°UܬՈ81u$âH4ÍUðJe‘<ûM:¢W_Bþ_®~êòÐõÓ®Qব.füúôéÒì€ endstream endobj 740 0 obj << /Length 2839 /Filter /FlateDecode >> stream xÚ…ksܸí{~…«™Î¬§¶¬×>tßrwI&í=2Í^çÚ^ÓáJÜ'zE”¼v<þïP¢l¹ùàñ ‚b\œ.‚‹w¯¾ß¿ºy›„©Ÿn¢ÍÅþx‘®ý ¶»È_¯“‹}~ñïÕ…h{Ù]^Gë`úô}_÷Ýe¸[5ùõª©/ÿ³ÿë«€åÞ¼¶aäëd‡R“­¿‹Â‹ëhã¯IæÏRè¡“•¬{SoÞÆ±£Fpqnüt·%î•îEI}uy¯ÃÕYå}¡i,ꜥ¬O#VÕøVÿ¸ Ãp%ý•ð™¨ipôÕ­ÌÔAÉÜ réSÕ÷ŽÂš#áruÄ­¥±À5êo4üt½#a_À€ªâ^û4ÜRKª:+‡œqÐM9ô åv¯Îõ X?ø¶Å½V™(ŠVC­.ÃUo  ôO¼h˜UW3SR0€Ã©;ž ²ÃµlVCl{&Ž8³oYн•„h}Ÿ •<‰˜Iê4ÑÎêþŒ(iÂIê~Iï\Ïht8ã†x$ø5ÓqЊ“¼¢!YÕ'×f|x¥)1À!JIw@ âFe€4gŒý0qX{y×Ol^¡€mÑ9ÿ ¼ÃùS×µí$-%ɇæV:¼M‹YE»â›N]/mækS÷¢ôØ!8Oy·²ë1>´q'5®ã¢Ö~Ê™ÎÍQ²]µ²ƒyj‘­ Ÿ2û¬il<©ÝfšèD%!ebä#âýqä¹'̨I!ΔZÍIˆ:ˆÅh ¨ÙlÀil>Æ„°YS\AæªgŒåeg$ìY•%¡û¡« Gºå>áß×O惃V” (Uñ† ‚0ûË4X©Þt¤P·¤¸¼Ë¤9aUmØæœë¥ã1šl#Öd¯ÞƒýöþŠÆF«l •)spä%z[6}oò*’Žôô™Œì4 $¢>Ù çB²>°¹"ˆsa·-­§5Îu½tL[ƒ¶lq`­Ñ°ËÖ‰)°bKöÙœf§tcRù”ñ>)C~hé ‰¢¡9ï™YΑ'+™'°‰–i[ ŽñM“!E½ä±&Qƒñ¯æÖ 35-F.eµõ¢ˆ<¢Ÿ{Îx–d0͙ٖÙçV ¹ÚG1”8!Z»&@Ðͱ‘XÀçîœcSóˆ‰(LX`_Ê«y½(ǹ5 “ÏBLœõ=ÒÕ©mA8ºO®0%°äAƪÉÇŠñŠéF x£Ý(¬="åJ”͉å5w4ê\vÄŒ2NÝ{iÈÊ\ÖƒOÃÉÒ¸Îä´ë´ñg45SéÓr–7ÙP™pGA¡BÁê0ðL*üceŠ[0À‚®Ñ&1÷î]:ÖgˆDË•’(t~€,‘›„‰Ø×„›Žrãž+Pœjà‰@£%"Œ8A¡Ä(Òj©bIíñ‚«%û¼µ »<]·6ê§uÙ›kZÉ·4J1'>Þ˜ß(”ö—›N-\¯îúoK³û6bGÂ!ׄÀˆRˆžJQpŽöSÁ÷'Wdá߯KõYÒ 7“!Y'¢šÅ§áÞÖ)¼Ö€3b¶X~º¥¿5d;´.-€¼UKQŸ¢Ø# zžA¦‡ADr&Äôa?Œá. ð€ª[gj{[$Áx·áµ_–}ª+ï—6 Á”Xbo“a˜¬þ‰ÆktÑ eNcQê†F¥¦^‡ac ”÷aCbOW§Z³ÜÙÓ iCÛ6îž!Éz±Bõ>Õ#^|ÃÁòþìý×å8t"û,-} ˆ]C;^àãYŸŽ+ÇïÉ-â=zK×íGÚñË$šÜ@}_šRÀï½ï_ ãäõë+" l áÀßËÞ(”ý(ËõM«UÙÔ‹¯K»ÐNî^Ô]hSÐìB†î«Ö|¦»€Àæ EŽKÊRº2áNÏÈxv¦Œ2™Ë0¸ì¹8ÁÛ£|†ÔÅ­‰Ê÷Ï&&iLXyšAä€#¬DÕÔù s«ré"š^~1pÂð¹ü>8ãÉ9¼òþ,¿ Êîâ:ŽÈ öðRØÉ-Õ´FàäpªÚ¡¨úØß?• iØ…šÙBŸ9\,¹´á2"ä‹û(å±jƒuHS¹ •Õ›,µ‹h]`p€ÚØ|„”HñÚåkrSŠ`%1Š_P»™Çô6µØzÐŽ‡6mI°eÐ%xŸËЫJºXdA”.Jv­3¥¥°D7 Ú']®Ý¤0W6+q’ÎPÆ>sdÓö‹¤”e ‡9K'Œ;£S§âÙ¹Z¢¶Yk„«9„½`xý¥#OLÄ Q.¡h[²}Î×>ãë… û"„²êZ^ñ% §Õ“4`T–=ŸÐY·9bÆÓ›´ž¤”Äz¸d8³(ö$;a¨Tþ¢’CMÁe﨡žNuĵO0ŠS¦°[œöR±˜ÛœýÜÂR:ÐYæ'¾SŽð¯°-IïP€QsÉö^¿ Ø”)9¼­TSc+²)ÃîÑÕ‘ð¶ùä–;Ž<Å]bÁâŠêŸËYE‹…ܪ²ï†Ät}3ÈHKZŠŒ zEè#öµÊHk¥5¿ÔL9'Ý>®}Ûç¬hÖ—÷\èÑQU\µ±”ùâ•h—J¸_©¯©[¾Åa`›\šˆÎk4µ…,5È€ÕóÃ?èbŸídbÀO˜}a:Jáø,MùYšâë.'Òq¹sI¢5ÏE<q€§¹O'ñÖÔ»€å×Þƒ°ê õYLþèÑ\(ûÏì.ž oƤæV²\ï@ßÜói°·N`;‚K/\çGñ \ZÛõ¦ó²íZ{¤"›ºƒâæ°tx¯ÍûÄ´×Jžž“‡ç M¿¼!êäÛ5>¡÷Å|´àz¨E%ð ;Yvy«gíý­í!Š:æ b¦s§úÞ<ñdžÿ–µÜ®p9Ÿ†ä28³÷„:°xp™ãP²þi2 Àb’¯´¾^n|àóh»eûª8˜õU#¸*p_WDãß`4‹|8™S’}\â¸=dé‚>¦tóI¾}™.ÚÄMb’‡›6Eú§|†ÂЈ“I6á@«¯<2¿î¹$:6U_îé¯òá/‘u^c"-Z×eŸýP1N‹ËÂ9òóƒÏÒÕ¶oÚÊS½üS¾‡ìOPIjF³×/Àó„‹jsg'v4pº½U/J•=húÉ °„àÞ¾M¤Œïõå”6˜»-LH¦‰lix„4Ë[¢¬Úb|ò‡ìmàe¹Õ$*ªÁòòÿ|ÁÃÑH%2>“©»ùì‡^Ö³º]ûøÈŒ¸ÜÚ€Îóù½³4³Ùv7…Ètï±›¼fŸ&ð†mjêàɾaðéʉG–U!ôˆ[ôºVÖ;±’ÎOø³ÿ ¸¶?áÇ‘Ÿ¦ü#~2›ñfÿêù»¢± endstream endobj 758 0 obj << /Length 2182 /Filter /FlateDecode >> stream xÚ•Xmã¶þ~¿Â°¨Œze½[Ê·Mz×l-R¬smд€V¢mådQ©µÝ_ßy!eyW¹¶Ÿ8óp8’Ù!ýÅ~á/þüáÛí‡õ§8Xä^ž†éb»[Ä~àåQ¾Ød¡—$ñb[-þá~w(:-úå}˜ønàqûØê~d®¬†Rײ]þsûÃß(^Š¢‰VyñfDê%¬ôó2W êßËû(HÜ®‘Z!™ºú ”`T]Ž/²QÌœj}`‰¿ê?lhëRVVzè:Ùk¹Ç _äÐ3R=Ù,ZÍÀN2•º• —æß¾—'9›ÙJÈR·Ñ¢n‘ÙŒ~µñ–a˜”¡cqad/´A„RckÔÐhåq'=ÿ«ï‡mÅ >Ôf¢zÖX˜~yŸø¾[J¬–÷qž»Üêý¡¹p/ÏZÊãQ fì¦é°OKn+yj¹«‘EÅØS]ò‘+¹£Àîï$«Ã­T,÷«ŸøJˆ9 Zw߬×åt§ð€€-'b/¼Vè5èo-ëOáf„žŸÄú(2/H7¬ôáŒÛn2·]¾;_ÀYÑxì+¢=©—g)ÝÈu"÷DÇÄnKã›ÉŒ:О lMÿYÉÚ´åA*a¤ŠAËc¡ë²h`ëÑÝ=ú${£RmÅ“Tvúû¹};ŠÕÀ*³Œf]1ùZ4š@°56Ìrk,â|m"ÿ@¹û¼w˜„-uÝî™·‡GÌ‹h䉴’áØQzö|­iQî­i»Ž½¦ëkuÆ>§î›ù§£„vP¢bJI£…f„×Â-Ä¥WÚ—èºf«Æ€£o×Þœ½°ëi`Œ`ºDÑxÜîëW:ÑÔG9ƬLA®‡Tm²í¶‹]j:/ù®hEó~âS­~D—[q¬5^läÁù$Å%  nx>™gyvdqìr^êæÀLæ¡5ù±Y•9IÃávÏ:m?$ 0ƒÉß˦bŠO{å  )_m~{‰ÌÐÆ#Q ˆÂÍõ,/·Yê^é ÊÇÁuË‘žn9òh\Œq‚< ¾!Hfˆ811ÍRÈ«N”5†ä’yqÖ¢Uþ0~#°]æ!¢sûbØgÏJr÷XŸÑ„Lncc_q:ˆH+#x¨K# Ñ£7‚×á7ÎcTÔûÖcìãõГ»9¯»ÅÖpjÞ<€ê¶l†J|Üsç…;éˆGæñ:bçxÐivP}’ ÝÈvMQb䘱Hx{t¡ì§ÃC¸)L¥õF¡‚kÐ0ffœÍ›°¦xp/ÅÉ ®xx”* €+>r‡v8šÅ^AÉ-_ÄΔÂ\šÃ‰(E‰5±}]+µœoÓŒ±ïûì²6/_ÆÚòM™n(ÜwðˆJ EL@e‹²¸eDéú(¬®Š@Œñ„˜«/¬´Ù/¢aÿôwgÎæº'å-r@ )^A²µʹ’'AÓÐG@y(ú¢Ô´a$K®@]J÷;\ƒaÿ{ùþƫߑ, yò,Œl¹=ŽÉŒ[~i…Éæ0à5é:…è G®7¡§xyá{­)wQ 8\µ/Vù(ÜG| !é<8,0jÚ MÃÔdLfÆÌ]†Lann76P•a¹ÓR„~3=’ηoóô_Qš\DÓ|­™ƒŽqÆÄw—¬¨µ¤ðwÊ©(‹ÂaÐRA‡ÂÖ€}~àyQ`G|oòîÞÖØO‘ß÷<(÷øûr /­Üo³—€e7Ö®±+s/¢èñÉ®4]1ë&ìÐqÙtF|2‚Ǻè-2„kHr+Ù9<þáiýÓÓ¼<;üþT𪦔 4*IiÀq~6èQæ­jÊ#¤i]›q]ft1òyhy€/”Åý°Wš™±×ê™ޗ*ÖâÚ‡×)ðZ8Ú ¸¤ÃÎß&ÞK’€™ÅVždûn2»Š+I6 ©V…×q>YÁÎÙaœoBï“ߊÅ(ˆ¢G;„~_õ_Œ~^BXBÊAÏåìF=Zœût~yÎ4ÝN$œ_(g¬oÓâ>?;ÌP¹“s! =І™›² eªŠ¾"òÉ6·o„™ ŲI쪆á:‹;Ì5Õhô,ÛÍ©–Siè…o>j"/Lýˆ>þâ—…´¹'æÏå±…ê¦i ûkøæ‡Æ 6^’»jDaŸŠ”_ŒÕúÍ“ñF3!Pýˆ‰ÙyÞâ¾?üø#Ú<_К¥ 5ëäJ=™Á×WÁŠóhòË„wòH?ˆt;ë¶’øIuú·à|XÒ§`šá9Meh!˜0?v¤h–“¡ùo¨‹UÓ/3w0àk¥tŒ`n_®™ûÛ ´™íÒMæñµWÖ…{†_£‹Õfcnëóè8“Ÿã›ÿè(΀NñÓ.ö¢Ð¼.’›!·þUð– endstream endobj 692 0 obj << /Type /ObjStm /N 100 /First 869 /Length 1214 /Filter /FlateDecode >> stream xÚÅXÝj7½ß§ÐèÓüK`rÑßBš‹¶!!ý(…`—Øôí{fë5®³Õd¡Fú䣣£ÑÌh´î£´âÑŠ**} áB’?¥Pt´Z˜­vGëEˆÐF3´½È‹Ç æu°%O§b"h¹°ÞÑOžŒço+‘<ÝK$O#çõÒUGGßG+#y• îZ¨€jÐ?¼·„: îÑÑÁ!‰`%àHz[¢Q!åÁ>Ì$#A„f+ä-GÀì ŒæT ̱‚9°ç 0w–%7CÝ1À<’4 Ó 6lX0FlIHŽNR& ¨£Ósd¤Á±7tš™ å£cX”¥°¦B³*ÅÖ‘#`6Á fƒõƒÁìœ#`Îc3´,!`Ž$0÷$„©¹§Bø÷TˆÝòÈ-C%¯' úÈNnYœBÁ£T„,'Œƒ å"‚QƒuEpèXR4Õ(&+¼-à5b”Xü×¼¡sLÇö G">xI!A‰4V•žæ²†N¨a…Õþ†µWû›m-÷ÒÑÁzQ¥uÜ–¹/u! rœŠJÊqFÇs¿k*X]+8øVcÃu5¶:)!¢Î9“ZËÅÅrz^^;­¼,§Ÿ~þ¥ «®éM£*éêãû÷o–gÏv±TÅÒ©½2‚í8ð 8xͨ™ÂRÔ4ë ÖÐvœ×”`8jÍœ"†àFý8,Q3™¯jïq øN<¢™S¬4*5žPù8ð ÑVÑ?…%XbÌ9w¯‘3…u«-lnslT‡êà;¸ë$’Gµþho—×W·å⢜.ólq1­³.‘fÅ·ŠÔƒ»+`ÖéŇëw?žoËërzñü²œ^?Ý–{ÂWþqÆ?Þþv^N߃ü|u{ƒx_å,§—ç›ëÞoÖä¼ýpþõ÷·ß]*¯lHmÈ]zEæ}ƒåÞ~rãªàáqÃÞï‘yÝcw©ƒwÎï!vKoîˆ=ŸÃJòò—±Ûñᮬ4ü@ð}*ÔêÆsXX$/É)ŽLŸÅqà-¿YÔ1‡%­¤4'ÂáðŽÃnÙ-p1õIC(œŒo*¤Õæ“»S˜MöoyspåY§@â®úÔAÿ'ð¦Âóæ“ÄŠ·WW|5xËõ~> stream xÚ}YÉrãȽë+h [Bcårða¦g±&Âí°<ŽŠD‘„ 8 ŠýõÎÌ—‚4Ô'äVY[ÖˬB4ÛÍ¢ÙÏwß¿Ü}ú)‹g«p5Oæ³—íl•‡s,–I˜çÙ쥘ý|Þ›CgOIqˆïsÓâeàŠ~Ó•®yøÏË/w‘úýñåî»d™†ù"]’$š%Ñœ.²Yœ¥a6Ofy¾¢ïlSß}z®ãÙîîNšŽ†3XÓ7WÏËÞ><¥QüúÇq`û‡8h¿Bt*›Â@—- uµ ÚCUv*j:§Bûf¦‚¸µ2‡6„î;µî¸#tîpí²óC¨mÓC´6GˆLSðbФŸâ(\å:êÎ9î-ŽÄr‘!›¯x6­…â䎯 Ê_ígômo´ùÉœUéðudu„™9Ð\7ÆO†µÿtµíÊÚ¶j|åÕ—¡‘ å)/–AQ¶f]Ù‚¹Uð{”G;¨ÏÑÎ÷‰c0å_ÊM Iã:µyµPµ¶iµ›N­ÖÊ÷­-BX=«Ë³ëAì]¥Ã!Ñqjk×c-ÓÀ½ÉšeðY÷]çÐ[7Ö%ÁÖžÀS ¸¦hÁIÏLœÊª%ûPÁìl§nÔ¯}?T¦‘å¿t41ÎÓÞt8F¥~ ÇÄÔ†üËmð¹Gì0Ù•‡ûpp™‡+=£ßÛŠÃ>™çµDÔ†Ã(™g{Ÿ,–¦¯ÁSÖ¦c;Y$nMú–;m’fwÓËîh{ë³z. ¶›šxYТÑ2:(T¼P™?jLTv+aõ–“-…1([”3ØUŽuæ^¼±vxe{±˜:¥˜Ö"@°ÿÑÛ¶ƒÀ{JæsD-ÉëMDƒ¦«Îµ¶"T‘¼i]µ†© lÃV C xZßZL •áð s4ÍÎG‘ôw‰&à¶µOUùªöÃööÐ}+Â"¸œ_&1-8Ù-‚z,Pä§HıÜí;e{£;TÔìzæÏÜ[ûNg:v2Ú©µ:˜fóÀmñEËŒ[Rþ¸4Yá6}­Ü}Ó·Ý»S‚þ7R‰˜ ¦øoߪ퀚•_§ûžû©³ª¹´š»Ñ]“‰åìhüÄÁWçjP[³¡¥§é(2“¨oõÌÆÁÐàþׇœ€ÀžîÁk# g~0F¾„ˆRˆˆLh –Í®)·gG¼Šƒ]eÚ–G‚nèDVcZU]–v™’$ÂDk-Œ x, Såü•‘³ÅEu «GhÜ ´«]M­;÷—¤~ˈðEWÌWnçB\7\ÙÖNƒˆh¤±T‚9‘hI’lØoö l%G+eèh'÷`—×G„ÏÍÇ8FG{ÛûC ÃÈ#]\¦Ö5(ß+D8ÍETL¼‚tOX¶n¯qeèK£yä vmü`VOË‹œ$²‰3uç’ ’3û„Cí…(_ì±ZÎèÅCvª¤¿ŠÖ«®£ =ØcWÚöÆŠ³!iŸ¦ã»ÓĘzô–R2t ñÎSМ( ”…$:PoæXRiÆ´š˜0K¬Î%>8—LqÈEIvÉEI.§¶Eð‘Æ´øÂ©ë²))ë¾å ‹Ú¼—*ÍhxU/s" Yovå¿ï¥?ꬦ»D³û 8ðie>ò8Ou{)üHÞ ÓYÈ¢!d/^FÛuÙRèò’ËKÄ›c_T@[óñN›fsåùå/ù>Ëu5W¼Œré.©š3†o}DX~Èf­”bœhöÎ)ž§e$»”˜Ðfnº4–Ù2vMBäÑxj‘NeäˆX«é}q4;ª2îÁná5¸Î;Ô*tðÆt0´9÷¿GQêL§n¤ˆ„ƒúi’«7_,»[ºäÉoAM–/µâ$bí(‰Õ y鯔~ÌL3Ä\)¹0v• Á¡Vù¨‘@>RDhÏç©ùvkúª{Ä—TÅËŸêU)åbÅ_ÐH[ªrÀŸá!æ‚IGi©xb|ëq’[ê¹'â˹ۻɌ³qu-fiUÙXP—5bÎo@š&ZamaÖŽgÊ$.½ÒV\¶jñfÊ 9ŠYÎÌã7‡Ùoâõ³os­ýhÌß‘ëœëyÙº\/G,1`uº"Ò™<‚aÙ–P¦ç+g.7/Ž^ºnøÒ.Êw$Û¶rý!ƒ¯ w“C»ÿ’„ÑŸ7ŽÑ3:”Ÿ2FÑ{Ôuér\0%]αA,Æœ)½´’rm!± «F0J ö\M2…‹.Yƒ„üG™’¾U,?¼ˆèãDFøÎ8TzfÛ7xW‹»R×XМMçŽÞÆOJœõ‡ƒ;vò"ÀüPs3#ë.MªÖ)E‹»k¦ž6tZt¬xé—xwȗȬS*vD¸{óWÚ]s¶f<30Q™Ë 1/§‹M‡ÓÅR ‘)Rûí'E$­Ê njiÌhÚ¨ì_MùªÝÛªÂsO|í ±8KDô8:(†˜¢«S‹‘$xµgßr2¬ò§ÖWŠ{úÝþ‰ýqDôÙO@„T™®>P~.‡SÁ½ñÕäèäK k»?M€û37§ÀlPx/ÒàÍõ̱¶åbRbgXa©P‘a!ÏB,¢ZôVÝ 3Gú]d7%%›œµÂÎ O-¨^Œã*Ýx•úLv•ÌD€ÔO1lO7'IÒ,× M&Ýò÷MrÀ•[–¶¸yº`QåLa‹Çj´’C…Ÿ¬JÊ*º²Ì#²8ÎÁ{"ÿÆÏp²`) R¹à•Žó7^é|Ü0Û²(·ü/ªa?R™á®|»½êèÇ‚V$Ñ®b–;4þôS²˜ÅiH7à”ß¡Ÿ²E¸LâÙSº ³UŠžþ~Æ»$Õ/ ¹&¼Ò—N¹¡ˆ—«Çlï#^„ùÒg m‡·Û˜‘°ÁRž¶Û¯ªmð^žqCÕöJ%%!¬6¸ŠRì#½Æû·¶4øü ÏÓ0ÕtTÓé…ïˆôùbvöäi_â:›{ãöÊôg}2dÓ²Sªì¼Ñ»Åí)÷µÊ\ZøË°¾Ç¤ù¸äf×ÿ n~l ;Hˆ‘D óÛ¿ÿ,Â× endstream endobj 754 0 obj << /Type /XObject /Subtype /Image /Width 961 /Height 700 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 785 0 R /Length 97907 /Filter /FlateDecode >> stream xÚìXIÇíÞÙ±gogï½ örv=*н¡gE° ¬Ø;Ÿ½WìœÅ¨ ‚ŠÒ‘–ÞÊ÷& 1¤lv7›`ÞçÿäÙl63»³ïÌüvöÝÙ¬¬‚iEŒ™æ6šÉBFµ¨QQä÷zA-ATª…¼Ö=Z< jiéú‰8ªÞbD®‹ü Õ#„Ш¡“EE£óŽjÑc¡ëb jó¡‘ë"C§ä¨“e Þ óŽjÑÃÁÙC†\Ëz ë"C§•P£ü«¡‰\ßm"E0CEêT>ªøM¡jÐKãÔÜ•ÈE´9vƒÈ¯zK;ÿöD:¯®Åhoókž™Z×O‹›õ\S¨AæàR€ `×#üQh ÷ñÿBáW‹ýÅh‚F?äùuˆA™~ö-Yk(-µ†ÚŽåG¿2ÔVò1£[lçQƒ\d ¬kÔ  9üM÷'SüÍ”Ý@þ†PÁt„&õÈ ~$ÞZšþÓ[lâ­:ª!³p½ î±ÔœœTFo­1G-£ ¡é}ÀÓª\ ¡)<«BüÞºõ7Èȵò¡‰»‘Ò³f£Ñå6T r®uß1Ö\‚ƒ*Äs$Þé]ct`‡x ²@>™OÚ¨‡¬D†.‰$£àÀ¤NɦP°k Y„6z~éBh²ãoh‰ì!jÍÑõS®•¤E$ÕœšÞ4ü …ÕNެP!4YüÀßZˆ…¸·gx–ÇL ZS€ÿÆ µìÖÚ«€Ñ˜d ÕДZ£þµx½Í)„6ŵ jƒŒº~Sª-ím…@o ù½5C¨`J ñÈ"ʨ@W "^‹ô™Þbg¡P¨‚‚ÐD†…ɆÉÑXk¨U mÔ«i¿ðÌ¿U†ZwLù®±™\‹ö™rC ²™º~ó!´YýìÕ=å&.„F€º !´Ñƒ¢¥ÅF•(ŸÖ ÊñDÆÌÊ9fEhº†³¨•vþõ.j¢=ó/B£!´…šl“žElRJ¡³L›’Â}Ã>`z,´ùšZô éõ®Ð"4q¯#˜Z„ð[„Ì× ÓèYfˆ°EM#B›5š.„6¥E5= d… ¡‹Ÿ‘£©ÈÆ ™ƒL¹oHá qä“ÖI8F/ñˆ 2|æìÐ7ÁÊ,€Ðdj#X÷ 6ù×ÁˆŸn"ÜKÜ¯ÈæeÖ™,¤¡Ù¬M\J“™æ,Bi. BSnQ)b€B…ÐYôÍ M0µ| …¦…,|MœiM¹ánJ­1B[¦Ö-´|äcÔ®˜èEèŒ y2äNÈ!C¿È¡Þr9dÈG!C†*/2dÈ‘!—C† y2d¨ò"C†2ärÈG!C† U^dÛ{‘#Cm&2äQÈ!Cõ2äºÈÐU2ÔŽ!C† Õ_dÈë"C.‡ º(C† u È!×E†\2äQÈ¡«`TÈPïƒ r9dÈ£!C†j12té‡ j*‘!B† Øâû‘?…"aff|…ÏŒŒtSäâââé¹ÉÄD †2Í¢ ÒÊ4¢û÷ï÷ïßÏèfy§<³,‚Ê¢¬‚jF\&“–+WN"kÿD¬ÀóÔ2ò·(4 fhÇÌÑÞΜ9óäɨß)týl¼‰ÅBž€/‘HÙZÌ#r)œ™D qÄB¶‰ºyý²]/[ÓÓ)Äâ”(—$ÔÅ¥GbMñt%5.¾Ið%ДŒˆ¤˜„†´Î}íêÕ+ ü*’kI†#1A)ä2’ê—"—Ò³%#,¹RéxÊø%ž2´DêŠè•ÎÕ„IîÌÈ‘Ãõ¿…€¢ó¤-…ÓyˆÍéêÈg‡U¡B…BF7<#~ÎCÐÆšR;|ö´Ï?ãǦ(ZЪø„žA£Ð–4eäŒÑ­Ûüråþö9é -°…üìõBKIÔÖð¿wÙØ,Þ¸á†ÖˆÎá¹(º—mO¿G÷‰õÅVBÑfŽ&:"MK+Ì6M"Íó•‘ŸiJßÁæÅ–Í›—/ÿ—&r¦Ïf|.Xü,ÈGü š4q©ÿ/$Cд …Uñ ;ƒÚJÚÝíQõêÞóçÝãó²k Wµ ä³Ùii]ºÜ­UëÆÍ?ôþw÷.¯éÓ|γ§÷W._ø‚ÃN5¤a«V,ö{x‹ÍJ1k½Ãfž·|}§šoDc»Md¥ÑpŠÏšŸ¢Å"^ùòåÒ˜)„{dsQ´™‡£)€4ÑAiÒ,MQÓ29M³³â1³–Fuåò%êäLqØ™,<›uð¹@ò3µ§y¦ó3¨K—Î/_<-CÐ:ЂcM|¢½3!‚´v[CdgB[ BŸ<V«–﨑<žòz'ÍÉ ä`ïØþ£~ý÷'|ðôÿ7äC`ݺuâ#wíð‰t%Ñ• [brk«V­Z_#"„bqnIŒKÿ’˜ Dú$ÖLJ¤+}ë I(Ê% 3°ÚPR"­­´6*R¹Š?û§ÜG/Ð’H[|CR5Jä¤?_â©%2.®Ž”ëÅBžMJR,¨ÄÕ–ˆD¿–qøKS$Ò.›4`k:Zà$âë{ÝÂ|¢µ3>B?ëîîªÞU+—-Z4<,”ÈÎ „¶„þÏ/©AƒÏ={Fp9,[ùÕ²%³iSVx^E€nâÞÝ[+—/üþžÁø‰¯äĘM×¾ € || U¿‹YM‹h˜Þ5º?ü#Î »aèï†ÿ¢lsÔËÙ ­VÙfS±ú«f"š?i­¤€Ð¹ÑøI…Ð9ËJlε¥ •µöAEg )Ç‘rFzÅryÎ möȪ,güó×è¥FX,Ñ7ß8qbúô馼fî×ë9ˆ½f™u›ÙKLLüùÇͦ6~NblÜPH ¾r‚IðEf¨\ž-…RR’Ê=6.3$±¦H ëŒkHhH„ÆÉ¥zG‰Ž'%ÆU­Z•`0 ±Ar££â&bSN(¨°Ï4Ñ"8øµQ¨×B ÝD0Íž=z¥%K‘⥻3nnk`7à–W¬XË›6m ¸3¡­¡¿}e7i"jÞ\’–öËáù<ζ­2€Ž«W$B^E˜ê8eùr÷µÿþü•’§©¨¨¯Zk@;¼6¼ xb(M]ÖåO:ÅYOœŠ‰À<þ–8¤m õ9šD­EÑ꯺œ¬Õº˜OÑzZ9kn©Ðº,­9­¡-8G‡*¨Ãy¦Ó¾}{¨Þ&YS\‡±è:<È…Lë‹÷ õA1,„¼hÜ%-ÔÌÑׇjž gÊR ÎfæÈ ë Þ 9þ™žø LÏŸý×½{7KÅoäñƒÿp–_¿z¦F‹  —Ь]»JÍË—/-Z´hhhZh%¢þ#$Õ¿_­ò ½G„ vvÃÎÎ>=<Öß„ÐV‚Ð{ëÖ¬ýû³M¢’ÆäGFf%'gˆ„ͧ uõ?Ÿv½znß¶!9)FS‘?›6mýUkýñ£{üŸýÇå0ŒÆBZÆv6:Ld`Ùè.á¯$…ÜzZkDZ—¢u‡ u[KëP´ZÆ_)æjŽ?kEtæg‹RtÛ6mÞ¼~ÿîBÒÑÑ´‚´Xšœ–‘ÅiÃ\M+Z[BrC´¬WkÖ¬Þ°a=}á͸Ølä,&5¨k<“˜üÙZæ¯Óó@÷ÉG§L* ü¬ {ø?{¤‰®®+¡7OX^þ¯‹’97ºã£…n"šÔLŸÐ~D˜zõê »Ñ¯Ÿ)XB×ÍRK¦¼ %ä(žYщ<¡€ljýÆü–ÊM€5"Ksc )·OHˆ©P¡¼§Çš¸è¯‰ QšŠú®µtpÿŽ ÀWªðu"äPµZoÈ„¡?ârä%B "´ÞáhÃül¢ !´Öˆ4~P‡Hãó³2Z3Z¨Š…†ON,4_ Í' ý3…Q¶lÙd&›†XèìõxqÎDb¡Yù;Z¨!c¡…RED uEc,´n¸ªži5å„¶wp¸é뛄¬È+&-ýAÎ*& M<}ÐÆc¡U 5E: ZôÄBÞØ@ ´ð—ĘU"½ŒI˜K”c¡W¯Z±qÃ:=!Ðb¡µT®Ô uÖæg¼gsÌ Í‚+¯÷ïÞlÞ䪉kV¯€>¥—­’97¬w3†Ú‰¼yí¯õGH°hÑ¢Þ½!Â'¦Iÿý»l‰’Ÿû:À'ì qXBm=šôÞ?ÖÿGÊX~þšüérøåßïq8Ê!h›ƒÐ2© iÓ&sf;} Nˆûޝ˜È°CwýòYÞC¡IÅcàŒcã?h4²ÚlÍ6ˆÐBƒ­Òø­¡ApZoȇ&Bk´¡ Ž\’ðs-4b¡•Q‹9ã«zb¡ÙS1‰…~ùòe×®]¾}ˆh,41åϨ`º‰­(&ÙâïdQE/§+*Uª”Ædd’Ÿ9D_´ù¢PŒMB_ð³"[‡Í ¤ –ȳEfÌœdä³±øgN¨ åXè±cF]8Æäøg•D˜¸ÔÞnþXh%ZDGGlÚ¸V -æÎu†neÙÒÅÐBO"ï‚^ja‰zQ>1¡uwfÙÒEp,ë×¹ÂòêUÿÂ2|Ü„ÐGhCƒÉ¬wv¼Þñèû"+‰‘²ÁÃÊÇ+£S¿àüE-©˜;oÞÜ1£GÞñ½ó_¯]»ráçÏ8Õû+õ¤–—®4Óµ¶'˜ÁÔô%b¶Ex/úõU"bK4–5Wæ^«ˆáZq#Zë{ÆP°¥xÂmÿ%†dðV¬,·vîØ6þœœ¯º³Þ‘Á¾ROÇ*Ã$¢$C1†$15üCï#™©J7$¹ÈÀ¾>ºððÏ52†#…ñ jg\¿ÿP ÉÀ“Êó)?ggT¿(”oH¸AZ#üi°á’•Is(éQÛ¶m‚_ª–ÙÄ%Ö•a̳A ³XŒS>GuÑâÞÝëÑ' |Bï¼øºÈuîkÔ;°jå²¢E‹¾}Ndgàj‘­• ôÕOÿ¹öÏ΀°¼ó•Wÿsý÷¾ÞÍå(+T›#à©Y·oßìÚµËfO·ÐWÑQá†ôíËûƒûw|û&RÞÌbQChM %EÂZÔýÕè6zs1š—î^Þ%#“x¨ÛÃܱ¿XZk¥Q6 ‚?ˆž-uB;Ô+ó¤ÿ?ö>ÇswµJ,-´b–¦ŒÓƉšTÓÁÕ¤yÛd™g‡OýÏgâÄ ™f–ช³9^xÆT¾\93Ñ$xÎ'üŒ |2â[¸‰hAK"f:¢7ožií¶†ÈÎ „¶„¾ñùR£ýF]ó¢úŽê÷5JfǨž"dñyÊw|s¹xõ‹ÅL.W®¬ß£ûû¼·…~xùã“®Â?òÙÿöÍKc¦K<3I³)üËá“áæN§a”Ç\"пätCÙ÷ÅbÕ\©BžjRhe´@‰Çç«b¡ù\ÅXèú ¾ Á‚¦ Ÿ©Eb¡Å×/9é‘Î m’ˆÅBŸ¢ô„L J„'(Û¹óæymß.ÐÅ‹Sm8Ì׸„ú”+w=ÿ KÊúcŒsm£'5=)"$ƒs8 ³§”×›” ñÅÓ‰&>¥34e\µôF,ë uÆrÌŠúV«VMÜ †+gKøK".p{îF¤!\27qØ:Á‹çOLA Z¡o š¶Am=ýü‡_"µöÔêr¼,œ >’Æb< ,Žr²h–êÝßxŽÑ«WÏ7®½ x¶móºûw¯„}|ûý[ȈPPø§À§}÷yo zÃf3eRA>âg ­;þlhÀ™°ˆRt>i /{I*+•=`«ø57EÎÀ¦æ$c¡ÓÒÒlllT[RÆ…ÎÓpå $JáÊ4¼ ?z¹k×.T&s&7îLÎ"UÒ“QÏ–D[&¥+¨M"Å‘°gœs>IÉ){Ìü?¿{}úØIÜË3“™š—–Æø‰Òa ™B0Œ@Ú íîîºzÕ £Z†¥Í‰Ó"c“N[ªÍ\­ G´Ó¯q˜,âí›WíÚµÍk`›ìN”±9Oȹ Ã3¦¥.‹¼¶z*xþÿ@ZXŸ˜¾3¡­¡M—€Ç¨X±b|l„ræ Úå,Frizº–¡Clvå{6K<ðÚÕKħïÀÃi©%qÚ¼DM ªÍŠÖäßc Š›>…aíßç=k–3¨L ˜écf `3ᇭ—œ-Ϙþþ{赫 äcƒE ZXŸ˜²3¡ Bƒ† |úÔq„¸Ö6m] Mà49–®V­Z\ìw£Ów˜™¥)á4ÔD_˜H˜«-O×ùFS§?v„'¦e1M.a™Í‚ÍÖMÎÖ1ìl0 ¹y³¦CƒLƒg6ê= ŒBçB§‰˜˜V²JI 6+1GIì4Pb¶˜?YÙJ`1”JS*>-”Àe'3Sã™)ñÌÔ8FjÜÖ-“'ýÃH‰MU+9”¢úT*I©”¤h¥AQɉQi©qLµRb©9J‰IÕ”2h¥°¿'E)•ŠLÆôóGòÏÈ$¥~`JTê{b¦ˆŸ xÐ7PB|DBè(û” úŒùŠ‹ W*ô>c£Õ ÅD}V+:ò“ZQ?0}E~W*êG(¦Èï Ј*ÁBÈ÷oÔŠøú^¥w o_0c _¿g+<è—¿„©øås`x¶Þ†zúüF©O ×Ÿ•zóù#èu¶B_>)õòSˆR• øøA©PLïUúð"Óûç ïý?¼Ãôü=(ØÓ»àg ÷AOß©øŒéícø |ô6[oÿ |“­·oüÞ¾Æôôæ•ß›WÔzýòá•^Õ«WÕZ‰„dVÕ«Wçô©ƒ¨òT÷°6k°¶k7 Q6#ªö$»mQI£ÙÉn‹²Û¥7~êÆ .u# šºq V5wJå4€ªÆPÕ*æ4’Ð`~PJÕ~¾÷ÇZÔœ6§½U k‡• ²ªeV6Ñ¡/aýï¿ÿòîù¯fšthØ?½SêMN›¯ì”ªG€®áKNO]Öw|Ázœnå[x®î냰þèû·÷ê ë³°þ ëË SÓèà°þ“²Ôì5{̘¨0U7šÝ¥ª;Y¬ÏÅú_èˆc"?cŠŽ„ŽéW_£”ê¿*aÝ7&èÙc¿$Ä*;úŸqßT‚…ˆ%|W*ÓÄ "Õü™” Q€%iJÅö`Ä‚f<›™­4ÐO¥r JÉTlÀªDL\v’JÉX"iÙ‰$(™JW@Y¿ˆ èKƒÇ0<ÃPËJlÃø @!´ŒBë™{¨„¨8Ùo8RÍN\·n‰¾!IÅ|Ã2z/žø€!ñABcÀGÿh#†ž»Õ$&ãÕ÷ºŠ ãÒxäßà,zfÀ›©€Ä´ ú-{ =³dàÙ•+WFª?2‹Ç«P¡BFF* k2ýSëÐðÒLão–$6 ‹±‰V¢"4jÔPß\+Df8Ç}.€\äñû&&Þ ÉðdìþˆD¨Blê{!µù„Ôü'º7Œ3UÎÛØ ½óBç%B‹,L滳P«VÍÏŸÞ¡;,(œÃ*B£Å¼¥.‹==7’z! ¹ˆóuˆë +´CD,f€l€G!ŒôÈ>d¿G÷íìzѰ!¦ï\ MŽÜ0=x#¯â7¬?øoN¹»wn èß…p ©A!tÁChÇ)÷zï@N^€@:³´­mÏGîR{¹¡¥pÚ¢–šƒ¨É¡µÉŒmÔM}ÿ7{z,[æB’iGeB¡Îfdfš±¹ð’³Z{vo_0‚g$„ÐyŽÐæ“ω##†³@FH:bë‘0—Ä&‰C§DzÅÕ’„Šxj ìòåË1Rõ‘ 7Ò‘ž^^@M:°!Ô#)5‰Èê×;&h죑#G\¼pŽ"$羌Š(yˆ^gËíRêÒ©#dªÁjûK"M‘n:Äz¤¯™¢·%$ÜÏŸ7g÷./Í5ZÍ»þ.õŒW¡ BGG~µ±±ð˜È½­ˆ¥éi¶ùAš6–zݼY3u¬>)™ŒÓf&jêP-”Q$4÷¸ÉfëÁãZµjEþøJä½x2êÒ Ìædfsc³ÅÈY”7ä¬Ö€ý|o_' ϨD,ÿ!4¨E‹æ/ü#÷FƒÒyÎÒ‡íwœ2)wk~œ6;Q›ªM¥kƒ˜-µ\$ ] LE9U½Z5¹ÉÅh'“æ¼ef:°9‘3‡r“Û°Aƒð°4쌄:Z‘3]ÁT$·©Wj-hiÑÂyÖ»‘u†":¦w‚û`4/½k¨¥F˘-Mã ­UìF[rmLêPT©é¬1ÌÒª_‰²ôÌÓ÷zï2æa!œ¦›¨Is5=hM`¥n:9œ(ýâˆø±\»zyÈÁ–ƒd#¨L/-ÓÆÌy…Íy­Á1ñÞ—“Z¶l? Á3’äB[?BSøõƵKvv¶æ JCèKB[LDhã,­ü£Æ ´QŠ6¼}mxhZÐD†¦Û¶móú¥?‘¨ij8M+QóMf’\M'Z›³ó‹V­Z±~;xL•ÍA˃¾jf¶06[9«:BC‚š5kŠÈ !´u 4!©¨ÌàJÍ_5†ŽÓRSâÊ•-›Æøiz^š‰ëf­™©)‡fôˆ m¿ÛDr×<ÝC6ºR«ˆíŒ>ö²Ô Y*ˆÍ~øWctÚøW }„.k®ù•~öSç¿Æ¨±…œ_u‚ê‡ÜÙiÉåÊ•ås†Ÿ‚7ÜízN_/QS¡ H Ÿ«‰É}ò M›VôŽBÓ‹ÐøÃË80o„6³Aðú‚êÎè—Ε©á0Ãm§ñG¡q¸Z°½b »ß?~ÌÉ“Gõv©’< jó@µ…ÐÚHÀ¶ I%Ÿ“Çþ?ŽpL²ÏšÉ>f0ÓÊÌ›…F_{÷îõð/‚F$„Ð;“R±bŸ˜ˆü‹ÐøÛ›¡Mš6ù,ëŸðD=4æAöuŠ9†yh¾“±aÃCƒ‰ô¶45 ¨6'W[­)>#™_ð˜È“wóçÏÙ±c[^–6nc*-‹‰<âgf¶6[ŽœÕª]»Vä÷0H¡ C,4hèA§O·ÚXh½'õð éôN6ìÙ< !gù×X4K“Dh­’±ÐcÔ†`;1!ªR¥JªwÙ³u¨-MÔä ÚÌ\m]€mVb·tî;wòöŸõC2´,&8-F~gfSß‘MŒœõ7ÑiŒŸåÊ•CĈ„º0ÌÈi×ÎmÓ¦N1=gF½sYÐHzçİB“‘ÃÄ¢0T칓eÂi ¤Õ8M ¡sN럑ÃKšËúöÍ«}úèï I5%¨¶W›…® 8f›E|^Z¹re9ìT+Ácœl´L˜-ÌÌ–Áfã¾yN60 !4²Ö®]ËŒý/U¨6™«ÉÓ5 €Mš´ {cÇØ¬YÓàà×ä ‡¦³#²'S¢å¼f6-M af¦ùí'Ý»u}úäêYB*„ŽŽübcc#à1«#¢¶T¼‡’¨Ïý߈á[º6«iBkòtM?fÓßùAÜÔ”„òåË ,³”åÓJ‡S‰ó-›™…bf-U«V5!î;êJB*„µhÑü…ÿÈÕ‘ÈP›BÔ.KnòXGámãÖÆÕ´ÒµÉ˜m9ÞÎzpß×Ö¶‡E¨˜N<6“­‡–Ù4]}“bf ½h;91ÚÆÆõH¡­¡™9²Ð)^¸`îÆ nÈÕ‘,IÔ¶={<¸ÛÄhjëGkó6}¤]P9\ßálòXï²dM…c–*6UV‚Êt³u2³–ž?îÜ©#ê,4” r¡ 5%ŽÞ9ôò#]›™±ó¼­U#Fü}þì)ËÀ°yÙ 9™~`Î̬¥ÿ<:aÂ8Ô; !„¶&„¶RSbË•-›ÆH°|ÖHùVÚ¡‰UzмYSqÎWòbY¯DlóIb.q ƒjÕªñõ£9 Ь'ݪ½zþ%½ )JÉs­u]éî¶õ HºB·ÍbÆ[à,÷ìÑÍ÷ÖäíÖ)Ëø€Ù‰ZªÚ;eÊDýoþàj“›ÍJ´2ê¦M q?nߺ|öÌ Ð¹³'Mùݾuåg|¤n.1Q_«U«–÷<œÏ™fZ6ÈÌ‚|ÆÌZ?~ÌéSÇQW…„ÚªØéå‹{<.ÃÜgÙmí*—% ‘·['?[Æ, Õ3gLóÞ³¤ºpq5.i³Ó~‚ðyibËž™™<1!êúµ ¯^> ûLMŸsëåË'7®_Lü¥µÛW.Ÿ¹ß¦M+äíÖ)Ëø€%‰ºmÛÖ/_yÈHð™´’‚Ÿ=}¨•ÑÊËÜÜVb*6/*ÓÌiù·}«X±bjr êªÔRƒ\F:Bè¼Dhü»ùR /=]–‘.§ ø#ü=Ò8ÉÐÄÇF䉳‰E,…‚Ÿ‘NX¬,Qf¦H"²ÒVWu^ŒŸ‚ÌÌ 8éð©ì}èˆè0S¾G!—(R"JWÈ i@ÀãqÓa\"s5SKc$”+[–ËNÒì=¥~:%7Vy²\&áÓ…ŒÔxë:vìЪUËÖ­[uéÒÙk›';-‰2¨HÅ\¹LÅ’½· |…2¡–;í'á|ÙrœAFºR°_%b6.•pär^®,ä<‰„C<…çþR’c²Ç·U­œM©T` èB‚¬ÖJûÞ·n\†óÈç¦ZS¡îÀéVDA€ËaÚŸ¿³$'rZÖUlô×?þ¨Ž  !´U!´¦Òª»ù†æJ©˜ÿéÓ§ÁC÷²³¥ øcXX˜DÌÃR:dÐéÿ×ÍZ¤|ÁˆT³+˜"p6¹ŒsúÔÑMî˜6nX»lé"øÔ+ø ¶™>m²ÛÚ•Yh)g ð ¤!AoÛMŠ%""bè½íºƒºwïôWã†v½ºi©YÓÆ]º´/R¤È¬Y³àÔ ˆ^ÐÍwð!½ìì@]»uoÔ¸qÏ^½´Ô¤iÓN]ºÏW9_„D|öìÙÃÄ,::Z.—÷éÓ²¸ÿþ³gϤR)£:Á'ÿÝïÜ©£®':´7%ƒ?‚'„›þõ£‡w5jضmÛ®]»vêÔ©sçζ¶¶:thÕ²ÅÇÐ Üiôv ÚO8)»víš¿‡·S“£22°H$¥Ì"C; X€¯¤²zÉå¤bËéË—/0`@JJ \‹÷RÛ7Hð]ð+Í5àä+V„3>¹k—™Ù¨ š…\Êb±Ž=êè8µˆÊæÏŸóæM¨2©ˆÖì,1É€ÐøFÅ,Àzìw¯gÏîû‘( !´5 4ˆËaH%Í5xýôkÝ¥ÉÄùC)¨U§¿€¢!±ÔvîØ:}Ú]|âñx>|`À †©I*a¾y6o®óÙ3Ç1ò92sÆÔÇè•““#l°Î}PôÕËg2|j ýNzzº»»{£Fê0;;;¡€O6eè7g̘Ѿu…õËëƒfNþ³zuÇiN˜¦Lušì8}ÊÔéõêÖügdõjUJBÇêããgÓèèú€n¾ [¶þgñrP¿ñ“«T«®›oíºõl‡¬P¹ Á|fæÎ[¹rå¿ئM›À-Z‰ÿVºdÍ?ªzïÙ}þüùt…\ ×6Ϲsœµ<À©gÏž+)\¥EC"º;)Bxîïü »Ñ¾}û¦M›6nܸE‹]ºtéß¿ÿ!Cš7o–’CpÆ?ìê,((¨w;(ŠZªµÔ ×Äæ¶›µØ fê°²o?‡÷ïß+2ؘ8,ñT†±•®àGG~Þì¹*ˆ‹Ë‚ ëÖ`| Kσ•^Û¼{]·Ní,²ñãÆA¿SºT)]ÁúÝ»Án+ïòçH5º’F¡9þOï/[º(1á;¦Èïg9Oÿ¬–ïíË«W-s]³<äýK kèa3ø,çP4idžý}Z.—&œïä~ã'ÝŒJy]»ß´e+Ý|»÷ê½úÈ)Ø`öÆmÅŠÃÉWí½{÷RM3fpµÎXµhX#ôâš;gÁòòåË5{ÜØÑ'ŽÒòä=z¬Y³&’Á¾ÁªG¦O› zóꉡ1І•ÒáéÝ»wl3t. 7à ͡ß5Bƒœ&Öèë`§·ÜŒÆoDhu¾j„õ;ÑÞÁÿ|é"4ƒÁÐåX6›™™ùæÍ›R¥JÙT(ûôè’—]Û6©™fäô»/;9.9°ûÒÖÇzvïW¯n“Uÿ¨Zå+VBý²bNF¨l\#F »pþ*$„ÐV8 sï¡SqÏ3nÕ ^Ì&‚Г'MðÞ³ƒ8B¿}tƒÆ¨Öè¡}¡“úúõ+ñbS´dñ¼€~¹(h'22võË—/zO„\.¯ZµêùóçeR!½í²l…Måñ&i"ôZ—z}zBYüø ‚­ÎW¡Ç.p!‹Ð@¶cÇŽÕ‚X ¸Q‘˜˜øçŸ5Š+vÆcê×ëëF;´Ã®°2•–Ð qß+Uª$à1pÚ×רuÏž=TÚðPaì˜Qݺukݺuíڵ˕+W¡B…ºuë¶k×®oß¾ãÆ›3gŽ««kÇŽŒ‡@q¹¹»?/97DÍÏ f=k5ïU»vó*%JÇø°i÷š+¯X|z0P´‡‡œ>œdUñÜ|•ƒÃ@­—/ÖËϘ4ÇŠaã[7/)Dƒ%ôf¡NY—¢5²àAh\&Æîb”/_~ô˜±XAÁEŸŸ\ó*¾ „Ð;u|òøB ·ÌžÞ¡¹†Ïc̼° ÆºæÇN(YtÓÕ(´qjUÈ%k]×ÖªYøùÏuÎäÿðÇÊ¥[!ëÔ¾u9¾z¸¨^­æŠ¥ûàÁßßß”0~!=œŒP™ºZµj€Ê !´5ÇBëÞÍÇAhŽ8Åóù ¡·¼Í%AèÇÁ5q„ÖUrøè)‚ƒƒÕ- ݱÛÚ•ëÜWir`rqY@¡årÉÆ;vìhèDܹs§lÙ²B¡È!à#ô·]«V-?q²ã¤)SÕ‚¯µjVº&‚Ð#:´úÐÓ·6UªNœ ÙMS ¾Ö¨Y èšB_¸paÚ´iÿý·ÄÊd2‰DÒµkW8ûî΃¿ÝXçæ<–»wï.•J=ªÌ¿uóŠƒ}]OÖDè+W®?~\ ¡¹\.\&ó¢x<U„ÆS÷n]{öìÙªU+ ç5jÔ«WpºW¯^ÇŸ>}ú²eË<==»véŒ"‘ðƒ‚‚J”(®9þ¬¥µwGZÝŦf9(Ÿjõ+¬¾5Òi·=ü%44Tª¬5F‰ÅLÐ])³£#?;99ÆuGk þûþn4_¨bz³ÀF¡ Q´F"­ŠQ´hÑ Çmý†‹W®þÕ¤ Ò“&MR>f(—ˆ ÑŒÉIyüRý•ÃJ*[¦LãgBoSÿtÙ øHôùµz½€ÏXxå_àgï§û³78å%¼¸—H 8äo¿ý¶`Þ–Ê6ÕO{À¼uã±bŲ¯›Z4kwýâkXùïâMmZw4`b¶ª×Çcr„Œ Ù,ðe•ïöý‰Š©À ´ú> ©íiLÐ23rÐŽÐQ?ÂmlløÜTÍ,zÙöÔDhNäËAw éãó«PJ>LVtyé 9>…ê"4t¸óæ:ûœ8µ–`å‚ù³â¾™ˆÐ7H3t"Æïèè¨~ÖÒ„ù]i»iu-Ý:ÝJ½„6Ñ¡…Р §¯LZ¶ZKkŽžVo@¡á„&&&j!´H$ÊÌÌtrr‚S?¦o»¯××ö˜Z¼X±š5ÿLJJ2ìÓ§¡ÝÖ®^¹b)>Bƒ9sF ¡‚„9¾gÞnð“ûÕíàЧK—.íÛ·o®2X~6lØ”)S-Z´~ýzàS fï>v†5Ö%g·ûc4¿ºÞÕ¨S (±ÎÃÁ׃ 4þn”ŸsÞ³“k}Fº`ë– 7¬Åág]„mX·¶ÉH7> Ylö\¯›…&Bë¥hü,‚ƒ€x5×È¥¢›7oBÉT®\ùç°ðˆËW®(…5•*U:vì˜ê1C¼ÖBè€çU“Þ§é"ôìY3R‚±`ûá…=ÙàÊg®ºåü¼ùáö_4ûî©(ä¹ÑR’I…GŽùãZãFÏ=b* ò®­§»tìµl‘G«¦L˜ uë4º|Æÿ郈:µ.Y°v)::Z,☆Ç’óF_?ê>C„„”ºHn+mhFzÔ¢Eóçþ~ZíºÔãçØš4ª[„Œ­Z¹B.“BhPpàóÇ9ì­%Xù>8@sK -óÞ¾}[¼xqB½gËå–.]ÚÏ’囂ТØ^Kf7˜8ºŽæN¯ÏŠèIpÚÈŒ:}úÝ×·ýÔ_/~ŽžãáµÐËûZÄOÊZÍçó¡ÄöíÛçºm“ÚŸ.¯}rdq¥òeJ•*…¬P(ÜÜÜàïj„|8”ç‚ Ö®]»sçÎÞ¼qwŽDNDD”öü ¦†¹t„õ+® ×\¹òƈ’¿•¹?3ÿø@Ø &&FJåW'¢C"b%'F¾ê>?¨ ·zúü¹“°ž™#á DÊB¡u)Z…X_º WÜéݺuƒYì²4"*ôØÿy/»ÞX“Ò³gϰ°0pK±}ÖBè=»·ÏpšªFhÏMë{ÙöÀdÛ³{µjU?†ª9²yã½-ÀÏ®¾Tóöä ´@­³£K§Lqìe;¬É_möí¼8ÏyU±bÅû; –:p܉ƒ¾°P²d©*•«Áò”‰€Ù³*ÛT;{ö¬ª^dã;¶‚4ß`Ùk›ç®[¢ä­îݹaoß•Bh«AhFŽHÌÈñ6în ?–+a`‚¤4Z‰…9?¥ðcÞÄúBèMë49A ¡=VϯYÝæÞ¾ù÷÷RƒZÕæÏŸ§ÀðA ¡?}|òþ¥QÁf¤¨úeLêG …“'OÖ¨Q#===33û‹Œ@(².B_:Ö¢cÇÖ;víÁQï>¶ÞžñšÚŒþ{Õ²K÷Yë·¨zÀG¯ëVò™è²’„æp8™™™OŸ>…ë‘j6å_œXraM³ú`ÓåÁOl6ã[5BOšøOÔpRÍRY4û#æÏáŒl ]ýÜÿ¿–-ÛîÞ~¥UËN­[·T:tè˜1c¦M›~âêêº}ûvggçÃÿ6æcÂ]»vÕjTM‹Ÿ§nÏf¿/ÿ­õSͦ•aýÒ Ã`ùº•8 Š½×QiŒCCʤ܇÷oBEÀçg½ Z´pÎc?_H¡å2=YBh]ŠÆÉBBà üýý¡dÊ”)û&ø=FÑß"£½÷¨Z­¬/Q¢x‹X,’ËDFzòä ‡zëŽBCÑíÛ·{þü9° |$üü¶ÿ·øÙåú*œSM<>å%¸¸×è(qºBÞ§ýða3*U¬2lð?Øy¯P¾R“¿ZU¬`S¯ncXÀ&î³±©úïâM¶=†üÕ¸Õ¶mÛpž¹Hú…¥³Ùsƒzå†õnØÊ”¤D)y¨½Þ;gÍšÊIGÙ —¿Zwc|„ÖÚÀòMpFŽ—17¸’ÔCAs7úUkî¦rÌ»ÓLsýáàù\)óEÔU½­›…¡yѯÕ¯½ðŸÞßn¬#"¿C‹ÔqÑúêå3ÐÛ®\ábT°Ùõ«ç"´T"xñâEë6­4¬ªß Þo¿—®V½jÝzuôª\ù²•«Ø`ƒà¯^½’¦h5BŸ=Ø|ÄÈAQ±ñQq èj¦³ÓŽõpšúŒ×¸<¯‰Ð§‚层 œ)#tRRÒŠ+0²‰‰©R¥rÉÅ/o›ñõúº!=[ÂI_´hü„-k!´!7ÓBèèèèïß¿óüIžDŸ] ߊùó¹nØš Ÿw•£±¦!ôÃû·[·j·Ï­3>/ÎþÏ¿]ÛÎ@ÑS¦L™5k–‹‹‹»»»§§'uß¾öŒÔx£óOž2¹ý š<çpÿ’¥‹)ª¡×ÜUºLÉ¥Šc1mûÖwšá¤÷zø9Çô`›B®œ%cú5jdu]³\k !ô:÷U'Ž€Dp†¾ í,ð{ó‘,‚‚8ìÝõpÝ ¹PhÓœfhÖ—à“¦8bÚ°aÇ*‰…lÍÿB‚ÁÁ/Õ_›6møö,Äå ´ºè¾„‡ð8©° ؾDp~ÏAÿ#Àϳ/.æ)ƒÙr²àÝSá‡çFC) ²ôë7`Ø©U«Ô8²÷z« GÝQèJ+Þ{}ùOûÞ#6hW^x- Ò,X€u@gN€5'Oƾ.^¼±JÞjÑÂy^Û1„Ö“ Žèä›+cËe_M„í{ÐmàÐó#uÚpé˜ á¨S§¶r|lÁp8ã˦(çc±··—ËåB¡Psˆ8Ø›é:†ó ߓùšAJׯ]lÛ¶ÓáýwŸ/ž{õìIxØçx@ ?ÿüÓÞ¾ÏÀ{öìѶMëý{wñ¹)FS±wèÝkbs5$/>3ä÷ò¥Š+ª; íþplËÞÊÙÛÚ ¨­é1®é€ýUá÷ä| ]ÁÇÞl‚ñêåK§õ2­!„Þì¹~ÇvOH÷G ™„ÆÞ|d4‹ À+YÏe¯˜‚Ík÷ìÅK­Zsùڦ͚a¥ºsçN­P1H08( g9©mÛÖ<œ>f\L„ ¡õ?6Ï{±e*ð³ã™Y\n²n! ²vb©’%•Ó5Y…¡gÍrö9êᫌB_6:µCÍô¸(F‹2Bëõ|„öºþ`á¶=ê¯mzô²6Ò~ô?ó·ì6¡333wîÜ •bïæUe/­|iÈo¿…„„( Ý@eRíïï¿iÓ¦»wï*_ )ŠTÇ W'éÍ¡%ªÉÛK”(—fw<Ò¬5¯\ýý÷ß¡ ç΋Mcb Bß ¹Þ½ÔÊõöl¯E|';¡ÿMÁÎ¥Ú ó ?½$àL™LZ«VIÿ,iØ …ûê=Eÿ3Ö;é+—n…¯Ï|Øoô°!SÛ´î>uê4Bw¸L[ù"<(Û•+–q9iBñIÞŠÇI.óûï\v* ¤‚‡ÐÔÖX'B뽓«I†¯bn%󣉥 nå^O¡>|8uêÔ9sæDGGƒSýõ×_P)úõî†Õ///ìBÝé2ˆ#4@øÀß¼y3fÌ???’3rjÓ¶mÝlg;ÀçØ%?Ÿ~ùÄïsøç„-[¼&MüÇ”ÞP&x{{ÿÙ  ç\¶Riu‡¦+Q¬A»êcݺaoúV«Zm›Ã‡C"d}@&e?¼sÉâyšd«—¢ =Nøôñ]™”ƒ{hl¨˜ZYà ´&?ãgƒÐ˜ïaaÀýúiÖ»Þ}°ÂLOOÒÖú)„~ôùAmæ¯ï“÷Qsð9áémíáhÂnpõêÕß+³ê߃üQ{ñ¼uOïµmÝyØà”ãÏ×??·kÓÃiêêâÅK€ÏdefŠð0\)$''^¸p>Z#™_Ÿ?5nÜ•R@h"øÑÑ8æ!BãÜ»§oR;<¶éÙ£; tçö-'éBŸ_ùü[¢xqÀ-"¼ ¡¡„ͤO¤v¬•\¯Tr½’J~V«n‰Ôæ6™~dL)„ÆßPŽ×‰¹YaaYK–ÄGDè"4S$Íš5+ËÇç=ƒg B3™Ìnݺ%$$Ü»woöìÙ C]/¦M›–””ü,ôò-q„ŽŒŒ …ww÷³gϪӉå|Æüù+ãµ)½~[_‡á§Nø?Ÿ;ðøÑç°O î¦ xÆ X²°W^.89x¡Ï ÒeKjRô˜µÝæî¿öîhÝY£çí_´hѸ¸8±»68> ¦AÐqN—¢ MjÇbB¾i¸‡ÆÔ›…^„Öâgü,ð. “““Ë”)¥wéê5¨/¯ßÞ¹ÿ–K—V滺ºªÞK¤†Ð/¾>i°©]ïýCQš[úœ} ‰]°`ñò—èò³ÿ³‡ 6€*߬YÓ¹sœgÏžQ¿^Ý ·/ŒÞøðáC‰ÅvÛclÝ .‹/1{æ: èmž—m*U5r”\.¡çü#h¼woG倔ß'\Ùè@t~‰…Æ™‘CS±ûga}$xa2?š2BA—/û»û¬Áúâ§œWÖ²­ ¡g,­|»Ù?Í 62ÁŽ Bgf¤§ éœR»xrýR¬ ýßÃy<œSöŒ‘€Óœi³©:u2t‰·fÍjüGÉôú@Öû÷Y+f+–ÍÉ Q£²6mÊ:t(ËÃ#ËÖö×úää’%KêËw ~¾š}ñâÅQ£FÅÇǧ6²ýüùóF=zôøñãïß¿'žA„^µrY™2åôÿÛÿÙ''gw·5ºü|ñÂéÒ¥Kýùg+—Ϫå°A*”zxxT¬RnÑéÁ!ZÕ¡ú *_©¬———ѳÑ¡óo^¿0o®³n¬…&E«WÂfsfÏ€ü µ²¸uó’V8üL$ £­œ)N.ÞµkV†C† qvV>š÷{™2WoÜZë¾#j Rõ¼F:<>¤µWO,ÊÔD„ιîàA ƒ—.]Ú¹s§··÷½{÷ Y€:•™™±wïÞ’%K­_ëíµùJùrW¬X¡PHuäõuès×÷:*¤ƒÐÄgºÓû«‘Cƒ¦ cÍ94)JŒ»G¡›5kZ²Dñ 3+ "ôh‡v;uĹÀAhŸ‡\×,ß±ÝÓ¨`³Ó§ŽR@èZMªº™®«?Uv^5–HAIDEx(°Áßæš•™™””ôõëW&“):±—=¡–BþîÝ»gLõ.l9~ˆ£^ÈjÝú$c*ZT{ hùò¨¨(jùª:&&ÆÆÆfêÔ©3g΄ÞßLì“c>| ¡V¾ˆ|îçkWÏ÷îm·eóFÝmΜ:HÖ²esR3ÚiÅÃÏtž=}wŸì9ŸÇ7UÎÆp}„îø3ðó¼ùsÓ•gI„Ÿ EtˆŒ ïı€¸†Æ¢g9OWÜÂfrñaO½Y`zÇŸ‰dA¡•o‹á;::Êd²ŒŒŒÉ“'+‡v+UºûðQ:ua¹zõê™™™Dz§k‡}šmí„“)-­`–Jx2©$gÏ_—í'3Ë—¯¸Åã"Pôꇊ+—bG¾P½ºu¾~ù€Ê©ð t¾{µ Á9ôPtüƒd~4H/?Dh…\âà`ÍýKP`—/_6:±€^„Nˆûöôñ݇÷ol“zµJ÷~íz êhS£Üˆ ªRõ²vC:uµoÓ¯_ü‰§  €œŸ¹K°Y¶ ®–ŠypN32ÒáÜITk Ió}FÄ} ëÆ %3kŽBëâôС‘DÄ¥–¯¡“““?åØ÷ïß̈́Ќ B¿ zY²dÉ>}ì M»!à¥îܱ¥X±bíÛµ0á&¾2vÓ¦M%Jo?°á¼ã€–—œ¢õü`Û¾ `//¯tcW1#:€Êqoݼäää¸aÝšóçNjQ´ÛÚ•°rû*ØÖâgCYÀŬ?Ÿ=sœxD:{8êQº\,b‹…l™L:lØ0Í–yÆ jÀGèú£›6öìid†º÷¯ÍÝ·ZËå²±cÇ–+WaÍŠC@ѽz3g®Ñ¹ô‘¬A,æÏ²eÊèÞÃBBÊ_m”o ¾?…Ô ­bF…lÀÀþ­:ý5~Î jÙ¡ñÐaCñ_å läeÒcÇŽí!lÊM9‰{‘@¿ožÍ™=ãô©£4Ëyúû੘¥ìïïß¼E³ªÕª”-W¦fíU¦l™ªÕª¶hÙ< v‘ÌÌ l:S$ YôÖAI…Y7of•(‘ÍÌ¥J)¥FèÖ­³ŽK—IL °ÄzÆŒo(ü±OŸ>Fíؽ{÷e”¬[·nÿý·Ñ—’ìݳª0p”îOLFü™SÇ»wï  èß791ÊT:0áª*44tà æu+·í[¿Ç¸¦ÝÇ6iãP¯z¸Ò2tðçÏŸa3‚ãÏŠr^\ì—Û=§O›¼há ÙÍžëA°_a%°4\~*Èó³Þ,–,žgJZ;@ZĉDà–X³ìááW©êË|„.ߥêŽã[oÞ¸DA´¿DC¬¢h—bÅŠ÷¶Þ¢yÇþ™  ö RÞê]P@‹ÍP9 ŒÇ †‘š‘C"æ…‡‡ÿ=üo{‡>4|Äð¯_¿JÄ\#> `ª†+¹%²ÉöÈé2ÞÙ3DZ^˜¬ÎŸ;™.çí©eRt²™”4:¨®PHÞ›Ä×ÏŒh%ÅÏø³1À¾eݾÍÌ!!rifbbVLt& {eüü¿Æáûøø+V¬%ƒ?ž:uJ&à1‚'1Â’9’ˆ' y[ªT©¾}œgN›?o¶ëšÛ½NhŠÏÐ?­ºg8{ölàgðdü—}#Y‰.]<3|øPTH¡­¡ <‰ÏMWÈÒÓ)I!3¯hº¡ÉTÈyÐ#SåÁ4ÚG“€W•“˜agð} ›¢ïÞͲ·Ïd™eR,`6›•””HAðG™THàN½%<ùÎíkmÚ´ªZµJ¹rå0(*Q¢DË–ÍçÌ™yïÎuáYË7àÁµ•ò2X€¯doUõ}ù¦É¤eÍRðA°_Å´N›fz”£h¬< ¾ÃJ|úä)Šøj¦º©D€¨#_ÈsÓúeK¡r@BmE®ðR1!ßCBB*Ø ûÌdÄÓ›&$öé*[$³jú´)‡z£r@2ôd &„С‘Ì!FJl\ÌWÇÿ!)H™‡Êɬ²µíá÷ЕBhëDh$$$¤/fjlB\DJR-‚¤Òq¨T‘Ì­5þˆú†Ê _¡I½!4RVJRtùòåQ9 !„6ß+Å)p5Bh$$$$$$kÖË€ÇíÛµE倄:O ÚW#„FBBBBB²f>ulÜØQ¨B[W#„FBBBBB²f¹»­^½ê_TH¡­Š«[´h>tè Å‹æíõÞqÇ÷Ú—ðö»þ*à)põÆ k«{öì\ýÛo¥›6ýkðàèB )ø9{ñ:*ŠÂ¦Ê•m~ÆG倄Úš¤|±¯˜§µ259æõ«§çΞüïÑä¢HHHHy"½í3RÁV|lD•*•Q9 å¯Xh5â"^"¬wS´Ø«UÐCkŸ›ÂLç°Q( ”…%³ÈGFð(Dè ùL¹Lœž.7*¹\"0) ÏI9é ~zº¶ žX”F{‘Š,©D KÒ2,ÀWXIo2òY<}|¿k×κë%b®2çB2‰ˆ‹zR„ÐDÖ¯Dæ…£¶¡ŸÌôÚSÚÐCèžîß»I\ýî’:ûÏž<¨^½Úõkp¶ùþícëV-M )®öâùSïƒ_åÓjý½! Ç©ŽÞÞÞF³8vôÀäIÿèòó§OŸ†ÒÛÎŽ‚àaaaf£hBÙ‰Ðz–É"´‰˜ã )æ…Ö{Ç0üó;R“æ•*UòÈ¡½ÄϾßC_øÀ-Î6Ø>lÛºÉD„†D\׬(HýüÙ#Ý-gΘö.èeA"CFZÜ“°GÖ||ƒŸÌà'1) +_"ôàA·o÷òöÞCP..KV­ü!´•DtÄÅ|kظI‡nýwíô2´Íí[WZ¶íÖ°qÓÐoˆ£æÓ§OK•*í¼ØuÖ’µ8‚ *V²¹xñ¢„|؉ Î0Ëyú¡ƒ{ŽöÖÒ&÷eKeeL‹ÂWÈ¥AAA}ì{ƒçÔlTµýÀ¶›`¡fê°Ò¡¯ýû÷ïr µËuv}zaY´X_+‹¾ýp²X¹béúu®:×22¸D²ëÔuíœÅdÛ± P4$B—CgO¨L&“a ér!Ÿ‰°6ßrà0->Bãó0µ pö‡ø°¹Åÿþ †¯'NÜ­²½{÷8pàðáÃÇŽ;yòä©S§Îž={áÂ…Ë—/_»v­I“&UªT.^¼8þ¨²åúûW¶¶= BÃAén9iâx½hšžÆ½Êp•Ë,Öϱ'§Öóh•øÅ‚“Ê‹g²¿2Y¡ æ;fÚ&;ŒÁbRæ.¨4&#è=ëå[VBÓô,† $ õ6`ÇŽ+V¬ˆŒŒT¯|ùò¥9úg,3*‚Iù(Øi?S“cˆ‹™O0£I[ûNØbODq‰_ht6‚ñuS§:ŽuZ³rÛµ†5û¨§`ã¿7jÜtɆÓÓ—ììmoO“ÄÜ«W¯V²©üêǨ5i¾ÿ~©„O8¹Ïýzmóp]³<4äµ®üÞž>mòÍëb£ÃdRŽ üÌÜssw+Q¢x§aþoÐúÿÆiiÏ ŽƒÁ6lHWBRYlܨ?‹1£Gœ9}\¡Óå=»÷X7©øct†PuP‘–|އDêü$ÿ™–Î(R8ð èÞvv©Ã³)—Kä2‘nðIffF·nÝ´ÆÐ¼½½ac„µù¡qŠŽH›c‚„oÁX蔿?ˆáëÊ•+Ÿ>}êïïÿêÕ«ÀÀ@¸|þøñcxxxDDDttt|||RRƒÁèÔ©SŸÞ½Ú·k[æ÷ß RœÅF¡¹ì$Hd­ëʂЫ9¯žØßy„Ã?_ÊY„¼ã±É}“ç:L§Ïœ°0B³&ʘõ³xþl.'Ééܼ뚟œ|•÷„c…ÍÿÉfNyÍHø/5ö^jÌÝÔøGŒä猴÷Ln4CÀ1KA=Å>Mú{ƒÌbµ²0Ù‘½xÍ2¡¿ì¹ÌPëÞk4b±.Ÿ=<<̇Ð_BÓ<–ŠšüžuõòQÌwÞ^¯^ÿºMìë´²­Ó¡kínjÙµ©Õ¯yÍ!ë høWŸ-{Ôkß±n×Öuz5­Ý¯AÍ~ÆÇFɨ޴âÕâÞG]]êÖv,ò#.Ä’ñ ¤Ÿ‘5kéºó¶ë._'—]:wæNk›‘£Fcයx÷ÊJZ.ã^½|fÞ\çE çÌ™=8ùÖÍKZ:tp““ãòÏœ1•ÅŒQЏV²ºBæ8uJ¥jågîsÐ%[MÍð¶¯X¥œÓŒé*ÄeÊb ™,fÌtÒÍ¢m›Ö¯_=5„ÐJ$¾ä“\»˜è2Äby9Ú7%ùƒÎä34—ºt¨l£Ì«Eþ/ÙI‰ŒÈ(æÿ.ðZôVüV?óÆ]މò]|óW­ /"h4|}}ýüüž={¶gÏÿÙ;°&²-Ž»ê×]·—§kÝ·«î³®®½¡+`ï½+¢X±wbo€ˆAÁ‚ ¢H‘Þ{ï„4B:ï$£ãH2 Îùþ_¾™;·LŸßÜœ9÷l­öB¿vâvÿ²"#Eý^èîÿÕíÜNÿ†vXùü£›?^?þ­«þ׉óÛé–5Xçÿ²{˜ü÷êúŽ.S~‰ëóí‚ßÚÇņÒDèÃw”\P7ž©Y„¦‘Ã驃†ö<A<¸ŸšÁêöµCÿ%3hÏZ{áü©‚Ð¥¥ùÆÇ ­n[¦¥FŸ;k ´Ljù²E  ëׯžfeÄi‡y©ç]R\¸ÿÀþï~n·ùÎDš]lø9Ô|ëšeó ›zM,\6ïF Íó¤8~>ìl,àæòƒòà·šMÄÆ„¬ß¼vÌøÑËV-~ëóºú[‘Ïfp£˜ÙnŒx»œ ‹Lé.‡R^¥¸›¤˜fÆÜÍÉxÉ`‡1 Fì(xWZ²QôûÒØ2/“¡½°ðûžeiiLõš˜8AKž#¤/\¸pñâÅÏŸ?ÿ¡·ª¸«Ò‘3‡WgG©…Ð!tzЊòÜxvú=B×ü Êû3!÷×φŒÔ& y× ‡ÿþù?¿·îÄÒ¤„ˆnÿí¾õˆ ™aܤÅ×®™Ói´J„öOàes 9Å2zíæž’’RVVRRRH¨¨°  _ÉÉ_ZÊ'ðø®Í #Ã}«u–C⣇÷ãÃ`°ôc„Vm\ÿÖ­[é\'ñS· $¾Ówü#³hù™1­Zµ  9J›½oBiÿ3U+Îh@‘ÐÐP² ¸“·oÿŸªvÔ;„.ÎÌÞ¸DàqNÇÖ9Z Î)ƦÆe” NÈéØJ¢ßZævûBdsµ8“õ¡‹hî1hnêÔ©T8­“~ùQ¶â"!‰ÐÒ·§\T«–ZžGGÝ8rÐ Ý@šÅH‘þcX5¾îÝ»·2?§¥¥QùY$BËl@KAhNž¡ìßÕúª¥é¬ÉZ¾ç7Bßܱ@OwìF"›«Ëc­á?3å?p&Bs°ºTððˆÙÓ¯Î?owØ“ÏË©‘&ìl­dòèì£ôç¦n–ÿÎo`¸WÕÊ+#4'JâÂi•ýæTÚã=IVëã¯®Ž¹½.þáîD êˆ[Yé/ìpF>“Qý­¸x•ÿKŸ²˜8;qÆ<ââÂQ3Ц.+”ºpäff2î]v䌠:—^è, •"&‰®®®Û·o¯U„žøwÉéDhú÷ç÷ïV9cÇ-Ûx‚„äÕ;.öêÓ—ÍÊ€EãÆÿ»@׈\´v·yï¾ýa‘Jžš_TRVX\ž&ˆŠ<¨5a¢–¶BM˜xéÒ%é÷kÊúÜY“5º+õõ×_½rÉÛë%l/Ÿ›EèÉãû—.ž®&BŽ3jàäÿVÁÏŸ¼\}RE÷×ꦥ­I‡<¡‰1£+7¡T¿kâÝËcLtp•Ï)¡Å)åæßí H–¨Ëg\å"!¤s–LÌéüÙ»ôN­ƒ»”‹Eâðd•Z(ȃ‡;ÙM˜®®®´·šnÿpmûB×Óç„U7WúaøzàÀÊüœ••Eåçââb˜…ôäääøøøC‡AÁxÉŸ¤õÐpïµ»o•ØÝÄÌMY¿|ÞÔá}öꯑáÀÍ›Öy¸=¯NU"ô€~}F þ‡”‘Z/# ˜ŠgÏck•ð$ý«×¼®?ëÞÝÈçe×HÎÎc¦h<Ér!7êËMgÿšÝ{·Uk+x¹Ühfæ+ ²Ûñ4›ñgæGL Á̾6N…E²Q;ØÕØŠœlÆ÷=ËîÙs32˜Ú oßçñ8’~iÏ·ì>%Þ~’ý¦@8nNQuÚÑ›ðZ);7Ü4¸\îš5k¼½½uttªÐÀVæü³WNå?²æÞ*|×ùÃú³M…ëSÎñÝóÇl²rªnÅ݆wêÒa¤Ë¯ó­ÚvöûÓ{ÛÝ×ùÊ{Ú©C;õö¸×³?·ìírry{›ñ?½éÙnfû_ª…М,‘óÃbƒ=%+—"tf,˜ôyBr Ož8 ¢^¼0 w9yoUÞŸI¥$Eýõ¿>z{¯¨¬5må–-›LMÏ=™LÔ7²ú³g¯ oº·ïšÁ+2ó×;ðz<ú»wï~ùõ·YôטŊ4zaË–­|}}¥>´Êzû¶M$6S•¶qÚê 4!<¶à<‘ù¸ï#~–CÑz–šœ’’"uÀV¹ :Zg©E§‰Ÿ†H\‹Þ¼ü€Ð¿µÌíúsH7æ Î9]¿ !C‘ß›âLQ ¡‹ Nž ÖaР+W,­RövÖŠ[ÕxnJMhĈ¡•sîÞµÍÃýñDÎÍNª)„3p@…­-¡Ò{÷ÆRÃŽïußÿÞþ5ÿæ '+?$k\ ÈbT§‰¤„ˆÁ#=L{Fn©9kçÞ¶²¬ÎVð’9Œhëlw“´;âO ÓüvnoOÝ¡~Gg„E{œH½—“ëÉà§Tk+̯ó•™ç¯6ßwàž4Í7>Ÿ¿n—Ðé%gÎjIïÐÍ»¼î#JjÜ‘C(®X±ÂÀÀ`×®]ÕDh6“±fV¡í É oÝØÿ\~dÛ;„¾eÊÿ«]Åé‚È <Ÿ×ì~?•nªºsf-7nÖ¸ñ3ÆiO;iÊØ©5fhk̯1oܸ9c5gŽ0McòdiÆÌÔ=gܨógç1ÓÕChÑS‡²¡C*:t¨øý÷²Q#ÊF/éÒf‹† ={¤ô¥ž¸á9|L4<¸—H¤ú¿±hÿäógÿmØ å='ý·GŸö¿uÞ~Ì–HÙrØúÏž½]ž?QÁÿá=BóE%ÇÞÌ\ù¨S*7ÜÕÕõ—NÝ—ž µâ| u–Ô_~ýðáCq¥ÿ+#ô­›«u–ÆWIÑ[·l ðT¡‹Ä‚Ó§Owøý'%ü,‡¢îø½©©©b>, Μ9Óþ÷»kÿù—­%ñ]¿hýYEúô I¶Ï¿ü´çÈß~éôý¥K—7AEhQhb™@$0Ùÿ¡ÃYêKåçΟ\8V–/$|¤UBhà„^½zûãþýûcÆŒ!¦ád(ó¡¡ë2.tƒ -ƒÐŠÿ$úСCÀϱ±±4ùÌÌÌ ½(>únÏ;uüM¬n_Š üR¡2Ú¯ô4ƒ¥ä]À`OíÉð4T[¶÷n×BS7yÜØ1 ¡9¹ìyEœõïþt{ÒѨ÷”+óXì4‰wÇV‘ĻÚW&fÎnæ~µ2?ƒžæ¼ª1T鋞‚&òsÌF¢CŽÏù Û­‰Çf…/àó÷O/†¶¹zˆïéyw'˜f&?ÉÍ aÒw稼³uÄ{…äéí>væ8¹¼ëÙƒkÑè”àß¹’'ãíû¼^cª…Ðâ¢r¨ŒÌ—óÕ«WáyíÚµj"ôÑ¡-ïÀ8"0ïÏ6nÎï6dõŒÂÙ#‹yœ\rvÎèây)«É tÑ™@Ë¥Ó§ˆž?°3ß½<=¹`Ò碉š°¨ðâµä3Ö¯_OÜóoݼBxg³›6m"¿Rz–ÑÛ·ntÿkÝ>K‚™õöXüôk§ÆvDÿó=z;>±SÍ…ø=BÇg Cr\¡·<(,æ/Y¹–€äeg¼còÊÊKã²+/ªÐV·-9èþÚ™Äf±UQ&()âÂôõ«fÖw®©Ð%ÅâE‹õ×Ÿ«¢è>㺬\¹²¸X¤¸‰Å‹÷×ê:^·O MK¯4±bå ÅM|„ÐÒÏËÅ…œÅ>êv¦tJsug•‹‹ÈÏ é#´HÈ !Ö­M›6p[€kŸ˜2e ép‚Ý„Z±ƒDM >¨8ƒb¼o9”ÿ?H ô‘#G€Ÿiò3¼SCÁäÄÈršmxpo-ÕŸÏËiQ ›5sš¨î¯Øý[c̨†ŽÐ¹ V¯ò¼Ñ¥0ý&Ƶë¡~c/MÍf¾ë…ËdñÎ ÚM<{öhÖê9Uò3!sk3æL«ÎVpb$îÐaײž¦\^½E#@³«[vÎÓ{xìŸrkmœÛñ´›ìœ7 ^²ú=P³øš5Ïìÿ¾÷(Åá9Ÿ—{äŒ`ââB˜Þo,˜¾¼°:}Éž5iG2©ãw’ð³%%ð}áÂ…5kÖT¡½_±ûþXžþî`+ÌrX ¢wºÏåv7?¼+-›P¤;³°a"´øÞmIo³á~ÁÇ>Tï|¡S‚Š ö@±ý]… Â_¸pá?|ÿŸÿü ôøë¯¿Àô’%‹Å¢|ú÷çÊòóõøãÏžº;M›ç¬Ü?TcÖÀ“7ÜøýÏ¿œTþ ï=Bû%ð K…€Ð ƒn¸"¦£ö³àÍ–aü´·K³Yi«u–'Ƈ©Ðïù–?LѪ"4}Š&ø4töŸÚ´JÔDèßdºë¬‘J:CU„.ÈgÀcýçŸ&ÖðÞ½{ee¥p½÷îÝ›HWé¢B"4"´zÎ!õÒu\ï9LLLRSSeø¹¨¨H?ƒ? ¦¥Ä(>úÞ^¯FŒêúò©bOæiS'KGuì¹óC¡KŠye¥ù…bÍcWR,^²tI­®³÷QõÿÁy†ÃúŒë²jÕ*šŽ¤û‡RŠ&ùYâ+2¶óÊU+Uwä(â,Ò®Ú‘£ÛÜU3Ê Uvä€#îääD®ä¾}ûŒ¥6xð`"eàÀÒшЈÐÕ8]Ñ že„èGä8uêTff¦ ?—Iê˳sçÎAÁôT%=Š9’î[)Èò¶šOI¨A[{¼Ä©›†×t#úœÞP¦hjl^·ŠûÞ“ÐÚ5«†ôë™e}úƶùÿŽÓ¸gs³6úÇ!-t ßIsþ¡#FjlEbVäÀÓc{ KñçÙó$£¨œÔÈŽÒš¬y'ÜN)BƒæmXøÔñAuŽ7N :ænŽÏ¹ Ç}Éw6H¢Û??Øžèz,5Ô2+Å‘Á dæg«‰Ðf×ø_ÿQžÈÜaT0qqá WN@PÞù+üñs‹Â#YQ¬Ÿz•AµÏ¨C‡Œ1|Ô¨‘45xð ;…|(Óà_÷/+.‘YO603 tL뙽„¯Æ÷.9èÝAgf3f +ö÷d«},j¡‹wn-ïÑCP‚~ÔŽ›]þÇÅûv)ëôcædgÚØÜÉÉÉ’‰u@ßãÎôʃ?Lþs 4Lô¾øß)K·±ùOÇÿjÎÐÝ{úÉô%Ûzš‹zXÛôz˜ØºÏT¸Ðé,q27TïiÕOºØ Zý¸›Žª d99樇ÐW,.ØÝ¿MúB§¥FÑɉy¡¨a‘4ŒgN¡˜ÖIhÏ£öÝ~¹°§ª=féÿ~îø™™™²Ï ó!¡ êwˆ (šÊÏÒ/•7QùsÂüã{sº|V™ŸÉHw‚óGÊòE*!tiIÑܹsï“èèhQº~E‚\iƒGèzt]®„¦‘ãÌ™3¹¹¹yyy4ùŒø!3=¾! 4fB\XCè|^ÎÊ9“¢-w¸šèìØ ûgnÏÊã,ÒÒ[2›fTçºDh¾çp ›‘ÉHyaÂïG¼— “ÿ†7´”gË«þŽŠó?S“?ƒ¬#ü«=¶ZqE8¹œXf¶;#áANðÉ0+/ŒRž¦?û^̈º“þ‚‘¬>B3™Œ.ÿ”™TœžÎt|ÎÙ´O¸aÐúËÎõðfwü»tÍa>¯º/eµzÒ®šRØëÛò%ZE6|ks> 4€tv:“ÇÎÕ›]h¸YæŸçò˜³0Ô/¯ál‰ÐC·tYlò¯ç¸¿X=%•5vwOj\èòþýŠ7¯§rUT((ÐL©ÿFrr—f.Ð9ÊçJ¨þèé[@È‹7]Öž©3dè°‰'Ž?KoŸUËŸ¦¬Ýz–è|Þmdy|üè|YæííÝúÓOg/Ö»tí¼ezѱñ9‚¤#SW=ê¼úÉï$9“zeâ“§B»¿v62ÜG 4rY ÿÉãûEx9‡y±Yi\v¤À"¡€A§/Z(ÈKHH€ódÝU-ž¥&MAæµ’¾—´´4¥Aíˆ&Ö_×VJÑ2ü¼ÖBó“O>QÚ„LP»BÙ vw`ôÿlP;_âÌ<š /q\.÷óÏ?WŒÐ{öì).!B#B7C£"4ˆ™+ùÇÍ¥&R.AèóçÏ«ÄÏ`§OŸ&Z^Í„œžJÚö¾•‚<¡ÁR„>qLqUŠ•›,EèÕ©¤•’MÝ ž/G޶\k0ËÖ¤9¸÷˜Ñ#Ù¬Ìnê€ýáÁªj —Ê"4—5Z¡‡ñö’Eh£ÃF4›`o³ºTdÝÏÖ2ŸÙéPŸW‘/j|GéoÛ`lš&Bƒ´çMô÷{£Æ±ø  èxF®3Õ)7ænN赬 ‹Ìˬ(«ì¤‡¹@×ìHf~.Cí&•¿ëQÞ¾_™ñù|/ß¼Ðp–ýî=q›®åOøÜjQupÒ²Œ—O8©‰LÉÉ™ÍxíÄá°>ìø(Ö‹‡ÜPÿ<•6¤¶‚Єýý5÷öÝÕþoIÇ“wõ!f++%=Bò.“›ZÑ©SáQ£ê4ªôþ :yÞx8&&†˜åäeýðßùí»O›7Üàò××ßüëïÿ¶þeZzZò»j™m;ÎÒÛ~ŽÎ:”–™››O›6}êÔi «W¯>}ú4=33.Ï×:Üà ›6Ù­ó¸Ûµàmì|ñ¶ëáT„ ¹ ëÿ€Ð©É‘ÔÐv@ȉñaB?zxàçy¢#ˆÄ§ŽvÒ¾hŽò­(-÷ïØ~W5hs¿ñÝ&LÒ†@§ qc*7!CÑ2ü êûo׉“&(mâã¡U ·ÏéÔšŒ_ÇÕ].• ¸Ë¦|èšîÔš1¨³dh•ˆ”w­°``8¾Äz¶lÙrîܹó(Ö·o_bQÇŽK߃DQáGÝH¯ZˆÐõˆÐ .'W,â+Fè‹/òx<úü vêÔ)(˜™Ø úð!ƒ¦Ð„Ö/Ÿwß’óëfž?Qõ“zÄð¡j4QÍ`1Ìóf]YÜÁ°—Õ§œýÂüBŸ{a¾Rwyõ±—ÆÌ eäx2Ò_0RsSž0ÒžIø™Ää&1ò9Õ"ÃÈ(0óWÿ-oÙ¡ôS¯²å›DA!y5uFÕÁIÛèš‘ðõ³ŠDwo+Î&¶º.q™öz]ÍæߟA‹×ÿøÇjŠÆ´=¯¢¾;™¼åt¨yßNp cr ŠÉÏ oïÈã‹·¿çg5ÓÖw®ÙÝ¿mh°G_=²ß[7˜I–¦Î-±o…¨€Ôºu«g5èó󲓣¡Hxx¸°@9¥“M¬<'ÛÄ_£~#àSs,?/•6¡´‰ø¾~1—໓d€ï‚‹Æ’¾cÒ c¥|Ÿ6”tP€Ýõ ¡µå‡¾"4ßY³f«:jÔ(I4žb1!˜öðð _$+,`#B#B#BS;-å!´™™YAA}~;qâ„tŒ€¤‚а&M¡9y™+—-K¿ŽÉlfHà™Á~æ35B†œ¼Ü¸fRSÕžgDè7§tꔲAÿÒäþÅ&H‰-ðwéì™ù´ÿ¢Ñ#Zeú|£¿ö\LM7cwïázu¡ÏÙ×}ðjjÊ-ýáÚ[ÕX Âé—Zeëµð6„ËÎðó ]£I=w~(²DB6îôÒ’â•«Vâ.?3Fiÿ3Àíšµº%%ÅPæÎ!šX±rym4A"tI.Gìh`,²¹Z.ãw¤P‰³t¹°@xû2gÅT±³¡ƒÐb!/>>~ýúõGŽ‘®óãC/ ÒÕÕ555…•)/+1bDnÒÅbÄÚ:Ñ;C„n-ïC¡'Nœ¸wïÞ}ª˜††”×sRÇê7sÆÔAƒN›:™Ô˜Ñ#ÇKM<øꬦæ¸Ñ£FÀ„ñ1£‹ÐÎNeBkêN~>úìxíí¨ß:wxÆrS¡m¢ìÇiEøÄ&”St€WÙ *ºt.Þ '²³)ðõ‰l­‹×­©èܹlÈà‚ ŸšmQžGGttô§¿N_·ý<1kj)‰ÎñÈñ£{‚`È'?NÙmdAÌ?ky\Ý|ªÐ =ó³ôÌ‚å ð­¡ó%Ý›üŒ´ØÈpJKðs*ÊTi‚Ÿ•zqP— ˆ{èС֭[õ×ê¶ÎR«2Ùê]Ñì7¾d066®Œ‘4›022jÕªUMMtUµ‰Ÿ†%—²ó‹3XâðdY~~/qD Ð5d+ÉáˆÂ’è ´Ô…[\$**,¨²3–KÃ:——•Ž9º#´DÌÜ™ªàÞÕªÆd¤*¨äôÔ®»iS'mÑßP¥¼Þ¼ ö<“&j9lP¥^¿rRÜ ˆ‘“¬³j5ÅÌô¬³“5eÑÂyÔÙW.ŽçΞPZsÝ(%) vl‚Ãõ»‘f£ÆŽÔ1ÔÛnº‡ÐÿûãíÞþñlï^BN{öôíÙãô)ãv=ú¶˜°øþ½yË&:M|9ð»9 ìí¬ko+ºþÙõèýT¹oòA¶'>ÒýêÙ¯§·×«†s,°‰zoBžY‰EG–÷é]Ñ¡©ò¾}  ²“jêÂW|&ddrxÄ„­çîoùÓ”ÙËñ¹Ù2y¶î3…<SwŽŸµ&V¬WóÎ&ñ¬¬¬Ú~ým-¾Z«¨¦N«V­½½½…l¥Õ¦2±¤ABòˆˆÎNöR~ÎSí¨IGß ÕÒÖ”ü/Ùñû>㺠›Ó?wüî“O>™8iBDDtBCÚDHH¥‰Îï›è M@℉Úááá*5QRR¤­¥5bÀ ]:ëÔаþ§Lž\RRXSg¦¨€C8rÈFA>³<4›‰¡ë¡)ÿÖÏÑ'ZYZ\"ZÜÛä/ªc{vo«í&–.]Ô¶›À&T;³ÀÏCd0[ƒW=Íû3ó ÛCVwê»l—áeidž*ò˜œ³úãŸU]û¯+>68$ÐÍß÷U€ÿë @÷Pð›ð¯ðP¯ˆpŸÈð·Q‘¾1Q~qÑ „¸ øØ Ä¸à¤„Фİä„ð”ÄðÔ”ˆ´”È´äÈôÔ¨´ÔètPJS“#@)IɉáÉ aɉ¡P01>”MK+ Œ‹‘(6: &Ê?:Êš‹ŽôŽðŠð…€ÕˆyG„zÁŠ……x…†x†¿ yÀ ºÃš¸ú¿ðw ðs ð}åï÷ÊÏ÷¥ßÛ—¾>.¾>/|½_¼õ~òñzæíùÌë³÷'¯7O==Pu'Øá^’Ýî ‡ÀÛÓŽ84ù¸ÀÁ‚Cæïû2ÀïÄ@ÿëÀ· @·à7É™ä =øMhÈ›°`ÏðOÉY 'œp®FøDÁéá-=c%'R´¬DpvÁi&=Ùà” &Î@8“%çp˜ääL O‘ž«’3YzöJNã4©R£3Ò¢ÓàÜN‰’œÌÉp&KNþĄЄXÉ9 A»Q’“ö-¬¬[Xˆgp œŸA¯ƒ<âBù,±d°6¼ó£P( º±"´P'ÈÏò–à±³Å"~¡Xð^ùïÅ/ñʼn÷NBª¸¢%–ˆ÷!³´l¡¤ >¥rI[E…„ >¨HXüN¢w*‰K>¨³YB }YZL¨L¢‰Ê@¥„Ê%*+/'%± PE9žuk’}þÞÞ84ï”TÒc'9ˆÅäa•‰q*œ”³Br’'Œôü©¨PH9µ$gZ¥3<¿°ês[ö”þøÜæ¾;óE²'¹œó<ŸÏe$'F„»ÃE—žÌm0>>ÂÕÝíÍ[_©Þâ/þâ/þâ/þR_»¿†'"tAhx¬ÇDú%%„Á„°€-à3ð……ª ø¹€Íp¡¥$EFGù`S—&$DJn’Þ^þoQ( …ª,xF¼ñy›˜‰Ýº¨PøÖûy>ŸU `!ä š½r)ª•‚ù,aÇÏçEQa5ÝÝËÓË÷í3444444ùO w¯7ˆÐõ‡ÐÅù¼lBB ìÚƒ…jD]Ä|nN|\T` ¿‡»»ŸïÛØ˜Hv^¦ÒëB‚"!Ï×ÇE¡=}}£¢Ãñ逆†††¦ÀàIáåë+}p¼9DhDhªÞø9Ÿ™›“îááqõêÕS§N:tèĉW®\q}õ*3#Yw“zå!t\|4>ÐÐÐÐÐ<)¼}ý¡BH:OÀÿp8@-*u)™GéD¨rm5[?ª9 ^ó˜YÎÎΆ††ûöí»rÅâÑ£‡×¯_#f?~œ•*suT³ ¡?$"B£¡¡¡¡ÑFèlD膌Р‡l¡Qµ'¡ / À ×ÂârnnVZZò¹³g23ÓX¬\;;Û={öxyyò¹¹Š FG…³ÙLBPƒõ+¡ÑÐÐÐС›!B“Ò2}ÎÔþê*)®„>ES+—©YÁ45§ÒvTóé‚f2²oݺuôè&3'99aðàA_ýõÈ‘#€„!ÅÔÔôÆé)ù_ Ô‚11‘ß~ûííÛ7ÉvìØ&¯ "4"t#5ØÛ|n6!UZ†BéxtÐ\ª*B˃jš‰Ø›ä333ÒNœ8áøäqbbÜ AÿhhŒ$1b8x8ÀßïôéÓ ñ‚|†¼‚99™7o^oÛ¶íåËfD ©©Iò "B£¡¡¡¡U¡IC„n€]eW­bFUìM‡ik¡Ñ!õ±3+))ñèÑ£~¾oO˜ÿúë¯##Ã23ÓBCƒÚ´icjz1>.æÂ… áááÀÌò B~™Ù¥V­ZõêÕ+))fåD„FCCCCC„n:/(L[Rg«L'§éL(M$g “×4µª)®¹Êjåm#ªùHR#7X×ÅåElLÔðáÃ@AAþÿü3pôèQ€ÁÁÁ×®]KMMðò ¦¥&GGE@àç¶mÛ^½zRä‘MMô|뇆†††¦ÈàIáåëG}| B#B+Ue„¦¹zUf@~F$qø\¸)]±°HNJˆŠ 6lh›6mF )vv¶®®®l6KAA(5pà¢È•+—¢---äD„FCCCCC„nÎ]e߯ª-¯ÃY1E« çJQÍYb?33ãÔ©S../âãbÂÃBŒFF„%ÄÇúx{I{’S ŸlذŽ(²°0?sæ´‚‚ˆÐhhhhhˆÐM¡«ô” r¯$¦éY¡*ÊÒñúP°ÎŠ@P͵#:·P,LNN>sæ pï÷À@/Ï766Ö.ðù_Õ,ˆÖ¸LWW·ÛÇÖ³gÏ.\¸ðÖ­[d¶;w¢ãÇÓ¯ÙÚÚúÎ;Õ_à .@Ózzzj×ðäɨaÊ”)x¸Ñ¡Ñª‰Ðu&$XTCðˆ.*s8OOÏëׯøúúBŠX,¬ìÌ\Í‚ˆÐhËfÍš7êvíÚýüÞ¾ûî»Ö­[C"üž}ªB/^¼ò×BÛÛÛŸ={ööíÛˆÐhˆÐhͧ…jPNÂŽHȰ•³Ú¡ÑšBÖ­[7XtáÂíììüðáÃZEh¨ÿäÉyKaÝ#´ƒƒdÃÓ  …ª_!B£5„ÖÐЀE{öì©¡O:õ¿ÿýï³Ï>ƒôŸþyöìÙ@ªÄ¢aƵmÛÒ¿ûî;XT™rÿþûïN:SS §©©)™2þü_~ùåÎ;gΜE«V­"Ò} åßÿ… „+HåFD‰Ù»wï~òÉ'¢££Cæùõ×_»téò¬’/4ó?üôÛ¡C‡ Œ?¾¥Ô®^½Jd°±±ùñÇ?ÿüó¹sçºõë×ïË/¿¤"4 þo¿ý)cƌٸq#°:?ÀöáÇaéÙ³gaÑÌ™3É5éÕ«¤ôèуL÷H![DC«„~rˆÐˆÐ("4>ÐB<65 Ò»wïNø9PÚÎÎö«¯¾²²²"KÍ™32,]º”˜UìÈqòäIXºpáBbv×®]Do0I¹–––„\%BÃìôéÓÉÚV¬X)@ÂÄì?ÿüCmÖŸ |²ò3fÀìÊ•+Ɇ?ûì³_~ùèòÃÖuîÜ™Ü@×°n­Zµ‚i"±“ÔðäAC„nýáp P¨Ú“HÈ•"´€š(Eè(|: 5X„®ÒºtéråÊ"¡ `zÑ¢EÔz>|øå—_vèÐB;99µk×øœ˜ÕÔÔüùçŸ544¾øâ Â{yõêÕPüâÅ‹òúÆdm.\€mmm˜?ÿüó¿þú‹Úœ©©)¡üñÇï¿ÿžhˆ´iÓ¦Až£G´––LùŒŒŒ>‡_CCCH¹uë•ØÑÐj¡£¤…B!B#B£5|„.úÞÆ·páÂ;wR0¨MôúHËT•´lÙ’@S¥ŸB+ÙÎΦúé' è7’Þ}ûö¨&rVFèV­ZQý¨oß¾ ÆŽ Ó×®]ƒi``j[°!Ÿ|ò Сÿþ2ë³mÛ6H‡Í$ß¶lÙÓÓ§Oÿúë¯mll †3f@ÊúõëaéùóçñäAC„n>œaÿàÎëæ ›7.ã/þâ/õ×þurR$"4Z3wä¨Ò¨ T Ó2_ðõë×ÒïÝ»G¡÷íÛöîÝKølìÚµëúõë0±dÉÂwbêÔ©òºM›6Ôª¬¬¬H„‡i‚u©Ö¶m[¡‰VFŽ)“ÚµþèÑ£Ï>ûlÔ¨Q0ݹsç#F<“Æ'#E~øáŒã†Ý|:%9ÊöÞM7ç / UYpuØÝ¿•š…†­¡õôôª,Ò¡C‡O>ù„`K¥Mp²¶¶öºuë § $þòË/½zõÚ¿?¤11TBh{{{X‡1cÆP3PƒÚ9::¶jÕŠúm uaeˆÙÁƒ·k×îÎ;¸aÃbGAÍ·nݺžLõ…†æƒ©ßB>“FÕ#ƒ`ƒmÞ¼™ˆËAFÞ *!ý;v Ï4DèæƒÐ®¯3Òb‘ÐÐ\#p¥(†ä@ÿ7º«Wx½qA„Fkž Ö§OfÂ[ø™4ŽÜ_ýÕ²eK2ƒ¾¾>¤NÁÐ'„Sñ§Ÿ~J†×سg¤@ÍãÆ#³©ŠÐD'¹¦¦&á• K;vìHEh‡˜™ðÄ~&üL¤uZ[[ÃjÀºýôÓOD 09à=$¶mÛVæSD44Dè¦Ð¾o_ç1Ó‘ÐÐ\#~¾n ø9.6¤ãoˆx q¡ˆÐhÍ¡¯^½Ú¥KbP•îÝ»öÙgÀ–Û·o'ó_¸pˆš¸RnÞ¼YeÄg€d¤ °û÷c¸¨ŠÐ`S¦L”o¾ù¦G_|ñÅ€¾úê+êÐ*3g΄†`døñÇ!sß¾}É¡aƒí‚ôñãÇ“)D°k }ú­[·äU»fͨ–Œ·L¦PÝ0€À!j#f7mÚ´|ùrj%öööJÝÎÎÎ'Ož„·ƒ‘#GºÁªêèèȼ&ÀÍŸ?È!°’°*w,9rª…l2ûŠtö@CC„F„FCCSŠÐœ¼týÍëF ¢1fä;·á¦·mÙÀeg B£¡¡¡¡!B7„nÑ¢… ?TNAkJVåè ¸[h"ô•+—/[´bùb?±ˆïçë³ k–¦ˆÐhhhhhˆÐÍ ¡e ª‰Yvv¶<–ÆSå¾’‡Ð9Y 0¿xî,,`KQ™ãìü”Hdæ&#B£¡¡¡¡!B7«^h*MÉLË06™ŸL¬±o³XVV–»»»b„®òø6Ã#Kî+y WÐk×—¯_¿ yd¢XÄ{õÊÅíõ+>7 Z^¢¼i¥ÐêÓ-!/¯  @U„nžG–ØW 9DBàqåEtä@CCCCC„nÍãd¢é ]%)ÑäjDèÆîÈ!ó&Õl¬bGZ ­Îš9DèŽÐòz¤eþ»§ƒÐøZãrä ÐMûÈ*uä@„FCCCCC„n>]X("4 Ç[W#µ6º"wb£5dG5}ÓÞWpÀË&y©®,a¡ Åjz½ ´XÄC¡P(”ª_„¦>>¡>B+p‚EGަíÈQå¿ÍÖ‘…B¡PˆÐˆÐ*!´<”¢ÐlŒÈÑ9”~TØŽ,¹¯*#tfzìËŽ7¯›]±8wÕòÂkoÞ0%t ~¯›]¿véöm ëë©ÉQÍ¡É3„š˜˜íôôèîÝÛGŒŒ( U¿Ú»w×ÊË@‹/ÔÖÒ$ÄÎËA„F„VŠÐhÍÖ‘C©_G³ÝW•ú¡ÃW/ܵ¹b{ïšÝýëöv7ìozh‹$^6?u׿z³Eh*9S§+#ôwß~;kÖ,µLCCÃD]S»ìž={f̘¡^Ù lÙ²E½²;wV{c'L˜Ð¸6¶:GVuôèÑ;wÂëœJ ¥vìØAL¯X±BOOOAæ¹ÓæökÝoاÃ@C?JLêвüöëÖ¯.wÔÞ½{»wï®^Y¥ÛЮY8²4ˬ–ÚòåË'Jíû￟={"4"4"4Ãáu¨V¡ïX]qxpóÝÍ{6×nÝ´°ºeaeu…,²¾s=~xÇöžå«ËèÈ!ƒÐ•Õ©cGKKKõnn»wïVûƨvÙ´´4333õÊZ[[‡‡‡«WvÈ!jo¬±±qãÚØêY5vTvvvdd$ÇSic“’’ˆé§OŸzzz*È\XX˜ôÞnݺ•D±µk×Â/‡Ã©Ë•žž®­­­^Y¥ÛЮY8²j—3f "4"4"4Z ´Õ-³{w¯_6¿pêäq“ãÇLL¨2>ab|æÌ‰+—€¥­ïX B#B#B7|„NLLLNNV¡}||ªTYRRâææVï§1"4"4"4Z]"4Ÿ›}û¦Ù‹‹’’á1Œ‘’’’””bccma~šÏÍiæ­˜ŸAýúö±··WïæHí£Úeø|¾ze¸ŠŠŠÔ+«¯¯¯öÆr¹ÜƵ±Õ9²jì(¸fY,VYY™JûâÅ rZ(ŠÅbš¸·‹z?9ÎÁƒÕ+KcÈ5 GVí²sçÎ]¿^ ­ú}ãÚ¥S§ŽçädgeeeddE§¦¦&'''$$EÇÆÆFGGÃï©SÆ·o™#B+Eè÷ôèÞ¨kµûw”bóöö,Tµ”‹‹‹«çêê /&õ¾—x<žÚÿM4+[¸pá¦M¡¡«DhÜ?hh ¬Bç^¿zÑØø(<€²³³333¢‰ŽèÄÄD h¢#Ò½uÓŒÏe4g„VÊψЈРaG½|ùRŽÍW¯^©±zê7"4"4"tDh7444ùV¹Z‚ÐÇŽ Íd2‘"tNsjG'۔ɓ<<<ðF­ÔÈ/×Ðj|G©Ð #44TÕRùùùoß¾m{©¤¤îZx¶(µ;viæÝ¢’©W¼Ñ"t¡ÊñÒQ(’‡Ðb±8'''++ (šðåHJJŠ&|9x<^3GhyAí*kîœÙ *К†Á%YwUK…„„Àû5£FdZm„~^³fR~Û·oŸ¯¯/9»cÇܪ"4\ÅªŽ¢HÚ›¾Ñôý°»{JÑ2üŒ_#"B×»/´R—y³JñXÚ¡«#g§Ç­Zµª‘¾S*ûÉL+ͬ¸TCFèºÙü¦‡ÐB¡PB!B#B«‡Ð,‹&?WHÝkwïÞMR4ÆŽV¡ášMNNV©uhoš/ÝôÝr€¢/íØGåg¾³;ò3"tÝ#´J=Æò<=”vJ+ίti­#4;ƒPÓCè|>«¦ºr7¬bङØXºV7¿©!´±$.´„†Ä÷C« B#B«€Ðººº4ù¹2E#B«Ðaaa C¥"j8BÇÆÆÒ÷aöôð0ÒÛHEhÆ#—Õ‹–¨1„"ZÓAè÷ WZRT÷ûf§q•yª™_Þš BÓŒ…E|]Hý%Åç1[·n]Û Yy•šBWó›BKø6aHMÞç„@ѧN—ðÝ|ÚÈÈH¥üÀÏ¿ÿþûƒ^ʱ€€Å]}„600Ø·o_³BhŸ‚‚š™Ío˜ëlÑ«Iµ*++£ÉÏä÷ƒTeØ9é.^Š]/-¥UEb:¾ÍŠ ¹>Z,xëõ\Àgñ9™o½›B/´™Ù…O?ý´V’~†&‰Ð5²ù¡}\É+ˆÏɺ}ÓìŠÅE‡©©©Uµ ¿wׯâòyë;|¡ÙE„nâ­*Ð?ŸãœE/߸œ>E³ÙìÀÀ@:9ï7ð2»NòóÒé³.;o±Ç€H)wóß¼hYll,’m3Dhä,BÓÌ †/tE}„³£ t:¡¦‡Ð\ã³Ï>«¥tbV(í°møHY›ßÄÚñ±Í“G6ö¬l¬¯Ýµ¹~ÿîwºwÃöÞMÇÇwï?´¿…­*B{xxØØØ¬Y³†Ïç«TV(®^½zÞ¼ydX<• Ððï%&E'&&Rû]á="..®ŽšÊÏjP´¿¿¿LøAÿ&ìÛ¾ÃÇüðó¼‰S'më›·€¢Ÿw¯ÔõõñA¬m®ýäê¡Ь¼l*!´R_èúýœP¡óù,ž¡™M¡?ýôS÷—JÅç1k0²q“ŒÜÌ7¿J1¥M¾þóÞ#´Ëó®/¾~õÈýõã7<=ž‚EBó(Í¥t#¼yëÓ¼:4ÄÛ6}PŸÞ½¡I„‚MII^»vm~~>}~ÖÑÑÉÌÌ„}‹ŠT{Â’üLEè†CÑfffiiiä,PëÆôÄÖ8BWægU)Z¥¡ ¢÷lÙ¶xú̇’‰@ÑÓÿÕD~®3ËÉÉ!þÜ>|xý"tŒ¡¹õÐj‡q®¨^P;:Qïê¡óßz=“"t†¡“;áœ9s¢oŸÞÿü3Ô¯_ß¿ÿîOLÃs™˜ §³2“•~ÀØ ‡iæ›O¡“}}^‘W¼{ªˆÐY„N'…Ý${¡/]ºDÅW„~ðàå!4L<þxXOOE¬^½:++‹h‘Ãᨴ?a­ 9r–ŠÐE;884(„+..Þ°aƒ<Š®Y„–ÇÏô)ZÑ¢½½½óòò¨‰j»è 5ê^è÷^÷M‡fg£ÓíLŸÉ•v#B«Ôî™ór˜•„ø(bÚÑÑÌCF¡¡¡kÉíHЦ"4êÅ‹‰‰III g777&“ «¸ˆ@ ÐÑÑÉÎΦ¶èââ¢êÇnòº!Xe„ƒ »ºJú¼Ê`0ž<nfnL®?Ó¤èÔÔTÅž'UšA?СѡQ(DhDè&@Ñ$B?›ššªT`Å;wî$ù¹â}=‹¥ vGÓ@h‚¢×¯_¯BGD ݼù?.´twosïÞ›Nœiû'öË/,WÌÏ$E¯Ý¾V^[îîÖÀÛÍÏѡѡQ(DhDè&CÑ›6mj"øÙÜÜ\ÕJÊÊʪƒ²ÏŸ?/--m}éÒ¥ððpNU–››»téR¥ýàåËöçÏ·(+kQQAªUrò˜Í›©ÉÀ±Ót¦™d˜(Eè…‡:¿t†"™™™• ÞžÈišc¾~ýº°°éºŽ%3ö …]÷½yóæmÛ¶?_¾|Y½J’’’Õ+›““Ò4ÞDôõõ˱aÆYXXÐGh.—Ûc÷n*<“j¯ú´LoðÔUSS4ÉÏ` Æ.XÚb©<Íj1Ëò‚%·'ªw:"4"4"4 …Ý´ÚÎÎÎâ½íر£M›6£G¶ ­ªÉªúPËÒÏÏϺ†@þºººÔÈP¿{ÕÀÀ@Þ:X[[Ëð³R„607o‘ž^%BƒþÜ·Of˜Å üüôŇ7ŽåZË [ÊÓ–[è t|||bb"¢ "4"4"4öB7«`wˆÐˆÐÍ¡322’(æïïoooOMQ5¨ÇsssSoeÒÓÓ#""”fc³ÙÔ5ŒŠŠº{÷.52Ôï^=zôh•;¡J~VŠÐ“÷ì‘ÇÏ o¯_OHH)Â`0¦¬œR™¢eø¹¦Þ}hŽ–ˆ††B!B#B7I ®®N >>>,K½²êEuhhwòäIgggšü¬¡Ç+DèÏàŧr©ÜÜ\Š^phãsG™lÕGh‘H¤öK"4"tcé…n2ƒñÉÄ…®¼]2)ª‘F„F„n¶Miu> +))qttT¯lrr²ƒAÇÅÅ%%%ÕìN&—Ù 8è4wË… lmmiò³R„^sìX‹‚yÝéÈyÿH(zÅ;Š~~òìIå<ÕGhx™pÐhˆÐˆÐêY Ž½ÐµMÑŸ+OËï»Iá]«&‰è !­À¢¢¢Ô+[¹ÿV©ÕøçlÀÏ»wïöõõ¥&óx<:Å_¼xñàÁr–ÅbYZ*QÅíö“¥eÕ-ÿ»c‡‚²999@ÑsöÍ©’Ÿk¡14"4"t-!´z8]ZZÄe§Â^hª¦Nü9 SÀŠM¡«Äi:½ÐˆÐˆÐ4-55544´:5899©÷]¡z]Ê5T­J~V ¡+hŒÁM¡ÁôNžl#ËÏåå]Ì”ò¨˜¢ßx¿‘·´šM3´@ ˜5kÛ´i»]ºta©y ô;C„®YœVLÔÍ ¡IŠ–÷Kêæ K.‡AÈâ²)9íìô8,4€˜Ž‰{øÐ¶ijWåß ¦Ñ‘ZUóòòªÎô\.×ÃãÎ:¢ FPPPíñsý"tiié¦3g~63k‘—'çÒÒ–ÁÁý7oöU+ ` "´››NÓÒÒÚ·7ƒÇ>¡©Sw# !B×;šª¡ãtUÍäqÒßz¿d2’Ð9è÷B7Ã0pHúÝïž*"t¦°€íëó¢PÌ'+I:ºù tYY ¬zîÞw…‘‘‘UŽîWãàMŸŸë¡ ‹ˆ‰Ù~öì¤Ý»çïßÅÖVíà5…Ðôýg¡›BGH: Z¦Îº!jØÛܼ4BÍ¡ÑZmç ô…F„F„®=btwwW»xII‰zƒžÊªáa«ô»¶ÄÄDê'€ÄÌäì±cÇÆO˜OÆtuuë¡kêƒÐJÃAçæænÜx´|ù ú¯¿fé––öH¼M¡ßƒ\3GèZíÖ®ŒÐ£FÒߤgmeŒ½Ð‘Cž#‡¼øÍÞ‘ºÆ,::HIíâPV½ï CBB²”yùʘH$R üÅÅÅÔ±¶SSSgΜIÎ2ŒÙ³gØÊ1--­ôôtš+óúõkúøõˆÐ{×ï7dž<Í8ãÙc¹ï2J‡Â¨-œ[´àHUB"t‹"qðàíH¼ˆÐ ÍBWn뙳Óî[ÆýÍ7íºví¢¥©qêÔq7g/çÌ\ì…F¡"tn’¯÷+òÞÅc«ˆÐìL¡@ŠÐ">Y ºy"4Aƒ@íâÀQÀ®j¸‘¼xñBÕR*ˆæñx .,))!Sz7nÜYeþ]»vЬÜÏÏÏç7|„VÛ脃–"´'…œe5dzt4]„Ϋ;„® ¨•©U± ±RcU3Ðl·6Œtäà°RC‚ý÷îÖ_ºtQî´iÓfèAÛ¶éÛÚÚ¤¥Æc/4 …]Ûììì¬öØ%\.W=oÀÀ@ƒ¡j§7ý‘¦¡×®]+ýƒ è*{Î>Lß½j ß‹ÞÚßß_©£;"4"t]"4I§*¡,Ïôª“¡ú#_hvúËçîß½±ßîñÿŽûæ›oºví2oÞœ3gNøx» òóP(DhDèÚ°ÜÜ\yÙѱ7oÞ¨1èvII‰‹‹‹ª}×ôDBïܹ³rW*AÑÑÑÑ2鯯Æ4+OMM¥ïÓšÎÛ„¡¯¶h(ϱDâ€x¡ë¡«dZš­‰U­¡nNØÛœ¼4BT„~ëõ’tä ¹ÁA¾æf—/_Ò³g¶m¿1bX#í Æ^h"4"t·ÀÀÀÌÌLõʪ=^¡ŸŸŸªì­ @40³P(¤ÎîÞ½›úÝ_vv¶ŠV ¡óòòBhGœkøíãíM Âd2=|Hî.yVPPðRjÖÖÖ¿ürDèQ£–éaaaH¼M¡Ik°­Ø%C©OˆBVÚJý"´ŒrsÒž<±ß·o÷¿ÿŽý¦]».]:c5 ºâ/é >½{5C„&:Õ¾$Fjjø¨:N¢‚Á>rssõôôHŠ&Ú××7??fîÖ7oé®_·b%IÑׯ]Û´h©î¼…tjHKKûá‡3Ô®V-''g—Ô†ÞܺâcwhÞª"´JŒ­Ô—£šËN÷ñzÉ ñ9¡°€äkfvaÙ²Å=zt':¨·nÝl{ߺavPc/4ª×È[ïWäÄU¡¹ìÌ)B‹E|åˆÝÌ{¡+¤_©ñ‰õo}â»B.—+ãF«ÀÙÛÛ›þwyJ} 3Ö®]KP4Ѐܡ¡¡ÀÏ—/_VZsdd$ý®øj’ÞpøÙbA…{@‚ÍC‚¢awéΚWúÚOøÂ“&EÃõòãÇ߇æàLž¼‰·É÷Bsê¡C¬„,~¸:7 „N%¤BWî ~üøÁÞ½»ÆÓ vP{{¹a5 𾥤¤¨ýç;›Í~óF2Ò´§§'5^ôªì°-(((//‹Å¯_¿®ú9ÓìW (ZWW*'R ÌÌÌèÔ äO¿Ç¸ 4ì«U3f?Š^1Áúù‹€Ÿ‰îÓ×s§NWZ££ãöí'‰XРG\t›B¿¹úEhú~шÐJ;¨ßššž_ºtQ÷î¶mûåðáC뽃{¡k56"4"4"t `°Ú»»»HSZ?î €¸@Ñ --&è*·d¸zõꬬ,¨ÿÁƒ›7o¦o0ž>7^h‹K¦ÖFÆ$EóœÜH~.zå³nÁâe_MÂÞ©ö@ähˆÐªRt…œ@sµçÈ¡*‡×-B§ùx¹0rkðQ˜“úè‘Ýž=;ÇŽÓ®Ý×]ºtž;wöéÓ&Þ^nù|‚(ªq"tâ[ï—äïž*"t¡SI!B#B“éääTå¸!áááŠË<“ ÏzÅþ¾¾@¹ÀÞôºâãÑqqq•׸nÑ¢E¼téRdd¤Ò/ãH£ïÊÒ4º2E«ÄÏÄ«Î0ñh¡Së¡å¥TTûkA:7d/tÍ#´Lu@€ÙA}ÜøŽNØÀG'T0Šwsº¶M^¨gÂ/B±EEEݹsÚÈȈÎwE»ºº ÓGh2@4ïÖ­›¾¾~eŠËÊʺ}û6ÑkíååE³fúqöš Bƒíßµ;øª5¡õ-’3Œë ýñhСë¡Õ­jdé&‰ÐÍͽ¡ñ"e•[!CÎ èº"´±±ñ®÷¶}ûö!C†¬X±bÅrrrTª022²rÜc: fbb2~üxú+ƒ·nÝjggG¡‰ÑPpÏž=°uáááUR´¸ uòx<:9ûÝX,<,LÉrÒƒô‹¦ÆèP°ciÆ0AC„®„®¨ÞÈ)jd@„®¯^è©S'NðbåNÚ¦ñR@¡±{¡#7mÚ4aÂ--­”””êTõêÕ+™¿i"´““S»ví€äwɱ³gÏVWMMÍ .Ð_=àm¨ª¼¼œX+Åíáá¡” ƒFsÌ8Iª30zCægšÍår}||i¡ë¡k„À«L—ÇêJžN)D蚢hy¿¤nÞ°är„,.›’ÓÎNÃBˆé˜è°‡m›6BSOT¥}ìèÈMòóÆ###*sssµµµÓÒÒÔ®­¸¸`X „öôôL¿¯xÛ¶m„ ¹V (öC\\šE"W„NG½ÌP,nnnb±¸0p’¬š5‡äg±‹÷º‹o8L¥è :«ôÃÓ|1é1húK#B7d´VÜݰîóØ ]ËݶÝš~/t3 Ó­ ÿ9**ФÊÔÔÔI“&Ñ£JKOO§î „–‡OÄç„°&[¶l¡ N÷îÝÛ»w/MGàgƒòòr"@4u­äQtYYðÍ­¦ùEarròÚµkez°!¥Q4쨭ëÖ‡\³!øyíüE° 7®X’}róö'VY6??Ÿ¦¯Ž_@ÀWsçº( ¢‚†]ï­ÀO¾5Fç ô…F„F„¦c%%%6l G¬&©8vúôéêQ4òªU«^½zEŽ6B…U&“¹zõê*ý`Ɉ4)úþýûæææô#r@åD8Ž€€‹%ö@ÑUz°ÐÿNfNØ+W®¬ìÒ)ÚÇüÁÏD"AÑ'6mszüD^Aú]îËlÁdÎÝ»éº!týf±š†#‡¼øèÈ-c[·n%ùY†u}}}çΫ^¨g‡£££ˆK zBV ôéò>»£Æ…ŽŒŒTü¡Ÿ­­­©©)ákAÿsBÂáähº—oÃæÐÉéêêJç9¡P¸dÉ’Êé‘¢”yï¸guÇEþ(°í4ëï¿màH¯­[iFçFC„FC„Æ^h"4"tݘH$¢ÎÊPåË—/Õvçì\¶l™Y­b~ ¡þÅaeeUeN¨öâŋĴ]! ½k×.:9¹\.Íðk°Â4ãHÏŸ?¿ÊôFGѪðsAA¼¥ç--ûΙ3f×.ШíÛ¿uv„nçê:rëV"±ß‚‡+}RІF¡Y©„°ù…]³V¹cP“Íf«WÀçܹsÝÝÝ¡Z¥üLß¼¼¼H~È¿v횪•$$$¬Y³¦Æ=4hó=eÊ”@9öàÁƒY³f5Uvþc²Ë³’’=cã¯]r®¬ï¬¬–ÒÌ­A#ô{C„®s„N!„½Ð("4"tm#tii©£££P(T¯B`fÀÅ©S§Ö?ƒÕÈßúeeeóæÍ£™™f$gúÃ|CÓ¶rÌÂÂbøðá,«é=Áá݇Ïç+ÍæèæöÇæÍ-¸ÜüÌçÿ®¯ïã0/h¡ß"t½!´HŠÐ<&7OŠÐ9Ø B)DèœÄ·^/É×ÿj!4ëC7‚¡c¡›,Bƒ>yò¤¸¸˜~g#5èܺuë ´~ýzjb½l¹¹9u&L˜°sçNjJLLL•ãââhFù£Ù_-Ï ›Ë廆zþç ÜŠŠŠèSãíãÓîæM¡ÛÞ»çÒ„†tl¾#Eh"tC@h¾¡ܼT¯Œœ„æFDB!7(2ù¡k¨íó`Jž^…Áyg \#o½\È+ˆËNS¡Ó yR„æ‘•€¤ŽÝ„ŒÏç;9951õÔÿ*.'ŒJ×/^¼ 㨺Ÿy<^Såç éשô=‚Ö·`0Z”•}öôi‹’’<ÞrCCD ÆÐáR„NA„F„®÷^è ˆd ”4fQ§´²œ]ý]¼«\„jVJgÁy­ÔÂÃÃå-b04‡i’&¯{ÙÂÂÂÚÚšœ…`ëÖ­j 4ðóªU«š¤ÿF…ÔÉ™fÐlÂmÝÚ"'§çêÕWîÜ飫ûIZZ¿-[¡Ñ¡kJ_…&çˆã³Ä±™)*M``|näøñ£&jí08žÄ•É€jV‚3·—¡ˆÐˆÐÕ´äääÀÀÀæùôñòò’‰^R™Ÿ‰]Dç+E„&ø™Éd6™Ý•ŸŸ?aÂF-­]„6l8¯`ôO³o&O½já8- çíÚõíÌ™!aa m3>x°|æK3sDhDhDèÆÕ mû<8%W™Z@Õk¿„é k.¼úÑ\]Çy“ÖNÖš6ëéë™lÕÑO?ýXƒµÉÔL¨–ꯃMh˜‚óÎDhDèê tlll3|údggGFF*æg‚÷/^¬´6™¾a¶)ñs…Ô¹¥}{³-* ²R…ÿ7otwï–ùhô†½ýƒçÏkdÝN<µLk©…Ú øU­Þ§ölÙúàØ© ÷KW7¯^£vàDh´:Ch6+…öB%f C“ø¤Ìo9 ûï,Ã)z.ó×½|§ùg§?þð)ó.5³Úþ¬‘zT[KMÔYý Mpž BמBCü·mÓõéÝ«É#t…4´/wß&f@tÔ¯áªägÂäÅ|nV&ƒÐššúôË+>ÁŽ9¦·COž6ìÜ ˜f—k-7laHj~ûù4¿%-"<|Í‚Åi¶ŽÄhæ ¶£ëæ¥+<äXä999ÄײÇo M‚\ "4"týõBÇeäƱA^¡kôwŽž¬¹ìÖ,žIéØÏÕ\£===Û¶}J’se ¢dPuWWW¥çU="ô¡}û_Ÿ¿RžIE޲ݬ»úÿìt×¶†'½N éÉMn’›žÜä¥@Òn %@ ÐL³M'”PC3=Bc1îÝÆ½Ë²,˶Ü$˲lYîí¼- Be<êmÿë_Z£Ñô93óÍÑ>ûTž ¡k¡¡£:W Ï®Ÿ Lüè«ÿiÇoè{ÀÌþ³—¬¥§×7p þ0óHz€åÄÝŽdÃÒγ Îf('ˆÐˆÐÖUKKK@@°´ç<€üüüèjç²²²ñãÇ#=c‰¡»E芊 6íUˆÐ[6þÍ;|†¡ë£þ>›ýkoo_¼xÛÀ¿—”m}v¡¡=­š¦hƒŸEY’èô2Ú_ 2|× „þ¤ÿ×ÏDh¦×1p þ0óHz€åÄÝŽdÃÒγ Îf('ˆÐˆÐV—\.õ¤/4 *--0a‚AŠF„6¡á¥ŒMîq"´X,^é;¡.ñKNJb¹´óçÃÞ|Óû–[Ò)Jñä“˽½Wêg}A„F!BÛ¨:+ O.¦½`Ŧþ¾ýñóècCú|þ¿Ð$fzê3¤XNÜíHƒ_M²“ì‚Ê "4"´-TUUÔäMT-怢õ™ÇÃúöÛ—ST¨ÊUZðœL|ã /c󦧧—••±YË×ÿö>çmÌýfô³Bƒ¦Ç€Ð3F{±é€¾°°è‹/¦Þ}÷QŠêÒ¥oä¿òÊÔíÛ#B£l†ÐE´1 (%·<8Oûd`Ò}?ŸxÙ0B^<ÀkêlÍÄú¦ƒ{5Ã:ÝŽÔ™ÀÂ%˜ggØç4”DhDh‰nsç *))™8qbss3"4­¶¶6ºÅe@@€vFŽÏ>Og„cȲot¶CCCaË—/ÕSXXs|‘…½mÓfîÁSÆ¢8æÏœÅf!ß~;¢š VÔ?ñÄïtBl·Fè« ×ÞÞ‚d‹í¨ZèÄìÒK±ù?lÄÏo¡¿ü¹ß†í'´'Ö1p ýIh×ilX{viØ,A3™þ6°´ÃwÁi åÚvr×dÑK—.-((0†Ð«V­âp8@Ñ7nÔžkóæÍø°ÖIj7p`7¯À½ &­Ú¼°‡Á ¶¡%ɲISŒEq$²»À1ëòÌ3~3½ B£lƒÐÅñqAU¢¬…ŽÏ(ñäh¼xõޝÇôÓççñç|ïãOOghO¬cà@†_M˼¥Yh7Øʉ ¡ â‚5Wé-ÔBè"¡¡‰›&‹nmmõööæñxú ü åÊ ýÈ#K)J@»_?¦ºÙÊÊÊääd“–Ÿ’’R\\lƆÁÙüáÃ,Aèúúúßý`¡§Ã&ŠÃãºÚáµÐgC3¢’ùþ9gB³h¹ø½û¾ûá'`PºÒï~øñÐß¼4“4#óÝ΢]‡lꢬb7Ø[JHd2ÿLH"4"´MT[[ë~íëëKS´¡‘ŸÙDt¬_¿wÅŠ´#"˜®‹‹/²iE¨‘X,†W63¶ ÞòbccýüשÔTû?ÒŸ=BC ‡­Ý¿kÏ´ßFÏ;AÇ+/a¹DhDhDhÇ:%[p1*;<‘™\ qxb>8ì š§ô¥á«ödhÏ1”('©9DhDhfM›6Í’TÏpjZZÜí™=yòäüü|¡‘Ÿ­«ŒŒ “*ÎýýýMBnZ555Aª.ÂÃÃþZñ—Æ›×n†e²Y‚T*½té’UzNA„F„F„vl-tcc-pÑ™Ðô“Ah´QŸ É€rÒÔX‹ͬeË–œ( “æ‚Yv¨µeË–)S¦lß¾}‡–Lí=Ùië¢GŽ9oÞ<äg+ª¹¹ÙÔãjÆŸðnxñâEKÒ˜WUUZ+úÂ…Ûî¿5Eµêðóm·]y÷Ý‘PÞ¡Q6AhIm¬…F£M2"4"4³üüüÚÚÚ.\¸`R%XEE…@­Ý»w?ûì³_ýuNNŽf¤{tbGfذa—.]±…JJI6s&=fÒ+‡ÃÉÍÍ5ãÿàgKþ©¬¬ 2£êÛ d2\eqqI|0¡G³j~>ÿü´µk÷Zk-NÐjC„F„vT-4³/ùŸ÷?…܈6†ÐòºJ“Z^'B„v{„&ªîÒjjjL=** žÑ°€œ‰'º}÷ß(ó4rñâÇïêê‰D&]/ðfÇ>ñNŵ%iâJKKõ­µû………ÁÁÁšhýûϾôÒøG]4bÄ[7%@„F„6€ÐÕÅñ±AU•X }Õõòê©3|îyèÎû¿k̸5R´¡+U­¾‚òªC·²DècGv)ä’†zB7ÖiF„v3„¦kí% ûy£££.\HÔmî€:¢QõæÌ™w:{åÊ… Ø×¸Â”þþþ,ƒ–µUeöÖEFFZkßáþ ŸD½µµÕ>Ùlœ¡%ˆÐˆÐNZ ÍÉÍxõ­—Ÿ|¿çW§ŸÿÆÿßÿúê¡§Ÿ2!>ÊŠ[EQ”ívÙ¦ ·óZœ¡ÒƒØ"ô‘Ã;5ˆÐî„Ð …âÈ‘#š&–©S§VVV²YTLL ÍÏD+m…±ÎûPž¬Ôôô{¢úOžlRV:(cb±ØÔÕ¥§§›½µ<ÏZo'ÃË’vpÁNY»ÖkÕ*ÚS×­3õk¡¼g÷ö»ï»óµ‰ }q`ØU¿³è±{ì±pñ<@ 'çOJ%Dhû tkKÃÙÓûºEèÈðó€Ð§OìmmiD„v³Zè}*Dè <„††–––v[˧ágr}òä’’ hÎûPž¦£G÷ž:µïœ9àWÇŽ¥êê(BzÍŸO¿5p`‡Ã°€í””S×›ŸŸmöfs8k]Îtðs}}½%üü¾—%Á¡£}CYYŸñãM¢hDhD艀6ÖB_GGUeƒ‡|à©»?Ùö´ž5þßñçžü¿žo½ûZ'“™*õ!–RKJ}"ÕL©ó«±%0/Ö<0và.¸B··µäsÓ.vƒÐ0òì©}9Ù)0="´ûrhS´¡i~¦‡###™ëñÂÃÃuª²µ[#üëÓå!*.)ù`âÄ›óò4øwÍÿš3gÏéÓ ³755˜ºÒòòrórGÓÊÊʲV·õ|>?$$Ä’TxúülE;B_9DhDhgð¼y³~åžo/ü[ŸŸi}ñ…Ÿ|íÍÿ°©ÖÐÉ@¤ÅfËÚvÁUº¾NTeEQÒ•¨ð á¡:>&0=-*'+±­­µ¾NŒí–±ÐЦZ›Ÿ5õÌÚý\3K¡Q(¢j£ê½bÅÃ{÷jà­99L˜PÒ]lƒykR@›§´´´ÌÌL«ìx||<,Í’%tvv~`ˆŸ5ýñ„ ˆÐ(ÓºN…ÐbYu‘ ¡yž\ œ_¯»´ã7ôýÂO͘5…Mlþ4ˆ¦.Áêí »àÄÍS!´@c…\ÜÙÙÞÑÑÖÚÒÐÒ¬Ð7Œ‡_a˜R>} ¡¯-D…ÐYžŒÐåe‚KçÁŸ}Ú×E›Ò ô«ÏÏ´`¿8Œµ#B£ºÕ3gR …ÿ^ž4©Û4Ë©©©¦väÝÒÒÔmvçÄÄD3òæé«µµ500°Û8¨nWÓ3~~ù™ösçv[‰ªÒˆ#ÐY*„ B#BÛ“¢}jû¹ÿ<Ó{Ó ýà3÷FG…"B#B›kDh·Ehš¢Ÿ}öY†ÆS3YYYˆÐ(³õÎŒJê«©¹9- îß½;—ñ½¬ªªÊÔ` ö]dF„…-ž÷‡Î”qqqtgîJ*•ZüŒB„¶gFŽ¥Ký{ÀÃÆøù³}ÏÞÿà= 2DhDhDh äЗD"yûí·™ódffvû¯4"4Ê  ܺõލ¨Ï½½ç¬[÷ØÊ•”Hä½r¥±é™;ònjjÚ¼f³v?Ý11„] èöööe n½€wøìØŸ~ÉçréñQQQV‰Û ³V?†–#´FNÈíHµ··J%ÚR ÍÞüÎ÷ÞÑÿò úÕ ûí'+›'l„v†]@„F„vE~ž8q¢ÏÒ¥K™):77711‘a‚sçÎa:h”¾¦¬Zõ°·÷’­[»ºº”ñéé¯zy½4t¨±é™;òÎÎÎþþþï§RS5Õo›Ð%%%c–µ÷‰J·„&,7iß®]½Vé‰>...##ÊÇÍ-ZrˆÐˆÐΓúß|wÉãúÉ·H¡Pü6m”¥Þß÷4}¶ûûöÓÃßþô­A„ÑsÄj‰Æ?|üC~~>ó6œ:v|Ö¨±Á±4?kì¿ê¯ñ¿´¤C¢ŽÁ¶z')ïŒMI$†Z$úÀË e6B×TÅʼn=¾¼eëßÏö}HŸŸ¿>óüíwÞÆÜM¡yhl.‡P¥ì‚M ×H|\°Tëö¥ïÄ„P¯±¿EE\4ôk‘B…ÐMuÚã¡]¡5ü ÃsÕÕYð¨e¦h@…$”å‚’æsÂg£l£1Ÿ5œ%B3¯hö”i'–­Ñg+Ï\÷ã/¹ÙÙÝnpkk«þÿ,ÕÕÕ.\hhh°úñÉÊÊ:þüÇŒ1@Ñ"Ñû^^ð&âZ-E„F„vÊZh±Hxëm·Ü|ëÍà[n»EéÛ•¾ù–›>ÿê«ó§±Ê^GÁ§ì‚cš“så‰'§wŸËIB„v{„~?~¼&ty®Ö? .d¦h¡P†O%”“ ôØc™W4sÌ8cüL;jÓNÿ³ç˜rò䥗_öú÷¿½×®Ý£‰ÓÎÏχk}gå,U__H‡gÃEú®—×um"?#B£°º[ä€ëåÕ`y\W[†¯xp<ÜÌ]Z’ûQŸ{ö|€vß¾• 9ˆÐîÐóæÍÓnú7÷ú ÊE‹UWW3Ì^QQlur@!BÛ¡Ï™[Í€Ðó¼&2¤òÈÉáôé3þî»OQßqGä;ïxEF&Àö[+‰´¶222ÂÃÃ[[[5càR½páOsæÐ»h‘IüŒÂZh4Ú]%âO<±Oï÷?íûÑáÃá†gLó–ˆùˆÐ’‘C¡Ù¨ªª N‡µ’ ¡m‡Ð)ÉÉG—øãçÎÈäß½}Ö`ÏÜðØc«(ª‰ægµ;ï¹gû A3­{@•/]ºTRRbõCµÐúÎÍIŠ 1øScƒ¬©±éÍŒÐÛ·o5ò×Ñ£†'''5*¤I‰ ð¼kÇ&DhDhfI¥Rxâ[Ò1Ê“ÅãñŒ0|æpcžôû$« 4s,sGß¾s¯‡çk~ùå¹V<iii‘‘‘mmm¶8ԈЈÐRI!m¬…ÖxëÖM£F¸ŠIâRYXóÓæÍaˆ š ¡K…940Ö׉`Œ¢¾*00€YQÆE„F„fVmmíÅ‹môÜG¡¬…ЋfÏQ\Ž1ˆÐŒ›ÄÅa„†‹^E-ïÍÐEú*È!BÛ¡« ic-´A„>yòè•„hMôÔ £Ï<ˆôˆf@h¸‚"ÂC""ÂõÕš‘ iX˜r$3"4"t·ª¯¯¿pá›^áP(G!trR’ÁXŽÎÈäYÞ> 3Ú¡»ººRRR¢¢¢lýWŽ!´ä¡¡q±—Å•ùX „3ª×šE¿75Ö–— æM÷æí™³mÚÏÿü³ ×H|\æõ_ÛŠú*°ÞHI½\¬5FŒ­BèZíɡݡ'MšdÉC¼¡¡ÁÏÏ[¢œ¡Aã~ü9týo›1ïÜé3Aèšš[d“vn„.D„v}fCèÿüg–Ù•ÏIII111vkGàx„ÎS!t"4"´3ÖBr²`ÂÏÒêJú§¿þZ›™‘l‹®ýØL騤Р= "B#B#B3¨µµ5 Àìl–„‚ØZEEdìXbƒ¾/PVPF™0Áy7ïÂ…ˆ·Þš|ÓMmx¾ááK/ÍØºõ¤ ”J¥p¡UVVÚs/¡¡±š9úRÀù°Ðk€\*ä76È’’âºEP jêèŒd9¥C(Ú`÷ÜÈÏVG芲ŸÇÙÔ¿˜¡—/WV­³¹¡.‰„ôïO×è’²2ØàêêêüãË/§ñx|#{$y×Ëë¥9sh¿8kÖäU«´ ‹Åfo@߯û~9êËAÓéû›ñßü÷Ãÿ"B£¡u(ÚØ§>BƒOŸ>žžž¤½Ý–Mý¥';Õ¦í ½c 8Ax¶E-tMu©B.ñ„fYªÝ ¡A2™ìܹsÚÝêK(2 4Ì qž=ÊÏ¿ö/<ðÊ #>zõt¼ÿ¾;ì°ñëãÆQuu׺á&ä®  ï+:;;ãU²°½mMMÍ€QV­Ôé€fuÙê£0G† B#Bc-4s-4x颹ÇîÕ|]³zåæÉC«Oþéõów qÑV¬…v,?c-´í·b¸ZÌ׌áåËæ¯Z¹9<³Zóçõ¥K—Š‹‹MpàÀS§NDhàç‰'Âß©öè•W®Q4ðPÊþjk#óæ‘o¼v.Ö®uù‰D¯yyQr¹6?Óîòíøñ+½³Á55pÔ@mŠ^S¾¦ß¨~ÝFV#B#Bc,4BK«+7­[±Æ{ûôŸìÛ¥é—ðÀ–µÓ~ÀËËô¨@äg–]ÄÏ kYÿ\}9"ÂZë‚+kÀ¨~Å~ìù…µÐÌ]-©Ø²qí¡¹#VŽtñâYí®½çÏŸkF,4CF6S:6„š%BÃäããCŸ»Ý;7Á˜í[7Ð_§L™äŒËR╘åË–€gLŸò¿ÿ}~êÉ'Ý¡A»±þ÷íÛwâÄ Àæ§Ÿ~zÈ!‰Ä9ù”•e /@€X{èèQrÏ=ºÇÿí·Ýa×úÎkŒŸ•ÎÎ>`ÅÕÑýgÞŸýG÷¯¨¨ÐŸ °°p•J .üF¥çŸÚ³ºEZŧµÐÆ9¶oÛ|Áÿ´Î4ùÜl<2ˆÐ,9õÒaÆõìùÀ£>òç’y½z=Ã#F oPÔ` ÖBÓª­­=þ<|üõŸþùì³Ïúõë·eË–W^yÅÎÙºLÒ¿ÿm€¢ë€îP¶“BAF6œZyùrDhs”œœÜ㞇õ_0ÎS }ä¡¡-:.6"+3‰ùÙ”æ„y]µ×ØÑ}z¿O{ü¸±ò:©V4r¸?B755%$$0LÐÖÖX¤÷§»\.÷òòÚ°aÿþõ¯ÀAðõõmmmuÎÝœ;—ë&cÔ(bz–2T÷JK#/¼`ô°çç#B›¦ššÿ¯~üjvÌì£2·ùE„FBèZ5BÆÅŠ+¸ž\ ýê+/Ïœ9‘$=¡¹ñ±—5WŽáU´FZ5kæ´Q#ýû YÆ\?M¡V-4¿¦Z¨WÁ€ ¡3¡Ý£zéÒ¥áááÌÓ$ª¤ùZ__?nܸªª*ž5kÖÅ‹a€ÇãMž<¹Í¾AÆ „°É´›’b”åÀ@zÀ{(k©«‹lØ L$¨<¼wêÝþê«n²§O›FuvCècbÎ\ºdÊ|E¸JƒF ú“óçFÙÆü,)Ú :S…Ð|DhG!tuŸ¶' 4ËZ褤¸eK³4’$"´1˪‹++Ë= Ÿ²ê"½ ®Ch¡Ý,ƒ E!?{yyiòÜÄÆÆÒÃùùù¾¾¾ö¤èFB~"¤œÔ=ó Eï­_‰£­ ‰„|óúÀ¾J¨ŽöâÅn²³‰ééÏΜIutèóóM<Þ—ÞÞþ/³ÇÄÄDFFVWW3˜ægÚË –³¡hçAh È!B;B§¥D” s=rsÒ£"C þÔØ ÓnTˆödÃ5’–É€ÐJŠ––46H¥ ýê¹Ͳ©¬ÛÄB³¡hx^Ÿ?~ÿþý"‘H3R¡A\.7̾iãÆòlFw“͘Á„дýT5ë(3BzõRÌåE¨BÍ3pœ³³Ýg—“32ž>]‡¢oÊÏÿÊÇÇ~îèèHNN†‹®±±qÈØ!Kr—èä…ŠþnôwÌ­9òM*„®«K%…Ââl7¹JTP­ujÐh´ÆpuÀ5RZ’cÁB®!´öx@hŽÔB{Tß,)º½½=((H •ÌB¡í¯3„<ªòï„4Ÿ,!¡{„¢LU[™=[ö¹'¡ªRå× „͸™R23Ÿ›4éÁ;®zëÖo&O6›Ÿ»ººrrr«Ô¯sßþö«ñ_ýòç/úî7¹_Ÿ/ú83BsT]íd _Ë„9…©%E™h4Zßpu”—æZÆáˆÐî†ÐR©”Ïç ŒhìØ±lê“““5ŽÐõ„<­¦èOÉ32Yg'yüqV øûï˜8Ú’wßUÀ•©æçD‡wÞ<7<µµµÚ×QGG‡¹G².¨’’í‘ …Bf\õõõˆÐ(6Ý ¨ÎÍI(,Ȭ­)Å:F4ÚÖ–Õ‹Ùœì¸ô¡Ý¡ÓÒÒ|}}×®]»Ã¾úê«­[·²Y<å[[[ŽÐ ßÔ ~†}„ jöõe…д QÝêðauÚç[5ŸPåj~/2p`SRð˜Pee%\P\.׺‹E„F„ÖùZYi]­(59œÏ˨ ¤BmJªŠü¬ô”ˆºÚJ—VDh—äèêêJLLŒ‰‰Ñ©+;~üø¶mÛØ/G.—ûûûÃ\GèãZM{$!2½É"#M@h:qô¡Cø6*…‚üö›úp=C¨KZðLû¿º‡ô™gð°éª¦¦&888==½³³Óê G„F„®󯺊_/×É*óó’ÓS#S’ÂSS"ÓS£3Ò¢3Óc³2b³3ãs²89W8¹‰y¹‰0Y>'…—ŸRŸÊç¥Ñ.,H/,H+ä§ øéE…é•‹T_|ø5µ— Ÿ|^jÌÈM.ÈOæq“òóh'r9à+y¹à0''!7VŸ›—“›“››“•™•™[•ž› @’¦4lyxJrxjRXJRXrRhRbhò•¤+!‰W‚‚¯Ä©|9!îªãcãb.)‹¶£U\yäáІÓ§NPb¼òdÁ)Kº¬<}pCSTç45'N·ò¤Ã©O‹ÌHÌL‹R eÁˆB’I;6'+NY„T‰“åJUÀ®@aƒ".P¡@ªŠeJ/…¯.®…ü4eyVip± CU¶3ü p¡²`§ñ Ò xiÜTžÊܼd.\&œ$¸^r³¡_ÉÎŒËJÍHk* ®¬´h7¥VV^/é6"´ÄBÃ;  ¼üj6‹“'O²¬¾þþܾfÍšèèhÿNÈSzý!q×Oo ?lEƒÿ*÷P¥¤huX3ˆP<=~NW5*¼þ`ΘGîšÂÃÃáבVáD­¹ö6Dh‡!tuUa¬¼AQÝØ S¹Feiƒ\M[Q/Q»Ji¹ÒÀÞ×[¤t–Õ?)§WΨ\Bøêb•«€©×(kj¤]«ôµGª¼¹YÞÒ\Õ- pkKƒÒ­àÆ6¥›ÚÚh7·ƒÛ[Tní ÝÑF»³£½³ÜîêêT» ó.9 ÎN©«§€>#ªSÓçH}¾”ç®]iÕÙTÙ6¥›”§»µ±•6]T¥¢¥YA(0Êb£*?P®–(¥µKx ]¯–Fí®]¶U%Y®õ©.Û"ò¯.äUš‹Esùh—sø —\tˆÐnÙœŠurrrTTÔñãÇ·lÙâÒ—éÏz ~œ?xˆhM6~¼É VÄð­bCÖ­#·Þª:8=õ·<Ó^nàH2vããAjmm…K¯¹¹Ù¦+B„F„¾†ÐhtE)ÇØÚEíp„ÎÎÎ>wîœUµÿþ‰'–••¹ô“â!„¦ÝŸ¡z²à`s ĸv-V`±˜|õ•ú°¼A¨x#ü þ@÷>þ8±A¨‚S¨´´tÈè!cg5è_}~Ý{d¯ú¯e¶ºÐÐP–Ý º B‹¡ˆÐj#½¸˜Ëé^¡%¥úxp¡¡ÍæçiÓ¦™Ýê_[MMM»wïVý/Ÿi»?”m­jBž0NÑ/r^5Y[¹ÿ~3) ô¨îRƤ•öy"¡„Æù9‹P7é=__w>8ÓNŸ:K'u3xuÙê£ЩÔsrr‚ƒƒ%‰Ý¶Ê™ú*È!B#B£Ù[*Qö mpí@~ÖQmmm@@€P(tчÅÆšötUo†£F™ÐàGQ’¤§ ^=fÍ"7бÍê¨qx¦½ÚÀ¡‹Œtó£¤OÑ~.,, ,--µó&!B#Bk!tA}¨¶¦œËIJK KJINR5Ú«je¤EeeD+[ieÅæfÅåå$äe'(då&äs®ð¸ªÖXÜ~~raAŠ@Ù+EpµVš  M9F5ÌW6BTµÛ‚¹T •æ$rU-¼”M½râaùœœxes¬8Xª9¡²iXvFÌÕæ„à´(zÔm ¯6'Tnðµæ„‰¡ªæ„!LÍ c±9¡ó5'ŒÓjN˜@7' Q6'LÔ4' KSµ(¼Úœ05BÕœ0RÙœPU6TÍ £³3èæ„±ª"wµ9a¶²tA1S•Þ+PzéE±@Y†•­\Á|žª¸ªÊ-`UsÂ4¥ùéÅ…ªf³`^*Ÿ§*ÆÜ¤|p®² s gÃZ”…V¹öÌXUYU6xLMV¶xMM MMŠÈç$+›ÖUj_ƒ`Dh[ tppp·‹²?k”––Þæ‚i‘÷v‡Ðà>„lŽ´¡Á@’À“–uÝìJ*( ï¼£Þý¾ªæÊîü‘W[['¢è™!3µù9++ n_ùùùÙ§AèDh‡#tmMi¬˜™ÇM–V7ÔW7*¤´5ͬô*”§®5¶ºÞ•*‹ÔŸ"UK+ÚÊ%Ð „ÑË¿º:UK«&¥¯µ(ln¼Ö¢°¥IÓ¢PÙj¬õZ‹Bº9¡¦Eas»V‹B¦æ„ØœÐq2Öœ°C«9a;ÝœnK¨iNxµ-aÛõm [µÚ¶\mK¨jNبՖŠ–¦-áu%\SȯïëËv¥¼¶Róy­`_³øºB®n<Û Õx–^]DÈÏOMK ¯•UÔÖ¡mÐÝ&X¶?Óª««ƒ)..v­+U¬j?Ø-E?ÕEnŸl Y„©ª¶t{K¨Ë,à™ö_Ž•Gu›î;Ç÷Ã~:¼q"4"´æÈÓ|×Ö”×J…9hOw_ª¶v¤™g”IK䵢ĸ ¦F"´cPÖÛÛ;::ÚX?¿åååAAAÉÉÉV¬¦®¯¯‡­*ì®—>àŸ'¨“ÝÁ¡ïoV‘^&¡>¶”¢ÁÀ™@›n¦ädòÜsê}J(>k~¡{ˆxÀƒ:LW(ƒÇ8e`Tl”³3¿«*êˆÐ(³º®V¤zô#D¡=·Š¸Z\Èåd¤$_‰ŠŠ¸r%Ž“›.® Ç[wF¾¢^’Œí ÀÆÓ¦MËÎÎf f‹/]ºÓXñ¿ã¬¬¬–¦Mø<ϱϋ2BcнºÈ "ì•j¨S ´ Ìéêì$kÖ[ècr7¡¶˜Ïà|Bݪ{|Fö$~ö¼¼`ùFÙÆ!s‡˜AÑŸ½õÙü)ó þ$‰Þ|òÍs'LÈf‰Fk\#)*/-ˆŽŽÚ½{÷ºuë–.]ºfÍš;w†……•skŒG7™5£ë!4¥’þ°u™‘@ëv}_ÓõàÇ#ÔVêþT%vIª;@¿‘P>ŒiŸü­î1¹çÒâÐüüø´ùYCÑ‘1‘&!ôjɘ{Æ,œ¶P‡Ÿÿ þCÁZh”ÕÚžäi£å#B£»umMYJJ²’·o«¬ó7lX[*T‰ËO<1oÞ<`0©¤„yÆÜœŒjI%mXÂáCÏèòmS~¶±ÖŠ…ÖP4Ð4?w›(š}eo{{{|||XX˜B¡°Êm9$$dêÔ©sŒhèС£FràSã 3b94NPõ[mqâh RW©zåñÈÛo«7þsBå˜ËÏàŽÆ™3î*2™lãö ŽKŒ3¡iŠž>nº†Ÿ¡Qf!4¶m0è‚y3¦46‹©õÉ¡å’V‡üì–IKJK‹×¬YsÁÿ|AAÞÿ½ûNß¾Ÿp8Y½{.äs“’Ö¯__P_S]dlÆŠò’}{wõèqç¶mÿÐK(ðŒÍˆÍ íÓœ¦è7îÝ»×ÇǧÕ6v466FFFFGG·Ø’튊Š&L˜ÐâP|Ü`^,‡ÆBBy[!q4p©£û׾}ä.ºâýVB-e—ö™ÁuBPðdL6BÿNýþî³ïì5ðT»0B_¹ö¶f<ËΆÐ«[»¶ÖÈng1»Ú¼-Ä€7â(-,,X±bEâ•øU«VÜ}÷]ÙÙé¥BAFzòwÜñÏ?çss7mÚ”%«.16#LÞ²eÓM7Ýôꫯð ò૱Ý¡¼ZCÑýúõûöÛo[mÜé]]]]HHHbb¢vî;kÕN ‡ó³²Z•B?ÒÉHƒÇT! –Q4Ð)0ªsJ.'¿ü¢ÞÔçb<ƒ‹ ÕC÷üðRŒ‰ýÉp ?|o \›çOœÿᱡQæ!´DÌ£­…Ð×NX…ˆ<}k78lläõ@Þýìgѯ¿aÇk–iÆÆ[¬±ƒƒv9ˤÅ"Q9°npðåµBC UgK˜Hþõ/õþ¬Ša®´Øû ìû±cH1lÕÜÜ 7“o_ûPy65ûû÷¿×¼Ûž?~~ÈcC\¡3TÍÓ€"´Û#4ËE1ÏbÇê#4ûýb¹/hµ´Š_W'…›ÒŽ; ùÜœìô>xÿŽ;îøè£>œÜLsêä‰ððpiµ˜aÆœœôwþû6=ËŽí[€¢wîØjlFWAhæjgWAhÄbqhhhtt´ÜàuäÈc? …ÂuëÖM˜0Áò$Ò………ÎÃÏ 5ìúÞÆî°°‚P‹ $:6ÕÀ«@­Î ÎNâç§Nû|¡¶[žiÑÝëÛn#õõH1Ý«ªª .y¸ Ëd²á}‡ëð³6E#B£,GèꪉÖG¤öWƒã Ó5Tú# NÉ0’ý,lÌf³Í‰vЖ—•ö„åss23RV®\–•ÆËÏ‹‹Ù³gOIII]m%ÃŒ/øûøL¤goßöÏúõkÏXP¯BèÆF™öxgFh}`v9„¦ÕÔÔ” §Æ’åÌ;×xUd"üš——wéÒ¥²²2 îÒí3gÎt~åZ%–Cã`BýË ‰£]›8º²’|þ¹z“þK¨Dëñ³Pä×ïòÀˆ0̯3\.®¾””Íå3ðÿê󳆢߽ï]WDh "´ZÄ£m BK´ât&ÐÉ0¥xëìv9¦"t·KfØÿ²‘?ÝÀRIaSc}QQц €{££"R’¯ÄÆD=zdÇŽ0¾^^£suX6£K"´~\‡+"´æ! ˆ”žžn^t‡1„¦ù™®†µÀòá WWW»Í³ãv}«”"òUuÁÇ:D䡇ÔiŸ§ªÔzü >b`g@„1ú‚Wœ@ ÐùiÛ_ÛÚ&œàæš1Ò‰Z rˆÐΆÐfÁír–I‹[Ze2YllìÞ½{€88c’¥UgtÕZhýOEhí¿z⢢êêê,Ghm~Ö®LNHH ±VëBÇj™µb9´½MÕçµe  4kOµ´©SÕiŸ{ê”Uá™ö/jÝkkat%‹á‹ˆˆ€¯V‡m¡Åš‘–X‰ÐÖXmOK«Íuím­ÍÍÍMMM-ÍÍm­- õÒj1ßÚ3ÔËUÝ Ó¯DhŽóÆBÛ!~ÃþM‹np\\\l B¿ñƾ¾¾³:Ïœ9ó—_~‘H$®þìȰn,‡Æ‰„zÛ ‰£i퓹„Ë%o½¥^õÿ•k~.%Ôºûøõ×îL&À‹_Ìpþ1ýý¿“RRRzMrC„æ¨ZdC„ÖüÕnl<óO'èváˆÐh´‹ƒt¡\VQ_'ª“•w ÏæÎèzí®µÐú!”AAAiiiúãèèh„åååeee&Lht댽]„¼kÝXmbœ¬ ‡° ¤ß|“غÇ={”™™•«»P+,NûlÌ' ìÝ®]îŒÐB¡ð¿÷þwÖøYùyHß!?û1ý®²¸¸¸ÀÀÀ¢¢"ûo'"´"´=™vœ]¡Ý)šY‰$,,,22²Vë/s8V~~~šÏç{{{Ó —€¢'MšäÞ½˜BßÓ`.7>b)EßîÞm“}¯«#?ý¤^Ñ¿ nx¦=Rw¿nº‰¸þÿÝ ô†Œë1N‡¢Ÿ‡~:túÓ½3TÂ?¦» ½{»¹ÒFèÔ‚oÿóí¾û¾â{Dh‡ ´> w ±,ç²îŽFh™ ¡+•EŠò¹œ+'ŽíÞµs#x÷.üÄOü¼îóı=ùy‰šËÇ,óêåU‰ñA 5Úã]¡í ü|úôi???©TêëëËåråryRR°t|||MM£;é=–ŸA„¼jëXmï4ÐIŸ©ö÷7a››ÉäÉê´Ï´gíÏ´/Øþ <¡i~–adX`˜†¢MBh { u„η'B3p2›ŸŒUY3×Z³©wP,´„æå%9¼#,øtÒ•`4­o¸:ŽÞQÀMB„¶)BOŸ>ýìÙ³p³„®««koo§)š¾ƒ544¤¤¤KÇÄÄTUU”Ý5Ë>±§¨‚(,N TÌ&D=/¼ñ†zÆoű#?ƒ½ l¹ÍxÐéZ›Ÿi…_§)š=B½ûÚë)IɈÐN‚Ð,Ë€Ðì·ÍQ}éâq–×V Ñhc†k$0à„G!´<ÛˆŸ33’gΜ~ýµW5¹/h„Vݵ®£hZMMMÀÒÑÑÑ"‘ÉÖnв[,‡vâ誀 Ë@úõו„Ì ]»Èwª&¾P~ö…gÚOënó»ïzD¡„þþþïuøY›¢Ù#ô¤Qcd#Æüô‹No¡–î0tOI½{÷F„fhúg9Bk/“êN΃ÐA')ÈHh4ƒá+…™“áš;fxDØ9·AhGeäÐ ´†¢ù|¾Á(嬬,`éÈÈȲ²2D\›?Dy‰Eß,±*^ž5'lª öQR[K†UOö¡"ÁÏ!6xõjOAè}¡TXjð׈ ˆWŸy•Ír"ÂÃ÷/\F¢R‚7l=rà ÖB[…¢ÍH‘áiPY–‹Œ„F3®‘¸èK üœ“ûÄÑW4';ÚZMStCCÃÍ­­­ÃáK‡‡‡ÃÙŠuP(Mc‡ÐwÕ[29ªà ‹ÛB´§ÆÇ“§5Õ¿# Uì~O5°©†ÞÝPpµ2wK¤P(º]HGGÇè¡?wE&Bƒ'þü«Õ»:B„¶5B³ä0,F»"B—2úôù gÏh÷ý¤°(ÚZmÊ]®=???$$$,,L tZ’× eH!ìúÁ6Û ¦Ÿ*ÐÂ2ŠfŽ‹è"Ë–©Ó>ßO¨=‚gÚÏè²Å^Û6o¾²}?ÍÏà‚Ãg—Î_€íp„ö„愈Ðh´%-*Ï›â;¾Oï÷?íûÑáÃᆧO$®à"BÛ¡µk¥ø|~hh(à4 ÀWÄ «¨•ØQôMU¶¡ÍU¸…ʼn£_]ýõB¥:”Ÿ£ láÒ¥XÖØª¶¶vÊð‘~¦½Àk’P(D„¶ E›4Þ i[=©óÆ B£Ñ®‚ÐÛ·m5ò×Ñ£†''')ê%IWà+xçöˆÐŽBh:;;‹ŠŠÂ€¥¹\n{{;"‡…òf‡Ð=ä6cÎbUÐ…ÅAÔÍ„šE¨2‡òs¥jô¶ÃÁ‚ÆVÌœU~:P¡åQ>cÆ"B[ŽÐ,²[×*ˆÐh´Û ´°(ƒæà À:Y9Œ©¯«  G–•d»AR;—FhíK¡Pœ‘‘!“É=ÌÓ%ÇÆrh¼W€a6??N(GÃ3í—u·í?ÿÁRÆJgNŸ^ì5I‡ŸiïþcILT"´£š˜ØÁ·Iq#ˆÐnï"AîùsÇÜ}èàžÃ‡öâ§SÚsþ܉â"Ž©]-.ˆ‰ˆ«—‹5#òª°0åH‰˜çÒmëN½!4 ®í:1ioo2dÈ'Ÿ|âðÄ]TM„<çØXÓTafðsBå;?Çؼ <¢ I¥Róf„+799Þ…cccGÿüKsH¼A„îŒLóãÏÖjáimk±„pç"´=]RœwæÌÑø¸ˆÜœ4´KNÖÙ3Ç„ÅS9êë*Áz#E0—Ût­bÖFh[ÞÛ§NzæÌ™7Nœ8Q.—#›¡qåиŒP¿«B2XÂó„ZëðLû™‘áþE¨¹¹ùË>;}šý,uuu‰‰‰!!! ô¿HãFŒœã3Ù˜Çø­¢¢ÚišM55"´:<,0+3¹ZRÖ ¨A»„ádÁ)‹¿l^R;·ìZÅž•Òv@hàç)S¦ðx¼ììì#GŽH$’ & E›!vݳÕ^ êO¨'XðóíLü ~Cw#Ÿ{Î#Šß’? ŸøËð––æ)kjjâãッƒ“’’xµ"BÛ¡vƒ¡íéè¨*±Ð9Ûm¡Nœ8DhæZhQ´­ZÃÏ0L#4 Ðmõ¸k·—‚gØQôb{±h>¡‡ç5ÆqiŸ9Iµa×oêï¿»ù)//_4Þ›D¥p^·ÒÏà4UUU111@Î)))lRC#B»:H;ù¦"BÛÓi© õòj„R×2œ²ô´+ˆÐÝV;Û‚¢mŠÐÀÏ“'OÎÏϧ¿jš¦èqãÆ!E›ª‘ìúÎ:ûBé:U¨†??@¨ýNÏ´— ý¤$÷/<ÓÆO”FÑËxM‰DšŸ*++£¢¢è6¿MMMγ͈Ð.Sš~zûj¹µ¨=`ݵ89BÛ9éÚ*9\¡Ã¸\®æ«6BÓ}üøq|F˜¤Sìú»siÌõ9.ú*Ý)ùüŽ.??ù$qû®5¯ÄÇïþc‰¦Ñ_m@Ä,ßÒÒÒˆˆ g¸6» í@„F„F„¶Ã’¡Ñ¶CèraNzJä™SvîØô÷ßk6m\³yÓ:ÿÙ¼~ÏîÎ=y±´8 óB;IsBƒ2CrBžr¶XíÄÑc u ¡æªÜYù9ƒP7ê"ô´in^lºººÆý<¬SÝ7í¿§Ï9yòd[[›3o9"´+F‰86C·)ר¯ÚÁ- #õÃÈõ«¦ ÎÂrNˆÐ:ÿŒ3Õ¶}"QÝÞëׯ75ÖjÆÀðÚ5~6¬1¡¯Ä]>°oÛÆ¿Ö¬ò[¾bù²+´½¼zÕ `é=»7ÇD]D„fƒÐ;vìHHH(--5©x(!BÛ_Ü3–Cã,g…gÚ+ DqÄÆºy™9°gOÌ?»u²ÏuD$6¼Ë¹ëß¡]¡­km6B£SƒèÛ-ñZ> ›Ù¡ b°þ€±f\œF6Ãbq)}0ýV.ÓŒ\úç"z¤¤ªÜT„–VñîߺsÇæãÇ …B¡°¤¤¤¨¨¨°°Çãåçççå奥¥íß¿oǶ¿Þ!­*D„fS ÝØØG/"""$$$<<œËå²iI´O%Dh;ë¨ÓÆr¸„{ëòó£+å0vRÕ××ë÷ÄM;âï‡÷@„F„¶B[ÈÒæ5'd¨Ý5}™+±-äjgä`ß,‹«¡ÍµÜÇLJ.'‡íƒ1ûöN™2ÅŒZh@è}{þY·n°syyyii)Ptqq±@ (((ŠöãÀ-/#cÝ:¿ƒ¶!B›ÈÑÞÞ6!!!88ˆ:55µªªÊXU ôþýû¡í©BždнºËáäÎ&ÔMºíííæfé „'/Dh𔣜!ó"´C(×ùqÚFµÐl(—}T†#]7C’õC;0pÚZnin6lXÏž<úh/¿•Ëzõz†GŒÞÚÒdBïݽÙÏo…X,f@hø\å·âÀþ­ˆÐ–ÇBK¥ÒÌÌÌ•¢££áPëô`¸gÏžƒ²¹©9uê2°åÊ®"úŽZÄæë½Æ@GX˜›—¯á¿Íñö5æñÆ'&\qÚG„¶3Û?£Ù,m»XhöaÖŠýèváNÞœ} ÖB[Ñ r¯±£ûô~ŸöøqcêÍkNx¡W®H$eee@Ñt,ŸÏЦc9€¥Wù-wQ„ÖÔwžæ„---pœ###aw€¨³²²èÞÊ€¢>Œdk7íg‡Ð÷7#6_ïOtùùÁIG( ä@„vÒH«gäpT,´I\ír±ÐÆj¡¡­ÎQ[[3kæ´Q#ýûŒº:Œ1¡k$…4BWWWWVV–——EÓÑ………@wtE4 Ð]#¸t×*¶û¤¼Lp)à<ø³Oû²Dh…B‘ª¥äää   lÙ²åŸþ<³¬£F›ÎQ/U=zX,®li–›ÔNS -•JZ ¸t-´^ßÌ@hxm9i\cÆŒñõõEÒ°›¾ÇXSý—(ŽË—±(±Ò‘#çî½÷¬±n(_~yBs³5ÁÒùZ{ÆèTgcèÈ<—1èe`Ný%èOÌ0£±•ÚºCpc[kõÞ Ý2Ÿ3öNè  wø´$/´¡e2™ ¼¢‚Žˆ.))lЦc9€¨]¡íËa•¼ÐáááK—.Ḛ§va,‡©þRüî»\×bÒ'ŸL€[‘>?÷èá¿oßO äè¶•™]ÙÏÅ̺ì—ÀŒÐ–o•pú³Ï>]²hvLäEDh;85Ú%»VIK5 ¡kkkÅbqee%- ‹ŠŠ€¢éŠhøêÒíZ]« ?;D•„<†±ìÍ#Ô­ºì7b–#”ûØc«õºåƒÆ{`,43£šŠÐ sYqcÍrb»!´6KŸ={zÔÈ_žzê‰G{=2jäðËæ Kò-Ah4ƒS’c¡]¡SSâLB躺:„.--u'„væZhägª»ŠèÛeˆÐ„Új üàüy,DÝ n¶999!!!ÁÁÁÿûߨn¨Ð>†?¼>##Ç“ÚX¼q·˜1s±ykév™,wÄFÒr\‰Z·Öïý÷޹뮻þïÿÞö;üò¥u²rä^+:;ëŠL*B(u-×HEÙY‰&!´\.¯ªª‰DtD40sqq1ÝÇJAAŒq-„6–eQ?Œß©ùÙ±ÚÊ¡ïkB„&Ô·ºü|×]¤»È0¤ÎÎN¸…ÂmÎ Ÿ””·ÙNU÷32™ì…fhFÑСslº1.„Ð:aÏ– ´± º ¶¡»]©C$êÇB‹ÅÅ'íùqÈwÏ?ÿìƒöüqèà;6 øÙÀ–[TQX\”«K´{”F;­á4ÁÉ‚S&®,4 ¡ …D"‹ÅtDtYYYII šÏçà ßÕc¡¿º««kçÎÈ”Bc,UH¨;tú§ŸÜ¹l 4å›oVóÇÏ Öž¾©©©   ***44^9N}}½Á%¯X±íöÛécøÜs³¥R©'#´± ^“*o™šMl†IHÌLÈìƒC…ÐڱйÙI7¬îßïë{î¹ûÍ7_Ÿ5sJpÐy™´aØlW‰ …ÅœÊò´KN–D,0v6ÍChölNˆr”þÇŽ¢o«ñl„Þe ŠÃ½;ù™3gýÍ7çK ñ >@¿!!!€ÍqqqB¡°½½½Û%wtt¼ýöxŠêºùæ”… 7ÛzG\¡8Ö…ÚÍ2#‡T"¼pzú4ßW_}ù¾ûîýî»~›7­ãrR‰ÑžlcÝÐÐÀ€ÐUUUn‘Ãù9P×F–±žÐßé2äw†w.---¯¿îcŸo½5Ì×w`szzzuuµ ¿|9¢gÏo¼1‘ r{ B³ ɰz ‡Ù×ì—©ÏÒ΃ÐÚæåglÝò×àÁï¿ÿ¾—ÿóRaA¢Z¡~rƒ¼ÐX êVvýH§ós ¡îÒÅÈÁƒÝ¿l>|îÞ{ýõºã­·ÆvuuY¸ð¯¿ölˆöÂÕ›š‡Ðæ5'´¼š=·Û¢-É ][Sv 9 ­Ð Í «««Ý ¡<©ʱúc9˜½ß@M¬‡tGÿÉ'>Õ¢½ã={nOq¡]põ¤vìC2¬>±õ×Ijg!B£ÑˆÐÆš!©T*Åæ„ŽEh…B|k­c‡Ð÷zl,Ǻü|Ûm¤®Î#ÊFVVÎcmÐÚwÙ€3]k\½k–m‹ د×*¾ B£Ñ®‚ÐMMM ]SSãrIí˜åZ}êÔ©Í›7#ÜÚGyËÁ`!¡îÕEè~ý<¨x >Ÿ¢DôŽ?ýô‘Hd»uñøü/~û n¿‹ÐÆâ¢@š c3/Á¤F‹ì;=ìvvû`Dh4ÚÍÐÁ·L&s¹ZhN¶5B9r^@jkkéÔ¯ÈÏ.§Þì(úV©ç!ôQQ{÷zPÙJ¥/¼0öú¦›ò¦O_k£µ(ŠÑK—> ^¿^(zB[ýH:$÷²3 ¶BÓQÐttqq1ð3]ÍårÝ&ÚÖ¡Ñ€Ðaaa€Ðiiiáááa*%$$À‘‹Å­­­ÈÏN.?v}Oƒç!ô0]~¾åbãLÆN§e˶ÞzkÊË/{·´´X}áðÞ½vïÞgÌ „B ¿Ç¶nE„F„F„F£¡›››º®®3rXÈ/)999111ajEFFfffÂ7Øíò³ÃB^Ù!ôÃÆÏe„ê©‹Ð_|áÒþâ‹_8pÎêK޼råí n‹Œæ£íö]"È@„F„F£]¡é(h:Š£¨¨ø™ŽâÈËË“Ë守ÐÎ Ïâêêj8Ή‰‰¡ZJJJx:th¨qÙ4õ;оYâI}Ò@DzežX<àXgL@d䋦¤˜™£´¬ìë©S{îÝKuvjøÙíøùÜéõr"4"4íºÝÒÒBGAÓUÐ¸Ž®‚æp8 …“ÚÙ³9!¼³ìرÚú“EsBËk7ÚBß|3Y¹’X#êßUUPXøå´i÷Ÿ=Û# 66ÖŒ%Ìß²å‰E‹¨ÚZmxöZhaQ&] B„vK rÏŸ;~ðàîC÷>´?úóОóçNqLEèÖÖV„nhhp§¾]%/ôéÓ§1ÃQJeäçZÈbãçrB=BŒõpýù礢Âã ‰B¡˜èç÷øúõTSà®IÝÙÙ)“ÉàN›ššº|ýúç‡ »55ÕZÈB„v?—ç9s4>."7' 톓uöÌ1a1ÇT„¦£ é(ŽÂÂBàg:Š#77·±±Ú!IíΜ9ó÷ßãmßþê"ämCðÜ«‹ô{d:;£üLû¡‡È… S<ººþ:xð¥Y³n¨¬Ôà®1„†»«X,†{i||¼¦}qDDDZZš&uO{{ûÒmÛþ={6¥µ@ ä@!B»ŸÃó2“«%e Š´KNœ²ˆðË&!t[[B777#B3›“›±|Ùðß~˺y¡Ïž=û×_áßþZ Çϵ“[ª=µG•ñÝ 4ø†È”)Äi*œKQW®¼?mÚmW®èÔB‡„„À]4333***\-àj¸‹VTTèGPëpzÔ’%nÜHµ¶Z¡%É*•¾øâ ä@„F„¶££BªÄBç¬îC3œ28q&!t{{;ÝŽâàóùÀÏtGNNNKK "´»V9þ ›Ù¡ b°þ€ÎH E#B[b±¸”>˜~+—iF.ýs=RRUŽ͆¢±ŬóçIOº'k@èsÐ[-BhÚð>RV润¶¶vØ¢Eþü³ÝnÔˆÐ(û 4—š‡¾Ì•Ørµ“rÄæ‘k¤‘‡Í²ÜÇLJ.*‡íƒ1ûöN™2ÅÂXh: šŽâàr¹ÀÏtGFFFGG‡K×Bc,4Х𛉷·29›ü'ÔyŒâPy/¡î·”¢á­ÞMÜXyùùvkóûé OšðSvn6"4Ê%š}T†#]:C’õC;¡­å–æÆaÆõìùÀ£öò[¹¬W¯G`xĈá­-Mfä`@h7hNˆµÐ¨nÅá×^S#_?Bå#ü·ß~5jTtt4ŸÏ/..®¬¬”J¥ð`…W’ÎÎN¼™;ƒbbÈSO©¸îB&T1B2 çj€‚:àÍEæ¾½jhšöâìÅ&Ø´c“U®}„¦í}Ø{¸ïð~"4ʺíiùœ¹wBDhkQ4ÜyàÓ*y¡[ZZè(h:Š#77ø™ŽâHMMmhh@„6µº­­­©©I&“UUU•—— x7ÉÉÉã™\¡’f 444\%ˆ‰‰)aú•¢¢¢`!b±Ͱ|x Ôbù.J[dÉrÓM*œ»ŸPû‘Mô:UЋe ï/ðã M{FÐŒþ#û‡F„êOoÖ …îð®- áºæp8pNII‰‹‹£oõß_¡Áó“ç¿þÞë­­­ˆÐ(Dh‡;5ÅÊülŸ®UÒRMCh€=: š®‚Òƒ[7] 7p¹\Žmÿ@ „oÛ¶mõêÕ+V¬ˆÍÎΆ³¿ÂSr‡ZØ÷·© ÉG©A®7¡Ò‰Ír4¡^¶”¢á-fñb7Lm¡ÁIÿúeÝ/ý†ö£a8L%€«;11^™áÌãñŠŠŠ€¥««« þo¥_ ½ºlõO š»t.óÝÚŠfvJr¬Õk¡Ñv@èÔ”8“º±±‘¡áޱÐ í6:s†Ü¿:íó\B•# [àbBU…ÁXÒðFï5ž€Ð¾§}¿ù]JZŠÕ9j6Žß=~ôÔÑBGÚnÎκ"“ŠJ]Ë5RQvV¢IÝÐÐ@GAÓQÙÙÙÀÏtGrr²L&ÃŒˆÐn ÆF2q¢Ûž$ÔEd`+ù¡°”¢á½ÞnÜ¡¦.ü~Â÷ûìïêê²V,ô¬ÐYƒÇ 6‚B„v¬E…ÅE¹ ¹D»Gi´ÓNœ,8eâÊB“º¾¾žŽ‚¦« ³²²€Ÿé*褤$©TŠͲÚi•M^yE l1í³µA¨>Vhc8a‚òMÇÍÚ¯ØoèÜ¡‹W-njj²Ê¡—r—™>dËî-&µOD„F„F„¶§«D…ÂbNeyÚ% 'K";›ÆZ.—3 ´D"ñp„fß7="´sjËrûí*H»“PxmãrBͳBâhxÓÉÎvù"G#ô_Ò¿Æn;fê¡UãTúöï;}át3²ä!B#B#B£ÑæÙBÃÝž¡Åb1"4"´‹J*%ß}§Æ³W ƒ¬kc_$ÔS–R4¼ïÀ[ÅñFè·¾|ë‡ñ?Ä%ÄY}áæ6¿D„F„F„F£­‹Ð2™Œ¡+++¡¡]Q‘‘ä‰'ÔiŸ½U‚ˆkóõ‚:àÝÞ€\T@¹—._êr²×DhDhDh4ÚêÍМ°¼¼ÚàptTèÌ™ÓÀ£Gÿöÿ÷¸W¯G¡ãIA,P§}îI¨CH¶v÷_„êa)E?þ¸ò=ežà–>G%__ß÷UzüñÇ¡¡¡Ñh+"tMM CR»²²2—Î m°ßyO¨…†wÏ|L””?TcØGª–n´q,¡^³Bâèùó v„µÐ(Dh4Ú Z*•2 ´P(tƒ®UŒõ8ïÆ=wî\|Fœ8Aî»OE_·j>¦}v´K5Þ ‰£á¨¸ …F;BWWW3tð]RRâ6턵ÐkÖ¬A„¶Š‰——ºž!Ô%$X§ñaU8e oFð~„B„F!B£ÑN…Ð………tD4‡ÃÉÎÎЦ+¢‹‹‹Ý wBKb9lŠÐ¶Ý?ÿüÓs ™™ä¥—Ô¸5HÕ¢ ÁÕ©œI¨­ÐÆpìX7IF»BK$„صŠíºVñÌp +ª«‹üý7¹í6bõ ÔßÈ«Nœ8z*ÀÆ2Іw¥Œ ,øˆÐ(Dh4Úñ]UUœÌçóéˆè¼¼¼œœ h:–èÚ 9XV#Û¿ƒïl7èFÂq’HÈ€j¸zƒPñHªNï@U˜e oLðÞäÒ‰£¡¡¡ÝÞu²r©D(ª(D»„ádÁ)3¡‹ŠŠ•éˆh.—›››K'ˆÎÈÈ€‘nƒÐf'å°B3«¥¥¥¡¡A&“‰D¢²²2xÓ îÔÔÔøøøèèèÐ릀×÷~„…‘ÇS§}žH(!ªë$ŽþÁ Aýû+ß¡ÜFr¹|ÓŠMpWTTH$¸ê›ššÚ­š‹Ún+Èåµ](‘\.ƒS'Î$„‹Å  cÜ&©ÖBk × ÇÆÆÂ˜áŒäççÓWx塯mmmžú óæ‘oT¡ÔC„:Š\ê‚ÞD¨»,¥èGU¾I¹‡Z[[ÎÃ}˜ÃáÀ+pRRR\\Ü ¢¢¢"##5pg€Ïˆˆ€O†Éà.wl˜î¥¥¥•••p—P(ÍÍ͈Ð(Dh;»¦º´ŸÛÑÑoÁÍ(œ,8cpâàô™„Ðp߆/ ¨÷aº•ÌÌL—FhûT>;gR;w )*"ï½§†¨¾„ÊBuY'¨Âo,£hx“‚÷)O}›Tª³³nþr¹\*•9„pZ£GF„F„F„¶ƒ‹‹8ÕÕUíím ”K N™D"†Óǡ黮1„ær¹n€ÐN m;¹eCÅ£GÉ=÷¨Ó>/$T‚¨‹[H¨IVH oU"r ¡o™´43=Þm›ššJ]KpÊ:::àôÁIdÐ%%%EEEt£B———G÷±’••î…ÐTwr*„¶èºB+dôh52=K¨ËÈŸnäc„zØRІw+xÃB!B£¡}%!¢¥¥¥±±¡Ôµ§¬¹¹NŸ~D4B …Âââb:"º  €ËåÒ}¬dggK»\-4'{BÛ®Óû+-¼ð‚–†ŠØévÎ&Ô§Vhc8j”òm …²¡õ+ L…Ií¹´ÌX”µl‡µkº¡¡¡åR2¡ýVTTT0 4|®ò[ááy¡]¡ÝC]]dýzuÚç» õÒ¦ûº‚P‹u«¥ o[©©xé B£¬ƒÐVOÇb3"4ʽoÏ?ëÖ­x.---))¡#¢ù|~~~>ÝÇJNNNzzúºu«Øæº½Ú§ Ú<„.//Ç»1UU‘¯¿V£Ñ[ªÖgÈ™nï BýË ‰£áÍ G#B£l„Ы¦õG¬Çf^ŽÁÉô·ÁØ™·Í^Ø¡5+Ò|ÕþÉà4 #Qfh­Jr¹\3†W­ZµnÝ:óúàþ­;wl>vì`³P(¤#¢ y<ÝÇJZZÚýûvlÿûÈáصŠk5'tu…„^½TDt#¡¼1í³'™O¨­Ôï_ð†B„FY¡»%^ý‘¦ÎbŒ¢õ5iEÌ$o „–«¤\¸Zšaz@ç«Áiô‡Q¦ª¬¬Œ>ËË–-ÓŒ„["=²¢¢‚§Œ=B9´ýı½;¶o^¿nõêU+W¯Ö¶ø¯ kvíÜrôðÎãÇv!B#BÛGmmä÷ßÉ t–†‡ u©Ò#½UºcEÃ[Xp0^RˆÐ(;!´Aâ5ºšaØZ+².BkËl„FY¢úúzúDïß¿ÆìÚµ‹þ:eÊÍd¦"ôEÿ£Î=súÐÑ#{ŽÝ{âØ¾«>¾ïäñýý]ºxâÜ™ˆÐˆÐöŸOÞyG@Ÿ*aÒƒ}…Po[JÑð.odžœ8e B3ÄZ˜•áTm‹Xƒ}ÝŠ U,ënŒÞxÄ` ÕØØ8lذž=xôÑ^Ë—/ïÕë1bxSS“%t:<ô\DØù¨ÿ˜¨‹´c£hÃO®‹Ðú9î<§kWÔÁƒê´Ï·êOLûŒVðøª‚y,ix/ƒ·3W”T* º|yßÎ]vHŠmI-t·k,ÖÂùk¡mÔœP¡PÔ©+ªSK3Lè|eP· ºU}½Ükìè>½ß§=~ÜØúúzí è]¡]%#‡=èíí=f̘_~ùå'•&OžBÓ+ê¡™G¢Ì–\.—ÉjfÍœ6j䯳ŸQ[+ƒ1ˆÐæ%è°–33’gΜ~ýµW¡;;;kjj “““ÃÂÂBBBBUJHHÈÏϯªªjs²µSRÈóÏ«QçgB"4¢õœC¨/­ÐÆÞÔà}ÍùÕÑÑ1eŒWå™Ë$*EÛþkþ>¸g¯I‹‰Dô;rïÞ½¡¡-GhS3r˜”ÃB„vªŒ„Ö†grÖlT68eAEt}eeÅÑ£‡E¢J~F„ÖÏbçä½Z(¸H+++9N\\@2MËáááiiiEEEµµµ€ÓÎ|Kïê"kÖ[nQáÍ=„Ú†¬ˆfL½Ô ‰£á}-9ÙÙigÓšuGÎéð3íµÓg …B¬…FÙ¡Ñ"4ÐZ-Êùä œ Ÿú?Á)ÃZh†jgWAèÖÖV™–jjjà¡™••íïïöìYøZމ‰ÉÉÉ)//ojjr¹û¹HD¾ÔÔ+þ—P‰H‰h%Ôó–R4¼µÁ»›3¿_þ1ÞÛ ?ƒƒcW.^‚B„F„FYWˆÐŽâgë"´²_u-­X±ÂÇÇgÖ¬Y .\¹r%Œ9uê”KßÌ/_&?¬Nû<…P¥‡hÖê+uÀ¼Ç9¡Š‹‹·Ï]¤a授¤¬½Ç´)úÉS¡QˆÐNŽÐªÈ[”+ “Ú9¹ZZÈŒê´Ï½T-Å Ñfx»*øÇ2І÷¸À@§»Fòóó,Z®æúÀè3K×h#´÷ðß迟¡QˆÐΆÐɉÑ<·©© ¡ÔµÔÜÜ”ŸŸ§ÏÚ2.Dh}uvv¶¶¶ÚaE<y[“é÷„ÊED[à$B½c…ÄÑÓ§+ß윩@×ï)Æ9ÚÃÏ™+‰èFt øì¶"4"4"´,“–rrR.^8#•V766`BfJ]]]}Áÿ4œ>8‰X mëÈ ·Ah¡PxäÈ[¯eß>r×]*n¹P+0í3Ú.%ÔT+$ކ7;x¿s-›7¿Ú?Ô Bï[´<=5Íà‹0Š'%%…NÅCÓuBB—Ë­ªªZ´h"4"4"´\QVzâø¡ŒŒt 0v¥§§Á)ƒ§OÿœbR;Dh"´\NþŸ½³oêlûøæì1—wÌÙ;{§ßÞ16&lŠ[‹V¨{©P£XBR(V¤h±*u×Ô%M5uM›&©k¾'9…4r¢MÒûý¯^ÉÉsÎIÎsÒó;wîç~bb8>—Æþ¼“ýÜÀ¤óS=jÍ¢ÓÚ4øØþÐÆ¾–tï“ÙjõÝíîî¶Ù°¹?:M€Ÿ ®ìß±SªMõôôÔ××ZXXBBB«À] µ5¥i©±7o\8ëÖ£ÎB]†:u 4 ´ºE¡‘Ùì·ÙìW&¥_cOiQ_„žÎÔìÃû6;R»€§±±ÑÚÀ(tï!zhüh‰r!h¯¥Ýу¾2Wª„D@h@h•™Aoliªª(Ë/ÌÏk„Qg¡.C'´C¡¡'2 ÍfLVxæùñ.õEèƒx_e³Ùì-bž±±±„øxo¯ÎÖ¶§NQ©Ty¶  ­zîêlk„EÁ3 4 ôÄ"4‰ÍþjÒó3ò³ýjÊÏS[µç ÿÌf—-Bƒ¡Á`ÅZƒêB«FCCC555ÉÉÉ.\8zô¨2v1Âf`³ß~Vï\Ž'Zuœßf³/0Bƒ¡Á`å tWgTÝÕY¯)|‹— 4™LŽŠŠ’m]ƒQ\\™PQQÑ××§¤(t›½ÈùA?FWG„~n@ µ>:Û›¡A€Ð`°¢-¹xÁ B_¹tŠÙÕ¬)Qh'kîì„âUTTF°ñèèhsssVVVtt4oôF$)¡#¸#¼€™üLdq¨Î_±ÙY@N€Ð @h0X¡ÝÍl»pž(B_ ôGíavBMAèÊÊÊ„„Ä̱±±ÅÅÅ Ce¹Ðè¢è´¬9¹OtióÍöá&i¢JKK}}Ï›˜ì¹};‚N§Bƒ¡Á`u@è~ÖÍ ³:!îBè kgP{@huFèŽŽŽ‚‚Ì111)))555CCC7¨@„¦°Ù?*kT.‡Vfqx1›Ý¨Q´ÓÙÙ¹páÖ3ÎaX9†1§MK{ÿýíÿ} O8B õSÈÙa!—Ä#4Zx+( ¨ µ×Äá„*K„V=B×××§§§ãÓûæææÒh4Ù6ØÐÐ$ÿ;Çf¿¬Q¹SZ8ñÉpØ?d³Ã5uúûûÿû_ DÎS(>ùdš³ó@h 4<±Í 7 /TSCEfzdb\H\Œ„NKŽÈ͉/,H`С¨š tffæÎ;ÊÊÊz{{Õåÿ3›];¹M"Ær/N0W°±·ÙØ›x±Õäê£1M@Í›wO™ÒÄÅæ óÂ0g ;‡aýhÉ«¯ÍÏ'Bƒ¡ÕßÔê’;·¯^¸púâ…3ÏÂ_µþ{ñÌÛ×j¨d‚ÍQHoèïîïgŽ7ZŽ^EmPËö–r-H䘄à AªÔ/Ä(úÑΉ@èl¨&òÍ›Ðij§Y³\¹½Ó‡aŽ6À}LÅ0oîƒwBƒ¡Õܵ5¥7o^NK/)Îk„Qgݺy¥®†L¡¥4 ´p·¶Ö§§%"ÿþÛ¯€Ð“\¾ÄúéÞ‰@èìq‰ì¾>è4õR]]ÝóÏŸàvÐ «æë¯Ö‹üü³‹TìëëËáÊÐÐZ5Ž‹(, ÑÚzº;ÁaÔY¨ËâãîB«r8a µüRà9äf¯AÝÖÖ—ŪBms9¨lì A„^±zL=ú¸„þñGG©6È`0®sµbÅ @h@h@hÕ8)1º­µN=“NÁ¢Œº u ´˜iV&y"Gss3ïJíçç‡?¦ÑhÃÃÃp‰Qˆ~$FÑt¨¡O Éâ¸zºKõá‡â9,°ŠáJÚÄÈ ­2çæ¤³˜4€RÍ2ê²¼Ü @hQag@hoootùº­­ÍÀÀ §§.1 Ñ~õÌåX*ÈÏ=Æîî†îRGî™2¥ŽÛM•\röÀ°‹8K¿öÚßÅÅex3:^XXG¤Ž% 4 ´T.——H´ «Bƒ5¡ÑwJ³^|ìm­Ah­N888(O!Ž={öDFFvww“Édfhh(~âT"«a.G-û— Bëè@_©©Ðü‹/,1Œ.Ðeӧǹºú‰Z¥ºº:99á4‚j¡³)BBK…æàI ÀЀÐDº¸ åõ×_ųÈEiZ…Ö¾DùÅKç@(ü¬p}§n¹ç…dq\¸¥¾b2™K–¸Î˜qý3Æ0ÆC%¼ûî6?¿kDÖkmm%‘Hx¾ú§ÔÔÔ4::  ­p„æ]XEµXÂßxR!4?{¨¸V÷{‘ûz»xKÐãýûö<¸O~„®­ÎŸ=û3f<‡{î³ë¨Z3œ 5S«È%“½¹ D×Óªª*taEËÑ{Æ—+dvÒNbýTªzµ ??ò»« :JÝE¥R½hiéÃb±dÛHww7…BIHH077„„–6z< ‹löbèz²E¡UÃ!`Q•ÓðÛ·=»wðzmwǶ·5ʃÐ-¥–æF³¿ÿö§¹?^@Ñc+“Ö&Š6!´ÖäBËüëp{{{vvö;wbbbB#fs§B£sÕ ©± R1„~qD%ü\ÇÆžDèùó¡—& -ÛpB"Ä+BOÎD@è 5ÓÌÌ æÀ‹hIÀÙ“øSKKK9£Ð'ŽܸAwÓF=)«›Õž•‘Žž"Ÿªol|sï^3 ¼xöìÎΟº»?š—ÇYÂb-qs„© B© ̓g¡õ7 ‘*r¨_:«¥¥éòåÀÖÖæ~&L­B¤¢†"ô™3gxÁg¡?›™™ÕòýàËb±²²²9§¦¦vttß8âðªª*¸¬(O.Äú_ÝÊDèM‚ü‰ÐJJäð÷÷Ç):11ñ³……•JÅÔh_ˆœÑ_ÈÁPO¥C膕ÆÏlìeA„þá虉ԂmÛ8çáá•»v±¹3 öôôtvv¾éブ?vçNzf& 4ZÓ“ ­‘S«äæB‹ Jk\.t]]þ`dd¤¤¤$55‘sVV“É„‹‚:k„Íþ„E?Ô®„’Åáë =3¡íèÈAèÑÑ%;v § }£¯_¿þâþýhù7ofegBƒ¡5ÝÙ¤@hMDèœìT@h¡Qh ªÈÑÐЀOwâ{ŸxRRR¢££ïܹãåå…–Ÿªdš bý$K9½EŸ§La××C·L¤\Åš›-¿~ôhV~>›[9G×ÅëêB _uqINN¦Óé€Ð @hvQa½£ T³ÜÙÑRT˜ ­é¹ÐÃÃÃøt'¼$ÁÁA6·Â3¾BС¤‰ÍåxC¡¿ùúd‚ÕÚÖö@ôÈÈS·o¿}û,oï©UUœ%º,‹D"EEE¡»f©F7Bƒ¡ÕÇ-MU5Ô’nf;ÿŒÒ`µ5ê&ÔY¨ËZ›«¤EèÝ;]‘Ûšÿ™‹=Þáå¼g—äBOl"ÇÀÀÀõë×ñÇ}}}¼)Q@šqÍb³?œ¨\Ž»B²8öíƒ>Q˜²s³ìÄøâ‹BWV1F0XÙî¤Q©U…ä´nf›Æµ› ‰]]] ÑÑÑ---lný:ÄÌü‘çñ5:@ªÔ&býC>„v‚ÐÙÙpø¥ãgƒÑÛÛ+ž¢¥ª ][[•––ÆûUHyÚyòäãùùxEŽ)uuÓ(”{Ïœ„„ÆMï¨í¢7ådÅV–çµ6WÒZ+rÀ`帼­¥ªº² 7;}éÐW*r¨BS©TtL®Î%%%ãë×!Ц@i³ R1„~n@>„þRŸgÎd‹‚øY?ã/‰§èJ‰ënW1¹2>Dœ˜˜ƒßç*Iccc›wízîÆ þ Äÿ÷áï¿BBÿ3£¹«³‰BÎÊ͉ÏΊË!%äæ$æç&䥿§¤¦‘‹ÓÉÅ¥%eä¬22©œBª *˲+8Ω,çº"§weNU%çAU9ú›]UŽZÞs%«¼9³¬mŠc m6R’^ZŒöÂuQjI!Úi rQArqA2ú[˜Ÿ\—T˜—X€œ›Ïy‡ y9 yÙñH8&Åæâ²I±ÙY±Ù™1¤Ì˜¬Ì謌¨¬ŒèÌŒÈÌô¨Œ´H®ï¦§F¤§ÞMO‰HK‰HMç8),¬Bs8çÈ£.HKætêÔ5©œ>B…º,+=Š”:‘”ƒ:”Ó­¤t¯‡:šÛã¨ßãórâó9N@'çÄàœ!I…ùIEÈÜ3?‹JŠR8'UQ*:»ÐiÆ9ÙÐ)GNÇÏÀ²ÒÌrJ&:-ÑÉYQ†Ì9QÑI[Å9uïÏÈÔª\jenue^uÇ•ÜÓ»¹,§¼4»œ’]Vš¾D”t>g–g”¦¥å§ä%£oS^NRzùI奤®ÎzfW“`kP"‡–Uä@„œ““^ZZªšßˆArýÂÎfÿ[Ù¹¹llŠ BÛØÀ±'7[7ƒG ðñƒ?¼ûÎÏèo^^bQDѳߟ¿j:ÍÔb£…"H†“Ýg[ÿæVTTĈF#²—ÄÄ?\]?svþØÉéwW׳ÁÁ0Á7 ´@p ]Ó{Xí½ÝÈ=Ý´{fµ#w³Úº™¸[‘Y¸-\7ãfâîjW¾ ù~N{| ÷¶Æj÷ï ßuoO'rÇô¾^ä.äûV&ò@?2‹ëîÁäŽ{†{¹îBîæx}Lä‘áÁ‘ä!äÑ‘áÑQÜ#cÈc£\ApAÕÃÅ=þèßè×èƒðžâtÙ0ÇœND]ÉíSNç¢.æöõ 2ÞûÝèd¸V°¸çɽs†{þpO$::£ð³ë“üÞÎ;ɹg8ß¹ÍwJ7Ž?·9æžùü'9ëÞI.òµšªÀ]×ÕÕEFF¦¦¦òÿ~´ÁbƒémSQ^wrÝÁceÛ 4 4üª®Ñn¬+õ¬¡Ö¬\hMO䨮®Æs6úúúàŠ qº¢ì\ŽoùùÕWÙð…€º»»­mŽ88v?%Š¢·Ûo_ÿäz%ñó??Op³;Ðqss3zjä`$&¿Ú£È=9ù959œÖV;þ@èI[ÔN( ,ÌÎÎ ƒœ Í~ƒB¿<ÆžÒ"=?°±©‚mnGýåHfzë[nEfù_0ݰ©­­ME#~æÅŸç¾5÷\À9¼À²Òðf8???""bµñj„ÊZ,4Y¸Úv5Ïë½×Bƒ¡'³iíu,F‹Ð`@h$r¨!BÛØØ”——ó/éìì400À)=Æs6x×zFk%±@ôã2ärx É∇C~Occcï÷9dç4š@b'f#3#­×mŠŠ¸;ž¢¦õÌõ*{×zÛó/ßh¿¤@„f1šé ”’Ì\RlVz4)3&›„ÚŠç ÜËM(ÌO,ÄÇg¦”¥‘‹ðYie%霔̊ҬJJVe9©ª<›;+§ª†‚µj8!z@­ÌÁrNæò,üäG«s‡¦“K8§.¹ˆsÒrÌ=c9ï-'™¹¤øœ,ôþã(䬮ÎzCc†ª8‹Cf„477¯¬¬äçgô·ªª*<<<==½¿®2Ú£óÄúÙ~éúA~~ñEöÈò{xl¶i3éäEDΡ9º†ûÁAúì6/'g¼ü#?E Íß@_ÆD®”÷­Ä9B¯Ø¸"** Ïî„)¡9Eí:›Ð%!­•ÚÍl‡á„ U2&ápBV{G{mEY6§¨]g£Fµ Ìj› .ߥ¥¥øcDα±±aaa r6´OmlökÊÈå ³±‡ÚÈŽ7GÅEE6ë73#07Þˆ0Õ]Ÿ—“{åb «‘itZH¹xcÓÊÕ===ü-&ÿ™ÅbEGG£/»¨¹¿ä–áÔ¤TÚ|«9žÝã: 4HÍ™Z¥0µœ’…®ï]u´6¨ +'ý¦µa3‚êÊòœ’¢TõŸZE *«sEt¡Dð 9Z¯¥ÄÑÑ¥AèƒB²8¢¢à`sD&“Ï»ïD¨\pöŠ­©¯üEuuõ†e+z£RÐK¶õyw¬8EK?ØÞÞŽþ!ÄËB ¼vþÚ¿žûëØþc2 4ÿr@hŸà›ÞÙ³‚Áí­å´û_wN!+vÒ¨Œ®&˜à[C]mõëpQQ\/ÔV§•‘Ë1OŸŸ}–=njÉ«­F&œhó… S~ÇÑÓ¦¦&|r{³ÑRtš“•µ764Ù2^û®¬¬L?[N³ôÄ<×>¿– Eã½·~ïœßç:qˆçBƒ‹ÐŒ®fî¥ z± ·€8E+I…„–¡ÇÇ¥ñÇù©X`S[_uvî o öv6¢VÔ,„Ö ºÐ “ɤs5ÄB6r¿pWW¾\QA6¢ô;1Š~´SBŸ’Åqó&`A­_ ãmf-àü…gqäää$&&ÊóMÌ߸ߢÐÐP^í» §h@h@hÙZ ˆJ|,~ b|UZ„ÿN$.„h¶v¸“F­¯«Ù·o_HðŠrò7_5wÿþ;dÄÃYY騨à¤=‹Z±©±&àì©éÓŸ8îwßµº\ÔŠ‡Ðš2;¡BtæÌü·Úòòrôbccñå¾¾¾ør…DØ@ ÔßÄúé^I½HŸ§Ogß/Ûz@ ƒ>N½| Ýr†‡‡ó ”ŽÒ ‹~ùw|ðà²ç—ݺv -¤Ñh¼ªï—m´šb…^uÁ\æ}:Z š`–`Wªh™™VÙ !ZfzGmUUÅ®]»22R½½wýë_OæÖ×Uååe=þøãG*£”>|¸¨¨1³¨Q{äcGÿž6mÚÇTYQŠžŠZZÍ9x£™Ð%.ê¯jbýÒ¨X~¦²±é‚½lÝÄb±ø™ŸBEŒòòòâãã‡dª |=xékKqŠÖy]'2$’÷’½‘½ÁtœŸç<¯ª ç§æF¡¥Bèñp®@„“\1>EDÌ;ŸÈü¬=M«iiiD¬u·”\øÝwÿCÎÎNÿú«/çÌù¡ŒRœ›C:sæLMMµ@0™ÅjyIq>ZñóôéOœ:y-µ" ´:#tYY2gbb"\/Ô_?ËŸËqVHÇ•+phïéòåðyó¶¿ñÆž×^óæùÍ7w/Xà•”D³"“É ¯©©QEO8?Bƒ&¡eHäPHZ(Bü€ªùV}9hFWú§äïï_UI).Êûßÿ¾}üñÇøa6¹¤ ª²4èúµ¸¸¸Z«˜ÑZ_}ù¾Šÿ‰cˆ¢Oúû‰ZZ=º¾¾Á³@)i(j§ò‘?—c™ ??ú(›Å‚CËÆ3(Þ|óïñ·¸?ýÔ7d@” Ð?ÃÁÁA¡¯^¿|%&2ŠE¯ücå„ó3 4HZ!QhiGóÉIø€ÐZénV[cCƒOtÔÝ2JqA~öîÝ;Š sËËŠSS“Μ9S[[Ë 7‰Y14$ØÌÌ_ùÄñ£ì½" ´z!4>¿0ºÊŸ_Z#D‘3—£–=%H†‹Áq½'_t¹…Ð?œvåJ‘$ˆˆˆêêjþ…ÝÝݶ¦fw¼}O»l÷tvšòÁOѶ˜í„ó3 4H9¤MäJ¢*rÙˆ ÕíD¥gýŒP‘C‹‹rôõ²¨TêÁƒÏœ9”ŸMÊHIN¼|é’¿¿?ZÎbv xHÖ¡Õ¡™LfLLLff¦¨0ÚéÓ§áz¡šMŒ¢é†Ð…á¹spPïiÑ"'QüÌu««ÁM¡oÜ·ÖvnvŽáªµ AáxM¼Ò A«× 0öxŠžp~„iÓì„@°`…dDô÷Òéô”””³gÏ">þ<"+´Aò¸df9W„žx„îïïOJJJNNFàŠ ò&†ÐOõCèÕã#«ìû3ï‰ÐH===Û·¹î1·‰ÏBðœs*°,ð&zжÍÐäÊÅ@Q­ü  Òt„†0X ±èªþÞ®á¡AÄT}}}ýýCƒ=¬¡óÊ·" ôD"ôðð0^­®»»®Z£"býâÈ8~®gcÏ báüùpD•…Ðt:ÝxýÆÄ#§3&ö[ÙûìÞã¶ÕŸ79dßß–ãf…D=÷Ó¹ÎπРmŠBƒÁŠMê`ÒYŒ½A"<˺" ôÄ 4^­.**ª££®Ú§ÿ“-—ãš, €Ð€Ð€Ð`0 ôd@膆†ˆˆˆÊÊJøÏ¯ÝÊ•!—£‘½(È„?ÿ Çò¹¹ùaXƒ(„~챸;wb¤ÚàNWw•­ô ÑÓ¾¾>¼Þõ&}´|þú™þ€Ð€Ð€Ð`0ZÙ-ªô\{{{TTTQQÑøju íêã/ˆQôôû}S= Çò1Œ·ßÞ‰acÂz胬²²²¤Ú`IIÉ™mÛ-ï2·æÍZZZzÊÅ_¨æ©V€Ð€Ð€Ð`0 ´êñÃ0 4‹ÅЉ‰!‘H'}¯   ñ ƒƒƒEMšàx)1„þW÷}„Öd©SÙÍÍp •œœµp¡ûÛo;Μùßßyõêí¥¥å555è^uxx˜øm6sr9ª.ßÞ¢»ÎÑÔYåjZp Zhkd¬æG „V=?+¡ñju))) ¡\DËà_r‰[ô."ê©LbýÂ0—Ÿ›ØØ+‚ýý÷peŒT‡„„ ¿Ûïr÷ÀYÀ” A§üŽBBk.B·5SšJj«ó•j´ ´#*0 4D¡åDh|ðþððpVVV||¼b«ÕñS4ð³šk”ÍþŒE?ÔÎÆB…dq<GQf®ŽŠŠ¢R©DS(”Ff9'/ ØnÝ&^j 4 ´Æ!4"Û†:J_7³L‰îëë­¯ã°:0Z„Æ«ÕÅÄÄ(©NÑÀÏ!'â¹&‚ü>>ãó¢Aêxuc³?"€Ð¿±Ù| ˆÐ{÷ÂñSŒ˜LfhhhWW—|Þ½¹¹¹ù·\½öÚk€Ð€ÐÝÚD!eDŽŽôõvu³ÚTàþ¾®‘‘!´Ó–ÆRÁrcàý=ÝôNZ-×uÊù[‡vvÄWÙÞÇï ­•Ý—²9úúú”ñrtt455uûöí'NœÀ—Ek„l ô!6{Û6A„†Úá ÅŒáèèèêêjˆBBO„ni$''†ö÷vw²-*0Bˆ>´S<$‹ÇÀŒö¦² ÌdÐøcàªÜ»À®Á€ÐÈ¡ŒºÐò¨¹¹944Á³@þ3P´ú+žB#°ËË{€Ÿÿû_8rÂ588800 êÕ±±1‹%êÕœœœÌÌLÞÓììì>ÅÆÆ2™L@h@hmBèÙÕ¤÷tw𺥱·4W4Ô«À­Í¼¸Š÷οk@J@h@hµBèáááäädtÝGl)„Ðâã¡.´:kˆÍþ@,?ÿt¿åÛoÿƒÐ^^pä„‹”—÷ê×_ï<~\à´G_+!!,Xà)vôA}}=ú]½óÿçõÄw1,‡Ï¤wÞ9´aÃÍšüZ,B·3è ÆhüZâvÄChd|ïM ”ºšB¸©¡l¢öοk@J@èÉVZ"EO B766†……É\Ù# @±%õ@²ÉB,BûÜoæàðB“ÉpØDjŽ…Å´¼¼¬­½üüp¾þµ‰ÉôÐÐYöö===âWg2™ï½g-zŠð´7"¡¡µ¡»™mBÚØ¢@Œe@èna]_[RS§×ב'jïü»¤„† ¾Õ!zxx8111;;[ž€Øž={ˆÏ.Rž¢Ä"téýfYY÷(îÃᘉSRFƳ.`lö´ÂÂ-,¾Bð‚M-/w"P'!!õ‰'BE!4òüùNt4¡¡Å#tWg½€ÅP4ç%cãñk‰·P„®­.¬®ÈQk©Eµwþ]RBBˉÐò«¡¡!,,Lþ€Ðj¢6û=üüý©ì7Þà œ«+3 ški‰ #Šæ÷,;»ÞÞ^‰ë^»†a)bzî\@h@h­AèV¡Ä+¡¥ågB3ÛÆCluE~…$Ôh/¢^’ÁÕ•ùRí]I»¤„„ž@„NHHÈÉÉQÈÖdF袢"Ö¹xꢷ‹·&ŽÉR¶¶ˆ@èÝ6³¶æ \~>0 JÎȘqñ"??sBЇYz!4!tÝx‹ DßA×Ik¡]Y–SFÎe´/1¯Jåʲ\i÷®Œ]RBBOB×ÕÕ…‡‡+0n,3BËÎÝÝݽà£&™ØavKß\š›—6~…Š@è2Lf¿û.-ÉšekËÐÏùûË „„ž<Íb´Ð;j…ZB‹j/Þ,fëxˆ-/%•–¤‰1Úø]N!ɰw…ïZõx >>>//OÎí?~Ü‘O_}õ•••ÿ …"jÝüüüK—. teeåÉ“'‰ìÚÛÙÛ³÷ĉBb¾ãÃ[t¶,~~±Þ³zëæ¬£VS…63v1>ÔyèãýÍû?þúc·|7ü)òz§õÚ}±[χÐÛàÚ/“²óòVÙÙñà™_¤·úø88HœQhttôÒ¥Ðyóœ¾ýÖ‘çÿýÏÉÂâ@[[›fɀм}šàºmmm±±±¼§ù¹ùa·ÂºˆYÑrxx¸˜6ómm±ÎÎY¶¶·nýbañxZÚLOO­ImE7£îÀU‰Ð-XLLLcc# ´Tä ˆÐŸg`©[ݺ«£žÖZ®wu6Œ‡ØæJ}M ÜÜX6Q{çß5 % 4 4îÖÖúô´DäßûU6„>r䈎ŽÎø¼hù…x2$$D!TY\\œ“#c}b&“)†…*..Nü<2çoÜx(5]xž à ôà'mbž @[Õ*!!¡®®NË>º1ÏáÊÐÐpR!´ëŠz,QKÛ€à.&¡ëTÃ]õˆ$IYÉUåÙÈÑ‘7ûZÊj*óTà–Æò^´SÕï×µÕù`­7ºWjk¦B‹w µüRà9äf/B#~ bË‹–_555QQQò'?³¹“zË<±ËÀÀÂxi hP(”²²2‰d>ÓÑñŸ‰›‹Šv;¦U¡Q€Z•+99¹ººZ›>ºƒ¾ÎÕŠ+&!B‹¡Vâ/ÉÐ@æh=B#÷vw ‚ÁZì®®ŽêÊüû?:B+>‘ƒÇϼŒEQtVV‰DRȦrrrd.2::ŠøYÚÑ‘è¢/¾MeuõWúú•”üƒÐˆ¨ý._É#ô-.//‡D@hèWLž†˜\ŽÉ†Ð¼ptWgü…¿Zú·~ Ÿ5<ÒÓ[a`’Gyyy…f„Vg„&R"C--BƒÁÚm½~ddˆ”ÝÒH„VñßÒ ñª¢’ŸÙÜ¡Uò„²‹¸’v­ºººÜÜ\âíKJJ¾16FW ×÷ì‘8·8$•Š‹‹ç¬X‘(âK³V£†¯B+¡ÅT™†\h0x’<Ë-ÃB„V„Zz+333;;[!Û—¿À^ZÚµúûûÑ-€TaüÈÈȽ§NM!“°·ä h``àÎ;vŽvz&zëÍ×›Ù˜JU f¥åô}ûæXX$ñ}µCbb¾55¾w¯ýÁƒ€ÐÐbbÂÒܪ"‡ú#4…œqíÊéS'!Ÿ>á/ü•ðùÚ•3eœœ @hµFhþ MØÜägtL•üŒO3ÑÒÒ"óJ@Ú/¢hâí“’’Ñh´ôõÝŽbñëòËˬ–m Øâ–wo¦x/Š—ÙU³Õ.«÷ü½G`^K1'ó+îîØðð³.̵²ò;þ;só§nÝÂÆÆÞߺU³ˆB+œ¢ ¾¤ðºÐÒŽV„./ͺèu#+# 4úÊ\ô¯ dBk BÓét&?Ë?Ù±l% ‘rss¥*É‹Þ$/ÐýÅï¿k÷¤ iUX\¸îÀ:œœÇÛ2ÄòRÐ%‚›Zçé‰ÑhœÁª##Ssrݶ¢’ŸÑv“˼…ÁÁÁ;wîH[Z€‡ JbÕÐdÖ­[ÖáÖ¢zWå®í>Û‰ÿªò†——@á—÷4-= ZÙ"áj%âq­º"p –Ö苃¾>€ÐêЈ9ež.P(ý"~–vÃlR6oRr¼ ñpDÂR®KKKå)Òz¡û¸Í¶›­ïZûvø ð³s–ó óRýÔ²ÐÎü¡ÛÚô·o׸c­ì(·Ö tJbXsC à,­Ñ'5)ZÚÁÁ1§œ3ógƒô÷÷‡„„HUK9òv¤î׺ºÓuW}°jŸË>¶L% qÅÇÇKÞxî·Äf@’“Yccc¯^\¢»ä×å¿ê˜ë,³[6wÉÜ9óçøúùJ•rnñ>±²z =6ö¹9 4 ´Ä< %íB698Ø[˜â¶³µÒÓ]ammfnª¯»v¹½½µ¥ ïU´ÄÌT½*Êm­T€%0Z#zÏž=Ž|úì³Ïlllø—ýÍ öò:??ÿôéÓ¼`2A­û~'æ‰{í+kƒƒƒe(TÆ•T«$''wuu‰oƒ>æË/ÏêìŠq400€ç]äääH{¾ùZ‹šM ÕY­z×®®.¶¸½¼ÜMŒ7yx¸¸8Ûl1Ú°sç7GÞ«h‰‹“µ§‡3Ï›THHÈ]Ľ BktEâÊÍ͵··Ç)ßZò×_õôôHµªªªÕϯæ!´=fom`-Ãûa±X'ò¢âÔÔTQ¯æä{vtt„…Å<ö˜ÏŽÇ8yx¹z5lr¢#ƒÁ8àu` ‹¾_älî\\T 8””D|ð)'miÉÁ掎×vîüÁÂâå}û°înllìkSS@èIŽÐš%y9P÷A€ o@èɃÐü6B"‘,X müÿ‰|íWky½êéUeB‘=þ\kônݺ%íTŒ¨÷ÅŒ"DŸåßÿÖ}çmo¿ý† ½óŽî¬Y¶Ï?¯_SS; ¯˜a·Â¸Ø³¶ÁlþÂþrœV¾¼ÒÓÆsdd„¿Ydlä¾Ãûø—ܾáwÖO‹Ldd$Á_(| mûöU..MMœÿ–•ÕÕ‹ìì^Þ»÷‰À@Í DBBBƒÁ€Ð€Ðò¬ŽSô®¤J åWàñÀ•ï®4bºì™e$¶OÍL5t1\³}þ6ýàÈàÖ¶Öu–ëV»­ÖsÑÛ{d/ÁR(”ŠŠ ñm¬­÷O™R‡aìûîÓÑqœ„—Ëüœü¯¬Àïqp„Æ›úÈjýz·¼¼‚Iu¹\ñí ÞÏü¼âµå÷îDî„ßAÀŒz§hœŸñ§{þÞ£ÅÇg`` $$DbFñcl¥  ­5ÍOÅOÁ`@h@huFè}ûöɳ:…B177ÿóÏ?yyÑ2Èè#7Ì G²åï.Ÿ ’—ŸgtΈ‡Ð[“·Î]?—÷Ô·Ã×ÔErZiJJŠøQ„66Þo¿­÷øã6̇Ðc?œX§·]ÇÌúÇõo^Ì{ê^à.1gÁsrr2‘è<~8‘¡›þüs½Ìù*ª[A·¾œòå/Ø/¸gc³ga³xO‘—þ´”¿=¢è û7ÜûQ Èb2ð3®¶¶¶¤¤$@h@h@hi9ÆÿÈÙ¿ž¢Á€Ð€Ðš…Ð<~fóUä¢ûúúVÍZÅChƒÇ ¢B£Ä¯²Ýwû–€->->æAæ¡Q¡º»twUírNs^b¼DbIñ£ùõÝwæS§–?õÔ>ÄÏ3f<ü0éã­¥´¨éJKMÛðøQQh7ÌÍBÏ•JUàE€Ð²!´òêÂɯñSò?Vç¹TTÐBS Å6 =™š\’¿s‡'ò—_|®æ}ûömˆòªEWWWK»)Ãé†<›f³üÛåcccW),.´u·MIOÁŸÒh4ý箜“˜ZQQàŸÈ»ÊÌÌyõÕo=·lÙ†a+V8þý÷Å·ßþËßÿâ¤BèÞÞÞeo-…ÐFS.\šÈ±vïZ‚‰•••o.[ö®™ÙßêóÁGFFì7Û¯ù¿5þûý‰¯%C±h R{{»7W¿üò‹z"´Úò³¨ù¡Å 4WBƒ¡¡Å›Å¤µ4×"/Y¬£þ‰<ÉYÖ).*Îd™‰¿¯ÿÀÀ€¢ÞÕ™72rsü·?lààPE òQc|îBÄçO?ý‰ÄÙÂ{mM䨫«»zéòN7÷Û7o L¯ãhäh?Õ^(BëüW‡×eüà ½{{ô^»k-‘á„EEEO\º„ÕÕ¹ú©Qù;7s7Ëi–è3®~ýÝ`)*ÎIU,Z³„n+è\mݺUÍZÝ ´Â9¡Á€Ð€Ðš› ÍŸŒ¡†ïêÔÕ«Ï.[æò÷ßxX]÷ÿOOïù$>8•JÅCÖGŽhqœußnwƒ6[óN²"’2üv›Ûó=Ä«ùŒî–·Ünª?B»an‹f.ÊJÏâm‡WÔn·ïnô´©©éˆÿ"EíÔ¡ÑÝ“ÎG:¼;ÝùºR­N¼X4$rBk¢^ÔNü@h0 4 ´¶"´zŠÅb½åæöXjê[¶\½sç}cc¬½}ž ñ-ÜÕ´i—egj›%Ë[Ú³³ùí°fýVK+^³gç¥ï-]üÔâ¯úzÉëKô~Ö«7ËLdläþ#ûy[޽rSâÔ*jˆÐñññ«^ù'Eÿ§Y?IõëñbрРDhQd‹™8[ªY­Å'Šˆ™¡[ª-‹o/qªÁr 4?<Bƒ¡¡¡U¬y¶¶œù”»º¦_½ŠMÍÍ=ráÁuÚiý! ð?•wöò+‡ íÞ<~ÆŸÞØí}7R€ з±1‘ÇÄĈzéflìl›¹ŽŽÈÿ33{$) £Ó?\¿_‚ìvìØ¿#~FSx½ìÍeÒf8, ­l„Ï™˜Ü’¸%!4AÌVs„ƒÁ€Ð€Ðê)ï'°âbEsýow÷ŽŽâ!èÉPæ×ÕÜŠ›ù ;š[ ]‹ BçççÓh4áûõóÃêêx]3Þ‹ˆ%ØÏ™³º @ñw:vFv.˜ ¡×?±>!!AÚ0™ÌÈÈH@è LäϺ¢ÔD"ÏŸŠjÀ› -Õ[µG@h0$›.„„¼½mÖ×Ç£²iee_WQ©×miiÉËËÓúC444äabÁ|ÖÿK‡?í´ÅD„f±X™™™B_:våÊGffÿutDþÔÞþáÂBŒÅzßÎ_‚¼ÙÕUâöÓÒ²Ÿyæèï¿[)üÈÜ{Ð3á!ôÊ×WÊö“D[[[JJ ô„#´Ðü" ˆ,!²™Zž·  BBƒ¤Rÿ—g®_ÞìïÓÙÙïêUñ[ˆŒŒä ¦„¯„„‚!z"°ÍË…¶?xPª÷ÿãæ6ôÒKLj ]óÂ?s2þôîO C¶MQ©Tí»Ó,„xó GhñÉò ´œo Öz„n¬+„„V ÖYY½»z5ÌüÈÊj •Š.*/\½Ê‹p¾1{ö°0Q«Óh4‰4IŽ•„D3á‰%%%…ïD Á¶Ä‰lx½ÞÙ™ø;熠OsgìWx šÅbýõþ?µû~üèGyn©rssµ¬X4 ´T¹Ð€Ðœ„JZ„FüœšNk«„„V†ÚÛÛ_ß³]TæØÚ\%::šàô…Z ³'üóÏ^:œðÖžƒQᢎjaa!‘íWUUQ%eÎðz´!h|îuiÑ]]]»Øx™±#æˆøÙø cßݾ2äBóKËŠEBO¨„@h0Lģдö:£ZIšcg‡UTìÁ·­™yÀ™³Dn7ˆ€1.t&'Otl%Þ•TVVΘq€ŸŸñ@´ŽŽ5Axæ7ÆÕßß&:…^⩵uÇÖ¿6üµÈn‘É5ëhë ¾þ2økõºøäx@håÍN(mвB9$ŽóR…µ5®.4 †D@híS||¼øÉʃ®\õÔ7¾»ÿÈ%Ï=£ $`A;÷‡íýÛÏÎù¨¯¯<L¤efffww7‘–mmm§ñû‚‚‰áÜO>1@è'žˆ ßx—‡§Óf#¡ð,Ò«þZ˜Ÿ›+~×õõõ¢Êô‰ÏfYj¸t7u÷!ú¡ñ6:it'ü ´ò&ø&2A‰<Í–2Z!ÍÖØÙ Á`0$rBk™z{{%fí"9[ÛÖ^ ᧯ÎÐ8³MúcccZph4AÜE°M<7˜xÈšMl^õÇ/>òH ?Bù¥‰Ð. —”Xn9lçÔ•*ŠŸ+/ݲßd°çn"7©©©Ò&` ê6¾h,”Ÿq:B+¡'‰  „†D@hõQRR‚Bøä¼ÙˆÀZ9dgg«Õgioo÷Ýųf9òüâ‹?ñ?ýÏ‹º 8𲝝/--M€³««Kª@ôcE AKi©à×èèhHHˆT9ð¨ñÒ-K½ë¼…ò³Éy“ à @h@hy¢Ð 4ƒÞØÑ^×ÒT%Þ¨ j ¤†D@èI®þþþøxB)©ú†!±üÖ•b´VO­êàÕÕÕ½úªßƒ©NüOßyGäôÙŠ'ˆ«¸¸˜àpNã‰ÌèLj~÷ݵDê6óƒ4‚gÓ•k¥‚gžd˜¹ÝX¹Y-4X¸vÇZóÛæIúÇõuÌtô¬ô"ã4op@h5Aè ÉâÐn„F`ÜPWÁdvI“IG-Q{€=ð"4ÌN=áJKKC\$±™ß߇íÖ¬?lïìn`2›>–@ÚibyÈÖi·±…ëVGí@h‚ÕêØÜÚ[öõõ¥¦¦ÿQQQÛðÑO<sà€?ñ ¸HÛ™˜"xF«P(ÙŽ0ZÝH»º¿zýêÉ€“è3¢nÒÐï ´zR´Êö®­ÝI«¯ª,Fç“$¡6¨!jÖÞBBO6Õ×7°¹ó\ ¨–’ÉYYYMMMW._>çº#òÀÑ»¡a---999ùù“- O<ö.U.GYYYmm-Á@4ž.C:zdd¤Ì? O$1áD[=É¥­]C%Óhmˆ»‰ ý÷hooEkï¡¡'•FGG_|qVss3¢biç¼@ødc`d³É@=Gª& ]TTD<=#))‰xþ0ê"È=22òÖ[¿áYÐÕÕÕ¥¥¥Ò(ƒA$iDE £·   -Bc&jÉø—D-T éõyéèÝ××G¡QKô_­…ÖäBO¬ òIvvÖÈŸ~ò1 ´’TTD^²Ä¼¶¶öîݸÇ;fgwàöíÛ––»OŸ¾Fp ]]]$énXø¥sçÔó3"„~òÉß0Ì‘Ïßò?1c®œQhtï€àÓÍÁÍg»OfF¦D|,1*yB>`cc㫯®}þyž}ôWþ§o¾¹DÔº4MLÅãk×OY¼Û‚ñóÏØÏ¼§ú˜¾™®™L‰ûÏÇG„Ÿq/=ÍS~~~SS“  áº->¿-ÿ`»òòr"¢޼lLJ‡yÙÚ2Ÿø{hhZ£s¡¡Á€Ð€Ð ¡*((ú÷¿¿óñ9{ðàé©S“tu=n܈øä“¾¾'%®KdÖ<\~>~[Ù‚Œ(äm_¿{÷' ‹ß…ú›'NˆYø„)l±E9V?¿ZT"‡ñã+篌_kÏ¡=¦—M?›^3=ÐzÀ-ÇmÙ:"M°@´€²³³ÛÚÚd8Èeeeò$º£Kª as@h@h@hµªÈ „„ ÕØØf±XÏ<óIVV¾\b"NGlFd­­­óß›S%z€žÊÿ¶]ýü°5k°âbŒÍtg'fl<ßÊJ´zöjwÌ](B/ÿh9º¬´÷>ìó³î.Ý‡àˆ¢×›¯'³MLL¬¤R®_?D0v=<½sgQQÁ€¾>ÒÎ8‰ ­%ϸBöý‰¿#nGèü[g팵›¡¡¡¡Á`@h@èI»–ø}vFö¼wçYMµâEeyF ~çgÔ@.„®«ÃFF èûüŒ+¡{zzîܺe²i³ÇV§›×ƒÆ‡”q¯øvŦÇ7mÀ6,Àè?¤¿ôã¥ÁAÁ¢r?æ-Ç?~Qô‹~ŒŒ%𱼨Ø}Æ—.Gh„î2—ï–s\áÐÐЭ[·–~²ïúOmŒ¹£e_@h@h­DhRfRy9¥¿¿NL¨eYY)Z    z­®®¬¬”‚¶r[ýoÍÕvb6Ï-·"ç}õ¡0oƒÁ¨¾¯u¶¶8BGDDð ˆß‚À”âR©´´Tâ“Y[ZcÖ¼{¨?ùZýÝÔP‘šsíêÅüü<‰Yyy¹¨%jÖÞBBƒ„ ÑñrиVÎ]醹!¯úi‘öŸ}ÿY\|Q„îìÄ 1++ŒLVBš¯ÛЕj¼h¢?üµ¸ûn²“¡‰RƒÌBµÐÉéyÜÏž9ƒîjhøg‰»û±Ë—%ÇÕfK$®ððp™Ç®ûkÿÏóߟ¯þÓñBƒ¡»:jkJÓRcoÞ¸pÖ_¼QÔµGkï¡¡AB•››K£Ñ¤ZånøÝ%/,Yþòrr YbãÞÞÞ?·üé¹Ï“BãüÜÕʼnEÛÚb¥¥ò#tVVÖÍ]>›û£ÓpŠæñ3Z˜|äT”L•–å‘Ξ=Bjð\TtøÒ%‰Aü,í½O¨ÇÓÓÓe[wãüü½ðí…ýýZx€Ð€ÐZ‰ÐÈ zcKSUEY~a~†x£6¨%j°„„‰’ SuÄÇÇ›¬21]mJ¤qZZšÑ£-N[$#4ÎÏtú?÷)Z„öûûpÝõP<øŒ(Úhá’Í.Âù™‘´ÛÃSÅÇülpð*GGܳæÍú»Ÿ¾ukþæÍ¼……ee;Çר¨Äµk¤zIII¢TŠ×¦•›\0BÏûh$rBBkHwu6ˆ7À3$^ËAó+&&¦+"Ývºyz,·X.&VÉAèü|ÌÀà~æQ´¢hy:àÔ鲋7øó7ø3:ÚîDûîÝ?]`µg à q1™Ì””6gJJÓW^ÙQ[[G|ÝÁÁAÙÆúð5xÄà„þ BBO.%&&Jû+<ƒÁ NÝŽ0ô7().žØ^¸<ÇÞ~¶‘QKK‹ «GF&<ùäÄÏ\Kˆ–m\áàà »•»Ér“êÊjíû^BBBƒÁ€ÐjEÎù$;;käO?ùzb•È-)%m92 ÇÅiÉ›Á`äääð– õööª¡‘Z[[·ššûmu%8ŸúÒ)O;#ã*UÓ»rölS<[†@tBBBðíÛ[t×Y‘ÉäIû¥@·0Ž\}ÿý÷€Ð€Ð€Ð`0 4D¡Aãß|òYfFb'i×mhh(#V,‚'Ô¾±±QáH,[{Ò¡ÁÁwnÞjjjÒ‚®ŒŒLàfA³ù,] :&*ÚpîE·£ ¤žÈäƒ6['9HC BBƒ„jçöš«![Öm¶4Rrr²Ä‰ó”’’244D¼ýÈȈ´l/-rkæÍ³àAã~ýug"Õê<­Õ»²ÝÁ3~ €4 4 4 4   ÂÅb±~ûqî6 +GsK›u›)í4¶p²´v±°ZøË¯%%%Dƒ–Ä¡ùE0ñƒ'&“I"‘¡‰ÈÍíð´iäz쫯ŒÅ¯•šœ,ž…‚tMM 4 4 4 4  =iµçî¼3—H©éf„±)Á-ô÷÷ËPÿMZ„njj*--„&¢¾¾¾ÿüÇš¡§O¿sㆄjÏw‚nlY­›q<@?##º¾±ËGÕÚ¢‚@h@h@h@h0zÒjddÄHw¯ªnSÝõ===·€ÈVÚâ¡¡¡ääd©V©¨¨¨¯¯„–)-9Íë—SÇ›¬ÕÒ8<®Ñ»19) 4 4 4   R B#R“j§Ò?ÌÏÏïììÿAo\çÙÁÁ“ÿéíÛ‘“§[ùÑDBÐb@à BBƒ$"‡ ‰Ðyyy¼êv•’’288(¦A]]Ý /8aXŸ7ò?}ã ýIˆ&‚ ÒÆkôž¡A€Ð`0 4 4ˆ_L&ó§ïfo³°r±²qÛb†àù½³›­½‹…Õs&2/^GGG~~¾´ûç –J³2B¿úªßƒcèœøŸ¾óŽÓ¤ê\<-m „ƒ¡¡Aĵ×kGíµG +©ÖÊÊÊb±XÒîKÚ±„D¨Zh úÓO×À¹  „ƒ¡¡AJ>µJ^n®TkÉÅ![Z"uBWuu œÛ€Ð @h0¤<%JI¶ÃÃÃ2Le(0µ·ú•W|0ŒÎgkþ§o½e] „BƒÁ€Ðš‚ÐØ}Bk™jjjªªª¤]«¼¼¼¡¡AýÔS‹1Ì›ÏsùŸ¾ðÂïÐe @h 4 ­ÍOÎâ)Zó¢Ö‰‰ÃÃÃÒ®%íÔÞH£££ÃÝÈ„BƒÁ€ÐZ™È­e’-Z†±„ÝÝÝg÷„Bƒ¡Á`@h@hš«§§'==]5ÝÜÜ,qvo@h 4 „Ö>„†\h-SQQQ[[›ô—¡ai§öFªªª’8»7 4 BBƒÔ\²eq455Q(i×*((?»7›[ècÎùsyþüs=þ§óç›C¯¡A€Ð`0 ´z"´ÐúùZã$qº@¡’ajo¤´´4ñ³{ƒ@€Ð @h0ZûŠÚ_ØÖÖPJ.@ÎÍÍŒŽ Cþù§¹ÇŽK©·þóÖ|öùʰºŸŸŸÊÖdVddä®.\¸ð7W¿ÿþ; 4 4 4 =áEíòò²Ž=„¼ÃËcÍš•È3g¾ùÛ¯¿®Y³JòÉDz­(Ϻ:: ÿïÿ¾‘mÝÙ³¿ûóÏ?d[÷¥—^”ùÃ~ñÅçšõaåéÙÿoïÞzÓ¾8ç%t×ëËXÕË©Úr»‹­}Óv9iZ×i•&õrR+-·m¥uS§ªÒ”-ÒÚ¤ÍÙGNÆØÆÆ€9Ÿb;>äì¤û©E Œ æùêQä:Æ`ø>¡ÄnùŠzûí_¶üžõÖOOŸþyWÆ¿xýõ÷ÈnÙ&Oø7ÞøIî2á¦àG¯½öᇕÐZBƒ„nóÏUiøÓUÞyçW×®]lí\Þ{ï-_–O›Ë¦Ïœù¨µÓ~ö÷Oâñ¡ÖNûæ›o´üņ¿°t×{˜[¶å+jqñVË_ìÙ³_^¾t¡›ãÜäéÓ?ë‘/6ܲ-Ÿöý÷ÿìYh -¡ABw -¡%´„–ÐZBƒ„–ÐòÁ™O¶vÚôD²åómù´ëkK3ù©ÖN{c>»²\hí´¿ýͯ[þb§3ãÝõÅæ–mùŠÚÚ¼×ò{çöìâÂÍ.:Œ wæÞ}÷÷=òņ[¶åÓþã³OÏû·„–Ð$t§9sæ£\6Ý™—­£æé;WÕO̶üÿ&zÊîSîZBKhÐZº¢$´„–Ð&¡ABwoB'“Ñ–ÿwOéï»ìJpE•ûËÑH¿ë¡™pc>+¡%´„ ÀAIh -¡AB ¡MBƒ„–ÐÚ$4Hh ¡%´„ -¡$´„–Ѐ„ÐZBKhÐHh“Ð ¡»+¡O}?Ü5¯W‚#ÇÔæIBKh º‹Ù=Ê×|dw=8rHm>$´„–Ð ¡=QæÉC «è@×’„–Ð$´Gyå#¡qIh“Ð ¡OdB{ˆwµHhR‡\EZBKhÐâ•„ÆU$¡MBƒ„îê„®ù]<¾»f$´„îœëGBKh Úƒ»ò‘иŠtåHh -¡AB !å#¡qù¦v&¡ABŸ°„®œt?:ÄõãŠ:ö?‚$´„–Ð ¡8 -¡%4Hh$´IhÐ@B›„ -¡$´„–Ð ¡%4€„–ÐÐZBKh  m$´„Ð&¡ABKh m$´„¦4ü¡xõ?àX~¦^Í3mÿ%ñó%´„–Ð ¡%4íL¯ù±é‡¼Çrùý,l$´„–Ð ¡ñ¬ï!Ÿ>ÞgtÛV³•g$¡%´„–Ð$4^5qŒ/–ÐHh -¡ M÷&tåKvß>Uk•ïßóvõ6üüu^gRÝ®5?s3—¡æYÔ¹„ /Ò~—¤Î¹×¹Òê\¼:W{ÃsDB›„ -¡yÕÏBïW§ ŸŒ­óÁ »·þåiò²5¼0u.vu²6<Õž®yò&Ͻ™hø÷OKh“Ð ¡%4ݛР?í‘dduO¶öº”†_c whòµåÍänÍrnòšAB›„ -¡é„ÞÓrûlýçu6¡ë¼dâXº™ïÚÑðwk~]õ¯F!-¡MBƒ„–ÐtiB7ÿŽðYèý³Ä6$t“¯I ½X˜Êe¢×®œûò‹O?ùøoÀ‘w¨p· w®p“Ðff&¡O@Bom.§ÇóÙѵ•ùðŸKw37fGÓcýñÈEàH„;T¸[…;W¸‹­®ÌÍÍŒMŽ nm,Uþ8Œ&âÌ̬ÎÂ#Ex¼ÐÐk÷n¬¯ÑKùlrñn~e1ï_~Á«‘]Z˜™Í§Fâ—Ã.Üõ*7ŸKD£ñd<3=ÕÂcÄ@$2;“’ÐÐÁæý»ë«…Ìdt$q%½œˆ]I\¹žJöö§&Æ''†&'†§ÒÃÓ“ÑéÉX6Ëebùéx®(‘Ï–ä³eùÄL¾øÆL6üŸÉ†|)—‰f§‚ÈôTøTE™Éði‡2é¡©‰p.%ãé±p¦ýÁxªo"Õ~íK%¯%¯¥‚‘«£ÅKx5™¸šŒ_ AR»”ˆ]ŽÇ.Å£—â‘‹±ÈÅhäÛèð7Ñáo#Ã"Cß ^(9?4ðõÐÀù¡þ¯û¿èûªèúÿúi£Ò^¼æÃM0ØW¼!Â-nšáâmn¬p“E‡¾‰ nÄØpé¹Üp³Æ.†¿ë…ºt‹‡ÛýJ2qe´èj8ŠFñ¹>6z}<(9å£(=Þ_<¨ÆÂѳâÁ¹É¡ò8=Éf"á° gn:(¨á )º/ç`nfd.?2›OÎæŠò¥Ã;L'²Sñl&>=w¢L:Ï‘ôÄpzlhb|h|t •ì ÷¦dâz"\à‘ëÙ©Øúê­õBucÏÎŒÆF"áG¨‰ÍÏ¥ö¯òY?Ø^ ­=|¬Þ/Ù? 6K¶ž<¶‹žl?}ò äáÓ§Á£gE×ì<{²³< žï<{þ¼lçEðâyÉ‹ïkç^”WºþŸ‡í”< 7Pù–*ÞdÏŠŠ7b¸)K·iñÆ 7qé¶~”oýÇ[á`øþ¨Ø|\:N^3¥ã§t ­…#ª|týà y„ïä¥#¼âØ®8¤ïTÛE¥#¿ò ß|yï{œ‡÷¬¯Þ w:OËpxúX m@B›„@BKh$´„@B÷DBŸ:uªá{^…Ýs©~ Ýá ½§]Ûœ²Ê@Bwã³Ð•»çíêÆÞsÂêoæ•'¯yê|¼;ÐkBËIè®Hèýººúéëú'©ÿÎæ?‰—|=ÛÏçÏŸ—ÐøZè&Sv¿'«›|gËç[ÿœì~–ÐûÏ ÷<#}T ]ç…à@ý,¡{'¡ä…èÙ~öZèNþ¦vû½ä¸^ -¡€žíg/äèä„þÓ·ûžðv¥êVÿnÍ“Ô|ge—ßYÿ|%4Ð;¶·Vòù|ÈæÔèðîÛºsºº+ß³_?ïvlõïÖ?àÛSÁmëçð¶„> í©`€¶õsx[BŸ€„ mý¼ûZè­ -¡hØÏå×B‡~¸ððÁ¦²mÛvvž9>º±ŸË¯… ¼ðøÑCeÛ¶=}êYh€NLèÉåç™ë¼]Nè­ô“'•mÛvÿþ½å…G)@w å¼û*ŽÂéõõeÛ¶­®­Ü¹•+ÜÎ8º¨ŸC9ß½o ô]¸1?uïÞ‚²mÛ6·· …ùpµÏäFòÓ $@W$tdðB& 7“ŸX(ÌoÜ»û' º endstream endobj 785 0 obj << /Type /XObject /Subtype /Image /Width 961 /Height 700 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 13105 /Filter /FlateDecode >> stream xÚíÝœåÁÀá÷ŽÞADT°Š]±w{+Ö(6L슜5ì5ˆ-‚ +v 6¬€¨  é+û~3³wˆ&F?1°Àóü~î½·å¼Ùÿî;³{C¿Í†T9"ì:ä¨^ê‰]†ü1ËÓ»àç8 èß.Ú|ý ]‡´\èŸqÒÓ«†WÙ®Ã}Ç}yèÝÿºñüuÈŠUç6dãP4¤W2ªµáÿpyê ¹!¬â¬QyÇ…Câ_C7tÑüާÒýpO7jÔØŠ‡´þÉ%½¿ˆ~…»bë…þkÅϫֺiãjœ\÷åÉñ°ßÿ‡ngV®¨£V®:ïoq»Pßaµ—ý—§^|ºî=¼ÜÖ+-ç¯5Ú¼¿¬÷ãK¦-I‡q«üàðxuh¾õòKQÀüÛyiÀaëõCØ! ¸JUÀuŠrÍúóG ~8³¨f~çŸQ+›‚Ö¬Qõ}õr¨S´ÀµªnXH'K9 ž“?§v­ŸÜà'³ë¢ºU£juÃOî€5çŸQ;;­;ÿÆu«ÿpånWg~ÀÅõf9ŽŽ7æÏǵ~øMëý÷Õÿ£P­Æ²rAjU«ú¾fÿ´ µ¸áÿ4à:•§~ðën½³Ú?YU‹kþäW_à÷­VkkU¼à#Wï=ê„Ê€‹ÏQ1ûm~¸ÚÚ·§ý3y­™Ç?˜n†\1l¹'ÍéߦÆÍãâéÆÖ;7l÷ê¼ =k¬ñâì²WKâ+?/‹ßý+¹YÓQg4¤|Zßô'„ÏÏ|ÏÑiÀM{N‰Óm_@‡ÍâGÉi“ž£**Fõ¨6•›7êò6í?1Îùøèn°Ï#ßÇѤkóîoÏÍ}Ñ5]Ýݶ倹ï­¹ês³Êú'5®?ê Ó>Í ?-ü჊™7¦ëK‹GÆÄ²/.I†ÚìÊQqÌUéíª•¼Ÿù—,àÍ_žÇ>´Òo^ŽºÓ&d«Y‹Šþ!5êÀd¸Á+ÓãÈ+j„â!ýÓ"?¹BòåŸÃjWÞb“ž#ã䛤Wì7=~wW:ºmP‹Ç§Íz¾EÝ{&æ>Ù19ã«®{½UöÝõE¾:oÞãÍÓnü²<7úÎå’UcÔÑÇ~Z1ùåÒv|ÿÒ 7‘ܲ÷ô8å•MþWŸýfÅ·—]™N¡G=ΧŽÚ¹ªÙËÞ*›÷ÊÖé³ÑUcâÌ×ÒÑ¾ôË܇ Cã”®É݆4{xÊì—[ÕºcBž=ò‡½?3N}-]åŸ~|Í—g–¾³C¶åýÀ÷s_Ý4 ¸èü¯cé§§tÀ?l÷£.¹à‹òƒª.YwZÙͧ<‘û°Nh=cÞõ'ÞR>±Uò(—}òAÉó¹Ïž˜Òã†ÒIÒã†Ïì[2<>ðÍÐ®ÏÆ³ùÜãg]ñQ|3„â‡e.~%Kž÷¨øüìs¿œ–üzÙ=º­Ø¼€ßÅ&¡î7sî?õ†‘ñ®Ð²dîØ’}BûÒ¯{tzhzÜ·êú'ı]Ïþ ÞBÇøÍÅž‹¯&Ïú_˜ñDס±÷Èá%OÅW“êãSnï1%wÝ¼ç» IÖêÉSï:åαñ’މïÏzಱGòî‹Ï|õœiIÀ«Nù®ÇÉ÷•þíÏõ·ÅlÇÕùñˆ²màösJ>å8°8ôŽ«„Ð6¦y՜ѯòú[Ï™sÝ©ÏÇW«…¶¥3¯>ñîÜèf!<:eä›—¼ß0þš;rã“Wî“Ù½/ÿ:Þ9ñƒ.Ó’ª}\Þçô O&ÏRñƒòç.~+¾žÍÛ?ì|áwÓ’ÿCõOgÝuòM“çµúß|n|ýÔ˦N«Ünß3,©œpÔx7öëtë´™…êƒãã'w_þ§ˆŒí~ç¬9×—=zéèxpòpÏþn×—âg']wKù”¦!\ß½ì//•ÍMžœÞ3~øå½ÊË“¸â˜YWòò´4à.ñås/ú ^Pxy<Ó~€·'«c£oÇTÍ7úÆý’Ó{ãQáÕ¸K2:0]¤Ûâàd¢ñBü4™„\™®ãâ¥É<¤"vO^Â?ÉÕ;ÆGÓWó±q¥$à4âþqÛÐxò·“×Þ±IÀ«ÅžékÙì.…ð;qpJ6%k^1¹j ýBn³äôÄøÏª‰ò´‰ÉC]ç›òæ-¦ŽI74o’€ãß“3Kã-E¡èýØ0 ¸t‡ôn{„°z|9„«â Ù‹ü4à Éz³Ò´i!ìNž×vIÀ§ÄC“+Ü4ù·OJ¶È?#\;pÍáSÒˆºÆ“¼’ÿû¹¥snOÿg'V^P.}Õy.îQýã²¶é~ìô1y4>ŸÌ-†Ä×k¦3ûdÕÈ¥O·«Çxa²èßNK—éÖôå~V®FpErßÔ’LÙWýY².¬2=Y¶HïŠÐqæ)¿_ÀãäuNîÁŠÉSÜÆåU;±˜Bÿ9Þ”=“Ü.Š×¤S‘Iš$O['„¿ÄÒÒ5þîôá~-™ú¼ßOî¥îqÿPgÒØÙ~lpìÒ¢“ùlL¦ŧkû°o’ÿcÓ‰ý /àJø¥˜Î’ÂõY¶©ñ蠟+ý©ÕŠñÙìŒ7ârIÀÇ'£KãÙé$ÝrÓiԇ٦܃qÍÐhëìÇôŽë&g›Ö—ÄB»xQ:¼ Y%—¹CQam'ktÜ 4ß±n6Û¬(ª ¸M: ­ãc¡j¢­1[íßèØxF:jZöVp:÷×ON{%§›Æ·“ÑÊù=ÃSG$¯ÀíÒ­³j“F¥_—žûf¬®‰›¦Ã×’;®C|bõä¼0KòÑìdUÜ*dï¯MÏ]%÷jhRöP/¾öêÉJ[ѼòkÞ3é—VZnïÉ6z‡Ï«–œ>ò·Ät“á´xLð¼tcalL7_ŽBÓíÒ™v2\> øÕtxSS‡üÓB÷$àÖñÃÍ~çX¥Só®MSÜ+=ïáÿð½¹ôù´èÈ­ÂéõóëÜŸ’€“%ÛÄ—’ÓFéé=ñðdxuL§ÄÇ$kb×K¯Û1ž•œnþmo ų³E˦Ѓæu¬³Ì¢›B]ž½(¿;ç/X9ÿØ&vŽW†üõmpºuÔ9»+öŒç%7”^4 ÖÍfrm’ÓUÿxy¿ñ1n”üLþéñOጘÍÌ÷MŸÓ¯qì{×*¨€ÇÇô¹x½c®í?-ÆjU;±ªµ;í®JãSógÐUïb_³¥ðåÔä~›™ß}Ô${öÛ(yäïO_§âÀôìo¾ÌÊ?þ†A³â·ùÕ&ñxlœ<Ïg»ƒnNnðfŒ_¾P«þ™ésñ-qÃÊ€O‹C²sNòÿ|erµÚsºþ-®>Tµœï;{>+ûÚ'yê}4™…„pEÜ-9=*—<,½è³lùË–oÍ#®zaRLž±ÖO_ÑB¸8î.ÏßÇ¥Ó±{büæ¦]ªÿŽ/0…îWÏ·ùï<¾jïÓœüÞÇ}“yáÙj»N¼7=#öO»-²‘ƒ²édúÜ`ßs{ÈÅs“€KÓgÙÕ’u~íxG¶·.—¼ßì8ë‰c—_2ž7»ÞŸólSµk3ü±ríÉÖ`¾ÑÎÙ.Ç|ÀCóת ¸ø¶ò8ó¥®/f÷ ÙüìOÉMwÍ? é ¼g¯d%øvÛ ¸Qü>„úýbœôäÙŸÍx‹/cü¢gçù_÷žà ²¯ï窇¯¿ÊÜp~À×e¿\ðŠo%ÏXþ}pÇüÆIãd©ü™ÉÔ¥öIÏ΋qÀ ¿}IšÎ{)ÔšüV¨ øoñãücùJgÅ­wÛ·‹׬ÚÕþ«¶_NÏÿBᎸeðrÙƒÜ~~ÀÙsÐgÓª®Ù;§={á[YÀéœ5™°îšÜt£üÏLW‰ƒLž¿Øèp¿ì÷K–ïßž>¢rÐ8·'—ݘ|fðÝ?¼ZðþUwœËßëÑ# 8[ÊU“€w¨|v›žî…^ïê1–ž¾D<|ÖßCjΞÓv\a˘ÿPʽɣü‹ŸûmZ=}‘i»`À‡%[Ò!ݯ©ú®÷ÄÏ (àNñ‘ôþ¶u“ÙÒˆX3pÍ ³OHæœäçÙÖðqé—æ›×뚟ͅï“éÇ¿ÜíÇ¿/k•¾6ŒûQÀ½böÄ~{åûÀŽ|3»ÝoÕ§båC²mí,àãx¿½u¼äï3jÔœuçùï1µ·do±l×tŸÊ”Ÿ‹kürÀÿˆnTœnIµX0àÓÒýôQοTsŸGò“ß=à¿g[)á²ÿð'3òÛ8k†ÉùÏ´/ùO¯ú£€7)µgýô °  ¸q6ƒ uªÞÞðÒÙí >à‡c¶O¾ä•v•ï#Nû®VÖÜ-æHgEµ¿›U÷—~9®:2n³`ÀkÆÓáÉŠÕ~à±éðÓŠZp³ïÓOBŒŸ“.e“òt6=iH:ÉžÁŽ©|RaûüC{{ÜïÀ|[§;~)àºåÙ4zƒ8åGŸžmkV–|λ­³éÛ‹ ±({ÄÓzϨ_ð–ù½­ÞN3ûôÕwú%‰Ž{ù“ùï}–~V”=0—®–K7ØÃrÓÇÿrÀgÓè:S’_y€·Í?½÷NV‰ý!]¦±Sþ'wH_)Cx½*àmâߪ.z8›7õUP‘m×>÷üå€ÏÍïɸ3–,pøâÛtãfŸ$à¦ýîJÏíöÃû…ðÆåÃ×Ißî™Ñ¤ò’óãõÕBË·sÛ&Ûý7TµzÆ+Â/ühº ]ãþÛ-pÑ3¹#CØm^ðš¹Ñk&O–åoÂ+ð)›l²åNŒ·%ß K·þ½c³d¥ŸÔ¼Æº1ÝI¹ÅÔXµé–ý!} flíâJN’2¯ý/\<³bÝVçü(à•¾³A(º2Ý }Z|¤I(>'.Ì~ùâ¯M¿3TœL7Ï) ûgo€\UZñ—þsó×÷d•Lþ_­‡•®zÆ’¢Ð oº®ÿbÀ¯¥{>ê$ë/pAóö áÐ\²Jl?Y%y”ã¿_Àý6©´|h4lÊ6éªWðñ‰:•0Ù´âÍf¡Þ I‰;Ç7W áØŠ7kürÀ'ŧ“ÛŸK÷/ðñ®a•áé+ðˆ¸_qhòYùrKÄûÀ放÷uœµ[Õ%µß½“ËŸl>¿49^~EÀ[•Æáýg ¿)»`À¡þ‡qøå¦S»‹c:2ŽÝªΛܵ([isï½YúzŸdC!ÜãcÉ=ç¥7~1ºj7d›ÏrŸ}§m—<Û}¿<·4]~q }Q,ôAÅ“/$›Ž 6YúΨiÏ$×Ëß™.ÔÎ’Kcú{W¼Ú»qÌÛeñ‘êÙÎŒ¸qòÇøÃ;ï^Œ_¿ËNL&âØAÓâÅáW¼WEüøµ¹ôŒû,phöUî“OÊû¤«Ä 1~4&~¹þïð|Évhë‰åï>÷±ª€kŽ‹ñØÊ+þ¥tÆ›³âsÉâv)›ýÖÈ8¸Qøå€|'>?~úŹ>? 8ÜÇ¿9«ßÔ$àíçÆ T”Ü'9V+9dþx½’äiíììW\ë⇺hÁ=)û÷xºû¦ùÝÝŸ¸({Seß’¦éܱ$}ŒÖ,Ù®ê¦KªU]ئÇÓÿ<¹V³’ƒC½üÿf³’tOýCzݱßJ%éJ¶õõOÞwæJp?P’èrâž•¬Ûî¶gnþcÑz%;„P;…jÇßÿä•›‡ƒKšUÝ v§ž}/Éî¡:§ü³ÏÉÙ:§«I8²$}Û³¤yX©$û€uIö®³ÒIÚÞw=Ó}ï°MÉFaã’ìSJ‡•¤ŸˆjÙéÑ¿o°cI2á©vøýn:¦ÆB-K‹’Ê]T[”¤ûªsó37ç7Š»tIžŠ.¾hkvÓSÿX7?ºññs³¹CI:ul_²FrºQIÛºfÏ6§ž?ÿWÞä¦gnïXmõ’=C³’lÞ%é]ÐøˆnÞeÍl•hóÓ=Oýý^¯V)™/]qV8þ¡ë¶Ü´$y•/IßÌlÓåævU×Üè²'î:4m|YßëöI—d’ô5bù’?d[†Ç¤w£tC¨¤MÕ….zä¡‹Z„³’©ùŸ³¥lTr@¶ÛçÚ>§Õ<'}£¥å¥}ú^²YXö49Á}ÀRd“í–­åÝëyjWÓ}ðÛþزµ¼]ÿ¶”-PËí—‚…(šQKŠ¿MóÉÕ–©å}zÿ¥l:ݺ,Ä*#•ø[}´å2µ¸V\ʨױKÁB´^ˆ¿U·‹–¥¥]ý›¥m‰>[)Xˆ“oâoµWÿeii;<º”-PÃiÅKëÈBü­êͨ» -íµç-e Ô~ÀÒ°Oí%ÄßlàËоºóR¶@ç_³4,ŰÖ:üÍ.¾vÙYÖjÓ,eKôè!KÃÃ2³š³­‡,;˺ѧKÛ^u)Xˆ5‡Ép!žþ¦4[f–õø»—²ZiÜÒ°{=%Ã…ðø—™E½ý”¥lþðäÒ°gvSáB8õŸËÌ¢~°´ý]÷/ KqÓÉ*\m¾^V–´ÎŒKÙ½¸ûÒ°/´Wá½Ö2² Û½µ”-PÑäå–†Å¹ŠÆÝ§,# zÖ KÙµ±4,E­E"\ËÌŸ>xäR¶@G=°4,Åzip¡,3RøåÚKÙÝpæÒ°<¢Á…³ŒüIaÓÉKÛTí­m–†¥8çJ þ Xh  ,`°€AÀ€€AÀ ,`0 `°€AÀ  ,`°€AÀ€€AÀ  ,`0 `°€AÀ ,`0XÀ `ƒ€ƒ€ 00XÀ `@À `ƒ€  0XÀPH?9ä¤}ÿL¿ùÃÛ†T0rÀ[Ĺ_-xÆW_Ͼ«  9à[Ë.» –È€ëLÔ2×wþ·µ¸Ž€¡Ð>2^–·ÌÆ+ßöuų€ëušv¸€¡°~¥bÕp\¼,Öz«â¶“þU:' ¸è‘Øû¤[ʧ  9àV¹B¨?ó»4Ôkb§äôʘüçØ- …ðåñOÉi¯xHrúî„ÉiÃÙIÀ}rË'Ã⑆¸xtÅÉ;v¼1¾BµÙodç Kþl|þ-aC¼çüÐ&4ŽÏeç Jž4"Þ+`(à€Ї¯“껇ð͇Ùy_$÷Ÿ™ Ÿ0nÀMç.Ϋç&× —®‘îÖ*KîwJ7‡'  7à3ãUó?sulØ­ü•Æ¡ÁKé^è ¦²J¨q½ÐPÀ×­‡pJœöƤ?J?ȱwùœ7Ç~;PÀP°7(9«jX«k×ú!lrÉcç7=¦szÆZg?véêû•  õØßƒ€ƒ€ 0,-_ÿèÕ'´kQ$`X^·ÃEw¿>nÖÇ]sâÎ-‹ KàºAÛÞýÚ¸Ù÷½æ¤W)0,ÛÀ ÚzaÏׯÎþäñü¹ýªEîzX’®T“C/èùêØ9îzXD_1d•ªáÙCÚüìÕVXõÿѱ»QÀ÷Å5«†Ýã&?w­]§ìåm$(¼€[o]ëW|n0ð6p®½À»Bõ~põº†Åð&£.ÏNŸOsüôþÐ}Ôj!]8¸â›‹zdŸøÊ¼©l;êèd¸öóÓâ7Ýj‡ð¯Éqü¨Fa÷·KsßÜÙHÀ°¸.þ~Xrz^Ì5 a§Ø9¿ \ûwºbÆ´4à#âG/=!žÂÖ3ËŸ8åîÜ;5Â1/ÆûKjo>oÄßO}4¾%`XlSè^qÕ^šžõùš¸zðö¹g‹“^sIÀ«Íþ0™%7ÿ> ¸Ú3×K®ÿ—ø—Ê)ôÕq‹äû§'¬.`X\O µç\]vKŸÉï…¾8;\]x& øÈøÇtxqð6ñöì weC*>/Þ±b2ݶ ‹/àús »ÅmßV‹%ù€Š+¤—ü= øºü±öI>&~úxjÆôÊ€[¹·/XWÀ°øý¦T»zzõ+b‹Nqã|Àcíü®æMBï¸Z:Ü) ø¼8´^ʽÐ;¿ZãCõ ‹-à“ãÖï=vŽG?3²òƒ7ÆôàÏáÚ$à«ò³éc’€;Ä þý}àþüi<]À°Øn»Wtµf?6§{eÀÓ}Ρè½$à=ã­éu^HÎY7¾–ýÁoöL÷díB··e/Îw [ÀáݹqÞÏe/¶iÀM¿š°YÆôm¤·ÊþÒ°ÙßcštŸxiµÐðéô@ï‡Ç.µŠ®7Ô 5nŒG _À—ÄñE!œ¿¯VõYèu§–¿ûå¬'Ò€[¼sqâ}ñÄVǾ5/ö«†e1®Ûè³8÷í™ñá:†Åp«’#’Ó5JÒ8X²\rºÒI_»éV%+¦‡sowæaÍÎŒ‡¦ú8üÆgnÝ7»ÍNWõhjÿùîg®;Ä^hXœÿ7uÝ(ýr{ÜÔAí`‰ ¸M²R¨v@ÅÐbÃpèã§Sã-V–À€Ã×=ú›8.4,‘;°;0XÀ `ƒ€ƒ€ 00XÀ `@À `ƒ€  0XÀ `@À `ƒ€ƒ€ 00XÀ `ƒ€AÀ  ,`°€AÀ€€AÀ ,`0 `°€AÀ  ,`°€AÀ€€AÀ iÀkw¸ìä­êüäÌâ¶0|ÀµïÍ®ùݶ?9;>ûs·Ø÷JCü|ì³ë:›Ÿ5qÞn¿2àæ±·€¡0n‘V-ýºS|XÀ°¤¼_¼=?x¶_Qºé»ïÅWVÿ‡€[{Í_+'×uw»¼Ë.!´>#¾Ýq=C!Ü´bê^Õx=Ë'Æ/6« ¸ÃÔ8e^ì™îá:xFü¾<ö«qLús;  b¸kŒãoعFþ›þå—®¶ÿù€7(û¬CÍšWÄ¿…°öÜowÍ{ÇKL¡¡€ÞÈÅ8ý±¶ÉpÿØ==ç¯ñÜ|ÀÏÆuÒïGÌmºÄC’Q³áw  )àdâ|ê“Óãì#B¸2ž¶Ibÿø`>à)cÓo7¹/îúVT· 1àD³f—6 WÝîí,àåçÿœcÃÈqöBCÁü'jæÝ“9òýñ¸v™-²€ÄOòß¶k>Ÿ&`(¸€Ÿ‹ûæÄýÃÅñðtØ`Vù)ô·“‹Óï7Ú®A²9¼|:ìuo5CÁ|X½Uúu½‘³ë„M*>HwG߯Ê|K<-Ýsõ}Åá´Ø5n†:ñICa\ô`,}í¦«Ÿš3¯còÝMñÝÃÿpgîëæù€—›Pqý^§¾{„Pý“Šëö¼xdÙ!LžÕmC!ª64¹âì7vÈr>cbŒ¹Ç[U}cå¾e1Nº0ýŒVã{Kcü|ÏdtæŒx§€¡ ö÷À `ƒ€ƒ€  þýýã¾Nm« –Ì€[w¼ý£é®ÚÃ’9…n¸k—~“¿¸ïÔM« [ÀÍX§jØè€ þ·-ZçØ;>ž1ÿ¥Ø]‹:àÝâeUÃMò×ùؽán—<;ùËû“—bw=,ê€Û>wÔ¯øþ¸hÝãîüd†»ã6ðo8?w×Ãÿ<à?¦Gš {wLä¾YÇ&+wÜ$ý~ãóÏÛ¼2à5O½|ÿ¢ö©:ÄûvÁÝ¡`î—[>„êÓã’ñKÓjfÛÀÅÏÆÙ“r½²€oŽeãã#_ N†‡NIñ~wpLúsØÀ'Ç?…°]Œ×‡PÞ¿ò;±®ŽW×(:lnðq±wƒ°íØ88=ÄûÐ5kþ-^á vP(·ˆ½Bè:ûýC80vÈ®[1$ŒÊ$àoÇÔÎŽš•Ü/ˆ÷Ïç60HÀáÝïBxý…Äæá®¹ ²€·È¿•´upóx[vÌÒ$àÉU‡xo'`(”€»Æ ”·Wücø¶_þ}àcãÉé«$ï/Ì®4rph:ÿ''`(”€ÛƳ÷‹›Õ+½³m<)ðþñ¬ô‚Õ“€·Ý²+Mê/pˆwC¾y¾Çäâ0pd—Šó·ˆ·¦ç·On–{9¶J·ÇL©–?Ä{CCÁ|óìá†Ð%ŽTõQʯfµH†O§;±‰{„PÜ' øæxzþï­Ø &à=bì”çT¼Ù¬¯Û»Ïô4à6ãËî»rð¤øVËÏâýúàÀîP0לסÚÔ¸Öü?fØkp.~¹CöAŽUúŒšÐw½øâ‡x/v`w(œ€ÿ£[å¿6Èûä[ßx­Ê¿÷-^¡XÀP¨WùzhúAŽ¿Å3 –¼€{Äþ§qwÙ›  K^À5»ÏŠñ»9*%,;¬,0XÀ `ƒ€ƒ€ 00XÀ `@À `ƒ€  0Ø] ,`0 `°€AÀ  ,`°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  ,`°€AÀÀ/\­q½ìkQãÆÅÙ ^ãjÕ×þáâÚ†‚ ¸IÅÙ×Ýb<8 +k´[¼¬êâMb÷Ÿ½éÊG óú¸bú¥[iÅ]é׿ñõ°ËÜK~EÀM¦÷0,怯ʿò~úê{cÒ¯‡ÆK¼ô¿Ü< wÀ»f‰®»^7J7Å­BíÕ§—o½Uͪ€×ߥIXy¥lcy½7K·‹ëmŸZ½¡€a±\{Î;Éé‰qëöñ¼dðñäj!¿ |þ¤'žž|ä˜ËÏølp2\óíäÇM?&„cÒŸÛYÀ°X/•Õ á±ÉÕjÎìBÓÜÃ!p‡8pûoiÀ[κ纖U$78ã–-ÚõG‡æÆWÚµ0,Þ€/ˆíCõi…ðtiÃp@àuãµU[Èõs÷§Ãú¹$à1²ÛÜ‘l)  àâ)/„+c2^;^ÞòÏH/l¼}ìš]qÌàP/–OMÍŽ‡  !àðØôâ÷>I#_hXq}eÀ‡ÇS³k$ï¯È®7npX>Žêš·¥€¡ î·©è–nºG2‹Î¼Nü{~~Ý=¬J‡ÒmàÉcò»®‹¼ …p›Ø'ͫWpñôÑ黽=ÒXïÎ[-^’üTÜ-݉õá¼M’ 0,ö€ÃèòÙÙŸ,4,+%TމO·ª×©4 x÷ŠÑÇïÞ­<¾Âze㬻þ]±rµYcök-`XÜߟË^Í>ËQùAŽ®¥1WvZöA޽ÆÇ8ñЬÚ=GÇ\Œ/4I†÷Çx½€aq¼J»VùÁZí–ËþL¡ÝêÙFïaǵªÞ.ÿ"»úzÅËÇdz?7ÜùÌÛfçUߢÆÅð/yáÖôôˆXâïaÉ ø‰xÕú-Ž™4¾•€aÉ x¥÷ÓŸ1fƒ `Xòa»:¶qHXBvL,0P€ –Ü€Ôe‹bÃ’p­]¯ýäûŽZAÀPˆ×j\ã—®²Ê Ly  (àâ“ë…ð×xȯ¸jõí¯xoÊ£'®"`(”€ïŒBè<÷À_yõŽzàûO»íVKÀP?žüÿ{ÍޢˠéOŸ¶¦€áw x•f¡Ù«e}­¸RØp³t°üŽ»g‡j«.VÜ£ê±56Üwílr³–aímŠW{!n¸rh¸zÝô¬º[ìµjå?zÖvÿ¶uÿKÄMíùí7í[Ï]¿CÀãû^“|ºMÒÖo_メþõeÉ9O¥ÿ0ÒÌnJ†Ÿd$¸Ïød8lÓôPY_cÿ¹éÍ*·;ÍN¾éŸnân;2Í=«è¿¾otî+3Üõð{à³ã“×=eƨ:¡æ¤±G¯{Ðûq—_šM×w×ÃïpìåùŸIÀñœdØ.Ž’xApv(»âõ¡ÆçÓ—ä­ã#IÀ±Ûümà4à•f|X3ûGXº†Í²#ÞmÞï>J ‹$à±Ùtwô4àu²CÔí‘?Ôdÿ$à/³ÏCNÖ÷eÝšš¼ÓþS<7ý¾iÙ«¡AÙ´óÖòYhXd¿šõ4`^ðF.}§wÌÿyþ¸±IÀÏfÃw§çfn/‹M“€[þ(àKâ¬ì²Ü·!™l½râ ø…¬§s«‡7æ…ì=ÊïO;! ¸o6|ojÑñ¥Ê·§7ùQÀWÇå/º8}»÷„gæÅÒc ‹$àYOß •_Ÿ ×Í}”üA6…žÔ?l‘n$§g§{¡ðqñÌù—Õª‘̾O‰# ‹$à¸y’Ó.ñ–ª€÷‹Ù¿Xvj¼. 8·^2Ü?þ#Ô›ü}z\ʵç|U½*àÞ±Y>àõ*ÞKßÞ'>Ž)ï’^ôÕ”"Ã" xø–µÚ3oíª€CÿÜ+6óê;Ÿ¹eåtx¥ýÏ><Ûý¼~»üŸ®zÀÞÕ[¶k–ýc£‡spãì̆ÿµã†EöBÃ" ø¿T6óÇÄ  ü8àÍ6ú/•m±€¡v\h°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  ,`°€AÀ€€AÀ ,`0 `°€AÀƒ€ 00XÀ `ƒ€ƒ€  0XÀ `@À `ƒ€  00XÀ `ƒ€ƒ€ 00XÀ `@À `ƒ€  0Ø] ,`0 `°€AÀ  ,`°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  –Å€›öøKöµE­³Á™=š´é±WÕÅ-zðó·m `X¼}?«zúõ”ÏO¿VŸ16ì/«ºx“ØýçnYãÚû ‹y Ý'n™~ybÌÄé×-ã=aõówø7½ ‹9àãYÉiÍ÷<\Ö0œXðRCAÜ*>šœî<)” žÊ­Öïu`ºù{æsÏþu³,à&'<üÚuË]qIzý}îüÔEuBØ®wÙkwÃb 8|5.9¹:·ÂjñÎЧ¼òÛÀM¾¨x©ïôÓ€k¼ßê3nĸÁÉ{ÄògT|Ú2ì1(Npˆ€añ|g\+„Þ aØèÚÆ«*~.îÂZcÓ€¯“ ßŽIÀûǾ+‡°GîaSh(ˆ€‹ÃÊñÊôÅuƒpfÜ9pÓøtzaç$ࢩï¥Ã­Ò€ß-kœŽÅV†B¸Yî®p\Ü)„½âŸÃc3kæn/ªÚ‰Õ:öÈ®8kp(šý}çÔóqOC!† M¯Bݹw}Ÿ¾î¦ŸOL/[1 xŸxa~cypòm•“ ðµ¹&“žH/~¶N<£2àóŸëh“Ü6Þœ}äcÎàP£ü]o#AA¼gì;¥ƒ³sm*n”Kß] ’€ëÏú´(nŸn>§QzöÑ­V0@Àõ挭ÒÁ†qäס2àðJ®} ?I÷Bÿ#^ZV|? ¸sìY-„Kg4 Õ+^0,ö€ÃÀ8"?ø6Þ1?àå¿,½÷šŸ¦7¿~cæ[ñªŒï_ýðôÜQÉÕ¾‰ï$`XÜŸ0àÜü Ë€ÝÒ/›869]ãš¡ßüs姦·pJ¯‡ÎlŸK†ÅgœòíÛ§WÛá™Ï.0,î€É^Ù{­ØÓßÃ’ðÐY+&§}㟠K^À'Ä)ß92Þô³WøÓߎ»wÀá/Žõà‰E?{ùþçÿw=,ö€  ,`°€AÀÀ xË›õ­öÿ¬¯AÕÉ!=Z _À-'ÇïÞøÿµ×òÙôÑG KNnŽ› _À‡Åÿ¿íÓ€?š”œì~~sÃâ øŒØa!¶ ‹ÓÝoÇWz%Óà-{¾ùR·t{vÕ^Û÷Ú ­èUçÈ>¯^±BØë¾wî[';PÎU¿ÿ̵+‡pòkñõ^mzMž×ë¬pT¯ÕÂò½ö^ï†7žè”} rÝ û?}ÚJ½ö0,ýGÄOlÎÏM|ô¹¹SöL6Ù'sk^ÿYþÚ¸øê‰ÃÞ“– a« eo?:9N])t‡ ØxÀ̲϶׈NžþÒôx’å:Óg?ùì¼×ã†E1…>3Âeï­¦SÆ6LŽg7Ø<\§¯j¾gïB÷xB/ÆmC¨~O<çGSè|Àñ‚:¡Ù¨Üê¡îЩ'¯æ³ ‹0àûã6iTçÅÓ“€?M‡—¦tOθ=9Ý#^ªu:%dÃnÿ!àéìù†Ø>´Ù¿’t•€aüþì,ªÍãmIÀä>:ýsÞ˜RgÓx]zV¶ÇÞüA¼å?üdväʸ_òßNépwÃ" xê—YT-â3IÀ×çÞ9 8=tÛ4à5«ˆqèSÿ1àìˆ:gÅýÃq½t¸‘€a(Ûr× aÀ»Åçj‡Ðò›Ykþ\Àeÿ&ájßÄ¥/°ç%ã—Ëký[ÀÕ†ÍHnÖn¶€aþ‡u»mâìöáçnòmîù î0°ôùÖ³ßÜ(¹É—wý4à°õ„Š!U¼Ï0,Š€‹ý°—&½iËd°fvÀöp쀒Ó]”÷y@§Öé;nòÀŽá¶’×ÝN¯}¼KXá_Ÿ<:X+¬”?úû¡¶M?ÈÕå±;ö>:þYÀ°(þ]­¸s“ôË9鋱€a ¸M|9ý‡Á'Ìm,`Xâ.z*~qëãÓæí㯑`É 8Ô;÷݉ïß´£?'„%1`  0XÀ `@À `ƒ€ƒ€ 00XÀ `ƒ€ƒ€  0XÀ `@À `ƒ€  ,`0 `°€AÀ  ,`°€AÀ€€AÀ  ,`0 `°€AÀ ,`0 `°€AÀ€€AÀ  ,`°» 0XÀ `@À `ƒ€  00XÀ `ƒ€ƒ€ 00XÀ `@À `ƒ€  0XÀ `@À `ƒ€ƒ€ 00XÀ `@À `ƒ€  0XÀ `@À `ƒ€ƒ€ 00XÀ `ƒ€ƒ€  0XÀ `@À `ƒ€  ,`0 `°€AÀ  ,`°€AÀ€€AÀÀ²éÿsÁÉ— endstream endobj 790 0 obj << /Length 2277 /Filter /FlateDecode >> stream xÚukã¸íûüŠÀŸ÷Ï8Ï“™¾˜0ŠÙàü ªòE€'y£‰+*q½¿£#Ø‘ÏMÛ24Y;Ø£q4MÛæ«9ü†|òãóïI™Fq’‚ôx¥ª*ÊT¬V*K£¬HVY•Gqž¯vLJ_ŽÉê'ûð'Ž;/žØ7U¤”¸ñŸé2ŸÒdÃ'Bàƒ¾àE"t>4»ƒ-¸­1ð±«ûFÔíq†VÞµÍî+O­.M÷fù`½zЏ´Rqx]"0´™ÀRqâH?™Þ5f˜¯búoˆ‘xCˆ¢B”æáU÷qW&ÚFÖÍ ~1$O¹ ëÛÍ“4ã €ÈcàhAØü›ó`>Ùž¦œîvæ½¥°¯Vôù4 [½5-c^¼vWä¡[Eh{åñÜ7nÑÌä—i‡Áïø„š§tÇÿ7ùZï.U|wi,V`k/ tæâ„f™WD#l'3 Ó®|°M†Áù<üQÊ0žN¶wÃbtÒžI–‘^8þ¬Ÿ1Íßž$Ê2²Î¡|Š;Ê2¶µm[ÊÚ;ÄÓpþ p ©9¢.šRÈ;ÁpˆnÓŒj†÷ÓH™‡Ak÷‚E8€&oˆ–л¡É‡€°5îL!ˆ¤¶é ) No€@ðZw86;æÂ´¶¾c*ÂàØtÁb¥¤bº…šâ@Ÿ„”Ká£|´nÐÑEÃ8œÌýš‘ž¶lŽãQñ>›¶¹a_u;šEƒÑC›Ç·ü‹§ºÏ½Š9ååMèšµsœf Œ[ ¶aZkÜÀXŽ,@™š²‚£ÞŽ£5Æ3c @¤ó{ìå-š>à20d#¦a(/XxØ>ç%I?Ãe‚Ô[F’¹F²¢’m‘( Ï… C0Š4Œ8­G þȹÉdVÌÅ´ûzZÓ-ÝkP’„pàí#If7d1dµ­}8'U~”¼´`ÏÁoº@ViŒ®yB'€Á¶Õ»¯ÂÁ À”7x:K ŠC ˆÃþÊŽsÔ_§—wæM”ñ1–,šc*kë¥çý™A2îéÉPaLJ›!pj­x Î4/cç2XkýŠȧ"†±|C¦{âšaLò8’±ICÓÖò*-ÖYLংnÍS¾ÜÒ¿†9¾Z€¶²ë:¡Yå]vÀ@VÎY‘¤ÑeËD«Û›7ñtsqB[ƒËwß%ÃhÜËÓðë(NYó½}m¥÷Sjåï Îž PKŠrjˆ®µÓ q‹Vùœ]Mñ…ðgË‹¹ÏªÐô=C/úàÆÉ"©,Bƒ[aB.˜iЯßÄb*Ÿ¨‡™G,êÌà"@SùýífUB _dXÃø$‹ÊxÃâ^Q‡q*z0X!6É„.ÚUQY¤¼:®XfHD ™HÈ¢¸8–dTå$#.g2dk`¸)±¤ì‘Ü$dßJH§Ål”»Æ6O’ù³Ÿ¡cÅI?8ž¸ƒ<1XóVøÇn`Ú-úì‹À…9å‚2q^IåEǰëáz„ª¯§š ¦çú¿§7wêad.…gJªåLM˜Ì’¶é{åAÔ,K¯ð°R€šëÓ{ñ¥ãñVa¯YÂ^ôp~ô»?Á~‹u#t{˜´ ©1Ó¢ð)Á*”ƒˆ§ô _O†9ÿ9Ù¦5ÔÏ@”|^Í%»éÅAôÝÇ0’oiBÝrû’’‰³Ê×ÜX"@‰)•”·{&õMæë› ½ç*6š oÀÒW‡¸L[k¿Nß' ‘$Ø©Tœï!5ˬξÿ?¿û•O3¨` Žb%R•¼›»Ÿþ¥ûIÎ endstream endobj 787 0 obj << /Type /XObject /Subtype /Image /Width 853 /Height 601 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 50554 /Filter /FlateDecode >> stream xÚìXÓZ€UpëÅu½îuÝ[TPdª¸·×=@Ü{+*â@EÜ[¯{ï‰\Ü *¨ ¢ìÝ–îÝ2þ/ ÔÒ‘&mŠE“ç}ú¤i𓤧ç¼ùÎ9I² •ÎËæñ¸yy¹ùùùðš››CAa"òLB.aòJ:?mÊÇI¾Áüª“¡'ß ÿ©S ÿ7P€˜ £j ÓV¾…Ž'ñÙ,—– î'—Ià#©˜/²E…ùÁÖ‰°bÃáƒH®&ýð´ Æ†¯ŠAejH1áD.A¢yr âF†ƒEîäXäªA袀¥ãÐT#ŒË(çÑa ú ꢧû<þØ¿ZÁ/K ' 9G-;éÌuD².þÖߪèPŠ`ØYQà(1$ê`–HZ 1}EŸX;*%*Y¥´¡Õ„ZuƒU1Qµ¶Y?"dx(dàoËbÒ…"Ì‹)ñû%ôODé_‰Õ?‚(Ác€ÄõOªWÿÌÒsI Z’ì2÷Ë!ê~ÚôOF‚þú;PúgVú'¢ôï’@(jD"SHܯXÍE\-3-žÃÊ,ÈTÔÏaŒš›þ‘`€èq”c€B"(*iÀŸf€dH éZÆ kL$Úºg®î÷B""î'4Üý$d»ŸAúWŒîW úg¦u(bVcøÎ@þGÛ‚‹¡Í²‡•õ1òõÓ{÷ï^¼zùß»·/„<¾ñþ%ƒžJI è,qÀb€$H`Ùý6MÐ{ ³cÙâ÷»ú3“–ߥ¤ˆYiŒ‘;+ôÔ±BÍšÝÊ—oüð‰€¼j¬U­ÚðêÕGEG}ÅT7%é[Ðýk{vmÙ°nÕŠeó—-™»|é<¯å;wl¼sëÂ÷oŸaÊñ~‘ö_áOÑ¿bþøS%°¸Tð'*_1ˆå~% ô§_ÿ„T˯!2 Ĭ4†œAÇùêúÔÆfn•*CN¿ ú'ä³¼‚å>±A>sè+«…Ö_ÇH2vtÔûsg{®^¼+`sHÈÃØ¯Q FfB|ì‹çOŽÚ½jÅ‚O싌x%¦: P@ò PJ v ”’$&Z—“­Éûw¯×y¯8rxwb| äOJó~¹`‰h.éhÒ0 ÞH (7Ù­„H¾§ñ/È‹Ëú ?ý~-÷+é;¿]è1+!qg°õÏÛëaíÚ»æÎ¹Çãü¦ÅŒ€Çbegwï~·^½ë7®ÇÐ>òäþÊå ÂÞ>c³hºøµjÅÂà ›,f–®­•RLºÞj]_sM­ ±Á³¦Öuð'ñkõ58äw2@‡ @¼Á@ÂHž ’r·I’n´e°ï·>Ã}DÅϤA¿_Òý ñaF;%9ôGŠ©1¤Qć0µÝ@—àÙlý;~,ª^½Û#†?çrÌf± YÛ·}oÜøýøq1|.–¦¦¦Äl÷;vd/3;›[7.îØ¾)öË']²JHÿôú¹úg€’d€p®ØB>G(à…€Ë(^ùòÊåÂåq9È+*ó\.›Ëeqy“ËcrxÙ0ÆW¾ðtÁT…]€úòëãM‘‰.?g†Ë7žvØ"Їo¸êÆÂCáB(Ô†H±*|céD ‰6ÄxêG¤D Ü›c#ÒŽÊhLÚ?Ô½)¡VDÚèD¨‚b‰¯@ D„ ~BˆÔàiÀ×µAáÔs±HE²6÷BU8Ú(øT$À†S€Ð0ôº;.!T¡x¢ˆ¤ÆFnß¾FTcÈ=¢Þ‚fx{{*w`ÕÊå¥K—ŽŽŠÄ³3Øú÷_pF“&Ÿ{õŠå°Ùhã/º<î³m[FË–Ìè(¬ß®ž¢>X¹|þ—è÷tz6™é‰7¬yûæ9\Fò+]3Zßj®¬¹¾®í¦šÇž×µDeã?þ …×tp©ˆ†ÅøeK*”*bMrYaIíÉ‘¡•<ÅÍÀÑáŠäšþ¹Eo°V\O.È¥(9Ï.1«£SÂâºù¿1IÚâ†D µ#+‚MÏ*( C).ðÆ$µE& ¸¤ü~äIpu¶ÁNâ‰F !šXÿ4 $<ü•šx(—è2]3sæt¨”-Z@HcH?"/¯5°ð ó+V,…ù×ãÜlýûÃjÑBغµ8;ûÇoÁã²·n‘N›–å²XÀgcßXæÕËïµËÒÒâ³²’U‰Q[l÷_ÿâùc]ÛÔëWºœÍ˜…šZHHÿŒÜ ݱDöoÖ \\cÉh6§¶`}-Âd6 ë"¨­˜`K±MÉÄ!q—ôŠùú nä5D´LÜÚk~ ¾$÷÷+¹m¾æ8lSÍ@ÂÂ^@5ºví*¥r,_¾¤téÒ‘‘a¢Ucà‹°©¾}]Ô6¨WcÈ="ØØ {{;xõõõÁ¿3ØúÇç²¶lÉß»7?=ýÇâl/..?33W(`«ŽÑ¨ä‡†<ܶu}fF¢*qߣ[¶l‘˜£¶üèáÀÐÿ8lºaÑ?S/Äo€§¨5!ËK®rÌݽ% ¶Þ€¤J`1x *(%ª‚ºT-,dF™ž±Ýù0•Ï,¬OD<˜&0õÍýÌå/”û‘ÖTªn žž+¡2…W˜_¾l1âK¼± Ds#ª_TÝ !ýˆPz÷î»Ñ§3!§Ò¡L%Rül¤ƒ3!+à3“ÙI__iœTX"ä3UWVYþÈïß½Þ´Ñ39!&=5^•øïÑjK€ý{·‡½}©h§Vn¤˜ôOoÓ°1ú‡sãØmÐ::ªôýp|¤×úÊöúCùé}ÿØÚûþ©õ$µïŸj¯BªïŸ™ôýÇþ¾EàƒH'ø{ßáFd4zûþv§Ãì×§ œôŠôý …ºúøéîû‡¯›Ÿá]éÔ=ì¾zú Õáiëì§ÒgOë¦}ùt££ïŸ®Î~FöýãhGO?Õí,Ôï~l3ëû§Ý@Ö¬^Ujo;Ä—Ö¯óÒg êyý*Tí‹°ÁÒ¥Kx÷Ƙ∖-]„¸Ÿ‹3¼ÂÎàw*½ú™ñ>4)ô{Öw˜÷‹Éüt)úÒƒo÷Øl$ôÇd±0ô®¹b7nXû%*<5ù6‰qQöïŒùòYÑN]ÜúGhˆ®HvôϰcëŸzß?à*Ò÷O&Ì ï_ÁƵôý£:û‘З¬Dlö·§X»ó¼‰4!ŒIâèãgdLoO?Ñž~Úâ“ JˆÆ'uwö3¦ïŸñýý *Æèèb±÷ýÓi ³g{@­ºtÉB¢e#ïÂ^¨Ù‹r‰^!ýˆ–.YDzÎÇæW¯ZóðŠsg õOWù öÎöWÛ~ûOÈgfгև®_ùheí ÆW”HD&“~òÄá;·/&%Æ`óòùë—ϧ¥%+V¢ek…"¤þVu9é UdŒ‰µõño\ï¾a¢#·Õƒm*Dl$X`–]b]è,3¥XhvăÎr^¦‰EhºÚ…u!6¶ÉXŽÑFŒÔXrt!3tì›ñG­ïÄÊõ7ì1DBCšq±Pä|®.|xøÑ¤«óŽiS:J=àÒ[è™°¼%RìkÖ6bæ`Ƚ»×p¹Cî=ö‚÷å¬Z¹´téÒoß<ų3hü#¹+Ÿ.Œ½:vÇó0¿ã¥ß³}w¿ÚÉa#?}6‹ÍçéÉP8Ä~Þäçñ2!>Z_¿¼ß¿wûט(!¯Ö/WzËëã—DõÐÆu>Åk€”.br$Ð AÖG‰ŸÑâW’ÝD!WcÈ=¢×¯CÔv]‚ggôêßõÏ›ím6∷‰Ïjo¯Ý|O³LV¢bÄ“ÇEžûÆá°0e•+òŸ=}¼g×ÖÈ/â¾Ò$úsØÉ{ß¼~Áå²MäÌ¿(ÿD!y³b$‡Ï/xåñ9HG?>‡Ëãr¯>‡¯~Ýhí¨ó>|,„zêD ‘áhvn#жÍêèc'4ôH‹Nj§AeµMÅž*¾«£kŸV4~Œ¾E¶Œ§‘ˆ[€PhÏ=-aôßS_YÑOG§>½ ]é ;é©wóÃ;6P­c^‚t7ËâA¨˜¤PzÉ")\F‚˜•Ƹ3zõïé÷àR¾¥êÖë~ÔfއÊf²@ü|&¹ Sñ<8lYÀ>¼x²u“Ïý»—£>¾ùö5â{l$ýéí“G·÷ìÚöšÅbÀµ%u„ôïGtNû¤RD™ CCоâ‚ÈLa«Ú;HÑ÷/Çè¾xïõGö]þrK|?]ÏËS‚ñ]¢9+ü(#…¦%iû±$>Ê-Ç|(ÞÎêˇÑ!Mž®}†Æ0¥ªÛÇO[”¾|]à TâŒFâTâEâ NâêéÇ2et‘E^¸Œ1+!kgôê_ ãX  —›ÍÈf¡Ã@иDZ·B©Dœž–réâÙ€í·ûo8|pg`€ßžÝþ7o\NOO‹„”û‘,I½q´ÕlÒaò:b¶ Ü4¬çÞ2˜mÄF¶ë»ùŒ9%œ‡füMW hÏÕת+2.§ LÞ«åF.ÅÙÎk\S¯i[{M×ÓUrëDR Ĭ4†”Ñ«BAvƒ]õ«û[U÷¯þèkH6“­„Éb±ÙL.‡Éçaܦ0b)æåäH%‘€Ïc0h©©ÉÙÙt>Ÿ'#G(JÛ( 4O üYhJ껩`q ¡¾;ÊÉ|s#¬v¤ Y²'2:;¬|?Åú(ñû]Ä\1+1~gôê¹À¿Jª¹ŠÁœÌÃ?ŽRµŸ Â/æç<#<ÐH4­ âB“j!ñ»X£‘D·\¬Gë‡0ê®Ë„|¯”÷€óµ>3?á¯,~¤ˆYiŒ1;SÌúGaæa@ó’@‚%Ñ RA2ƒxD‚Û ‹ß K:N¬±Ø #Ägå3oë3p»Dô¾£0OTô/[ÈGa°™™ 2PXÌt¬ì fv:K #‰Ê¤’MKá°2´„,xM¦£dI4%™‰@–â!!+#!ˆ‡ídÓ’(Y@VHV"Md; è×3âÒ8 Hûž™—QÀw áB*›¤ |MM‰MM¾¢¤$Å© ùÄ/@rR4B"ð^“”D%ÆŸ•$Ä}Rÿå#÷ !þ{$JÜ7 øûAÌD|ûúA…÷±1À;”¯_€pÅ뻯Ñïb¾„#D‡_¯ÈLÔÛBÞ|ùü6º€7ÑŸÞD!¼Fø¯¯>_+xõIÁçÈ—À'„Ÿ"^|,àùÇ‘(ï|xòþ)ð!ôÃ;àé{„Ð÷áïÂCÂBÞ‡=y‡òx „o…¿AfÂÞ> {SÀÛ7ÿ½}üx…òðõ«à×/‡(¯^¡¼†×ç^÷_>G¹¼xv_ÌÜ{þ¸KAAA¡Á=´”@K EéQP’@‘‚,ϼDJ˜ ÅNaA„JŠª ¤züAQ‚½ùOY¬A§(ñ…ž¢TPP**ŠÇ´´DKN¤}÷ôB(R´*ŠYE© Åoa9Œ€–ÏHA”ÛP€”ä/ ÊöŠÒþRÚG!¼.¨ >!URA(jŠ/Ÿß ‡¢‰T+´º (ú]A¤@Y=AU–²òB«3Hí†ÖtPåÖ}ÊÚ©%UëMÕújX®² Fkd´vFëëÄ8¨|Ñêˆ ¿õyMDP|jó˜R“¾¢€9€ ¤%U›Z`ßRÒS¿# .—®´ T9ëˆÏL{ÉFH5*4¥&#…ÅH- HC(-6 Ô+…ÃÌà°€LD®è?t Q/Fh˜RÉz–Á*t6¥Â¡FÇaf¢‚‡š(ŸŽèŸÖAâÁýœAn"=èxl"_ p@G¼ÅÀH éwèÕÞ°eÈÖð>zUã¬:‡.jBÖN¼£e±¦Ï)5Q5Q“±74 íaCºÇ¤~¶‹a›Ö?¦G·XÂýð‡¯KK ­Ì0µX OñªëyÐúË¢Ec ¾ÖŒôêñ.•Çöéz¨ K3ú'ä3Q¨p(Õ\B›€í X ­À¦iÆÑLVs°_;#ÑFáß°uØóƒóä“÷[ Œní5¾Á÷gµùšg?ªÙ—Â@”¾Gé%äIàoíÅ¢‚FØ Ä6HL öCó4FrŽ…è94Áˆ%{&ô=’•²>Jü( Ñ?Šß–E0îÉÔl2Ñþ¬pŽbCàâ»Ç‚6´ÔP|ÃШ(Z†(?vOªRà¼Â“/5ƒrˆÖÌV4CJ Gã?Bä/Fôÿ[ä¿O¼èiA[1EnIhD™¬V¼k¯¨šñ7ƒÒ?ÊM&,ÓKàÏ÷@2TÐÄ6h¸ ¤Ú…ŠâòC!¥vºìÎÇ3ÂôtÊž)}ÏÔÊWlÖ'4ëÃ'~T=Hé%%7h,4¹ šB5CŠ()¾Öçâð7|^'3ú4åxeïçúÊW‚¬ ÷QƒþÉ où’ó[¥ŠNÊ…j3¤PJcÒº)û ù-t ¹GDhŠq›ê…ƒúi×WàÀ:$z bkz–¨¼È§dzàÏPA²m°’£…äÈ!^c$Ó! ››i\Î4‚§GóÈ5=Ò|ïg)ßÏká-†p%~¿9¾g°þ›ÃàÙš.m#KÿŠŸªê¨øâ` j€Á@úGÀq韊ªèé!ATTä]/tB2µÐäŠX1Á¹³éñuæÕâñ½bV>Êú(~AýÃ…B t.TýT%d—mßUnY-icUÛy½G¤u}ìÝÆ“ºêh²Þ…jçÁˆÑ0Iä1ÐÊ-£ƒ‰°ßƒ¬C&U—¨l¿è§BÅŒ°`Ý-ÅDFÛé®2pÔjƒkä#!°¦Âü;aühY¼àr6ƒ1(Wë0:<Ãl±ï=‹ .ðŒ™5#rª‚YÝSP¨bRý3^‡0¾¨š vÒ$êŸVÅ2‘þá<Ÿº’w*t4²«x RùÔfÔæ y †þi±¾¢3j*ˆ§x'G‹ßM"„ZŒRã‘ůeq¤ªîhži2‰¡™Ö4¾Wâ”ÏÔÖG‰…IôOWÄÉú§Ñ÷˜§™TÿÔ5‚6(2ð!žÅä„&1Ã_\ µÃáxfazDd¯˜}¯x”²>ŠŸ¯øÐQˆTA¢cFLU“ ²ñÞ¦Ld?à )WüyFgZÇ#`zÃþ fì{”òQPúGAAŽ’¡‚LêŸN,!d¸{­ˆ|3,n?,)&i~'ßM’9¦Ô<ŽÁ™ÜÙ+9¾§Eù(ë£(Qú§õŽ&Ú½R:¦âL®Ø~‹âOÑ€3OÆîéWAŒÔ š§ç„lbÏ5 þGœP¿Ô¡v†8ǘ|+*²G)¥Å$¿øgØ Bh´7Cä°%þnÞ(6 ’~añ9žA¦÷seEJQƒÛ÷(壠ô‚¢XÛˆŽ í„$i![lðãE¦†ûKcâ³gøS­Ù?Có~ºé™Ì÷¨¥& ’gƒ?OvBRÍÐhE,>Wü 0þW 5Wˆ ÇLEVY!¤B|¿ˆþ1 ¡Î eƒæ!„äi¡iä…“lêúµ éiñæx®J`Þ+Ó£d‚Ò?…þ x t!ÔûR Ÿ:9¿3¿hÈ6!,ZÈã0¤™´8“È“Ç"#ÞÒiÉ|ƒ`S°Á'A”Ñý\Í#Cö(ߣøÅQú^nŽýÃn”ˆ¹99ÒÜ™Àáëæp •Ë%v>7'???//O,d›çoªø]ôH^^®â@r‘r’ŒV`¥ çY.Ão„‡¹6Íçó¹\N,‘‰Å"¾3fpN–IÄ<²œüÁwƒw×®]ÚµkÓ¾}ÛîÝ­ý·ú±²3 ®R%"ŽL*„ÓR°·r)¼…sbp$wº,™~~nÌÀ[±ˆE¢@JÄl™Œ[$ W,fã×§§¡³2 ¢ˆŠ<€üš¾1J„Íj.ÂÙcfò¸ôâ”7øïÀÏ {¾b‚\Ê‘>2âç:ezäêŸ*ÙtE ›®ºP"â}úôiÀÀ½íí ¾%qµ¦¨b¡LŒ[§B‰Šýu½À_"Ï;«WÏn(=m­5ª¯ZA>êi]ªT©:€Âîœ4ÂPƒéj"X‡èfá´ÄÆÆèê`o ØÚZÿݼ©}o5ZµlÞ½{g83f@½ àgcoVkÐLwÀÀ½íí6¶Íš7ïÕ»·-Z¶´îÞºÈ(T±èÌ™3ñM 2™ÌÑÑ’¸ÿ~HHˆ~`# 4'4ÈÁ  ¾ˆäd19–B ‡õ0èN³fM;vìØ£GkkënݺÙÙÙuéÒ¥]Û6#Èzæ3AJáO ?J@@ÀÄIœœ\'O™¼k×®ïß¿ÃGpàØ[PƒÉHSô`èK— 2–™tÿÆ¡ƒ»¶l^9¼'8è-3>’ˆØ£³ñ€$‘«žÌÀ[$‰\>¬€g;áa/8l:—uË—/ïׯ_VV\G Âf°Áwá/5—§$ƒ<à¯#|€¢C.“0™ÌÇOž<}Nâܹsoܸÿ©DHJ*?wô«@<½0(((ðèÀaÓ%b¾ê“¾ýú´ïÞbüÜAÐÎúo0@ð쪟Ëå=Ð1Ñét½‰ T‘/^´ªVaãê&(k7*_Îrš»‡VÊ–µð^Ú¨W÷? ,õôôÌÉ‘f€ð­œœooïfÍš5Ò1ÙÛÛ ø<¢[†2ßÝݽsûÿ­[Þ˜>ñ¯Úµ­&OuC™4Åmâäi“¦LkÔ°îØáµkÕ( râÄ ø5…úLL3h¦Û´mû± —}þ™X£VmÍtë7ld7xøÿª×À™.TijgÏ®^½úß8¦7‚R.X°6^¡|ÙºÖܸóܹs9rF°Pé÷êÕk¥A6 " •úEGÅ„Q—= ÷ƒÝèܹsË–-›7oÞ¦M›îÝ»÷íÛwàÀ­[·ÊÊHÄY-ŠLøg………98Úé¨×¬VçþMzom7¾Ug×&u›Ö„….}œß¿/—KÁðWë\E+°nå€ty qŸ7ù­›6uââÅóÖû¬AÝ f-œ ý·ú&%DÉå|‘À0«)H¶©+ ˜Ç™¢,:Ws¥K—F5éèÑ£ð׆ÜnÀÂýÓXž’¤Ð¿þ„¶Æ`¥…Ä<&ºÈÕ±X´Ösm… j×®Û»×àF [tîØ»»µsåÊUëÖ­…ø„~úŸxo#42= õ/›ž‚QiÚô´™´`Èèš¼K}Hã'ÓøIá)´®ØÛÞ6‚U/Ë¥ÿŒefùrå4å=mm ºGZ Q\ÕfÑ?>õÍšü‘OsDa³«Z¥Bð“çJ6nòoÕºM»ö®ß~P±b9~BoX-èb‡òå-W¯^­0@Â… ì'\•7hÐàŠŽÉËËËÂÂ.Þ7¾4qâ„éþBçõý.íÚ5UËŒÙsá¼ 4ؾw·'ÛÁ û·þ]¦LéS§NÉdb]q?ÜéNìóÏ„ñ4Àÿêý–mÛi¦kÛÛaõ¡“°ÂÌ [Ë”)ƒ‘®2888€eeë›àJ2(%$Ô¦iÈ k®ï˜óË—/Ǹ®AsrÏž=׬Y“mÐû{¨ÈÉ8*#›•Ù¶Mk;;»N:5iÒ¤víÚuêÔ¡íÖ­›««ë¸qãàuåÊ¥øÜ%—˼¼½,--¬7Ÿÿoÿuÿ™y ÏŒý}Ÿ´.x ¼w¢×Í`… 6ä ÀÖ÷ÃnÊ•ó®]9ëæ6yÃúµÎÿ{÷Î%Û·ùy®Y á#XáÆµó°2QýÐLbõª¥;6«&D( Uýƒë—ùóç—*œà·ˆŽŽ«×{rHпÔo‚CëøGý„ÏTg¸G6,Ú2hýÄ¿2÷¯áo_‚ßý²³ÖÖÝêÕmÕsâ¸%+Vš3g®9:–™Ü¶N@i…Ùèv{†þûeñã÷¼™¾÷C˜–rÏ@ýË‘Ù÷¶[³x:/ñ•&{·®†âtzÑ œ {›èª.09E©¨›7oJÄ„ct¹¹9¶¶¶ ù:&¨‰ ? áè¦þMs÷€}vîÓO©€Ûø:.ÎöZÏ›Þ6_œú§LW©€ËèñNÎÎz/ÔôN§k:‹ÅÊËË{ýúu¹rå¬þWùÉáE/yvlQÝ¿^^.!ý{óæÍýû÷‰ëŸ®$ôW^gNïÜ©È^³fͬ¬¬à(jÕªÕ²eKÈ$ƒž2eÊ¢E‹ºtî„'îuú¤)“¬jÿÏc¯ ˜ŠEÙ2h®P¥l[ÇS·;ÀB·@§?jTqŸî¦0@õ>“‘Z8ú+(®uèà®9³=N<¬êc¨ûM›:q†Ç4ôíɇ`µcGö)ôŒ@ÜO3‰éîS`Ëjˆ? UýÍ‹ÅpE¦4À²eË®]»V$j*5@ÿø>îü~ û½ïBø+ÐîÎegæZ;/lî9B˜™BˆÓÅb¸_‡ö6 çn©nU{Ôðiûv^r›¼hß΋[6éeÛ§Që–í«YãÏ+VÂÿ«D9¥yÅýÃhïÃÐ??ÄÏýfCàPø¼L^‚)ôïÍóc‡÷ÿg¸«’‘ƒ\ €‰‰ÁßeÎýÖ¯úï¿ÿb4‰êjÔŽ‹‹ƒ ~ùòE«ûÉd²š5kž;wN*«‹—®°²ªþϸ ªú·vq#çÞº~ ì6_œú§LWUÿFÏ[LTÿÀÊF­&`L&8<==ý¯¿êX”)sÚwJÌ5Ÿ‘ÎЫƒŒZ[ÑæËÇX!G”ãºtñ”V÷CQÑÁÊ7o\”Ëñ6°jMB¹eMTI‚‡Gÿ™T„F«V­:rÔhôDÃõÒÙØ4ú'Øýü¼:>­ýV“4NŒ)ôÏÙÙ±{÷î;wn­˜`ÜoðàÁ“&MZ°`Áºuë|}}]]ûb7(Ãa:8Ú[n®i}^÷G©¾õ¼3¢™u8c݆6ƒ·]ú7síß/Î’>÷+l.²<7‡¿eóú ë×b¸Ÿ¦þë}ÖÀ:¹9ú£OÄ&¿ušI¨êŸVÄN"<ì9‡•¥ºD&Þ¸qÎLõêÕ?|ŠŠŽý¾|å**XR­Zµ#GŽ(†„`í0lð]ø <ú7s†ûÁˆÔñ·-œ,P,cÕM/p¿MAÛ~x×»'ˆ§zÏ’T"8tèПÖ3rîÈaS@ó¶œêÞµ÷Ò¾íÚt™4nÌ4lÐìÒéÐ'bÔoºhÞvØ¥„„‘M¶ÚQ‚GAñ+ü-ýó\âº_Ò‡-š5,EdZµr…L*"¤@Äc륳/ðh¤,üb­º¦ú'qß¼ycaa2£Õý8Nùò僃ƒ èR¨ª¤ދf6?²³§5fÆöÂýÓ3òWCÿN½‹Ùq+XùöÂç„Y¾þóýw]M3¸ñWMÿx<œ±={öÀoݱEýO—Ö>>´°ZÕJåÊ•ƒ“,—˽¼¼àëÆë_<3ÍÏQ´g¤ë_øÛçµk×iР!ìF=àÕÙÙyèСp>çÍ›·víÚ;v¸ººÞ¸~ ó>BìØØX8èXU/î ËW\ªºpåõae+XÞFÍ=ê +$&&ÂF0ƧC"á«. ™™éq ^šc=TÅL«þ;{–3h‰b!VP¬# MýÓ4@e"mIhê\-æææØØØÀ Y¸xIl|"ð(ôio{´HéÕ«WTTdK‘Ž}ÆÖ?¿ëzÛõD±ëe[«VÍo¿²·áÞfp?ÏÛëÑÛRè_Aão¶¾–_ɤI“{Û nñw‡=;.ÌñXU¦ŒE_ç¡àƒ\ÇÛfÊ–-W£z-˜Ÿ4qFu«ZgΜQü¯uZÜŽí[>÷ÇEÌûoõ ر…ª7)(~!ý£B`äï›ä»Y¼$Ž˜Ž5²ªþ!JSøQ/ñuÒmÃôÏwõܺµ­îí™{/.šÔ«5wî9æÀR5ý‹{ÛãóÓnzÕéŸT‡:E9¬CWèïøñãuêÔÉÉÉÉËËE¿"%¢—Jý»x¤M×®í·bààh·Ë¯9¶þ6òwß/Ûv·±n³Rÿú›ìíÁª'Æ/^IŠþ±Ùì¼¼¼'Ož€KײªúìØ’ˆókZ5þ½¥ |Äb±P73Fÿ˜Š)õÍÏÑôçè²ôïièmÛvܹír»¶ÖíÛwÍ4hШQ£¦N ùÄÓÓsÛ¶mÆÑ—ÇõšÕRs¿)Û ¼eÙ¥!jÕmY–/9?æÿlX}ß¾}о¦Z ›žªË ¤NÐý‹ÏÃv?­ú,˜?ëQðm؆ÕȤZ’Ð¥šˆ‘„6ýƒ#⇆†Â™©T©òëð÷¨~Kصg_ÍZµ`¹¥¥%ä‘H(“ ‰þíݳsÞÜYÊÆ_pémÿ‚û-¾¶Š¿©ªxŸôç_Ø­7:—#—9:: ì^탌E÷ÿU­ÖâïvüϪQÃæ0ƒÞÜÆÊªæ²…ízü»y»­[·J%B]ñºŒ´xt;›üÖ+®_ç….ÌÊH¤ªN ŠKïiêΑ¿/¯sÄ´a³7„R2ûNK´ºœs§•êòƒás9Ƴø+Dõ›ð²YãúóÇ:|½îƒ‡à ”ýqêßÎ-ëüiÕ²E½üY»ÚžÍ-qê|úìÙ³öÚ5iÚhܤQ…ŠåkÕ®Ù°Q­T©Z¹z +te¾øòåKüF¥þÙßzØðþñI)ñÉ©h¦Ét·íëšaèŸá#¯=ð:~NUÿN†E£CBÀú Ö¿ŒŒŒ+V Vªœ˜˜X£Fõ²–—¶ºÇ\óØ«-üè ,€ÐNz†é_BB·oßx|Þ‡Œ‡NW¢· ùùìG/tIXÚ]d,„qútÿVûvöÞ<}âÙ™C;uì8iÒ¤3f,^¼ØÛÛÛÏÏlÐÅʼnNKÑ{¿Ç‰“&vîßDUðfì[¶¼E©ÒZôoÍ­å+•µ,g¶ wtiìæî¦õZI[ø¡r2w½Ï¥ny®Y®©dºôÏÇ{Õ±#û`#·Ç‘ËÕ“ÀÖ?ÀkíJ-Ih(SXØs6+Ks9\sÁœ´©nîªÿ—ðˆ&MF ªiÓ¦AAAHÇ9«è=|²ÂÃ_hn3¹Pÿ”K¾DGpÙ4$˜¶mÿ\àþÐCà~3/,ärhjŽÍ÷Dðá©ÞÆVø³ôéÓoðÀ)5kÔ9´ûZ»6] EÍè_µ?ªÜ}mù"?'‡áM›´‚«¬!füìy󿡲wúä1XrüØAôíÂ… © ”‚âWÔ?›¦h‚¤k¢Ô¿Dæ§ Ÿ7 •#®DoŽÏŽTÑ?º.àS»^=Qý{xå ”º-Ä©³FÙµkÛŠqŒíptJý3¬áÎÝ{t™’*Ûvì?²‘Šþñ0BêèäÐù   ›Cû>}ûÀF°D‰ªþy{¯ÊåqrÙL—‡Oÿ´l#h¤[¤ñwó¥ÛªúìyøÜÆuйqú§ûìé¸ïÚìÛ A}$.1o(üâK'!㾜œd2™@ P ÍåêËfZïû—Èþ„“£éÏ‹ê×®^èØÑúàÞ»à~ξ yõ9ªï¿þúËÉÉÑÕÕµW¯ž;´ß»;€ÇÉÒ»5È!NνǷV ÞÂÓ+V-WºLiÍèŸwÐè¶ÈN:õkŒ.é9¦e?×¾Šî¦Äò@Žœ‡Þuu­KOiõ1]ú·ÉoÝöm~°L?ç«&Gÿ€+—NëM"ìís63SÛƒ`¸è½_Bž½Pû×\ºz½e«VèYݱc‡Z÷Ø`xØsÍm&'Æ*ôOKƒ)Óœg›§€ûM>=“ƒüÖê…3ßw&ûb½yöT­[W§Îì6úìt7fè ñ¶=œìºÒ«‡³çòí0S¿nãs'8~Òø¥•*U¾}û¶¹FÖ¹Y‘ˆ7a¸Îúë¯:›üÖשó'ÌOž^òÄ©}mÛŒ= OüËé~D+¾s…³h*£OGp¿Ì†–™ËÑ:ÿ•“§f€„¢-€ÈÓ¸räsçÎUÕ?”{IŒX¶ðS6ÿfÑåè_PPД)SfÍš•™êï¿ÿ†­ƒ Zµùûû£Ã=4‡åâ×?HWW×ׯ_5*88˜àÈ_\´­[6ÙÛõ;qä1â~§^<þý9uófÿ ãÇrXÿ¥þ®]»þjRí×W¹Zye³¯êTƲL“NµG{Ù OSR«¾ÕÁƒa#Dó€T ºcÑÂ9ªV¦Õu ýxòè®TÂÆ<4VpÐ-µ$0ôOÕý°“ÀÐ?4ï¡ÝÞœûôQýר;8¢'3'',Qí[„ôïáç 6t˜»Î1+ù£jÐ/õÉ-õ0 îlpåÊ•Š*­Z¶ÿÏ?ë/œãóäþW cûnƒŒEâ~×>€ûuêÐÓmÊj KÈ3ùyyB.–›™™~þüYxÕ-¤  (áú‡ÑÞGÞ_°êå^=mAÿºun;q`wœî÷òÄ2K P<=Ö~%ýƒ2V“JfÞqWйù'ú—¯\VÕG­µ™u°ïÚ»#5ï 8ûpßÒ¥K'''‹tDË1ò€HMˌ׼+‹¦êºñ “éfcCkZõOÍý°“ÀÖ?¸¨ÉÌ̬T©œ½‹W®Âÿåù«7wîÁ|ùòHÕÓÓSqÏd†aú÷,æq“öJ§Ç«®yâøA++«}{w– +.Z´¨ºUÍåKvwél_ï¯FãÆÌX0Ç{Ñ\ŸAýÿ±ªVsè iÓ&¯²´,ëä0¼L‹¥K—ÊdìÓ§Obb"ú 6©bRkð5RÿŽ;6jÔ(ÐKµM±æ3õç·ÇÿÝ·éáxêøÓàŸ>F$Í›·pù²EšîÔ´i°…V­ZΞå1s¦{ãF ÃÞ<Ó†ýðქ¥…ÛN'Ôë&nî­uä¯Úá+QQQší˜8G‹E̤„(7·É'OÂ0@5ý;qì|%5ù«XÈÔ{Þt%K0ÜOozõO$`B6«Q£†jº……ÅÉ3gwì ,¸ÊéÓªCfðè_DBX«-=ºîpüž¥5];»žÆæHC€\òòåKGG'Hô?¬š5mݸQ‹ å+–+WnÚ47¸À±°°œ9Ý p«ß%«j5F !“I…T> JÿpüÕT;?iïÛ¨þ ŸŸÉK0XÿÀþªV®è=cNý»°Ù­ð1F,sÓ?÷å#‘§Œm…Xyà8{œú——›“=°[V}‹ÌÆå˜ãúÊ¿Es¹Üüü|–ûpPAöÔ!,:ÍÚÚZ×ãQÖ¬YÝí_kÈÿ>ÿ?òË”)p<`ĈüóÈ÷õÍ·³û±<3³lÙ²ÚÒ]ƒ®ªþ]¸paĈ)))Ù¸'¢ú÷ôéÓfÍš>|rÅû÷ïñ'SÿV­\Z©R•~}‡„†|psóðöZ£é~Ο*_¾Ü_Õ¹|éŒòS63À1DâëëûG* N @ínĪîú7ÿDÿªÕ*ûûûëýõ±[å2ÞkççÌöÐlŸU5@åBXmÖLw˜Áô¡–ÄÍÕ’Àp?>Þ°t•ú—˜˜hee5eÊ”éÓ§CÍe"ý ?Q8}øð\ý;ty8ÝìYW¯œsp°ß¼iƒæ:§OhÛ¶5¡»¾¨õÿœîá8m§cÁ=ýþi‰Œú¼6L3îî7gîlÅ£±xÜOW+°OÏ•sÙz¦+8Ãcš2(b«åʸøÃMZ“@µÆýð$Gÿ§Î‰DðOž½£Î-…™£ô=Jÿ(((~+èYIɉ1$Æ]aS°A-™:·%Tÿ((((~y´¤Ô䨬ŒxR€MeÓ“©³JAAQ‚ ô‚‚‚‚‚‚‚‚Ò? Jÿ((((((((((ý£         ô‚‚‚‚‚‚‚‚¢déƒFb£   0G¨ò™‚‚ÂúÇ %¡{¢N…™¹U>SPP˜*ú‡<ìIÄ¥N…¹A•ϦîûGµ2˜3@ųÅÙÈÃsԑ˹"a6é§TÈgJÄE– oa!¹IHÉK¢ðÁå†<¼yp¹CÕ¤”þáoe€¢õþ½øy|—Ð.…<~P»v­kWÏc¬óíëÇöíÚ;zÀÈj⹓ïÃ_–Ðß.)!¦T©R;¶oÖ»æÅó§㿘4‰b8 *‰bNþesAËLüNTIiºÓµ_?(è0ÖqsŸöï‰#„’ΑËÖoØ`Û³—^–,Y"•JTŸ3E,!78èÖ&¿uÖ¯U#0`KjòW±ˆ41 Ù`D±±±;wîœ4y’“‹#3ß¿‡`#“&0qâDGgHbò”É»ví2, p¿OŸ> 8ÐÁÞÞà‹QQQ&3@:åW%:ú§µ•!úó»RD¦råÊ:°ÿ.݆o˜a¬ƒîÃÖ-Ô?؈çš¿’þ= y¨¹æt÷©ïÂ^üJVCÏN~õМ‚G§ó2é¼ :?‹Îg–HýÐßuÛ6ÿ]»q²xñ¢U+—Qúg&­Àɉ_›6oÑŦoÀ]ëܺy¹mG›¦Í[F~x_“ž™g2ÓFŸÒÈ·c|ús< 6›Â`Å0˜‘tÆ;Fö+ŠÎ‰§óitSŸ¨l=ì=óÅfj*Ãø$è/òµM|>ûöí+V¬ˆ‹‹S.|ñâ…)ô/-‰Ë0ø(XÙi´ÌDü0h)8š°ÅeÜf'<$§!1³áì“3eÊäÑnkVn½ÚôïVŸ"ßj9±)ßš5o¹hý©i‹v889á8]¹r¥šUõ—_ÙziÖ¢õÞ½{%bqYâ< òßêë¹fydÄ+M‚ƒnM›:ñƵóI QR Û÷c€ªxy{YZZXn>ÿßþëþ£Æ¼ý»h+¬_¿>GNXf%±a$rrd½l{úÌ]"ú˜+ÃPžÅNEÄi>’¥eç°øò,6| ÐÁÞ6Bèpà×”ÉÄ2©P³Á://ׯÆF-þ³k×.X™r-3Ö¿¬Bô·) êµråJ¸ }ùòåÛ·oá²åãÇÑÑѱ±± )))t:ÝÚÚÚÑ¡wçN+U¬ˆÓ@Š-úÇaeÀFÖz®üµõoõÕãûz s~|Ûà$"Þ¿öÝè½ÑÏåÔécŬÌñRFã|n(‹ÃÎp;;§ŽOëã¯p³ÍPÿxitð½¬WôÔÿhI÷h‰wi)é™OéÙïœ:Ÿm’õô%kèTIÅ&yeêå£8’>{Å4^ÿ½ã^¢+¹÷Š µŒH$‚K?___¨ÜM§_"³}—[TÌ¿r’kðQÌõØÖ¨Q߆-œ´³kÐ¥G}›.õì;ÔëÓºîÀæú5ýÛ±IÛž:wmØ£}ƒÞ-ë÷iR·Oó¦)I±xj4բ˂:½»`ÐcqÃú“K}OŽ(Î6_ #-îïVm=wÜò ¸í¶8Àº[w(îÔÖ>bø!¬tìb WÄf¢2)çÊ¥Ósf{,˜?kÖLwp¼›7.ªq` ›ÛäåËNwŸÂd$ êaˆx¦\:yʤjµªNßã¬ieª¸ïrú£F7÷i =£Jb‘$ܧ»áLB©ˆÎ]<‘Y¿Œðß}¹"‘,…®î~‘q²ôì<¡@xêkÊ ñÝ« ŠèŸ\.yøð¡›››——Ç XEô/7ÇÙÙÙÂÂBé~eË–=xð TBéŸYQà{¹925ýÓÛ¦ Ô?p?(ðÕÜ/>>^é~,«[·n}\œâ¿G5jÔ°fÍZ/B®þy­]õk胖²xæäA¶|VÌG{û¼yºháܵ“ú3.o _Zßß®ëŠå‹ Kb̸‘‹w­ð½°¥½u{ÍÚĤúǽËf­ñ²h ¯¬÷Ûpl½—ovúǦq¾ÓioèI÷i_/e~<‘q$#òXú—s™ ·³2ŸÑÙÑ Nâ‰â°hK|„•›åÍ[- }ÁÊH§ÇÅ3þ=Ïmã ¯Ð8ïú]¶‘úñMtãGIØ!˜ÞíÛ·ƒƒƒCBBMý{rݲR~j¢áÑ¿–Íí›þÕ°Od=÷ ?7œ¬ybkµÇ‹«Æ«(slšÞÃéUç©Ûøø7?6¯Að?c;T_¿nì×Hœú·ñ¬ž?Ô¿v’«8GþÞ»{Ý©ÿXTí§Ö­óR]áÌéãÖ¶}”+ô5{Ïîf¢99¼-›×Ÿ9}49éË®@<0=%Ó¦NÀ Ÿ<º›žú ,1âý Ã:Êe/o/«Úÿ[tv š‰MÚÒ{‚ŸÚÂ…§€žmذA.JÜoÑ™Øî§š„¯/®$”ú—Ãâ3'öýËlRÖ½±4ìyW¨ Š£sù"éóÇ´®õ3›V„ÕXnÃr˜<¢ú'qß¼yS¦LTí ”››£Ö1 /7Š‚E‹Á háC5þ–ý+\ij.Œþ¿ìêÕ«¡´‡ÌðîÝ;]îÇãñºwïÞ¦M«C÷lò[W¥J•zuÿÊLÓ•JpÐ-…þý‹±º[·øbo 63M¡+ÙÈO$)áKa5—þöÙ’ù/çƒéX>n½÷ê7¯C¾Ç~¼võ¼Çpgp¿ÈƒKgM «–Ä„©cÿ}w>(;¥‡£ ‡•NúQ¨ÃÊâEdó9?–øÜñ÷Ûx ŸÅ}Ÿ ¯F&ñ5&bÞ¢ÙŽ}¦NŸôúÕã‚Ǥ±£é!´oW2ßN{º-%Ø7ñá†ÄPÿäðýi12Sÿ£1?Òø4)' <òQ3›œp8j^:­ÿIõÖ¹ÉÉtÃ’8ÀUWã/,Ÿ0a¤I“‚‚‚ŠêßRÃnmø­Œì%3æD¤x*Ô?¬uþ}P¨ä—ZËg”Ð'lz÷WÚݪm×›·hûöu(úiü÷ÏM›·\êw^¹‚Ë IÇÄ“¨Vý ûÎÉ`IX™OBBssår¹E*xz2NÕ?°;P» çÿݰ~í i°ðæ‹qß> TðiQý#vöÄBvXX˜¥¥…Ç^5ºÌ5œá+º©}4m§£……EDD„HÈÒ›„¨0 ½q?UÜv:ÁW"##õ&¡Ô?YZ¶ðß}¨Úe6°ÌlhÉßí—'–J¾¦HbSó$Rþ6ïÌõËd5­ :L–Æ(Ô?)Î3É :TµaŽN1\¥Èj2©P© óÏ¢0O0ôAKT´2hW/OOOM÷KNNVu?‘Hú§Ö*\3Ñ?V6¢Þ^«~ý;vtÿ¨Á®ov/ý;¹büœ™Óá4¢«=¾åÚ« ¸]weažúÇÚ(`4Îç\å o‚û-¿¾†ÇÉ$%‰+—Ït·ïî3ðfJÐþ£}FôõYïItãšúÇŠFš}£Îd<Û‘|kMü™yߎ͈9=÷ÛÕq`ƒŸO¥§<¤1?ÑxtšñG±÷÷Ϲ1± f6mçAÞÀIûÒ¡S%Šf߬´4zíö¹~;ùÆè_lŠ$ä_It¢•½¸¸¸Ç/_¾Ü¤ú7°‹<À›Oéþò¹ðº ÓÉÙeê‚mJÁ›±bo»™ŒTøÈ¥oŸñ37(?š½ú`ûŽá#Bú÷)‰'•çJd¹Ÿ’ù|‘tݺu®ºöÇdÀÀ}ûö)Æè׿]þ³fº/^<ïØ‘}/_üÇËe§£Ü¾uißÞ#õdÆÁÑÞzps-îWº°º*­Å;»6uíß5AŽNšIè¥KA2œú'þœ˜'àÓ»ÔE¡q9¶Çˆ<‘–³&ÌlT®`yCKZÆyb‘øS!ýò³¡rW†þÐiæÌ™Š(%¥¿šþv–¢•A‹zy{{kº_zzºªûÉd2x ˾}ûæëë _ü†4¬ü|ýƒrãÊ¥3°ïÕ¿€þô¬ÄyÓÆíÕÁsñ,5‡Y´pîÓ c’Ъ];u°ïÑMɃDÃ8×8LW97‰kq Üoæ…\N)IÜ¿ÝqˆÓíô`åA=`„xxÍZí¹Ì¨£àd±¿ÐÓÑ@óB¶&Ÿ_ðmç¸Ï>ƒ"˜·O¶$ÁG°¬¦Ø4à(23hÕ[ç^¼ÆNM¥÷/9}‰Ãa!ñÀ篙œä/ß"çm±·ÐeŒÔý»ó’»îx¦’+!0=6›=kÖ¬—/_zxx¯à0gr×ñìàÝ<ÇÙ¸Tˆ.ÿÃhQ1ÿñ]ÖÖÕ‚qÎRŸùBF&èQüÝ´WÃ&õz×w®Ö²Àêžÿ»äQåå° I¶ ßwîy«Ý˜-–x6Þ>­îù¾µžµþßȺ¥¬tÑý2Ÿ5r÷©Àgã•ÿžðÏ·oÛ¨þyaJ9]¶¬µ|V’ݦm‡9žG”šç:Ì}É’…û÷Ú8 V.\¼áL‹Öí"Þ¿Ä4+Ô?Gz lŽ÷“¾ðÓ_¸p¡RÕjm'¶qœ„…Ä2e,Þ¼y£è3¦_ÿ–/[¨T>Uâ¾}\0–1ú2ÕäµEÜO‡Î9Ú'&&*:NsºâIâÇФkŸôÙ?ô¯~™¬&è6MéÝe6© \+Hß>“¥1à+„ôO*lß¾=%Ê~ÕªU‹EŠnŠ”þý"úÇÀ ¡ê×z‘‘‘QQQªîG£Ñ˜L¦ÒýòòòTŒ<_üûþuíÚyРþZ¹{ç*ºÝ»[»»MÑʵ+ç°SÝñû•ôÅÎÎVsÍÕ«–= }ˆÖ&Yñd韣u×üË—Qr.^tîiCrão!—Â.þµ®Í¸“n,V:/"›½EÀO§“Dü÷Ï=zw¿‘ü@yDJÆÌþçô™£Æ'ž–ù”öå\F¨òÙùß6ûèÑãõ?íŸÏ´}»iÄG0À§ÛR¾^ÌÌzNã&uOpAóÀ÷ÆÍƒò]ºÎÞ¾Ÿ·e7oî*á½ÿXcf Wå'/pZÚÉIoü …nnn>>>«V­2Rÿ˜tÚ¬Q’Ëÿ"1Þ×!Ì.µóü–èß©ýÜ6ÿËðæG½Ï~õ„Ù©VÞúEB¢G1fÔD—Q.}G¸ôæü¹Áú'óê5«¥Çýt`íÕ÷ïßí61çÎu›ÕlÙ³nùJ–È=Ð*X–«ˆEÙ Èjå+•mÝ»þŸ «ïÛ·; UýEÆåòE|¯>EWÀ,U÷kTN°gs.Oˆö $¤à íÚµCÏÇ¥K—ÑyÈ 1—Ò¿_Cÿ°Û”úçëë î÷õëWœîÓà‹PóbïÒÓ † êcpæô±è¨w•0'ß ^z yÄc}Ö˜îôBIn0—/ž&KÿTÙÅÙÑÜõ•Å+eÍ+hS¸ÿñvƒ í‡Ë`&#-ÂKEH‹ð9Ž1IŒügøÐcšîÜÍ|dëd«÷"# ^&N‹»žùjwêå¥q›G}šÚõU—Zmëþ7ÃæMÀØÏ7VÇ…ïOK¸•AÇ߬y£=Äž›ï#²ç¬޺Ϻ\Qÿâ†ü>ÿ ¥úéKœvŽFéŸXšÇå*·èßùرcP¼?~ÜHý[á!ò]R uŸße·¨˜r¿à@fŒŒî-ã°²”oÇ8ÈH¹  5ý“îܦ—3|ˆ(蟙V`°w·T^4°|$Ù»ckmÞ¼yh•zêä´GúváÂ…ÊÛzËg5^¿ù»e›¹k¢¾7gÍáZu®ØrûýݪýÛWˆu™+Ô¿oˆÌ`п%¬…2îd÷Ù¨àM “›—›ÎwßóÎ`ý;súè&¿u¡Oî+•O,däçòåR6ÌŸ8vàÜÙãëŸ\&ž8ibçþMô»Ÿ6ìàÒØÝÝ]&a'1iҤήMúÎìPŠàä:§$áæî†DýS ñÈKX“ ÷©Ù3G剥Ê!!øõO$dEDD ûV±bE(࿾2dˆZ#5¥%:ú‡Ý¦€êŸŸŸ¸_\\N÷ƒ ®eà‹ qQfrì¨þÁõµ‰¶Ïãd–2b5r˜Õ\Ô§0ìîŽNŽöæ®Y4F»¼l‡˜ó¸‰o'ç}C3èÑ^ƒ³›ëœÄƒ7GÍ£ÕýP>=>bÌ0cŽ‚ƒtÿûx<=h}â!·/KœÂû5 iõ¿ûÃ[=õqjvlÈÖä˜ó™ÏhœÃõϺŸìø9ÎãÜK×Ù›T:øñ8Y~;ù'I`Þk ø4‰1ú·ïcЊ%[ÏÒÛ‹Éåp¸gÏžY³f£/1;ÖÌËH)h<º~†oY ìP#ïÊÉž?u€tæH‰yêŸøâi$Ê·Þ‹_´ßEAß¿Ä÷2Ÿ5°‚øÚÌJ–;a„5ªÿõW0Ÿ:uþ„ùÉ“'‰E<üå³&oß<ý»Eë™+÷ƒòq÷²uem7xÏ¿ÍZ´¹ï:á…ú÷ö;G’#ýÖ… `‹èwÂ2fø°èèG®$>N8™Æ© !ý»réôýJý÷CG|HÅÌ×/¯÷Yc°þåÈ¥NνǷÆå~ØsLK¤ož\¢' G»ñ­`ý¾3 ëìŽhý\ûêI¢¨þÈ@ŸnÝ KÍW`صÌ‹$_S”+ã×?°Ð%K–TL£Fååå&$$ o---étºj#5¥¿Fß?Œ‘¿[¶lQs?.—+ u¹ßÿÙ;8©ª_“1ïÍ3Žo2“IÞ̘Äh&[\PDp€û$&šÅ¸áF4‰F¢$ˆ" ˆ,,¢`E‚ìÐ44½TïKuWU×Þ{í{Wïà;U·»,ººª«·êZ¾ï÷ÿÕïö­»TÝ{nݯϹç^ÁªU«ÄŒâÇ9I¾»ÍÒ0®úçr4OŸ>Õܪϵ×^3`Œ8cîøð½ð1÷ÿ]ü¦oî:’ÓÜ’Å ³ïäJ!gË¢?¿pÝeÿ½kÇ{㤭ÍÚ™÷Ü3åæ[¤\9mzQqîH:´6X}­–R}ÁwMžòÆŒzSÝXµ’ '¿þ–éÛÕ¡"?œ}ï¡á´ˆ ø^“ÅVfÑÿÝ\´ªé“?èßøYí¯¯/½ï’¼_|¿àÅ™ëg«„Vmôq*¬#î¿|åŒîµ›<«7yÞý›û/¯ù,ýJüþ÷еÞù¯û„^rSϪ žÑèߠDz8äŸ}öÙ¼¼¼‡zh4ú7ç'Ï=Ø.xOÞÛw¾ËÞã¼à¬ÏZú»-»æ«Ï9ùþZOê_›Ùxê’‹{ü…/¢ßÐç]?ܦޟÝwêû—ùb·,û\Í~dÚuS¤<ñølŸÏ=‚ž¿RUQxþ}÷áß.»jêÌ›ïxè¼ï~ÿ?¿þÍ£ÙûFð­Ã{þ:|ÝÛäyl÷·ßýí_ï»$¿a‡¿«³ÅÑaó7IZXÞz Bï 7Àa韼ºè×Ï<ù¹þ}æßµóá[Ãã=¬ÓT\ÿúÝ,.÷;݇«ñ ä~"×þø»³n›Ù3Býûú@ý;ïÿئ7¨MÃÕ¿6¯EœÖ¿öµ¯IŸðÃ?IdäßÝj©ŸgÁüyãWûÙ=Vü»:ðc˜úÜŸS`ñiîOóþÙ #–ùÚÂyG_›cÛ±à¥ïÌ=~H¨Ú˜ë_aÁѯLž>éÁ?ôå¦.øëü‘} ECÅ%¯]÷ý¥ÓÕÍr¯vx½$b¬bÿ¾O~úÌÏc»ŸÈ‡u»¦ß2-þ^À‘ßÂm°˜ó·þË_Ѹó÷º·©œWÕ¼Y¯Þ+ßôDÝ—ë›{Íö kœ7‰\Åÿ<Ü9[üðWÛnþq—Vk}öÏþ%oy—­ñþm§ë¦{»Z[,[?rå{'M­–1×?p¿'N¨Õê런Ó+¾vjÓʾÄÞí®ïžùÙ{kúoÁ³þð¦^ñîMôÚ-–$Ô¿Žm›?;çÑñØ=Ûó²€;¶Æ>Õ:¶žöá‡~ñ‡ßÿÎå²G»MÊ­À;>9tÓݸê–ß¼þÖ6‡­ù{]òÿÎ9ÿ¾Ù~iÙî_ÿeÓ7Ï=Oüm|÷µ³ž»fƳo¯ÿ(ÎÒ®UF¯¯Ëù›}—ÎÞý-!{í>oΞ W>¸²èIÿÄøÒ–½U÷ìUå#Ð?ñ ÷ܯ•µ¥¡Ú¿ã9û…þ‰1'{<Ë—½zpÿ.IÿzºÝ'{½Î8÷]OwÇ/üåå3ÏûñŸ¦ ·]æþùS/½å[³gÏŽ³ñ7Ôd<¤†Ü/о|ó¹Î~tø¿]Î_̼ñ÷üÿãšýÃSÃnü{|ß¾}¡9oÞ¼ÅA®¹æiÌUW]¼ ú—’ú'~‡¥ÄßówÙ²eÍÍÍÜïdð–ÑxóÍ7ÅŒõCÔäˆtà‚Òíïǘ¦ºR6Ê_x±„Y³n \ÄÇU‚)ÔõCØõ]3núí¯f»ú¯>’òÔ“³§|ÿ–mú·ùùŸþà–›>üàÝñп›rÓ¤'æ÷eÆO¾²`ßB×R{Õò›/\<¥ÚXâÞéÜáy¹oL6ÔÌ;gl•4¤þ‰ÜÿÌÏ÷îùx4ûÂ¥ÜPõ7Sá›M{æ¶>¸Œp¿_Ð}µ¾êã‹­Ìêm¡þ­Ùè9û;§´:ëï´Ýþ@ç¡£ÎÒrûÊõž[ïë’×Új¶¯^|RL3âµp៧M»îúë§Ç™k®¹ú£˜n3`B]þûKŸ­z%°gó;„ï ýSUÛì ¸Á­—ô¬\Ø·Ó­­–Ní.ÉsŒx_Œ«þuÿáw§.¸À7˜>vãWë©ï|§{ÞÜ¡*[¬¦Öæ>Øj2µ èSÿU:«×<é+w~÷êÇ„þ‰Ë¯{àw=ø»W>øoü׌>ñÒòOÿç—Ï_rõ=â­‹§=uù O‹ßÍ[=‚ûþ5Ú: ®ª9{/xüÓó%åy|÷ù…PJi˾}e¦‘éßú¬·>Úþ^èÚ¿†z¥ThÐÕˆ1b¡ÂÅ[Á[]™:;â*$BKÄùè?Ïÿ·é?¿p¸úwãƒ}íÿ²fÍš¡º~x…ÿˆU„÷‰a€áîì]2ô*"»~x—¼dúÖÿŽt¿ÐÝ`|+_9ém–þõötÝwß}±·‰R©los )‘ïõ¦ñôü]±b…Ùl¶ÛíqºŸ@ºX´¹Q“ ú'ìH«®N3ýÿ¿?ú“;”ïüþèksþòûg6•>úÃ[ÿü‹™s~ùã8ïÚ—Hýó”ØmóµZš-ÚéoÝöíW®”i7©öžpدíuïp~CÕTßú£ñ¸ŸÈ¶Ú0ëæQõ_všuÖÖãíǦŠõ[@Z`<8ß(ܯhU“bkkã!‹½bäúgµZ¾5¹wÊÝÖ=¿™çæEÿ¶Ý.‡9·Àñ+zŸü½ßëí?ãZhgßÕyñ—Oýrf×Yžmk=Bÿ„¶6ZÝóœwÎÿ­¿ºÄ~x·óO¿òWÛ“ç[„ôïÚç¾õÀk?È»å{¶ ‘¹ù†ß÷ïÔåßïþíÓqÜ–ÄÖÕék‹rÓ!Û| í—ÎùÑÏ[äqŒtÑò-ÂîøÍºY?zlʵSo¿ýöé·Þ;gÞûÿðÕ»žúÝR¥ßd‰i ‹KãéPPPðÅÿõ¿~üÀ÷=øÔýÍQÖiL>ý+¹wÏþû¹úíõ…ò±âµ•}dúw}çÖ_yååâ¤0ÜUüüÁúçr´Ü0@ÿ®R?PÿüuAœ«püºÃö­ÏZ¶·Î\û£o.¼4»öИo¨gŸfñÎåqêŸÈ¬ûo/)>1‚}ñy„j,æBký>³êo¦ª-åYÍ•ï´(ÞoÕï2 3tÔZ½fˈW!4ï_.8õŸß?¹x¥7¿È^%·íüÔõ³9gžwêå¥>kT%*…Öf±ùÔY¯³ g«%gŸÓiû|kh¶C»\U%öa}‘| )·ýéò/]*ò·YÿÕósîœ{©ôgdŒ57×öÍov.Z0š•ùû,òúÊmÂåT*•ô§ÓÞò•ÿúéþ÷=÷ÿô~ñƒ ÿgŸýí¿û_ü÷46úkm:ë÷ÎyáÍx>CoO×Úµkï¹çî¾û‘ 6ìÝ»·±¹Ym/Ú&ÿËËÇf…ªþÛ}þÆŠçÞŽç7ÉÃõ¯ÝüÏõ¯ÞP~ûaw:Mµ¤ÒÃK‹sÅ4ÊÚRiäÞ=ëC‹Þî[~pó•·}{¸7åûþ­çßvÇ,±âYÅM·Ü¹Š8ÀýD.ûÁy·ßqÛ«8ý¶Ïm–+þÓôÍ/†îñâzâǧ:ÚOùÛ\Ýõy•à7¿h¹úÜÀmŸkŒ}ús-ÂßÄþ•>ç?üÃ?Üwß}÷‡qÙe—Io}ãßè퉮ÎÓô/EO¯™Hýq9ÍížØú·jÕ*·Û¿û –-[&flmÖ%•þýuá_ÒCÿ¤<ýðýÛçýrå¯~´réàg™i×];‚U$@ÿ<56ËZû½ë8gþÅŸ¬ßëü“ßk·Œá†zýµE—_ýýÖœøõïÍCk}âáÑ+‡»Áj¯²˜ò,‡,Æ=fã§–†÷³•[]z‹×9*«©UØ„ïýÓú‡s>ùêÅ'þM{y¥}¬JT mÊ­b@ü9>;çœö¿½{²Ž÷7.ÌÏåêbÿ>‹<ðäâûÎÏÂÇÜtÏ.¸fvøÿ}7ÞùÜw&?>ÍÕ·þöú;^ˆçø<–Î_Ow§q'ú¯ŸÑ»µG«­®¶îP×Í¿·{:^èw¿蟈Þ¶uãGÛß›ÿ—Ÿ}öiawŲc’ì…<0üOÉ;Ú±¿E{›³¼¼ü‹_<ã‘7nŠßýzý1‹\.÷· m˜¡U<úæÀU|ïú¯Kâ4ãÉî÷`p555C®â´‡¾mZe–úöÍÀCßÚV-<ôMÕØY|èÛòùŠAIÏû?þmï|þз˜ú'öï½÷Þ+}Ô믿>Ðë¿»CŠÎÍÍ Ilàûè_ªë_xeQ4ý[³fM[[[üî'Xºtiðþ¥ú$Ñ?ñIÒOÿœöæGúù¢…Q«4?Þ±-9õÏélyð½'ÿý/¾/{Ï1£Çö­Ï<Õ¶1ÜPsæ<þ³_ÿ"~÷“žrŵWZLÆÑ+‡Ç¸Ç‹³Ö*”ÏZ?GE¸ŸÇj«qÚÍj­U¯··Æýƒ¸L½wßuòêɾ†¨M>cÝ©+¯èýñ¼q×½ÇQX?èøŸ>¶èß/| |Ì-?üã%×Í9­êò'óþûšÇÃÇL™ùìu³~7‚!]äºíóï6Êíþfá~«Šëêîy>ÌýâÖ?ïkK¾»i]C½RºØoÎS­YýFöáOušjS‹îdG^])~¡?ìéñ ùÉ»»;,XðÏ_ù§ßl¹-÷{fó¬ÿû/g-^¼¸»«=Î#V1þ˃®â¹îxîƒ;G³Šþõ:}Ž_Ì’úwX®ùVWIþI¯?ÔÅC œôµw³\ùuÓyEt>rO¯Ã—þõtÝqÇ’àmܸQhø===_ÿzŸÇ‹wO ø@HÿİŒ6¯ ×Jbý3õgèVIÿÖ­[×ÙÙ¿û ^{í51ã'ÓÄèŸÝÚT^Z òµ¯}õ þ{¸ùõ3s’SÿD^[ò×m[7í*Æ[ÿ^mÑ3;~'Üïíck²Tiwïu톚2õê·Ž¬–þ‰ÌYøô† kÆL9œ{Âxš^n–úç6·Õ”ºè{'o¹Y ònuÉÉ›nÌ©“'§M›6ÈeݸV2¥Ï÷bè_´VIÿn¿ýö—^ziÞp¸é¦›ÄŒÑþcM°þÉ«Šôû¯¾úª{î¾3”o˜~ë­7‡¹æšÉáΘqË ×O‹_]´ú·ß.¡a©¥3ž¸M¸ß¢KÆoC}ýÜsØŽ Wÿ>Pì¼eÖ͈«ÚKóO^=ù³oÛýÌœö>h+Êiß±­ûWO~vî¹'§\ÓV^8¶kŒÖ ¬T*ÿ׿ÿϯ^X)ý¹ú@/à¿ï9í7¡¤¬ò ÿv×dI.yã}1ÍÑc…£×?‘9k+ߨ­³¦"ÚCßbëŸ7P­äij¨«•—ÄHC½Òç1}vÒ~A ä~C¶ü†é™UèÙÂ… ¿øÅ3.Ÿyþ¯Þ™iesÖÏøþ­ç‹ /^©@q®bÁ‚gœqÆå3b¬â¼á®âó®Õ¡s½ow“­Cnè~ýé¨1 3“õ˜œíÕúxô/Ø„íêîjïêl´Oø¡xWx øÌ§NöNŸ>ýKYý Dü_9`LxÄqwÆ(°Zêc,\dßÞ¢ÌÜs÷Ï=ûÌ É?q¤ª¢PLsÇí³^ùë_MNö¾Øk±˜ Í~(|ÌšÕoìß÷Iø˜_üüþð?³ïyó¥C.911êb#ˆ¯ðÉÇŒ,b3ƹŠëožþØü9/¬~QÊw.úÎÇ;¶^òÝïxé%)û^|ñ² /X¾lñÿ½à²I·=З+nøís¿‰g_ºê_~ò—ŸíühÛø}‹ó¾{Þ¢íKÃóÊö×>ÏŽ¥§eûç¹ðûäg'Ͼ`¾Šhñµèº^yùÔ¥—ûÛŸS—]Ú¹h¯U?V~ìßg) ^Û$tnÚm¿»ý¾?ýÃWïúñC =®ÖÓünÞj1ÍMwÿáÖ{ÿ(yz„¿líî÷ßÿ¬³¿|éÌÇ.›ùxŒ\:ã±3ÎøbAA¿Í1äbÛ|›u¨Ø‚‘íRÏßýûvÝÏ>¼½|ªEUUÕÌY3íAßø×KoùÖÔŸü·ˆøÚ7þå _øÂíwÜVSS#unIÁ®¢²²2lçö¯â\± 1ò¶ÛgÉåòa­¢§§kÖ̙Ӯ¼zîc¿A¦^~Õ]wÞÙÓÓ9V%³½Í)5þ†lüµ&ÉI“„'RÿÂÚ&æ#Iúƒw²Þ–ô/ó_~)í÷tš /þñùñ^Ńþ" ¾«`È£¹­8·}çßDÄ€øs ú8Ÿ…콕µã‚)󲇿Î_¼À Ó¼öæûß™<û¼Ë}yñF—£e„Òë1wuu>ÿü S§]?uÚô¹núõ¯-]Ú¨nË_Â6¯¥«ÃQ,;p?¿}d FÚÛÓe4ß~ûíG}dÆÌ[gÝ6söìÙkÖ¬ihhoÅ£¬q®bõêÕbBEýèˆWÑîw)Š{î¾û–›oAþçž{T*U»ß‰¡¡ÛÜõ‘Ä’ÓÞ#ng‹xU)+b¤¥I“öûÎak:zdïh"6«`¬b¸«˜ØLìïóàèµvwµ 2]m#«@ªÒÑÓãi÷ÛFm’¶Îø.=="fÍvÏØV^í*„¼‰…ijå##floÃýÐ?6!„BúG!„BÐ?B!„‚þB!„ôB!„ „B!$¹ô¯ÃïöylšºŠÊ²c%EÙ¥%9åeÇ+E*NÈ+óåUù5òÂZ¹LQ[¤R«•e"Zu¹¦®\§®Ðk«ôºjƒVnÔÉë5 ÆÚCmc½¢¡^Ù(bTˆˆ‘õ†£¾Æ “´Õ]•˜Q§©Ѫ+Ūƒ ,S«©S–ª%JE‰X²¶HYS¤¨)@|Œšj‘‚šª|ñÁª+ó«*óª*NT–‹äŠ\Qv\|òòÒce%9¥%GK‹–e—g)–)*<\Tx¨¨à¬à Haþ‚¼ù'öœØ—bo^.I\ÄÏlöýbäíûBì±k)<,v–Øe%EGJ‹³ÅN,)É)+=V^v¬¢ôX d–çV‰^q¢ªòDuEž¼2/PJEÁÅC”ÕšB…(®5EÊ`‰ $eI] ¥¢t‰b,l¢ÈUH%PEC  W §^n –Õ@I–Þ@1n¦^ÙÔ leÛ¨fƒ(ɯÓViëeX]W®V–Љõ*…V&>ølÕ•ye¢|æ–—æT”æjÕU>¯­#ð~Ž!„LŒþù}vŸ×.¬IœÝŽÖŽvOg‡¯?Þþx:Û=§ÅÝx\í§§#÷ççí ¬Â¶ðÀºº:¥´}ž.w_ÚûÒ-ÒÑóy:¥Û(õ„ßÚ¨·[ÊÉà× ä¤H¯”Sœ±ŸÑ–T•’á‰ý{5Q?éâ„’}dO„þu{Ý­R$ýóùãñ\BR%¢ü{\&ZQVV’{üxq‘¬NU+nÐãB©Rµ‹gF1™˜xÀìí~wQááú—“½·¹Qþ¡„ôoôˆŠ8­Ï/}¾‡þ2Ðý¼V³©177wÆ Ë–-[¸páÒ¥Kׯ_4;»¹É0àŠ—³E­Q Á‹gF1™˜XÌ2¤þËŽ9lMèŸdw±N›ýÈ¢WBÈÄæ¿þÑGJNý'”’¢ã1õ¯-¨vŸçóiD"¿Bø»¡i†“D.ml—O29⻵eÿþýóçÏŸ7oÞúõYÿû®M›6JîÞ½»µ¥>üèp9…Ñ)ö¦xft:„+*Å,ák Ó¿ÏGõ¯1Ãõ/T¹7i> åµ ã1榛n§%ó™ÃÇœ{î¹)÷™o»í¶ ¤Ögþáøâ‹/¦ÖgþÙÏ~öÜsÏ%ó'|饗üñdÕ¿Æ þµŽ@ÿ’ÇÊÐ?2~ñû쥥%Bز²Ö™Í- †7ßXÑÜÜ`³™?úh‡øÁÌÏÏó¸Ì‘ú>£R!w8¬RĶm}_šÑaoEÿƼö/üçWb<Æüñ§%ó™ÃÇL™2%å>óâÅ‹Ýnwj}æ5kÖ444¤ÖgÞ¶m›\.O…Ïœ)úª P×^O8è[±¿†/|À’c ‡O9dÅ&Éœª?«¥uË–-‹½bµš í5×\}öÙgOŸ>MXœ³zõêÍ›775½ýˆ¤NGKhF•ªöË_þò{ï½ZÂïÿ¼4ccƒQ£U¡c«ñß`HÒ¡‘1QóJ:”ZŸ9¿¯¤p‰ŸwÖ¬Y‰_¯¤‰Ÿ÷§?ý©L&Ù¼’þ%ÿ¼)¤"W«”áêßƒŠ§8Îw‡«Ñ„0ΑÔ"‘6¯µ¹©aéÒ¥{>Ý­Ó©¯¾zòM7Ý(tnÚ´ëD„Ë•–/_¾\«Õø¼–pýs»Ì¡M¦æwßÝtÖYg­[·FZB}½¾FµÞ Eÿ&Jÿš››G¬5ïÓO?rŸ9¿¯ÉdêííMü¼/½ô’ÃáHüz­VkWWWâç?’:ndóŠ ÕÞÞžüó&¹þ…|/~ý´Š,¶_ž0oý£™œÞòkÓëu‹-*.’-}mÉÙgŸ][[ÝÜÜPUU~æ™g®^½J£V½õÖ[â?A!Šáúçq[B3ŠéEÖ¬yûŒ3θøâ‹õzø34£±^‡þM”þ¥"£©Ñâû&?£©ÁKEFSs˜*¤¶þy Ÿ9üÏALJ†ãrdèO‰h«_H8±—<èb£}G’9 tÝ5· O;|øPJqÝuSEÊËK&O¾ê†® WQQ¶qãÆúz£Ïc‘fq:šƒ—ó™C36Ô”Š1‹p¿³Î:kÆõbŒ4£ÑhÐ4b–ð•†ô/|d‘ì˜ÝŠþ¡èú‡þ¡c€8¡?Ë$¹þÅH¤þÅùñ÷#"Nñ—8RÖgeôZE­|êÔkÏ<óÌ믟®RÖŠ1}´ãèÑ£‡-4‹¤¿ôÏ(æºêª+¥YÖ¯_' ðw²¤ívkðBAôoxúž‹.º(£ô ¸¸˜ï›ÆTVVޏ!5Q(>Ÿ/s¾¯Ô%Dúí:0¡ŒŸþ Zç6\ý‹VÑÛ‡+–CŽ$™œŽvOssÓ²eË>¤Q«äÕ•‹/ª­©Öjê òƒUõm>çý³ÛšB3îÙóé3ÏüJšE$+kíŠË¥=;ú7Jý»æšk>@ÿ¥ƒ¶®†;[ ‹³5v¸OKqŒÏ»á˜dj ¹³Ão0V¬X!œíDîñ²²’ü¼|°-++KŒ÷ù<ž°£#¤ñ̼í3ú7ì›çŸ>ò³toïô/Yô/aÁ¾H2\ØÕÕát:óòò6mÚ$ämË–-EEEbLG‡?tÕ_(JU­ÅÜÏŒb21ñ€ÙÑ¿a:!úè_:è5o$éê=–®.OOwggG{{»xíîîêh÷zÝæÈ‰µ:µN§jg·59­mbÆŽŽÏgô·yö1˜LLŒþ¡ ¼ü—¿¼ùærô”kö·9Ûý.›cPñ 7@¥ªV­QJÑé5-­M&sksK£xK)&ˆt?ôýôý#$uãt4ÇH´¹Ð¿a…®@ãïØé_‹ôýCÿýK”þõùúGúGã/úԿϧ!„Œ_Úý® þùÂGÉrìÖôýôo,ô¯¡¸èXøYý#ýCÿÆœdêú1<ýÓ¨åÙÇsNÈd„1ÌÑã9ju5ú‡þú—lú§ÕÔœ(”åä—BÆ0â°Ê-”iµrô®@ãoRéßñü¼ü"™ÆqpÏ?þ 7W\qEYºº:N.„ø|>ég*Eõ/¯¨¨VQÍi`<W¾¬ýYãïŽ;rrr8Ë@b³ÙvIUý“Õ©œ¦Æqpå£\û4þ¢èú‡þ@¢]?Ð?ôýÈpý›4iÒ€SXä©`DÂfAÿèù 4þ¦¢þ 8‰sN‰ÖÖÖhÈÆt[¡è )Tû~B0<ÀCÓ‡F*T ¥:---Ç­ƒŒ ,¡m…þÑø è_ªë_´‘цcÌ©ˆÝnokk®þef‘¶ú‡þú7±úçv6K‰óÚ¿AOÖq:!ú—±¿þ}ÈØ"Aã/ú™@výùÞÈô/ZMà€f»xô®Òø¿þ¥w‘ ñýô/Ùô¯³ÓÔ?›˜fXú7¬šœØ•‡–¿#(3é½­$ýs;[ümýëìð…CYáQ›¥ý£ëÐø;zÄ ¥H–~–±þŸv‹Æ_c”™!ËOF5þ¢è ©¥ÑÎæñ럃ž¿™Ôø;dL(Ñ?ýKrýnãïmÁ»­Ð?ôÐ?ôÒîú‡þ@z“<]?Ð?ôý@ÿÐ?ô®4þ¢èú™úÇîÐ?ýKZý;ãú7ý+++«««ãäIˆÏç+ ’ ú×$%Rÿ’ö,@HJýþíØ±#''‡³ $!6›mGûï»/ùº~ôù^oo×hôoçÎíœÇ Aÿhü@RöüRÿšƒúW;¦¿Û“8égf’m×'ÃçWPÿšýmΠþyC‡¡ˆ¬0Ûf1¢tý€Ô%™E²£ágô èú€þ‡þá~™i}Ò~ßû¡Gú:edi ‡>fÐåDÎ9ׄHôÆ_@ÿÐ?’®5~Ô+š× :c9#›‹Ú?ôýCÿHXãÔ¿Ì;¬…£è@ÂH¦®QôÏÑ$%~ý›t:úGöFÿпè_d™DÿÐ?ô¯Oÿú}¯·g„úGíIæÚ¿L‰þÑõ Í£é_‡O–Ðç±yœAý««ÅýHò\û—V¿uAýs„éŸãóSV€þ¡€þþ ?ËHúçr4IAÿH2ôüÐ’§þÅèùÃHÑ?2DÿB¾×¯RÆIÿ!èúèßDë_Ÿï ª^Ííl>!+R¡„ŒOTAýs…éŸ+ìÿ2¡Vôý€T&yº~Xƒú犥^Yþ þ5¡„Œ¿þ5…é_c(Aý3 è c¡†¢Âìð³ úGúG×€4nüM*ý‹¼7 !èú‡þúGíŸÈOzß·¿}~(ÿÕÏw¾È÷¾waY™ è¿è_ÚÔþM½vÊÿøÇwß}wëÖ­Û·oßµk×Þ½{:tìØ±M›6þù_ÿú9:­£ èú€þ¥GíŸÐ¿+Vœ8q¢¸¸¸²²R¡PhµÚÆÆF³Ùœ››{É%¿ºháEß»Ðlj˜Øg[‚þ¡tý½þ­\¹²¨¨h€û9ί|å_Ÿ|ò±+¯¸ü®;oŸ¨Û‚þ%RÿÊÊÊêêê8¹@âóùÊ‚<ùÄèß(õoÍš5’û544˜L&á~mmmb@¼µjÕª'Ÿ|òæ›oŠ]GãÑ#xVEä4d¼·9ú‡þ]vÙe;‚äääp–€$Äf³I?S4þŽ^ÿÖ¯__[[r?‡Ã!ܯ»»;´µíº}ûöÅp?Áßÿþ÷;î¸ml›hQ»Äß;s¶9ú‡þú—túçH.ý{饗þ“çŸþÎè·}Ž_*Ò»»Arê_fnó>ý³7ú}Aýk÷„CYþ«ý£ñп±Ð?³¾¨ ;ü,“ú÷ç?¿ô“ß;d¿úW¤‹ èú—úGú‡þ¡Z$Q×(úç´7HIý‹¼7`xü~Wy­a×Ѫ+Iª|’]- §(¢èúè_‚õ/ä{)ª±S^cÈ-76X»Zœ½ÍŽžÛîú‰ $Òhë…S úG× ñ7Ùôϕʵ»²« ¦MKG]sÇšM_yÍt1@H2DKQ8?9R%éŸÓÞØÔ¿Žv3ìÀ,Ì?bAÿÐ?@ÿÆqB‘d;Ñ¿z)©¢±³ã`…ÑÜQ[ß&"7x¯šr½4<áùêWÿmo‘4Û8¢pŠ"ŠþÑø è_Âõ¯Ï÷ÒUÿt­þ*½G¤Rëú' Ox„ÆDí-c»¥hDáDÿâ¹­w”§l£€þŸþ5¤´þ©›¼ej‡ÈžœªÉ×Þ 8’žI¡1þ M3`‚øGŽòC&aÆu»¥nDá Ó¿†0ý«¥0ÿ°Å¬Kcýò¡~Ñ&@ÿ UHž®â„"+8~–IWýSֻБ-e_uí ÒðÈ"”#4mxȉcÏùg$1Û-# 'ú‡þú‡þ¹þÕêíòV‘•MWM¹AY„oD‡tâ!ç°„h LÝ$f»¥bDáDÿ†Ô¿ðÇFÓõhüÍpý›t:úK¯âÜZ­±æV4‰ä”'_{£4<²߈)ýJœsE.a42 “˜í–Š…ý‹Gÿ}ëòË/?D&“éƒFÎ20áx<éGI©TJ?Sè_âkÿ*T¦£%F‘Å+ß½zêÒðÈ"|#rxБCNm®È?Ó ‰Ùn©Q8Ñ¿×þI?§<ðÀ¯~õ«¬ ›7o漎B¡~”^ýõ‚ ‰×¿ÒÚæC…Z‘OsäBÿ¤á‘EøFh |xÈ "'t®AÿLƒ$f»¥bDáDÿ¸öhüEÿÆ\ÿŠäûòêDv«¹úº›¤áGri 4&ž Bã#çŠ\Â(?d&Û-# 'ú‡þ@ÚC×Äë_Aeýîc ‘Gª&O½qõ–=Cæ“#ÕÒ,1"ÜcÈi†;e&„íQ8Ñ?ôÐ?ôolóÉ‘Êã%ÚÝ95;T¿ô×U·ÿÏÏîº÷—"wüðçwþèiøö{úF††·î–‰é#Þ=aÐ ¢ÍÿÄi¶Û ÅòX‰vçáÊ ×¿Aïêq™ß ·}¦ç/Ðø;fúg«—’úW*×ï9.?Z¤>V¢É–©ÞÙú©ùÛ§Çw*’†³ÞÛ% &d\#Š¥(œe5zôgþú—hýë÷½~ý3JIýóû]âôºóHÅŽƒ„$Wv®…³ÝïBÿxæ/  ׿>ߨíAýs[]ö þ©RRÿbGº7 !qpôϦ¶Ïÿ/ èŸ ýCÿý ý3édùG‚ç—húç êŸÅe¯ꟜÓ4!ã£ò þÕ·ùìAýs‡C‘ÂüC“ýCÿ uI¢®&­,ÿpøY&õÚ?‚þ¡èµd¢á‡þ¡týÑ?jÿú‡þ¡€þ·þ9lF)Ôþ%þ¡öé½ÂïÛ¹AŒ‰¼Éú‡þÑø èßXé_È÷z2OÿYû7è3ë3Ù~lfˆþ¡è ‰Ñ¿ŽvOaþÛâ êŸ2³kÿî¾ûÎŒƒº’Éú7¨ Æ£ePÿaúç;0 Ð?ôRœ¤êúQ˜Ø‘Ùú÷“ŸÜªŒ| Ï»›ßq9-R²Ö­ ïß·»ºªTV)«wíÚAï†Ø¿ÑŸßš¹¿èúèú—Ƶ^4þYû—ÂŒþÑõhüEÿ¸ö/|¹öýCÿýK>ý3H¡öž¿cÞø­Ÿ/¿è¿€þ%Vÿú|¯§§3Óôûþô/ÕõO¯×·´´pr€$¤££C$5õÏHí!ã¯Æ0ý3„RÐbÒ Ñô/++k÷îÝœe 1™LYAî¹ûî¤éú¡)Ì?~–É@ý£ö 4þŒ7ÉÔóý#ý£ëÀ¸“L¿èµýCÿп.‡Õ …Ú?BÐ?ÒSÿú}/õÚ?‚þ¡èß úg£ö$ôÖˆªVc›7¨~wè0)È;hiEÿÐ?Ha’¨ëGkPÿ¬­Ôþôý@ÿ¨ýã©ã÷ÔOvã©è]?€Æß‰Ó?½jÿпqýú<óýCÿý›8ýëó½ Ô¿øs÷ÝwþcÄЕÈ:® ¯Gÿ¨ýCÿhüô/±úçêŸÙi3õ¯:ýN»/¾87Tùžw7¿ãrZ¤d­[Þ¿owuU©4¬RVïÚµý‹Ýø­r/³«ƒúgÓ?}(AýS£è c¡ê þé3Yÿ_땱þÅ_û—ÝÑ?ôÒ›dêúþqo“ hðåÚ?ôýô/©ô¯§§ËnÕKAÿп1oüÖÏ—Æ_ô®@ão"õ/ä{èúGÐ?ôýúç@ÿp?2þúg·|Aýk÷»íaf~ÞA3úGã/ c8¡ä²£„ èú¡…Ųâò2NÓ„ŒGÄÁUX\ˆþ¡®$O×aéŸ^'Ï/..­,¯S×BÆ0â°Ê/*2jÑ¿‘éŸ^¯oiiáäIHGG‡>Èoó›TÔ?ñ§ÑPSZQ’_TLÔU”6Ô×:ôox¹ôÒK³‚ìÞ½›³ $!&“Iú™J…Æ_¿Ï®¨‘é4UngSød„ñˆËÑdÔ׈ƒ®ÍgGÿèù éDRê_ŸïõéŸE'Åílv»Ìe%GµšJ«Ùà°9A2>1Ø,F½N^QzÌã2‰CýãÚ?@ÿÆWÿú}o€þ !ôy-§©NYRQv¬´8§¬ôXEYneynUež¼2_^]P+/TÔÊ”µEJE±ZYZ§,ÕÔ•iÔe:u¹6 &m¥AŒ^¼V´âµB¯-×iÊtšÀ«V-æ)ѨJÔªbµª¤NY¬RÕ)‹T ±ŠBe­LQ[¨¨)¬•‹õæ×T‹ä‰WyU^uå‰`r«+r«Ês++ŽW”¯,;&N¦åe9å¥GËJs„Ç–g—g—)):R";\\t¸Hv¨¨ðPQÁ!YáAYÁÂü¾äíÏ?±/˜½y¹$q nðÀ–»@¤P$ÿ€Ø5²‚ƒb7v–ìP±ìpIÑáÀN,:"vh©Ø³%Ùå%GÅŽ»»"PJU–RU~¼ª"WŒ*Q<ªòäU'jªòÅFž_H¢¦@Y+^¥K³`a 9u°Š¢(u¥Zu™VSª ÔrQhõ¢ k+úʳ¾²^_i Ttè´¢`Wh5"åU¹8"Ôª2q‰%«ÅŠš¢Z¹LD^•_]‘WYq¢²,·¼T|ÚbJ·«Åç1…þ#“’Ÿ'ŽÖ:ôýôo,ô¯® ÿ`àüMÿÑ{œ-~Ÿ½½Í)âosôÅgi ÄÖæ•b•âóXN9¯ûóÇôOÓ?o`9>›´p¿¯o]ÒªÛý".‘èÇß#ÒÙáí¯«SJ[ ]þî@Úééè‘øŽgÛõ¥·[ädoO 'EzEN:ÙŸSŸ‰@"9%Ñ¿ ‚{$¸kûHÚ_wŸ´7{º;¥ØÑÒïò‹ÐWñ‰âL°¨KN_)Å)X® /ä¾P!ÿ¼„ûÅ;TÂC…ÙÔŸð²Jd!ï+çm}åÜ*çbØãlÝéÇ .ÿÄs ú‡þ@ “D=[ê ò†ŸeÓ?’biiRF )ôýôý#QݯQ‘bŸÝÖ9ÀÆAÿèú@ã/ú—–qØ}ë ýCÿп!õÏ籸-uŠâŠÒ£Å…‡KŠ³Ë‚ØW”….ª?!¯Ê“W.¤WÖÈDê×·ËÔŠÀeóêºR­ªLWWªÓ–ë5ô÷ûvýÐVˆ1:u°ë‡ºL[WªQõwýP«•ý]?Äkû»~Ô*äa]?ªÂº~T„uý(ëúQ½ë‡,¬ëGA”®tÇHp×Üøº~Ⱥ~‡uý( ëúQÖõ#P<ú»~‹M_×y²¦¿ëG­Tzƒ]?”BØ×õCUª­ tM týPKr°ôú:4êõ•ú@y®öc*•  û’"P†• qh*å}…¶*O|ñÙDA-+ |à²À‘•S§,u;[|3úGã/ ¥ng“Ûig%q.³[êÛ¼vº~À¸“™]?|v§­Q«.¯(Ëq;[ÝŽ&ôýôoBô/pÛgy¦®Tœ =ÎæÀ³?h‰#d<šì­ñß–8âtšŠZy¡Côý€t"…º~tø}s9[\:¢wXûyo–1™ÑeoðºÍEÛý.ôýô/ú×i·h¥Húxæ¯UÏÙŸdrÕœÝbP)«JKŠŽÏ‘É µ•–ViüØÎ¨÷ymE‡Ð?º~¿ã¯}¾‡þ2 N[}K“67÷ø† –-[¶páÂ¥K—®_¿>;;»Á¨vÚŒc:#ú‡þú7áúçê׿ϧ™t:áoGB«ÛuE.-ß…¤Vœ6ƒ¹Õ°ÿþùóçÏ›7o}Öº]»vnܸaþü—ÅŸ»wïnjÐÿ9«uaú÷ùøüûÍ-*ôÆ_@ÿÆBÿTyÂÏ2’þÙ,Z)1ô/‘Ö4NËGÿÈq;›KKK„°e­[kjm¬7jßX±¬©Ñ`±4´cû‹/¾˜——g·ÖÇž±¶¦Êfm•"–°õý÷¢Ïˆþ¡€þ%HÿB¾7Jý“ÂÿP=š r 1¦ ½5`‚x–¿ù±Ÿ2Á•¢d¼ã°̦†-[¶,ZôŠÙÔ¤×Õ]sõä³Ïþ§éÓ®'Ƭ^ýöæÍ›êuð•Šê/ùŸ·¼»)´„^ø]´Ñ?ôÒ•dêú1„þµõÏí6Û­:[ØdÂmýSÒžÈibO0‚)£ÍíFKä,ñ/9Æêâ\;Iò8 Æ¥K—~úéß5åÕ“¯ºñÆŠêë®›*¢ÓÕË–/_®ÑÔ9lÆh3¶4×o޴ᬳ¾´víÛÒ M´maú>^èŸ ýCÿý Ä %?ï€mDúN ›±GÅ9ä,qf¬ô/ÆbIêÆålÔé4‹-*’,YòêÙgÿ“\^ÑØ ¯¬(9óÌ3ß~{e]]í[o½%—W;íõÑfÓ‹¬^ýÖgœqñÅiµ*ñg´Ñ?º~¿É¬qÚTü}drHý‹æŸÆÄi}ñ˜h‹ÅýÒ©öÏdjžvèÐ¥R>uêµ"e¥²ÉW]yýõӅ•——lܸÑh4DÖþ…f44µµUbá~gõ¥wÖ¯c¢Íˆþ 7“'Ovñz½œ\ ééé‘~¦Ð¿xªÑâ¯ýeý[¤þÅÿ½Æ¤’$mìV½ÛíGJVV–N§ª©©¼öÚ)gžyæôéÓŠjVõÑŽíGµÛÌ1fs]uåÒ,YëÖ\Ÿµ6ÚŒèßÈ—,Y²uëVÎ2„455- ’úgÖJýÍ…|c~í߸¶Y“4H›ÏÖÜÔ´lٲÇÖÕÕTU•½úê_åò u]mAþ‰7Ö××{\¦3îùt÷ÓOÏ‘fY·võŠåˢψþqíÐø›(ýë÷½žî1Ó¿AÛFCUmqN9@ãixÖÈUaì%Çø Ñš¤I WÚ íí^ƒÁ°bÅ ál¹¹9¥%²¼Ç?ø`[VV–ïõ8£›ýCÿ =IÆ®±õÏe¶[t¡‘£I@ŠÆb9„$,N{Cg§ßétæååmÚ´IÈÛ–-[ŠŠŠÄ˜v¿Ïa5ŽéŒaú6>?w¿©ýCÿý ýkVåŸ8~–é×?ô¡jížžîŽöööÎŽŽî®®6ŸÃnÑõŒè=€Æß„é_Ÿï·þ’Êhð¸Z}‹ÇÕ2¤øtFôýô/Iôϸ5tÿHBȸEëóZƒúç ŸŸ»ÏÔ¬Dÿhüôo,ôO™bøYý#ýCÿÐ?ôôý ÉÔõý#ýCÿÐ?ôô®4þô¯NQ¼ãÃÖ¯ÙøÎ[¼òÊëè_E¶¸I­,AÿÐ?@ÿ’Mÿ4ªÒ¶®Ï>üI‰ì!d #«¿m{G[WŠþÑø è_Réßþ½Ûe‡<®BȘG\öí@ÿÐ?@ÿ’JÿøÈ -ç4MÈxD\âCÿÐ?HWR¡ë‡ZJ¸þI' NÓ„ŒGúÆÌÕ¿IýÄ3úèßXè_Ÿïõtw „  Ö¿ÓnR<“Ñõhü[ý³šÕRÂôO:)8M2>ú§È?±OŒaú§E:ú2\ÿþú7|ý“Î8êï¡„ I¨ÒÈÈÖaý?ý³Y4y¹ûZÑ?BÆ'­ÁƒÑjÖxƒúç÷;­a¦tôe¸þ…ÆG^ûg0A\.g˜p:;;¥%«Õj’<ú×ƉÐ?“Z úGú—<µÑôï¶Ûn»ÿþû—yã78ïÀ„SQQ!ý(½øâ‹·¹à‚ ’¤ëÇçú×ï{è!è_Êé@ò“<=‡¡fMÞñ}­Mè!ã£MŠüÜ}V“Æë ê_›3tŠHGú9L×H’¨ñ·ïŒƒþ‚þ¡èúGú7‘·}Žèä;‰ž¿€þ¡„ <ô ýôý#ýCÿ’$êúUÿꤠ„ è@šê_Ÿï¡„ )¤týÇAÿœAýkµ™Å hokS-§iBÆGÿj¥ƒÑë±ô·9B‡¡ˆtô¡è c¡Ò§ý#ý£ñýCÿAÿÐ?ôÓ4!èú0\’©ëúGú‡þ è!è]?2¢ñ·^_‰þ‚þ¡¢Âýv}¼Åëq¢„ 4þdHí_ƒ¡ŠÚ?BÐ?ô€Æ_ôôý ÉÙõƒÆ_BпÕ?§Óéõz9¹@ÒÓÓã 2÷HΞ¿4þ‚þ¥–þ]|ñÅK‚lݺ•³ $!MMMÒÏ¿„ôž¿QÐó—‚þqí 4þ‚þ¡è <õƒôýCÿ à¡o„ôýôý#ýCÿèú4þ¢„ èúèúGú‡þÑø èúGú‡þ è!èú Ðõƒ‚þ¡€þ¡„ è]?€Æ_ôôýCÿýCÿAÿÐ?ýCÿAÿÐ?ôý#ýCÿ’º~BÐ?ôÐ?ôôý£ëÐø‹þ‚þ¡12yòdg¯×ËÉ’žžég ý#„ cØø»dÉ’­[·r–€$¤©©iIô‚þqíÐø‹þ‚þ¡è¤+tý „ è è!èúGÏ_ ñý#ýCÿÐ?@ÿÐ?BÐ?ôÆ_@ÿÐ?BÐ?ôýCÿAÿÐ?€d€®„ôýôý#ýCÿèú4þ¢„ èúèúGú‡þÑø èúGú‡þ è!èú Ðõƒ‚þ¡€þ¡„ è]?€Æ_ôôýCÿýCÿAÿÐ?ýCÿAÿÐ?ôý#$¥õ¯¹AŽþÅ£sçÎ]»v-'HBŒFãÜ 7Üp]?!±õO¸_þ‰}6K=úGí¤ôü%„ÄSûg³6xÝ&ô®@ã/úG¿èúèúG¿4þ è!4þ¢èúG¿è@‚¡ë!„Æ_ôÐ?ôôÆ_º~¿è!<õýCÿýCÿAÿÐ?ýCÿAÿÐ?ôý#ýCÿ’º~BÐ?ôÐ?ôôý£ëÐø‹þ‚þeˆþMêgÈÉÐ?@ÿÐ?BпT׿p©‹a€‘~Hã/ è!è_ºêŸ4ýôý#ýCÿ’º~Bпaé_h$úèúGú—öúãÝóÎ;oJ™3gÎ òòË/s–€ G&“I?JO=õ”ô3Eã/!ý–þ €ž¿r „ôo=iüôý#ýCÿÐ?@ÿÐ?Bпô¸ísì> è¤tý „ <óÐ?ôôý㙿@ã/úGú‡þ¡€þ¡„ è¿€þ¡„ èú‡þ‚þ¡É]?!èúèúGú‡þÑõhüEÿAÿÐ?ôÐ?ôôý£ñÐ?ôôý@ÿÐ?BÐ?ô HÑ®eÅÙÆjNÓ„ŒGÄÁUV|ýCÿýK*ý«×WÖ)eæUøô„ÑGVâàj0T¡tý“JÿÄŸÆ*m]‰Q_AÈꩾ:xСè ¬>¯µV^ UW:mFªhï8lƒ®ZQ]àóXпøçλvíZN.„ƹARHÿœöz—³¥¬8[«®0·jmf-'hBÆ'j‹I§×V•—8ôÐ?®ýjÿ&DÿD¼îV—£E¥(./Ë)->ZVr¬¼ìxeynUE^uež¼*¿¦º@QS¨¨‘)keuŠâ:E‰ZU¢Q•hëJ5”iÕÁhÊôR´e:m`@§¯¥:µ˜²/U±Z)RT§‹ D¥‹-TÕ*kÄZ‚‘ç×V‹•æ‰È«NÔT¯Õ•'ª*r«+ŽW‰”« |ÂceÇ*JsÄÉ4’ì²’£¥%Ù¥ÅÙ¥EGJŠŽ.–*–.’,*<$+8ÌÂüý…ù óöäíÏ?±/ܽy$ nðÀ–» àD`Gˆ="v,?°Äλ¬¸ðP‰ì°Ø‰%²#b‡vkÉñŠØÑÁ=.ö{NEYNe ÇDaŒ@ É­®Ì•‹KŽTŠjåyB%Ï¥K³@aENQ(•À:e‘ZU$Š¥(œš:‘@A…V(º}åYÄ +7hËõÚ ½&m°xkDêÊÔÊRµª´NY*"U­(ÏEµ5²ÚêÂy¡¼2¿ªâ„8š*ÊrËÄ.ÏU+K\ŽF«e€¢è¤:)ÔõCª”ç£6¯Õﳋ´ùl}ñZE|^‹Ï#Å,â•â6Ó*Å#ÅÕÑÛ±µšÀôÒú–æµHË—Ö%­ÚßæiÄÙîq‰ôŸ<""Þ`|]"mtµuwùƒiïîé|ÇîNñ5Ez{ºz{EºENööœ<)¥÷”È©“ÁœúLÉ)‰àö?)è ¦Gì iOvYO (vepŸv®ØÅÁ}Ý%"íýNŸ( ý¥ÂÛ,'}e&X~‚É)J”TºN+ä}%›¥>r€ƒþ¡èú—–±Y¼nÓ ý£ñý‹§ë‡ÓÞ¤ª-*/É..<\Rt¤´DºÀ>'ÐÉ¢üXuåñjéZúê<¥¼@!—.ž/¨«- tâPi”ÅZU±V]¢S—¯™/ÓièúAÒªë‡0hˤ‘¬.– ¿˜=ØÝ£PQ(º y Ð,±ÏVvL”Ìò’œ²bñùªÅ.G£×M×ôп ½ñ‹£EœžÄéÏf6ø£Ñ ®­©³÷;ë¬/½³~­mFôÆ_@ÿ2MÿFÐø;&µƒê_œ_0‘½THâo÷çvÙÅ‘’••¥Óªjä×^;åÌ3Ïœ>}š¢¶J§U~´cûÑ£Gí6sŒÅ\W]y…4KÖºÕÂ×g­‰6#ú‡þú—±ú7&µÃíy1J;EÿÒ2>¯¥¹©iÙ²e‡¨SÕTU–¾úê_åÕå꺚üüÜ7Ö××»-1fÜóéî§Ÿ~JšEdÝÚ·—/=úŒèßHôoîܹk×®åäIˆÑhœä†nÈ´ž¿ÃmüÔâ¢õüg!#¸L´&ÝA¿#=Ó¸óo»ßk0V¬X±qã†Üã9¥%²¼Ç?ض-++KŒ÷zƒÞ}¤3¢Ôþ@z’=S%Ø“+;;üN§3//oÓ¦MBÞ¶lÙRTT$ÆÁ‹¸xo”3¢týÑ¿‘(5od¬ëu~WOwWGGG{{{gGGwWg›×>ä3‡?#ú‡þú‡þ’D Ág³×mr;›†¿‘ΈþÑø èúGHFýCÿýCÿAÿÐ?ôRº~BÐ?ôÐ?ô‚þÑõhüEÿAÿÐ?ôп‰Õ?K«JÌU¯¯׈Uˆ!ýCÿhüôobõOLßÔ êhoûì³SãšövcCÀ3ñ‚þ¡è ¥­Í •¢ôäÉ“½=]ý¿½žñyõˆUˆ©Åb¥áobë°vª=Ñ?ôý€´$E»~¨•EV«Iˆ™ßgO@z{»,æ–ÀJû?Œø´]•Ç-Þê׸\vƒ®z@ÝcbÖ>èª ú‡þ¡€þ%^ÿÌ-ªÙá“'{Ûý.Ÿ×’€t´»z{»ÅJMÍÊ€}ë;;;Ú|N‡­>˜†ñym«+ ¯{LØÚ#WMÐ?ô®@ãï„蟩Yqâøž®®¿Ïáu›qúëìl+•ªÂ¤ºG·ÛÚÒ¤H@`ÕýCÿÐ?@ÿ&VÿÚ¼6«%ióÙCúgjVJu¦VMSCMbnÕ„ê¼öðU£CèúGã/ I V·³i@&ÅAä\±#VÒ?ií-MªcuÒÒT7Qk_5:„þ¡è ®>ePý{ê×U12ýó ¦õµF}EÒØ ˜¨µ‡¯BÿÐ?ôÒƒTìú®.Gã€<õÔS1 0ðÖSOEÎ;ƒê_½¾Z¯)K@ê òø×¾ìõ×cd4«F‡Ð?ôýô/ ôÏ<¨­ÅÖ¿áº_@ÿ<–HÓk*5ª’'‚#‹^[ÿÚ…ãE[NŒ·âY5:„þ¡±sÑEÍ ²víZN.„Fég*µÝBÿ"­°¿ê¯a¸Tÿ´ueu Ù ›4ÚÀ¢­+íÂñ¢-'Æ[ñ¬BÿÐ?zþ@zÒúçu›œöúAMÿ¢M;^9RÀÔÊemA´ˆ­m`¸Q«Jâ_»p¼hˉñV<«F‡Ð?ôÆ_@ÿ’Yÿ"+¥ª¿1Ô?ªT¥(Œ±a Œ,šºÒø×./Úrb¼ϪÑ!ôýCÿý›xýsµ:mÆh‰Ô¿ÇŽðÌÁš_KÔø«.‹ícÜø¶jtýCÿÐ?HRºëGlý ¯ì«úSýÓD¿öol P¬(þµ­þ…¯BÿÐ?ôп ×?«Õa3ÄH¸þÅž2v<îÖAêßÔåuÊ¢ øÞé#‹XQük8^”åÄx+žU£CèúG× ñ7 ô¯ÅaÕLjT(UýÅž2v„gÞ÷V)4Aß|`\ÿ¢¬=èx²á¾ϪÑ!ôýCÿý›xýs¶ in’þÆý‚ú×)`:u¹ZY”€è4ñ¯=ömŸG³jtýCÿhüô/ ô¯ÙnÕ% ƒêŸ^S¡Q% zmÅD­=|Õèú‡þ¡€þM¸þ¹Mv‹6q;›£Xq¢×TNÔÚÃW¡èúéAJwýp;šlfM2¨þ´•Úº’Ä «š¨µ‡¯BÿÐ?ôп ×?—½ÑfV' .GS¤€µ6©U HksÝD­=|Õèú‡þÑõhüMýkHÌyÐåhTR|B§.9|ðã®®vSSQ[‘€˜šÕ~±Òį=|ÕõúJ’ö‡¡¥U…þ¡€þ¡"~Ÿ]äÔ©“„¤q\.»^[Ù_Ù‹þÑø è_Fë_¨Ðåhà•×4}mììðöôô¨Å­Í ôýôý#$ÒÕ鳘[ÔÊ¢LÖ¿Iý kôR…ïúþ2Æq;{{»Kd‡MͪÌÔ¿p©Ô£M€þú‡þ’Š‘::»{+ÐõhüM¼þ©²íÛðÎú7E6¼Ã+¯ôºýoë͵èúAú§VoÛš•}èãbÙ!B2-¢ä°5K£*Fÿ©ƒ^û7eÊ”™3gÎ òòË/s–€ G&“I?JO=õÔ” i£ûö|(Þõ¸ZÉ̈ò¿ïvôoÂõ€Ú¿„éßÁýÛõšR€dlDùGAlµGÐìGÈÉÞ…þ^ÿ"ßBÿ UH›®Ò)  ›È#h@jªò¾þõÿ”nW¢ £Ñ¿(=‚Ñ?@ÿÐ?B’Eÿêõ•Ó¦]û•¯ü«”o˜Ö`¨BÿÆöÆ/tý'\ÿ¤5âøSÿLÍÊß<óÄ´ë¦Ütãô­[߯bø¹ßÎ1·¨Ð¿8ïê<`8ôп¤Ò?j„H'~ý[·vÅÃý⑇())öy­Å²Bñ§Èúuo¢<ô Ð?ô4Ó¿C¥${‡îw;›Å¯»uÿþ½ÒȦz9ú‡þú—–ú7iÒ¤§ÎÈ1ñ¿KH éŸÍ¬É9z8''Ûë1‡Fú<–ììÀH«Yþ¡i¤q×úntþ$$½½îV‘ˆ‘&1¿è ™©¡K¸#kÿ¤t‘¤´þqÛçÄ„®@ão6þF¾hç.~ AÿÐ?ôп4пA/ù‹-‡„ è¿€þ¥´þ…ß ý#èú‡þú—á¿èIQýkn¨©(=¶ó£-ë³ÞZ¹ré[o.]õÖ²PÞ^µ|ㆷw}²5÷ØžFc5ú‡þ@æ9]?â@ÿH:éŸ,ÿÀ–Íkß|cé’ů,z寋…ç‘×–,¸qÃ*q@¡è ™£áâ‡þ‘´Ñ?»EûÞ»kÖg­úðÃôz]CCC}}½Á`Ðétjµº®®N©T–——¿ûîæ¬µ+·mÍÓ£týyê!)­›7¾½lÙá}ÍÍÍÂF£^¯×h4ÂU*•B¡¨¬¬\¶lñ{[Ö¢è è!©®›6¬Z¼x‘ÙlŽ¡âuÉâE[Þ]ƒþÑø èúGH:èß«‹¬VkKKKSS“0@©ýW«Õ ”ÚÅÀ’ů è è!©®«NÒ?»ÝÞÚÚÚÜÜ, PªÔétÂ¥ @1,韘ýý›;wîÚµk9¹@"NsƒÜpà éÚõƒ½ iO<µ‡#†þ jÿ¨ý€L#{þ’Q‰¦N§Ód2µ´´HWÖ××ëõza€Rû¯øý£ëdiÜøKú'ôÏív›ÍæÖÖVé À††ƒÁ PªcÐ?ôÐ?ôtÒ?ÇCÿš››Ñ?ýCÿI'ýóz½‹Åd2IW666FéþÏFŒDÿÐ?@ÿÐ?BÒIÿÚÚÚ¬V«Ùl–®ljjª¯¯—n¨ÕjÅxôý€Lƒ®„d²þ‰·Ð?ôÐ?ô„Çíl¶[L-ºØÓˆ)Ù\I¨~¿?†þÙl6ô®@ã/úGBR×Ô ñx\§†ÂãqŠ)Åôl´dÓ¿öööúçp8Ð?ôÐ?ôHqØuÚÚ“½½=bO …˜FL(¦s±é’Mÿbtýp:è¿€þ¡DŠÑ °Ù,Âë|ñÑÓÓcµšÅ\lº¤Ò?!ç1nüâr¹Ð?ôпôÓ¿I“&EùV´‘™§½±ª¢ðäÉ“íííqꟘ²··WÌ%æe¦Šþ¹Ýnôý€L#º~Äп8§ÏÌ«þd…9mmmÞøðûýÂ4Ä\\˜Tú'vbŒ‡¾‰‡þ¡€þelíߤ ƒŽÌ@!DÿÒIÿ¤«þ¤ª?£Ñ(ÜOªúS©T>Ÿý£ëÐø›™ú>M3Vÿ<ñ!¦Dÿ’PÿºººbèŸØkèúè_Zê_$±«Ñ?ô/mô¯»»[ºêOjù5 Âý¤–_¥RÙÞÞŽþÑø è_†×þEÖffû/ú—6ú×ÓÓ#]õ'Uýéõzá~RÕŸB¡» ýCÿýCÿb÷ Î4ýóù|îøS¢I¨½½½1ô¯«« ýCÿ Ó ë×þ¡i¯ÒUR˯N§î'µüÖÖÖöôô è ôü¥ç/ú—NúwòäÉú'äý«\tÑEsƒ¬]»–“ $!F£Qú™â©$¶þy½^W|ˆ)Ñ¿$Ô¿S§NI>BÏùî'µüÖÔÔˆwÑ?zþ@¦þô/íõOºê/ôœ_á~RÕŸ\.Gÿhüôý#!ý+)ÊU«UíÎøSÖÕ)Å\è_²5þÆÐ?Ñ?@ÿÐ?z毢¦tϧ;ív›ß?ô½_Ä46›íÓÝ‹¹xæo²uý®ú =çW¸ŸÔò[]]ÝÝÝþ¡idB×2²´4iòOÙþáû••C¶üVT”‹)Åôb.6]²Ý÷Oºê/ôœ_á~RÕ_UUUgg'ú‡þú‡þ).GS½QYŸ½óã6oÊŠ1˜RL/æbÓ%ÛS?bè·}¦ëÐø‹þ‘ð¸Í¦¦®²ºR;b1¥˜ž–lú×ÙÙ)]õzίp?©å·²²’gþ¢€þ¡dP t9šbñKZýëè舡^¯ý£ñÐ?ôtÒ¿ööv©Ó‡Ôò«T*…ûI-¿n·ýCÿýCÿI'ýkkk“®ú =çW¸ŸTõW^^þÿÛ;·ç&®<ó7„‡Àfó´S›©ÚdvØ [;ûªüyÈPI†ª-¦¼Ùܧv';68$ÀÌTv À‰mlXb ¹@¶Áø"[[’%Û²dɲ­«¯’llýIM´ËjËòE-õ÷«¯ºZG}Õ9-}:§Ïéééiôý#BoA×€òÖ¿D"¡¢“““èúGú‡þ”“þÅb1宿ôs~Åý”–_³ÙFÑ?º~Aã/úPNú7;;«Üõ—~ί¸ŸRõg2™B¡ú‡þþ¡å¤ÓÓÓ*úÐ? ‚@ÿÐ?€rÒ¿ÉÉI宿ôs~Åý”–_£Ñ8::Šþ¡A èdâv\l<[]}²¦úTmÍi¦E›ÖœºØxÎçu®Vÿ¢Ñ¨r×_ú9¿â~JÕ_ww÷ÈÈú‡þ¡· ë¨0âëoh¨ëhoqôY èHF\h¨÷ûœ«Ò¿H$¢¢^¯ýCÿ‚@ÿÐ?HÓÜtÙf5NDÇæSPt$#$;Zš¯¬JÿÂá°Šþy<ô®AÐø‹þAšÖW#a¿fmuˆd‡dʪô/ ªèŸËåBÿÐ?‚ Ð?ôÒXÌñØÒ¥$;z,†Ué_ Péú!fˆþ­oãïÛo¿}ìØ1~\‚Ð`ø|¾·S¡ýÛ²eKþ³åÇØ¿Ú´­¯þÉaçøùû«ÀÖ4¢*¿ˆ¢ÜûGµzÖ¿ô’·²âê_>j‡ûiGÿÆÆÆTôÏáp èAz =týÈårK*ú¶dEöF²—W¯9L¯µd•lÏÌÞ¾6õOñºÕêßcLo$Hm¡pøðAáÖüL:Eæ?ú°òÈ‘ר*}DÿÐ?‚ Ð?è_¶¤©×þå¿eWYqÉ<K®ñ7—ûeÏäš×­û…ãÊGQy`:ñ½?¼«$F#ãëßèè¨ÇãRît:v»] P©”yô®AÐø‹þåÒ¿çÕ‘óßc¹êß²)¹æõGlß¾}ŠìÕÖTIJÕéÊËŠŠŠÂkÿ*ß÷ûý*úgµZ?¨|ýCÿ‚@ÿt¢Ùí¼kÔ?• ¢èߊ,.ÌïÚµkëÖ‡¶oßVy`ÿ¶mËüîÝÏß^¼U˜þUúŸC‡Šø »ÝnåÀþþþ¾¾>1@¥ý·»»ûСª??†þÑøKú§«Ú¿|º~ä/r:¯ýS—=ôOù¹ØË{^Úùä…Wö‹Üõ£ú³£'Ž\WW;00àñx”;eÞáp(Šû}Vuúø'>S{ýCÿ‚@ÿhü-XÿV¬ýËÿÖÁ2Ó¿eïñCÿ–4ÏÌL½ñú«/¾ðÜ[o¾6;;-)ëß™šOÎÕ‹Ý}|øÐÁƒ8x0“JáG>üôÄ_êjOœ­ÿýCÿ‚ÐOè¤ëG®¦Þe;êæêÕ›g㯊F®Øó·DõoE…[¶ç/ú—£ 8 êêjÃáàâBl-¿ˆþ}u©îË‹u _ÔÔ9U_wú\}ÕÎVýïÙϾºTÿÍWç>GÿÐ?‚ Ð?žú¡óÑžyêGÑ P ¿L×8W¿ý¢ùZcKÓÅ-—äÂQhkýZAÞBÿèúA¿è߯)ß&?Rdí˜MèŸæ†}¶˜Ñ?ô ý㙿…ÉØ†þiMÿ̦vôOí¼Ë<ÙÆ_‚ Ð?ô¯ °Û Ó“!¤K;LM†ì¶.ô¯èî—cý#ýCÿJžPÀãó:±hæ“È (HHFHv„ƒôýÓB¼óÎ;ºú¹ÔÛùVVVÎÎÎêç|=ê÷ûõs¾»~õ«ŠŠß ‹HÈã÷9ƒãCPt$#¢áá\9…þ¡èç‹þ¡è€®@ÿŠ¥*q¾èú§¥_µ@ÿÐ?ôo“õïã?.ø+½Xë>õÔS%wÌ¥x¾UUU‰Dbó×}æ™gÀæï÷üùó¡Phó×Ý»w¯Ùl.l]ù^r»ÝÚ_ý@ÿÐ?MéßZj–еîŽ;Jî˜Kñ|×R ·–uŸ~úéÑÑÑÍßïZjáÖ²î³Ï>k0 [÷Ì™3v»]ûë¢èú‡þ¡èú‡þ¡èú·¾ú÷È#üâÿ t Q—šJùéOÿVãGÈ1+åÑGýùÏŸ(­c~챿{â‰ÇKë˜ö³Çüï7g_èú‡þ­Ë°Ïê‘Ù#8s”-RH!…”ÍOAÿÐ?ôo<ñ* T¾¯Ð?ôýCÿýCÿÐ?ôýôý@ÿÐ?ôÐ?ô¯Ìð;.6ž­®>YS}ª¶æ4Ó¢MkN]l<çó:Ñ?¾ÃÖúKñõ74Ôu´·8ú,Pt$#.4Ôû}NôÐ?ôoƒhnºl³'¢cs‰)(:’’-ÍWп’h^)×f#œxž§£Ãó-SÖ[yV/´è,¡õÆÕHØÏ?#í Ù!™‚þ•Ä÷j9ýp¬x^evâyžN9¹PžçKy.ïEÿ@‹¹3›@º´ƒdGÅ€þ¡Z>/蟒®ýãï µ%§ÙÝ[2ßR‘ŸeßU_eÙ´­Vÿò©3ÇÓŠ®3SþUéŸ,þ¡èú§¢Cºªí,§üÕ³þfVÅÕ?mÖþåó-þiAÿ$¥¦úhžúWæSYýCÿÖ~^ºÒ!]o9²Þ»Ñ¿ì”%5ÊË%ïfVfת,¶l döZê{_vûšÕ¿\—–8aöI¤Q8|ø pk~&"ó}Xyäȇè_"©þ<_ý;S{\–GÿÐ?ô/ÿ/:}ê.µ»è_™éß²æ¶d^ekù¯²â’y&j¶ö/;=û?c.ñÓ³†Ã£ÊGQy`:ñ½?¼«$F#ã«Õ¿Å…xÃùÓ+êßõæ‹¢çÏ’åÑ?ôÚ’<õ¯ÌÆ÷Fÿ¸÷O·÷þ–¨²ØŠmÇ+®µâ’%§+nD߀±}ûö)å³¶¦JRªNŸP^VTTPûwç΀Óôõ—gÔõO/œ¯²[»dyôý£kµèúW–µ˶Ûn‚þeË'ú‡þ-aqa~×®][·>´}û¶Êû·m{Xæwï~þöâ­ôovzL.ŸÀØPWç·7š¿l¾¶Œþuܼl1·Ø¬wî,ÊòèúÇÏ%úGþ¢åÚø»ùµù'¢:¿ýo~.öòž—v>¹Cᕽ{æçâ…uýHöþ˜¿w÷û»wo/.$bÙHº¼+ËÈ’]†Ñ¿uVKÃ>/™/³‡«ŸoY¶w«ŸoÙë]Þç«~^èŸÊvë¢ùl¼ŒõoÙ{üп%MÀ33So¼þê‹/<÷Ö›¯ÍÎNKJÁúW耮 ço®¾·¹ mµ¿*¹bÏ_è_žšòéù‹þåhއBººÚp8¸¸+xàôô¬¥5Þ²¦Æä©Å2@)ü2Í~ ýCÿпõÒ¿eûo¦òqï*˜M蟶찘Ñ?JúÇ37 “± ýÓšþ™MíëŸ\A{~ý|KS#úè,‹Ýf˜ž qih‡©ÉÝÖU˜þõYÛ~ò“¿Q*™öôÐ?È&ðø¼ŽD,šù$2( ’’’á §ýîݹó·n}Há—ÿ¼Óïµ¢€þA6‘ÇïsLJ èHFDÃùrJEÿBãý¿yeç“;þå—ÿT[[-S™ÿ÷û×p`ý@ÿÐ?€EEÿ>9väÅž{éÅçÆîD<Úm蔗‰Oþ„þ è@™éŸßÛ«ÈÞwß^žM=Ý#>¼|ùk%qlÄŽþ è@9éßDx¨¥ùjKKS<N'&b‘¦¦db4ìBÿÐ?ô Ìã³A!+1$kÑø €þ¡å§ û èú‡þú€þ¡èú€þ¡èú€þ¡èú Yý;ðßÿ)D‚ÿÿŒ™ßÿÞo+ßÿú€þ¡°ï°ãbãÙêê“5Õ§jkN3-Ú´æÔÅÆs>¯sµúçq™¶¤â÷ユNüß¾¦$zÝôýCÿ ͈¯¿¡¡®£½ÅÑg¢#q¡¡Þïs®vØç}ûö)²÷éñ?Jʱ¿|¤¼¬¨¨`ØgôýƒLš›.۬ƉèØ\b ŠŽd„dGKó•Uû›Øµk×Ö­mßþðïÿëímÛ–ùÝ»ŸOÄ'hü@ÿÐ?ȤõÆÕHØÏ¥¡$;$SVßõÛxyÏK;ŸÜ¡ðÊÞ=±ÙIIGÿÐ?ô2±˜;ã± . í ÙÑc1Ðów"<49yãõW_|á¹·Þ|mj**)ôü€²Ô¿-Ë…vüJS“§þ¥?Æ\Iå-(–þ “O00^WW ŽOFÜ ü¥®æî¦QŸ­tE«Tô/Sí–Õ(Ï‚×cñº-Ãîžá¡$îTñÍ®~“kÀ4Øo’‹hÀ!å¹ËÑgpØ:ûìöÞvkÏM¹šzÌ­f9`K««ß835› d_° ±ôÌ‚lý‹†\ò{4Î'&…¹ÄÄâQ!$b a!®0JTˆ)̳™üq™äòÊl-Q¶¯ìKÙõüÜ”p+Éô­yaFø±5&,.ñ‰Û‹Â\’Ûswnϧ¸u现<Ç;‹ršÂÝïoß½+ÜîÝýþÞ=…»÷…û÷RÜÿA 63î+‘úüïIÜMñ½d’SÉ,û>I2%+SyšÌ\ÉâT^ß”Ü_LHaø±TÄSåäA™I•ŸTAš–¥”®¿*äJxº§JxFÙÎ(ÒãÙe;IªägòøƒBž³œKÊÌ”_.:þŸÀ¦±œþúè €þ€¦õoË–-+¦lé½dÏÀ†êßïÚd Ãú6¿ö/ÓÁ–Ìgûá’³í1ŸÄÌÕ—=•åÉM€eùá‡Ö¢¹œ0»ÚP}õÄü7B31€ºû]¹r%ÿ{ÿòÔ°\•„y&¼_õƒÀý®¤"ýË® \/ýSiÌEÿ6ÂýŠ®ëÒø‹þ¨»_þ÷þ©ßѧ‘{ÿÐ?u÷[mão>¶ÙU…kéù›¹…<‹Ñ?€\î—þ  Y÷Cÿôà~ùÜûeã~Úø Zs¿´þ ÷¢úq¿Æ/>Ǧï߿χPÆî—¾÷Ï﵊ûݺ•às({F†{e:?DÇ£á>€2¶>¥å×ûÅ'½‡d€ üÜO¬oÔg•y×`¯oØ15=់Ä&¼ÞþÞž›fã >%€rÂ﵊ã‰é¹Ýý†þ¡}'¾ø?e#7 endstream endobj 806 0 obj << /Length 526 /Filter /FlateDecode >> stream xÚmTM›0½çW NFJ¼|_wÕT­Z©‡¨RÕöàÐÙ]úë;ö˜$D9ùùyæñf<8ô*/ô>¯ž÷«§]yŒ²<νýÉcÍØ1ͲÔÛ—ÞoòRóN‹>ØÄYH"Šë©û *ˆ*Ç£n” þî¿®B§û´K’ÑtK‹8ò6qN3”ü°˜¨¤XHÞ›¶T02Ô¤ÆQÉ5GÔµJkQb$|Ð’ºvUÏ»šâáÏ Š""Æáß5s¸Ê Âî Ðÿð‘æ²DàOŽ9L¦"¨f…”e }—âÄÇV¯±‡Q#˜l)Ž\:PsY Äàqp°‘ A‡MìD¯1 §NwAþ‡u•Í=èƒmJèÅ_FYzßÖ4"ÜJ ·9´ieVãP©Áq'tqv±¸¼ñ¾zÂõë™÷¯¢–RÆEjỜÕÛì¡vÀd>êsÛH'æìjí6²Â ð€1ù9>²Ãx€KÖ¾Fï×È´Íñõ"c§h!W7å,¢:3×7s]¼b•ÉözçIá ©Zœe ms‡…ë&’¶Pà*…öLœºó¿_ ²ƒßÆG~Y/œ™zX†åa˜8.R¼»™­þf¡ •ûÙñþJ £õ<ÈÖÜí”ަߌõdFz÷>,Íü@ä)£éüì‹”OûÕ Û;m endstream endobj 818 0 obj << /Length 1817 /Filter /FlateDecode >> stream xÚ•XKoã6¾çW>Ɉ͈Ô{oÛͦH‹lc,Ðn{ -%`I®$oâþúÎp†z8jŠž4/ŽÈó’<çÙñœŸ¯<~þ´¹º¹S±#&‘ïlžœ4Q 8 …GÎ&s¾»ŸöúØåÍr­âÄU‚ž¿æ:+ªgb2Ýé埛_nî|ßIE©±H”tÖÊJ…ììÔ4K™¸yÕÎ˵Åî·¥”ÒÍOíßÈ'n{:ë¦kIKÖü2T›—Õ“ÑÕ%ÉYØå¯ þð=~ÃS6ké‰4Li˃‘RÒýzû€„ºo‹J7牆ºÊˆøòðÛÍ—‡ß'&‚T˜ÀÐùШ` Hž‘øÔ¢íÌF»}‘ž»¸+¢ïK\»ÆKݬЇú™5uCOògHôŽûq¾›eäã®8¼ÕÂ"<ጘ=v ïöR¿«Ë–¶s'yÙ»=œ6‘ýýÇà5\>räÀàÁð©+zæ¯EÛ¾À=œ»}Íš–vÇ.BOõK/±/SnW“d›³Çr=·ímžexK~¸EEOMûV¤Û]S;xŸ$¸7 ßÕYVtE]­HL÷ Ä©ÅôCj§ÙÇ¡Öü¢šU/Mѱýñpz.ª¶ßÈÌV{RÙ£zì…|x•9>ÈÆ™Š|Q‘…fGºÙ]CybÊ4Aº Í¡PPÖ%¢1©¤JÝÕ,ª6µB³ ˆ!›€nM(bˆ§°/E·'ªÛl_æ€D&Lˆ~Þ\ýu%ÓDø2L`ž£T,â$ ¡Hú"ˆ”¨P$~äìÊ«›ûÒwn뫯TI'µ¯7÷R‘¤¶’Bù Ø'ˆÌäCÌÑü€Éª,ǘÚÚcnsãeª\ÑãŠ4 M`°)?îƒ 9[UQ^ê3‰ô¡­IdÂ$£\iÁKG™èÛB‹ÔpH0*žHHׂ‡ó`Ù#ÈË:;Q¹Œ¼0?Ä÷¶$jž â¹´‡Qo¡éñR.ÇaÓF)o¶[Õe±Ó’rO}ÌŽ=Îb|Xè‘éÝÏì˜++Å_ŸEzK„9úئ0¥“¯Ö”l6ll4Lü^WsņwÍUm´àò;!›%äéKͽ½€4m!N+[¬P5„`ÜGKœLrìífACÄ)â–02Œ-î-f ‚¹ÒP¿®Þ9>Þ4£ŸiYÃ2jYó mÓ4îàðÃMä™áim?AÊÏmNåîN†nûΘ$cÃø`SúÓxhåi2jå °qô Âپ «æúv¿AXJw¦uƒIߺS›P¬ç®h1°Àsx!u,x -#=ì‰$[mÞSé’F3 ±.tÄí¡Ê™õÔ)ÙQC’CYmBÙ‹–˜ŒÆ'¸Ë!ý";XRl¬E6\ªá5[8µ¢ P/:vØ’ZKçF ÏO~µ¤3ÿ‹ŽÁ™˜ >qØoih¸µ#;Ì®>œJ3!Ä!*ð$ÍJ<´P@ð‘{”Ý3I Lh–7í÷öƒ5:šÛoN_¶ mu3× ¹¦5ê<¦I ">¿jƒ3—ǧq7ÄÛ™sjšã‡iXC꥓ )æ2nQЉÍÍ]à’ÖP 0¦¯X§÷<“ÜXV“ÄvTUƒ5†5ܯZšb$•½ôß5È€ôLú—âp Ù– G ñ-£Tó[ûp2ÒÅëÂÜÓš7<¹­~èXœSTœ51nê·º^ƒç ì`\Y]¯ÖsÒQÛ53ÆdâUJ‘Èh|àsðM,=fß EàÓ[ &ãk ÍÀÞJŽ 0·¶Ž!ƒÐbWC#m­;­ìÁ Ÿú"•ñÛoϦå‚hö…G â·±ÅobL6d1©…}­ü¡'3Ò`µ¬²‹JÚæ°Ýl¦–Úý^dt{.a’nŠ~!¥C‚›oÜ$ÁÐ!Í@Úíé9‰‡ü²ªîrkÙÛ@"Ù6ï^ò|öCçš¾|ù€©»¶e hF.#—ŽKÇ'O-6)#·ºˆ¥î±nyŠ7^pÞ«…æ_ÂxÆïBøZ16,¯òg=|#è1ôÀ¡;Ü=âtÖæ—gu> stream xÚí½y°Õç«þkþ™?¦aÚÆÏ=3žŽîèèvÄÄë~ýLžîžñŸ3ñx/ÂóÆ6 ¶Çnºi„ÜØŒ1ûj°#±‰ÅHˆEíÒݯt7Ý}©ª»Õ¾ïUwïW•UyóæròäV•Y÷›ñ‰Š¬Ì“'OfåùÔ¯N<92: ‡ŠÅ•+›Ÿ}ö½nnn´…+v²É˯Тé3>3Ч'ƒËq[8yäò毰öY¢m¢kš¶T*dó¹r¥J^_«ÐªµJ¡\Ê”‹i=2–(‰deTXäT(3È‹TÙT F(®É¨ªRâa}­ÌGE…õ-6T9XÛØ`±Ù`]“M)œßªœßzÌïS24c'µ*ϬY’Â8oõË>ùµO‡ëC¬}ÜÒ @ý Ỻ8¯Uõë|{]¨Ö0T›êYI+ÛЮõª–жJY…¦,ZN×¢¥ ù–®úŒ2¹,ÅÀ4_áÒo%œu“„®š÷p;$¬ãáÍ6{xÓj n18tÒÀ›ö¸-.êJظ–pÖ¥®CÖ¥ë§TÊÓUW‹y lÍÃõcÏeãáP “‰Òy+¼*vF¶ÃUÏÃíòð†ñ† þ*×øÍ Ë.éb!‹,ç³ÉµJ©ŽQ×ò©hx)—‰×–” |Þ^Ç+ù|. -f3±F[ \2k¶L< dÒ‘J#}—’{…Ö‰²0ؤ„©„Ùttl´ÿÂÙ}ôÖïßÙÿчΞþpd¨'V$*¶IÂ.i‘p>VñpµMvTÅ<~¶¥MÒ”~[jà*Ÿm”pýJ^+‘ñ. \8'­Â÷¦aJÆSq(Ÿ|6qyT–ɇ£C½ÉDh­Rà‰©¶æ2±šLä™ÔdRÝRq+Â`–Ùb+ú- ¥ŒÐ>¬•àþíÿñ¯þÕ_žpÿï¾ëïúÑ?ì¾óöûîÝýäã~xôÐÂÜdoÃmlvÌÃ\íÃFT¼©û/j{þ 2ú— ¿~ímn›)åÊò« ÏOêz˜¬,±3™¨EÔêÎ7 \X^šgË„âjã6#a³WÊY¶‡…¾Zk¿ò•ø×ÿú¿íß÷a¡.ÒÅ|cy±¢Ð’ÿþßžüÃ?¼ãŸa–3?91ôæ/þä_~øô“¿:wöÓÙÙ‰D<â÷Ïötyé·ÏüøîÚ¿ï×£#½åRÎSÁ°›%k—pHÅÌþ0v÷`áû‡ï¯m»õëH+„ýžšnVá½çΘ›J&¢ÿ\O÷Ù—_«p_­UAÛÀ’L~UÏd2‘ˆú…L$¨–4Ã`Êgjb[&³”IM&s=]Û2iÁ_rf û} ³1[‰…K«¤±ößø 9ö—¾ŸÏ¥j‘p!](l­}ÿðèç>÷øúO/¥IÆ7E8¸ôê¾çïùñÏžú$—I*ºÔwÿÏî~éÅgþ1¶=î,ë·Ûó6P¨ØxÝÖnŠötd2Ò§hÝ„{íÖ¯"¶ÖlÈÀ‘ðr£ Ÿþ$ŸM ÌLOÐk®ÎðP³ ÏÒ&ªŽ„¶2Ée“J†/I2©T -É3Ù.“a‰LêíN…ÁfKHÙ2›o¦¶_5Ç–ô$ü³{O|îsOÿÃ>Îç&ÏÖg($N'“ó7}ñ‹‡ß?¼ ]Ôl!Ÿ9{æØžÝÿtqàB&Ób~nâÇwßqâøûéTT«QbW}rºQ‚vÁP®P†‡i­9 9ËT,,laH\Wñ:¿ŠõllMÈV:“[îÌiT¼ëÒÓÂá^cúmy¼%áR1ߨƒ]Ùtœ8üûwþò/ÿ‚®Ìk¯ýJw×¹L:N,ÌM U˜æ•§X̉™dÒ1-ÄLÒé˜RÂÅBVž‰ºL&E™8ËÍ6:|1“ŠJ¤W‰Ù"ºVõð+¿›üâ?¸ñÆ®\¶æÞd:ÓlŽH?þØÂ¿ÿ÷Cÿßÿ;Sȱ¾,V–}O<öËW^z.•ˆ°9zä­Ç{hvú²V0,‘°Sn:6oP 7Dj6$–yXÏÀ6¨x}­º¶V)W*År©P,å Åt®˜ÊæM’Y‚HJJ./%+4È6gò52*Ùdk”²99)E²@^I©F¡A¥°5_£XnR’R!Je©T£Rªl£¬B™^µ¦jµÜ¤Ò¤\®•% ÉÖZë šKšÔÓÐÂZ LØêŠ¿V…_~.Œ <ÿëg¾õ­›ß}çÐßÿýß}õ†ëSɨÀÑ#o UX Sn«ËLRÉ1“j#êo- nÈD̤©Ž?8¬%e0lWS°Ôl#ÃTUïûÙOR‰°À÷ìþƒ?øƒ©‰Qšß2›".qHøÔÉÐøã×]7›Éd„æa¹o>õÿ³?KNMf%è¼MŒïÙýS“ÃñXM8´øà÷ ôwUk÷Œ°$ÜœÉíjNâ¼`]q^²°fà]’Ij]å$ó°è^©„·§ßövMâä¦Zõ=,•°øV´±ÔÉjó5ñnO)_B“RÅhvsýJýFžFG\‡n+ã»ÿ®Íwiëà«768i­pºÚ€'óDËM!èFùLNÔªðôÔH<’±ÿï袚™Þ†CKB®©^R}è*md2I™‰K—ú„qÉV&Õ¢4”¢RMŽ ™ ×R6½ñ¿¾÷*ÃÿùŸTeRkÖp [šÂl«÷Þ{ƒ^i~ÏÝwÒü/ü9Íá`Ól^êIXéáÙéôŸþiñÏÿ¼œLn5,çs™‡^»å–ÏÞ}§R,dÖè¼õöœýÙOï ®ú£‘e)~ß´l ñØ#?ïî:]ËS­EB&a†‡·ªyËÆUÉ̶Hx{û°R³Â¼–¢·Ú%šñ°š‡‹œVu/sá–{¥Qqc‰É6 Þ– &‹5nñ·iÁâÞ· ç0Þæ Ñò`¹ñÁHûƒüŽ *’P…CÁ@,º•ðÖ[þê¯þ÷¿þë¿’.ªp©”Û&áõrÃÁ@4º|ñb]Š?ýé¿Ð¼ÀÝwÿˆâƱ±‹ÂÛF&ŬTÂtÍL¶dr÷îQV_ûÚid¨I©©4MbcÇàíf[øéOLÅøêW¯§×qŸ¸œhšMÅÀJ —ŠÛº¢ré½{?{î¹Ï‚¡œ¸m2‘_Xø,Þ,3…«óp¥\8wöÓGþy$´(Å7?õgö§‹þÙò—_|êÜÙ“™L\õÞ E$,]˜SJX#*æ“°ä¯:eô+Ó¯"NÞÖD¬ ëxX9Ã)ámîm²µ|Ýb‹±!khÙ²™aͤr-‰×¨{ÒoIy+\µRnTá𒌿ø‹?¿öÚÿóÈá·¥ _~ñiªÂÙlrû q¥f&‹?ýIÍWôJó»wÿæñ‹ûĵL2‰mŠ•¢L&»ïªoøÀ}4¿•!S&vÝš!1[@ÊWo¸®ö¥ð_ÿ^¶¼Q˜t¬,q,CÂuRD¹NµÖºÎц©@0[,¤–Ó‹3ñ™Xv•–”j}Õ´îæHÓÇ:t©ï¡²äŸ ­ø¤øç'eKˆçŸ{lp §~ݶûšI˜£iB²Ž„Ë, +Û(¶+z«‰X[ÂrkIX³›&$KÔ¢bI‹ñÚZ¥ºV.UÊÚ„3´ÕmÂ’FãÂÂÌ6Š5¬¶ ë4‡óJJ5 Ê JjÔ›‹ÊrJåR“² ùT)oo®5—%Tªbãñ¶4UjIÁ¶¶_i›ðVšJ©¹¹ÿ ú‚hTáÀlh5 2>6ôGôoÏŸ=ZõKªpE¼¹~óÅÆZ¥™ÉŒ˜ò'÷ì¡Ëò†ºµøùÏT2©ÅKÙ:Tg³t=oe²âïï=_ÛðþŸÑ¼ÀOþe…Ó#—ú„·Z2±áæ8 ³Ýuç?S‘þëù{zýɿܭb¶RVPkIU  Ÿ[<·§ùB!5¾üöäÛŸÌœÉÔþžK¥ÒuWkH¸Rôûg|à§S—V—æØ&_xþÉé©ñª0Žâ¼Ù'ᜠoÿ·ŽÑ8¬±Pý;­¶bF$¬tµV F õ˜ª†QÈWï´påJ}|'ÃmÂÚm¿ö7ö:~‡¥Ü(4BhUÔ›U©ÇÏÍX¤*ƒc8ˆF\ +Ä*<=1´º¼Ðdž¸<: ̈|SB^_+mý–,ç©0‚¦É’ô?øþwéÊ$wmϤáz›pSÂ¥,•V"“ybh°G˜—ˆ™hÈÄâ*f»óGÿDÇòóû~Bó÷üø.š§×¦Ù&„ÂT*y D ×bZѽR>™ýð±ÞÇN̤¡xäs?ßsj?6]n†ÊÃS”sÉdìÕ}/~øÁÛKi6=Ý'~ÿÎÁÕեƈүª:MÇnÍHæ5ËY化™«Í·Õú|yÛ… d‰™óŠ…ê13Å[TEä¿ÌÏjó*q2]öâŒdÞx‹u­& Uª&ÙÐb­µhÃôq±õ¨ÙÚP6HICî¨Ó¼&›W©Ð±!™Œ7ªðâŒÈôÔðŸþéŸÌÎŒJ U8¸º´ýîã½Ýò€$=qìãò%b&ÕJ-®UíúkUšI`†Í–L¶"aÆj`™­ûÂÉzïˆ{D›ýxÏ–öŸ¯›íS¡0´S¹`ëâÝ&á‚dU3Ù»ã‡þÇïÿÇ]“fŸèyäk¾ölßSÓÛd:]Èëx˜¾ygg&úå½£#½ôõ¤ÅÌÔ0Eì3Ó¥bVzW5·„3ªíñfE«ÆÉLçämÈ÷nïP‘×ê>!.\“¡P±2HV4,«4\HÝ«µ„ÏÆ,!ë9Ùš™éÚ8öŒ'(µS¼†ÜkJ¿Û¯LI߆ªP…ÇFûþ)‘é©éÛ™é¡ —k÷»mÅ0 r#ò€d+b&%éýnåÆÌš4ß”3Ó¢L1°Âl=d°þ¾³2§ KD³‹Y¹] .HÖÖSžxû?>÷o<ô.v}î±ÏýɳI/ÖÿæKåsµû—3Ù4#$®Ö¾‰ Ο~öé‡G‡{|ó—•LN ¾ºï×ý}ÝÙlz[W:­XׯÊÅ’RŽz¿âª&ÊÛëtѸ…S]Ńu§¨qÛÊm*Õj­ëk­G~±˜Ë×i“Ú„·HiSë\GµMx;µFéŒzâÙÅlANN…Fq¾Aq‹bBƒ’ykp±Nm¾(P–#m–´ËVÕ©4z+(º7ÞVšÍ¼õ…Å:ÍU5ŠÕªµ†…m›k…¾2ý ×0¥«ðØHoa\ÉÔÄÅfÎÔÃ`EÝ‘f2¬žÉäV&iÕ›Ýêm ’LæÇ•l“‰jlY¿uRÛÍÖ­k¶F,s¬–„· ùüÂÉ]¿Øõŧ¾ø7/ÿ5ͼrñÅD*ÝÐo¦¡bvÓi!—Mww}ø¡ûŽ}ôÎÄåþù™‘…ÙQbr|àÌ©ž}zïà`_*WQSÛà «KØ6çìR±Ó6ær=lÛ¨÷–v¶Ö7øŠ±1~•£¢9ñŒþ[í,õ…Ûh ×b<ÆÖî ¡!‹¨É ÷né·’§>—«ð»“—ægÇæjLJªp¢Þ¼­²Hû˜I3™¨e2*d2¹-“x­5X¥òÖª6å¯ ™dŒË¤¦}‰umÓï–îhœf« ­ÁšÕ‘p!µŸ#÷ ‡³¹dGWV–‰X>Ÿ£ßmµn3åœî¨›æ¢âÖ©Ø6.2 rZËwU¯[ŋӬ]²ÕQ.Óº%³€%÷J!=ÖžV)+ªp©~[t±ª°îö·µúEÉ´2©Ý£])¨¹7+«Ô”LC&µ>{TTÇô+—p­]¢f¶ª–Ù¨ðl“{…¾Iu K=œÔjQ6€ âB½š¬­¯Õ~Óµ­¼•ÏÍ*n§m²'Û¨eƒƒ`˜s8gnŽT©åÖÕ¯9÷nï±™#7ú±PÞ\ßX¯ úeÞ"¯_½xL6ꙨëWŽ´²S„YoLd²^³ ­Ô¯LÅõð 6K½He:p-ënŸ—IXTq’±UZ§bk-Æ6ÙØ1!õÆÕä¤ 8ϕɇ¸9(ÞëšlŠ—ž{Öyޤ¾{êí`Z¿eµž½Œ¸w»c·$\Ì'Šùx!—¨‘'¢+ñèr<º#j7b×ßQ ˆH¸v/^8ä _˜X]Õ˜®.W瓱åÕ•¹Õebve‰˜Y&§Wéuf90½´(t¨›Zô L|„a¼Î„o~¼¶mmó™•ú¶ËÛ7©ÏL*6¬ÿ/¹py¡öïäØÂÜ(1?;Rgxn¦Æì414;543uifòâôä¥é©KS“§&''§ˆñÉñþÉËý—û&Æú'ÆúÆ/÷õ^&F{ƈ‘îQa¢ktèÂqé<1|éÜðÅsCÏ—ˆAâÌÅÓÄ`ÿ)b ÿä@ß©þ¾“ý½Ä‰¾âÓ:Ç{»?©s¬§ë“ž®c݈»Î`ºêÐ¥U»º„+.¹¾îã˯÷D º8k×']¥t­ö*Öö÷ N óàÀ©Úµ=x†.òKµ«}h°vÙÕªÀ¹¡Kç©Ö ¯ÕŽá.¢VYFjP é¹LŒöŒöŽÓëXïx½¢Õ*Ýx?1Y«†Ä Uá©zÅœ®ÕÖ:Ts©þNó3Cu†çfGæf¨š7ê{½îÕ%p™ ™ÔX™ Ö˜«ÄBM\‚ÁÈfDÍoþšå"Xt1YŠGÉ„D"ºœˆ-'c+Áºè„miÃHú¶¡@}óÚ¶uU. æ$…Æê:%©’Z)ŸB®&Ûºuk¨4GÔÜ^0sû=&9æÐ‘ƒ¥‹]9§ÑI?g„­=¬Èm]@5ÓiBd f®>¾Ç ¸òÇ<¹Ríaî˜094ÉïmÔî²bä¦ÅfÅM…Š{ ùAÒ¸gp«Î®oÁhƒ’õÃT“ ë—‚ø[@#þÒh=¯nÈò6š­Ý‘©?“H¥9¢˜O lo—° ý6Ö5eXë욎niIÞ­ímg°µuW³±×hSƒÚ5oc C«šÔXÍv"Z·%æRqÙÓ*n[ä|;Z’9!m·¨Õ¸l­7í:,^÷ÓoÉÃú-·I¿l ;Lj…-J†I›¤¨$#Âÿp“-Jú¨<DÿÉ yN¶ÿy]؆‘'åñ`Ës¥; õ¡oª¼pL²ÏWóÑðl¶_i'ÏE¾­RðÕ£Ò6Ót7(©ˆäŽj‰[+aר¸ÀR±—Úoc!— ?ÀÔ°™+VƒgÏhv»lø¶hìS(Û%^ÇÜ[²èÞŒ}îõž~Û'a7«Ø\`ìŒí²u'ªÆTcTÑ-“¶‘2˜Ô¬YåêY·âuνN†¾^Õ¯DÂëbg ¨ØZ`lØÆfÃãœÁÊÈád3Z6/g–«-©[_§2ªö`êÔ•m·n^åR±Q¼6¸ú‘vQk£„ݤbOØØr„¬ýÔiµl³¢ÝåSÁ¡\SÖ5ñzܽގ~™n#ÛKXØÂj ¨=…Dö$>&W&ë¯á¬ê“õôѹkI†–µ±| g1Ôý@Vkî †õ y_ÍGK0(±`ßMfî~R4LØFÜ$aUœlŠK\]eŒÚØ‚mprN/`¶ÅÏ®2¶©?3•kĺÆÄ[2#^=÷Ú«ßdÇè×­Öh1©b×ÛØ Ûád›äl«Çò¡Ù,[{¬kN¼^uo‰åÞ¤ÛtçV 머=±=-í²ýZÖl”®v.\ÿ…µV¹-okÛl }“î»%l{EKmlUÈÆlRËN™™÷ßCWzÕIÁZömEó4sÖµ.Þ6»×+-^–°í±6n¯í •[jfÝî…ÒZø8 +·eâu¯{“ž0›§$ìzó…Ç%÷¿g¬i9kg´ì=?{Ï´Ì(7kM¹¼#3”ÚôîD÷z\Âfl¼¼8wôýwÞxýâÀûðŠW¼Zy%޾ÿîÊÒ‚žºùd6MÄV%¬".%ªd¹]dRáB.¾#%ÌkãÕå…÷Þ;ÔÓ}fb|` T¡~+¸âã10é7\Ìå2W:qÊe3¡Õ©xKXÇÆgN¤oÏB> °ªPT­ÎžùT×À¹LlÑ?»¹¹±±¾^îÄi}}Ž-àŸÍ¦#;^Âê6>þD4²Ä÷߀ªVT¹t[zé§h"'Så;w¢£«=myiVµñÐ¥Þ\6Ž*€½PµêcÿÅ–ÏÆ&._ÚÜÜ,•J,a:ºñ±K¹LÔi ïÚ>‰ e3®B*a*¡øjs[ñlh:çÖ”¿õ;õJQ.'OþÖÓ˜>Ц„Y=èúÅÁîJ¥R(r“Ì09LÅb±\.tmnJ8Þ„K¦׺GÂ-–$ÜÁEmo‘Su˜„o;H ëÚ%aÕHX-‹ke 9Sòl¥+añb“^ub>Êk[–Œqå32ÑÝjÎbhe+ 'tóïZ¹F»NµKŠÊЗ¹=êž]ÝŸuœŸ¾ê^žxâ¢$y#Í?öèÞ'Ÿ|Ô„3éÈ f¤o¥¡²t+ÕyÕÐÚÆxÛ> ëS—žæBåŒl¡¹”œ[iÁ°V¥f,4š CTW™(›²J*-륵Ðú©vCQu%le<ŒÑ8Öè(%Y¶Úû«Å…¿xà>aa,ºº] E ™t˜Ô$H8Ë1Qþªo…]+ÓóÊ%ºkµ617ÑÑI$lÀ¨æ$, A KبÌMgeBÂ쫚±‰E[Õ O‘,šM÷Àí5ª¡=¶«¨œ§×ÆëÁ. ›¸¤Ë¥ìí·ß.Tóoì§%û÷½$¼½ãŽ;‘°\¼5š!Iɨ„eÛ™²ÊW-ÇzZœ‘°R× µò§däoBÂ&~#[”°òg&CŒĜGÁYÕš×ü¿T÷Ë8Õ®*jgH˜}ºD*åâM7ÝtõÕW}á ×P<|Í5Ÿ§ùo~óæj¥$“°Ìº2LHX+6-a©Ûµlß^ rq‡š#LÄ´†"a]Ø6-Ø ú¡m=¼áÉÖœL„—&‚vWÕ»6ôKªXÈ~ç¶[¯¿î+ßûîmÅBNùÇ»ÙS”p>ŸÏpLTÕ·ÒåªóÂŒòU+Õµ¦'::C­»¹Ñ s.4 mްR ¬üÐfGh<±«½uÙPC«ÑðÒz›°k‹j]ÂVÚÉmoækyΦÓÉÝwýð–ocÏÝwf2)Ù8B «Ô¾–IØè¼V‚öJ˜*-c¡t­ç«n«LÉȇ±•jyd0$ÌÙ¡ÕíÁzuËÆß;‚ÿ蔕Ôh°g±w„›‹j=¶ÞGWª›³2öR)çÂáàÁƒ"‘P¥œUí¢Æ®}édx°¿&á\.—æ˜d×­t¹j2ÙB£éµö¢œaOtt5 ÷wÑñ2ª {ås.ìÚx謶¾¨Þ½äÈÃëkezÕê'l¯„=:A†$ Ã3F‹ºKmjÙɱ¾wGo[†„[)aÕ‹Áý¾t±cGà„„‡.öêJ˜*àììL¹\JéM»4¦”ë'2ðÌÌ4)"aU&._J%#¨2Ø U«‰ËCìÚ—MGf¦F?þèýD"Q,²8‹Åx<þáGèHéx!a%±ÈÒR`¶O”KiTì ]È%–3ñè²nŒ„½ÝgßyûàèèHG¶EŒŒ ÓÑÑ1Ò‘µÓ‘0‘ˆ-¯.ÏGÃŽcQŸ‹h7ìˆë#nǵG*_á©}¹Lm ÝþÞóG¿óÚ«¿ë<è¸èèèéH!a !ÁK^Ÿ"0ω5ð1yùjÌgcôƒta~r|ìRçAÇEGGÇhâÌ4%kqAÑVuÝ‘ê6zà&Oo§_u¤)Š;SúmXŽ¸Ú²½Ý‚]‡ ðJëì”ê€u a€„ $¬K>M'C^ìOŦÂã£xWÂä±hd¥R.}æÁ‰Š /Ó!àÓxQ™TÈres“6^+{*6ÝiWl¯·ø÷EËŠ_Ø(áåÅ™D"F[U+BŽÇ#t ÒØ>¸âËfÓë- —KWdÑx‹ËÐÊb¨î`BÂÙtøòèIl­ZªVòem­Df ¡Ãcûjµ\.çó¹D  U*ei4Þú2´²ÊÌI˜‚™ÁþóTI×*…J9çQªÕb¥R¢b3!¶ÏçÓÉÄJË(äÓÒh¼-ehe1d;Ø áRÖ£T©ðM ‹±}2¾ZíNŒÆÛU†VCº#Ô;¬K¸ZγŸ„e×sÇÌmÎÞJ*añˆbáÅHÈ×2b‘¥¶—¡•Åîõ€mÞXÏg£V$¬õÜmJ¸RÉ+Í.Wg•ˆ_+ZËÅUªiTó$B!OdOÚ’f«ú(.­²Y,†êyPÝ5ÏŽPï­kA¹r1-¥æ=ÅÛ­À¸¹PúV–L¶¹¸J¶PusÆVJT%¼²4³´8)ƒòÑßʪ.YYžá)ƒjª{ç|k½â¼é¡ %a]R‰à@ß9ª§•r®THI¡:(["[.M ]¨LÀ^˳¹VaÊå „G<¢¥ÀdÀ7.…2ÑZ"[¥º\¹¹”ÅÀ¤¹20öÎóÖ\1xN{_Òñ_ot<æ%\Ê©úVœØrÖU«•Íß —T$ì›[˜‘B™È–è®’.glNÐîL—A¶ý– g1TóWkéŽPïpHÂ<ž”YÚ„97o„…µâ䨄Ù{Ñ’¡r+[$Ì>@H€J8[,$¥PT}«œqz¡²02Ê¥¬u ³Õd—ýLD†ŽÀÃÎ'¥Ô¼§öVuF¹?%Ïæ²ÂÈP•°aÜ7?*…2ÑZ"[%}+Ì+·•A»3WöÞußš+gÎŒ}Iw„z€u —í's Ì™Â<Û¢ª)u7×ÚÊ€„çFeÔ¬¢7¯ûVMûiÀÜÞÙ…á,†j>¦w„z€Îx÷ñå¥ZWa¹y¾ úɬDüZÑZ®ºJ5+)´;ž2¨f%[h¨l¦‹¡ÌSu!ÏŽPï°AÂÅŒWbN.ªHxÑ?á_¸Ü2þ‰¶—¡•Åîõ›$Ù¥1IÓ¸ O¶Ö~“m/C+‹!ÝêvH8]ÈÅ=J©vÓœò…)Æ <¨~ iß¼0e± †vg¥¶ïõ›$ìÕU%¼˜¦Ÿá-c)0Ýö2´²Ò¡Þ`]Â¥B:Ÿy”bAE±èR+‡/‹F—Ú^†VCº#Ô;ìpÊ»G],¤HCCÁ•yâü¹ãtDÉØJ8èk‰ø*•vÝÆ2´²ÒEÃ: rc.± áH“!aBxØÜL˜0a²cÊfÓ«+ F~è5¬»¹±¶3%,Àþ8Y_«ll¬û&ÓÉ 5 ëJ¬ô­K8É¿t<ëë•x,²˜2´$ ¶P*&)íˤ‚0´˜b!Yït–< $ 0$ ;A²±Ä…²™ÐÊ}€{$ 1 aÎe"¶KX5–EËÊô²µâBÕÍ)ykmÎIX´îFË%¬¥A©3•›ð+—½/¶„ ½Ö%_íï­I¸˜OÊVI„¥ µfdóŒµZ›ÈpîKšX¶!˜†¬H&C’'ù·²WÂÊôªbTÕ5¿„•›3$ÌN v „ùŠ„e 9÷ Ü'áp“–JX5æß„³™‚Z%á†u[/ai+#LexRus†c %Æ…h«„uHÆWšNðoÅO]ƒað ¯ðo $ 0$ 0@Â0@ C $Ì–°ß7yø½7_Ýÿ[âµW_Ä+^ñŠWw¾‡ß;ðOu’„Sï¼ýúùsŸŒ ÷€Ë!Y½ûÎëK©Ž‘ðÉ ]ì*—2à HY§N~Ø1>sê£ÕåY|¬¯@Ê"q±¥7t±û}ïvwrRÂk¹L¨‰y ôÕvàHYƒýçÆ››ýwüÇÂHŒ s—m•pú0V%ò}õ«×_}õUÿw_†ýÎKX‡d|¹¿÷L]ÂqÙªº„—ñ±¼#á庄U\—N®üèŸÿáúë¾òw{ïÓ+Íßuç?eR«Zz¤Ð´.á3”-¿T!a$¬äå—ž»åÛ߸õ–›*åì@/½%öýî/Jx×®]â«l¹lrúœ›Ø{þ eÇh¨$ŒÄFÏ›Ó'ÙÜà* GB ‚r?=~¬TLÑ’r1}ìØGÂÂx4`¯„‰l:$Ðz ·øœ·KÂVŽ¡YŠî á|6|æôÉ3gNQqa¹”=uêÄÙ³§¤-º¦%,Z×Q ïR›t+¬t¡4¬’m.Š]–†‘@™9ÛoÊïe4Ëx«º•j È(‰Ö÷ÏÞU·•Àè Ôý,´ŽT+x¸³9‚Bßr)-_XʱsÖ%[î$œ‹ËVõ÷¥µF5Â93´¬:Ï^ÈÈSw/ŒÝüÚs¹î)²ý:Õœkh1¤¬þsüdSÈÕ$L†¤lù·j»„Uãdo3â[i¨.”°s'Ðâ©æ9j a‹6סºJKÔVÌÀøCP·ƒÝª`TºMBÂ@Â.lް+æo—vº9Âè¿]00[Âì^ŒJØh$ìP›°‰FN6GX1ª¡Ÿ$°p‰„ãÑʼn˃G|é¥_?óÌ£Ï>ýèsÏ>.òëçžxå•>8úvoÏéhØßVþg Áh çDïÝ¿· IX·$œrÖê´`1æïaâ“Bïà6 _ºxáõWûôS>¼÷—ýòÁ‡’òK⑇"ï{å×dB¯4G ÃªGiÙ Ä'\"á\&òú«/¼ôâso½uÐç[XZZZ\\ôûý ³³³333SSSCCCû÷ï{ñ7ϼyàEJ £j{ô¶ò6Iø%¼ÿ•_?þøÃ‹‹ÕÕÕååeòp ðù|sssäáéééÉÉÉáááÇßûúk¿±UÂÁ&#a0*á}¿{nïÞ‡"‘CÂ?¼÷¡×^}Áš„Ö…„ð6 ÿê¡h4 WVVÈÃB‹ÄüüÚõ>V€w$¼4ÐV”˜(áX, …(& ÁðÂÂyX†ÉÆ o9°‹×%|š²å—*$ €„› ŽÇã ÓLSÂa¯H˜¿ã–‰?†Úø'¾õ][¹‹Ät1Zü_˜Å2ãŸ;Ðz '“Ép8 …–áÅÅEŸÏGZ$h~‡Hx§õ€„!aà §R©H$BÁ°Ð2LÁ°ßï'÷ Á0Í{EÂì±#”7h˜»±ÂܘZcYqQ÷¾Ý{"dƒ@²³2}¢Øéùo]±>Ð¥‰2CÂÀmÞŠ„9‡D³åcv2‹÷Þšñ’SœY™;Q†fÊÜcÊÙ8Ð¥ÑÓ€ÓÎd2Ñh4 -ÃËËË$^᮹¹9rrGJØz3©‰1yÊfïí5‘Lùj£9¿\´>»†3²øábxà g³ÙX,FÁ°Ð2LÁðââ¢Ða˜‚azÛÍFk¢-ͪcÜÆÕ©ælŠaœ.Ó£}j5ƒðŸvÚ+aŠwZ$l¢‹‚•qÚwH$lã¶kŒ5þ G%œËå¦%’0çP–öÕkHÂü#UšÞµÑ½mÙnW›0ÿ9D›0€„]+aöð•5GúGÞЕV$ÌÓTë¶ÞZ([S½#@{%ÌøcŽz®Ÿ0xKÂŒ.j0´QÂ!Û.áLjU@Â$aÆm˶HX´.$ €„•f àCA²sÖ%]ìí9E.dc²U´kZ‹àHY=J¢Ð á|>Ïp4$Lé¥Ì×%L†¤lù¥  a¥„ƒº{Q¶÷22ýxb[òçïð¨„7ŠÅbÞ’°íýíy:åZÙ£¹›>pg#áB¡Àp<÷„èoÏ0$ 0'a¡5XùÈû‰‰‰D"±Ã›#xh½„q{#áb±Èp2™„„!a€£þ’Ç‹  mããã©T v›„½õœ&€®„…Ö`q¼2°_¾|v§„µÆLxNÂ¥R‰!áL& »°9‘0$a¡5X/‚ ,´EŒ9#á•&^í'Ìx²›íù£Ÿ0-ár¹,´‹ãE…0xtt4›ÍÚ!á†u76ª-0tŒ„s¹œ“Ö! ôöœ¬K8*[Õ×K»àcxGÂþ¾Ó¢Ä²é  áJ¥"´‹ãE…¶ˆ‘‘‘|>ß”pPêÀ|.V—ðIÊ–_ª0–I¸Z­2$\,!apTÂÂ_râ =d`¡-bxx¸T*AÂàœ„×ÖÖ„Ö`q¼2° •ËeHœ“ðúú:C•JÅ~ 'W8#aö˜“ÖïqàÉ߯îjIžä—ª átrEÀv ³õe×x ®’0š#𺄯\¹"´‹ãE…¶ˆÁÁAR´u ‹ÖmJxY õ¶ÅZºÛšÎœ³„h “$üÙgŸ ­Áâxd`!  EÛ!á†ueNKü¬J¬)á|6*[EŽÿc´‹X]Â[M%Lé¥Ì5%LžÔu©¶„uˆEüM Gd«êö»PÂΨÆX à ûû{Om5¤VÅæ†„ÅæJ/u`.mJØÏ/ÕöJ¸Í6¶6’-$ €w%LšeHx}}ÝëæÒ´„ík1¬%#HïJxccƒñÇœØEÍCI˜b]F5ñf Hœ0ź ‹·-CÂà„·-‹ø@Âà„„«Õ*cq“É8)ab_o÷‰¦„·­êë9‹úÚ.aëû27^$ €%ì«K¸!±LjEp±XdH8N7%¼"u`.©Køy’_ª5 ¯WÓ‰¥ŽI¸•†„%¼¸¸è÷û…¿çfgg¥ãH¤R)$Ü´.$  Ë$\(–––(Z†)–Ž#‘L&”°±°¯·«.áLD¶ª¯Öâsƒ!a—„#¾þžS¢Ä2I. '‰†„“+Ræ2u w OòKµn½!a€ çóùåååÅÅE¡ex~~^:ŽD<÷„mL’'O{G’d _ °$¼²²BÁ°Ð2LÁ°t '$œJ, 8Ô#áX,f]¢u!a$,“p.—[]]]^^Z†}>Ÿt‰h4꜄u‰†}=u Óîd«z»OF!a€w eõõœÚŠBù$‰D Sz© “!É“üRmJx± $ €„sÁ`peeEø{Îï÷Kó ‡ÃvH¸a]H +% …(Z†)–Ž#á°„uˆ†zº>­K8,[ÕÛ}"YÀÇ ðŽ„úzNŠK'— g³Y†„iUSÂËR6%ü)y’_ª-0Oÿ4‹Ýzù[ï‡a-ئp7 -Ë‹‹Òq$¼%aά<žS†æva-Ø™ŽD"$[¡e˜‚aé8´Ð[‘°£æH0†µ`J˜ÂãN’°ÖjK$Œæ:RÂÑh4 -ÃËËËÒq$<*a¶Á¬[‹axH`T±XŒ‚a¡e˜‚aé8$a­‘ a€k%Lo='aÝ® 6>X”p&“aHxyyÙs]ÔL[ÔŠ!a€^ZZ²_ÂñÅtQÓvÒŠµtów¢Ÿ0†µ ³%œN§ÌѼ nZ·)á@Ü1ØñN&“Œ.j4c‡„Ö•K8¾ÈfKÂé°lUMÂaHà ‡úºOŠã”0ÅÃ[–8p›„õ\ª-a¢áùž®ãu ‡d«z»i×óøXÞ‘ð|_÷ Qbéä’ áx<θm™^›^’:0— ×%|œ²å—*$ €„eŽÅbŒ|è§$¼÷¡h4Ê0Ãï}Û%¼ÿ•_?þø#$^Æ î###?þðë¯ýÆF 'ã'$ÜÞ¡&­ß b®ü襀%üú«/¼ôâso½uÐç[P}¼Ñððð«û÷½øÛgÞ<𢠋Ö]wXÂnjÒÊgd®üÍJøÍ7~ûö!rìsO<þÈ#ÿê‘G¤ì%žzòÑ—_zþà—Þ:ô²Ö%žïnJX¶ªGOÂÎÝ4çÜMü_²[E aäo%¬uÏ2$ €ë%<[—pCb©Ä¢A /JØ”ð'äI~©¶@Âl‰‰³(I÷Dºã' a÷Hغ$Ïh—„­?Ѱs$¼¾^MÄ|-–°]̵+æ_ €„¥ˆÖuZÂe燚dû°CeBÂ@ÂF%¬K84ÛU—p6”­ªÝ¬§&ap'¤¬žîO·¢Pƒ¦ôRfê&C’'ù¥  aH aH C°ó$¼Ð@Â-pú G8 aÝNbÖ{ëŽ6iKþüåGç4 ašÖUHX‡ph¦ë±º„We«º»>‰„fŒÞ.a½“°îl; -?n”À;žéé>.J,™”p@êÀL:X—ð1ò¤®K5%¬G88Óu¾)áí«j]”ƒÆ$l} ž[‰“°2 Ý€—$œéé:.J,7(áx@êÀLª.áóÇÈ“üRm¯„­ÿxç¥ÁÑHÍ@Ân–°ÓmÂ-<$ $ŒHØJÓ$ €„!a$Ü‘mÂ06Ô&¬ú0 ûñ¶=HHØ=H˜)áJ":ß@Â-pú0v“„u§»Î\—ðŠlU÷…c‘à4>V€w$<ÝÓõ‰(±dÜoPÂ~©3©Õº„?&OòKUp<:/  áHX´.$ €„Ý#a]D gR+²U$á0$ ð¤¬î®OD‰™°ÔR óK@Â0@Â0n„#ó a$Ü 7­»¾  a×HXðêt×¹º„“+²UÝçÑZ|¬ÏHxuºûÂ'¢Ä’1ƒŽù¥Ì$ë>÷1eË/Õ¦„çš@ÂH¸®)wÉ?  á¶H˜ |ä½7rÙôv ë^jJxY¶ªû<íz +À;žê¾pL”X2æ3(aŸÔš6%<ÅiÔåÀeE$  áIX­9@­0š#0š#Í0îÔæåÓŠM¬•=ìX¹‰Å…e‡&ól¾kûdKžVN‘ÅòÝ\«0ü»f¸Œ<@s„®„OŠ×]Ë¿‰Å…bM7·#¶‘lÌ“óY,¡Í_ º), :O„›%¬ë»$,Ì&OÙø é„—,–‡½9;CÝ”ºF…„Á“ðlwIXõÇ©‘°! sÆu­‘0ëŠÑ°Ù´yê¶0•6^“púëke7K˜aQs ÅZ›j/µb-ö¾8#žµÖËã´„ e ƒÎ–p,2+×#¼:Ùu„·­ªKxÒÞ6avû­Åæ_Ó‘°Ò³÷¹_ÂF³…„3žì¾ð±(±dlÁ „¤lJø#ÊV×¥¢u½(aë-›#ø o¯ŽŒ¶ÛÒb—-­´„ÀÀ`çHX—Ðêä…º„ÓÉeÙª®ó‡l•°sÿÁµQ¶œݵ¶G¼¶KØPÉ!aࡺ„E‰% J˜ÒK˜®K˜ IÙòKµ!áðl»%l¢¬Ó]‚mï'lÈ<ü=Ÿ-ž"‹åiA$Ìß%(á¦u[ a€„y%¬Gh¥)áIJlU×¹i->V€g$¼2Ù}þcQb‰¨A G¤Ü’ðÊ$¿T!a$ $  aHvž„gš8"a7b˜èÅÄèNæDJ{;È™^ëPß<Ó»f÷…³x~è 7¬Û ³ëç.æîS°rwC‹oiKyÊ|Tš¸íÅÆ1—ØaÖ!´2A…©KxI¶ª«¶ë Uu°5ËSÝìh—Q÷P‡]C[Ù5csCÃNš0É!^Ðr OÔ%ÜX":oPÂóR¦“Ëu HÙòKÕi [lŽà¹éµ¬1Â$ÿ`‰NŒ™é s6G˜¸ƒÛŠ„Ñ a·IØÐC,=f&ÿBC­£†$Ì?2§éáz´$ÌÙfnôÇû¬ {BÂFBΆhÕìœ íj¯æü²K¹eæpÇœ? øÿ­C›0€„].aë‘•<¦Áâ¹&Ff3$a‹%·ø Å#aþ}â9 »_¶?ý³ÅléïáÜ;Ñ&l¢Ù°«$¬Vi ÊÊãémï‚kû¨•KnåAöû [?ç´FÂéä’! Sz¯HÜ/ál:øÆë/pJøÐ›/SzHì’p>}ý5^ ¿yàEJ €]®”sï½»OWÂgO¿O~÷íW(=$ vIxm­<=yñ£ÞdK˜~wÿåÑ~J €]ΤVȇÁ•¹þÞãçNpú¤Š„{.ºtfl´gm­Bé!a°KµÿæR«›ëÕJ9_.g•ÐrZKi(¥Ì®•°n§#CCY–[ø å²åG0;4”¥éŽ^öŽCýýh£„­àN söï幃ØÜí Ç¢1½¹íCYÚr³†½‡£[r \ aW5GhU^‡$lqTFÝa6íZÍÄýt&¼­{6ìú.Ð=E†îævTÂüËMÏúиî‘0çx›ü·k j%OöW€Ñ[ª€„í’°]mÂüÃN:*ažQí kM¸íC€r¶3³[BøoâÀ îíþä¶ÿyó™SGv „Í5x¶^Âeã£2ZüyÎ9ЦígÃú÷Ú„·$<>ÚõÇü¿ µlòrw%ln5'a‹„-ŽÊhî™zæJå´„ù¿¤ aà9 /úF®¿þÚ«¯¾JàoÿóõKþQ/JØÄ˜ínnvh<ɲÃãmZ<V!aàE ‡W§îøÇï]ÝWþîoo8pàuz¥ùýó"ÁiÏuQãÖg›aû ;7ž$gxÏÿ0˜¸t€„û›'oùö7n½åæÁÁ|.6Ð×Ko‰—~û´w{G€'$¼ä”ûéñc™úq¹LèØ±„…+‹—!apNÂñÈÜ™Ó'Μ9•ËF¤]ž:U[‹ÌBÂàhs…¾„baX6;$ ¸Y@Â0@Â<Ý®ŒvR²>n¤£V.[{±õseKÇ3λ‰[óìitZpk†²ä¼÷Êô °¶ßò`×vQ¶c¸NþbXù,ø‹žÃnñ(jöJØâz&ò4q'µ]cGð·iî9K^¶{äaH´]¿úå½D4´uÍ?ø‹{ö>ô³%ae•d×\­ŸÀ¦¥ú`eìGÎÁvŒ¶Ï˜–0ÿ𘜃?[¼iÍÀ ^˜½(Ô‹Ÿß·G\øÓ{îú燼8€•1|xÆ44$#£Uõ-çŽ8o|¶(asƒIÚÕ¶£›gÙÈ=à<-*´åfÛo¿]¸2_~ñ)Zò›çÞÞqÇÞ½Yî_ÍFÇ,[~„é4M·Ÿ8$a£g›ýÕf1.s@ @nÖÈÆoºé¦«¯¾ê _øüÏïÿñ5×|žæ¿ùÍ›ó¹øŽz²†îp7¦[e9›[íÝûˆL i®IÜœ„-~v5°Ðª?æf³™øwn»õúë¾"ð½ïÞ–Í$hyç=èS÷DÙûÉÖt™°¢A£çÊÞñ6x¦§‰nðh{ïˆxd.‘ˆî¾ë‡·|û{î¾3™ŒÑ’ë'¬_ñ'ét‡U'vd´Ÿ0O,jïx›Nô.3Ÿµ~ÂÀµ]ÔÑ…PpõàÁ¡Ðj":ïò.j—N-Æð±¼)ëÒÀi¶ô’1_!OÄÜßOxÑ723ÕÇvÜÉŠ”Å~b‘·nÖ –£ó3ƒß0¸’ÕÊâ˜Vwƒ„`Ç $ 0$ 0@Â0@ C ï4¦&OŸ;{¡¯€Ιsg&'ùëN44MÎYô¸*ŒôY°'˜žºx¡¯¯«·çÒÐEv8T¨:P¥à´~4“N\¹²éÒé„o~T˱°;9ßÝÕ30Ì%“v8T¨:œë¾ ¯šÕÉéÉ‹ë$)×LT˜õõõéÉA*$ìºú¦g§ÒéF¬€*U‡îþ~ÝŠ3;5‹…×Öªy—MëkkÑH¨Vv,T¨"t÷÷ûFu+ŽaøÌ©£ìJ»fºtðÀ+T0*†²ô¾…‘Á¡~ºöØá\ øGyjM$857=xîÌoÚ÷òKϺ* ‰ FŃ„M44½è™ë¾ØÊ%Pa¨HZ™ƒ„©âHÐ-0žò y0$ 0@Â0@ C°$ MS²EßJÃît€•Î¥¸ÁΔ0%ðÏfÒ‰+W6Ù¤Ó ßü¨¡ÒÀy•â";S¡ÕÉéÉ‹ëkÕ²ÞDiÖ××§'i+||À6ã";X³S±Xxm­šç›Ö××¢‘Pm+|‚À&p‚+áðêÔ`ßÉÍÍÍR©ÄyýSJ Xh+åÈœ1ŽghÚŽ¡=r>]´'.B{ÀÎÁÊZ[’=@\ê®’°øD’B¡cN£££»wï/‹Z ñ´„A[ƒ9/Bq2zZü”[ a+%„w‚„Àu×]÷¥/}éþûïç¿þµ”Ìù8æ˜â)ÏZσÖJ¦úüe­§<›(dÌÈœK$Ìÿ9ª>Oœ½V÷‚ÔºZt/?ÆZΣÓJ¿Ã¯dWIXëá ±Xìë_ÿú—¿üeòðää$¥ä¹þe®ê¼î%ÁHÀnOP^Òü›*$¢ {%ÌùÀ΋ÐÐEÂs=˜¸PM\ÆìË•‘3gŒ– n¯„ÓéôîÝ»¯½öÚn¸¡»»›ÿú7qµó\­‘°‰BWIX«MX×6Fi(Ó—±nkè*U àÑá ÏÎÎ>õÔS333âÕþüóÏ­>}øá‡†‚{%Ìø“Eù{ÊP½3™«fŽæ7GÂZÍZŸ£E ëæÃs·@ÂhXs¡„óù|&“yá…n¼ñÆ[o½uzzšÞ=zôÆúôÚk¯eš¥lc$l.ä°7ÆŸIX¸y&΋ÐhÔj— -6G´&¶ëo>HØ^ ÏÏÏß~ûí7ß|3½?~ü[ßúÍ?óÌ3étÚ´„ùÍ<Ý {NÂümÂÕm¹µ"a´ w¶„ïÙ³çûßÿþ~ðz}øá‡‰„¹ë_«wÏ_ºü¿•ø{Ghõyà÷?~ÄyQ†ú°/`›¨þ1m1F”p.—K7§………{î¹g÷îÝ÷Þ{o0LoŸ(%ãúÇOu`ZÂÒ‹=™¸ð„i{á…ÆÇÇM_ÿ6Þ¯ aHt°„{.›™™*•Š)¾©\.NO×¶b”y—Ú„h]ŸN\„€³nBÂm;bdèÜû‡%ñB!¯Û5¨XÌÇã±Ãï½I[©Þ¶€Qp‚,a¿0|æÔуö éþ ºtðÀ+”ž¶ÂÇì!ØÉާæ¦ÏùàíCû^~éY6”†RRzÚ ° \„`'K8V¬Ì¢odb¬ûbÿ)6”†Râá2Àvp‚,a±D‚:àÊN«!رHH a%Üsáã¹¹ËÅBçDJÅÔìì(ÒQ ×;ß=zø@:,ä“éäŠKÓ©à‘ï“! ÝøcTÂÍÎðï:ð»ñѾl&R'ŠW¼â¯;ùul¸çЛ/‘ÞøcBÂõÎðçÎ}ûÐï^~éiäC²"¹Ñè?&$,é ßu±ÿ$ò¡¹ÌI€-@ $ H€Ë% M‡V&}#°“!šÕ´„i§þù‘L:~åÊ&ìdÒé„o~ÄܳbÍI8´:9=yqccc­ZÌçâ°“Y«–Ö××§'È­‘ðìT,¦ý¦S«È‡ÑH°æFç%^ì;±¹¹AþÇ™"Ÿml¬‘ ¡fNÂâ¸îùl gˆ\.nbDwH a€„!a`…]Í©Å;µ¾m‹Ë aH8*ÃV:Í  ƒŽ1°ª“e²0¯ ›)Ù tcrå¥iì*-€„!aà ³#d™ ¦d·$˜Ø£‰2´+ò0$ tMkÈœFkZÂŒ…6 @Â0pUsÄ®í“nÀÉH)KÓ^ £9@ÂÀ+æo£àÿ¿Ï Fs€„û{GØØ¡ÛPì\›° 3H.é'ÌîÒÀÓ3A–€'~æéá`¨wš#€[%M§Vp9uUâ<gÉåb00€„†„HH aH a€„!a€„†„HH aH¼(áTr9 WçØPJ‰ñ-Œ9òÖë¯ÿî×_9ðÆ>¼ÚòúƯ9ü–ß7î “Z—g²™Ô•Æ´©õJi(%¥GÅÀþÉ÷~°§ûÌÄø°:«‡ß;L¸\ÂñØâüÜåuÊZo¢4”ŽÒÓV¨>XçÔ©cc£ƒñØJ!ŸöBg•ÎíéÓÇ\.a ×ãñ(Ù5Ï7QÊX,Âòüœ?w"Y*—2À èÜÒv³„ñ¥á¡îÍÍÍR©Ä)aJIa3mEÛ*KõÁ4ì§Õp>ËÆ–L,îÅÄî$Ï—´³œ cºÔ›ËÆaK‡ s;<Ôçf Ç¢ÞžS•J¥P(ä˜ÓèèèîÝ»ÇÇÇ‹Åb¹\¦­T[†MH¦ƒ%¬L {‚]Û'sÛB°×]wÝ—¾ô¥ûï¿ß„„eq ìÕš—^ÒÊd²ð’ñ–!O>æ’©FÂæê8h„MÿðA$ ›–pVcŠÅb_ÿú׿üå/“‡''')¥ +uÄø©ÎÞP7+ u¿Ì%ãܸSÂʯT­¯cÕȶ—'žx„(ÓâšìѽO>ùh'I8NïÞ½ûÚk¯½á†º»»i‰E ë^ØZ¹ñçlt-ç7$ ›—7$l…HdYø8öþêAqá/¸OX‹®zT³³³O=õÔÌÌŒ(áçŸþkõéÃ?–8!a­_åŒV Æ^ª­yŒ%ºù8$aÓ­Ž ]mºóp ÉÞ~ûíÂtàý´dÿ¾—„·wÜq‡k#á\6’J.K‰Fü=Ý5 çóùL&ó /Üxã·Þzëôô4½=zôèõéµ×^Ë4'JI¦­h[YnÆBéZåBå¶ìôº™ð†sÎÂðOuGÀ%h}:²[:½E aÓTÊÅ›nºéꫯú®¡xøšk>OóßüæÍÕJI&aÕ3ŸËF](áùùyúr¹ùæ›éõøñãßúÖ·hþ™gžI§Óí•°Öuζç~MÈÜ^ CÈ•0Ï¥.Ì0>bHØ ÅBö;·Ýzýu_øÞwo+rÊ?æ<$aÁÃ{öìùþ÷¿ÿƒü€^~øáD"‘‘LºVF&d(ËP5a(WkYþÊ…Ê|´êg2öÁjm¼%aÝo[HعF‰t:¹û®Þòíoì¹ûÎL&EK<*á\.—nN ÷ÜsÏîÝ»ï½÷Þ`0˜Þ>QJ†„Ø!Öú×r¯Vž°åF‰\8qÛ€„¤‹ÅÅ™L&u¥1mâÕú+ÏÅÅi÷ ˆ½0ùÈ‘·^ýwo¼þÊ7öáÕ–×7ÞxåÈá·| ã00M,˜›½¼±±±NŸ:&û¦õú47;Fg¸íŸ²ß7ñÞïötŸ™öBgõð{‡þ H˜Ã·p9’ó˜ìžè¬Æb:Ãmÿ”Oúxlt0[)ä“À^è¬Ò¹=}ú$ L-]êÚÜÜ,•Jp¦íUú‰Ag˜Îs{?èóçND#KyÒ!èÜž?÷)$ ̵ ÿ+‹ ™ìÚµK:/}«LÉN°£&:«.yT"ž1çô“5Ü÷Œ¹%%’¸l[¨šØÍÜJyÜS°hÄ×Ó}’$\(rÚX6ÃN¦›r‡LM Ÿ¤óÌ®<—„˜ŒH¸%V9óäC—HXzÁhÍï »¥„•"U> ¶E†‹ßàð—°ò²‘]Qª²l¡˜’Wk%ÐÍg׌Ýñ”JYtsvÚÛR OY¥=ŠO\ç•3ª“ê¶:=ZŸ2™Œ¸„æ÷îÝûØc‰K謚°Öµ' ›•Wãj„MóÄÒÇBÑücî}òÉG=-a-m2BeÕ” ³Ê¥³ É_*þ…ŒºYÂBaêl¯¬¬‡ùàƒŠ ï¿ÿ~aa04!aÝ‹=þT asD"çbïýÕƒâÂ_Ñ3ÍIœWΨNªÛvêT,oºé¦«¯¾ê _¸†âák®ù<Íó›7—J%1 U¶„MÄ æ®HØ4•òÖMñ°øAW+¥Nj¶¢&£‘0gàmÈ®ð”0M¹\ö;·Ýzýu_øÞwo£Yš€-aþ6aDÂí¥XÐÅB®#{Gp6™ÚÞZk—H͵ snÒ2 þ'éµÊ6ìì)›Í¦RÉÝwýð–ocÏÝw¦Ó)áï9H¸ó%Òé­:“IÑ7Jxu²·ûx¹œÏ¦C‰x@‰X=e µæ…·âB冲҅ʷʽË2d”S·ºÇ¨u,ü‡ïáð|W×§$a áÒ˜ŒOdÝP(xðàp8DÖ•­¥³J¦3Lç™}±i]HŒkFõú×ú !a˹p¸öAG"¡J9«ÚEMõÌgÒ¡R)Gn$C¶@Â}ŸÒîr©`2¶@ÕÊÑü#áùnãÞ¥6íd“iéU¹J0a:Ïí½ a[$ gÉ­‘ðÅþ“¥R&›\IÆýÀ4R³9½¯Hx®‘°c“DÂsí½¨.]섽Yƒ¾æTÏ|:µR,¦É-pxujhðt¡Î¦V1?ðáÐ\oÏÉ™™©R©˜Âd÷T.—¦§'é Óyvè”ýÑJ68pvTÂ.¨žùtr%ŸO‘É-ðð¥3ù\2“XF4ëb‘ùË£=Gß'‘ˆ‹…,&û&:Ÿñxìý#oѦóÜÞzd¨+™Ö‘L„jgX5N.çs rck$<:|žvG;G€WøÇΞùèà›ûGF†Ð€`ã4<|‰Î*[:Ãmÿ”—ãs3CÙt˜~ñÀ™6Bç3“ѹ]^œP=óéÚ@jqrck$<>J?ybéÄR"æ^‚´…¹á 玽óök¯¼ük`t>é¬Ò¹¥3ì†zuyÒ7?¼¸ ì…ÎjpeJë´§KdErc $ NOŒuç²Ñš„£ ÀCÄ#óËñÉñ¾Kƒg]Ðù¤³JçØN&_Ìf"äF2d "áÉñÞl&œŠâÑyà9b‘¹hØO\T •É­iŽ˜™êˤkNDç$k÷kÉ-iŽ˜š›¤Ý%c>|ý‘ˆùÒ© ¹‘ Ù ûæ.¦ëwjÄ#ójN®[#aÿüP:¹œ¬ýÑ3 Yë ±Lnl„ éÄbíßöè€ús‹äÆH8š^ŒÕîÔ‹ÎÚ:•z‹„ŸÜH†l„W/×þ•‹ÌÇ"³„ rck$Z¯·EP$< ™#+’[ a¡«0í±¾kü €™z<â¡ y×Tžç  a:ÿÂð™SGØ72<䉟íÃC—x…ÊL%ï˜Cã?(H€#œš›> stream xÚ¥Û²Û¶ñÝ_qÂL'ß/ÃÄwCÛ´Ú–õž;[ÕªûëoŸùÂØ¶/ÞDш{šx+ຠS/aÞ‘7™»ñÇ»Aˆôñ}ð2üx¿Œü̽¾ù'Þðc/K;¡=h¦þàûáÙ´ØY¹ESuÇšJíP®ø „rÿ8 ÆöŒŒ.šzÛ„ŽÇïºÑ?wºnqSwKÙÇ2ð½<Éy7¼®a©ó}¹š; «­ ‰s}|tn›Wt¦"»[²”–Aâårcñï—*JòùrF–!°ËSK6ì1È(åîI•gÆ5;nûsbG&SÜEñÝÊX½åÁ²js=u{. YåL«6çûUì.„»ª*T;³lq"f¸«ãÿ/Àä÷ p×4B?ž•axI>•a¦¬TôÚ‰«“&Œ)a)DŽ î™‡ò$3ÆLhtÅbFìÓåIˆ²Ç!–D-*Ùoj¤žtiŒ. »“KóäžV^–'"à€—XÓ¡’tЉ0IÜZµaü¾ü¤kz0—Ô}dR>ô¯÷A ~`G)êŒðeì‰vªZ`ï1Ùëaéx…ªiÿ¾'³† ÓKô yê~*Ïm:yÅ. ûÊøâ Îª@¯Éº‹8MB²®.‹f«oHÇ£ow<Àn'ç=ñ¬m&û’K«ä†®ªNµ¬»£¦[‰Â`´“|ðРܦkMI›´fl_-ÿ³àaµüEØ“Œ¿ÌO°`4ì¶î¡¹ýõ…¹=@æÐtqÌÜàä˜zËÝK Y3¼QÅò-‹ž1Œ¿:ý¨Ž§JømõrnG¦8—§Tue!²à‰©Ç™—E}Dù±Òõ¾=0½ÄŠ,"iQ?.À Ðàæ¤iûvød„`1ç(‚4÷Â$°ë,˜ÉŽ·¹í¥ÖF£\oNªÐF¬-ȰgbðFۨœtÑΩzÁ0ó)¦A³Õƒµ†Y@©â+½kÚTª~X0üOŠ º3¿pÿRVÏ»Ý! [-Kõñ ëÍ`ÞRð9'áãù#š¥Ä^L·Ã±Ñ,ÐÂ5!´BŒ²¬?ÀR×'$Ð1x—Z¸KÌB´)Xô`¸`Žº-{“¡.0½³™LDùkYc¼üsˆ[}~>^wN=þÅá&Èû¼†Œ*Î6Œëö[{å†û·¸ÊV“7eJŒ>A iû5Äj=î­çbbÚÛ†[–¶$s 4… „Z!܈Ւ&ß@ܾàÞ›î b>{D,¹q>QìˆÍ_›¦ªîYÏiAÇÆMD±‹Î/Œ"¹J@èZF03ÀÅ]ÈÆµ6Œ9ªóƒ0átjeÝôšÚ㑉}B_ìˆGY*àeùÄóØNŒñnfNQbæâC8§z/p/}òˆX{FQ-㤼 øŒˆ¡ìÇìHèøªZ¸â.<{[ »í¶Ä#ÊŒBtç¹}:¡}(3¿oJ:V¶ ]K_è*,6 ·×¦V ÷U¤Øío:¥0@Ëö½!ÚpK>ÈÛŽ 52C7å¾+Û«ÝVâkoïÍÊo9ÊÜPGòKˆ \="mÃ.‡1Ìù=Æ¿c"çãVa";ñ<Á€ê÷#RÐÁ©`”÷wŽ ÍmWеÐnTj?fŠÉm.¥zsô¤”¡QML2¼Ø2.”2.tG\KöM;$ôóå\Y}}BædþÔ,¹©Ôt@WHŠ;G:‡‡¨V éY‰ÛGn†dŽˆlZNzze,?eLߟÂñû“Ykn®¿Z݆ ›Ñ:Уu°¤¸4Œò,»wܪñêap³z؇Έ¡ÆS“íYN9ÒÝÎlugû>O¹êj’²ÍyÉWh‰¨ ÚI¿(`· …/€8ÁaIiõs×´Šc#tp‰uÅN¥·ßdxܧ$ŒF/اý‰6³e}¡È7­n^ñW¸•Oä¡sTBgºÁadÖéd¨âº‚ò„{âµ2×b2"Ð À[‡{ÍÉž”ÍÔÎs&Gt (x(í!&6{;ëy­$rK`Ý/'8›+$œ©`+—‚PíVF3¹\s8YÎN· ÐŽõí(ãÛp¾:-çÓE(¢ð¨$\°‹Œn9Ï(Ã2µ4çG[¶EKacYfσֹ«£[àRšƒ52ùSSΜÁ¯c°£r8v×÷9º2ÊÂŽ€Eå Ò§¢`Ë ã¤Ü&¤€øûµ…›9p9Bæv•fla1ÛŠ,ûºé6•fØà_ÍjÆÈb0mYb*Éi‚/^ZÌ’ÀbÐ"¤óŠÛ¯ ši¿âÐ{ÅtÃw  D ç+™‡w¨?·Ò¯Ø2 bruQM*:©™byšÇay¸ (S¥ysL¥ÌaÁr¡ƒ@[ë Qmƒ{&À2z3 D×!y.!Ê7³ó½®5Tu¬„ÉØr~LÏé¢ù–ÅoÁ’ ¥Ãã23éêÂA"‚h>•[Ò ûªKóEÅSÉç^³Ï²U›y½ÀÜeÎ7¿V|m­5hËcëSŒß‡1½B´å$е×b ¤¸‚O~dؑ؀Ñ@MÑ×cž°>Ffý#>¼Ïì)–ÇøÒýî{Fà±è½ ÿ†šèò»ï–¯_{L°¾OÀVé<ØÛ`sKÝ0æ>¤Šç§l‡—ÇãKcsó u/Zᣚfàж§—/^\.Üw‰<ö\ûë^üíûõ7K¼)<†Íç`’U–ÕèM†¸¶¶Ôä¾¼»ÏlfR=¬"þ*´âGh8‰eør(ñca¸Š%·醎sñH|“P‚ƒ,ôÍ$[ñösb÷[UwŠÊ@7º'ùeèû¹|®”{ЃðWã¿k¥€O{£òYïFŸ‡WnÕ˜!8Ü4Ýþ 0%Až4kâüò<ÉÏ }-A=j¥oÞèl ÷!N\oÞå. ½ÆÔØ/xö»ií§Òr:´-éím§‡7Ê›BÞpÆþ'èÍým’ò °-'Dò‘ ŒÇœ°’£÷™¥ˆ±éœ ¬ð`™µãvÛ3–78ÒNdo—¡'â%‰bÑèø£À­"ˆo[¡hÄì°çÔÃo~t£¸A¦ ùƒ ’lLÑ >Õ&5Ê@6d °k»”|î ø39bøúhªlcüx‰õÄŒ‚øQ2¢RÝ-dMâͰ¡ÈFûº Ɔ0ïb4H¢— ’#“À=Î qQÑ'ðÃjcF5}&o™- 1ÚýñßuU[Ò‹É”Ä0‚Ÿ9‘›fnäETUsEøo®FQA¾¾ôå½<×¼”/£«»ÌËÓ4ÆïžÀ2Œ½Ì_1Ë/ï—©ïãÿ,4ChÊØ6µ *“ößü+fü×±e¼ò²0`Å e³?™òÍúÙÿ+WDó endstream endobj 772 0 obj << /Type /ObjStm /N 100 /First 873 /Length 1659 /Filter /FlateDecode >> stream xÚ½YÁn7½ë+xl/\rf8A€Ä†S-Ä>¤5rp!pK†,Éß÷ÍZv[µ˜XÈi¹ÔÛÙÇ73œáªª…J² %k¡j ™“¸VüP…ZS ­¸æÀ9‡Z4p)¸÷uR1.UpÏAÉpVƒ KŽkÁæ-á-Œk1 ãjr"É m“’CÎÙ‘‚AñWc@)aÆ)fÌ4`„0ÓÅëa:ûzj¸oƒ¦hÈ*iR[ÅÀlX¢“m¾ÖJ`Š—[V `Ð\ ¶°Ü †¯†Þe ÒdõF9O,»X¾R0 )þx TÀÅ UÁL¦@c†µqbÿI )4°\3Á ¹Uˆµ¡ÈêBm`±X. k„_Ô]ç°XàA® 3{î—‚W±Û*0gŠåA_nɱð^+ibT1hn®Nì?5 àã$ãwƒMÉð»1!W‡õ÷!ˆ±‡ük¬4žl "3°,. ž”º&˜QÅSuîüÔ\2†#fàþ’Ùg¬Æ < ÎС;ÀcŽ¡°! »<“Âp« E\¬½ˆË g–‚x—‚ðtEŠBGSXVÕ‰Áh©Ùg<šaÕÜ ?¼)SÌõVÖõFj”æ ¡‘&èm%M°jž 9Õɳg“a?œTðKáMÞþñ'–£€ƒˆD'5»þôéÝäùóàV¢ \NÜÅf½SÓèqÐc˜¬Å'uakŽˆžÇ±”JlP¸EE€ufŠÖj6IL÷•8˜Ï–áÙ³0!Ínž:@¸²ÞÞxÄCu¿ÁSÃëÅüìhº 'ax½†ãéçe¸3xüårŠN?N'ÃŒOgË+lDêO†7Ó«ùõâlz5îMãÔoÓç§/çŸÃIòµqLÅóÄby‡×.`#T®7øW‹ùõeðÝs¼}1›Íñ†“quŠ+&#‘x8ާ³«K7uö% {GÃþôŸó³é›W/Ãp–‹ëéWüG«“áèúýr¼ÿõ|ö÷dx9_|˜.FžéÝðËp8ìäñÆWvID(Vd6y¤"å!}ÃEnWóbû( ¯æÇóWý´7¿¸8}¸úÙÅ]ó`-k¡A£‘v±Ñ†ÈXÇ®¼--GkX°MpQ–+E–܇M9jª]Xª5bsïÂbk©õñÍÐŽ6¥é:6×±E…†Kíãk9JÞS“éíïïÿòÈñ§/¼KÇ (äÖÞ¶„kéA™}W­ɀ.¢;$×±+É@ª›klF]Xm-ŠhVÀ!aëéÁr«ðqíÃJ‰uÓ¿K¦Ñ‹^V%æ”û°Ò¢·}]ØDQrŸ/PÑ<~{¨­_þŸPGŸiu¡néAmñöòi¡î-kW¨o×á;K¦e{°¬¬ß´,ãv¯Fz—휼ɯ9¯®´ºòê*«kY¯©O­Ž8DASZ“F7BŒD¨=×ÇÊc<¼¸œ/–矦ë…ò‰| 5×-l"Ñ5ý|Ž–‹óÙÇ2Bumب<‘üÌ€~"ù½¥HB= íV¥Œ¸/8– êG'¢‚êëÇ+F'À^£ýÝ1"ôÿN`EÈÃ*á$ÕOèÆm;¥„=½1§?3²—; ÒØÅ"Žq+2âµíR7™‡Qí[b®ÝÕÿ+ìmEW¯zú8–†±9¡½\µÏ0ÎK~íÁ*„ÙÔ‘n ¥èæ.,ÎVuC™Þ„e”t’>Œüç-úfEÙ'„Nx¦O‡1ú(6EÉÐŒ}´m¡ëgáâŸ}­¹î|×Ú´X¤“En¾:Û!Ø©ú¸ìíû™™ëÂ2Ìs_Êe§Ò'šo_Öúråà~jþ×­oî·ƒ R}Ø~Õ:=lýã^ÚI£(íAG%õ©•ÜtHþ=n‡Õý»af‚ؘ$ú'F”bôã ëxĺãƒXðïŒý|6×±þš²Ž½ÍÈZPTŠì|[T°ñ’•NÃä¯]‚ïNÁ(ƒ¦†q¤£²KðmÂ2>m3Ü`é­œc¡ÜgØ—§[  úG_E‰U¢-`Á ­aZuZ^Q.­gæ>,£/íô´øQ¨ú"´q}ç -´ÍuÇ[™©Ïn6ƒç:±„ftSÚ€5²<ÿ×ZÞ endstream endobj 880 0 obj << /Length 2218 /Filter /FlateDecode >> stream xÚM¯ã6î>¿"ð^LâçÏ8.ÐC;;SÌbQ`€EÛƒb+‰ðËkË“—ýõKŠ”íäyƒ^"Š_¢(Š"`qZ‹_Þü¼óô! ¹Ÿo£íb\$AàÁv‘í"?M“ž\ü±|w‘íj¥Á2òiü]ŠRÕ'š”ˆÕ_û½ X3¨Í;P»MP-0mý4I€‘´–²+ZÕ Š·A°|Y¿Ýt[¿]oP¨Úäþn“DHäÀOW›0(r¦lz-7°Å,—8DôZ.Ìý }‚ÿ òåFc-_ A‡JÏ3+=îITÍY°ˆ4 Äå"¾¾=¶)žá`»S¶3‘v4äߨ”4…ïûÈðô!Ž'!ìQàçQDì«M'tÄ¥KÑ®ÂÝRZ]ÝYéhIúB4AßAUrMŸWa.eßýˆWUUÕº½ˆªº_'¾ð æ, å,€EkM7r—ÃfÁú4çÑE‘5[eË#.a|œlic€$ý°~€Æ_qõ "}M´ÿöʰ€>Y3X3÷UÔÖ(T¬i$eô+ÊqÑ9s­ Q–M<e;²ѪnzC ¹Õ§Éþ¬:b-DM¨‹ˆâ¬$ºÇ’ûŽ.m–J½JÕϲüq¿Ê°˜¤GÝ¥k’„qÆbR‚;m |P•gËBC”×嚨x!p<ܘZ©â™,ì Äû7Xâ\œeñ|Ð/$€G~ÇJGÄR‰JŸ8˜£lÆ~´ b›y’ÌßEábç~’sL¹ öõîÓgÈ¡ÝÌ•p:ÂÌOw¼ñwŸ?­6 ÄòŸAØÍâ4Xv²­°7©_D«Ä¡’À:~ZÆN†Hà Çj¹¶Êð$œÄ‚å´6D8µâÒ­çN§ë‹3)¸`/»Æbw–Ò€˜%«º¨zò Nß¿²"8CÂýRÃMjUá~z‘í¾û¤£™[5ÎØ5‰nx=`C$ãB©Ÿó+ô±¸ " É9_œµvú<8]o=Ö¯9ĸð*\V²¦àý½[`g8?‚S›f¶I¡•ºH0ÛŸÂU¤75YvgÝW%ÁM%Š{N—{²×d¥¢(@™tíh¦[調ÔO³éi¡üUáf«Á9ëqÉ<_’Cb|Í“2"™F‹Œ #—uÌÉz*y4ÌrdE4eÙ]­kd¡Ž7B^ÏÂòÓdñ·ÜÃý:Åíí1ZRÚ Ä‚œß.i ÿÒä%ýmG¨#Å^jƒ&r`M8*ë½ÝxÌÖ† ‹¦áXÞ )~'|¶žqæ `“ºxî8lŒsæOÓ S§ÛÀä,XÅýÕzN¾BÞãÌþÖã^3ÍÛ0F=¾C.œq9å•ÐåX€ÈL¢òˆ`6¶cœ$eù<³<À¦ÑpƒØ6_Qß7%oõÓ#5ÕÏj玥©ìرŸ¤|Ø·|—¦â ?Fï™;§ÚU}ËEÙà"¨Ân 㬯ad'dcyƒ|CyƒÑ å·*¨Ê·¥‘ï}»ìkìˆbÃuÉÒ'̽s9Ô')'Œ°iÁ-õHjEñ,m­3q´ .‚Ô g„ñ6¬ ”þÉ'Èã§´bhàZ`™ç1ãõÌéú«usì\×Û8LEˆñ00¬:@z\y4¥çxpYÏ'äû— keˆäTï¬êŽÀ±·ŽZJ›äì¢G6ã[­I åB¥õ3Cê¥Ôêbæ²èA–÷hK¢4`W¦©“7¶ÃFš¬ A¶h/ô£ØC³Œ VÏ5xsæÛccŠS†8Ä&Œ)``œ„!œ<"ø¡¸«>­ìXذ&)ï$›ºébòêüÈÍXmëË r\Y*׉¹›œ“»– 8M V²eŠb¹÷=eÚF ư¿X™;( —Ñ:NÍ ?f1ÛF~œàɬ]2Á×sø:¤ó1<2f ¾ÊÖÓZ¼ùm]<ÓÖ}ø¸ÿôwúºðŸØQ¤ô€ÁÙùPƒ»ìqvÁqòê'Óo (Æf~hmìÌ-ƒj×òÓBÔv‹Šðw$.+Ún=š2s‡ï)G°+uÏ´š¬Ø­žT»y÷}oF‰ºˆ“¼ð›ííQóßlε‹½ÝðmÒË*Âõ r¿÷Pâî?5’[Zq€«= 7Á‚˜¨=°ìâ6¶ã’h}aè·›9ch#\èR+ÖŠˆ}o»hhÈöõG™$ÑÒVbIÛˆh %¤»ª)ÎÐÄVÀHmªþ„WÌR©ÚEŸ$Cã¯[ÌüÉIµamcõ¿&nLf3&Ÿi~xz‚À„xðkiž¾ Ð檞Õ}úÍšÑÍ”CYÅ®8Š.ëEŸ&$¡Ï-¯vXÅQ;*wã`¬ÉmËøøîZÄápãpè€çÊ2ôE÷0¿m~æ\ý… xø¬P»ìF¦¤S°VUµ {®>­¡úª»‡DxÔU¥¯wÙ €¿û¸ŽldÃgäpg¿#Ï~ ·´d†3-Ÿÿðliáöñ+ÿôσ8‰ý|—ÂuŽühÇÖ„wï÷oþhºc¸ endstream endobj 897 0 obj << /Length 1666 /Filter /FlateDecode >> stream xÚÙŽÛ6ðÝ_!ä%Ú`M‹º /9¶MÑ#m·íÃ&(h™¶‰•(U’×v¿¾ÃJ–¼ R,°g†siÏÙ9žóÝâíýbur'cYìÇÎýÖÉ""I}E¡s¿qÜw{Qw²¹Yú‘çúŒ¾¿I±QzG‹èÄÍ—ûž¼º ‚‘Ô0a©Ï¥³ˆdþ²Î8qóêд’àn¯Z‚P@©Ù/6„\[²æ†§.œN«CKZ ¿œ(á< úcYWMw§ŠÙݼ*K¡7ŒŽû¾:Ê'05 Ý[z®† X–ÜcY”‘þƒ^‘+ж2P<Ñ,rUGØ£êö„1 "°­Š¢:ZÅc÷Ïι+í¿´Ýæª;‚?{‘wÜ«|O¤£sɱۊ'yÃÝ ­ºÊjEËÏžç“á×€Ñ@’EnQ‰´0ê ÄÞ(~IÌ|/ È)O2¿È½ u³8 zÒªyæs8åG8ñ ï2Rà]”| ¨.D.[Kq.KÙ5*§¥lÐýUc·«-}=ÆÑü%ç>K³tê…JSSh8]2º•³ÎHœ,ŒCc!øÑYê%$bÏsßÐçáKï댥q@$ço“lÁS¸[(- Rš¾U-µÉ‚J3PëÀü5Šð9‹x6‘S‹¦k§Çm!À  '!‡ãÔô,s:këB!ƒ¡ŸSöÄD Jm øà}AÙ3¦?#åÒeoÏXbÒÇpü.»÷à|Ãðòôò–ô:Ù/Äâübú•óÆÜçžûܯîüÄáóc/0¡\ö) Y˜Øv÷“Ъ>¢£úLÒ>Ú™tïð„E©Í)j8XR±wa†¼ÊÜ\hBKѪâLHªdÏ-«2…Õ³*Mû¶oxî‡ 6@õä+QT;+¨:Ýv}¦o^¨üÑ2Ó0÷¡ñAÜwŸDqƒ½pRCã'?ô„ÜÕòH»$ ¡þÚ‹Ž lá°WS5®Å­„²¢þHûÊHÆuÛ 3š:ÐÙ¦~­û;Ûk;[¶/xÆ ¶¨+ú–âÑ’‘êÑ4*P¤lI+h=¢Ã† ‹Êν5o,ÊöŒ tk¦âØ kÄ2›O?ã™ÓL0‘é3aˆ>´áQð œ#ïÖÆ[‹RÚ S˜Œ¬}Uµáq/­f±õA<øåÌøÓêfòÃÑ­êÃ’xÔJŠ?|ÌI…$¸OÑS†ú ϾÐ7BïPe€GžRyªm´ Î$’ÞÓF¤îͨ íD  ºv; ß4ÂÏϰ< n¸2µŸyQ˜Ú©øÐXlÐÿh¯»-êâ°Sz®“ëb–¥¶ ¾¸|>pá‚Ç·‰ã›jA½Â`zÙ@‡/i!šµêÑœ‰«ÚZ ^ø^ìÃåèV2ã}¥óâ°1³8H·5·&Q˜ÅÅFÜÁIc€R@RÂ?•÷t0Ø«†!0jÖ_„ÁüÆ-Êç`ä͕ƖµPì@…œº¢% $wmC÷ˆR›$@lŸ‰7£õÇ-í›; "ŽªÝD­%u¢„7ÅÆÊ;êÙ„¢ÉTTÕ#AØ9á»ïºúõjµM)vLËnõdœ¿<ªGµ²éð‰D|5SÆãäYÂõ0r!åGe–’ðÍÛEÖHyüæÔ»ÊÕ;t`@R´Ð9{ºjKhš_€€Lµ”{ ¢èoÍgËt™ž°})ØÚÜòôY6ª @åãÉ`˜ü¹XôWê0é»]K+,†¬õ Ÿ:€úð¬[R à 8¨éÓ¹ÛcRNÊúL`{Ö8Y>ìÙæXa™l%Íi9ÜJíÁT ³qao;èsÓbñá~ñÏ‚ÃE׃?ß÷ +ñ0`aì;Ü‹籓—‹ÕÇ2tÞW‹_é8‰pOžÁU+±a~æ‡ öm}ÇÁåþMxHcK† ºÆã¦¤ïN=IÍ~{¶Tx_´W–«wÌЊ÷¯‹¿ûwç•òp=¬§º>„´Fó!ÆqÉÿRÏ Y8žÓ‹W3# ®¥¼¸{ú+Z%,IÓL ™½ †]eòØ`pË †J2 “°“ÝgÃs"–êÖ 0˜e¦å@¥ºÏ‡|LJ6,¡Kß_+*‡–Ý\Öð”mi…!‹z%CJ¬ ñl‰"¨eO14·„¼¼§¿RL~û^ð, ;ȹ?aâúµ_›º endstream endobj 894 0 obj << /Type /XObject /Subtype /Image /Width 484 /Height 543 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 20763 /Filter /FlateDecode >> stream xÚí½yÇçËùkÿÙ?v¤› ï‹ØðÄ„g"6vfçÉ!y"ÞìúÏ}ñü6bö--ÙRÈÖÚîF¢è[¶De>d{lÉ")‘&©ƒ¤$J²x7Ù÷}_èÐènÜGá>»Å÷ @W£ª²² QÀ·âˆB"+++«òƒÄ‰B._H¤’Ùlæã·oß¾MÛÛ[{ÈÇV²­ƒmDë–Û<Ü6€Ýý‡ÌÑ’­]ìsÁëêÈÖ dMXr6›â1A“¨KÅ<½ŢsÙx.#p7EVJ¢<‹¤9©:Mò"idŠR jdy(s|ä(í°U¥ÀGqk‹Åv•’*ÛRøß…9ß"µÞDôÛÒ¸¾”lÀìq’–a4`¥…Ùg¡|šxOhùìK¯å †ïbã¼tU¯|Iï(”ÑÛ¿*½R«óæw¡.E¨ '§€Ä]&5ÈcÚlœ´L¯˜¥5­çy-m…«3†]0ìê|Ç»ZA×Kt½Ýl]o›ý(aÅ•h×Qo[&꟨;ÇÕyS®Ntu¦%®®@r¦Ë)›MÑuXQëµ ]WZ û½îx<˜Ï%r¹„Ù¡u΢¡µ%®n›¡u©)Ck tmÎØÖ†€ô~×õÙ:QëT—Z=¨V¹ÚóÙLJù×S‰(­ʤ NÆ‚>I£ü4—Ö?¢Nåó©T2ð­%â¡jNQ7ÅÕ;tÅ…@¾Z”¶oIÑbH$§oPmÄÕTÄœžºuãƒß?ûö›ÇßïÌk'Çû#áMjRn]# ²·‘¦®[jl“[&m†¥›"j £]]Ì’g¦†oõH{úûSшŸ2ðô)Ê–J„gH» ¹85N…øŠù4«©/'ã¡i…BÈ9Þò¶†E­ßÕ,†6´ÃÙ¸»VËðGÿú?ý‹ñçW.ßȤËO³2]OOÍÿ«õÿÜqÇ[˜s°ê™K®¯9>úðÜÏúô“ßÿÆãý¯Çþñï?zð‰ï~ñù£ß=³ê˜£¦ÆÐz¯uÝŒvÉзÉ{ô/ó+ªm]–nõpz¯D½ËÕ´ÕÆú*«§¯ÌÓ&Z¢ÎnxV˜…Ì•#*Ì蕳Î.Ä1ÇÖµ…ƒjΖƒ L]‹³>²*¯Þ}÷ßÿËù_»˜N Ù´@ŸhÄôL:VVwZø¿ÿë‹ø‡‡žüÁf=Sósã¿;ù›o󑟾ø£ž—–—ç"á€Ë¹Üß{ýå_ÿìÿÃñc¿˜šÈe“2´n']—¬ÖµÑ1vÉÄ, &LÚá›?°m±¢¹-m2F½G¢^˜Ÿ¨õô§{z.;–"‘ Ëåèï»ñÊoê=}°ÊPµ¤U ™ß)D¢‹|¶ÁÒ)©¨æ$…ܸ¼L…s¨Þ†B’ÍT«Я @õ 6)Z¼ØÔ\ýÅ/þ3©ø‡GßI%«rN§w^}çüä'>ñüø/Ç"QÆŠßë9qì—ßúÆ#7®þ>Êüþ÷ù7?s»–¤£ë½ZwŒ®›<ÀÖali—,ÃiÙl.=SªJ:ÝDK[ûe¢aQüëÕž~í÷©DŒH&b‹‹sÉÊ 11>TëéËŠÁ*3àÛ)$™ˆÊ™Û)„6Q }¤ý>ÏN!JΙ8GqtmÕ ZI€‘vè\*ïZËÕŠºþÞw/â?ýû¯JV…Ÿ¨¬Ð[ˆFÿú¯ßÿԧοs~U½ª‰t*~ãú‡GÿÃèð­x,¤ÆŠcîºüÑ;B,Èvõ>Éb^×Tˆ^]‹»fèš^µJ×T”f·ëÓÐ+wK¦m­´ÍyÛä„|ÓS^ øY¯¢ÍYºu³>ÃÔÙLªÚÓGzB8.„ϽýæŸÿùgè:üìgïîëí‰WWóbO§uyÊd’õBâBH•Z!‚’÷ôL:±SË9óuç4mPÝ(À©‰‘x,(EL‘0 (jMW¿úÛùO}ê½/|¡7™(+:*Äk1áùçVÿí¿ÿÿßR:)0ÞS6Ö/<÷ÃW_~) °y÷ÂÙçŸû§åÅÆÐº¦héºq]W­«stÍtuõUKF×Õ¢8Ø53çEÍ ‰è2vã0»T*K…\¡ÉåÒÙl"’éX"‰+IÔI׉ÕI¦¤ÉL…t•T•x#6‰2TÃF’R2"ÔÏs©*Ù²eÒUr dÈÓcV$—Ëì&»C>›¯Aë9Ù’Q\¨ísùòeò9‘‚H¡N¶yx‡Bq‡ÄòTú¶:DM×Âæº«ÜÓ_yIˆc~ñ‹Ÿ}éK÷½õ晿ýÛÿëo>wo¬–þî…7ÄžÞ0´¦§›¢.^y) °©RZïToÔ Ùm˜‹ïWsŽ8´ÎYýØ%Àɉaê˜O|ïÛ±ˆ_äGÿÁüÁÂÜ­ïP6´–»Z®ë«W|ÿîßÍÞsÏr<c bºs%ö™Ï„ÿôO£ óqÆ´j·¹Ù‰#‡ÿ×ÂüD8äeã÷­}ò[ÃC½…òOr¢ÖRQKƒ!#íÜî”ÚIܘ’>-HÔ½Kà]×-uõ®Mv?-ÖÆØ;)µ {wâ.3×åyø\½k¤--D4vu½´³.ªXšSžRO”J»>®Ï¾0ýkÄ ³—¿àS °ðÔ·¶øFæZ¿šQçÕhŠÉØÅ]2×1C/Ÿ¡æçÊ=}qa2ö5püøoéZZšŸú}±§Ó¥ó¨K…ì|E‹óTˆ—WêÔSv )d¤®.î²Ë9ÿóá¯Pùßÿ èœânçX5÷c·7‰ï~÷[T z¤õ#?Jë?<úñ%¿·&@¬ÊD-wu6³k¾Çò¢ðéOgþìÏrÑèNÐ;•ŒÿøÇŸýÖ›ùL:ΘÅGí6Ðã{ßyÌ»é Ö¥¸œ‹ )ÄsÏü ¯÷Z¹ÌÝ¿‘aººq€-Ut}].íüîqµÚº¨k¹Åuù`[bò]Áº®¥Þn0sÃpºAæ c+ºZ:ºn°·ÔÕŠÒ–öwyŠ®ðˆRœ¤¨_P<˜¯ƒäˆøÛA Ã@¸£¡iõ©Ôù Õ°ÚÓ½î`pCÊÙ³§þò/ÿã_ýÕ_JÅžžÍ&w†C¹UFRÈúèh?]xßùÎ7i]äñÇÿ‘F¡ÓÓ£âÓj!™„ÔÕ;…Hœóøá¤¢>ÿùÿ\-PÙ9ÖÿJq·="ßùÎ7¨ó7÷Òãѧž¨§µÊìH8ËtµTÚé¤ðôÓ·_zé¶×—¬;9I­®Þöû·³™¸ô«F¹®ó¹tÏKÏþøßšçÊŸþé§×\K é¯üæ'=7®ÄãaùOMºš1´Î³]-MQŠ(­¥ì†Á¶|˜-•¶¦«ÕŒ]×µ|EÁÕR—ò’ÁsclD]ÔF¾‹dÚÛ 7‘¢A3ù®°жÆÒ…ú¯sÕžî÷4ð™ÏüÙg?û^8ÿ†4ñ•ßü”zz"©þ&±ú˜­²&òo—µF´~øð#´þÔSOÔ_­ìŽsfœsø±Ê†O>Aë;*8ÇúŸ“Kè–ò7Ÿ»§üÞñ_þ¶!½Z!¤(dWÇDr™X¡<ï<-”ƒÒ1·7‘IÇÖ…µ¥ðR(±I)Ùò佘Ê/e:ÅãcƒÿtôÛ×’oÃ)ŵ2ßBüò¥çF†û%­´«ét¹Z‘ޱw¯¤B"ê®Þ5ÆVN+š¼nlµÀˆ|ïlWË­æê†1¶â`»1ZRû˜Ìu©X(˱Ñr¼:“1¯NÅê4Æ«+aêDZà:ÞH¦Œ©xu&™)G­Sr˜ñêôÎK»òTc×»‚ÕÙL-Œ]!×€d)'dó"µÀuC0;¯J-æœÝÍî¸tA åxµÆÇé:§K¢ÚÓÝ˾M·oÓ%2;=öGô¯oÞ¸\O{z¾þ#IJ«“[Å\­¥zÎoë]„Ÿ«ÈíÉ|O©꿉 åÙw%i!®¡›å ¿ÿ=Zùö7Ðà|rlP|ZuNù·x Î1ße>öèÿ¦*ý—ÿü·ôøío>®(Àš•\Vv51å›èYëY ®“Ó騢æù7~¿òA<^þ†1*®–»RÕ|ÆåZ>úäwæÆ6=6îÕù_ýòÅÅ…Y:qâïttºzWøºáÛF¦´Sy­ïw^•ÅC4£Ùjß9Ju-i«¹Zè¥ÍW+O‘)Õµ$6¢üEdu$,FhMÅ«›ˆnúÝäŒÏ!Ùæ»}S‰ö˜|WŒZm€]§1^­}‹ÑÕtñ¸\êé‹ÔÓ×W¤ÌL 7¤¸ bO/Ñ»C¹%ÅGª‰¨‹†B¾þµ¯ÒuHŠÛ]HU•xu¥ïW\MÕ–8g…éWêÔSê…ÄÛÖí8Ç’4I8*ZîÑü:–<ñmZÿÖ7£uz¬ pN¬L>ŸRpuºîêX•L#¿_¾øÜÀs—WèU_8ðdÏŽ\=â -JSѵ@­†NûÍÅ÷Þð¸Ùô÷]~ûÍÓ››ž¯A³ wä‹×<ÏKÖ%‰jsEj+•÷_ù[º^¨­W.¡*uWKŠƒmÞ8}X)Ö× i¥ ¶¢ÀYÓH$ãg²wVi]aÔÝ`uÅȶÒ5 ÙR£ØrTjbøÐ˜-Vb…8r:É*¢/¦¡JºL~ÒyÑ;=}m©ÎâÂħ?ý'ËKSÒD±§{7=Å|ªÖÅÊ+T b!ćœoH©RÈíú1KAZˆ{‰ÍŽsvÆÕRK &i`ß­+•y ߪKïG¥AþÈÐÍŠ/‰•¡]ï’pMήN×^­å|köÌû¿?ßû<©ø…þg>êó?ü ªéiTÒ) ]ÓôòÒü?ýð»S“ô.¦ÆÒÂÿ—ç²™„ô'ðjºV”vƒ®Æá²ÀÈ.o7D³¥¢VÔµtŒ-¸¢®¥mÙt‘´ÔÛŠ3F´:‘Â[ªh©´ÕÞ»sæä!ùÄ?>u›°·n«Âòêi5HÉb?·DÑê–®ß */öôé©A·k¡Îâ¤ôéÒâ¤ØÓsÙ”¼—ë….$[5P/$›QøÕá®Bœ j,-ÖÓQט­ °ŸD74x£A}bJ]€™LbGÑQ+»z··ÏϽñï_ú÷_8óÿ¯õ~â¹OüÉÏÿ$ ¬U¾©Œ¥’å›Çc€](¿a¥oݼöóŸþxj¢ß¹2#g~näı_ ö%BãÜB%]×É«¢þƒÇœ"Êó± ªÈ§ak¢þ£Úê½Cw·RŒi3È¢2Å«/rÙ|9›ªÆ«SÜñê¤$p-‰WïF SKf_W)OÃŽ+Ì»®’(S®aIÊáëÔ™*™2é*ÙF²å¨uF$×H9R]]v‚زPv…|u¦vNL>/E2S»|Z*ì$VóªßV((Q~©µ®<­„;ŠÞåjÊ\ïéÓ“ýÎÕY9 s£µž¯Üi§OU{œ´ åBæw ”~r/Q¥…¬ÌÊÙåqP]ŽE–M¸[€}š,ªUl¬áêtìæê•}OíûÔO>õׯü­¼:ú›HL¨Z:^5¶Š«cõw–dBèë½ñãzâÃ÷ßœ›ZYš\]ž"æg‡¯_}ïç?}zdd0 +ßš•©ë=5vR¿±÷LÚV{»2JܪD­Å_y¼]¹í¿‰µ©ÿIiâ-›´~ ©kr ®ù$ò‘¿ÊXwZˆÊ¼ŽBDš¡hÉý¥Å¡u)ŸLÖ{ú[ó3Ã+ËÓ«Ž2 ³#’ž¡îRôîIwÒBæÊ…L‰…Ìï*$\ŽTËD-BåËœC™&Cï–ÉYÁÕí‚S€Ô† kºz=ì E‹®iLŽ–«QQ§R WWu]þ!Ÿ÷n®¿qöÔ Ï}î™'óÏ/þä…þügϼsáM¯wƒ†¶×ƶz˜Ýig˜ÓH,r«ß¢¤¤ÁVÛ£yf¦Ïé n¨Ç7ŒE98ªŠ–B ÅîéùlùÎ!Ì'J¥œ¦.H)2E'¤¿TÌ™uŽE®&JåÏ,9ve¨U®¸ºTquT-C&ý?~úoîxæïxöŽ«K7"±xXLâå[²¤SõÉ{jåó¸U¤béT2nlx"‘P*•¤Oså B¹$ç?Ú´ÊØ-fë‘vÞŒ´ ¹­²·ú¦Jf±F¶ºmœ·´Y4N¥rVýºÐ˜¢¥ÝB^*ÿ`>'ëéÙB9t–QS´”"»|ºÁÌ ˆ6(O&TvNV‡sÌYºNE€5Òá°EMŠ–¸:*1v”Án §¡2“:N±T,|¦«½á•{fl+‡Ù­”¶õÞÞ {ë¼ 1Ûó—ÖŠC3lf+ül¡¢w,½« и·:‡zúvi«T(*[ºÞËv÷;ñ[ÂB¦òmr¥­J!LK+Ú P¨8gKtNÉ s̉ZjìÊТü …J•Ê1Ôä,Y‰*¹º¦ÛŒÛÊav‹¥Ý4o[bï–9ÜFp5ZÓÌlÆÏiÞËUåâg:dkGÑê°Òy¶š(g.KËÉB+"º:“Šˆ¤“‘t* n„ƒëáÀz(è)Sþá|å땟výn"à/ÿ(Òïs¼„ÓOl®úʬÑðºwcÅ»áØ\'–7ϱ¾¶¸¾¶´±¶´î^$V$Xÿá[Qhn5ÍŠâ­üò±a¤]WGEÔƒ!&Ѥ´:„bpò¶fnš-±$T’íøÈF“ƒ&oð‡5¤è q(u‘\ë\Úá _4Ežu9·ÊÕ¼¡ïV뺣~nÓ¦nýß+«¯¡©[ÕµIع ÝZK ÖY:Ö-ÄæÆn®´sí mÓÞæR·A{w¸¶Õž×ºVØ '«šÙ¤œ ú¹ÙŠÎµ™¢ÛÏÒÒ9{{ëj›Û¸´›áí¬Yoë°·)[lòvÂt›äšdfãñ £~6£hXZ¯«÷–Ý•Lï`Íœ½(ý“NÃ_Rj¡r¡²¾ì–{›‹i°7+¦©Ø³,4Ðë‚ûúÑøÏÁÆ)ªÿ‡Â&Ë‚=×B/z¦[HQ±åÞÒf®VÛ4v[I»åÞ¶@ÝænäÇõí#v£ÕÖßDVjY§œ[æç¶Ut–¥èh[‰±-]­aì=fsH»™ÞŽ›ò¶eêV¶Ì“ocL]¾N6$ç¼.97ÑÏ–+ÚÂt´ •ØÆ®¶<0b+iëô¶u7EàÊ1óBo¶ iÙÈàYãaí­h[„;lëjˇٖJÛJooÇU¾ l{뛾Үîm¦„›kæ„ö%dBÎ{ágkmÚÇÕ6’v“½Í¥nKíÝj‡[ø`“:›8/ÍlzðÌëg(º{]­>·°}¤m½·P{bo[šÜ~6n¾™uÜgcül¹¢£¶“ž]íq;ÞyçÍ“¯¿Jœ:y xÄ#Ûÿ‘xç·Ö×]âê Ïê¹sgúû®ÏÍŽ€ q?vs}¥\}ýÚGÓS#áÐF:Aâ"}ݸ~©\}óæå`ÀÞ€ö‚ôEëW $aœq€!}MŒ¶ÆÕûöíÓL±µ«ép ¿jÕ^pµ’«Ã5¸\Ý g¸®4ÇÕU9suƒŸÖd^Ï_OT´½<‘ÓÕ¢ôÄÍÒ¥‰ÒW×ë™^m(„ñ”?Q1ÀÖ¼ðÂ3DVò/®´þܳO¿øâ³Mpµ6£V¥)ŒDµuÆVj(ºZ.d¹Ù®–›¡tžWu%ìN °.Úãé­'>õäbb(¸¹ÛÕa½vµšl9n­«uéQþÈ09§c9_…«è\Í|êäqJ9~ìeñé¡C‡dãê–ºZmŒ- hðºZ¾•yWKîV+DíUÅø£2€Ž!ŸË8pàÎ;ï¸ë®ý4ºÞ¿ÿ“´~ÿý÷òY«\N†ELºZ×°™=,·p\­îàqµˆ±Á6 3Ȥ_yèÁ{ï¹[äá¯>”I'•¾[ä5^]ÎÛ[¦\ÍA·a D×7Œ<‰zC܈Ðñ‘Aˆ~쑾üÅ#?Çn®e•«y É0RDqI^el%/Y§«ó1ÔF¶Œy 3kNaFpmÐq‘¤ßï=}úT àËçŠsöt)×°«÷ünÐþº.sô¨6¿®€öÿ-L \½O}i«ÇFûáj€}]=>:Ð ãê¹™±X4€3°#¤¯¹™ñnpu(àñ¸—Ó©H.+à¼ìƒNF<î¥pp½\MDBë›ë+A¿ÈXã"À"Úö™â=Ýèm‰+Þ0æ=;º"¢ƒ”6`žÖwÊp…w:5W‡j M s‹­ÞÁ’7pøÛ×g÷R•3\ Z¡t‹<ß.Xظ¢€AW`9]×­°¸àjp5ÀÕàjÍvu*¢>ü¸ x a’6[ìjÚ©wÙLcÁ‚ Ž%™ˆ{7VIž-su<æs9···¶J…\. @&IÓå\ …¶ÆÕëkK‘Hhk«˜IÇpBÚ ‡¤Ð¸:!øg¦†iPMohyà'O£ë­)”DÚlW QßÈÐÍB!—Ï&ÐòÀO.›Ì糤P½Qk¸ìáê­R*« }\]—3\ öíÛ§™¢ø*;ÿî9킳VÎÙ\MÝ è6Wó‹x‡{àêNrµÔš†l«›§&ËK†«^W“BI¤ºÄ W¶«÷ÕùÓúcƒF6i(VÑ9Œ½°«ÚðÞ¡Yyý5„]ç†WÙÂSC] àj¸ºÛb òG¹…ä+Љœ›(ŽƒvÎ èª Ï°ëÌ_5lFÄÀÕ  ]mF>†]ÍÞ1 6 fvgàU¸®†«§«¥K›»Z^Õ†ô¸š±;ÅWãìfp5\ W3ÆÕV}~oÁ¸š³ä¦Ö™s/ÆÛ®†«»yΞà³_Y¯æ×²™ÃÔ¯æ€#^ WÃÕÀ¼«ŸÐyâ†çhN½PÌù_µy jb`ÃÌšSG4›ÀÕp5˜Û àjl7í£YW\ W\ W@Ë]¨W@û¸º*çí­"\ vsµ6±Èæðàš«£8ÉeWß ‘ê/\ p5¸àj¸àj€«áj€«ájh¥«“ñ€\ íã꺜·àj°›«yˆ†7‡Ê®ÎeéT'ÙLÙÕ¤P©.ñšsu<Šà$›‰ÃÕÐé®ö×h®«“‰ó‡C¡à:=Ò:¥àôàjªrn™«ñ`Àï 6S©ÄÖVéöíÛôHë”Béô*N"®ævµ6Ñð†^WÓøycÝ™Ïç¶··óù|¦¶Ð:¥ärYz•òà<ºÆÕºÄÛWÇ…€ÛµL£èB¡VZ(½T*®¹”S±„}ûöi¦°Ó®f¨=G&“¦!tJ}!]§RIÊ©¾†«puS]m8W—K¥RJkÙÚÚZ]Y¢üš®¯ï«-Š/©mÛ°IÃ^ lÈßÈØJ¾¡¼@v•pu³]J†Ý®¥H$œÍf“Z å ‡C”Ÿ¶âq5[Ѻ®ùv ¨tέW8«€«[àêD<8;3V,Óé´¦«)O±X üŠsB85Û WóGZ ì®®ÞsW 1ÿèh©T"'´ÊCV§üjBÑ~WË71ïjF5LV W·ÄÕ‘áÞT*ÅéjÊHùi+W›Œð‡Äùù&ÇÕ¸zO\ããƒ.§3Nǵʳºâ üj3÷Œ‡›áB#^ hÛxõÂüäÍ›×9]ÝÓsò3~è9­‚!1<Äd50Ðæsö6ÖW¯\þ`ee%™L êK*•Z^^¦œ”¿ÎÌ \ÝV®bþ©É‘ çßbèšÒÇùsoRÎþ¥9¾C´óoÌý>÷àÀͳgOõõõƒÁD"Çc±YšÖ@ooï™Ó')åÔ´\èVW“q_ ë]L„67œCƒ·Þ|ãw¯¿~âƒÞ›¥Ç÷ß¿øÚ‰co½yš^¥<¸?*®ÞMUÎ-puºòÆ€mnvüúõKçÏ¿qêäñ'~K´N)”N¯*þ\àj%Wk ¯ \¯¸ZH§ÂüÄ¿ßçr®Î/ÌOÌÎŒÑ#­S ¥ë*lJÍÕ×I¤ºÄÛJW‹¤’¡D< Bë8w¸º ] pµW Á'W@û¸º.g¸lçj"¡õAÑÕi! àDt5)”DªK¼p5ÀÕàj€«áj€«®†«íÅškñü¹ß?öÏÄ‰ã¿Æ#ñØþÄùs§=î%¸ºKX_[~ó쉛7>œïØê¶o½ñÚ†g¹%®öÖ€«÷†+—ß¹éÝXŽEÖ6‚º-uÞ«W.6ÇÕU9ÃÕmµ«7<‹’° Ôy© ·ÖÕÚDBžÁkWÇÒɰ„¡A:w\óØê¼ÃC7xzz6#T\}Mooª«Ë¶¥•¢ “›3ŠÕ,¹I»Ötõ‘#Gä)⢙€«9]Ý ¸6t5g™{âjE'Ë×pµ®qµÔrõuF¢Ôð ¶—gfSÌ©øöÑP¾ÞlŒÝiVŒßÕ¢uájö–gŸ9JÄc›õZúG?xîÙv³«ÕÖÙ‰ì¢ÇÆŠ›ódã©9#§É\ @k¿4\{÷ѧ¾WOüþ߽˯æ7¿Wù͸Úò:ÀÕØˆdÜwðàAÑÌ'ŽýšR^yù%ñé¡C‡êó4:é»Å†1¶µ®–.æ]͈rðì®aÝp ® Ȥã¸óÎ;îºkÿ>±ÿ'iýþûïËf9¤©®æü*ÐÀè—ý‰€]²®¯&ájÚ–T2ö•‡¼÷ž»EþêC©¤ÐÁsö4¢<®6NA¼`2‹…?öÈ_þâ‘Ç„Håßµºe~µ®H‚±y º‚šÙZ<ó«h#]'>ßæéÓ§ü~R·¿Ãæìuæg_ãw‹´{$$Èç’ôØyó«áj=®¾W`çßÂôÀÕÝÀ0\ €]=ÒWÇc›"põ^173ô;qÍ`G¨óÎÍŒ5ÃÕu9ÃÕmB(°æq/Æc^é]mº-uÞpÐÓJWó® ô_%WgÓ±T2¬"òl¬;~ÀFP·„×9»y¦âjR(‰T—xájhp5ÀÕp5ÀÕWÃÕе®Î¤cÉD'¤M¸àj¸àj€«µ\½Q®€öquUÎ[[¸ìæjmÂA÷@ÿ•Š«£ÉD@qÁlî€ßE´N)z ›BÚ¬¸ú ‰T—x[æj!æõy~ÿz*™ØÚ*ݾ}›iÝï[§tz'Wï­«iüìñ8òùÜööv>ŸÏÔZ§”\6C¯RœG\½W®ŽE½.çâV©T(ÒJ ¥—JE·k‘rª²o÷²'¬¸_þÊìUµ÷ŠfoÃÙ×»­a\Ý WÇßš{1“IÓ:¥¾®S©$åT _7ôÇ=éžfv Ÿ4£ýë)h^Ð-®ŽnT±ÚÕÁ€{ue±T*¥´–­­­Çå×ëjù`»a¸ÅÈÐGq$/*T+„³Âj‰<Çȹ‰Zûèm:þDÎsÄ_y g€tuMÎ[¥&º:÷»œó‘H(›Í&µÊ)?mÅïjE9°Ű´æV<›0†š%óo®vŒš{Ñ< ÎFÐÕ\ÖŸ§aè|Ws¸ú*®NE“ñ!êž.ËajMWSÊIùi+yQñj¥ÝÕÓueP\g'jn¢¶_½6pš5ä? þÃd'êÚ‘®]³   !m–]Ýw…DªK¼¢«…膈µ®Ž†7G†n•J%RqBk¡<Åb‘òÓVüºkиbfv5-0¶2éjÅ7Í·!ÎC`¿µé}“â¯*c_p5†]]—sÍÕë"–»zh '•Jqºš2R~]®6?840z4ãjM½{“†D3 56œ†«0áꪜ\-H4®FHâêD<À ñŽŽô9«ét:®µPž•å§­äEQOTÜE=]ÔÉ3óghÈÌHäÕd}Ìo"¯»4“-c ¹ôÖ_3íLºæj)oÕ]­M(àâtu,êë鹯éêׯP~ÚŠßÕRç¨ujvƺÔfŠå(nÂ~Oa”ÃÞœóÔ ”ç¯?2ŠR¬g¢fc꪿š«å- €M\íÒ%Þ¦ºšXs/]úè" ˜“ɤ ¾¤R©åårNÊß‘'¨ eÒI~ƒ«\mÒÕ‘ðæÄøÀ¹so¬¬¬¨éšÒÇÛoŸ¥œ”¿SÏQ»ù¤cüQ¸Ú¼«‰ÍÕ¾¾ëgN¿Þ×× ‰D<ÅbdiZ½½½§÷塜Œ€|ÁyÀÕV¹Zˆùøàý±±±ÙÙYz|ÿý‹'N¼úÆÙSô*塜8•¸zO\MÄ¿wÓ9==rõʇo¿}æõ×_=vìez¤uJ¡tz•òà<àê=tu} ßæÆêŠ£<¦žž¦GZ§ÅIzWëcìXÔ+‚±4®nOW\ W@‡ºº D=5àjhWWå W€ ]­M(àè»\qu$÷à¤æêË$R]â-»ºT"ž*p5´«kr†«À~®æ äwôÂÕ`ÔÕ½—I¤ºÄ W\m—sþÜÛ§ŽûgâÄñ_wöã¹·çr- 1јÝÖ˜p5¿«cH[¹Úí^xó쉛7>œïëèHßzãµ5÷Ù= W³©Ë¹]}ùÒ{c#7½˱Èz7@GJÇ{åòE4&³{®6æj‚~gK\}íêÅ ÏbBðvt¼tÔhL4f÷4fwºšJ"Õ%Þš«×j´‘«‡¯GBž®êt¼ÃC7ИhÌîiL¸Z‹ªœ›êêÊßlYÜ#ŽÔ–®íÆZUM/Ú’mÒ˜Š—«¯aî6)v±z 7PþM8sZçjm‚þÕþÞKW‡qƒJå}†‘÷éåÝ‘’©õë[•ñÆ×é®öYgc*^®6½† 7¦I X[f3*£·üt*Rqõ%©.ñ6ÏÕ’°…«Ë<ûÌQ"Û¬§ÐúÓ?úÁsÏþ¿G4´*#«5¦M]mIcJ;—¸bmcÚÅÕV5¦æõ)¿\Õš}a7œ5ÍrÊ×Õj¨XˆZùörµ¼æJDÞ-®Þð,‰‡|ô©ïÕ¿ÿÄ7ÅDïÆ²a½p^$iRc*6¬™Æ´…«›Ñ˜ ò4;ûÂ6Peoâ<öæ¹xláênW'㾃ŠWÔ‰c¿¦”W^~I|zèС„àƒ«;¯1máêf4& k&ê}G`—c`¿jÒ†«;8’IÇ8pçwÜu×þ}bÿþOÒúý÷ß—Í$Ì|lïBWÛ¥1í±ª1ÙaÅÏÔü±ˆ¸Z³þš‡Ó1®îæˆH*ûÊCÞ{ÏÝ"õ¡TR0üÝb7»Úi£ï-iLž2xÙ㽦ºš'$ξH0®î WÓçÍX,|ø±Gøò<þ¨ D(®îÔÆ´‘«­mÌ=‰WóìÚd@Fﺽ\mí<˜_L|¾ÍÓ§OùýÔAüÆæìY2¤¦>ZÒ˜M}ã³Ñüjó©ChÙ<µ˜;ÜÁa :`H3f±v©D ŸKÒãžO Fc¢1÷¶1åš=Mº­çW‡×ªÀÕÝôó 4&so³mÕÒ¼*évuMÎ5W»«ÀÕÐ ÙÂÆì6ô»º*çFW‡×4Ùqu2\™~Ù,f¦ƒ~gWõ:Þé©!4&³{³ÛH'%®æð­º«µ úWú{?j«ýÞU·sNˆntIw #¥ã øœhL4f÷4f·ºú#©.ñšvu³ßÍW=îyߦ£ # œhL4f·5fW‘N†;ÒÕWÃÕ`#WGÃn¸ÚÇÕu9—àj°›«yøWú*®¦Æ/NRW“BI¤ºÄ W\ ®€nrµ«\ íãꪜK¥<\ vsµ6¿£æêP\ØÀ‰ÄÕ]âm±«cÑõphÍï[ñyWè‘Ö)§W·‰«£ÏæÆ²Ï»–LÆ·J¥Û·oÓ#­û¼nJ§WqpõÞºšÆÏkîÅ\.»½½Ïç3µ…Ö)%›ÍЫ”çW³\rUi‚«#aÏêê\©T* i¥…Ò‹Å¢sužrª²O¶ìI;·f¿ÍÞ »üú«VUƒsw¶85´ÚÕ597ÛÕ±èºË9›I§hR_H×Éd‚rª…¯Ñíû~W`½«9ø}·*®N„â±M6~ïÊŠcžÕu-ÇãñwÞyç…^x÷Ýw‰D=}kk˱»{¹té’øå) ”Ÿ¶bŒº¶ZΑ¼®Þµµ{akÍ‹j~ˆ0Óœ- ?ƒŽvµ³†•®׆o”Êsó’‰ÊrüøñÏï^Nœ8!¾DyŠÅ"å§­øÇÕzU¬(|1 õeù^ÔÐðÞ›q°šg‡]CÎr``WWåÜlWô_K¥’uW÷ôôü·ÝËÍ›7뮦œ”_—«ÍŒ« Æ[<Ô4¹žVâŒXþ!Â@ìÂÌç :ÎÕÚ|Ë}·~ÏãêHÈ32|Óé\M§ÓñÊ"ÂóÏ?ÿ@myñÅ)E|‰ò¬¬,S~ÚÊòxµ‡´›¾ö$^m¸üß XõýâÕÀ®þ=‰T—x›êêhØ33=ÔsãJÝÕ¢®‡††Þ{ï½ááẨEW_¿~™òÓVœñj]ó@ô†; Ïaó8?ãóDš1D±p3ÕÐÜD>ƒ' Á¨6b ®ÖëjÂíšûè÷שּׁ8’ɤ ¾¤R©åå%ÊIùÍ´C·uÏ69^X€¸ºT*DBNË]®öž{û C×”îp,¿ýÖiÊ©¬†44ç][ :ÛÕu9puÐ縚ϥSñ@,²®‰Ç½pëæ¥ß:Ñ×× ‰D<ÅbdiZ½½·N~âÄ«ï¿qlltvv–/^|ïøñWΜ9A¯RÊÉS Ø”¸Èe“¤P©.ñsõØè\6‘|œÕ‹F<ëžÅɉþË—Þ}ëÍ“'N¼üêoE´N)”N¯RœG@g“|ÙLœÚWý+ã73éx2î‹‘`¹ \÷üÒÂøôäÀÄX/=Ò:¥Pº®rÀ¦Ä_:%BI¤-põÔÄ­t*–ˆycQ^¢‘µpȹé‘Ö ”6%.xÓ©()´5®ž™ê] ¯à¤ò÷¸QR¨QW¯Öàrõüì`*‰Ç6ñ‰øbäê)”ÛÕU9W]\­Â±m(°2?7”J†±Íhd 'Bl3™‘BI¤\®®ÉYæjm‚~Çâüp2¢ëºþ/ º!¶Nò$…’Hy|«êj‚>‡cq4Æ#p5èqut=BI¤ºÄkÀÕ!¿cuy<÷ÇhHvàf-!øI¡$Ò¸Ú½:™¼BÄ£ë  Ë‰•]í%…¶ÈÕÎiÚ],ìÖ5纜Xd-ó’B[ãjk&Û Wã]tAò$…¶ÀÕáÀʦg>ñDÃ.]@·*ßRƒJ"m«½ëóå/C.®9'*D+aRhk\ð.F+HÐ €—“äI 5äê|$¸RƒwŠuå5N|¢=TîÔ¡cruUÎÆ\]yG€¨Àˆ®õ ªÕ\  í]®ˆ A }¨Ë®Û¹@W\ `o] 8üÞE{€&$LÒ¦)WVªpoH;u9gâBäã·hBÂt9§Iž:,]“s©hÄÕ~ïÒÒâøÖÖV±˜M§Â4!a–J¥¥…1R¨YWóáX …ÅB6óà¤Xȃ^R¨^ëÖ\í¨¡½IÀ»<:tm{{+ £å€]omI¡$R>KWålÀÕþÍÅÞž …\:W€RÉp>Ÿ%…’Hù]íqMË\­s®s®^àT.‰ú¹“É„W@ÛºšXwÏ`\ÝÙìÛ½ðä·j¿-Ûª“N®X¸Zmh WwU÷×´\ b  †jÒ”†ñ6û)¢<â:#c[Î*1^×[¦á(7a—f¸Ñ4Tq«íîj¶$ùª™Èð^“öÎóÁ™SmCv¢ÉcѬ6çáÄ@àêÎWk:V׫ìœj£Y ]m¸nü®Öõ€sw¾'Ä@àêÎŽ¨ Añ“¸ZŠZ9Œá´ÚV옉f•ŒÉS­pþÒtÕPoHW\ Wws ÄØ`ÛÀÕØRWdƒ k&cl8mlGÐu»Zò»Ååpu·Ìá „šÇšÙ{“âÕ†c üoj͈W›|§ötuUÎ¥b®îÚùÕõD51šç5CåŠ{×1<D3§®y jhlb ]îêP`Y$Ìs¾·çýŠ«CñØ&]BŇh`Šš«ß'‘òø¶.g¸¸ØÎÕ<ø6çoU\J„„Ø&N’W“BI¤ºÄ[uµ¹ \ íãêšœáj°Ÿ«9ðmÀÕ`ÎÕóºÄ W\ ®¸®€vrõR ¸ÚÇÕU9ÃÕ`CWkãÛ˜»Õs®£®¾H"Õ%^¸àjíæê}²…Qgö«m‹-ª­Ùøü‡ÙŒãå©^3*К Ò@9-¾¨Ì_b <ݼ•5·EßlWw˜ôºöݤyg‡³äîquÝ{¶¸Bšú&W﹫¥ï #pŧŒ X¾¡â&ìÒÇœ‰ ëj;â)\ï®y^•¦+V›]CÍB8—q¾4«­XþS£V8ûXxêÉ“Èy¼lW³¯aþvP;ÅŠõѼÔÕ>8³/6½¶æHÞ–gïN×õÆÿ†Ûþ®–Û•á[µîÏ–{_š»Ó›¨YmvNžƒÒµ ýyÎ…™ãå?4Îv¶äšá¿šz…pºÚªv`—cà"Ô\1Ðbü#yk[^sp®«9wÔæñj“]C¯«ù¯þÝYÒy›ñÓ€«uùÓL׳ÊÕfÚY³Ûrn®÷‚ä<‰V½/Xx¦ôážhFžÆÎ‘á0‹Uï;v‰°ßèzW{/Ð<›j­ªVˆ®ˆÞ7ž@ÉMºÊÕü× ûŒh^$싳ÍÛ†?ú™Ù\o D× VWG3àjgÐp „¿;ÞÕÞݱh“ï݆{„áaƒU1W[5"2pM^ºÖŽ–­6#bÉ·Ì–GŸL~ÎírW[ÑõÖoy¼Ú°Ðøß²ù#´VuÀˆ›üšÃð»°ùx5gü¿âÕ‚óMJµ««ƒBlCŠR¼ºš.Í#Íܰ¡<›Ú.ÔJ®?2J“ïŽ?±aý*»ð†WÍoÂØ/û\(¶O•4Oç™U̦X½%³…}AòœFÍ ´˜æ5làå¹f4N~ý4¬0ú8OGccÉäߣ1pî%™ µƒ«Û]×v¹†qaÛ p5\ àêŽ?jµæÍhÕ&í®€®¸®¸àê.wuë¿”±ã×@j“úÔ¦ŠµI™,‡=+² ëϘfiÕNÛäÔ´§.Ì\ W[^gSíåj3½rÏ]½WÞå®îìqµÞ9üòGέËço|«ê)ÿí€æOZÔj®™b8‘ýƒöÏš­Áy‚Ôê©·ýÍ4µZfž_ÁðÅ“¨«þŠÇÎs9µÃ¡™9źº€®.£ye2~ÿÂ,ör5ãGOš?;b|ä)Ÿÿsb3êÉó#5öÏÁØ+Æ ´¡Þöç/Ü|û›ojceɹି®Ê·Û¡YØÅ \ØfÔÁ.ÐØIlÿqµy±èò¡®¢,¯§jpöA]]C×§]kß#Œud3­jæHMÖÁÀ¹àyó2i«C³ê{k6eë ö(¾+qÞªBí×F­qµzêÊÉ(œ=>Q»ÿ‡2®Öl 'H³{꺥I3dÂÔìOÜœåv5ã'x{{hžbv°ÖÕ<·CÑ{m=®6ð¶»'ãj ‡†¢÷C‡™ ˜sìgþ¬5ïsM ê`Õx•óüšìá¡™?ÅÖ~¬02pvU ÄXÌ­ñjk]­7\Ünñj«b œß;˜üPl8^ÝŒ6#^­ùJ‹l`y Ä’3b¡ìèjÁÐ 6­[òßÒü<Í¢&¿šßÛy šŸ ­[âð#¬w>ÿ Hù+ÆyFöðªü16<4½]ÕÂû ›¹2yzG'Å@0¯ ýhW7û–†:A±žp\ ~c®¸®¸àjûºÚÀ÷MM½;0sîì~jôþ0ÜÚ[áñìÔÌ-RÑqàê½r5€«[&íVººyýºîW+þœÁÀå³Ê9gž36ἓ*Ï&ü9ù«bò'-&ü *·uÕ[ç­hå§ÆÀ­Kš};MöQËïGʾŠÌ\Œ»R0~o¾u¿Þáênpµµw#´ê'¢œÐûS;ÁôM;-üíªÞV2ÿ b½·¢5y—ËÜN“ÇÕº®"ó—U+zÇêÐu7Œ«ÍkÖ€¬¬µŸÉ›Žš¹é™Ï¤&oxeíÔ{6 œ]—Š®©}âhAÕ>ÄqÞ\0t'^¸®n¶«ynZØTW znÂyßHþûšŒ´›«›qKOc;jª«u]z]mø¦¸p5\Ý^ÝTWøk3“ÿwfU DW fb솿²A¼®nÒ<Aÿ_{°§…pÞÑÑäýW9o¡i`ˆÀwT]wtäÜ–óû5KÎ ÿßÎêý´Þ‚Ûir¾s†¾ \ŒË©Åó@ íŽ`jqgìtᵊK ®†«ájÐæ—+®+ünàj¸àj¸àj€«áj€«ÀÕWÃÕW€«Ûçêì… g_ý·'_õÔÉcxÄ#;òñäÉW/œ?ërÎÂÕvÄíš?÷öéþ¾ës³ã€Î†zúùsgÜî9¸Úv\½úáôÔH8´‘NE õtêï×®}ض®æ¿;n·ÝæfÏå`À“ËÆ v'2^âOä|•]åÈ3×oËÉÈ ˜ßªúÐJ¨¿S¯‡«mÇøØ@26ïX[¸Z.XéS¹®þQËÚj°'PŸlçç­ÎùÿE±K\-CÊ_åO”kPm/œ‰üC\y•4]ÍÈ@ò ÏÙŒPO¡õçž}úÅŸµµ« üRçý'…ÜÕ -³ÇŸ<‰ìrÌ'ê„s¬âHfmE °.^™Oÿèh=ñ©'ŸCÁM[«ájK\­™¨X¾™W[æjž Ð$<(šùÔÉã”rüØËâÓC‡Ùh\­6œVû«>v̤c -s5;ð«8^e¤0Èìq¯IW7#Þ€Uäs™ÜyçwݵŸF×û÷’Öï¿ÿ¾B>kkW›Œtj¼º®6PޱÁv³ÇÕp5hs2éÄWzðÞ{îyø«eÒI{}·¨9ZF ¤ñjµD½!nÄ@P‹„Bôðc<ðå/yüÑxlgW7ˆ±Åžl-+r³çr0à1vWgÿ~hÕÝ¡ÍüŒµåhþM nˆ Ú êï7{.ÙÎÕÒDiŠtìÝ0×Ì ÀËŸÊÕ Q+M±Å}©©ùÿ-ÂÕj6VüC1ÍÖ€«A[!þߢ\-OdKX-Q1ƒ¼|ÎMä•T+] ZfNW[û‹ ª¨ö”?‘ç­ªÿ“â?9b\ ZÉ /”I'íõÝ"c~µµ®V›Œ¡6óÄð<µ÷ Ff9c£MW«%ê )[¯æ¯\ ì „èáÇyàË_<òø£ñxŒR\M#4›þn±m'?·€‘‘ÞfÏaxRsª;0blˆ®úðÏèS“3\ Z IúýÞÓ§O¾|.!Ÿ³7:ÚkGWw³¨‰©ÉhÄË€Ó5ù“å/Q§^ß¶®Þ§²à~ ›ËÎÕ:GÒ9™vAñœ¢Ÿ õqêéÎÕiï¦÷n²#>ï²Û5»±¾èl¨§û}Ügpÿj¸àj€«áj€«áj€«®†«®W\ W\Ýå,-M_»yãÖà ³¹~óõw¸ÚŽ,/ÏÜìè%öíÛ'®:êéÔߎ™vvµù;ìñü‹í¸Ùß×?<ìv;¢ÑAG$®:êéÔßoöõ¶¹«OI»è^\^„nŒ@ÇC=ú{ßÐM]­öÇ^І%Mgü-{Ï(<¦ôw½ŒÌæ]½â\«èWS§¡µ]mæÿmÕ\Íþ»C†Û×Ù™áj€È /õäbb(¸iÓq5Õ¬ÎãjÆ÷˜Š¯ÂÕ“ÿ`~ðàAÑ*§N§”ãÇ^Ÿ:tȾ1M1ò+š=®æüj®˜þKÜÌî¼óŽ»îÚO£ëýû?Ië÷ß_!ŸíTWëNó¯#h*™tâ+=xï=w‹<üÕ‡2餭¿[dÏÊÐŒh~‡hlgQ–ºz® “"!‚=üØ#|ù‹G4Qн\ àjº#’ôû½§OŸ |ù\Âvsö€"ƒ#CcSSÀ&D‚\-®ìK"ÍåÒ‰D¤!z:õwêõ¶sõ>¥¥«\ív/ô ŒOO9ÝN—ÛI‡ïª¬: êãÔÓû††ÖlsWËÿoÅZWÛ‘›=—ƒnÏ@—@ýýfÏ¥6sµGJŨÕGiJ}½&sOC";§ô)cŠå3ö./Q”|¿üŒ $a\Àt Ôß'Æm@ÚlsW³ÌÌΩ˜Gí ‚]¦® 5\ @'ñ ÏÙŒPO¡õçž}úÅŸµ«Ù+š‰º ä,J×Þ53ÃÕt'@5ÒûôŽÖŸzò 11Ü´«QÍlCf~WË·R,®þóƒŠ9uò8¥?ö²øôСCvW³Ç½†»ºÆÕìñû®ÈþÁ—ÏÙíí$W3¾ ì$¦&û£?.o:L×äOz”¿Dýz}ǸzŸÒÒ‘®ÞÜXr®NSse31\át0ÔÇ©§;W§¼›Ëél|Þ%·kfc}ÐÙPO÷û–ÕTW@ûW\ W\ p5\ p5¸àj¸àjíéêhd-pnn,±¡<”§W·ÞÕd൵Åx<öquÙV{¤<”“òã àêVº:t9–§·¶¶JT´ÖByJ¥å§­ppuË\í\‡ƒ¥b1Å·®C!?m…“€«[ãêpÈ=>Ö»½½Íf9]M9iN[ѶòãÒüŸD]¯òÿù#'†kbr/œ%[òd¶ ãÀÕÖº:pö÷]Éçóét:É\¦¦¦><;;›Édr¹m¥µ¶ÖŸ–» ®t°«Ýn÷=÷ÜóÇüÇßÿþ÷ ¸ZþWæœÿrÛð§ì 5ÿº‘½ û)¢â??j–Ìs¼fjÅØWLýïìÜ|iž;]‡@·¹:¡²„B¡¿û»¿û‹¿ø Òõüü<å4ãjÆ3†£üê-„¿@c‰VÕd­ØoŽjml¬ùÕ^Õ{–èrW ‚pøðáÏ~ö³ŸûÜçúúú(Ť«5-¡wC]‘ þ½ˆ™[ëjÃï V¹š¿ò¾V°êHèNW///ÿä'?YZZª»ú—¿üåç+ËŋŔf¸Z탭®­YˆÚ&ì5?­³Ë±ÖÕfj¥ùª.óW†Ñû¦€èrW§R©x<þ«_ýê _øÂƒ>¸¸¸HOß}÷Ý/T–×^{-^[(g3ÆÕ&ǙƆ‘Æ‚*fê`Õ¸ÚÌW“Œ:› ­»ŒíºéêÍù¾r¹TBðEÂn)~ÿJo梁«WVVD;àb™Œ@ m«ý› ã#×Òi!Ûˆ„\Rü>Gï­FWÓ2==ý«_ýjvvV-¢«i+Ú¶¡4Ð1”ƒ hB.!º‘JÅH¡$Ò¸zbìz*GÖåãêÁþ+ËK Ùl&Æ·ärÙÅÅyÚJ>®Þ§´à}`ãqut=•ŒB[ãꩉ›´;Úi8¸*%àsLMô¾sáH$œI§ZK&“‡CΟ¡­hÛ†Ò Ã m&aRhk\=;u+™ O$äl`Í5Ósýƒ3§OLNŽ ZËÄÄØéÓ'(?m%/ :ŒXÄCò$…¶ÀÕïâÜt_2,»:¸Ú@ÈïX]¿uãƒ7Ïžxõ•_°¡<”“òÓVò¢ Ãˆ…×ñ)”DÚ‚qõüì@"î…ÝáàŠœPÀáqÏÌÏŽ\gCy('åW,:ŒXÄMò$…¶&²´0Ê®ŽWÔ4ZfCy%@‡-ÿÆK mI dÁ±8B»‹†œx—~"!§ó’BI¤-pµÓ1*T~¬à¤ìêè)´5®v­Œ Ñõhp5pà$Zž ²N m«Ý«±ÈZ¤<]Ѐ›’')´®ú×ÝÓåßKWtm]N% â"…’H[àêµ™ò‹•P`'b„ÚWû6f+W/à¥þxÛ.BĹ2E5ï¤ÔuPp5Ý%êÍùÅùÑ­­­õvû,TÛR©´8?Bõç=Àb¡ÝªXà<(¸€ncya8ò‹…”Ý–R± øÊõï $]ó\ @Wáß\¼²½½Ífmçjª3 –©þts€<WÐ…‘ê[=ïçóùt:ä^öÕ–¤éÅL!™L&—ËQý^ Ð’ãµä àjàj]v5¯¯6tµÚÂÕ»¸Zî«zЏ"}ª84•&²Ÿî‰«5ÿèšwPp5ÝìêÇBÎa¼TUš3QñUöBuÖåjóhøè¬=(¸¸Ú¼ÊxÕÙ®æo¸°·®–.Šér×)nbWKA³àj€1W§R©8ÇBÂQK‘¾$Ϧ–AqsÎ…êÌïj ÿH›tPp5pµ.›éZ—®`k\­ë@Œ%ÂÕîN&“÷RÿPßÈ“GL©§Kó(nÂX¨Îü®¶öGÇ“hò àjàj-Ísu›\ \ WÃÕ€6tuÿ­—–²ÙLÌÄ"íkÕ’ËfËõg»šÿ÷©/­;¨\Vó àjº ÿæÂäxÏ;çÏD"át:•°Ï’I§ÂáÐùs¿£ú³ïÝd£Ìd¸ ®  q­N\¿úîéSÇ&'Æm™;}êUª9Õ¿cÿ àjº€wÁ±8Òsý½7Î{ååŸÛª-Õ™jNõï˜ä?(¸€.$è[\sNÎM÷]µ T[ª3ç¿Úåu\ @×;ൠ„Öþhøqáj°p5ÀÕàj€«ÀÕàj€«ÀÕàj€«ÀÕWhÖœ“p5´¹¨/¼ýZ2ûøãÑжx\S$êl6…¦€¶WÓc&“ 7C54´›¢ÅˆÇ³äóyB!Ÿkuγ¶ˆÆ€ö‰Q¯»§h}yiÒíœs¸Ý«ó¡ßå\˜œ¸56ÒƒV€vˆQ“IË«« K®µ·?ºöÿsØS endstream endobj 918 0 obj << /Length 1480 /Filter /FlateDecode >> stream xÚWÍ’Û6 ¾û)49i;kšú—ö–f›N:&m¶§$3¡%Úâ¬L)"µöæéK”,y=iNE½½G½ßW¿>¬6oãÀ+H‘†©÷°óbJ ¥©—å!I’Ø{¨¼Oþ›šuš÷7ë0¡~HþÃY%äÓìæËÃ+êS£¶í‘J¾g׬>Ó$ hò^×¼? ÅqiVhÜ¡í¾gÔ Š« ÅNbN±()’£íX¯–9*- ‚KÝ<£hË§Ô¨Ž—â3¥!¯Èä8!…Ëí»ÝÍ:ŽcŸ‰l"×(“‹—­ÔLHåä­D1kºšÉáÀ{Q¢ª¬YÏJS,H*˜­.©ºš ¥Û«‡ý6´šWî”)fÈ0ü„yäJ‰V.¶¬|\kQ>ºÌÌ£±{ÉÞ&Á ŽÎàFIÓÜ„a?ÿµár¯kÜbêJËT÷ë/!î]@+MÉy§sf^šÄ9‚¯ÈIdøu”“ ÍÐôO!±HY>¦]áJ·HùMøæ´xRuåã·”yŠ>_†B‹Q ë-ŒÄm74ÀþŽ3=`Qiqd¨ý¬¡['gÛ†/5‰0Lp™[1‰tY¨[*g$v× ÿÃ"›jÊ=Wp8‡pirc …võoÄ#_"†·?Ç*Us®¯Ö‘. ø±k„Ö‹þ…îÁj'™_Bš€a ä$Nr›x«ªÛ®üÃB83]CÀìÀuÝV·(ÞA_†ŸØ¡kœ‡AÙ¸pÇm)}y .jqúл~¹r¨(&4 GЛH¢°ðµ(k`M„ìŠR†dª¨·†­Pµj£]èÚ‰Z”„t”ÍŽ“¨sÛ ÿ´ü @kí¢\ n³Ý”ÐÜïX€gãHháxÖ7_Cp°TϦûn¡¬^ìÈýï{ûÚžÍÍPü5 ðdâüJ”øÕÂôÕAêkÙ¿PA&£Ôßá QâNnÄâ¦Ý!ušdLA1"8‹<`¶ÎP= Ät¬ëbÔµ8ÕPÖÓTz )S‚4qñ|º» ¯"ÊL¤4™!Ê^`D”½Ÿ&Dw­ùïݘkaÐ:iÃsù¢ÿf”¤4YÞÞ{3QN×WÚf{Ä…wJ39öc;ú Ý ²Ô?׉ÿUXe~…Ó;s ÚÖY]ÏK”¹]xÁ¦0«uæ¿z3(ؘŒ…uøêZÙ*Ášvï>Ñžd5=ùå•ÐÈ™ìÙ·ó`³s°ØÉ¢(ŸÂ‡®íõõÁаÌ`–¬¹úÚyÖõØöm54\Ù–¼úíaõmÀ< ˜ºÔ ò‚ÄYd*G$NC/Ì"’e…WV›w‡Ä»oWãKsQ°É<ÉHåî¥9f± Ë,¢hºòQ,Jbt¶“v é°!¬«¶,0íÓ¼EÀb;hd0 áH{B¯àcžfÓ²ÊZÐØ@Á6JšØ/DÕp‚‹×H*¾cC£Ñ®iËCNû‘ÇÐÌž­`·ŸEŠH1®Uì¸{‹Bûòìk­=ýÌ ½Ÿ&¼¹ƒ]3ìÅo ÛõàcF«*{:XáPi鯽›ßçÑ¡PïF°ãÖ‹nxëÜ£ò‰õ‚ÃÐxÆ54cPŸ_'F8‹ý²¢ü$”}¤¤ÁÔö ƒ¹’÷ð‡Eè·]Ì• ”6DCgg€ÍÃä¦Þ{કO\ J‚î>šÞ‡ÊÐÖóJÙ ^TøïçrÛPcß" b¸nÌ™³éˆÜ¼ÁHX¸¼´ymÛCAda"nî}Ó~*áÕ€%?"ÈfÎÍê#wï¿Zëîn³Ù²þÀöDr½yâƒú¾>ŠG±qˆûàêe7àáàÁÉM!×Îÿ›‹ßØÈ4—"O ¤€ÐÈA´ØczÙÉA  endstream endobj 915 0 obj << /Type /XObject /Subtype /Image /Width 471 /Height 450 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 16857 /Filter /FlateDecode >> stream xÚí °$Ga¦0vp„$#-­#ìXa{mƒ9lvÁáÕ†7ì]o€mlã¯mŒ,Ð…n FBBIFh½Bƒ®Ñ9£ÑŒ4ïÍ»çͼkÞ}ßÝýú>«»ß{#mVUw¿ê:²2ëêªê?ã‹õª«²2³2¿ÊÊΪÎåÒÙ|Fʯ¿~á7Þ /\Øo ¯;ÉV^ …7Xxƒ—@ÎÌ2 ‡! Õ›½Á:g‰¶‰®aZA(÷æ$ïíVÉG»ÕREÈUÊY3r¶šäUTit¨P(6©Ñ©Ê”x(直é"°°·[a£ªÃÞûuj ìîïÓ¸PgÏ J¯ªŒW=êuÁ’ ­ØI¯ÉS[–¢@(å&,½ðųÃtÅÓ­¬ú5„­v1ÖUýzÞÚj"\­Ij€ÔFZmÁ¸ÕëZÂØ*v²i9S‹ 9â[RsÈ9ÊäÒ¤L–«Lúm£„ó~’°ÆÃ5ën‡„M<|¡Í¾`·£n³sè¦/8cà¶H¸l*a~»-á¼O%,A¬Kê I­ûÀ¬¶ça)ï…|2]ÍåvH¹U¬*vGÂŽt†kë ·ËÃûüAÞ•ÜóÂÀª*].eñb>½[$x ,n\*fvbë…\R\S)±I¸µW‹ÅB:]Ëçõ 1°`Õl¹dl{5—W둘»”¸W¨ptƒ-J˜¤0ŸÝ™êé:z쥟<ýä^záp×k/Žô§’ÛU…Š’°OF$Üï ëx¸Ö&»ªb?;2&iI¿ž¸Æf`%,Õä]ïüÄ™žne~i|t “Š‘ÍX‰§˜OWEòâøÈ@:Ý­–XúÀ¤µr Q&êHD™ÔTìE7˜f¶Ä¦ùƒ“LJ6¸ôø™ŸyÿÉ]äÂGþ4žŸ¾è¢?¼ä’ÿ53µ@Kg¥°±¶püØ3ÜÿõC·]íÕÿtõ—þñš/áÖ›¯¹ïÞ;^|þðÒÂ)ä`w†Û82욇™Æ‡yT|Áô[Ôö|Äû• »~n›É–›K´&¼8mêa²Áæ:=’)±G­oàbÃÀ¥õEºLH¿šßÀV$Ì`¶Éj%O÷°<ÂèÓ}èßñŽ?øÑ#/–JY¡”-ëëË¥ IYó?þྋ/¾òÐíÏRÓYœžùñÿ{ðƯ\uÿ}ÿÒÝõÊüüT*_Y™ïï=õо}ýµ_üÑ#ߨ…@u†ý4(áÚ¸„K*¦Î‡qz Û7ìl_m;­_WF!œ7ðÌôh£ ½»ûÄÂüL:µ³º²Ðß×õðƒÍ&<(Ž*XÉ¿H‘L§R;+r$ ÔÃn0‰gfª%’y‰(“…þÞ–H<øJÎÀl±•eÙ¨£ÄrÕ >ý³?û>qìw#37;Eþ$FG†Mxžì¢kàxô ’B>­eôœ"’jI×À±èº:’V™Œ*d"K¸Õ 60[JÉÙ–çÄãê9V0“ð-7Ÿ¸ì²ûÿñŽ u“ç¥Ò%ΦÓüàK—_~ä¹#KÆIÍ—Š¹®SÇ®»æ‹gÏôä2 #¦®¿öÊÇŸËfvèƒE°0(AörÜÃrb(Ž´£:ßü”T{² ·y¥W]bIÅ{ì*6³±=!Û™Ln{2'¯x÷”ÅÂà^>ýzÞ>°P.Ö›ðpo>›$yúÉ÷¿ÿ}¤Z~øÃêëíÎe“„¥…i¹ “em)— ÍHrÙ„ÍH²Ù„VÂåR^‰¾L¦›2q­¬6ÛøèÙ\fGÉøè0ù«0[ÜTºþá¿M_~ù ôG½…¼èÞt6׎ÈÞ{ÏÒ/ýÒÈŸ|f®T ],67–¿yÏ?|è;™TœÎóÏþäÞ{¾6?{žÒn¸Wì[𰼋ãýaµ„[UÜP+G—˜*^T¼·[ÛÝ­’P®¥²P(•sÅR&_Lå IËä›”3uÄ2…²"%‘¼‚¢HN I@9+ý5"/"äKj JÊ2™¢¡N©NU¢ùo¥\Q ÈT ‚LE‡J#ˆÿV[¨`jµŠ‚ª„¸\m®Ü­´n#n¶[GúTY£@Ú†¬cà00©`[›+b~ø;ÙôŽÌ÷¾ûí¿ø‹Ï>õäáO|âãûÝßɤwdžö ¹ «:Ã$¶­z$™tœN3’Z½3,·_±\—I3’†:^|ሑL´a§†‚•f& öÖ[n̤b2×_wÍ›Þô¦™©q²|`6MgX`ð«'£¿üË“ýè|.—“‡#äõË‹™÷½/ùž÷¤g¦s”ɤܦ&G¯»æŸf¦G“‰m:±èÚ‡n83Ô[ŸÑ‘°ÂÀ-ƒ6®4–+Ë5E念F¯Xw ‘ðÁú†cÜ«XÝÛÚãÕÿWÙéUö#š ê «>R~ÚX¨¸eK½Šz'öÂ^ýÿõ Ò£îö»/ Ÿü„j}=1ÙDEáXŠ„%2„ŠDMœ?]ÊÈŽ™Õí|¹”ÙÈ®Í%çù-²Fçª=Í‘%§uäÜà×î¸q}e.º¹¬deqZµ†ð½ïÜ3|¦_z†®å¹fª„óºÖ GØ‘p‘KÂ-C ëI¸Ì8FÁ%aÍÀ…ö¶´ZÛ­Tª•rE(–…|©Ä?&|@F‰Þ˜°4\Ü  ’U)²ûcÂe‰úàpQ‹P§T§¢AÐ ËTtD‰Š P­´Ž ‹ƒÄ•Uiø·uä¸þoM¦&h8øU lS»×W2¿¢D.õ&¼:ÝZm291ré¥ï<Ýu"ºµ¢DnÂÕæÃÈÒÃû»ÕF$sÍ-o¼á:R9W²Ö¡ÛoщDl§y Òxó¤VD²¹24pZÜñ¶[ȲÌ_¹Žt§ÇÎ ÊÿÉćã Ìvõ—ÿ™$é“¿÷ ò÷Ư\«c6!/«UЕpIG²‡Ç££ÝkÝK;‹d¹TÊÌÆÎ?1ýÄË‹Gs9ñë¹L&+¹Ú@ÂÕòÊÊü‡nš™:·µ¾@guiú_¿wßìÌdM~¦Ü8$¬øªNéaS!ëšY!dÆžpË6Þ1 º„YÆ(šP<,÷f¥;ýúk^çfþur¤×‹'8lMŸ¸ÀÑÙ6èBkÑþÕ¥Ñnô›´|CÇö:ˆz7¸DªÊÊÊi³S#[K  çÇÏÈ MV—gä&¼·+|Í])’ÄȘ%PlÿÿyR9‰»Z#©{@nHXÈ“Ô*d²Hî—š4×4#1‰ÍDè˜íË_ú"ÉËí·ÞH–o¸þj²Lþ6Ì6%'¦Z-êH¸Ô”°Ø§mºWÉËó/Þ3pω…“dƒh2~¨ûöë^½n%1[it•h^OQ)¤Ó‰GyðÅžX_¥Óßwâé'ßÚZ¯‡¨¼T5h86WU,7嬘ºÖ0³t›Ëz>X®)Ëê^qkßXgY×̤S±«Xnµ¤Û=æÁ užÐ\¦›ÙdŠÅžÑ….5‹ì±ë-ɰœ/SCê6T8táxåŽ>¥:Õ:òĆt:YoÂksMfgFßýî_™ŸW®”›ðöÖzëÓÇòïÛŽ=¢ZÓŒ¤V;ÀbGKú[SF²:Gç@&=a |/j ™­¯ç¤4;↦ͮ¿îˤ[>ïVhP_&Y®GB< ØKE3Aù¼[¥¾°«Œdyƈ¹Ù¦L\1°ÆlýÄ`Cƒ]*§Ékšf+—ój»– $\R|*mydê‰ÿôÿôG‡ÿçðZïe÷\ö+üJ<»&}Í—)Äç—sù,¥K\¯D¥žÓ¯=pÿ]ã£ýË‹çµLO ?úÈw‡ûòùlËT:£l¸WãGí*Zôç× ÑN'6ÅàNÍN_Û)j M:Ö ×Ñ›e\ÿ·&Óæ•V–%‰Ÿ–õZv7êúªô+×a²e³ OŒõ//Mj™™:ÛhÂ9©¬i;ÊHFõ#™>ˆ$«û°›4¶ ˆdqRK‹Lt»Á¶õ+‘i5[Ÿ©ÙêÝ`•c$Ü*äÓK'#_\þ­Ë?øðo‘…ž}0•ÉÖõ›««˜>4A´PÈgûz»îúÚ­Ç^zrêüÐâÜØÒü8azòÌ©W_xàþ¯f2I7j{¸}*.8¥b·ml,äŠÜeÇ'¥GؘDŽM¦ë¾õ×»a^Û¿Å`s.Üþ~ꈥ>¶ñìƒrN2ݽ­oú­I¾Ph6᧦ϟYœŸXZ™™V4á”4ÜÒX”sÌ”‘L‰‘ŒË‘L·D’Gƒu¯Ø´Iü™“LÔ2µ¯°®cú=Ð9£Ùjòh°fM$\Êl$ˆ{eˆ‡I'(™“Ñì‹F>Pñžømu{k㉟<öÍ{î¸çîC~ÿ¾o}óξ}÷sÏ>¹½½I:b¡¼êÍ’Šóœ*¶Ñ1®úÊÆ¼Bné$Û JÇàÓh'¦é·6[ŒwœÁx¨A°T+JÌîUK˜@Š…Þ„«‚øxʽµÖ¦´g ñq…‘®0ÉÄIëêK˜°'ÞhT艩ÕJÁ*$œ6Ú \Jÿâýÿñ’»/¾ä—¼:וÊäšd2ÙlN|}G©(ÏUÓJø W,žÇý]r÷T*’ÉÍÍõT*Q,È­›8s¦R0}ë¦'½boÆ(<³q™ú² ·µlðTõž]°(Ÿf’­‰r©Ö¬V[îUBô(þZµ¢i‚ôXt¹¦±në¿bû"›E">¦]-é¹7¯jÔd3™ˆsöHR]Ó¯ZÂ⸄h¶š‘ÙHâé&î•ïF% +=œ6PQá@VqIj&»{»â½0©ÛÚGùü¬âvÚØ!Ûq²ƒZæ| †5‡3Ææz¦Ï­k(^kîm}¹J¨¸>…4á {û{5Y¿Ô—·¨Û—ü,^=’})}ýªQ6vÒÃe²/ËdOêf—¼Ô¯JÅR÷@|1‹”¤ ɸ‘u[—Unª8MéÛW±]¼S±½c‡lìšËfïÕd¤ËÊ⸹(Þ­N6ÄKÁ̽›üޤ¹{]ší`Y¿½™½”~o«c$\.¦ÊÅd©)&S;›ÉäÎz‚ >ˆ-=-=Q¸[%Äcâ³x±èJ|›°#l-EE··–¶·Ó‰­Í…­ Âüæ:anƒ°6»¹FþÎm¬Î®¯ÉêfÖVd¦W—§+K“SË‹“â¾âîs›Ò¾­»H Óš¥ï%—Î/‰ßNN,-ŒçÇ$FæDæg #ó3#s3çæ¦ÏÎNŸ›973}vfzxzjx†0yfzrhúüÐÔùÁ©‰¡©‰ÁÉóƒ“ç ãý„±¾q™QBïøHÏáÜiÂè¹îѳÝ#g»çÄSgϼFz•pfèä™ÁW‡O N ö^‘8>Ð÷²Ä±þÞ—û{õõŽöž&¼€HE’ UK¬]rM#Un°ïx½ú œ!•S¬Ÿ¤–’º: ùthPÞà¤\™‡Ï¼*ÖíáS¤’Ÿ;#Öö‘a±ÚˆM {äÜiÒjFGN‹­c´— 6–1Ò‚&ÆúÏÆû'Ç&É߉I©¡‰nrˆ0-6CÂ0iÂ3RÜ[«i¹¤ýÎFççF$FæÇæH3¯·w©íOH8O 2Ù\ØYŒ¶K¢¸dƒ›D¿­ˆ–‹¯&vÖñõd|˜ÚÙH%6Ò‰ÍmItò¾dǸˆ´otUÚ]ÜWRåšlN¢Ð„¤S"U¢VO© ÊV²®ˆÎp„èvñ…™­Ï˜dï.3xYz³GW)ôè”oz¤t ä§{h=·=Ýž˜É"íeæúï7`x)®úÅã<¿\©÷cî.õŽÆWxæ´4f¡hž+Ô¹=-ü2’Ü£ígptt×p°—w¨A¯Î;8ÂàÕ0‚þ`mÁIšÖõDÂL*®ZÅm›\lÇH2ãOH[Æ/jå—­ý¡]—ÅËà^>ý Öo¥Mú¥KØe2-”¸ÉZ¤¬%ׄýÇMÌÑù]<ó_Ê+2Òúåu©ž_ÊcÁ‘ß•ú¯¾‘©±Â|šTç×ð§áé´Ö4†ÊÉRÉ[[;ZÐ4LËmœS&J©å‰½•°oT\¢©ØŠ…öÛØDÈî0å6sÕnç90šm•-oË|g¡â”x]s¯`Ó½9çÜ<ý¶OÂ~V±µŽ±;6vRÈö\ªñ©†WÑžI›' 5kU¹fÖõ@¼î¹×Í®oPõ«ð^s²Tl¯cÌmc«Ýãgcdp²-[—3ÍÕ¶Ôm®S5g°TtÇ­[Ô©*Š×÷B¿M”SÔÚ(a?©86¶ÝC6p²³ZvXÑþÀvQ0(×’u-öxîÞ`÷~©n#­),`w†ô~…DõK|T j&í«á¼î/ë™còÔ’)-cûÀÀÂ5ýÀZ– æ Cû¡õ\ß–  Р?MÆ ó<%&l#~’°ƒ*N{£biª ¯mÙ'Ì:ÌŽøÙWƶ”`þ’©:¨\ëò‰W°"^3÷:«ßthôëW ŒŠXT±ïmÌ)äv8Ù!9;ãj÷±5‡eëŒu­‰7¨îhîMûMw~•°‰ŠÛÓ1vf¤¢}Bv^ˆƒÒµðÂô]˜·ÊõX¼ÞŽ98ØõMûStþ–°ãcžÚØ®ùlQËn™™õÛC_zÕMÁÚömÕð4kÖµ/Þ6»7(#A–°ãc7lÜ^!;×UöÔ̺=©µq:¸•ë™xýëÞt ÌfGÂ¥B2—‰I/½ô˜M5‰RÜl9IRfÛ!¢4R¦ÄtI;@c·xS†˜qz…q¬ZJ•ÜÙ†ÃÙl•M^GžK‰˜øÐc “ƒF·V …ìë …ñ!±¢gÎgã«+ó.ì‹¿ôT)ùœšpkzõù‚;…]`š ̧ÉíÇI8ž1ñÿKFzòÞêÊq£7Þ\_H¥’丂Îs1þ¥b…¼+îQ0¡ÂK‘N˜‘g±›žG÷ªKuž¿õK/ćâ}®/x áBngrâ,éÿ A+(6vMÈî:™AËåÌj逻‡ÕÒc<;®Ö²Ä[ ¦RUñg—÷ˆ‰!Ý–p.>Ó#þšs¥(¶Äü+d×Ì)g[~¶eìvã\®ÈÖ ëv–x[$L®ÝU¸‘wdö»½s²%?»"ê@a¡¬¼?¡eˆ×ÿN6èh ;!d÷Ü-ÛStP¼íTÖÚ{‚Ê>µn%¼º°$áºu!a—…쉓}af&]GZ‹ÐX6n~ª]pÿ•°gµËFèK8'as.æÏÔÏ„ì9>“†äFÃM·ç?Tªâq, Ÿˆ<0‡Q!á²ü¬•¤r¢Ó«þW¹²ù‘*ÝíµÑE¢»%L ñBÂpr˜üL‘°V’F ôO£eܦ…u½‘p©”„r¨œìœ¨‘0×fôíVB°îvx%Ü´î…}HØ#'‡_Ë Ð%Üú×VGtüÉ`ÝvH˜…l:6<$IX(”KYÐ gHYЩ¿­Ñ~¤û©r3ʧŒÛ›·ÑVWZÝFÃoP$ õCr†–Û(a#Z–ª ›šÊE£†„CëäŽÑ²jÌÁèS;=ayYµ™Q´¦[v¶ra]H¸³µ¬gæNʶdkî[(ÖÍ 9C¶ð-$ Úifc9ÃÏ¡5-“lá[HøÚÏP´ß5 ÓBÂp¸ýÌ`i¸Ú-»r8¦ ½„ a(Ú®®C©nÞŒ[,^TË’pݺ0pÅÕ¶ííœÊ>*$Ì*as²éèðÐiIÂùr)€†Ð·8œb ¦!áÓÄ\F…„HH aH a€„!a„#‘e¥î§ô}¶dߨ4*§N„ýT1”¯°T掫úù!›6ì`#jï©÷Cuò„åÒЖ‰³•ÖÙ2÷•Ìä^H8Ð nW á¶KXùQó‡ tÿµ¶™îya?¨6òfŽTÛP©{hm<ÚØ(‘˜¦Š^8¦] ˇ°\€¦'Ô4BÓz¢» åT²—*ýì°œ}›‡Vþ@ %ºÕI·¦Ùo,g½¶X>0»„)¦2ºÆ±lÆ?û.FËŒʾ\ ì±Ñ«¥ã‡p$ã\gŸeKv8že µˆëÐÉž6Gš€îÈ‘æÆ(„Ž•0¥BÒ ÓB3tjGkÞ¶&aÏ®&vîL-ÂN©²G`9 ì§Òæ…›·ìŸekUב´¹'aÆc…JÂû{ÅüŽŒ} kï8؇#<0ý A—0ãp„ ³ß›s¥ßôvž¥Å™\¸gBÆáW}ËXònKX×öÓÌ^Ç‚"á¦u—0{O˜ñSÇ%ì î|(a®ákMϽN»ÛúH¬«&tOt¦Çûž°é¥+ÍáŽ0’0 ™Ôö™Án­„y[P€Æ„=°…1a ÚtéŒ#œŽ [h¼Œ ܽ1aÇíà˜p€†#G§Û%aâFbH.£¶QÂF÷¦ßz³Oz4º3¢”ñNŠ%¿ì÷Yºÿr8XVÖ¡m¼ß¿ð2°liA®ΎpéÐfGP.jvšã›Í^Ç:Y 4“x‘}¤@­»£ò CÂ0@ C $Ì%aÊs¾}Ø÷áYËñkC ¾Žio ¹ŽîjRyO¶ÿFÓÎy'j_ßêg ³ÔÛ”0oAÂA—pÛË3C á²Áˬtß¡gùµŠôIà,3ùŸ,`‰‡¥.{j¹íFÙ²?¡`¹è(/`¤,»WÊloCÕ½Ç1}…©éK&¹ë0ÄÕw¢š¾³L}‘©åB+ó¼y•ýÁ“°J¸ìÜëûèOl•-=ÔéàsÍŒvï9e7ÊÖ“ƒ–Wò>þæRuâM3˱ì,4ŸK°éŠ%Ð…æÿž0„H a€„!a–„ ¹¸Œƒ¦ž;5´îê=eNãG÷²†ï:´„›Ö݇„y"gœW  [“0 éäÖЀ(áJ9_*¦UȽDyAµ²¹†þ/ûJ–x(6Ó©{UT1(÷Uå^¼ñ;[€/Ê¢„‰‰!¹Œê¬„UZP­Ô ÊôSö•ºŸê®¡'R÷S–ãÒc°¿#t¦„)Ý<ûŸšºˆŽÕ[–°åøí PŽ5pEªvÓOFã¡GQ ào ×­ë„mÞM[èû±GBÂŽ “$lN:¹imL˜}ŒÔ½1a®±VW%laLØÂ 3Às orÕY ӿܧøÄt>}Œ‚}8‚ÅÛ”Ù õ9¿Sбì@•@Âö$œ+S€‹Ö/ÑP „¡œƒ„HH aH a€„!a€„†„ÀÞ-ä¢ að@ÂuëBÂà' ›“Nn œ’$œ-“>E ÉeTH aèX òÙ¨ $ H¸i]Hü#aR‰AYÂ¥l© K˜¸‘’˨0@ ۗp¤5°¤œeãæ§Ú…ÐÕSÞ‚rª<ýv:œJù²'û.º[²7FËÕ,§Æfüþ‘0WFxsÚ9îµ™eHö !–p {”5ªN¯ò_ÕiU]»•+UÛëö¥u#1íuSâ±¶»n–uk²ÑŽôëšQRµ¥›l–ÍX²lz:ŒN¨n‹ÖÖÆsdzšxë Ë%ÌÂÉ¥oOo&j‹öó–¤ƒÕÆ4/”êÊ^åØÛû…†ånÂÿ¦×CÝóεÀ ËÅ‚×îF>§¬1J{cÉ‚ƒ%Æ"" gÍ©smy_z»sääZn&ì æ:MWöDråÅ´°ôä“V smf¡&[»Ûb—0ã…•7ýFt åc¡Ä,_wlîëÆ¹¶“kÆÎ<ïp½]ìœb›µÝñô8[åèm„]ÂŒÍܶ„·¸(aíwv^J˜}<Á(úwŽôኄMÛF S²Ü 3–ý9%aÓ“K?¨i-5êëZ¨-K˜¥Ú@ÂMëz)aû×zGúöµ`§#M©\MÀãž0WWЃž0ï–6»Öìóœ[`Ÿëâçž°³·!#asR‰õÁ×$ gJ…„)‘úÿê.S7£ooºRw3Ó](»%’%*Óí¬i®Ý+1;gÍþ¹¶“ZÝÂg¯ôƒ²ÝN‚m¯«Õ†ýìØl¤ìYãÍŽ¡œ•$ü1$—Q—pëˆþ§¼Òоj3vËQZ–6rí5ÅtwÝ]è§’}G ­Þ´EÐ7ã-1ÓìèV®VÃr®)ÕQ;”Â7Í)×Aus¤ÝEµÌ^[Ø+3—„-Wz^šÑ:Rå,tZB a° Ýr>?.$ €„!aèL a€„†„vu0œqž˜ýñvú$:›¤ÏDeœ»ÄoCì”j'|OD™jÅUgèÚ½¯iÎ6H¸6éF#r\ÂÒb ãËz7c³‰yYÖ¯ß$L™€má Ê£ ”Éçô‰ñ\>(gk×XžONÿ—>¥œþ, ïóŒ)ÑnÃûÄŠö”SÆþØ Ë‰`©fôšfó±®x(ûò>cú ‚éCUJ@w] X¨„ÖJ˜·ŠZÂ\Ïl²?ÀÈÛæzz—EÂn/2 Ûi§ŒWËÖvzíGe9ƒö%ÌûxfÉö+èØ›—X rêlÚ)Ë—WÇ_Ý`³J8"Wb¡"y&á\fK¦]¦¿I dðÎ. Ó_já¶„M_•Àò"û.±½ÁÚ„"§äÎ~dyÇo9[8›,ÃÖª=o­öF¦­‰½J°7 ¦¼¯Ããሦu}"a®ù6{Âôñ ·{ÂvÔ爄ï„XøfÇñFGn ï8Ù¹²\«=î Ûéö[¨0–ï5Ø',µ]Â,$wÖú_%J™b!¡„dA»l´ÒtUäº+éûÒa…¨,dÐÎ.¼é´5Ó’±vö­¥Á´ˆ)gåËÀôˆ–KÆBY±ŸV;UÂB¡9U íxÀTŒ”% 7CrÕY {£|5?Umc´—†O?¯„u“mTì4Ý…½}éµö¸ºkÓÉ’w£dèæW•qkEdtNMëKíbÉK`ŒÇ´!˜–€nYY)ýtPú3,`œww–FÁ~B'a\ÅfKAš‘Y—€„A¸Ûµ#}x ™…„†„H€PJ˜>ãÃøHØ ã»T$l&áÍÎ<¬Á>·ÜÎÌs¬„ëÖÝ߯9(aGžÓ,2?ƒ ¡“°9ÉÕþ“ ï(‘<©^V®¤|jº¯n<àÊåŒ$á“Ä\FõX­>AÂHØ; Ûù@Âì¦÷u-ô„•»$¬+a¥-)æƒì@$œÞ¬c[Âî÷Â#á†u÷÷|-aíyf 3Œ¯ô5$œßP.Iî;I ÉeTYÂÙô¦ $ H¸i݆„7d aðDÂuëª$œUøÙˆDCÂä …ü€¦„‰!YDj,asñH $¼ÂeTH a€„‘ðÂâôkÝ]=ƒCƒø‹¿ø‹¿aú{ª»kaaÊ·^\š“Úß7|vÂñ±ÜÒÒ´?%ÜÝ××74”F@@@o –ëîëñ§„{‡†¦g&qŽBˆåˆ‡}+áù…Yœ#„b¹þ¡30$ # @¶%\˦×8)áˆ^À4 ÚÂAqQÊŠ·R±lÜüT»€€à´„ëÖuU %D£Q]K ÜËŠ« xK%@©Š.KØœD|y ï„$át!Wb*aeÝ–—›µÍ*Ä=çíííîînÝRÒ‘Qh‹.”ÅH)+£5ª\+ÿUUEJ‘êžÓS€âÐ¬Š ·ØøP’ð bH.£ŠÞ«eSëuÚ!aú=`X«w*•*•JŒ6Zi´²b4*+Æ:f´@ÿÔÂ)€¦BäªèŒ„ÖõXÂZÕ°;§sn±éíÚ´”ÂWŒ¦ÃÖ$̵YÇVTãáw$Ì@"¶<Ð뺄Ãý]žé-6å^˜KÂ!(FÞáÝ\C Gôž †ä2ªo%Ü!÷5nÜ/„¯-G0Þe@ÂŽ GAº< Gè–ãH&†#Ø‹Fpw8ª„3©u™¶H8Ùf½;e±K8êÙ”‘£i \fŸ c8‚„›ÖõF¦£{_ƒŠ²BèÌá# ³°[î·!a<€€€è@‘0q#1$—Q^kຄ á¦u!a?IØœØRï+’„S…|L‰$áœ#„PKxF’p‹ý~…’˨nHø „ðÿK¸ !¼Áÿ®9+0@Âí’p$ñ`—àÒ9™m>¿Æ[Úõºk´ñ›@Â0$Ü×ÝókdKízÝ-ékP£ á`IبÚkkµé–ôuã PïÅ(ñFÐ-´ôƒ‘0Y$¬»¥ÑŽ0¢„µѭ̺¶1ÚÒN<þ¿¿6ʈéµ¶,g£K¿‘x!aHØy '×ê¸&aze6]ÉØKa'@·ØF6tf¿l9+aúÕ†„pú ¯Öñ“„)F=H=aSµ²ŒSêzÂÀ! ×­«–prÍ” S…\L‰ã¶0 C¦ƒ ,ƒÀ¼·c ã$Üj¿rQ!a‘KØœØbïq^ Û”'%žÎé uÌ:PÂ,#´–§¨Yë'HX’ðqbH.£º-a®ÙŒÃZý²[2 wòìz-rjž0×Ìd ûSÂÀã  ~B„FÂéäª $ H¸iÝ=H|#aâ±Å¾†„ó¹˜"á¹…™'@È8òêÄÈÔJ¹œ%–ë:£²_I’0q#1$—Q!áö²•Þ‚õD­{d•x8p¾ôÒw옪¹»Íx|ÈìVÅ HAQþÕ]Ói•€ã%#GˆóÛ•åXåÈÉñ`IØm†Ï½M&׊n@JLwÙh›NC7ïro¡‰7Áb%.f뎄WpH¸THæ²Q%½ƒCsó3ª>°¬P¥HU=[mWY¹†‰jG£ØüÌØbV‰œxy¡¹FõosÕ^FÛ¨bÐ]DL˾R·LŒ JU¶”-µ Óݔŭ¢(áyQÂ*û )N ×­»·WuOÂF# *Ámɲ`›Ï9;—jBÒÜ\0ZÖݘ²Rû©îöƒ%§ì+U1žÝxÊÜFÞY o®Ok$lN<¶ ð¶IÂÓ\føe—pàF0§âMHšµËº+UË”½´»˜F´)7-=Ê–Få`ZøªÝ¹Î "Ó«IÂÓ’„[ìW,$^`t)1ðóÏ>^(d!á6Ò3¶Õ„¤Y»¬Z©Dw=ewÓ„6庥§, …@‰S¹`t²è‘€ 2±˜tP„­ô„ÛË©³kMHšµËº+Ù÷Ò~ja Цœ½ èe§**úÉ G™Ñ¹¸³öùp„…1áÀIøÄÐr’ææ‚rY»jcÊ^Ú]Œ" ,9e_iT¦…¯]fL(gg¢n G$Vê¸&aå|Smj§=°ÏŽ–„õÎ+‘s$/4×h7Ð]©ÚKƒríš bTŒ+uËD{ ˪ÃJD†Îo:&aI¹[ëá×$ è¼Ð5­ i°Fñb•ƒ‡./=ÚN.ðÐÐ?¶æ¬„7צÕf ]èë‘$œOæ2ÛJ a O£7QÞ)(×ÛG¡ã‡Ž”€*Jœ(ððB×d÷ÙÅgNŒÕ%Üj¿º„{ŽC2º”Xg8ö–®á@ xmhþÅîóç&—’°þp$ z>01°PÎ:(aBCÂË 0^7 1I¸n]Hü$asâÑù¾ž—!a$¬‘ðËÄ\F…„€Ž•ðÞ^-•X–„À 7­ €$ÌB,:ß  a„‰‰!¹Œ $ 0$ 0tž„—@Âà„ëÖ­Kxg©$ H¸a]„͉Eçz{ŽINä2[J a@ÇH¸Å~ #†d©¡„ˆmÏõž†„°F§CrH aH a€„!a€„†„ÀM WS;‹ að@ÂuëBÂà' ›Ûží=}T’ðN.³©D’ðÎ Ôž’$Üb¿b!!Iø(1$—Qe 'we að@ÂMëBÂà ³Ð”p1¿“Íl*éš…„¡fV’°Ê~…„¹Œ $ 0$ í‘p|±$ H¸aݽ]H|#ab[³½Ý §7•ˆžƒ„¡–ðœ$áVûò’„»Crµ!á…0x áºu!að“„͉mÍ@ÂHØ@Â3\F…„HH aH a€„!a€„ ¼žo €®[wo· @{%œˆÏË$ˆmM÷v¿$I8žMo(„#áûò;’„_"†diÓº0øGÂ,D·¦{$ òñLzC $ è «ì'K˜¸‘’˨u Çæë@Âà„Ö…„ÀGf º  a oNsH aH aè< Ï5€„À ×­ €Ÿ$lNtsª§ûEH k$ü"1$—Q!a€„†„HH aH a€„!a$ $  aH aH C ã aH a€„!a€„†„HH aH a€„!a€„†„HH aH C CÂH¸M^W"Ixçj OJn±ñ!$ 0$ €„!a€„†„HH aH a€„!a€„†„HH aH a€„!a$ $  aH aH C $ $ 0$ 0@Â0@ C $ $ 0$ 0@Â0ø_ƒÃCã#8G€C,7xfП^]î;sæÜøèüâ „â·¾¡¡µõYJ˜°¶>svì,é«@ø876²±1§UŸ$ $ 0@Â0@ C $ ‘tjm'¾¼µ9tH.H^:*Ë!.¢Î<›ô\C¡„œôµµÙ\.óz=\î_’ ’’£ÎÉrˆ‹¨#ϦI®!áð‘ØYY˜ŸØßßß#')øäbooäˆä«C²â"ê̳IÏ5$>–—&’ÉrÞ‹a $/‰DŒä«s²â"ê̳IÉ5$2’‰Õ‘s½.\!4˜ä…ô‹H¾Hî:$Ë!.¢Î<›”\CÂá îï;Y­VK¥R!D"Ó5mår™ÜÓ‘|鎪ù6Ë^–¤³Edš~ÝóëøÙT…‚/%×0$¬¬·þ¬Æn4[²h ˉ7Ê‚¯$ìÿ^$ [6’ªw¡úHw£æ Š„K}^JØf–•òF«R_‹ÈH”¬ùSÂÚ3¥-[m9Û¯0$,×áM%WÄb‹½½¯X¨Ã” ËôúÌ[™å LòEr”,eÓþ!Ü."£Lѳ¦\Ù–³iDZÎÖÞ\ç²QA(7Cz á3ƒ¯Ã2Ûéä*pƒxl±OªÃ…B!ËH2Z—å » eí§ªø)»hÉ ©À$_$wAÉ2o´,ËÞ‘öˆÊ‚2ÊZ[’JO­n±3.Û¬\¹Î¦‰„óÄÞHøìÐIAÈåÓ›éä pƒxl«{Øë­^^b–=*¢Î<›”\g3›år–¸Ñ ǶfF†_+•²ùÌf*±Ü ]èí §„I¾HeÏÚx Ѝ3Ï&%×Ùôf±˜!n$†ô@£çN é\j]V÷zƒý'ççf¡œ K¨T„ÙÙi’/£¾Sø²ÜÆ"Šè…%Õñ»ëlz£XH7z#áñÑÓäpä É%àñèÂøhïsÏ>‘J%ËÌ3óýÊåR2™xöÈa’/’»NȲËETDR}•kâÃB>IÜè„'Ç{ ùD6µžJ,—X[9ß}êèáÇ Á}Üèè¹Ç”äˆä«C²â"ê̳IÏu&µN¬HÜè„ãÛ³S}…üŽ(á%à‰ØÂÒüHO×Ñ'òèþnÐ!¹ y!9"ùê,‡¸ˆ:ólÒsI®åsqâFbHzÂÓ“ù\,“\Mî,÷HÄÖWÏOOž>tH.H^HŽ:'Ë!.¢Î<›ô\gR«ÄŠÄÞ GÌÍ æ²¢„S;‹Àm’ñrå :$–åQgžMz®ÓâóÛÄž GÌ,̓åËè¬!•XÎf¶‰‰!=ðòÂÙ¬ô¤F2¾@”pz“¸Ñ ¯,ŽdÓé%Ò?'Hl7z#áÕ¥ÑLj-%ÎŽ[ }7·FÜè„w¢³«â“z;‹\;@X‘F$Vˆ‰!=ðæÚyñ[¹øb">@‘ nôFÂÑÍIi,‚ô„ç$Ä lKÄHXž*œçþÍãDÄþðï$aËU‡ ÅüÝ`Ëà0@ €„às KOmL­-’TÆÙ#ÁÊW¸³æ^Qo}•³_ñ,K˜teq<—M½þú…@ͦ–ÇI²C–¯pgÍ¥¢ÞÐ UÎ~ų&áèÖôìôÙýýý=²@IêÞÞÞìô0I|˜òQÀ#7«Ün­ö@òh¹âY“ðüÌ™D"¶»[+*ìíîîÄ£bâÕ¯pg¿(jô¢Þ`¹Ê¥£ÑÉǸýöþ›oýÁb³³Á¨x{»Ö*žµß˜ðÀ#‘Èðïbâ¶+…í©{®_¼ù/—¾ò¹s_üëÂââÔ]wüñ~Ë[žûô§•×ez-í ç)Óõò2Ë;$˜ÑT–ó¥›`úJY,>Ï{^è›Ñ·lR¥¿ð…/ÈÛ?úè£dÍÃ?,ÿ{å•WÚ) ངMë[&“ù÷_ýÕgßòæ±Ï|äÜ·½ñƵb¾ï†ÏœúÛÿ’Yœ!ÿ.}áÔ‡~û•‹.úþOÿtleÅf¥Õ¶8ö•nT<$¬ÌŽ©Uî ‡„Ùm柬©ûUÆòÅHH¿âOÿôOþç/ù…_xé¿ë]ÿ,ÿùŸVH8dÞZZzðMozåg#Ÿü™ÿ}qß7ñVò™ÄÂY˜=þÜó¼´ÿ—ÞqòÍ‘G#‘ñÇãêìqÕ@;ÍÓç¦4OU3×JØTà°QNý}1êå…q3S “P,þöoþêw>ú!™¿ûüß‹E÷Úh—„z{‰]_»(rþS‘…¿ŒœþläØ7Åþ0 “§^ùñ}ëù+"g-Òõs‘ÑÈÐ7¾áž„Ù{S>‘0i9†@² û¯r½j¥î6öI0‹©¬åË4S,+ý™5Ó¤jWZ+UnTÓ×\}ÕçþòÏ®»öËÙl†¬±YÀc ³T¹…Ç#‘S?ÿxd泑ç?sÉæäˆ,áR&ýÌ_ÿÆÄŸD&>é¹,òt$rö¾ûØÛ#c£kö´kTëݨx~°6ûìÍ?XVæÔ(û’°n^ì\Œ´¡P(D£Û?þX,e10$D o®¯?õ¶·½üS‘Áÿ9úß.‰Ïˆ^|fä±ëÉ‚KŸüûß8wE䵋#ÏD"³G2¶GÆjiªkGÚ K'–°å5E»^^P®ÔÝW»vH‚YLŘ/íÀ©Q‚)9U­ôOÖ(kèd9qª ÒóHÜK²Fþ2V6zQJ8‘Hÿýß>yñÒȹ{¾BÄxæÌ•oº9²ðäÕäߨðñW~;òâ["/¼ë]±h”¥ó`óžÔB÷ÏçöIpÖTÈZ‹x,a–*—ÉdÆžþØOýÔ‘7Gž¾üMã_ý»®?xëè_E¦þ)2ú‘…ï¶ç/zþ¢ÈK‘ÈСCétš¥kd¡ Á²ÒŠ CÂ0h¯„IˆÇã=W]u"yêÍ‘'>òâû#'?é¾"rú¿GŽ0òôÛ#Ç"‘ÓÿøÖæfø*ž5 ÷÷›››„r&P¡"”ggÅÄ™* ù wÖ¸‹¢"PŠøSÂ$lllŸ~ë[_ˆD¿9òãŸüøí‘Ã?y6éŠDú®¸be~žœ_HX~5ÇØH÷sG§RÉR©˜H(—KÉdâÈ3?&‰7zËMó%f­T kÖ/ à™„-\÷···‡ž~ºûŠ+N]rÉÉH„tŒO½ím§?ò‘¡ý×õõõt:íï«ÙÚÕßÚ«,W–FO½úüã=26:”»ÔÑ‘s?öC’l’ø0å+ÜYs£(€7oQ³vÝ''1NOO÷ô Ÿ:599¹¹¹Içû>žõ«¿5 Ç·gf‡»O½ðÄáG~è@@’JL’M¦|…;knð†¹îÛ¿ú[þy£èìÚòØÔDßÙ¡WI*I°éï@._áΚKE< C®ûö¯þ6m™Ôöøv0àj˜ÊW¸³æjQ~å3ô×}ûWüä=Àm‡øºoÿê @„H$ 0H a0„Œµå1HÚeàgŸþ¿…|æõ×_Gi€÷¬¯Œ BEÞ÷„Éßr¹Hl'âk(ð̽òXÄúú1p6—YYšZ_Ã;À‹qàÕq²> stream xÚ3PHW0Ppçr áÒw31T°Ô³432SIS°4Õ3 ˜[陚š(„¤(Dk8g$”¤iê™héAè ÔĔ̼t'%±$Q36Ä‹Ëj0ŒºæÆ¦zæÆ£ MP”¹†p¦…_ endstream endobj 939 0 obj << /Length 2104 /Filter /FlateDecode >> stream xÚ•XÝã¶¿¿ÂðËj‹µVß–ìÃåpW´HÒ¢1’‡´XÐm³+‹‚Dí®ó×w>HYÚÓ^Òk83$g†Ãß ¬Ž«`õ×ý~¿ûpÿ%Ú®ÂÄOó,^í«"õ³$\mÓÔ·ÙjW­~ó>Dkdw»‰¶¹ûüý¤ÏgÑT<¨U#™R hD)oÿ³û;/ûQÐâÁj~’myÝ¿5¦»OWCi”nxJ¯ ¿È¢ g$[?ÂÕ&Ü‚…OûØÀVQ쉶j„QÏ’/â‚DâÍŒRãaî隿܆aèÉ¡ÿUÏüg%˜P¦wS{ fÝC¹sÏçñÇþÝ…Át8 ü"µ–Ó&©ûüóbNš|É<Ѷµ*E„ÊðwèeÿVVèyš57õú²S­QÍ‘‡µhŽƒ8²•©·;I ƒóèßAÕ²wƒ4è¥ä »weõ±аÓߺK„þheê ùëI‚1IOÂ’„‰'Üy HbrѸb†jX‹‚6™ÌƒÖzÒÖRv‡Q{JTxQM¥_n‹È»[ ä,ò„mh[ÝaÜâ¼À¤%¦:C¦bØÐHä\ŗƈ×;žòâÒŽeûN”O’ÖƒÑA×µ~¡Œµ«l–Œr)T¤^#β¿cÚn˜ñîŽË¹oÛÖpQ+æì/¬ß·À³ *«=wîuq`Ñ‘ÃaÄŠΘØ¦kæÓÍAÖýëп˜7àI½þˇ/Ö&ý"(æì5ÇŒQˆÊ20¹˜ÒÙXjis…¬®7K(³#pÍ·“;™g3œ@áô^o½“êî.<@ âð<ãVZþŽ+Š4v£¡ë1Ë‘~’»¦ùP—¢®Ù¶K>+=Xžß¡¯ýûQ÷äCèé­áôÎæYOÇ‹’½œ±J„Äã/øjµT3[0ÆÆîOœ™í"»àçIf˜'0…SåŽYCÏPº¸†©'Ï{YU#’](¥°Ng¦Þ3bÔ#9×ø,ýäü^0¨oeIP]γªQåP‹ŽÇg]¹8Ö#NÐr8#hجV©ÊÓÒñÌš <æŠM”jÊzàMÏŸF¾0¡÷ÿ•¥ÙèN¹Í€ù,»ž»!Ô8ð—/h>éÃë ´Ty²"» X¿XQæ.ÙKu2¦ýîþ~/º³8ú4÷ëÍ‹zR÷ŸÝ91ÞûK­íÆuªØà±mœ'‡ó'Û_±4ÛØã!_¶ ¦3¼—VL¡™ÜqÒvF~&¹kÁ#°ÚÙÇIã=_ Zæy6Õ£Ò$ˆDä§úÄ[·º¨¶Âœnü÷ʔ˖еÿÊ(a¨»&‰1ménBÃ;²Ð}GíU1H£}d Ü>´‡ì É ogN—EwüY¶äSÇÑÄ`ëµsº;ó ”0ƒÑgÁ˜ä uz€†\ö>k6°ioÖè©Yó@»åx1‡Ý\ìÛ¸PÁ9¬çò®äk|TM¿¶Yb[ác§ªÞ/1r?to³ÕmðÌØÎ;6Ð\Ú1-¸9À_‹Ã%ªêaGˆÑ Òòu‹Ñ€LÇþ’hZñ‚”û“8È&¥¸°GÏlÁ j^˜Ãe™Æ-ÕŒñ¯1¬3&ÑËŸ¥dÁÂëuvxƒõ×’ì[uÃÁry‚W›|ség&xë^Ù»¾ß@³bûÄׄ},F˜Ä/;ͳ¨¸‡¼‡«d× pa;UU>s\NSkärk óú޵Ö-tdëÅÔ]‹WÕ£^TÄÞúõ²fŠ3ºHp!Tk©? {j]ƒ‰¶/m8®A³ˆ" -’‰ˆê. Æ,P« -“Ð厧+ã&AŸÃoÖÖO<§žI6²càˆ ÂO^§ªˆ ³A²ò*J¯Æ³­©“Blc!4Í{Ù¹' ð—;í(+ü8;u{;—šíÂÂíµÙî zø¦ ?±ü4~Óõr¬R¬W6@%À©énso(éM^¤.ä©WɃjÊý°ß°w£š²ÔõDï$,æ@O.ŒO.Ò¯Ò*Ñÿø€\JGJÛ÷.Ö?,6Õ-X9ÙFW‹qð,ê7Bš,I¶î9‹=HB4ðÏ«J5$öNÓæN·ëDΣwyþTãB wkŽÅ7¬T'©°=Ü@ŸÍ ¨ß}[æ¹dñÛWœåNôó°Ÿú•9¿2ëWÆMK²å×82lo‹d¥‡}mõ†¦‚Võw̵‘Ⱦ‰ Z˜¦o_Ó¯è8ç¨U˜ Êå“ì¾À…}|,u­»‡›NV.VŸ»Nwß‹î(æ'UÉkÙz'f´XE˜¼‰™ãNcö/i†®é¿ã­~b0štuc¢cѬ*YýµûÓ/»óŸèí*«Š±B¤™äŒ%)±³ciƒeÃÙK^½9Äð.ÞZ[Ât6åóîÃÿ¢ Œ endstream endobj 875 0 obj << /Type /ObjStm /N 100 /First 866 /Length 1588 /Filter /FlateDecode >> stream xÚ­˜MoG†ïû+ú˜\zú£ú£$ Œœ H,¯"ìEÆHäßç©Á&öfµÓ`V3³óvuÕ[ÕoWOoâ‚ëÒœd×›º˜›ë=º$üÝ“K]\‰ÙåÔ]åÒg'Ñ`â¤F®Å• «Þ«+Òyn®hb˜0¤ºÚ\íf­»–€ÆâZU׉áÞÕõÂ( NÖ5:•²êXP{§ÙÅÍ»ÎMc^­.FÞ¸É6†£Hå˜ÒÍ ¡ÔT1¸if;:••M[Qaxٵ ”û8ÏÄEÅõB˜QqfRH› Þk¨.Eü/±sSê VR 6QãF Ë›DÔ3vcà¯5à p„b©€1•J0˜¡c§*G†7"S^%†ÏtÛ+Í VÉ¥¦äràß62ÚxCö¢Ù%Œµ®”dä$l´üâd¬)Äå -ššË‚9%ýY0Wl‚bö”k1¬Ý0ÒÜÈ5+þ2ºRJȹG»aJ‹G33)5£¹; 5ŠÉȇn±jS*OŒ›‚ÇbÜ~'p³*¸.¥XAP°E…û@ˆŠŸT‹B–fAi„0E7²©éF¶Õ»eE)WÑ’W*Ê$$ŠÙf·ª¦´#¹*‘(µØ $è3JRæÙáµdcçJ¨\&{«é©;éÌÜonzõû¼ôÇ«úΘ‹Oïß¿^=z4C6WîàÀMG™^:¢þ¡ôúÁÂÕë¥m˜˜^\nÞ¾\_¹7½xzä¦ãõç+÷Õúñ?Ö¼8ýs½š™i}qõ‘løjúmýqóéòíúã¼Öæ¿~YŸ½;}²ùìN S4û@p=wßD^3Ýé%6øCgüõ4oÓÿâMiŽ·×ì­ ï¼½áFš7®G°TŸïÔÑ^l ê#…^ªø˜Çœ(E¼-Å!lT/cf%¹aC÷(Å–µæó I•«.pƒ'çÈPó¶f† —îsÄæèkXr‚É[7AVoª;b8jð9-`£d/,ÞXƒ+4äsÙßl¾êX’ÑÖÓ˜ÜZï[²`ZM8÷Q‚/ªrG PöïQ‚éñÅÅ“'ó.m>Ù&m×-fÜjzùéÍÕüüüÝÅß«éÉæòl}9O^O?OϦÓ8?˜so‰*gñ½T6ïÄawêÙ76§Æú¦Á=žÙ{馟6Ç÷?nÎÏO/Îü³ó›Ë«£wï×?Uâ•iE6Ùz…ìM×SzñÚÒ¸Á-çeÎ\>hò¢ØB[Ì]‰¾ÌX[Á}Ìp¨,±1Ž9 ]ØY¥uŒbÎóVsûSg2—¬ Er‹ Ù]}lJT/gjŽË¾o·–߬5{å…Ss,"/8°-/9ÞW^r“—¤|gs¬7ííHÚ7E¢µo5ÇöùÃ|²¯Ù';X%W+×lßp*‰J±¯Iû¦nô~ŽÈü!‹^Ð:!Ö}€ÈdÚšÓ^OŽ7[]±}4ÞUrîŠï`s®ô €‹õ uÌ0 2wÙ-Ñ Ú~…ðîèÄv¶q,ºZè^w|¬¸Ë`ûX#]fZˆîf«°}¥î8Òïò¢hßÙ·Ý5\“ûˆ†êµ4h¸ö«A,;lh} k»Êk´>¢äÒéHCv…†e¸DJn,) ÇjB$ø>Xñ’¾tû`-jµ/´ìWu¬â³ÚþÝ‹BÙiþ+ö_FW endstream endobj 973 0 obj << /Length 1758 /Filter /FlateDecode >> stream xÚÛnÛ6ô=_aäÅ2à(’lYv غfëŠ`ð°‡u(h‰¶H¢ JqÜbÿ¾s!uqÔÆO$yîWÉ›&Þä·›_¶7÷K²q7«`5Ùî'›Ð] Zn.'Ûdòóþ(ÊZV³» ôœ…Ëë{•ç¢Hø¥…ä]ZÀ˽ˆåìßí7žásÿ¸Xô˜,#wø“»`å†Ìâç)-VŽÀ%râF×*gP"?{^P¤uª ¾Ý«Š¯ä³ÈÑ]¨½¿”ÕÌ_;Rk¸Ó.ßn©æûXC[™J-ŠUñï|ÏÝ„ô³z„ío–ÎNòšš3¨žkÞ¢$´ÖG´n-Y 4ü9Ã/û¦ˆY‹>®oŽ„:¹bú,ªTì2©çýW¯)Žè楪ji|·'ªdl8‘a`•/ L!2>•çú¨ÌM®’&“nK8t7&Nâú\Ú0мÞZ¥oç`»å³2auËBÝŽÑ-D~A¶ƒÇaµ|æd-;ôÛ‹¹<Ï|ßwÌÁu]ôÇ…8¬dÇl(–Ïr=“y€‰å—‘ 7©p«™ï˜°"_ø=×#ÅÜAÌð°ÇÃ]WiqÐ_Løô‰X³T×¼#óÀ…>ç;•é±hØ[ÅkÕ)Ú1Ñù¨e)*ÑFNZË\Û´Ð`QQÐ1~¡A„œ$ïP~\§¢,e‘ˆì$Îz:g dnÖmQ@ &þ2ã¢p„¥Ø‘# s£öL®½!sXF>KTk³vR4ãfãP2— X3XpJÉuCï4‡*€8ŽøqÏ ¶B˜ÔÚ™2ý2ƒŠ 65¿é]™×¢8ó•|¹Ít]ƒàÉryœK.[¸²¬°1².C‡˜|畉®%×z¢yí<@·h­SªIi8“ÒËÐ*Íx#B› í²9J¬¹6¨l«œ.ô Œ[è&“¬†7+ëw„òÄörè@Ϊ•` çÚ„G‘Ptß?ÑÄ\/\®±»Á³ÅÚõW‘mm3ßs’X߀žçÔO³d D !/½^gD+w³nq¯~‚瘌IZɸVÕs‹ñu˜hÜU»´l !¢À Ò×£´­œ¼Û+­}¬Tê ¼KB\Ç ¦¨Å°É\c°÷øãï4ÿ‚ádý3­ú3hÝ€%k4Ô‰v¦Æƒ£¡pÉ¢wÆd|øhW’`%]CÁ†9ðþB©˜óS‡ƒÓ»×±*ϼ£|‚•…"ˆ ‡hŸ‰it–…Û¢Ž¦ûëFiÛ¹*1=l;çÀôQë>dc«;¯ʺ© }ÙjÓ畎Õò*ojt‹÷†küÞs}Åe¦êSZ$ê4ÛŽYÍü×3*2¾º)ûC‘íg2ßÉ$±P¬\?TÔjPIè’¿C&©C%ò·óü•‡‰‡Ø0¤AŇb¯šz—:Ñ ˆm°¬¸ÆkŠìùXˆÀÛ‘™'¬—„ñ.y˜Æªñtj q“7 ×6 x™€hßȪR•~x1®Ë¤Î(0ý/8>WØ»lj gw×Þ˜ó®Àa{%zˆÅ!Šý QÙ‹Ìà€¬bšb=â±טù×ø yÃI­ÐÆ ,•¦¶D QÏa|LÍj²0ŠZÞ–< ÁéŽL£²XA|LÍú‰êî®cE“cDÍ’§¢ÃÂÉÅ‹= •Lð%Ot–ÒøÜ{†-îXŸ:QÔv™ÈtXѼN|ΑÉ–h?Lñ˜ÈÌ|¶gËiŠ=Ç4\ë©¡‡1}7ÖÌm / V!CüZÌbÌs7@·K %)N¼»†x1ètLãã^›9tйȲZeÜrÊpl&jŸÕЧn›€ÔY«‹ÊfëyOv<>©Tkû%×âòq'*}Mÿ•ƒó)k?®mƒçu%í0èçö+à dÆeÌL €€ô÷ÿ0æ~z³®ü¥Ñú†Ç¥Í <0G \¨špÿ Þ1Œ3ŸÒÇA‹Â{ŠXËà üÎðJãšéXl.MßÍUM)ñ:Vëëþ΀ˆ:>Ìa<å"`•% ¥·}Š‘éÒÃ;ʳŽÃèäîÛ6jÚb÷ 3¬Í¤í¨ò»…É}j/ÂÎ]aëŪ™Ó¯h¬ ±Ãi:“ÛÙR]e´o‡ ¢! ~ö3Æ¡$胫æºþŤî!áqo/ì‡$l¾*•“ƒj¤£…Rÿé«< à.¶×ÙiZqÛÕ³ËðÇðÇϳlô×/X—qÌþ¼ü!¼³ï»òŒ…ýÕåÃöæÈ›Ž¿ endstream endobj 1000 0 obj << /Length 2160 /Filter /FlateDecode >> stream xÚ¥X[“Ûº ~ϯp皉pᤒ0ìÞ½~ÔÓwôZÑsS±’ã·JëãU#t(Ì÷Ø­IëÖ²Dï™-. =d²*Í3êZ³¤ë “}@”éáȼŠ|T¾ç¶Y(§.f“)‚:wMÉ{f§ /Ðèg³ëé0¨X…gªMkÐÏ…)*:~€Å*œ+" Ž¹øV®#ß ÓëmÎÅœÄ8óìrÙa.¨ói?up‘/Â8hOÆêÄ™¹P‰ÛJ¥lÇz‘H§\Ä%ì .ÎøÄ’DÄQ4<2{n“ÉÃHÛհͨEšrùÎ’bž¨9îRm5žÉæ¨01 +jÃ}Â_x~2°$A*kÎ m¨ò~$¶Ë·ÍÙd5Ù«O¸r¢‚nâCTP."°TÕº¬Óì€ÏÙ[«ò3KjöýìDé–UMsPÇ?!LyÐHÄ^—(NP±ðÕÐ ýÝ:ÓJ äíòšÅ ÃÚ¥ÙöÈ®ááÐdE9›´>ëéðzøÀ§ôª&ÏR"¾]‡ÄS¢D«VrEKwLE Ür»Ý:ÝSÛ‹âI5\%’¤sÃþ„^  ŠZͽi3 _$ãÜ(t xioR/ôÈs^è;˜äÄ"¿Mëè‰Ðï0o¢—2ÙÀ©~_K\š¡ß·S)Ëò†ðØcP°Ã¬•×'2ù —œ†72îé˜Bœ@Úøœnu té–A† àhêþòåËÃÃû÷ïÞ÷QŸÄ " Ø-iqÁdK’Øœ‘pµSqÔ/&Jfvpa/ß©£õ……CÀ𯷓çIw‡&ãJ+×  m,Šy8o^d.@÷ãç¯DXW@ûùÃ=?Kžðšìƒza”P ?èR¥Ò—Šv¥Àžr¨²t!è¢|!ãxhç׿?0öã†tÿTW_ ¥æÕí%¡3¢¶MùØY5,áöôb¡B.éîÁ%PümÍߊ®ÍOjÀ°›Ð S!øÓšéº ÄÑs*kM,ÙÈ·oß.V!¼GZCn°VP7‡RGuó|sN³Îœ u”ãvîü]Sçó<é{Bùq礻czÚ•Tîýü9ÖF… BKs;ÿ—GñÚ¨^ðèSºƒšŸGÛ›a©_vo­F”ˆ/¬ƒïË«ìîHõýU‘†Òß_OÿX$®£S4Í‹¾Œ£.€hY9µ[Ü€Hû.‚VScã9ö¦Zܲ­\äUZ§í§úÃÃm&€A¥nQ¦îì<#øÿøÃà Oyù½ú#ˆ¸"QïùÛ‹©œwÔ TµÍ³Z§%ôë¦8Ù€Úòl Ž>…G¥ŸÓ CcI,†ÃN’£mÔÁóÅGƒ“oÄ.ôØŸÖzä€Rg#^F˜lë)ôÉFc¬Bk^ŽÆ¬¯ÄÕºÖ¯BD/bÿàù¾½¢-¡©Q¨…Û }¬6æ,„ëóò(±ùµ°UzŸXžUnOQèXE•×nã~˜«Ô>ÆFš3¸pqö31Q£ÏK³KIÀÀ"¥ TædH\þQb]€¨vÛLÄÎD¥¸j×¾é$hé—ô­ºª£+ ;P¶+½P°<~ÿIçÒ–âô®Ù¥=&'£‰bô_~ø÷ À$ ú¡ÚjSEƒ)×oþ ®ÖdF endstream endobj 970 0 obj << /Type /ObjStm /N 100 /First 906 /Length 1291 /Filter /FlateDecode >> stream xÚ­—ËŽT7†÷ý~·ë⛄X$hv‘a‘eH+Š„˜‰¼}¾:ICZsèÕ©cÿ.×½ÊU{*Id&÷4kMcòiIÌøö$½óIUøÎ¤­¦ÙJ²®I²Ê±¦Éæ8Ìfpá\óäƒs`«iª:Rí5‰ZjçZj-p=õ?ãë໦áÜÓáé~˜œ™cð<Fa¤ð7û@f-‚ûªBha…D+à^:A<bø¡[ŽTbly°%©`‡hFàVà˜æŽ•`äé=Gh”MJj‡PWz‹!Ï # ÂJ“KçD)I‹clŒÈ0Ãh*5á®jO “s|:D ÎVÜ!j€+Ä pKê`¶U*¢bƒ0)wÕ[á;Œ.¥°Ô1=ú&"–¸và|(î`¡¸oúvf›ª…ë-Õ¡6w`sÝÖ&T0.B€¨Äš"Z;wðkV¶5ƒB‹j°3T®6·ð²8T8¬kUC&Üb¡CÅäÖ$<ÅM­n7±ÔKè ÈÑ}óÛÃì…±a¡ák£ÇÀ&ë‚-,"•=MÂCÀ¥nxB¸ÌÀ“â\€b.¡\#„Š6¨¾ñ`×j@‘•ˆs‡á÷ºá#Ífà9î! »uèáÑ£ÃñIz1Y,éY:þôó/d\n$„YͪoÞ¿~ýëáñã‹Xi-÷òÜ1èƒàŒqk®XåêØQ²Ú"¶Z.äóƒXõškx·Ü¥¸?ÿëÏ/?ŽßsÓéÍÝ;Š¡ÅñÃñÙéÝíû·¯Nï¶ú¸-ýpúí—ßÝ~H/ +Êã°‘»û¯\÷ò-V&!¹½‚/Ú´=ÝZ–˜ãÐѤ¯1&CcV¿:¶PÕ|,–êüewþ·;žµ½/ºãg}ó¬U~ewDâòŸöxó·´GÞËíñ+Q¯Fê•öxi$:Ç~²£Óš.ÉߊÕryò=Çò ÉÎ|ß™>åR9<Gq‹w\›DIÝ‘‚×áÌ“¹Ï¬SwÀe¡ÑöÌvlLkÊñ€ç)•y;íɬyÚ"X™„é£Q“\êg›™'PjÛß%¬QFÇ”'òÅGÙçñ D#-8Êó^j×¶~}¬óööE,óô˜;†I•ŠB›ªºæ å•`î×ÇÆä¿Xâi©ÏZ«Jž{F“1#jb©‹I/ƒ1íúXJv=ŸÐÿ » endstream endobj 1037 0 obj << /Length 1813 /Filter /FlateDecode >> stream xÚ•ÙnÛFðÝ_¡7Q…Esy3€Ò¸RѪE‹$(ÖâÊbK‘*Ëî×wfg–—éT}ÑÎî;×Î å,ÎâýÅ÷›‹«[_,; Ýp±Ù-’Àá Š];üÅ&]|¶Þíå±QÕjíŽåÙ´¾+Y¤´É³B”@¹“[µúºùñÂá{à’hÃ%¡—ø‘»b±vC; +RÙÈÕ:tëš–÷ª¹³/Nà,Ÿ–°ÂÖ‰‡qý<"º$NõÙùªÉ…ÙA.Ö„˜¾Ó˜ÀöâÅàԱťF€Nq’œÁ¡ïo¾Ý®nÝh!\Û ü\ ?°Ý X¬}Ïx†\l×f:ÖóQ£ï "&ŠÐNâhÂh´µÕßêyæÀNüŽ¢’DVƒWi¨Üákíkƒ>eÍž MÐCö¨ ›ˆRM[5!–"]´++$-"ɾdzwž>¶ÜÔÄT8v üÊÜOËF=5K‚‰I ¢ê­A¬ÉÊ%«&;¨Wø z$Þ‹%¨èŶÇ1êú _cðÎ ;ü i¶Ù³Šš@GôÁ~¬~mžá½m«j%bKÆeå¶=Àî,{ï>žaçÝÇ3ìD|WeF‘²m&º¾ÿô wí}žm þ˜mU±Uœ§}¶ÝøëJa©¶þ‡M¯MMÒ œ m‘B‹|ë›ö²)G* Óm–ŸñH5ƒ¦Õµè‹ã¸¹ÂhaQ“@.ªz[eÇÕ! tþ¥Òë[™×Êœ+…"ž®¿¸®¯…cÕí8D̼0¹L³âázÙ6»?âåYéFêƒß<ÇçRì9µÓSè\ÒB¶Ù´Ñ™‰¤R“>èŒb9t¤hÓìÕÊ 26%ç¥LÇ÷û£ûý¬g î@ï`扙ôŽ"]²!X?¤Èd_$:9+aé$€#RÕ3«ž0?Ê*“÷9‹7Ï@òÁ‘–œ¡!™Qf[æí¡¨çl›}äÜoM1;”ÆÑôðɸ¬ä P«q+^|ÖÇ7·ê zÖuP/bM¼˜Ó–²‹iB€­öPAF¦’VÝêUlµú#éŽHYlÔU¥‰$ù¨ÒË‘ç ¢3¾j6=NYžU[EÙp¯h©cm츆FŒ2—Ì×6†”dF(ŠýJØgïHèðà^÷Ùo&vѵ¹¾YõÑ;lPûÊ£*TjOÚG3€‡11bæÈüÕp±:i;u!ÂèŽ{¥Ó°Ã2ÅØ.?M 0¡ 2æ¥d8¡^î9S±ˆêÄâiª-C¹%­JêáõÓÁŒ“1s¶ö Ø ë--M{Ä'î…!<Ú¢‘Y¡_3¢$-TŸ®æÑ…€.Iõ®ïÖH¬­Qíácâf4 UUC´ê-q'¡¥¯=ݘ31æüâAkèz‚À•/ÁSrð†ŽßÒb\‚ 5ÑPXÐ}FT%Á®Šà‚þlµ¯<Ó¼Õk¢ØÃzÎ|ãL4ÉTßúœYfÒÛÝ›ÿžlúîîÞÌôw7ýøeöOúòÉåå=vaƒyî1ÛË”03fÞ¦*OõõFO8U«®Ì”à… @÷x ‹êcY«Wð“)cF‡™‘cPHº™FsÇÿ;<ÏjNå:…úVÔºÑ!Ï fØña?‹À†f(ÿn3Rf™L(ÞpBÁû ; ]¿V-¸þ–ì =¶˜wÅ/®/üï‘é¤ÞôìÂÉCå)c0ëÆs[OÊÏ×ã.R}ÇJC}«n*DàÈÔ¾ª1-·¬p"Œû9œëË54øø\¦2z÷//ú66δVÕ@›½f^Ò9þ¢ËØc#ù‡6o²ãD4~ž‰Yor øUÌGˆŠN¨‰¤PWìj³?ôq˜¤ÀÙ &õý÷ÛZ>u²óRŽyçæ†éCr#wøQyÜàX=É)yÚCka_JØú kÌ‹)Œ® …Õɳb›·5Ì2ÌÁæ†fdÏZ‹Ym1—â®Ã5fWÃÁ³‡ÃaPõQm3LJýb3\fÀ£¯Í¼¿¯×2žD)Ù!Ëe•?ëâÈ×蹞éĉ™”¥‰2ñ é)5ñµ/†?±#2$1¡ U; ¹È”§½ê†d¤…˜¼žÉ´I„éÿúj:–™®m¸7y‚àlÿ”U“m[p?"ðËlîsÒ%í„Â݈k}Â}Hó<œSys¹T¸/FÿŽ•ØxŒ¬W®1ŸH=B—7n¾™ÈÈ]7Ùr$ƒá˜3S¼ïŸ§ÿ%ðìÜõP®('y¬'å| ó°Ü³ÎöôÐ៭kóG¨ðmáñ´!âË›‹Aň endstream endobj 1066 0 obj << /Length 2143 /Filter /FlateDecode >> stream xÚ•]oÛ6ð½¿ÂË0D.E_¶¬}hÓeëž ÔûÚaP$Új‰‚(%õ¿ßï(QtO:Þäñ>©`±_‹__½Û¾º¹KÂEægëh½ØîñjígA¶H7‘¿Z%‹m¹øìÝò¶Ýò:Z^ìÓ÷VÖuÞ”48V ¨j€s—bùÏö÷W/tsÇÖ*×QûI’°öW´È‡Ýò:‰¯í–áÆ_‚ ú†˜•«ÜÈŽ¨jØ!Ù¦?“EEèþ N„³VFz+šR”D'Ñ ¨µ±½$lU·²ë Ç£\‡Ÿ­2Úr™÷9¬HoòZ(d[ùY2,Ú¤¸#@Y_q„Û¼ƒy¤\VоÛexÝrã âjœÝœÉáåyÊxÌó%ð$³yã‘ôUy„Uç¯w’•|b•?fx¬úCÕ0ój)‹¡MïÒÎw‡ïà(ðv²«óžð XT‹C–¼(DÛ3ÅpäDëåuYÁŠª’M~$ìªoË16C}/:å<< ¸’˜' là8Ô"¬xwZH»?1ºÛW„CËÒò{XCƒ´ ±ÐØÓª•š¥çÑŒfè€Öjp h‰÷$ÆÅÞë×Ä–w{Å\ç P:@9k+jq0{1õžGÆïŠpt*ôÊÌ„1€¬P[½ÀcLɸ±!˜H°­ŸPÔv$&ƒïuT/Ùž Fx˜Y}tкBxLа¦ô:žx }zñ­'Ȥ‚+¦£ƒj3g³HéY#¬ÅIúZµwÉ}<µ»sÛ}¦¡}Î|™øÃöäw§bæGÓåÅqY4 Ð$ q(†Âú›Ë·Ë,ò´†Ð=jÑu¦B ½3h¬+´RœÆÜi9·Æ’±×2÷ÞàØ9¶Ë0„$;ˆí&ÿ'a'‘•°('¤`tNÆ«Mˆ£Þ2| 03ª )i•z¢‡S‚¬Ù4Zås/õ€¦ãXcÅ%a#ëªÐm­›„zhªËà5?1à,ªâpï8¼Çs"/Ì'ðN[ ʦ•AV+_¸¬ÝÀ!˜Ž8{qâË:ÉÝœP/Æ!ùãišf?YÀö UtUÛ«^*BÏ2TDüürÀDÒŠMšÊÄJ /üêŒu2ÿ—–q¹8V#×K˜–ùÔY4FNM–œ´ÙÜgÌͧáÖHÏ#Œý`ÃyJÍb³fäýöþç2ÏôפþÉÚ)ƒ®è¼Ddž‡ Y7{dš’½ûÝ-M9¯§iF;JÓtÞš"5‹¬9}Ú®ªµáÎfáÒš¹Î÷‚!€Ó#ÓÕ¸TCLÎâÉ»”.•ì"ÍÔOG™å—¥“,ñÆBlô|l Öêò±X³»|=gH]ñ¸vw@IÆšI‡7gÖ *«}ÃþZú„yÛœˆï¯ÛOÌ3U¶„0Ζ˜: Ùµf3“¯£“¦ƒá¸r/©ã¡*Åü½q¨úüþÈH»±Ÿ1©O~öÖØJ—MÖ *¿µi ^/q527³i“–ˆ×v Õƒê ºçEfvˆ¼Ú- è*¯Ý53–qqÆCÀ²@tº38b£¼e7¦™ç¯0zÜKgÖö‹.«ÂYj MsRçÌ/Ô†™ }V-kÅòûŠ6(Ý"ÓVeçzz›6e œGQ¼ä: e¦u̦=œÿ±˜ÿIb¨]Vp ÄOÍ‘0›Mùeûê?Ö ÈY endstream endobj 1100 0 obj << /Length 1451 /Filter /FlateDecode >> stream xÚ•WKsÛ6¾ûWèfjÆBø~x¦‡4©;íd:ZÓKÜ$B Ô ç×w]”B;îE\,‹}~X…‹ÇE¸øõêçõÕ»»4ZT¬Êã|±Þ-ªŒåÀ(ʘeYºX׋ÏÁ‡=?Ñ-Wq Ãï‡öpàªÆE#•@J*Üñ­Xþ³þý*¤{ÞÝ%Éä’´`e-VqÎ2¼â·Ýr•”¹UôEÔHKßõ² ƒnY½¸±œ,0{¡p($jn¸æü¤j‰¡ùjÍ‚“4{dò‹=PÒnûƒPæ—›~ÔÞ€'«(dUV¡ÍÝ2*¿œ±sËö€+g%¬uH=„aÜWðížåA\![(¬šÝòZÔÌšóî..QÌÂ,-mPÁº¤dQ^PDÇÖ¦ TÞ›NªG<“†“w: N=:0Xá—n±‹4Yv‹ì÷øñ1I2ÄAL°ñ󪆚³°æ€ú·wÁ‚èv2qhA«ö°šóßGÓúôÄ;É7Ð i±(¡%ó”Z2NY¾Ôæ_—«< ƒgÛß–xˆãôÚßX±2Oð`´\E%ìÇ3{1íe3{ íEá̦»lÄ’ï0-MX”V/`Züñÿ¡ZüÑ:ìKïfÚo‚3Ôå²\¸ŠOÊÀœÚU ø­lð÷Æç$&[”êbìfy”OB1äÎ…ß2Ðò ÞÑù–îÝ_@çEyŒ‹Q:C઀öq¸…Û*÷ÏbåíBóQ£7°€v:p3žr]rvŽ0Å-œ3³Í;Üå¿oOºlY¤þ †‚š q ¶}‡¥ŒÃTÛ6ü õ WÄjfÈCy^ÀH¢2־Ѡc¬²ÙÎ’mo//b‡r˜½¦ÁdÉD) ¹~† ‡üÀÀXcˆ01>vwˆHÜ 1n_f ö_Îâ…ÍZlô°>@^-¤ý¦AhZ €òãŽ"IÛKojzKÀ¤> stream xÚµXKo7¾ëWðØ^¸œÉ!`Hb¸ ÐAâC[#× £±dÈ þû~C;ëˆq¢‹4ä~;ï¹”„C ­YP ”¤ÊÉ \‹xBýQ š-d¥6jmx )ä¦NP(b Š1s¨\m¡Vv„£ê„+}'‡–* ZƆá¿uD¹ÆãÇ—ÖùB‰ qT±2> stream xÚÍËnã6ðž¯ÐÍ3¢Þî­Ýv‹-ТH\ìa·(‰¶ˆÊ¤`Qvœ¯ïCÉ–ã¦Âžz1‡Ãy?åÐÛz¡÷óÝ뻇 õVd•E™·Þxqš‘U¸òò""išxëÊûâ¨Y«ù>XFièÇÏj·c²ÂK#$GHH Ü°’®¹ ¢‡Qîш„iR5ˈÆ$Ir2’¢–_U…þ¯ƒ"ôÕïAú, ¡¿ –ðkå±á…±¡·¤`n‘ 3¥¸Ù– Ó°…Sö;)òÇñJVÎË?ÚŠiÞ¡G!+uDX+<»zÄÔÎ[#!Ðò<„h+\"¨6W•*û—š{®,øMiþ]°Ì³èEg Â/‡84"s_Éæ„P×·­Úk^!µ?G€ƒYU%äÖ ÓcÉÕ~4#$«t…vxß½þÕˆNsI0nÓô}\š¹À?òF±êG¦Ù;IÊ®iM~f%Y0|Lž\ABÇõ®Z”5‚5;¸P?ó! PžójšÍ¯a5¼»•‡G®û½ì q–úßã¡û¶áÌ %R3!mPÍÃà LÖ-OÍ»!Iæv¶ÝÛäN¨‡Zr¢nd,8ð}'”|€ ÍàìF¹’d“¨ÍKªd»]÷ÈwÐw&™‹£¨¶\·L׋{Ô·ühÄ,f&Úª\Æiä‚‘R…"’é«G£ Q®çì«ÂG†W0Á œ ¸¶íeˆÆö2èJñÎÑ*}+îÆUð,NœYÁÛŸÎ;ï‹E¯ƒUä«[/˜ïʽh5dvpÀ‰ƒtÕ@Ý ÊïÅi^%³œ)”="úÎV‘¡#oxyÓ¯q<•ý~Ї 5 Bã结 'ø·ÍÌ"Á$Ä¡›ª‹`ôÒöÜúÛô’Ú(#ÞHœAßNœy±‰3Ãc’8ƒ°½­íV̇4ÎiÅN¼âülöR@Sÿ8gÔÿÄfìÃ63á†@éÚ5gÍŶÖ3ƒnD½³ŸùX.ÚÍ¡çÓ¥·/Å o®f.ýŸ,¿âÛ–ß3é˜SôOÌ•<®;ŽÝë¼²×Ú¿÷êðm·^VÃÉÈrÖ|ñ¥2ËK®ÿ»à€Èú¤)t¡ÖøËep`Í;Ñ¡8!KårVê{[}0"bB£d:`–¿Õ ŸÌéj”ùI‚fQÁ¤ }cï[Ò‚Äy6Ðó—’û¥ˆÑ¤Â8míQG xXÛ(c Èb Îù,J2eÕ@X,zËœ’U~µwŒäˆ:Þè¼í'ȳ:¸Xu>Â쎳Œ05ä=^ì*Œ&–1ýóDR×òR˜Ž€6Éä­Þݵÿv4l›k ø;S@ð³Ä•t”"̧­aþ0Їힵ5}xyØ iº#ƒ5a´ íqñ¿iúï,‰¡ÛReA’ÂýoЦ,?­ïþ¸_¬¡ endstream endobj 1163 0 obj << /Length 1539 /Filter /FlateDecode >> stream xÚXKsÛ6¾ûWèjFbR|rHì¤Ó:™V‡¤M°Iœð5eKýõÝÅ.ø'µs1ß.€}/doq\x‹_î>lïÞ~ÚˆEꦑ-¶‡Eºqâ»a¸Yl÷‹¿û“¬[Õ,×~è9Kßûª(d¹§Ež•Ѝ¬Ƀܩå?Ûßî<¾çí'?^ßõÂM‚·lb7ñÅbíGnHwü©Ú÷e›½Ï3©³òˆ»A3o ™·X‹ÈM“xvÃ7/ôªþ Ú“­¡›²9Kù˜³º[µÏtJ8—)«ÛFßöÄ‚u^µD=gå¾z^¦¾³" YŠÄQ(Ðvû»}Y!Ê%EÇŽMƒÄQoäƒlå¼Ñ ¥,+ñ$s¦ôµPMóî÷ª´¼R'H]i‹¼ÈpçrŸìBb*h‹¢D=gíi"ù´Ž\®…“qVšÐcö¤J—è_ôEuˆÊ†RD0~HH²V+KÚÛóm1Ú{n¦¤þ##‰\0ˆIâ<æ²üî ®#oE€‚€ÜµgbÆÆàÁäa‚%EŸ¼1F‰BµM¶#¥AªjôŠø¾*kFg †K8]ñqZ ç¨ZÙ‚slï †MÙªÜ^ .ì­=#h'K‚ùQCÏ)šgºÕã+ÏE}Õ¯Èú—ºQZgUùâüï· *zWRãJ@d\ ˆ *a5o]ù]íß}’¹îŠG6’Üúòº§4‘-z5ð± ü@€>Ï´îÊ @îI * ¢À¡.5”œCÒ Ç8@Ë–ÒY8äéê¡dŸ´°ø|mOä÷©íúZ¶òBFßž UC_Z9H‡® ²Y” —`ÕûbÃF:  l»L±¯&Ð/VÄ!«÷,LÁþî$Ë#‹H>…÷›Þ><òÖ6éÀ9Å?wñIÏ–ŒÇ%©@[ yy®s©aîYÉÝ…¼Ø…À:Â.§1ÅÜ™ÊðãÐÔ3zµ3Yº­€1ܳ]Ä›&‡1Z’çnüpœ ìgœg0ó)‹aq ~RЊìàFU1fÌÏTkU7¸`(¼¢Wü!1»¤Ú‹^v‹´ ¥1ª¡[UkZ'Ü7 kÜ8 wÔ9†µ’ ÛõpÆ;Çÿô1˜²vÖh»0®L¢nˆIú4œï@šd¢Afæ+JÓ 6³†íäÉ„äxðEýàã²{¦ÎuwËLàΊ3fç“+¢!úe²Yó7KwCÑóy¢y~?½`ж€QÕ-䬱VFs=˜ƒJÝ“¬ƒ¶‰5“þ$ *LM ¡ÕR§KÉYðô¨ÐŽ`õñ”'jÊžò©;«$Hò¾jw.TÉ%_$i9Ìåqªö´ž Šjž3m ülÍt×ÈÁ¹¶mÛ:íœÓå¾tOÙîÄduÎ÷¶¤¿OŽ>×?,ßñÜö^\¶þf×,Ú77ÏÜ‹)€w(ñ~õóŠמq¿ÂÄ+'4XE¦ÎðÓ>Wë}QÑœnvŽ¢„KÍÈ¢‘ 1Rv(#ýd·gšvâ‡Íø{ —r.–7÷o¨wÕW$aSÓÈëRe„<Ìkdà%¸–ôÁg1°]XD¯#UrwšîÀ4EÄØ‡€aÍ(Ë?…x¨@&w/9ø­D¿žæFë¥ëjn@\;pØ€1h°â`hð:ÝœØ1ŸàÜßågM¯cX®¢:ý2Qá뜱»ª2Úì³’²Æ¼ƒ“‚ÂTyÅtó~â-7Ï@øNžT. RY ßt£ØþÔB–y OOî7˜íÖ™`˜&Lß©„Ù>æ§Ž>q# ìïööܰ˜¤1—¦°Ì®î‰ °®Õ.ûæy¾‰¸9]Q ãÂôSøÖª)älo.¡*òk•™táÑ1ÓoÜúSyõåë_¯H­Ñ¾›<{s¡È@ªuÐõú÷šËÈÁ?‘Fÿ©ZÛ"±ìHßmù¸½û…áB endstream endobj 1126 0 obj << /Type /ObjStm /N 100 /First 946 /Length 1378 /Filter /FlateDecode >> stream xÚµ˜Ko•7†÷çWxÙnüÙžñMЏ(m¥VBEÛˆE G*œSå"Ñßghò™ÂYü_¿ÏÍãcÌ.¸cqšíÿêzp9'%»”˜kc¾ñQlÐ]ª‘SªœÅI‰ÌªÓP6LD¨’!’Ó^m .ë¨ËmH²+’X¬®T¡7W“A$¸–ºAФ˜J©ñ'D˜YWu¥'6eN’1Š0ªb#uQ£-âQ ›g9pc0™ã0¹ÛFÆ¢jü,*mÈøS!ÊX­œÙð-ÙJ5›C)vèQmoÏ<›„ .JAJõ ¿"Ô[§ØL3¨H6—ªY@ » ýµÚ¾cÏfŒCÆå¹°v“ó/«ÀŸå¡Æ!Å ñD* ›*H&©ChR3[FüZ1GcªÔáGÏC†½›o9ž„áJ—ÐÌJÈð.¢hÞŒ%9IͶ/ÌJ7½Š5‡Dœ*Ú,„ ²<¢ªTF5n:MCÄV¥ ¼5˜¬2[9›!¤v4þf~ˆ¥´6PìÞeȈÍ^‡Ì¢3YÕlq:dÄ< [e`‡dOMÁÎÂ14©¥š¬¶îTÆH þܤJ0›çTk€¿Yø£ š‡Qñ´–aTŒ¢%ÛJÈJ·•hQu Ð¬¶BÖdÈУ¡r!Ý´§°9:Ú,OÜ)ÓIÜgnùù—_]Ͼ`)ÝWì°»zóæÅæÁƒûÁ9ùÖfÁ©ø†uæÀ¡y!ï¾ Tß1þ5¥Ç[:œQ§MžRèÓªíDŸ,‚YÕ© SÔ±áCrâ`Ôi2 VõV]ï§’}‹V•³ç¿9æX½¤¼r¼ß]º£#·ƒ°ª6V‹Ý->¨’Ba¹þÐ0î–÷ÕµÇ|ËÓóýËçÛKwê–§OŽÝr²}wéþÙêä¯?·Lœý¾Ý,Ùv»»¼°Š?Öo–gÛ‹ýÕùËíÅuA²Ÿ¶¯^Ÿ=Ú¿s§AîâE¨IóUõžÃb•*\/x¸Ûía=½¾ÆL¯qÙà#=r³<¿úír|ÿøz÷Çfy´?µ=»…Ë÷ËËãÓ8>LÁ—Í"ÊãrL^ªÝlÝŠaNê¥Up‡=Ÿ»å»ýÉÞáŽoïß¾=Û½ò'ûoÍN_Eª¶·ú¤ðj¶BíÍ1èF ÈŸ¡‡Å‹ÝþÿWñÒŠøžî¨&·Àö0N‘ôå"­¹àI¤VâV–èó]‰y¸p k`J×Fn„Eœ¤Î‘(‡‹úÞgÁñ3l7’q ±™ÐØpc>Éœ…û),Ùç< &™¼ÊÌ é i)*Ý&©1G;–ÜŸÍ*ú.êÕš-èŒ}¥ªHË>ÐGMQS}Kõ`ŒAW6 ¶›tÖtôý$ʘ^×Ó“Ò²ãí8™V©áAɇ[…žMؤÔÞ¾ÆÌ Â'{j=Nfì(zæ•ç5¬í/Ì–&ªRµŒ¶,Ȥõ¢Ý+a2òx•y{>Î#N,eª-ût'v«aûmêÿ§-+ñÿµe7-ðAý©FãX¬¸•{z‡›èÕvà‹ÁŸìn‚cãEF·X÷p™c.TÄÚÒ!ÀV>u#A¼¦sâþ:ìLÝê}Ž9“xz×›öËÁÊeLÉ·ædP#®Êl¿Ht®ž2éoÅ…ökÈŠ¡yéI°;žßdŒªðV‘`)á¥L‚¥ª'ÁÜÙEëš-‚Pi¨…3×Iæ@CÞÓÀÉ~ ȳ`º÷"“AgÝ—Ès*”ûÉ‘ “ɘ‹ôsMê!À¥û6[cxüûšnXãoã K endstream endobj 1193 0 obj << /Length 1637 /Filter /FlateDecode >> stream xÚ•ÙnÛFðÝ_!øÅ`1¼yi\)P£h• E+rm±áîÒ–òõÙŠˆDy°wvvfgvnÊY=­œÕ¯W?o¯^Ýî*µÓÈ‹VÛÇ•Fv꤫8ñì0 VÛ|õÁz³­–Ýzã…ŽåÛ´¾iªJÔ9mÊ¢–5P>ŠL®?m»rXЫ{ߟIÙx®oA @d‡,¤[»‰%…Ƌϴxw¸úV.´PRr`N`SÓª÷ÄÏÜ.³ÉCKx¥Šµ Ôʦƒíž)·´i™35!kIâ|K7ø.xÓÆuì4LIwù,ÊÞhîGÒ˜^à‡©õÔ9!šGZQ$®GZÚŒ¦n‰ú¥Ð{¾fÏ÷}¡åT3¡æ´ÀŽæB*ÄìB4-(oī֛¾#µ.AB0Aq²èþè8ÞŒš* b§êÛ¶é´Ìm¢Ûî &{ìëLä»À"lhb§ÎáÖ-Ѩ^XYS?Ëã õ­L”Y_ Íž‚㦣µ’Bõ¤KáSÁ|“ö€$ó PC$‚&ü`Â6pwFq]ײ—tý½ÕO”ÆŠàÉ:°A%ÊRr‘ßzæÖW÷^¼r=Û ƒ“„ø‰íF1 ùKê;PÊ»ûûŸ{°&qÎ,ËP+Èæ$Zdøè„ÎM-*ysKrJË–áã ¾1· Ck „@ø,ó×÷¢T®qIöI†£ìÐN‡þÑÝ:±ú ƒã„®É̼¸éÉx3²J@€Ã¿N˜³dœI/X¯×ÌåɇÀº>^Û„2iÀaÉq£¯‚ôÔWª•Y3îÂS581tª¢¾%l%l"´Ÿ1ÁõÌij p¥Ý·¥ä[L2/Ð~Aª¨ŠRtߊ¾·`/NÙK'g‚(LžÄ,VãÃ["¹5ŸÒ¹¤c“ €Rß“Õ;¿%ÌË^ŽÂñH,XÎN.= jòÉÖ˜5C— ƒ*,d”Ò mó¡}ŧÄópf²Oþ@¢ÁŸÜ•¼8Ñ ÃҘΒ Ì¡.KѰh<ÏtÏ5^Õ1ÈÉ'p„y€H”G˜§âYÖ6aQ2a‹Ùf54H¥i3ÜñÇQïM™>Ùìþ“™V?`Áí»ØÚ ­ƒ¾ØŠ[èò Ñ„ø¢É€ß±Ÿ;Þï¹Ü#€ÛšÊlÈ\¾ça-Sìi3² Œ0ôüžÛIì‚CAYÐh?hª^±;–$XÏc;h¼7M´1©Ã´˜:Ï``KN3ˆ›ä”¦«…ÎPmF¯šb®ôð’x•€’QÀfô;q¾ò!{â¦:–b'KŒçÈq¬7M'ê'©Äh¡¢MÛF‡i§ZQÝ|šx>o»¸aÑÛu÷§|\»B¬³Ëò¢‰ U‡kh0G|ü~(Í"É”º‹ ›ÚSƼÑûaZ>!¥TFyìòE•}a ½k1ßÖf3<¯1wË‹ì0ã¥×:Å_DÛu £qi*G@‚Ôyó‚pHï\oD,!úŽtLr Ì£ý BNBˆæ|›™Ê`­eæÐ äép<Ç"èÐQ^(±ƒÈZê¤æ¨ Õº( ÔfðDhôjÈÛ!«!L5¹žÙ&Õp)è¹™>Ȧ²ñÐhùÓzG1Ça%“Rˆ&dÌŸZ_NDƒ ñKdµ“ynìe.?º 9Xai¸Å@üËýغ¾WrÞ¯C,·»F}«ÜÎéMÀ,2ϯ©wýeß 0ºž‹IA¥hl¨ w$¶ã¥A¡¯ˆÜ` UZ¸Êsì ñÊÛ1<¸WBóëÄÐS›®ü‹މ¶Cop¸5=·ŒÝñ$ilšý> stream xÚÍËŽã6ì¾_ä2hìø‘¸@ív·ØúÀ¤] Û¢Pl%V×– K™Lúõ%E)Y·;è©'SI‘_r<ÙOâÉ·¯¾Þ¼º›%“’•Ų˜lv“2g Vë%Ëól²©'¢× ï­f‹eG)£ïkÝu\Õ´h¥I”;^‰Ùï›ï^Åþœû·ËÕ$Y²8ÏÖxJ¶bëe2Y, –Ó–ãqdD¥Uý‹³$Ž(TŒ¯TŒ'‹¤`åzuá L³<¤8þç±âWéKúÜ¥ªõ‘VÚVÜ]B¤é³rVzëß©Ù"MËÈ6 /ÍâHtÛY‰º–jO˜³Ñó@* A°® >ʶ%zÝ /”ÓG‰#oºí¨`ÔÒé³2/I1­¬kŠ%©†€!£ªuu脲sZ³d {”“}ä·ŠÊ2Zlœê98û àÞ Ò²žŒû“EµW­ÏŠèQ:"½ò.-ÎÚ2Z¾³ô­¸š´ÓRËy–¦Ñ^*÷’ä5Ü ²XyI=ßû£®Ï¾ÖwMúöƒìøp¢(q:—Ëȇü%¤œàËéóIdè”v¸Æcȧ';h!—ßk+¾¼á°ê:ãR«Ö+k}¯+zv¶¸„ÖM¢2Ê€Û}Ò5K Ÿc›ÙMâhÿ ·Ükþ%1‹À”$IÄϘ•wx+–ïïæ¤Å‡»Ú$aðòŽ1öû‹òò«º6ÏLÁÐÏvZi¬wØŽ¾u°Ã­öòQ(öbWè—Z¯ÑGYï…í¹m^Vr6Ní²#m‰Ó«ë‡ Ë?º<' J÷{úfƒ'Øó§Q§‰ÿBnn¯9-Þº †}ñÄ»¾…ÄÉr¤p·ÇpLVÑVbz?†.PTC´%46÷Ôû÷MX¦÷˜¸É=aŒ1X0"Ú¹æ¤,ÓÏiWÂ¥ #÷Ê¥H飤L¢Nv²"ÔÏJ>„þô\”ÇüÇËVFl`¡©ÔPijŽç#ƒØB¤t¾ àÇÐ86¾P{ÕG/:Áª?ê/F’OœsU¿L¯éžétgM\YzòСžR$áEU⧃´ŸO ¤BÓ?“ É…Ú«þ‹Ë(q0ùz¼9—ÆWÃGdÿ«¤û¿™ä•}?ƒ.Ì¥…,xÝj#>?޼Ÿ­²kŽ˜!×Ñ_„²²}v}«­=ðë¦ÓpÞÒ ¯o»Ëÿ³¹üªu÷ù°A*ô%HµzxYð<¸úžÅ)šÅ¹ó!¡þÂcD"ñÞæDD1ßr ]Áókúr"H¾Hhm*³ïžÝr$0»x–Öxæ­×a ¹g›é|lþ˜6Bîë³8 S¬Óùms#FÛ;>ÍmãIô­d¤œß”–†A°nÓmt<ù‰G°®Å­ÿ˜)[qŠW»o†¬dY^ ¢: ÒžFB!'+–¯Ëbø>p}r{o­³(a1aü½ *×°ã†ظ”ž9mHÕ,í™j½«Q€§¬x µ×H­Â®¯õŽ¢jDõÑ9v¨Å¶×ftŽ6rëÆÊ¬ÄQ›Œ†4Si>ºŽ;?Ð-–¢…ÎFW|‡­qöa€ûñïE¸ÝAÁÍkMÂKº^×¶ú(ê9a®>^F­%o5>_b˜ô…“ƒ|ø :ˆs2é¥ûáу/M‡Ò„©pÞooÈœ¤£¶Œˆ l%ögHHtÒœôaLC§°¤ž+Á”èîsZ¹æˆ·vۃŜú¿ÁuçžP•W\;ÂI´oõ*V®™kù áÝ{S½בx> stream xÚ­kÛ6ì{E€}8§HT?äW~èºÛ X{Ý¥¶v|¶’hó ±}I:ì¿%ÇÎù®×a ‰¤H‘"E‘¶g›™=ûǯWOž}ë†3‡3? ¼Ùj=‹}pgú>óÂ`¶Êf¬WÛ¤nÅn¾tÃÈâŒÆw,74}?÷mKtÍ'×»¹k[UAPÕn kM„Í.)šùï«io¹­ö¶gK/f<iÛ×U¹|ÛëÕ±ÝVå}bS.Õ·ŠýèŸüHÔÝ<²:¥´ÇÏk ŸÐЈ´*3"i_§¢i†Zš]¸‰"¿Wq2 oU†8ZËZLÙ2Ò+ÂðiZšÝe ~Ësu‘GŠG&˜arõëêû7¯¯æ±k½œ‡ÜZ}Oøµ “híÈXѸ¯v1’¹ÚJ¸† Ù)u|¢«Ô3ãkÒEe_n²o0» Gñ7¢íjV§Ò´Ã|—›…™lÚ®•yC²)­0^–4ŠCRÐóÇöùçWRK’å„’Klj˜íccµüæ™’­¡iå7b‘íÉÇKƒºú¥¾åíS’Ákn-³Ç±°ÔòFäÕþ¹6.œE [Àõ+êrÙú•^Õù2°ázÊB7ÇL½øçKàq!‹Xs‹Ú0¥'!0÷„>Žbºº‚ØýÅÿR?µmß¾(1% r¢Q·ÍÅÐ …6lU!+-ݰ—Iª“æQ„/äVÍcدEûMÒ&Šép±KvI¹Hqm䔌ãÃ2¿, 9-ó´OŸÉfP­ ðnÏ…TB?ÛÜX|8Þ¿UU‹læ× =û•v["up¸úL)¹6r1 Y“ QŸÙw&BÁý\ܘ~:úºÝgŠE€B|Y=,ã‘4âYweЦ ¿¸8“C—9þ#ý1€Èe±Ãïn H1ð,O Ÿ˜}/0i NQ_òƒÔ×ü£ãqüDQoÿVœÿ\ÜçÄÇ:{Ã^åPâ"³ä,çb^ƒlíºƒzÀs½a1‚`ÿ@äÊN µ=‹ªªp¦*O×ÓÏDßÍ>(îWƒÇ~@³ÄDoáHò©îA¨Z?ŸzU•¥XeøuY&7¹XáC^Uù è ¯¹¢…ÖOPPå "R3ü,ùIü2÷±œÁxº&;ßÕ¥?`Mt›äZÎu›ìÚk‘NÖxp}Þ+a¦(S-Nðö³©>miZ,ìÖbPWÇ·-ÿÏ Új+Œ§¸ËÕ#‡cYјI¢¦-Áz3µÔÔŒ`8¾ˆ¼¬#N½~[uy6Y§CÑ´ 3"]`•NzÄѸ}#û¿t±×é[àlUéá¤TÑæŸÚRDÛRÂH=&·‰Ì1æˆIj†„†\’®m ¦—8«2ɉés½,–(܃žóˆ8øƒH;Õ+qѱ=Æyd**•êÿÈ¡*åÄéø‘Êd¶)ÜP<Ù„;ÀÉÜÝ € éO‰Ÿ®ð ÕÆ½6½ßШ*#»±wb6wΚæ\¨úú¬ÍŠ|ÈyNÝðTy6Ôß}o£5ÔÒ§Ã*»Ø = :Ì> stream xÚÅX[k7~ß_¡ÇöE#›$0ÄÁm¡…û!­ñƒk/%4Ù-¾@úïû-Þd’Q.}ðZ£ùô霣sÓPM!…œk bþ¿…æÏ–CfÁ Q ª>À» \Z0ÊAHUÁÒæ/,hÖ%¨9K«Á’JÁ4Šg,Ç‹ c ¥ 0È¡’³r¨…aß–«¿ÀÀd…‡œRŸÂ‚¤ä#%l‡‘K/죲ÓÇÈyÙ¹ðŽJ_‰îó »³‹M’J–•ÈD[`•Õ·£ 3¨øÊŒí´õ9ü÷9Yeì„ ¬Ä.Y)²š;?^Ö®A†d-õ9,oêr#$p ú´Œ~2ÁÄäFÄj8;Cbq·=»5 ‚“H„¤ö9Ð);/hÁJh@ÖMêSf¾;,N¥K(Hd} çÃgÛm˜ýèø?ÚÕÝ 9A&t‹²ï–; ¾³ùÈ~áŠÿà¯ù)2ž˜«óÆ\Ý~ðw<ð ljØœ °[ZÍQ2ëò ^Z·(œÛç°CßS °SÔ.¿@² çÇ’5?û.U³n¡$ùA“Â˳»ìêþÞwmA¸øœrMnª`àùŒ",|Èøð qgT¼,ÕX¯Ôç F¥Ïa§–ûvjæ6B<)¼ut´š^†sD(!p_‡éͯ¿y´Äìt)GwŽÍý»w«gÏæÑM£ÁpXã?{²ÝÜ…££0 ðÒ¾èŽÇ '®;Âs÷à§`;(¦W7Û«Óõ]8Ó«—'a:[¸ ÙÏþþk—¬WÓ1vZoîn=6Ø×¯¦×ëÛíýÍÕúv/}î—õõÛËÛáÜs‚5&œ^åáØðò,žÜR_ðXçöØšÔu®Æ±!î>7PûÜ@Å,¦lƒà$Q`§¯ƒápѳ‚éŠ Q›•èùó`iQyœÜø¼¨!,´¤˜ê ~ꇒø`±8:sXj‘‘@ÔË ¥± Òµx¨å`‹F$·0YÜm”Z¡g43çRÔ8Ãr(ô vf‰”\ þ—1j®)6k‡¼šFÁîuËq¢EA%ƒÄ© \óÀÔ2Àƒ©½CÔ4Êœ,z˵`ÔPì¼Ñ’H4(ú´˜¤¬9f]òº¬îÕ;MÄÊ’sd¤DòV`ý 3L]‡Šô£RüµŠ`ª)SÅöøiÅööê[+¶÷ÆÃ{ £6¨Xô á´‡~(Ù¨~µØ!ÀL¨:¶JQ‰ 9|.wî#K7‚±Å,<ÈLIKÎ9ú=l¬ø/´d Äfô{ŽšBÓAf­8@:X’ç —€(º¨ º8E²ÔîT™UL÷$0êû¨z”ÐE-†+PVüÞÖ'eŒ—¼XS;Qå&ÆÀHd£ÌÈßLKà\P#‹ú­Ñ`ÀR³XŠ\êF³.Ó}ð‰Ð°–¥¼~®Ê¨6„èPVåðngé¸ûa $dç’ÿÿk2éLÑÕ'Ýéùf³ëùîˆËÕ?øà9:r5Þÿ~ן~»ùs5½ØÞ\¯oúnébúqúi:>ÏýÁ¼‚jŒSÌþ} }& ’Ðÿø—G”;Í.ÔónÂÓ0ý°=ÛœÀwÇÛ÷ï/7×·ß»•þ)÷´jh ZoÕ-[B0®Ã2tçÐÓœhÿœ°–$±`¢¨‡Š R»­g=zOhåX¹JÖØfsã5úwÿ876Ôe›íOæÀ¹ÅÒd ¬~-nº¤!(ý¬h:[Wt/Ï0/Bá_ÂUTœ =¾sÿÆ-%¸ endstream endobj 1275 0 obj << /Length 853 /Filter /FlateDecode >> stream xÚUM“Û6 ½ï¯Ðä²r3$õÝÉ%Ùd3ÓS;urhÓéÐms"‰‰Z¯ûë ÔFÛÚ“ž><€ €<:D<útó~sóæ>QŪ\æÑfUË(Jɲ,6uôG|wT½ÓÃj-3§ŒÖÏ£é$~YU2ÖÓø7m÷ÃJ”±migÝq6íIqT;®þÜü|ÃC_Ĭ”"ZËœeÁzÿ«1£ÓZ¾¹O’…AV²RHpåÉ­:ÃyIo5­Ó¨k”ªeh€Cd$ŒGÝ4$îlÛª.ÐÓÆ6¸¬-eF¶Õîø´mÌ7ýÅ–Q ±å)ƶÁ­eÊJ^\Ègs¿¥Åtìîñ¿)rpÀY%%98á¥B*:œ¸¼>Y±²*çÛ¸êh‰¬fÚÎvN™n¼œBŤ,ž§°±_yÆßÕ5.·½:è[¥á%p„aC4yrÉê ?þØè7í>(§¼ÉãíkâªAu˜äh÷?lÏ×l½ +’‹»ÛA gR-+ç»%øYÝî«pÛÑÚžg~,p2¶Ç8ÂTð© àSARÁ,Ø¥ëú¥ÑjÔôý9úd@jbö´žíDÂÉNMMâvæ`N~Ô¾ü }ãù_¶ÇJgÿþU—?ûÓ¥(ˆð·Ëü™ÍÇÍÍ?™š endstream endobj 1294 0 obj << /Length1 1612 /Length2 15115 /Length3 0 /Length 15952 /Filter /FlateDecode >> stream xÚ­·et\Ͳ%(ffV‰™™ÑB‹Ñb*I%fff¶˜™™Ù“-–ÅÌÌl¾{»ûõºÓó§çý¨µNFDîØ;2Ï)*2e5&13{ ”½ 3+?@dkâê¬`o÷•Ihá ø4r!PQI8]@öv’Æ.@~€Ð 4°³Øøøø¨öžN K­†ªãYþ ˜xþOÏçNg…€úóÁ hcï` ´sù„ø¿Þ¨\,s  ¡¤üMVQ@+­¨ÚŒmÊ®&6 SÀW)ÐÎH0·wØü{0µ·3ýSš3ó'–˜3Ààì4}nz˜þq1€N¶ gçÏgÈ`ádlçòÙ{ÈÎÔÆÕìŸvsûrp²ÿŒ°ýô}‚)Û;»8›:\ŸY•%¥þÍÓÅÒØåŸÜΠO7ÀÞü3ÒÌÞÔõŸ’þåû„ùôºƒìœ.@—r™f gcÏÏÜŸ`N ÑpuÙYüF€ÐÂØÉÌèìü ó‰ýOwþ«NÀÿV½±ƒƒç¿vÛÿ+êq¹8mÌ™ØØ?sšº|æ¶Ù!°ü3(²væö6ÖÛÍ\þ§Ï èô¯Ñþ33tŸ$ŒÍìíl<f@sE{—Ï”Úÿ;•™ÿûDþoø¿Eàÿyÿÿ‰ûŸýo‡øÿïyþOh)WEcÛÏø÷ø¼aì_ÿÜ16ÆNÿ¯pc[çÿaÃjÿMòÿGÖÅø³bvŸ‚°2³þÛr–yÍ”A.¦–sc›ÏNýË®agt²Ù?ýW3Ll¬¬ÿáS·™ZÛýÓz®»€vfÿIþS¤QgÑV–Q’føÏ;õ_QÊŸÚ»¨{:|û¥(Ø›ý¯Å?ââöo¦ÏÈÄÎÁàþLÈËÆæûÈö/¶ÿZ+»8<ºŸ%³²ý«ðÿñû¯•þÀ|±3µ7ûgVÔ\ŒíÌ>Çëþq›º:9}ªú¯ÿYðÿ\ÿkÐ@ )Âò¢½©@ˆUzV†K=nÞð¤¤îÏ~6ÈáP‡²&õâ€Zû>ÿôˆ-¾*£·ºPææ_ü;<NÞ÷äè÷GûqlhúRD¾t? Ñש»xöƒX Ê3Nµb¼/ç¿nBép³jîoOª¨”¾Áÿêâp‚»|¤  p+ À¢|p@ñ3MkŒÇîFkè/:9¥N>z| 껆þ±GÈO%`Œë÷ý„,ÅÅÓÈé®Éô/ô‹û¶€J0Z5$óiJ{Œï¨ éå·!±o(±áˆ žÂV+<ªŒÿÜA— µ<ö±’‚ÒE©º_?›Y¯§†—÷ðÍðT\t[mMNXú›&ÐŒÙpmBÜkûqÂå{Ñœ»v-ý‹QÜ;Á$ƒ¢û×ä2¦üg®±6¹þý+àupî&R™ðÅ·ìb7·_V9ŒÁû¼¥lå“Ò -›>}¹ tó,5ò ODk _ïáÔ€>>\Žä7ØôºDFúwëôÔî®6ÿ­†$âÁ XòtÜ~³”'¹†*œ93xâ +ÑV›;¸ù€^§t‘Å ƤÈ}: 8›¯n›JW#þÝ)ÃÊß¡úßêÌSt¨LÏ¥§ByÏçÅú.’×Čĉ–/&ØÿÄoJü,ÕØ“Lc;*8í|ªõ?ÆEŸØ­bWµ8œ$ZúåYÑé˜q"ÿHqm¯:4ì§‘~cAÒeÛz.O…B[˜´ð+óE$¤µ$h9hªFçŽ&‘ù¦˜é¯T<è·eª2!Mp÷#ÕªFXELßa߯þÖ%uôM†ÈoŒêÁõâ—4Ž˜]¤šmQÇÒsÆ#¾!œÊJª®¾÷ŒhƒæÌCõ>ÿ È+™÷® _±ÑIå$mÃ8-]3êr4bTZÊÇ;yyõE×wcɤßbÞdáÚ×Ä•K™èíÆŽéé^|꯼¸ï䣎þÇBµƒ@ç*6­º=À®=¹È~:e؉Y#PßCLRDŒš·‚ XÝPåùSšIT2“Ÿã›.šá¡>®…QGÞ5Ç‚"ý ƒ7Ä“p8‡6,FËÃt¿ÑШúøP~Ò©CÒÏ.Ì•DÉb8O%(‰Ñ}ÅbäŒ_¥ø”>QâlÜž¡nßFuxÍWÒ¾]C`×_\¸SzüÆ$Y<ZSW´|ȾP3Lj†±ƒ´Z§îdÏ”·…‹ßÝ<]õ¾ö0A?8¼Qgy2Ç<æËY_³DEWOc•¦9 tÁ6B¾w7ÞS6Àü+Í¥ú¶"£qÏN·E1˜¹.Сoú@qo ½Éà§@ØÚ™œÑDÇd;^©—Í•ŽÖ)êo¸îNš]œvÄd?Õ&ÚÞº\¦JT‰ü™ót‡6ÜóqTmWkÑÅѯ]÷ƒó †‘Î$¹÷/õÚ04¥A^¥Çg—Åt ¦s> ”­ptQåž³.»äåpâ@I± ’‘“ýòKŽ2ùø[‹5L;9_bS=u•)óU3&!ß8V?dfT£ãŒÎHJú£Ø‡ñoq¡Ü¼_6>%Ùa¦ê.”=&ˆR{RÁéÍ;®´2j¥Cú°»°5^(…? …²ÁOm÷;×ùhé©öAU°L\F9jÝmc>`?ºÚÖì§*-8yÓ@¬î™s äÜÒ´ƒb°jl@'¬ïh}Lªø³kúwq>¥º¼!u õþÄmÊJe 5ΕŮ4jw1­"*¶}KÊtRJOկÅʳ×o"°¦ÝüÛc¹ýµæe!¥¡ÙÒwfýQüT¡ø2¸ v \BMïM#éXÿÎci¾bR@ˆòÛ³º—/(ÕA[\êŽfų*HË’rrßÞ=¯wÌ)bJ¡ c¬®ën^,Ä´ö4ùmÃ+“ÍÛ«¯_ï.-fDö]ï§Ïêib‰ª'ÑœH×èÁe°O!ÁS[j¼Ý6Pmñðª{Vÿqz¬We!émc¶ñÞŠ07¾b£>Ä«VHå6&ágÏQïÏ[Óàfܬ'âFžý+áQ£b†–M†÷¾z)£ÉT‘í«ê\³˜¤©—‚®™¿ñsJ?I)ÅAP]÷ŠïC([¿xUÍíǣ①)½¬³YBÏ7&Þ*VÍ-›®·¨‹ìX̼T3 \‹Zše K3)½HЧ[¾Ç⇕AêR--Dëª~6@“ bf—døˆ¡ú}÷Žx(%wÍâiÅÞ‡ÇYd‹×l·Ã7¤–ZŸÖ.ÃäÉÉ?0rÂJñïPÖï©I5ãÀÕSì@¯ÈwN€rD-2ݸ0ÀÛt:IZÆÀ7n·b³gØÆâ¸äWv¤å£ÐÎó>hŠ„Ô´ž“8„ûÚp©¹†A K„#î‘á +ÎÏ|âÄs¥†Š€eü†ãGoT9• »Ÿ!62~³Õ¹¤älþúï̯Ë*TÑ/S8Çz‰uI'X y°{Vpȳ|þ‚!Ó3 ñbkŸ ¥ †Öv¥4*±°dSh]ï~‰ÅÚl—pxs3`qõ°Á¹œ]”ˆA’²Ä눥RÁ(üÊhbyBí‰éa6ŒÌ vÉ<ü,Êd´/,_í°f³4¬Ùf jPJî@ƒn•xäú¯ìªzblƒ&}À×ïàõº;3k;H%X‘Adô‚Öja÷•½»×`ejÊ6ö$öñÔõr=ii±=z}Aè xm Ò·’ÌÛÌøjr?-•”¤¨‹`¤‹ ¦Ÿ0¥ìJÑZzàƒŸÀW¿¤qðào­À¥„Vfmªn{ì›é|}°?Ö Î)ªË3b™‘Ô‰ªµ¼6Zê3–IºÒꨎZ»zšÅÌûšWJyòXüÚ¼¢o7Ã"x¿èpiÿ"ù7üî—ƒA‰:R8£šëmÄ4Α i £]Å{9ON]‰˜>-§ŸöRcÉ'î§3Ì’"úÄ©9±m‘/“=í‘PÕý†6¼ È»®UqðY˜E‹…#O憥§‰}Ç' zM'¸ËƼ#½n«÷rKïèÜåÂO­&î¢øß-Eü–¡)ø‹ºÅ,®»GËö„]—ò"S⻇ª„’T±Íß&-ÆnÙû÷·ò^^õÌ}§v{È–¨ƒÇÛRGa‹Ãþ‹aÜ]âU9ÖU[¿½¢už‡Žj™ZIPúƒmµ}8;õ7æ‚§9©0K¥zùæá³¦¿©›«§gÿ‹!´rˆ’ØÒÓÄN• £hz‡O¬‚0ý·.7Súóý茭¾ÔÖZ ³™«Æ#Ø{ñ(^}Q ƒã”,©sä "]ü²³(_xKìoM3­VÒ87±û¨F[Ásçù_—/Â=6黎¿V ~ŸºœrªŒû0ëí§†,ƒùM‰¶¸Áä‡ßýC5K…9tkXb´uË™t#@æW<¹IóDèdX˜·˜Iò„Fý’|È¥uN‹Ï=­á~{þ·¸Uß>6*˜Èm)ôÎX­&Pd´ÍÿýãBZ=8ØÙhnY¤Kô'ŒÃøü•˜(V=l€ÿ³µÎÑe­œœem-òž!Ñ«é9ÃÞ•5·ÐÍ· ;ÓÜ‚µÊµÚ}ª\V¿®üîŠÀêMÞ×LÓ¯*òÊîò4w?ËÍík\æHGã»óº7Ir”Oã*“m&–ç_û'i 1ý–É4O¯¸ñ“ÔÒ‚«bÜî#ß$bE[:Êps³x:¯â¯ùu¬AŒSö-Ñ!H¶Xo! =ÆCÔÒzìh2@¶áÜX[NZ£šœ1)êÉ„&`Üš‡¥VôÈõ„V õþP=†$ #”í0!§U9¨BòÙçjÛžn²,»«tB ŠÄjŠÞýÂ×SàÖ«îeƒ“RÜPË!Ý¥Êöy¶Oýœ§ot^Ù6£âoÓ‘¸†»P–‡^‹y§›qÑíÚŸã§?ºî„ÆT󩘕^îô½ `ÞåÇÂK‚k$B‹Þ\O÷2H¯|²{ ÉÇ-æ°QÆaô ßžª‚pcJb>%8eòÚà}Ïá)båD •.džžÊ¾ÀðáBb9՚ÅvPxë–ŠIQÿ] \L}]ù÷Éa}-ó¸Û0ÆA;Øp½¥Ÿ&ÖB7оÃ5mç[›TˆúNtD¿}Ô⢱ýxCÛOרÉósWf¶`+Ë—wFeL)UT•~„$ÒSªS!íÚQ›¯SÿtUoØHáï$-j²œ¹A_dîúã@4âWkïÍicR¦ò؉ð³†_˜W”|úû>ÍÈ+ñÉX%aí¸üÉ[qOaèú"–" $æšžF g:öî¢ež‡£tSëõ§ËßLŒV Ý ¤“Qq„#•Líô"âçlpGâZð¨N·G˜9È_—ȹ‘æÈFÙ¸E;ð†Ø§ˆãû!ÝìsµcI­ù’âkw¢í!4œÂד’æÖê»þø¨¢ ƒÃ¨´²…õxm)šê_`æÏcþT8Ówj½Úµz8<ó‹WË!8N§E?7cwc=ê bëýYuÔ×—óǶä0|­:€Uâì™šÆ tLC‚rNäèÔ5¡×¤€ë¿Fgx`Fìà0y{$qor=¿D ¾(ÞãÞXÙye§'oš> ÆV„Ýâ5@=Ò»v¹{ÅL€e7ž2©ž6&ò|‹žÒrëì%Y<µ*¤â†Qîëiíw–tä‡s€Z}Ϋú‚½Bw\êׂbbnoö}·ž$È?jÔ«“O× pí ¢í‡¥l¼Pöîi Žœ¸…ÍWWþ×í~ðqVsGUSãsFžLÕèrâ,2B;'ùœãøßY¨+¸]Œ‰N|!yåMf¸ß$Ç#]÷{€½bâ¾b°ëêtûU£cKÉØÂƒÍÏR^é]ô„ðÖˆÅ&›nÛ°êÑùÌ ³×´=V÷¬ýØ_yž—\:fx,o­LRLúé_,ƒèK!Û´ú­%-ü‡Át ,ÿJnÁ˜3Tm0 ôú—xAðÑ¿¯.Çñ–gü \¿‡æ0×t…¿Kë Pz5ɼ*t_ëÑXÇ~áX¸M2;7‡£Qȃ À°â3›Ö^j+¥,TÌ\Ç”zgq[ªûIÇAÛæ)—·_JóC<ýøöêh¡AÚ5´K#a:¹ÎÅß›µƒ¿Ë»õ!øÍ,?ÄŽÇë›Â{]m¥äíïî¤ÓEpV?°43<®é½8ûkƒ«Mù$;ïàq”?ŽfÉÈlÍiÐ…5³–~G3^/iT”é”ðùéõ%£º úš·íËjÊ'Q$ÞŒÞ篞øþRµI&Ÿ'…#ï— !eÔKchóçÎN*Ÿ!„É#´œeÁsîÙ›I¦÷5É8úÃ<á¹!Äd€X#lqƒî5k³¦³Šr²§ÚœYûʺ?ïfÿå¬þ}Ñž«Ät ©@(µe*ÂÎ µçØ=­æ7–Rò 3ƒlŽZlï¶<# µ³ÚŸHPÑí’[  úØC%õyGØLmœ&SYŸk˜‘¹‹anžt¥««Œ«’*: žáhº¥”ºrDíržäôƒšnC™€ž\KÒ»ëý468Ь¼2êK¸ñM ׈¸®dÄí—ñä^¸[g¨Šy6û¥2‡6ÛÛc©'ˆ´þ9d’¬¨œwèAëk* ÒqJf£¶vÔ6t[ª—ë$¹óG_Ÿ: Ç^æ~rLNv²ß‰±ë±_T±'kffAmia:Y0ËÉ3ÜÚRë©UŇH™mË8¼¢š€0[+¨%æ÷¬¸‹g¯f¹BÒª 7åj‚]9±ÉoJ–qÌÅ"÷,bŸù ª­ùrnß Ë@ùi®×Ò÷œüû¥ù&}EÊj9ô‡%KH¢…€ó4SïŒde—–¡1zw­ª¼oµ;zÇO`+NÉûíµ9`ç_Ñâˆá•uÀË»ÍOfëˆ*tWnQ;΋؉ t)„R”ðû6g¼©(˜'úf{IÐ_k`ÁÙ“›5àŸEdN3Ü0~ 0Žá}íÀÚÀèX67ÿa Í›‡Á1¡ð`èyÆú¾ñv°&›¬t<%÷‹ ›qø×aHÏ¢J¾ösбÁû êíLy‘®uõOW¸ñ J‹Þ-nПnÕ‘ Mèä ©Aœ›Ž ±\‹…[ÈÁ”«à J J×6£¢§» Üâ<+Î^DTÝ— zZ¬B“Üp‚£“ÌIÆ›¿þ85ñöCßðÑ(x¼Xïß—±`i`Ü%á¤Éô³˜¶t ˜’Ly71øî`öïCàS³NÉ/tÅ${2– jN‰NËCi¯¼Ÿ­^âÙ•!ûìçjì¯a]¶Óh S<+¢ ”îrùƒ”Ú¬rGË8X?;)<.†$Ö@–$)$éÙZþñä8H4…þeVĶãôOŒÒ²ÂØ5Ü/ r#“'¾ÓÑ,T0 ‰t7j§çŸ¯·f£·¡@œ×Î¥ˆ*üó c†çÚpIý³ öî ˜›‘„÷Ó´JrÄ—åÙ’>|ÐWp!CNýФñÓ+2,»¼Ž&Mq稥î$Btß´à ¡àŠMÚL³¨ÑzYÖ ,6XËTëíhþõç[ÉrD;añÆ™ƒlıH|Pì÷ã/Ìû¸p*¯ˆßŸÛÙtà¥x3faµ‹¸PBè1_×6Zw]‰°“ âÏä¼|î4ýå§¼3¡§ñ#—i5îg"Jal<ØÌ-AÕ)çº ¡%°Ãýš«H‹²jNHéÓË;åÞ¢mRº,•>³Ä¿œDç,a¥9ƒOïåKœå†È£œ¤ÞxE*k—ÆïŠõÍÒŽ¢c &’•J}½sæÛ§”+M¿AF¿YöµŲüÀ@S?vv}‰æ%ªˆ!d) Ù”abü>Ð5>wó`þ-¨tyñ’´ÉãG¹%TLjOÑñÎ#M+>?÷̳ö Õ·`‰y2°æF¢×ä#d,þèw1=¸i ÄM˜&:ÁŠ¢©n–ÌÆ~¬¸zâ2~Œ*ÎvãaL¬—¦ ùJG[‚gE]£ÐCôŒã-uF‹¨®:?Ò BŠ´Ó™(ÉPüìCd‰[U4›YJ†`ý™_ã•ü$'»£ªÏËȽÕY†d²ÉÊ@‡7Ó?ëÆ¾ásD$þ•XÝê®I…ã?Qf¼ïÄEØËܹ²0 >š×ù']îrŸ NÇ ·–I¼JN '&“¹×v½øÃ·´RšBíÆ¤JBÕîLó’Èvþà[&fàwx]ó·PQiª{”„wÕ&ÔczÂLGÓÄtFV¤ÅǶͦ¾¿:[Hƒ¹h¥hOš>5Ü_ .§‘V·g¶F½*Ø{y%ÐV<ÆÛþ–\¿¥Ö>†§Q4/A¢ìŽn’Wk®õþÃËàõ… w©l~D26¦(¬ 2t‡‰ÍÈHk,…’¾¢SZv&ØtÞþ‘0£í?G7ên˜ù¥ªù ɶæ{±žÍ\¤êùAöv ùÄc?ÞÓâH†²ì=Þó•aTþ×Ãy$Êj`4»y'¥”fdÚ¬yS`}Ú—›Á@}#õ ê—ï§=½Ëà HE¼EÚT¿À¦¡;‘kåJšø~„ßkZ°iûŒ…y]’hó0Lýñ~)u·µHkŸÇ}¾²4ø9$is~%!‰éj¯ÛýÊÓÓ8r\[훳MOI½Ÿ3±aÜá¨#΃¦ ,ÛU² BŠÞ1T»$0”…Hÿ(}òK"‹˜I{·6χxÄçᔢ¢BÒË|­§0ýõû`r–ÁÃ!fâ°Š# ?vaEÉR~ÛŒ>—èã ¦n›Nyï ¢ÀØgnº‹鯗ˆl÷0ð[3¼1ªJ€6¬¥[º—X$ˆ§÷‘J䱸¥×(´ù·([ü|}­Çš’)Š*‡gìèúÁøž¸¡žœÖ…¡¬™gÎõÉ<hG ·±µ¨„ì8¥Þ(bTèÅwÑ•K°åó[+2 ¬jTÈ¥ #9i6¾dƒž&_9oɾ>ºï+$|©¿/@NÉ^Ã2MØü-·#\°Y¤¸ãŒ‰p73ß#÷WÈfÅÝØEÿ“6Þbªlæƒvâv_o*¸Œ‘›-½ ÏúxÁá°`çT“½Nà.¤ÙˆÏDÂç^<ß^è{1´ø!±UA¢Óý‘ŠÕôÅØ*¦áå$^ªë‘7'ÕK¾Ý¨€ã #}||t”0÷Ø|]“¾k~– ¡Z7ëãa†+nL:‹âë‹ ´µ¿ƒ%ùA/gží5Ï”§!˜ý.ëòþm@YŸ€©¥ä1äqßW;õ/¹´"=·ç> w¶í•1ÌKzÄê‹ïe\:{(¨×Ó°|ƒxw¹ÍGlh²‰-ÑÂÚ·ÞñLµ á–¡ÅDºz£$Z쇕®iòW®Çg,òCz·$%ö¿¿G?ÀïÏ%äA²&Êfþ'¿}@)e©+€Ð±56ÿ9bêô*Å’áZº‹U2ò]Ç  æàÎ(˜¨Â›ÁJS•”02ˆ‡«•Šÿ×¥î²>mšbãÝDÛß\F¹v’V®ç©3~±ãÛbêâç·é¢Qº½¡Ž•¾Â㤵÷_«ö28FM=B>Tó„¡a°ºÞÞOõXÂäF+/#¸Æ‘E¨öÙµBõÕ®îÏϬ㗯D(WcYŒ_fSµîˆÎÃV)v" j kÙû9æ-$ü\J÷ Ì×%oo†CÂ_DLÁvÂW ë4•Þz‹GGá|d4‡xúTÔ~,¼P³ÿüõÛ”Â4Ÿ@ Š1Ì6ŽŒŽþnzøZKÖÊYx÷kÓK‹G]ÊA’ƒq5^3RYC²ãiˆé„»®Gg –—°*6íÈ»@Б°|rO½)Ã9ª_)•ûHÍÙ|¹¦4Ž: —¡M4[#ò1\ÍV"š&Cª©¦Rò¨Ò×Gïý&Ü?D…®¹+ò݉þJfœúMMVßT“7œuXm·áJˆ #Ë GF¿[þˆÃ秦ÀKÏóVqR'ݳ†v…MÝõ‰‚’Ÿ¶wUa_ þï˜ê¡Ô„|‰ÊbïûVa­ÀÏ~›µjRzhµ-¿ˆ¯HãÂÔ´´IïcZ–D.²Ÿ;ýXKXR³$#^oHsƒ¥]ƒ ïµˆòg®0Ãàúr‹¦Þ;tQBé­ÇLZKg$3†u‹;‡Æ»>b¥ìý‰å¬œŠ=Á¨÷“R.'lÉÿtÉ.;„÷&ÖýáÀ˜cÔ#ÅœÁäÇ›j³‡tQƒÍ{!ß9”ÆìZšœ¿ƒ"a°®²Ðé ¬ªœÏ ó,@é*\ø«àúŒ.+/бÑ'2ÞÙ4õ¾á YX³‡,-ûêoù/²Lfu;ÄtõuQÿÁ j.þÒNg c„b¨ %IX oB‚x憴–ÏãÑê£lÀ`± 6L$D}¥¾y/£ÝeªA –¯¡ “šYA®×&€ÓŒ'¥…bgs¯Ž@Sê$î,cÁ³dÓ„´'G³T…6CÁe|–”¡(X1ÈÆs±VË‹'ä-’ÈLEþøWµef[A'|.× ïOx}[A]“r1SN)rj]¢f«ÖàÝ"»ÖÕq•>5%ó³ð±ã$ð'?Ô¯z†€p‡ - áÚÏ£ñÒ²¬<ÙæVèÒ®ª*c¿®FÍÌAT¥àée㺋C¹]X•[Yg]›»km¦_ÔâˆQç/Æ ,.î[Ko>ÚœWPJ®ùÇ3QZœ˜3BúÇí¢ïË:óqMe[¯þÀ &¹×ørsA濤ÇÌ%‡¿Éc]ÖCøÓìx!æ'—jS%Ï]®¯ ¦e^¨¼æ¬>ºK46ñNû ¥Š'¸Õ¡DpnUÌX]‚΂9Á3‰YŠìaOkEøf“äåòïŠT GŸÑk7<ù%67Xܶ…««‡3å`ËÐ[ö&¥‡T*_z¶»‘çïfL7­’!ô—ÂÁy|Õ‘ „Þý.Ž!²º•I×öòÛç,h4Ÿðà-é« Ö0¡Õ±; YJeÊ·Ó,‚ñ:±¨ùGû«ã¬ø‰UhGT ô—4ç/fA-87KỂïîÒÈ3O™š‰?ªirѳ "5Ìx8P•óÙèx‡§: ëëÔXQN01rÚz÷¹ nê£ÂålÐíù"Ë\öLqS7”ŸSƽ$Ééw"’îæ`Žíq7Ø•’5¾3Õï›v;xË0;P 8dš…„Q›˜BÁzÅì÷xÛËbhŸû—ÖÕÎMÃÒífËK–ÁqÎ;•Ý%¼t– W#‚W~•^ܽ­Bé•ó!jp!„â¼5¼;¢Mž>Ü™ïÎÞ„ªé{vøÐû³Îת| -[¯#OÅ[fìf™Ió^Ú¸ŠhíÊ18 ŸÖÄA¦šSË9U(:µ÷gZòdC‰p¡"Ë€|ÂO¶$ýV6êïÞ/ûɆhe*K/ùôß6½“¼a-W;‹-@eR>ç¼9޳A0LõªÚ+mò;ŠëÚú³8¶ížE­ÖaÌÏÑÂ)X÷\ ±ú¸H¾»ïZ2þ•>!®qm ×29Ô°$Ü{šøìÇc O!þ „HêƒK> ¨õ0}Ná@¯4O¬ÄPбY'’ÅÙ®ëVW³%^$5ÿRj‘Dì˜ØÍ¡Ôî}Ùw:Æú­Küð¥¯úÄÉ('º¶ºXé(5¯üœHVQû£¡~¢g†š?ó¼1WQ4u¸™òz.ݱ<‹.v%qf¢v]]pÑ…$Ë)J3êoª"\=Xèëzûó².M FG%ô»\Ó’R_„áäÀýrÜ”Ð$æiT Þ~¶:Þv¦:¸»§RÓFoo®tåœÒ5~eð¨™OY 9Ãzs{>ït<>z¦Ðà ­]%!)fP®Øùf:zÄU.ÏÏ… ½òiRÀ5õ³TJ gSx+³…W‡"S†„q”NâT½ÈFi×cš6¯ó‘µ«¼±{þÆ0G·¤¤e­sú%kŸe1 ¯&Ø&LŸ_¯+¿?CЇ+ÝGw†™„È.+€ÉìÚ¿$¡÷ýWºÏ/Öñkð}©½e§ñrÄUØ­}åÌ•Â$†Mf•<(AŸ?/Ì6.9ÙšãdâÍki3fÐtÉQJeˆÕåÊr~{…±…§2 ¨Wqøi0™YÕ«=m¡BwQ¯Û(çYø“f>/‰]),ý‹ÖÝR‰TÛBQÞX€ÞeݪçÔ,ù/ÓŒ(,“A™Ö©ämVSsX÷Iݤ`$Š·hÕæ (}bȽ¢ÙY{&Ó gÎ>’DÙåµiÓeP]?i aYÔ *ø$؇L|çh&1Ia¼¼Ü2¹]8Ñöµ©jÅu‡Í<6¬Ÿ&˜ûóÂ*TøL™7±tÝèsƒNù8RÒúvh6;töÆÎèÿ²Ï”<¼Ò“zÞ€”9øÇè«iS&Üä1¡Ž˜›¢½«¢–‹‡ÜgíŽ×i_¯ˆÛVO˜œ¥ÃkÎfSûô?Ì–ö_<)»xYG°¤v¨<''…%…ú»R4ç¤Ü³û^jj¾V÷6n¯û¿ÜÆçWÞ×ï©Õ05ÀÅç’•·Ý3U|ŒDM hH’,¶ºIc¬¿=ÃRš°€š“5O…]N)¯”+RL¬¥;Óù3Ö6<Êó-„6ŸÅY+a’ÝãMhÛ&ü$FWn@׉Œ×QòAYLé@\¯´{]!Û°BÿడƨҗáÃô:ê9ºÕ:á%7Z¨Ž‹ÑÔîÛžƒ¹¨G'B‚,´¾©ŠÙ9•6u-ˆÓ?^«{ßÞ]R‡ õšqÎÑŹé©¾saÏÍ/qË€!„|]ûнõ!¬F:Z Žâ)#ÉeцX< üÞk»éãCõ‡óàRÖ\$ˆ³æfr±V髼{‚­ö Þéz::¹ö^± <ÓSð8µŸE'o*µvsz·’*Õ,® s²Y~8-4§å;n±ZÈš·|¸¢ø¾_Æ_Õï}f3WN(æRž­­,w€By*|¯«úuwYŽÂ]­¹þ <$9}›¸{¨uyØ{EI — Œ8Ô"fø ¸¹ó®õ¤§Ù–äI†qŠ¡ÛÖª‡ÕýbªL”ư5I¼XÆ%²Q¹†Û·›K¢"1£»—ÎDlæ7éÂjý èÈë#²ý¾t¡ psß¹­vHN”(ÎKéÆ[µ¯ûFÒý÷194d ýrk’6ÀW Å-Îò'ê‰Õcv¶ÄÇö#µÆŠÂ×´kOñ<‹²Ê^$²±À?©烧M·;Ds’c³³‚V²iCx«nPÐ xÆÙ Õp(C5V°¾fêPQÖ/íDõ¹éwúy›s pðŒ¾W°% {˜Ÿvб¬ø+TóxD޳$~Ž5gÔ“¦‘%E÷[媭¶Y¬±è¾ VvòñW›…‚2éî¿¿x$¹+}ßÚüZRz8ªsàÖŒ·¦>ýÛVgÚ[¨ö;–Nðí¤•©Ñ7ë8ÊÖ%«Twú‘¡FêÁÈŠæ0Ì`Ä¥=“ŠbGkKÚµLM’)ÒŽskj»…®ñV‘i2sÞ¯>¼DÙ1>j©ùa¼YÎ,öaͬSj’ç4ûاggépޱY·láße7ŽÉôwoÐ@lÄ~hûãEè#g¾v)ydr²ÈÉñí¦g¥F2q#· {S0ã8·üHºe•·Ë„ï{j‰0–)U~FFЙ…ô‡›Qô{Á¼eçdø«Ú…—¤é¼Ìi“ª©0Šù¹•u¶Ô63nuÖ<ݵ²5ÓÄœ¡ˆ¥Ï [†òvˆ–û™>±75à¿áÉyßx[‡­{É_7FQ$£†î^þ ýš6©,½Ô žò)e-¢s‹§yÙÌXµGóg‹}“²P9Ù{ŒŽHº¤'ÿÁyuÓµƒ1R–Víý§|³„f ,ÊYbÖxMèAKÖw?ÑÊà‹ãŽêÁzŒï:×T ¨A…-êºfu l½#—¬›rûÈü÷Oß_a.ø1¢Ž`6"J„),Î32.RªÑÆoõAõ6×»Éň3µ.ö‹ªøŠXTr/0ìöÓn_dS#P¡¿”oªÆÏ<ë#ÚY"a,M¾'¾³t¥|;€° oBٛ·Á!‘bXôyž ÎV½ƒt4Ç‚2%Ç‘kK‰A{c]&Iæ ›O#Ð9PåTN90„ü@ãG,g†Yšoó}ÞÜËT F“ÏèÎLÉÊF²l¯ð8×'TÍÓ½B{­ü pµø»ÒtÁ\•úÃÊk†IžW†Ð{Œ#)¯óóº¦bÚi \gÁ9sW4ó·Ý4Ú³Fõ,Öý&·̶ª{dRA;ƒâ‚í®­†K¹2Ee…3d·lòw@YÛ/¥¬É†£Ý¯¯üêoNRAÄNG¤óW^ÚY¥ìŠžæ2ØÔg’eâÅCe Zè“ß|7iê~Ùß—îZ¨*¬-–5*TFZ¨)•r[m”+ÃÑ ílÈ[ç#É\ù$Õg¡ä]1wtçãÑé ŸTö2p°É!eç›xOî >ðÿ6G‘lÞó–Ï÷СM3àø=8üŠìb"Óæ´Z\LÃúv>À Nþ įLž).œý6ä.ÔÍ*­¿_× žplR•rÞãú-2§5i¢I”RÜØB¤eª›ÜÓÄ#"ÜZÜ›YAa‹\ [íßZWÞ?{Ù{Ë.ÒÇMd8÷$3B},¯!ŠAè=YÄ-دb·“ù W¹ìOüêãhxŒRw iú7þҌǷ¤ÚF¤L½^XBQG4äV÷#ý-Sª+øÌ’)BÁÅŸoñïîîôãM›M••|U ô†*œ/:fŒèmt›øÆŠŽŒ«&„ XýˆœÅò±>%ÕPÆ‹î8‹^1-:¾p;Éêàϰv¬gÞgQ˜_“©'Ü‹4| ½ù½ ]ŸÍåÕ:&,%‹0»æöû•Zªeð¦Eü“$.¨b}†A'OzHÎ@ÈIþ¹s¸›ˆ ¨øÀ](I 鸦µ³£!¾`sµ#»jªž}2Ô¢‘Üx‚J´¾”Àa›Cj˜zP½£îŸ1#8ÌÀ¹cñ!l¿K§té–â‚]o·éxU‘ 3è#K`ž^ðÀX‹ý›ª/¸ Ǫüõg¢Xr¼ –%sÀtæ¸ o+b[Xåi ·x 7S_ïÈq§¹•ÍšñGÒ„]سå5V;2£Ýä¿LÕm¾yþýFÌ>•ÎFÐy¢šSÐñ±ŽK acC;‰=k)¶›"¾ÈEÁÔësR§²oNSÆPÂ'ÂíóüOme[<鬫§µ2 ¬\Ô«`‘¶Œ…¼ÍÁÃ6˜.7ɤ7‘¸‡Ãùh!Bê6Aö¡—¥°i;‘¬*¾êŸÍ›×4©â¶ªõ!G¼˜ Ò(š.‰?–Þ¼ÊAÑ£m4©vÝHªóKœÁ‘3aˆ€!I‚Sw5é$)í¸¸Ê=Nv…÷" Új ýYàM#úu¶›.Cžv]%}C OZíÍbOž+¼UgÃûÓJ,½k ž>Â^–r<¼rllrꥻˠÜ"Ù¾\??cœ×ÊÍdKû¢6Qì>±å~Üô){ºš‚s AEEËiƒ¢nQ˜/)EFø^¾‚“¸‚ÐàÆ/ciQ“í6ú€m3[¿þHÙ!YkpËðâmHáh|Q(‡Å@¿†@™o°c8­kBP÷X¸ô;OUÓ]“ðúhE¼ÁÆá›CjÖ’¤žLÐ$˘ŽP¬¾]H¡1ä f½»Ë€KÉGùÛoÖ¼jÕZÞU·]Üb ¬îpß`û¾Gž™“\=Zé¡ú€A—é‹Vݸžüúû"Ek hC è^±â¾únãøVÓñF”°ÓÈEVê{Ó=*ÖVYéfNKC5ù&ël),‹¾S“´¢H0Áý*7ù1ƒxéhÃz Û—ÄP™¨ÎÕ=ì—hÕ ­S‘ÃÒf?•+ƒsÚ O"-þâGe},Ô^´`>^g“X¼Ö?uflNÅ+©µ,UYáµ9{ª,?/& ñô.6ÏÑkŒj»Hg‘‚¨àÓŸõßx‘¬lZ—'ƒ ~SGݪ˜jç›:uÚÃa‡ó¹=.õñ‰+íåK5"×1#OÜk®TpéòȪ.0êFmQ‚éËW¡·8ãÒMíu{²ÊN…(au ½< ˆÛž–“æù‚!ù\àÉÀ}5a”Âgñ[qž (ЉÎÅ•uv)æËÛw©ÕÉ®¥ãF½nØ»ÒûU‚¼WŸšÐPlí_\4Ÿ Ñ033·»Z§…ßÀìö{–—<¾}­HMMjºÇíχ¹&"Od—\sc JP•ff‡«t–Oþ#…#—û£·X¢F~úïɉÜíX§¯,Úá½J%ÂesƒŸ;™ŽórÆF¼ƒÖѪ‘ÖeÔ9Ç×'ŒÏVGœÙÕ ×-9­S‰´é àz](Y›•GiI®·?'a@•N¿‹™›hÎÒÖpGAz„±V,ŠEÏ(–ûR\ñŽPs]¿­†®-ÕìÕ~?4-eˆô÷¬×Ú!#P¼¸çrõ=†œ®¥šÉ8~?ƒ(g‘+“¨èôÛUøœ3ÙJpæ$ÉÊlíFõœ·üBε£–‹Ë…µ.%Љ5Éw£ç/Ôâ¢ìxu~m«ð¥ÊüUØ©ÉâÔåvªüï8÷ææi—‹ìÂZu8Bô·gŒm$12+%´›ßq®"73~]넼¾“·Æ³VÑaùwñA+´ûaªkö’,¢ñgÊœ5P¹¯€»¸·ƒ˜Û{„Ë.½Œ — váèp%=\Œõ67À’&Ê-6‘†òkï¶ÐËé"×ô 3(©ëŠ‘[Ź)·qL¡Æ[.&ãha†Öo§õY,wuúRË«{ýâ'>h£OŠA±ì¼A¯ _€“„ÍR_H!Ê·ÅR4‘$3ƒ‡ÕóШɠhÌ!–ÇóÜŠI'/ÑÛÅÚ¬ù2¯ã?¾êMޯ׌뷑”):¢ ^½¨!nôÞÃÖ%9YÁÅ6€¨VNÒM}Qv‚–lâ¨Û6!ù6`_ðæPðPg·’þ/aëßUß¾ úáC¦ˆ0W0TQÔŽ²k»X¾¿þZ@‰Uh™¹4$ÀÉÊ HY«ù½ÎÔ½ˆSDï¦î'£'´&ÙêAi8áö6Îí®eÈØÛ…!‰| “wÌú-^H¤ŸR_ù¸`8)n×´¸jBÒõû'¦ÓõÕ•’¶žïL' =uìiÄü6üÈ— uWZ´•f©É¤k†CX­^À–Á©¶·9Z~É…À¾•úâÃ/‹TÅfæ@ªëlÜÈn§’Às <‰=(5õ·ÅÌh‹‰Œ=ó'=ŸŒT±´8æ¡Þòº®¡ÿØ#Û†_›¡¤p’`QÞi0QT¾ð £l«åÛ§ü¬rKuí‰YgHÞ`2dákõü’®õƒAÕƒ5ÑÕyÌ’4á¼}hÅ—m„kÊVBs!Ú›½AEÔåwŸ©\Q]caÑ$ºyæ¨;œx59hA[«ÙFT ¦{³{†óƒºùoœm‘XZ3–âÆ×è7§ ¨ž:]Ì^eó±{îq€‚!û`À°Š‚U¡9´{Òåá@HO èD2F8*§v¿ç‘$a…“Ž›‡9N°{ÎsÇì'wœ¿=ÁŸ#÷þL"v¥0¯3±A;œÎô\Kd(¾ÞÊ~ÖÜM.¡€ä.6íÏU?µ+q¶•… ÒEz¦~-ž“ÔX†É‘iåW¨ÐÉßò†aºÊ¶³PÓ6f ÙøG ˜×ß £¬_⋨(C Â+J>R˜=¾c¹I„ée=Ñjêú›+eð9(ÄåãDH6ÒÀ>£“ÜèÁ„ƒñ÷f•AÇf wYíüeÌJ¢É²ݹ¹ËÜC)PQ_¸Ôh±B øŠÓFŽÓ ÿêʲ¨Xáó²®Ü°I™4Ò ¶£fƒèíL6\9o9j] ¬{ãóD—MúÂaófæa',;uNåã70ö¡4X!™üœûþLO’îËqÙºˆ¤m,ª%,„J<<õM%­3÷EÐ'Ö6‰Îqj—–¨;©ûÌ±Ùøõ ¼´W&˜.h?£VÎ‡å„øFƒËDÓé ¼ÍÃK²‡Óí„F«„Ÿ ‚,øH!{ìïD Üð"«ÔâèÿЇîˆ÷ „Uþ>óaªÇÜÞ…è_úú%CèŒÔRHãÉ8ÿ÷—½.CÝvÖQUxw{Öú½RmãE;ÙÕŠ^Þ㩟phxE)“§Þ\Û~¯ â5¬$ÈûÉv4(L!QkYÓô½€BPÿh}X§QEZÌæ–žØ¯û I ¶#‚G£ß`«Á7¤½:)DÀåpCÕÜ%ÂÞ)8“²”À—c(tÉkN˜úUp ?S;$¤‘¹Ç­ý–ߨ²ÛŸßx¦n—ìÆ P^ƒ,¢o]𕡍4W§UdSµkCâ_Ç™ª¿-lG°1Å+»gSÿbêÿ¶Äo޼2U^XRRw/gÑ>¤²?ɨyL:ϘV娹¸eñšCøÁ¦Ýîç cU k‹Ó&z®~Mü*›Œy!ˆWb›t?õ¼qä©ÛˆwÒÛ/*%bµÔùVÁQ°›å¹µ |°–eìæ;ÏÚ<E­tŠJÄä¦LM~§D›CF ‹è󾁾ÎÚ¨ÇÔÚó{¶ñÞ½çÆK_kS”úŠs=AÇ"A£R –%=k¶åfià¬/w,4·Ç< £gQÐ X¦ý¹©SÐù‰ZDZ†1¬gÕõgöÒî´‚É$Œ¶Êæ;U¡Ö5Ý&àD²hFM‹t­Õ4;…s½ÐÀàTY³—GägøœŽ[; ½ ¨‚æIÒ qø?À­ãÖŒsŒP¦ËMM–þz=i˜…n§¦^\Å0J…y­ åAƒ9*ê%¹²Ù0ÙUQ|=’Od¬5ï…jìåc9 Î_ªî±Ó-*'æ3‘êÜœ-2Pc[¢ó/]Ucç‚LŸU°WåY|†Qf5‘Çlr"Ÿ;Úƒy\»¦LÁ´ÙY†S>L#HÀ}:‹ÂU€U©ó»dst¾šÔnSe.âj©Û'g.6…~VTe¥­Ê=fzP@Z’õ€¤n—c4ã–@°?ñJ#Eø{¸G•ÁÒë˜<–èÔ–:gÑ£þ"¾;à· ÑJ»ÙîÊ1RÓ¿f´Ö«F N¤ý Tjó&@0¤†¸¨aÔ¹Ó_–6÷Û¥$SBcùï4Ï8=hÕq6W;µª ‡aNïÒèFÜ*–Ä>ÿ[ñ‹™ìò’jŠU:}Ÿ{ÿÒ5o† endstream endobj 1296 0 obj << /Length1 1608 /Length2 9939 /Length3 0 /Length 10770 /Filter /FlateDecode >> stream xÚ­teTœÝ’5Ü]Cãî4¸»;4M 5,¸,@wîî ×à,x°`ï{gæÎºßüš¹?žµžSUg×®ÚuЉN[KÆfV„¹xrñqóŠ4!Î6p= ‹:—, j x6 a21ɹƒž˜‹<Ð,0ÛäÁ ??€OTT“ sõu‡Ø;xX tØ888ÿiù+`ãûŸžç›{óó su»x>Cü¯/êÁO0Àä´´MT4•¬Jš%° ØhÃm @»x€Ùv0wôæb ù«4îg,àá Až¯}@`׿\œW°»3ÄÃãùñØ»]<Ÿ{à @\@P¸í_žív°¿ ¹ºÃž#œŸ}Ï`Ú0O;ÄÕðœU[^ñ<=€žåö€<»0»çH[þWIûžaž½ž@ˆ‹ÀìãùW.0Àâá ú>ç~su‡üMîq±ÿ'N€;Øèn {x<Ã}ÈÉ實 ‹þí»Øþ+ógyþæÍ£­¯¡elÊñ¯Ûôï(ígÕ=õ}]Ÿ‰ýG0Ûÿ:ü…!+ ó¼áâp ðŠÎ¥``ëÉ#XanáØ á±,ÆÉ<4Š{s2£¾†b*Ìk¸³1¢£kYtJ3Ö&àŽ~rÍÌà•LÌxåŠJ¯M$iǯG ¬Î?8dNÙ»¾béú:8Ðuö¢g›Š#+ƒIH˜v@—êékíþ»ôàÍU)ö9%|”Ôû •û¶Š«›eG†JšÆáØ"CR`-Ô¸8#·K¹¯Ê?FScO"j ëâ¬l™7zëÉR”Þ•Øð´‡ˆÐ}¸N}]otލg¢BUê—ˆ¿–¶u\G¶÷wÇ|šdÊJ tìçÏ`ػǢö°70®T¢¸o}cž{rãü¨‘u*³0šxÅ…'ÿnáôéF´©±µËe¨y*ém_#4¶ùF`|–âÙD3ZÑ×/¨Ý^B-¬¤÷uF•=c À^únPwVºfCáãÌÛD/øõÔ†BΔ=6ŸpÑìHøí @wIð­þ¸Ú§Ïg⡦aÌ}ZÍ1$¬êéqòwß}á½æþEöIiDš®'8-©d`˜(rgÄtçüüHòV¦¿•ÝŽçÒœÅ$7)·f¨æÏ Äì"xN¢‡A%Ûo -aÑK&Øßðkß80Ätá»F¢‰GÚ놪×CàåmfÎÜ×êZÄ( Ù5R‹aF…µ6ÊÃæÓ7ï«KÅ!¥(f˜ËLõ’×ûX!x¬¼þƒWO<9Ž1·q­êՓ‹h&¯Ë{¾µ¨,PËò(RÊfI7 ¡ñ#Ú/dÿI}w[Þ‡OË`Òy5xN2`l)0ŒZúÝW¢w=É Üoäa˜Ez/þH÷ wê D¾Cm'˜ ¼Øzçs¹š¡öˆ!V7B‚Ë®0dM5UÈWBáB”HŽèÌEÙ…6×KÁwÛL­ÓùbªbÛ¦<*s39'äåJMBFä÷À±õÎEÁ‘ Ù˜Wý²©dcí;'j‡×WšGÑ ”)9®#zé&~eJçb…¸)%ÞäTÖ0’"ÝÕ°Â)¢ìŒH‘ý„.=¥4øœäÇ;z„tjpyw Š “wßq}+ï<\”-+ɽ_%·7¥IzÅÕGå;ÜÀÉ/:z8³}9ø&ª,”‘ü‹˜L¸¹š{“è² }K¹Jyæjµ]tcAÎkŸÌ—ÏÎÛ…×âúù"Ò¼saÂQ¡Ç3&í²ý|u¾óƒ1(s_}•á*WÔ×Vió†ÚcU“e*™•hs_×3±pQV;Ó›{hÒÖ8äz{Z’Ö<Å‹HëS(zcuÙ[oÈ.QO®™éXo%týkãöý?UU(ìûl¥ñ~4óº1½ˆôԯԪ׭¥¯ÇÇŽu—Y…T°HDÚ7À’nOÌEÄc³¯\‰XQÉfJ‰ÝŽ”¬°¯™‹w'÷&CíÁÛè³xIÇú[I[ª‰·ß*Œ“%ÁtM8ÍYý-ƒ²Æ\µPJƒÊä(^ Ó,Þ(kÙììs;H³á÷/¦ïxõ×~ÃB¯ŒdjßÐ>hÞ­?0Œm­¦Žj÷Bní/Žš(µÆu/¸³6S[ Ò K=›ÃºÆP¡"Ömb“ŒO¡þêèç)ú³>èP–ÿ}àúÊÌØrǺïéÀ°"'¬c³QÎã'­†He}Ìl¥ø»ÉG E0°°ýŸôPï*_]I¬G].õnâDšÕ»™ðÊŠÔ‰3pû YiÁ{3ÞfrþkqUjo¥¨&òdûÍËPR‚©BÃë~àŸ½¾s޽÷˜B¦/g}  t6Žt•¡æ—ô­zP- ÕÀsn—ŸŽ?2ÃéÔôÍ_óŒ!®¾”¼ÓV E iq¿õÙðV4)>gÂÏ»sNÖœyÿš2ÕcåŠi¤K…ŽPç’‡Ó’Ⲇ¨ý 4Ð8¸ú–F²ÎîàwR¥÷–”Ô¤BHŽÙDpf8ŒŽ^ &e?Ìêáyý}V+š’~»‰Ê K±ˆjX7|*ÐЕþy›Q€ºŠ€%›òûþl>áaÝÍH‚?Ílwóó¡ÈÊ¥wW1U ˜{­¡?ŸôC/FaÛôICâj9ÇŒÇùðw»|ñö“ßyºœc¿ò!ñC8Ú{—ÅP —r`ñÂÌ•†="“°ß©Û›cVZÝGöøfß}n0ZÑÙÁþÒµ•ýÙnÛÛ6.;k1Iå¾õ¹oð´Âý•$hÆQxJð­T¶Þ…cs³TV°†5í­íÒŽ0W»ucY+ Æ­…°å•3*t¼a6Y0³ñÐÆm”ÁÓ ¹ ž»K$ÓÕëñ[’›œ'ËRC™©–¡×’A•õ?Î$°.Ül¥¤X<äñ…â1Æ'fÿÒÕ.ª½½Gønôƒúq†ŒM´bôîÉMdÎ?Îl Éíð­X¼ñtæ¹ãDc"EýtÝÔ’ˆ¢†Í9C1 ‚’ÿý2Êk+á0ëíAS¾-ø>òQÖÓ/F®…µ™®B=Rv®º“ oÔ½¾þüÜã;¥pßÛ[¦,e@ƃÅû)BjÊð‡*¥áH€’•UUiZC'À±pØøâh£ r¨«MĨly™ r|[^¯ÙK;Ó*”¯fÈûí6þË=‹r'ØWjKñ¥¶÷Š•? *Î%œ’ 'ÌfèÕgwøTëcüCÑ»ÝKtøÊo—=™Yºü§^çÖns\Œ–DÉ î™dG²~v K=÷ï=çïÆˆ7 ÄeÓïð„§íA Á` ƒïí ½.| ÅH´<'ÅzåÑ—ŸßWmñ‡©ýTçúæNýª•ùƒ‹6mó—ÙÚ"žN#܃¯®_ ©¨vd;!þ¶póº0$Þ]ê¿ÿɃ ,· Ì*63Òh"X`acÚÖÊs$[÷z¿÷uóUR±Îvj%3X¬o²¸öZõSÿE«‰¶ä ³…cuv¯ÚW%¬5²ô5ö;”YEîÌ-µ{…r¹õ-ߟE†.Û›Hî:ðĵ‘eH gr†û¯NèÃôi–áD®ú²=¡÷ôû©±ÁúÈÅa«¡ïA·Úìɯæ¿+þ<@éġ°ÛDõ8“S¨%é¬Rak—ƒæzH94("™Èˆõ@EȨ÷ŠNqœ ¥ËŒPô‡å$£~ƒgÊÙ”&`8Y_Ñß×V¾\büö5»€ÝÝõÔÅÓ„½ŽÃsïj£Af[‰ëÀ°~…‡zÙ#ÉU›Ç çÀÂéÄÄfD¤…34÷À] Ùx©òX•(}¸¾ŸâV·»Še‡ŠÆ+ˆüÚ縸°è¤Nš“]!´÷§DxFþ0u»)Ã+Ä*ƒAÎ+ê1™îÇúQdŸiå]!L²·Ý âNÂZGú´}eÞ™›‹õHä9aÑ6Tà=£Š‰e¶ÞAdµÐ—¡¢©qÜ@À¢H€ÏYƒà„¨5¨bkO)QÝè§Ã…‰Zši ›Üøn™R[\q4×E€m/ãÄ¢þqæÃ ÛñE9þ)|=§&ÅõŽ«~Ç<.£Ù„JJiÝ®ÒA‚Tc½¼š.NÞ~ésZ‡ÌŠâ' 8§ØcÒ8]—[éSco&˜ò·P/`;ÒfQU½Ãƒþ^÷ºÇH'^°ï»ÈÕë‚WNðsÃÓ%ÔVáàvΆãðg[40 O¾aæ®fÓ†M=”ŸCÎè—rjÀ éßʉ;Œ†¯®îšäµ K#hùa4ž ƒÂrdîÀ/0pe×4Ìïd®±Õ{eÛ•Õ )Hç‡7ªIû‹:F02ºK 3Pc®ÖðF ÚcÃ[ˆLúèvßëÑlÚñ‡ šx^ [™eˆp,9Ñ~ YÒÚ¹kØ!ðØÿÍ>[‚e¾þ±õ‚cçëÐýyºßýä—„/ãÇÉ”×_z Ñ´z 6ú µ½HìËòxH?ßFõü˜Ð%G{Ý‹Kÿ;“œLì¡âM¿›íóꙢÒƯl“T”¦L÷âYhqÞ“Ž—¨¼O±£2&î©'D¢;ÖâÉIZ®ò{…áŒ{ZèE’‘jeFǹzrç_mf?fžæ¢ÑZ º²hGG3-#À.jšƒŸæR€7TUñöhÐü{¡ýSßådNâ×ÓÔê¼ÊÜÃK+ÊVÝÅXˆÎej…Jø~D1˜ |­ôÄ=oûN‘$k¤ªpaóþǓ&Ãb<è%æ¢âø´›£Ø â²"“ÞO‘¤Tü Šœ.GŸ2-ø^ºCùÆ·Îdgì’4¾Ôíùu›"ïš…é±Y„ÚIp}ÊÔ—Šb”W8é> †v+öƒ¯MÃYå[ýIÇfM ¦Ñ+µ³ÖY=0‡®V5Ñã´¡g( Ø˜øF§­¼4 bÕ£©=å!ÜÔNLK}/ˆöRðEÝ6ɾ|› ë-¥þyVõ£·w@×ãÍHêXXø—£C:üTâ ¥Ì`OÌš„µ%¾*ú»„…G_“cî6•ý´Ý“$=ó¬~_—̵äSó¼–‹žÊ³7&n1A§lÃR!"oGÛ¶ƒÏ k‹†<’KêO РF6£ümÞԉȉõÀåiF(õñûÆÜ_‚y5(èN>ZÊa„fàq|ÊšêE„¦sû‰Ã}óð´5ûã^áÃ4pŠož´ŽîÖ„²]5€äã⌨WoÎmG<?¨ñG«n_‰kÄŽ<æ3íª<¼XSJóí$“†hFÞpÄÊÈ`Ê ­è³×­-Å"ª=*Ûµmpjåâä¼e¯¡jø^;€Ûj#{ú5,¯?¤(ÏíÏ­é1óTìOuÿQ÷ØÅà&wrCú"ôèõc7ì,ç,J&ÆŠžõŸ|¹%¿TÎÎ@­¿ÓŠ(¬NxuhÌ##¡¶cjèÒaiVý-vE—~-Kf=›¢¨ Ì@» ]Æ/. GªXvgÖ×d+ùñzQøÙÍmïÎô6oÐåD7 󹂥Ô›[ñu˜W“‘1IÛ̇š^uu^Z¬”ëm±iObýí }Ö´½ˆ¢ñ…WVG= Rg…DÓó«Žù8Ͼ+ìITQ´ÒÉ\©ë#¹c²\ `zØKlÛkb—û<# Û¡jIæ»HÈIdÙ i;Hxµ NÊñ8ι^ÞbëYŽoçi‚û~`¶W£ ‰¥œ¾0#XY«£!+Bœ9 ü.‚¡Çã‘Æ^¾Ê.Ö óT­/QvÅÂÉL ç3)JE”u¥‘Š|ƒ%ò´g›Ýà…kÞ±8Ò[Ãý-Œã†e&?í|S_p¢„½J,*2£Ê,JPû¡¾ç;J¬Íé3i§1+ã”f6|Šy»JâLØlÉ}©!K û˜qqfèj—n2 Õ±ô r?IÊõ›å–…àe wIw?ñrkF¼>Ö<1¶±—Ë&NS)šÉ‘ìq }<ßijM&ÿsátêª0DZØW¼¤,>BÜ»2ýʽ#rÛÙ§ù«¡îËRÈ{-É? ædíùœ ›K1öÀÔí=½ì<îÃ'd±ß£k³¶„XsI­EÅ.±ƒðh;@Ô½ø¿^êdb@©j»ÆkD˜\ GeÞôu^çeªÎt €'émÓ‡øì_;¾¼S5‹ÉÀ.)Ö܉^Ço(ïz#ÝëÊÿóØ8H™T,MªIϳ0ý6‹ í´IMˆ»B)‹^UÂ]ŠÁŒpp¹;;6rEÀWÏ–üy!¢mêãu\ùgô”Cácø&Ëð;Ô&„ÁyÉ>èÍáì)*ËÚì"Î|Ëá¹]·+›ãÂñ$qja|‘«ärTzV­¢¹CüôÓéúV«ÈôÞ=štà&›‹Zέ£ ãn1·Ü_«s¼ Y¡Fø:IÁªqãžÎ3ýM½ÔÇc”÷»µe, bÎÁz4¦ “#,ÐIXÏ_:$ ¹liq}Z‘¡Àܲ¹,6üS9ÕUoÛö# }EšSaëj>ðÙRFðbðíF´óo5¼Ú‹xÓDf§Œ•tiEˆ³‘rAùó›ÚyÁsKÏø+©欟†¼êå^ˆ`å‡Nüfýa¸‘,•¸JËË(ޱCÚ¬.Ä¿‹K~ãº[¸Z¤n.¹È{ºù¦-½¶Ö`ˡà C*ÒÛ.EÓnŽ®vhÐ?tÑ òÞÉ^ŒÂíVÝ7Êõ¬?@6¬ŠtŸiàÁþIšã?è·,å?xÝÝÏq1Y\ì/cñ‚Q™F‹÷s-ƒ½Ǫe¥‡3©§²<ºÔKòô,C„’ÔF•„¶OEÁEŒÒ6ßépÚ=’.Ú~i™øÐ×õàL* ûÌð“ ¢ *”ñ¨Ö©í^)›Õ÷Ãb ¼>ê8"‘ï= ­dÒ áZƒXê[z…׿Ê鱇啬¸W†Ã.-výc»MÅCÙ­8Ÿt±8úèt冀¼{ÒjpÈú«^jzûó-È÷=j#¸é:¡[±«óÍ\䱦èí5i6ô¦ÊwõO´P?£Ç 8<ømÿ}ê‡}deiÆNìËV æ¡~åy}C“¦±>:*.¾!Ò/”$céfaŒnµ°|”­kD?³ 5Fe’ x&ŸÇâFvï«HBWg)(«¯ýn:² (¸L•£÷”Œ¹µ^˜…×ö醟“NZ  WÑW|9}°YáЩ[þvwÍÁYä)",/Žvía¹ºìñ1^¯úøÙÝùäŠÊ…·ÂÌÂc5ŸL䊃hÄ-D»1w_רjWW3{ôdöbÈô»5󵿡ÃO­®ß ¼ìßù"ߤÐçá=¦Òû‰o‘ÁûÞìÊš($Ü>Aäúð²Šß†WY/´Ñ®D»>Û;³ÔÅ»7(ï5Zûzö‹ÿ^fY¡ Ã^ûõÚÎn,eH?˜œNÝ’jþV~)d<;ÀTlžæû,fgôå¢.:ןÊ.Ñ©d™§\%šm‰òRžÞ´¹ìÓj\^[ïœW”¦nˆ\”vbÌ¿R¥~¬úY\Šj1~NB>Ì®t¥Sn}¨ ÅÕÈ}|›8ô1EOêQ÷2”`ßDÓ¨Œ½9òeýŒ?UìØài_¥M¬ßPRÊ;û½ —F½LéÅ/kÙ·Ý_fŠWBµnäD½Ý‚_SÌJ¾u*ædç–ûD2›Aßµ¦Õesqž%äö¼Å=Ì#˸ڑôï.Dú_•y&§Ä`Œe証l¬FÄäà?öäÍÅò`»¥}9ïDí/=’Œ زöáŸö“6?C ±—ÑÕwèÕº’–$PøPÐ#ïЄxL°ˆâ°‘˜ê|û Ç6÷áxç:ІŠW-D}“¡}ÙºA’ÀXþ¡4oûûHºë3ëüðFÂÌ6õ2dÃg€ÃªÞ±ÞúdסE,=O3*±²ýZ£é»Âªš+Óyºaì³d–v¿>¿ÌÈ4°Y3ÊÉ~Ïyð²xBülu”rûáŠÝ<év¸Æ<ÇÇÎ)鬺›rµ2;ªk×½S¬ÿLDÓéò|©¡¬älkôzO÷i(ظ1îU*Ù«³¡•ø‰Cý)åhÛâ²áH½˜‰e}ÀX®ú e·Œ1ú…õÙâ쥂㕴qòhØÀ}U|%Ñì—-âÂæjqÐ>­ÎˆŒ$¾ÏÅÂݦVõ½²RÆ+ròý¹:L›ìâ“ØßšÀ—¦×å°œ—ËÙ¢£ÇÖ‰BêM]‰È¤º_ÌE My9GÀß“ m¿„-R?‘Qn8âßó ìó¸UgtT&nÛÚ‰]çjh+iaùÆaýLs—»—»Æ‘/T¡H§£&šàIlMEÐ`R:^%=¨Àg\ËÛÞÀ*ÅDöùúþ®ç“—óÛL>Ë¢åþUv ŸSú©¬YïY]·wÞäÖ{ó¦PEò½µgNoÓ7S_*S¿ y„JFWÊÄbQÛ]*g¼ó ‰6ë§ÓG-q7cê@šÌŽŽöÌézA‚m˜“ñ8›]ÐÛ× }5n•@¢¿ÝˆK5Þðu/–v»ù}ZCçûB9C"¾Þ…ã7àf:Mb }ÐÑŸë5Ðjg—µ8ˆ²öZûyý̤ç§®oÔêªyj@ú[ú¹U|øúA¸kÅw«—¿Jã«"ü²~ñ†Yn}R¹Y_}üy¨×­w¨EŽBäÅÑ"ìôoµIÿ™ ¼æŒÔ[»RÙ+¯¤/äXyšM¯ë0ë#—w…}‹M(Pè]á7Wb—diŸõ[ðU ½ÚøÈœõ¡Eêbà’Îæ(¾Þ‰qb`±ŒŽÛò³Î÷4 ~¡ÂvæD]³ü1Hƒ!¦kR¤Îo«øQ,PÅõ¡¼Åˆ¢Y¤ÿ˜ûëÍÕ«ØX´ –€;À ‡Jluœ5¶§Q«Ë"–ʞ΂’:AY’r­iåîü0ۓà —νÇJ ¼X¨¯Ûê'w>Ë|wÚ¦B |ú»9­“wÊÛVÉ 7Ê5cËÇpOKwìüý÷({y¡CòUÛD¬a-ŸÃ,·Z¶ˆ~{.ÍÜ{ãé'?¹âD?¥" E-;;ÑñkKÅ iýÜ™±2Zäè þÝéåµ4V%…,~…N~¹ŽÔ€`_Ö,¿‘sGVðùP* Ûë] 7½I/*AaŠªp>›òóÆ“ÀûCR~‹Mñö¸•*%†;2siåøM÷aadé ï~Þø¯ë¡á±¾°¯q‘úgB^75ÙîzeÀDv ÝÒüÐíŠ#mÎì,“Ì€«Ã/IŽÑÃ|ߨӺϽ½ÈéÖB „É"FZ¢‹T·£Ã§=§Ðåa“6A‘‡ÖAg„õT …xÒ•†1jw9A\ƒ~{jšN=àý¦ÕNe†2V ñíOÄq§²§ woMý̸wþx\ÁŠ7{´Âh$–Nˆ†æ§d‚òŒJuQÍk‹LwâVìtBi _CÄ=ô½;òú Dòì¸DYWOß^Ñ.Çwò™Òƒ¸¦^T¡1‡·7l†÷V‰ß÷¤>‰$ýú(nV2¤Ðf:­™U4êÇ}¿ð‚⼜˜W°#«a„ë6Kåa‹ÞDåÀ*¬Kd,’åDÑŸ–ñ:¶Ò‡T/nùKôÆçÂ<ŽGAK¦«ÄÆ@Ù—(aÀeÄÑS–-¿jXŠ=Â4Ôe“>¸ÖÍ,¨>~%Æë\ó:ý-Ë_oCŽ\}œE®ë%C3€í­pB‚צ3XÌaSÃ-––¬É€Ìp¢B½¦ð˲Âîº+ÒÇ^ÛÒx_“§Ía¬h'ÇÝÀ¦Bâ?©SCµo[^¼Tôðlò=’ã79 ÚæH~­¬(rkõ7̉o—¨›–KÌSHQJ€v¾Ó|ßbÚ¡[œÇü08À¶h9ÙrÿÇÉâÌ@Ž_"¯NI# Ñ×s ^¢u\Ê ŸÀ„Žko¯3¼'ºªæ¨£®ß§àŽ€”ÌÚâ)œÆò‰@ÊD2ŠF¶Ë“Þ xúÉ™òžoàôHÿk½íaÆœV2sŸ®×SKúêï¡Ü™r!¸¿·°íwÖ6þã‰#fçãJ‘É-ò˜‡uÏ,I6rÖ»,>÷6ú !ÛtÕó¡ÙïäÈŒ%NëѼ]´6ýÈ‹¼Üs§åTú9©~Œ:£tÂ>š¨‘Ñ0•5¿•i­¤7É@4ï§½¢Nb§…]«†7£Ùsδoˆ5¾6*û1ð0 ¿Ê|ˆbG/a­—ð2Ëë\Ù&êŠíTůÄ.Ó†Ÿs`›viê4ŸÊµ›ýF9ú¹¼*yã_—ý²E¢©ÑáŽG0í€7§éÝÆ@ è&F-â2L8O•udkGÝ«¶Èg¯4jÜ÷ÚøÉ;)‹ ‰‘]· pþšr0ää»°Þ2ÉÍBŒß>aüƒî»Xÿ§h÷»)6™æíûÆF"Áw©<¾Yê«ÁCsÂu‘ëò‹«ÕÿݹÉ鸾0^pêS¼<‡;,3HTòt”ŒæÔëJŠ«3©°µE?2x?ùã[Ä4Gýa‡Íz‹¨ÛOçM›~ˆ&{CĨҙ9¢«¼²Žw×]k#¯¢rÄgS“ö×òmç^BGDé•\ù1PwzÍ}k»æT­ÈÈzïë?8ƊجN°s?ÒŸ>¼À'¨íÖ>³ú‰Lôa2Û|©k#¼ËNù^­òƺx‹ÛÍ©X­l<ÓåÞñ"n=8“rlnκ”ÉÈzf”9`ÆÖZ'Ñé^æØøñxè›ÛïûW¢ýDßÏCñ䭮ޛXÉÝ4YŠ´t¼È­"aÒ3Åœ Þ¥3\ÙN&Ai]¼ø$ì¦tdöVþB_I¸ŠÌpÛïD ùLCžƒÃö¸úUõÜîÆz²*’,éF¨ª°ëÇ^¸L¨ú©T刟´i 5®éyÑNíTÝ'”‘ðÍ%nn²oHÍX9Ú<œ)èJx¹ôEdž{Ò’ËåQ"‡2{}P°êÂØkÜߤr]?ð=T¯Z}c#?ÍóÈ`çÿyä˜ñR±.TåÓÇÈ}I3ú©LLÀ)WR‡PœÄy[~!*€äÖ›·b{µRgoƒÕ½¯ð'CnrgíŒÕƒ]F»b?ýŽ€+_\÷?Arº“RzyÁÓüŸx‡C:D9+sÅKà›5+^<0N Ã2aøiM­O$åÖ…ÄSU¡kâ^Óäû¾ß–9BI©´®'x£Ã:@ÕèQ>È'KOVé íšß‚Kgä0©Þƒyê6Õ‚†ôúu­iSºº e¿6Ò¯î`ø)²ÇHc5uöFXøÆ—ŒR·GîI¤fï³Sò&”9¹¡¦ 7ŠÂ¹ÈXœ8é(XåâÆµ•þf\?¾š®>d§ÅníÌô¾ÝùC\,¤^t2óò ®w£8YùAß ÍÄÿŒþwàvp“{°†J§ÜÀ#ÛfFÓ±,èmÏ\ÙL‰ŠJZ~UÃÄý—…›|¯ÿ0d27o"Ä“ 3¯ù CÍe§wB]–D¹‹:œ âÖFº«¶uLÑvánn¤kà|/_ ¦žEØ«4I¿€ÿDUxùÙÄ}síMIé]*séÑ€¾ïͳÑz¿³$ÓQèƒ!~Èlk2¬¸'}LXa\ëÄå¹jK7ßHýn›Œ×:Ë=b,ÚP`¤Ñj_%„ôýÒ=ÆË_ADZ襮L%œØãá+Ø70ç†6ù¢}‹A<þ]²r=+?7AæÐä›4BZ”-ÜýyË\ÂEêÞm¼¬š¶3øà8ZðÓêíÔ–®?rLBeņ$ä¬6‡W]’$±%<ìÃ%“HN(ÙÀñ„&GrB&Û: àczr(r¯a”—,ÉËtRc´¥œŽæþtã¾Ø›GÏsÝLfLü\"ý 'u°/d·w¢óª±Hti{½ÞÞM¢mãÿ˜VÍ&0úä <Œ¤¾¦ûÏâªúDãÁÓd×§@®Mƒžô7jõ!ÉrðF |«BŽ^g¼K#§k¥HÞäß%ùùB·‹%š 8g´Á(nç0 3Ú )²Ë™Þ;ß ZÎС9¹©Q—×’=\ÈâDÛó  ±YêEVI¿Á5X½¥ Òz”xìH£€çžo×/äTÍ7üEÓ¡}ê©-àÆW.Ö׳ðaåêiÀ³‹=5ÉM²OÉöI‹LR/úpÕ4hoî Räuµ†ÄúÛ#N'måqŒÛÑo1ù‹~òrJbu4¤Ú'#9œÅR„ã*å’®—¸ÊÖ†'„ÒÝág{"÷§Í ¬!Ç¿3ÚÈœjñÍKúlG`M³Š‡«1FЉ‘oW NôN©?lþ`TlmrÙ½ÅÍÁ) âÙbÈž_óhæíþ$6Zš“IZ:‚BòÆX`À¿aʪƒÄúÕO°Vc–ØlÙÑéÔd1?꡵Z›dëe—çËeol‡œẼn_ûyÐ5Îç×€Àe…Ñ0­é¼XÇffâÁ?…œÖi‹ûï¶Jªçî~ywÆ;Ýx¢eÛbZµ#ýïLJ:©ž¼§CÑhW=tnÔ˜ò©Æ2¥Ç9Wí ƒBý·ÂùUwp¹ëð%0E®éúòh&nå¿«0{ ž’w™ÆéMwS”F¤DÔ\ô˜¿è§BMÈ™xT6T,ç'WY•2T÷õÄE¼Ôì+zÝÙ•ýÿ„tô endstream endobj 1298 0 obj << /Length1 1614 /Length2 22248 /Length3 0 /Length 23087 /Filter /FlateDecode >> stream xÚ¬¶UT][´%Š ¸wwwww‚mtãîîîÜÝÝÝ-¸»;×¼œsëÖ­v«¾êÕÇjmÍ!}ô1úœs-2"E:![# ¸­=#7@MYCÑ24±°•¥¶™þšÙàÉÈD€†N¶6¢†N@n€Ð 403˜¸¸¸àÉ"¶vîfæNÊ¿T44´ÿeù'`äþŸž¿™Žf6ò¿/.@­5ÐÆé/Äÿu¢ p2L-@@€ˆ‚¢–”¼€RB^ ´:‚ŠÎF c€¬…1ÐÆH0µu€þc0¶µ1±ø§5Gú¿XBŽC€£ÐØâoÐÍh÷‹`t°¶ptüû°p˜9Ú8ý“-ÀÂÆälò¿vSÛ Ù9Øþ°þëû ¦hëèähì`açø[UQTü?x:™:ýSÛÑâ¯`kú7ÒÄÖØùŸ–þõý…ùëu2´°q8Ýœþ©e˜X8Ú ÝÿÖþ fç`ñ/ gG ³ÿb@ pš:˜€€ŽŽaþbÿ3ÿêð¿tohgrÿ7ÛöߨÿÉÁÂÉ2¥‡gbþ[ÓØéom3 x†¶Š”©-€‰ñ?ì&Îvÿés:ü; Êö Õ_†&¶6 w€ ОAÞÖéoIåÿÊôÿïDþ ñÿÿŸÈûÿOÜÿ®Ñÿrˆÿÿžçÿ-î ÉZÿMüçüsÉüoÁ†Ö ÷ÿSøÔþÇÿŠ”“áßAÙ˜ýƒ‘žñ?ŒŽân@E 'cs€©!èï”þµ«Ù˜@6À¿jþ;H#ãó©š[[Ùü3v¶ÿpmLþ;õ¿ýKœAYHFHDšæ¿QÿSü«¼“ª»Ý_jÿ£9[“ÿ¹øEXØÖ àIÇÄÆ  cadú{àþòábaóþ?Tüˆé¿Ör†Nn¿mÿÍü§ùÿñü×Jï¿ÁˆÙÛšü³WTœ mLþn¯ÿiøÇmììàðWÕOüߦÿsýïFÝ€Æð«K¶Æê&yÍaƒ~ bú¹F”çõ‚ì6”6;£úá²~ñ; Át‹Ã—ë'*?—|? ÒG;$ãÔúXÌN”F0´Ú‚³sòÄ“§GŠÁ±‘ᡞ[è¾<šìX82ClŸä3¢$'w‡ûãOèW¶ø28y|7ÎIš-e… ÃÙSÈœ-?Ãl >¢jþ“2¼ßf>Òé}Î_‡ðŽ”V ˜øó#Ø~GÐÍï;„" ?J 6UóêŒÏ‡ÏB¸1Œü뤀G+©`<ÄINÛmëD¸rq) 34³I㌱^‘S¿´¾ð®õ÷ƒŸïÛ“•¿ÿ‘Ž~µí6h±U0ÂF  ÁóS¼ ÷Q}v†CÄü)£w ñ«˜’íSs‚nZ‡€­ð¨#¥’=)>@t“ƒn»3˜e|ÚÖŸo×IUkÔ9mEÖݸªO(2æ®$~=ÇŒ 5Ôh¿j(<ãy×kFXÏ™D/n‘—°ûŽ’ú8B‹²Œ*çgò…G(§ˆX}d¿ÞEáW`ŽWá¹Hõ`fcDŠúøN\SOåe|²åo äð§w•ç· 'M¢©~ý,×—^±s;qe'³(ZZYS4?7ƺÌD¶b¸³1Pâxþ¯æ°XvÝR1Å‘3 ËÄ›0v>ÚüL8ú;0óª`CѼ\- F˜Ï /?ÇrãC‚¾ìµ#K¼Çn|'T¹(ШâË{{Nk¶ê*6¶‰žÚÂc„*å@Ø„ÉÀz5xÚŽZò-;†ºø^‘%ê=Wà7OJ„œ„nö‘ïC½²‰¤\òž‡ò1íÄME"—ÍëÒ!NêuÚ-ˆ[GY¡è?¶ÆÏ©åôv9½Ë‰¼ˆ¬K·ëúËŠ´JÏ%>¹Gîë¯Â^Ä(ÝåN"ž°Óÿ§J߈™OñJÌö6¤ VD0UòŒÀ,W0ß= ߺ–‰Œ®ËGPyIVt`C\sH¿Ð˜ííñ¡ÚGv×q¥/·£ƒÞJTι5 #²X7ól‡™Æ:íB)[í4øÛÃô†DB%52kô2/ ¾°lBDHý7P£|8‚?Š\ãz¿ J‚N_޾œ×¾¹¬:glf!» NàÆ¤J~0ÿ{|ÑÙüõÏ^ÉuTdWKpÇÃ`Ë??¸ý„ˆçE‰±r´«BLƒÖŽ:Ž«ó\:öýÕ­–Ú²DØÈç@~¼`2μÝΞtHù tâ(¾8ߟ[ÊiQ,ðÎv1EníÇ,p»cj¹ÌÁ’…µW7ŒÍù7›1 9:KÖgåÆà¹ö{;†dQÑYz+½™4ÀÀè35„´{+óÌiÎ;RÙ+æua™…• þTPüvGçÀIr‘ŒlÎmõ¦Öùhg~ïl€cWGŧšÜ’>t1ÃàÎ[lÎÿ É¿0¾â\çÛ—æÞeÕ*Í·ò²,e§Æ·Œá:ïà-'ôý¬,oˆ„xþ[")~6†úÑ[ß/Éüþ» T€u×蟃YQNu•mß*ñkB8Fqêf,"CªqõÜò–64Óˆ|ÕhR~PF·L«b~X룼R·4ÅwÕpdÌv—E®¤Ì梱-î/­_%ιb½B xt.eÛX‰zºÉD¿B;t¼`PÞ¢­¢s¶·Öæ¸B§ÍN ˜Ä "ö‘ÁªÃ(XÜ!8QÕ?;8o¹‹ð Òèí­¤&°y´ïPÆÞË(M3m¾~ÍD¶XRàíú¦²5àæZG{4®ë#_~ˆR7~¨µ\K*ß9‰SökÄWS†ŠYƒ¸ÚUЏYôçkóª›ð¸ÎòGÄNæ£4J¢°¹ÆŸWÈú$ºßŒùxŸð<_ŽÖ²—*Êg»cáÆÙÖ¾Áަ^€VÉzWøÛ¦^Ûÿ®!r"d§´á€Á«{Àôè+Ž y$ã6ù;'At(bŠº-’ʬ=E9’û¬«¶°ý:ñ÷ÉW2ã̪Šz ›pl´Î(ÚhÄÎß·;ø#RÍxŠ$e‡ûÁ·Con¡GÒ–°0Sýž¾O¶¤lÞÀZ˜îoÄIöZŒÝä¬ëO¥J$~מO`µ’ÕæÔ~ÁÒËÔ Œ,ºâ!e›kÕi$äHÔ9¥Óµ. w3Âd!Þ5ßLÒüÂ8–Û”²’˜òh´ÎOg²Û´Â}“3^Zn ¨Ï &‘\ þ§Æßcp«!!—yExËAÒ~NjeÕ-I¬ #XŠÈ™™‚ÙhbÑ’‹Úµu)ãÿ Îýĉà^¢(Ó£S\§§ÁÀsñ+gµ†„mrC‘FÞkFé}“gKMZd•ôG¦ýë‡%L™o~Èí%Ê–¸žOô4˜‰Ñrã5¦„™âÙ¥ˆépùïú¯W*Ö6= ê?ïáÎDáÀ~–Q'…Å‹ *Ó'†5’3nÅ·¡ÁÒ?ÕˆVÉ\$䮂‰žÚw-TM>ب†N-âÉÌTÌŒàæå3Ö3çßtn~3'ÏÔè6+N¨» Æ+}ö¬½,JÔûÀ|èóWï[„c ,’åò|ô$F+'k÷[{à ñ.“gÁ1óÊ(ŒuüJEÞ{ÚÇã”h+úÖ‘•áòg€b–È·¦AˆŠ!Kò£F‰­ž…RÑfíNˆŒ­oþA(B 0Ì2ð®ºâOéúæÅ„míÖÜv̲‘hŠ?›¬Ô ¹,Pl'_ÃÆ0 ÿ~3ƒœN:a*‰zQ_Wcx§>!-RÓ·¯²Ÿ˜¦cBmg\2:f°Šœ‰§ÜçggÃ<îÔ3ýC5|¥=¹šJÕÝÖ&ª‚EØ ñ¨ÑúD!Òã`ìÀÔHÓcShŸ§âü\[CÀ£‚k9^bSÔþÞxÛцÀS{`Ëž›ÏbuE‰í­ðÖñ4B˜e†h)QN‹§£€ˆÝ[E‰òªYŽÍq÷;aËÌ¢¶mZbQ2P¢7@%§cEwïûKê!ËÔ]·?w6ÙâpŸí£•ôr|À|=[£: ±©\J·s»ð–€N|Ìì!²†¦—ŸjÕA]ÊV¶a¿aYg½+Ì©LôÇhõEzˆ”ˆàŸ ?Xš”ZM9„ÏØNߌR±ï¹éYÜ.]äÉYˆ'‘—àÜe}*äøI-MÞxòO°C…„%ÏUGÊÂ~¾ß á—2ât5¾1"~±:~Ô‡8ª÷nnmìý &5':†Ðø„ʉ…²½³PR͈%ðo·É1m¢_«&(]Ê]Né:¯LL–8þEÀŸúfQ–¸u$èaéNðÈ­gËŒÉJ]€">þ¢cêÂá˜Þ¥Géÿ„V[é Ï­îQ5ã±Ý^ëÕw|%ÿ”±¿?pˆ&³…ª?/úÔæì®ì_ç$p3œÖ_úïîâòñ&t4@ÕMmÅÚ$æc#‰A yBG°ñ»ŠrÍBvS»ê“zÑ æ‡o—r†Êº‹«NíMdþD©!™V(‚bÐkTaO–Qh]%¨¤ÙHà¶%=%ùŒ€(ÙÉ#YŠî§ÈoŒáy â;ëø9%cssºÖì<+ÊÝj­†¤Ý4·]{\<~æ•°3üÐ"sºqâA5ã™Mñ :å’î1é¤ÈàŸpþ íAAµ)ÿ[;"¿¯¡Œ.88»£¡ÿI]amY-…$aŒémBãš%C“¹JðˆÅ¶Ê’÷N4— X½Ù TÜìY¥-º(mþxüä¡çàùY†NEõg~€„XÉÒŒöÔbHo¦~âN±ntàcX›(vØ¥e®Ó©±åv™¸Ž†§‰Ž­íZh¦0úèªvž OÇ/e[ÐÉc*?_O¦Í?–5·ú‹Åú™z°|D^(.UB¨WW4+zÿ(˜Kò[vì0éÑ9: W~ó0jþµÀÃ…¬cc0ðO9¼å¡k$ušbdþËâ®ò²u¶Ê…’/ä±BÈé—-4(Æ…ùdmެè]ùŒÍì{¾ Il0‡¤>óåêIRhAæ97ÖõŠH¼Ñ3wÐÜ^SØwÍ+tòQ§Žs⬠áK \ÕX]ºTJuÖN„oH™&ñÑÈ*iÍXĪÐ䦱©äóM#el„yj–G©äôëqLê KÝT2Óg&- =xSÓ@ëûzô{~u]Ggæ:«¯4¨§É¼Â1â¸ÝÐ3‘ñ”›äFë_õJêvl”D¾ð4ݺMD™Çj¢­%ìïSÍ î5ì’WµOÚìaBæÀ§½S$kÌÄ–¾äщ·¾EöŽk-é©Dei/Pf‚Gm¿üvñ‚~aNxÄö‚öúE‹)xOSi§ò#„ñI_/Råz D­ÅܰöAI\ƒÞ1i‚Ë)˜4aAR)‹´Õ( Œxb[­øû‹,wÀ¬¥9.Eª³Ÿ Òãž ¡ªâ˜ªÞa¯˜³Â@¥L9*,Ékâ­y³¨“xÀ“]KDu.77˜¤¥á`;²zÏt½Rc·v·ðÜÎ7phhÆ)mh(‚?«M’ÎÅb|íøH\‡b¼*9uΗ‰ÈÅìõÜ}/2#ÎY7 œ”Ç+’m FŒ@vu–ЬɿG}G–®äÑÔz¨8R‰=3µ!uªQ©wj¾¬'¢Þ ZeO° ÚóVOõ´íËE%‰a– Xƒo¶ûúÍÜ£o¿ž1‰~ͧë PÄ,ä†l È´ÌeÞV¡ë&²}Øö|ÏL»¸’è%*Ó·rˆKìƒ&Bž¸zã@F$ƒRiÁ¾h‘ûM#°u´Ú!jñ¸Üˆv%Éš4_9{Nû=!?À,ŒÏ¬aßEij–V‘ICÖ;`ó‰â4†/ãç>…Ž«—Çå{Qbjö-ÌTŽë4‡eM°0ÙÿdÒSº®¬•͘FxvA£5®*‡_lÓÔåüù?P<›ù0sGOÊ0z«"ש†³Õ”oز-lnȇÜ:Gk‘’&0P°†6¿šïîô¦L!8ô¬î‘aeD;›|Y!žÞq–¡a¥© Û¼–Sð§Cÿç µ\æÅ½m4?tM4Váû<Ýßæü”Ü _ì]s'p“Z›ïZ/_fÅ~rsfT¯’âC£´âhET©2Óòë”aJÇRk$w2[óVº¦‘²RšÍAxâÆ¢PªcŒíYžÉþfÖ¯:3Ðly k®S¾|Hv9¹5æÕîwnœMèúe²ŠaXÈ>à•aë-¯nÆÕ l3”óþ£H+9€œv Õ øê( ¼‹uÛ Ý #>fu?²M Þ?áüy«Éé>©Tà™÷£\v‘ˆ‡'Ò.#4Ön­ò®xy2¡¾B:9^¯dÇk3 ;•5N¿]pBÕ˜j"]ժɮúÿë˜  „¥Í¼_‹\FÒ) AyÊù{çϲ“³`An[:s™ŽÈÃîŒ:u¦Ñ/I£äÈ Bh£Ôêû“/¿/'àÀyu>5‘ç‰B›DñÎmnø zðˆüêTêY¬ò• –ãĬž£.6ÂLüDÆy˱[àqøS¨‰?Ê¿®ÐIÜiU”%:3k炨[ºý4–^ÀÌû›ÕÂÙÿãt¨ÂÞP7Š–Ü3›IJm7»Òý<59ÿ}ÊOU ßòGᘠíuêé%ÜÞ€±S®²@Ê+?ì#˜\Ÿ\Wù‡*ŒñÒþ[1ŠwñRL£¦ÓBõ˜T÷%;guæYá_m*lúMR÷®º†ÈÀc™Ñèt1ÙkØü ¾báG ˆs7o)Œ"ÈÀé7¸aZÕa 06ÁxHT(%YH궆~Ø É—WYo„Ф âµa´fø þÉgŠì$@4Ö8\Ž)z‹î9¯<Íx çþ¢Z²ÒÙ“U 2Á(;{ ®»IX§Áf‚Vß|Ÿ­WÃÔ‡ÍO-ª‹Cç艒¼ u˜X-øÔ/dD¥=|GåÛIïiÄs´ð=×QËÄ…ˆsç!TIî_¸¿÷9Ô‹YC‹ˆŽºë‡ddx¤T’°Óݘç|KmËGu*Ë¢¶" x柋î|¤—䉷Cc0>v¨FYzyŠ=¢íî ™?üÚ… ¼ŸQ¥›£\% ܨÎ-ž1"Ô”<`~Oú1…`zzªyR™‚éC±}+ꎀᯣ'o®+Ô¸ÝÔï(2ÝöŒIû%=Ê6[ûƒ›¬ªÚ¹ƒ^°Füíâó…>(wÒ8&C[ð³hVéˆÅxP$õÖĘVE Îëò{%ùhsËG˜·Q¦o¼I:ükéut}Ô¨è1 l©ÏV}ma›ëš¹osL‹…s‰4ò#Éí7ï$;F´Ø“ 6+gƒ Üæm”Ð"G·Îm—V5\y•nx¹^6s˜„jBH÷Ö“åò­l#Ÿ.s點âM1¹|Üx܈öåâÍw¬A5^­©ÑBj‘AXÍßi³æ×Þ>…`ÙŠÔt²£ ®•W…*ã)Ph< çóŸÁ{4Àû“}|¶7G˜œäaÃpW®ª»ïè_@BDO‹Ì2ù‘õÍú"›ªýÝø`L0 Ôoåõ½‹ÒÍsH¬Õi_ßI£[7mt„6›¨ÐÀX7>5µíɲ©AßéʲëxQFúÏøQ2…Õw%ÑïrY'%¸ã€Üfö?W¨‹ $gwÒíòñ'ðÝ <´PèÑVWKùä­×)m¿Z«Ü'g•S¼g›²… { j¬ËS¿giÓ†¯Ñ»1ÍÌëºàÜËØï‘ßêB^²C5—K-bŽæWc}GŸ*~®ÏÅŒ'„r}˜þ½“ídæ d ŽÀèSX»ç£&VAךɩ€™þð ²:ûš^ çL È+†¾FÔUßÓî&’1àE7)ìœ.Búìnš°O‹ø` À>!¬¤ZÉ*ioJñ‰¡î9Ì»hâ¢@& ý«/h{5¾|£žIÑ÷sÈM öi KöÎ5ÌÍ–8wíƒaƒÛM¶,¢ ÓôD.RŠÞU*ùBÖË ŽšÅãÁI9Š6èŠÅ.ña´YAvâ m j;‰ä©•8o…½§õþ”üôcΞç.s+]ËïTb7÷™  ²Ùu-óÕUôO²Ñ†qï2¾¶ð˜>Â=„Ÿ£Í&Ž3@?<ŸœÊF LÛ#Ï2çP2/Æiÿ+ÕNúµÂ±É…UãFÙ%¢¶Jž±£ÉJ‡à¦’„^´Ô+,ÏàûÛ‘=Ê}/æoº£1R¾¼Ð˜L âÛ"79…±†l9;xŸÙ®¨L_ºüŠgdÍ‘ö’ VQ4,?‰wŠIºä“æ6…— 0UóH·ZJ=23ÿ8#ÎEíÄÛS^²„Ïz§SfûaID J+ËE¶e׊$¢ü=»M)¾£Ð•>4Â…×$] ÕU8Kí NiîÉŸ Þ„Šˆ5=a-€®RvR’i9þeÉ#¯‡M‰=–a,­u:OâdŽŸï€ekÐô×ôt¤a½No›”ÒÞ hÔzÚâíäÂè|‘ºÑ,š+ƒÃ€2"½–¥Z?ϧÿPñ ¾ÜtƒH‡ƒЉ‰i(SJS±aýWüÈž›V Þ—ø“þ+Ü2D3jÖlz‰¤ÝÖRˆ›rßÅÙjÅB¹*.Ç€ó4IˆžŒ?¥pÃXèÙtãyD,MÍàžH_Ž]R<žð¬»X=Û/_‚ ¾iÍÎ碞A?­3¶¬ J±­b‘¥g1`mÜ?2òeÏ ÀL{céã<‰Áï©6µé?Nͼ‹Ô/æôÑó>õÑ‚¶Mûp¿S‡ŸÛ/«¼êÅZ×á#eÉ"Žç›œŠuoû´YÒƒˆSÏCÔ³VZWV®ÒÆ7>òf˜ËNý6ÜÅí¹ó›=¶àv'PéOdÖ:œ 㜾Ô%Ý å–1/‘ F:‹jƒ¥ŸÁyŒ×í‡Þ±?¶bšˆý>`™Is¯s5*¤ Õ¤¿HÑk¨ú.Î'šT5 ¢‚úiÝÈçˆý ¬­A¼™(=¯¼1’­’A)§¤Ü0^‘Åžb&2Ó½°J7þ)Õ+» »D̉ԄРiˆÑCÐQÛ>FHùÉÅ,F¯Ü<[0éÞØgyJ:†íÄKL/3T|¡þÕ ZÑù¤çN‘Á@6·@áj× à%MœÁ–4ä¹hãµ…áuÏ}û>º€MbjP‚\Ñ¿dJáƒÜ’hˆÀâÊ~\Ê”^î«EÓ€CôõÊ}qaÅaÈv`e–{éý–Á³ûKH._kÙÃ"é¸Ïæ5~>l¶í+9ÍͲã¯Q᜼îÃsªîD G›8ñêùœWL«UdÄÇ "˜Š¡“p=OΑ'o´NoÛåeB±_|zËþÂC“Ânô± ú_rÚ3‘ž~–‡XFxQRèk²ïï@q }ÅÅ.|ö}º3î«MÂW®Ù«—5KeculÒLÉ"†íÚ(o®¼` âÙ»rb² bÛä°ì>hCÛ]L»å1u yLñ˜4÷†>fötjÆ*µËþ¡‡™ìËp8ÿ”xxáaH³ò@SÖ*ö*×65®`5Þ7— ¬#­[(HmÝõy÷p;”åÄ­ r¼¢0›Pbã`«Óª5ãÝ7TÞò;+Ï® ,°?oS]–~À[g+‚ÍôÉóÍæ&;œZóœ¡Ön­? {2Š´µÍéÁMºLò*ب&Ò.;ùM?´0gtDÂÇæ© ³š\Wç„,Ãh(§eå"…‹s{Š¢LèœL¦z"«ßÑ91¨ºE!,u ßF÷ù炱¡0«Çò‹Ü¿ïp2vL0Â7•{™ÉŽãùàlê{ok1Ïh‡.K,Ò‡±¥—–:­ìóa}¯›†7FQ¢ßÛN‚–’FQ*¶h‹Çy‘™üÃ{‰CÎ5ÜHÖåJéÀ¼ƒï°rÚŠ³#Ì•“¤G¯†•»GQt]‡5õ|ùzC(vï*|#ÌÉoÏ-4‚q*#fæz“'aEm[)¸òèdÑŽ+Ö¶R¤é-žä±!^ƈ¿YìÈ숎w²k'xqÌÝi÷—î™dÊ7X¹¾"O¥%Ô«h>5®ˆWî85ŽbìMøÎ™9NçðÝ.A«§PÞ¿Ï„wºÕ9•½)h:t'‹ž«à?#ô˜ûxøý@ŸAõ…ߩʅZáñœ‘9 ºgw”"CÎŽG-« akF‚p¡¸r§mUJF÷ŽÍó9‚Ì ‡CO&/™|Š'á_]—›ùèìLÞ:‹åŽD¹r"㽠ܰ;äÉeŒÌË_hÌ'xS•£Z|¤Ä£zjÉû5ùZ »”wÜo˜u5©œóXìAÚ⼸£gà£^Î|á†GŸFzõ-µ‹Äk) F®ãp¥Ôœ-ý)Uu’lï(5¥p“ž8Ù˜a:òš•èQN$9sXdÿpÖŒ3ýcHÌa¯\9¨žñÛ7i%³˜þRÂBŽæ—ä¹Ð%2èý÷J wÑãá›㆛K°XÕ3xÛìÉáAëy-Y.Ÿð{ZÄ>¢±Ÿ&)cxÖ «äÊGgmßÈì5‚qÌŒ$ä*šxáëº\ðÕÞD!õÍÛÍ òÅ=}Š„ ‘ïOKq&Ük¢¸Öƒ`6ò’[/S0ÿˆwKŠO‘ï<ñ=ÞF ÅžüíÎ$¸X`Ïš}®·þí.åY¼ýM€øÛ ºâ7‹õLr¸‚÷I—‡KY¨Ø#VÆÎ+¯ÖLS±«de<¬¦6ûñ‡õGýÉ_„7ÒÄ>àÈZ¶uÕ-t_N©vá]·Ðåfö4t_¼Ñl°,ÈeË!0Z±Óº`»ƒÎëÆí;îì…'C;0oFîÝ~Á†ltÃ?E§ŒÑn6XhTi C‚ošÔŽ0ÆQÜoÄÛÓ#LƒIñœP’ùÔqµ0áŸY>MTõ¯EÛX`ýHo¦ÿͧ“Æï8ZôÔ)øLÜþ}2‘+NòqŽZ¢˜ša‹&«ó ‰Ê¹:\…ÐK£½ô»É3Åi~ÑRJlOíÏCჲ¾l” R¬£ñY=!|*¹[éi &´i_´;Uk¹aÆŽ.ÇôÙéÁÙ¡¼fÂð×¼ÔÑT¯ÕDV£Æ(éE¼2éñ‰ ùûÍþpèôÜ.ZR=^{­bN¥m—JÜ”Ÿl^¼íãŒå†Vžëuš†Ùkºm[O‘£Á¯;?“Äi {ëvñzˆS„GŠ™~…p‹9v+Ã7ˆýqo ‹PÊÖ+ pJ–Ê1Ÿ —ÑÉE@á¦(U¹"™³&¡úH°¦^>á+‰àˆàŠ”sæ€\ŒE¾ &Î<Êc®äÜ ¹e-Ì$jä¢eGçä.‹^¶¯3=2í9Í¿‚…÷Ô¨M“ã ˜¶ypòFº’£Ô[§†œXwýáÙ‹ —JŸQ*Åhl›ÏOWXt"¾£Öm‚å¡ï=+UÈ–å¼c1ÀªŠ%×o¸V¼cߌÏÚèf;¡Ns•ЙUBÂì†ìòýe¬ÑÆë`7k$yPŽC2˜B_Y©Qšýƒ,2Ys­1‰nã2D¶‘WªHb·îÔó„pnæ«>U5°(8_z‹)࣠ëÂ)Ý ‰õbO„¢Óªêž•IJ8Ÿ¬k‹3ºÞRÁEÏŸ‰ÚÙ’ÂŽDÔW'mù¬p~ù“?âEÛ„0ÃD0§Š¥½!›†ÌšN‘ CÚ‡ H'ç.Žÿjª0Cª 9:´±ÈÒÝ£É!MÂÏwKÍÏrWÌ ¶D[2÷Ò¬Ê%P2bH{¸Ðsð¢}‹“ 4S©9á—6"mpÓL\¨‚>7#½8fœÑ­%Ó¾Íji=i¿ þóÉ+vQ%Ii¬¨æap\# å‹1 ½Z£_{´¿1Wˆu‹T]æý7¥Ê6fž÷äîªï9%˶„F§Èzp§ÙYÈgº›Å™7²­˜—Ìœ2^­M¨y˜©¨ôLÃÛ+§=ë¿àÚËyn èMò•YÒw?<àê½Yæ ‹f‹¿X¿/=´}ÕÝÂ.xàÿ)án†blÌeïMû§°jŽÿëâOP6&Jfí|îR½MwÞÒ·Ó]BÍAÓ`awÝ2‘&„[ó7¥õžÝÇ.F£ýÊi½}ÊFw•±|ʰf¹6—ž|Ání]ÿ-ôÙ Q*åNØ 0Å¿6Á#¯ë”öø0<îosU]·‰G›Ü«Ž+WÔ)¹¿?É6xc+â Ý"tÀcå„ÏĆ3ã£yÍ­Ê7y‘tkb}2HÃ+¿ˆâ ¯h…ºÅX»¾`aÕ…z–KlÿΩ–K¥¸ªkà  sü"mw‰ÃØke—Øö¼®Yô »á›©ÖiIš¼š°>,’U­{!>޽câÔz'ªT‰ayjì9ªØi$_€ ÙJ‘(æ[ÿKÍ#l~ï`Ú•õV‘é"NÀê7»9ë2 ÀË/ódpí³Ðö®ÒÀ,E×µ/™&¯9pÉl·z¬ñ¹!ašÕ%›ƒyŸ‹Œpáê¦C±J3'ÇË5w!ÄýûysíRlÃÆ5Mµí*RÒ'+h¯QJ 2d £%–©·kþy±r]z‘ŒÙѼS‰¯ ÊW§›Ä¤ j„ùǸZf–Ù_k¯˜»¶eíZZ‡£ÜqƧ0 Y¨ìÚd»®XHQSxƒÚL‚d©GšÞš¾^ò½æs†þ!Opë³´TÄÌÞzù oûžçéGóG:Õ!–úYRˆ Y:îåÁþº§WEtn0o]“ö"Ï~<0Õ+åÀ“ɱØÑ•ˆ_;`hÁ“â甽 3œ…OuIråkûä?E¦—_Ñ×—å*‚¢Tœ¿\m—ÈQ¬š$mJ;Xõ)%w~ØG)Ç&K¥³}]YBˆŒø¢n×\¼êxé>»¯úP©bo3>_ö²€'±íYê•: ãßàê[ ÛyÓhdGT^Iåè½þbjxU-kû²úà“ž½˜µ²œ9Æ*®ªí„î«V«HyL…w‹ÿD¡Lä€-¼ÿÒmiÜâô¥W´ä¶:5jnV»Áˆnùd·ß?Æš6!•_ÄÔK•]=¢Ý[¾ù•Qp  WD­ùƒNÈ€³q&-Ô…IÜÿ)ç«éTïOg™¼¿‘¹®0”yέ {ŠKhýÓ‘G,:ÉÏøó™Ó9é)ݤ&L;³Ü¾s¦Â}‡ÎÆkºº3¦±¡5¬Kj(ì¨vÞ”ƒ;¸1±òí7w†Vÿ®’¾GGû噯Œ12h@pªl{\ÞˆPsÕñ´Ú‡g?±Í«À?w¤žGÅàM8‘A:‹}éNW馇2?éÞîe#Nƒcµ½žô*ü¶ŸÏ™§‘RE£Q ]À£Z_æPKEŠ™lyTU©Ì9¨‘Ó¦Â!Ì("Œ™OÒþíx6"IÌ¢}M`óUvø˜ ¥p@¿ó|h‹ þ¥D‹¿&V*º}ª†™ä©“ø ñÀóÞ{E‡]£ór3·ûÅ0¦â†žûw!Hk?ÉPwØÐFk|ƒ˜¸¼A7|@¢·ÐaÚrË»\æ4øŠR“Vn[|á7æš|Ú¥ƒ(_¹râ7tœˆÃed A¿³Žà(ë+žãw‚FÒDõ©ù1^,3G½SsÌ=cÜ]¤{ç‡àú#ÌtjרÌê©# Ãé){Ç"eS_0WÆP¿š§Å¨ûF!²–ø*¹ ìÄ£?–ÂÙs_F³è Ä[Xi–ˆÝ劭ÞZÿ ÀÇXÙtd—ß ÍÞ›}¥mã5ÌÎÕ¦ðaúz©™êÕCpÀpóØß^š”½ÀUŠi`ꩽÙ©˜He‰ )¶ð+è»[Š£$o°õWÖÔ\^,\¤®Š–„±Ë‹·>¢^ëbz ‘â yxÚ0¡<Ä þ´v‰ÌÆÝŒ ·®]{Ý-, è2$ÂîÇ}×èÝp²ŸHuÙ?ø¼X<Í­'uÌATƒo}´J_²iºg¼R€ˆŽc5‹uô¥§bìÆ‰–,³ÜG VQEõ|P£>m³ýë½C`ŸMúO<²¶$ð©%ŒÓ(­*ïÈ6ïô)Í'xñÂhíœÞáûÜÞ»NA/Ô2Nät®TLM§œ@›´EtÌȺ•¾¬Ûuö+¤0!v¹Ì˜(j’&F &t¾ éÛÇi4;á Jrß4c‡‡ íX98¶Ü¬““%¸ÈѶbEp!þЬ[ שɊT¥7bhØÝªšé*úŸ‘ˆÅ% ¶âbf¿‘¤ÁøøŠwÎtËRù”¿Cu¬f›눋1,í©ÜY´—ØŒjpÕ)zÕàd‚«?ÎxU m^;!=Ç1¤Ûvh‰¼½,/å …ùæz‰1°_˜Õ±XÞ/ç[]#;úAÞO!(…ÞÚuœk¤8iýWLŽ¢,¬P¸žA›TÌÏü–Èuú‘z |¤ùòN]³Imif¡íï§…®·hiëòµÆ5=Ä7–êDXÆÄ éšïö; “oIç:ê1ÕœÈ@ñ3ä11¦õ{?û 1EY$&Ð#]Ë3ËO»[aƒ¬TŠÍ´}Ïæþ™×d|&TqÀϯ®¨Ôm?§œåiƒ,ø~÷žÅPd ÛæŠÝ[w–ã×ù 26Á¼I°2( ?cI/ð^¦v¢¬¬öPñ3<w⟣ȊU™õÙÃÛ6)ËØM˜õ5þ“}(TVë, 2¬ïæ W¸ü5@+ǘúÖú3šÔÉz2'µëÐ žôÃÀêªbÝ•]ÿ‘?Ò´"‘>:Q6aÂ`™‡WãÉ)fâ¤9öUÚ0»„Rƒ¢ö=w–ýë¾± À=?&&à#m·Æü'ñF°:ºžørþ‰Øˆµ Ù.ñ™õ15urƒµÔy@\ü ÊsØ€A¡%îìõ==Iœ‡¢E ƪyfÒ¸!Mˆó€@ŽðYÁy"ÉaM«ŸôWê#¢ÛW%r¢p%áQÓÏþ+´£Îuåwdzì‹ÒDOF?Õò5.4ÊÀñW¾ÂÒM"át¶ä±°°QÝØnc±Cðòˆ­¦q´*Ÿ¿Û¬‰·PÓ»Gd„†óºß¶SÐ…XµóJ?Š‘ÜùbÛ2«ï¥ÍÑ ÝN>Ì l†}¼Ì?ÌQooQÄãYÎ&éÜ­ŠÇñ_)û"ìü;4Ší9óy-ŽPýøììד¿¥MÈFjº}dÅH|•ù›Ò€Æ»yR¸Q\‚ݲŠr¹Æ*›¯™¡húºŸðZ$q̇’Çv°ô¿ÈILÈ~¥œÈý‚{ЖTPÕÑý uäG_)žËFäpEŽŠøÑqŠ5°ÖçãÞ¼”ºzg¾J‘u§Dá«]ôVù”jßxà {Êò# “ú§dNíò¹Q–#úGšæ(QΞÌÚÈ> %CSû 4ÆÒ$Z«/¬o!UP¿H òrIJ¤åÒ¤#ñN%Gºµ|³³’$vHg¡©(±o". ð­½#.%ÙºüF}¢)|wOØå‰~XµîRG÷ßs(Œµ“ç°:A©æû518L×ha‘2žd@Æ)êãƒý‰2dÅ“öýNùùÃÙ±m{ä°Íƒ¾>Ös0üá;¶°úrÉ ÷üòL®8h?[Œñ˜Ñ4)Am8×È´«÷f­ûð‘æL°gÃù1öã4§q–×t:àºJ{/,{ÌVÕfzšáûËðQ1 뾌ÍZx…* ÖƒÓW‰5¹$ðlpTÛ5ƒãÜtÙÙ­õ¯ý³×U`P˜cJ€€Â`Ë$h’ÎßÒ †Å ÷–5£øó*Ú5wT—Vz7êZ—Úy56¶ ŒÆ1tCº41Ò¡š'´þ\Dù,z¸t°Vp¿©:•*’”—9B»Q݉ÍÀHÅG+ÙÖÌÀ+A—ÿ0˧‘^ù™oCì~MôÄ݉F€ø›ŸÁÕ\æªT–Ī©cG%nCyׇÑd½0>ÅM\†›˜<¯,HØkÇu’Ÿ¶wø Ã;4´ûçù'þƒ}ؘÎm× SεL{T/Í(£÷dò¢``?têJÖ¢æó9Ã.Rª:ê!}XiÏã‘veXP-œsqÖRS^Ø¿·j¡d3p&Hø4£Kqû”Ǧ&lyÚû5¸”Ÿ™Ë8Ôúçàä˜9ñ` (g—EÅ Íž\ æÄ‚êˆ3¿Å0µÏ¸{öÅ„Ûa-·f,»¦Ÿ†¼Àåöxòóƒhc=u%ð=T(j𿤥1pŽ˜K8½Þ` tW¨z|/ЖÐУ$ß7·qŸÆ“~#¤ÆÜz'ý9…C´êÈ?Ð?;·sájÔÊîó§PaÈ~XìÄK°Š [´5ƒ5¦gËâ»9ϯoîXýªi;}à ½ê¸·¸î·yIqZ¦wk”ï o$L=­Em0‹Þ)5²>½Î…Æ1ŠËÏoß] ³WaWÅÕÄÔÞ%¯ hQŒ¶Å†=/#1š¸ÇýùÎV»GÙ™U+fªôF7)“¢ÔD$U¹tVºIM!›©"Tßlå¤9Ùl‰feâ½·qIÖMï0}“¾%CmvÁ+j¯“'Sü /ÔQѲ ¤NˆýúHÈB›rH逤À´ií³òªG›Ì³ÒÕÇ‚P/K^E¥–€ÂÝaž ú[òÆôF¬2_—w(™/p‚L$/ÞQµéP‹cûë:Ë{n·SÇÈfªЦñ¸‚Æ÷$„À ò çÚÛó•f(¢ó|U©Ei±jk(¡­TÔv~.Øï8Ji„»LÜãÈT¸ªªI[qƒ“´0ÑÙ¨×éBy¡hd!6h{­öq[©Ð¨õ„U+Üm³ï¿+¹PØ’.”øL@•ù¨¸¤ç¹x7#wÔôB`ž¹/G™8šíp*Â¥Íúö뫟QRÊD…0:´» ©fhÈ%gÇðIõ#޽±²5Ÿ§&3=ÌÆyId°í âɯöä†f.aušð]¢»!qúþTQ­\Û¦3\™“‰¦5Ç?%Iưީ…â¤ÛFÐê¹DIl”CŒ±ßæÖ£ÝÒ}­‘ó;ÚÑ»W|žéEsŸŒÅf5†ôÉ=ü +‹…$voÈuÖÑAÆò@ ~’èä€ch@ $bâ’‘sÓ?%ðBHVègD 6‘µÓ®”TаN8Á?ÙLÒšhOôU²Äsfö¸m“„w‡I+·H’¤!¥¾Ùø5ª„õpøÚhÁªÛyÁ,WíçÚÝ`IŸ¶é¶·ÿRP}NS–רºrùØ8Þ:y >a*g` e†ïr…)³±UPPä/ñ’œ›{Œ¤»ó&r(Ÿô®7ÿí‚Ìþl-ôcÅrÜ+íìàP~(¦¥¥Í” … á¨[ö¬Nu»¿76*NP!¥bŠPçF,áÛ•µ§©sœ„¡œÀø-¦“GÆ©ˆjØ¡Yã†q~\CùBB¤yÈ]`É™ÅI‘Á Ìc¿švÀKJ*ùÓþãŸpl=Êè<0i7lÖq”û}í’Ká>î™»Y$qÚ˜1€Þ‰ƒÀ¦Kd}¢ü¡ª‹l?KI¥žûÚº‘™hC%¶«3*†œ5Ι<\ë©&¸V*_bþ eê|V¯!“zìoËvÆMhÈÏ,ášo®çe(£áÕˆà²÷ò¨b’&$oPÊô¹°øå»ðaxîÓØ‡¨Vnnö?ÍÞò¶i% ®èÔ¢nðù5u_äGúÑ^á Æ=YÉŽ&ŸrWîxE-í÷MèYÓÖ‹ÚÀ?ÙDÝsWäð~û³¬ÑQ¡û¤ð Ús­„7ÂÎ.Ý}@%Œ0­)ë¹ÌOMÙ'X¨KØäRNòR踟xlˈD¦À2㽡œêx|gmêå×M˜ƒÆ×ðÁËûšøˆ%œì÷OHCÈ×Ì=C6#Sã2—õi@$•ýЯb¼i g!"ªââwܪYr•, ¡3×ÄE=Ãʱ8{°âBtì¢TV8 3|YB—9lßÛ ñ¨Õ2"HÈŠÖñH:NM(fèÏhŠÎ¥ºlím¬5]]?Ï=1qgsÃÉžð]c¡fpSx¹Ü(è:R?a“‘t¼2ÞWRµL‚T@žÐ í°ÑŒ±Uïzè?6´±Eèª^/p)+¶•>usŸ¶‡iáü&{f½GkŒÇø«ëÙ,SFÊw•ù©W÷E±»ÄrfÂerÙ2-U§g*F¿$îtø3$LQ!iPGÃä+±)ꆔ7TÁE"À»Y5áæÁR,‡2È2…ðiÌÊi¥`ÖÚ‰K@o±&‡æ‘­=q%¤‡Ác;>’Â8Ó¤òˆ6+ĹÕ‚FH¿»º¦$ ë@Dƒ—V‰|{OP½ª;OVú^éß?™ï»‚2…gEj‚Óçš=šo<Ϥòúu‹†·W¨É„Ç éyG&9Æ«ˆx•úqŒEDËÍÄ1ön³%jŸ©¦މ§Ã¦ÄÕ`Í4€_‰õiæßûÍýJ«&®ÏàÎZƒuCKsÚä¾×ZÍÅf„5ŒôÃ.&ŠmޝIÕ /&¹–¸X­°ï𺠉?ÕŠ¸…¥éÚžînBeBÕHQGí!`ÒÁ~€Ql¸4¹„2Dòí¥éd®Yâaúfªj`ýÉUæ¢ß¡ö}w¯Üu}â%öNôÚíéÈ8åb<³º†jоgÝh>úLäBÕ,Ú¼ßj~;¸×CVbVª¸±Í?› ßíµj\}¼Çvº’»Ã.(øÝÙ¸ÕuŤZ‹¼áµJûr/‰ªÛ8VÕvüu×Gúøü‹aѥf¦¶Z¡} ppa}]ÄÚd³o\O³I ³YòË”AêlámG-Õµ—;š'.fŨ½TzÇÓÉúgk)t‚#=\@]ƒóåï§!ݰá$T?.Rïf:åâÇ!TMƒF]µšùÆÚÒ"÷Vò Ÿn†[wi}:Ísc©@©ÃÚ¶ Œ>8zRíŒ8[Ù‹(h­Ç „ý"Љœ\ÔÏ;µ]e¾ÙLS²F `ŽŽR…Á®ß&ìRßÈH¹ÕU Ù j»n°&‰]=,Ьÿ<>m¥6i™™^·XbÆÐûªG|;\–ÕÍÛE¥reT¤ô‘P£«³¹+—=ÝY‰pð= Ø«Ç)—¯@f1ÿy#îË’(²iÜÂÀœ·Bn.‡»Ç“A]ûr\®;~ü˜´ª\h8ÿYÞžœõb=ùÍd󼘶`p=Q”±ÉN\q¤%–xPîºAc¼†Ÿó@YDÀø}h¤i¦ ÙKwÂÌQ³&ÑêcŸI yoK¹¯-F·¾ñrq2¤Uåá›ÔàXiL» «Ð΄fx Ñ½zÛÃE_ mÐ g‚@7 1¢Þ$õªþa«Üã›ø~§éN³òËÌlÂ3j´&*1¬‘ÅydŸˆÖÒÒN‰™ÑIåMñÀp¶vU’‹3[¶_ŒŽtª$"î['ñ²äø]úLN.ª‚@Ìäìi:j©îzHF ®gSJðüu n3·ò3ZfxÚv$WQaÆÅM4”NwË ÷u!¾kÍRÎêˆiõåZð¹~´*Ý(´màë}ˆ®zv‰ÓÛ-ÚcVàJñ‚=c7^`üQŒSÔê™f(¯U¨8@)žçË,è b³m M– ‡—âÅÃ-W³×bð±'¸PÝølzd=1×4_AR¨Fðû Ñ"I ûkŒig£­6¡7í‘ÃZå†*×ýÙhÛ7¼ cžLw «âüec&ZSÞ`˜Æµ–•© xµ3u<}›Z¡†HJ¤pa+ÄEŸ‘”`=ª9ÔÇTT`ü-¶gÔtYÔ±æ.ø€t–këý’Þ ¥ 7Na¬ÏÔNõ§¥­ÒG}s…pêBÇ”´#Þ˜ ÓÖ‚“gJ¯Oß 4«\Ãð`µDØÖÂ¥e­"$=ˆcªÛ‡é[gÁåà¶n)£p—z–ã-}0(SôÓ¾áô »^etWÎEô>@ï¢#¦Ö½tФµ[Áà ƒßUð„€%ê‡96ëi©S ®›ŒÅWál[ú-df"ÅÍèù¸\ÿ™­Ø42ÑJ.OÂÿ8‡}å0tž”Ñ•ŠÐï½ÂØ>‡“-T¹§Ðû•ÊT¿Þò@ã }sb˜nÕ…óK’¾]¹òs]õ¬¥*Ãs„h•K¬]g}H¾p˜ŸÏØÌëë¦J¦ZóowˆýõÅ›ÄÍœ{9/¤Û  8$ëîF¼w¶mšá•Þ†Õ`梩ìŠJ/*ÕKé„÷óó.ãúl0жñy׳—¯RŒ¡X¿¹Wùq æÓ5^Èbñc†9:öZ¹tJ‘cªØ¯ VX Ýž¹Þ°„AOyÕt¢0uï}rN…æe¯®zPT¡+g²Ê&ÍdG Ð_›,"K?(Înqd©$‰÷¹FZ'Å?4¨Õ}·Åþ÷û08Yë¸Hï5!f@ȯl þm#k¤|\R¨šÇ\ âeñ K »×žcUb@¤z$§kËŸb'•grA×—uF9Xפj—$æ†ÉõÁ Hú€ÄïÀèýý•ìð]™¥}†âÝs«]î`ô̺.¼p–ªLQî°Î+‰¦ ±F‹càAÁ3FöƒáX;Õ—*LµCéyA—æPôÛ9ît{èrÖq…ˆ W"L&c=|ŠÔfÌ´d¨÷'Á¯±Ð%¤÷™ÖWËOQžâÃÖ RÁþô c §& ÙÓà±LŒK –ÝLÒ«¤¶b;QÎÛ÷&&c=šÐåŽnŒ]ƒ_]Ö6QžÃ.xúÒ5<Ä)E÷9 àq] SåÏr¾DpÌìíó69² òK:ûöt~ýÆZ¼ß†/ºõ¯s4— ྡྷ9ìíÿ¯×êÆ?dœ‹¨Q槬hµüÀê|Ç:Ø¡ãÂò!P]ϪÏÙdÖ´¹zÕ=~ψN•’êÏü‚ÇÎÿÐ`]‰T¢ÀvSá+>‡±¾^ü¨z??jK_TÜ/ˆ}Íoý¡0¹¢¤q¢¥jÔo[ðAŸVþƒuÔcPÛ{ 1QZ!02ŽV˜Vy9 Kî³·1@Ä TÚqz+hr‘•f¢ ¼Š °¾-)UÜóm¨„ª„©‹FÊó÷Æ®?0ÕéU^Í `À2ùÌÌ~ŽGéÀí™g¾¿b²§­‡\Léî<¢(odŽÎ×â:¦­vYì¦m?>$ñ2Æ™™ëŸŒ\ÐZ4‚c%(K¢6óÆÝ½èDqÇÎBÐó©ixtïÝÞ ƒ iÀLµé&g?§^ü$ͺž!:¬ŒvBÚï›S¿"Ûµ‡ñذl‚BuÎýUWI1`ÆP7¼C ‡$€ÑùÑmîpÖÕeB¯ûºI³bØSœ1øºéf6Œ.ؤ¾ÀšS:çÖÕ]|#eÇi¬žÁQÉ~HØ´$ É€-pªiV®¯p ¨G^ÌM'r§?’W›“Œðt~ûd|¦;qÚpþ…|éW”þg¨¬‚¼ þ‚Ñ×TוÀQ1y/rë¿,T«,ëQ‚¯ËiNBî%ær3EÃÏ]Ë&.Ñö¡³~ÓñÄÂ$å&»!æûÊìc]ÇUòØt:$ù[ÐïýÍ%”5 ˆçÕ½ôµ“ÁºBàD5r!gy„3&>m6SÑ0ª.fNýQ¯Îö%|‹„½édëŸÖ8Ç\¯€™e:üÖª ¥”Ÿ, _ã:<šÈ 2Çë™ã)£ËœâB8 ¢Hj“Kvð@Jr˜Ÿ&_p^·ÑN÷ 'Áƒ­æÝ5?Ƕ–ðºæýìÌl‰ d·y8«QÂvÙ$½Ôî['”ö¶4ÊЖÜ×Û–©ef† JˆéãW()eÄ{ÿ¿’‘µšR,ò~Z£Mù:ùõ5BôwæÆü]kŠZHÅ~W“ Ù0'hû­.œ ã‘"T@ÏÑ\s Xíâu=iÀ†T€.Xç܃˜[L‹ÒY-ütŸÃ[SÖE¦eu9;#¼Ú÷$ë&4$Dý3"A·eéÎç-ºˆÌòô×È!mª$Yâ膒 v£Ýù¡lj«’à˜&ôwÉWÚ¼ÃçPÑ'L´³ÜG¼R•ì4ÿd­ðžDµ$'iéè%2«Æ%.Rô6c]½hÝJ)ðCfBˆæ…ájxÇ‚Sç˜-ŒþêaøeiâLþkBÖÃp§Áð#¹ý 5[mü 𚎩0)‡÷6úêo¼n47ª6[&ïB’¹œ¸dk@8È;µß~h:Ⱦ`.,Œ?kÀÝíÛY!Éø G›Önû÷*á§I\¥P°ÅÖ€àá`ˆFÝZ”æl–PÕJËÁ€¢×°?®öº/=ì0Ëf0BÈ[ì½U0íYes%º4:]öÚ…*ROÄÈk¹Þê´Ê[øø¡‹ó’dé2ࢠð®uzövS¶´.¦mH†%†ÙÑØFk¯ ˜ñeåÒ@j²€algI£(p•W+Ýûß—ø¸VzÊ)4y–§‘HT{‰» PìÛP”‡ö´¨£h„F Ï.éëMCœ­w€Ú,¶6á¦z\€HÝnü+dv³€™¤Ð §¯„¿±sè†Þ6·Ðª“¸$!jøí]ûéõˇÈ])W*>â§,ÏŒs¢ŠfI jÙ{s^6“é³Fh†Ffšu»Rrud ˆ˜t¯¡ãç53?& ª'ýó‘°×ìàü£!}â+¾í^@1Ù.§Q½G"$@uµI‘íNÕçÝáênG[X‘lë4lrà @§ŸuÁpÕP‚ýAÓ—{½á/'ÔosŒkéF ‰ÝTîd¦¸˜²+’¸kYÃÁ\3ucŠöÙWŠóA"ˆAŪó›Ÿq/si>–à³UÞ:–‰Œ´<ÈH!Ç^I¡ EbeÍKëïè ruO±u ¢H£ÌÄÖ—ô”Uªõ4•¹öoȸjªš—7CGú¥Ü¡×w2T‘Ñ`¨„„GMwV®H5ÛM†õR®š˜mpŽa›Q¡¬yG‚'>JôO“Óg䵊u á¹¬ôCË ‘æAý"~Pì~ ²m[Rt”H×’§’C£JP\õwô GÓQ>³û+ƒ¤ )Ƕª¢çÝ.ñ>§ˆJ‡xnP*boó×4ëgJÔáŽõßA6éÑéÔfëÜu[È:É";SVþwJJplô­meVdG)93§ Šª¨3® 1„µK%«jYW«™3mÞmMíãZá×µOâøp›Ãá*Â+õ¹%¦€?úh Hàz0="¦”„FŸ Nøp2ÀÔã¶«Ô£n*Ëk zš<ÉõP$µçß­WÿG-uͰoW ÖàSå<¸ÖØÆÅýÃH4ú2pþ¦¶,5HQÒñWΡ´®”}1¸5ËÂXf.–íoKÀrÀ ºTEŠŸy¬‡3ÿƒTï˜,BX­²îp¦­½›Boç/œˆ?¸1ãxØ/û±Ô°©.QI™Í½eåÒï¸änõ1áÓöÊÖ;¼>¯-À®9ÆiV‰·îuBÃ\Z7餭º Ë’muÂf]¿‹ Œ?e,ÍN2A¯ñŠ—‰!ZÉ:Adrjõljø1©Ô£ð‰àjôâ@,šÎ•¼ö_FéÌŒá"B|;¦ä*, ‘üZ1iŠ×u6™dÁ|œo0F›‰Êlç› ˆö,YgënêîG=Ö¾T}e½Øý“±Ç€VIs#Õ»h\bô•ÝåÞ,l-£…ŽDàÉ Z€OLî¢Uçè#´FÆ}óxÿ­Ì½¥~¨3F…ƒÔÆ­šL)IŸ/ÒÀ•xS2GvvU³7ÂÜH=Ýjè AnæTQOrFŽÛaÌd‚Ûˆý`s ´|¨(džlS÷Ÿ$éÓ7×2葾È÷^¦q%Ç*N”­ZãT2èS­éøæ÷—ýƒûôý˜^Å8&cË^Mù7!Ô_Zj›#X¾C:Äåõ²±˜ûçÌ!%ÎHP²@rë©Í½ÓÀ×|à¥?¯|Ÿ«ëß©rE¥½Cû[_!£„rØ6Sûe𙄛œÊ`øTe¢ÔÑTéÔÒžM ˆ}êuòA;-„”Ý\ཊÓôRP©ÕÌ‚¥)žC-·¾m$?ÿ«?—¡Œ§4a„¾a»d-ÇJçÿHkxwxKªcy9‹•Ã}¾\—>ã ]ÔénÓnoQéKÁDÄÂJùKD-ýoi>ɲ %¢Ö2Á±ì=ÊóÒu<8™ÞÜfô*^F(6%kqwýâz&—_mümÙ‘ËOgVÁ®úпxÇ”E2>mÏTµ[µ'H£—¤~e‰:[ýäçáy8$ë}j†B–ã®s²«äz”»“šÒSó5×ðu–e¤h&¨žë·=^&˜F©Äûó¤Õ’©ÌÝðèâþ˜Q&«u‹>úÍíÑlUðUõP·×äûåûcÑéëAøüøÞÐ⨛HOƒŒów¤ç’Ð55iDFp]sÓ~+¸’!eóFLä÷?N÷ÄVõíò4}ü°pÏ?°|H*Ð’Ã¸Ž… vEB“Y®t»+·ÝÂ^%º”ÏW˜MÎ×á|(»öÎǔԽxwF‰PžÞÌÛ+ê1â,·›Ühä·‹ûƒ– UË«0#€ lÇYlŒ—|k5Õî>ùتLÈÔ.Ú…Eí?-åLÏ€b7B¸X!¹Òœ‰½ç¾®hô2)=„#D‚¡õ»ã„¥YhYBé×ð®»îe'«OEùÏ”ICü—­“ð×âZ+þmU€¯°Â'¢ÜéÉ +vGú¾®Ú­#Öqþ‹åk¬’øGÞ¿Mo¥ë™ É%±l“ZÍZhEÌ-¸:ÅÿJܨOP_bÏH¾¸þPüYé²}]<Ã¥ óÔ×°þàõi\¿8N†¨›ð§Ë 09¾Ä à´R—<ÆÇmw¯Ï±9œ'Wpß#úøˆ3#cÛ\j#§ì²Æ–/Y¸¤¨Îß…¿-Æn§=<=+ÃŽ*Ì® 9(À¤QUÈ‘š±Èê„ü„>Oà*Ú}Uð£™Ç‚U¨ç’DzŽÁ=1C[v Kù1š) q¬Ë&áÖ²+›€[ËŠ–uZƒë†[ÂÜqÿRw-%K¾Àbç_}ìÛ‚Éܾ`LÏ^G]+Ùj¥Cʉ‹ª,úØgŒKò‘]¥—Uònåh6å: ‹0ˆLöbàší”š˜¢;ë]F€ü}«¼TÃ\yŠžTȹMº"n=d‡Út¼Ñ¡‰} yãÂx :~ÁR½$îg¿¦]Ä·ÀµÖÆj’ï?…|­É Güb6ÏwÓ±öl0Ï= À=¡Ž†µF¾¢®e“°;hÆg®Š€ÃgÔôºý*Åw’àÖAw'·=õ¥“ÒñÙ([ýJˆ‚gÆ÷\A–´wJ÷©3¥ˆ/—wÚGŃaêRÖ<¤¹CÏ?´ ö¹«ú?9Nïõ¢Ž8Œë”¹ålEK¿:…³$¡9$Ozi½\ûÿ¥‰[#n”€øÌ·Ÿ¡¶cïö’SÁ½ª-»°uŽ&½ˆàËþ9îL%{¸™”#Ü"bI#ÃÈõD6¶"8‚’…~—®SÌ…>wÄ­º-X[©„W}¿C-IL ]}¯á^¤dµ²£ø`“‘"n¤!æp'¸x:ÔœLh‚Rô+ˆ}l×îÒ tlî*w²<߇F s!f…<Àî;Þø"˜­kM-¶^1(ÓFétíWþ¸ ¬ƒºpÇùWhq"²Ðà ¶S±Z½7ÎÇ¥vaB}nœÎˆd_h-%Îùöl Sz›ðéQ_M„‹j97lrîÖJÛvþíŒ!ÒŒ8EÔžjr窼,ÉÃ$úøÍRï§Èz?Ùt–Ï·f|´ù(ÔôïWüu¬˜ óL C¨®,Òs³y=@¢ê5P T„ˆü½…`Ùйóo®§³Ï;€fŠCtöE7YˆÖïó­ì.Ó-ŠúÔáwÆ‚”žE§Vn°­…]=£ü}jî7Ì6u,*¤ñ­V°Þ±z¼L¼ _7$ᨓ݊À×”+ĸ§ìëcµ5yL§¯o+­< ê ‡.ûzñ²‘‚ŒÇSpC¯ ¢þ{{ßn£+|À”ˆA⨛¥?z¤ORá¸ò|ÿvÃZ½fßêPt˜± ¡cíH"ÈÇ}€ÚÄs@³sÔh¢älYchÞcÜ!’%YÄ`š„6x™UÞ¡ÀyÕ·×cb>ÔW2BÓœ´[ ÃÔf·aé|@Þ$öWÖrõ·ŸÎ›.¡q$‚UZ׎r K:È£•“ 5Ú1µû‚V/ìARãIK’amÔ T(üÿrëi.¬ˆòA‘çO>˜t°¶m[ŸZÓ·~2[äðÃeC˜•QJÀçòk`¦|‡ oƒK)w÷fªR®Hü–Æd;Ñ7*`Ü]úšvzõ58c–ð(ʤ¡ƒBgJö?ØuÑürS:dQÅÉéAŽOgóÌ‹ãµLHuðð{ŸT™>)ÖÖñŽL{IÂð_xLc[P¿•Þ¾ ìl7aNBÿ\Ë8Ö³vW%ÃåÐ^F•;D¸©@m.n7QPMÒÝîö¼;CI;‰#í%daêø ¯ÒÔƒ6 §¸)®Œ°1¿ÿÚúë¾Ë£Õyæy/s:ë$xò¹Oû©Þg·Þ¦ÿ#1ù€9dVCV*s‘j€º±GúŠìÂ`…¢½z”K÷‚ä`NˆÙõ¹zúÌ€‹ÉMÏ;G‹d´?\‰v£ùqäÄ飜5ì¬#qÁ!¾¬kP%aÎGEbÍCé~ˆ³ydž¿7*¾W:[¢÷tõˆ:ðÉV¡œ§ñµ`1ÍÁPÛ‘ÑN¡P{Æ`k7Ÿå›áñ[ÁM£sB¥…P0)¹ºãšBá Îñ5viób7éŠÄb9œ}ôic-Éçy”ßΚrã…H”c;”*âšÍÃḬa'«—â7ø endstream endobj 1300 0 obj << /Length1 1616 /Length2 24663 /Length3 0 /Length 25498 /Filter /FlateDecode >> stream xÚ¬ºS”ek°%œ¶Yé¶mÛ¶í¬´mÛά´m£Ò¨´mÛù×9·oß·û©ÿ~Øc¬/"¾3bF¬±6)¡‚2­ ‰½‘©˜½3-#@UI]ÁÐÆÆÐÄÒ^†VÉÞÖð×Ì CJ*ìhjèlio'bèlÊP75ˆ˜˜˜Œœœœ0¤a{GKs gÅ_ Jjjšÿ²ü0òøOÏß›N–æv²¿®¦6ö¶¦vÎ!þ¯/*›šœ-Lf–6¦ayMI9q…¸œ*@ÜÔÎÔÑРàbdci ±46µs2¥˜Ù;lþã0¶·3±ü§4'º¿X‚NC€“ƒ©±åßk¦îƦÿ¸h¦Ž¶–NNŸ–NsGC;ç¿=p¶XÚÛ¸˜üCà¯ÝÌþ_BŽö#lÿúþ‚)Ø;9;;Z:8þfUûžÎ†Îÿäv²üëØ›ý4±7vù§¤}aþz -íœÎ¦îÎÿä22˜X:9ØzüÍýÌÁÑò_.N–væÿÅ€àhjnèhbcêäôæ/ö?Ýù¯:ÿKõ†6ÿÞ¶ÿ7êr°tv2µ1£ƒadú›ÓØùonsK;úFEÒÎÌÀÈðv‡ÿô¹š:þÛ Šf†ò/ C{;€‰© ½œ½óß”Šÿ;•éþ߉üÿ@âÿ'ÿ?‘÷ÿŸ¸ÿ]£ÿe‰ÿÿîó‡s±±‘3´5ý÷à?ß1À?/»ÿ-ÚÐÖÒÆãÿÿß#ÕMÿƒäÿFÒÙðo+íÌÿÊÁ@ÇðFK'1KwSKgc €™¡Íß>ýkWµ31u´±´3ý«ç¿­Ð220ü7ŸŠ…¥±µÝ?gý—©ÉçþW¢™Ó+i©‰ª RÿïïÔãþjï¬âáð—Úÿ(EÖÞäþA²wxÑ2²±h™8˜ÿ®#€“…Ùçÿñ_ Æÿ:Ë:;Zº´ÿ–ÍÀøoñÿã÷_'Ýÿ#jgloòÏ´(;Ú™ü°ÿiøÇmìâèøW×wþoÑÿyþwÔMMÝMaV—칃­Ò³2œë0óF&E´úAGBJUŠ ýkì{ýÒÃw8+ >jC蚦¹¾Ú=Ï>¤¨÷aØ÷¦š^àúSö"o’u²SÒë•Âgœ«G{]/Èlƒi±1¨îN**é•|@àMw2;B]?Sú»ú£‘<9 ø§5Ä¡w!5¡Ô“%4¿¹²Æ¿%Ðw©—·gx^Å4›o{›î„ÁL`m)+× Çv1+ñeÉÙõ“ãÏæSÌÿíÙ$}cOŠò|,V᜴Sõs¯Ïýg@ÛæÞµ=ã c¥¿Ä7EÿÁ bxFkø~üLê|aDŠöÁë!lFXw7®eÊõ?ä= wPVD¢$)“­!8C³éÒ%u–x¿Ö®¬Î½üLevTm\¤h ÷-'VÍFvÈ×+,òÆØó³`kHØövº"¹Ì"Ô»yÖØàiI)tvfŽOL¿¬pǯ7 Ó¶uˆòßã¾9 ³Í#FáÄÃelßœºÁõ¥Dí¼ÈH”ûCòZßf~ë€Úž@T|ò¬„ô»Ôh}òvëØbû[iÀ'íùX¶–éN*t®·…£@è†ìø¿Ê€ýZ|÷éžÑä«Xà¶{¾}4Þ¯ýh‹ƒìWV€ùègo£g]žo¸+jÔÞ<1[‹O¾(Øäº3Œz©ùÓÔ§«~È.6MakA–uN·BC×°êŠvúèvk(S—y{R!ë:ç€ým ŽgR¦ñ¾¨ BNÁ&DŒ­‘>T4ÿ¥'‘£6P7Ï œØdˆüöGû: ö#&(ÑwüË]¦ÍéÞ/à›4œ ”Å$B¯Ö;A1cì¢yêM¥E¼ê7(–w܂֯5ïI0Í}iì9¤^^Êz#,`Ý2ü´Vd5¢«f²êr’etë.-ùPèì(´‘-TÊ;Dã 5^;¤²ÚÈ@‚ ‹ÁCUòÓÚ¡í‡A“#ïføH¼ª‘½ŽIøBä˜Üeðtß%QO+È}2JŸ*»ØÞSÊÍФQ~êëQ´XËa¼ôòÛ™Âay]YçPv–}?2ªHhÈòlv>Ó®(HUSnT‰XŽŒÆCiãøÎ|öÚ>9…^:^u“D1?‚ n‚ÿ—φÁ±<ÅÜhA›ª$-¾™|ÅöðøÜ+6c;(–Xãò)¸¤á£ìj/bñÈq`[£ŒÈHÿ¨èO­åÃò¤:„‘x´ùå¦ÞE雿‚ttÐKº×ÃÏùä’ á%ë÷Ÿ‰‹| „ôÙ 7Ç(ÁŸWÙLޏÏ^°mÏ!ˆkÒãzïÜ¨á »IRs\ÜÙV{þ â g‘™Œ ZÊp<®½Åà6Oï9 ¥’+K #– FA íEZ¬‰(ñÖGçé`8X~U³ž¾RºZÉH®³ Yf|Ûk¶…‚ò,÷/Ê9D›óç/pái²?½E9h«QȾÄ_‰žQϧmÖ¨Þesr‹ˆ“Í¿}RÛÑZ×ý e@„ïfP¥ ÐèúM[¸åÂùð;2m  » »ìZñhhæ¥(Z?C1ÎÁÇêL’ªjÚ¸ 1öߦ¸=½5à I’y8GÖ+ùW³_%ã¦h #aé]òZÆòSû• ÇÝTý9£.¿rS«ˆDÚÔaHªGFCSnº‡ý½º‡9ò—í)í­– ?0;kæ ‡Þòs³+l¬Ñ’{Ë ºÛ5îF€4oC¸ž¢€†$$!È/áÜ2ÚœI0&Šö¡1^XÉ%ŠsöÕ}\ÞŒÀñI-i%ãFuSÎÀw5ß ùéèKy …ÙÃÉò/-m<m‘×¥ŒÂÃõxø!>£&Ú_­:éöªÑÕÖ)σ/ãÄã‰c}‘ŸÅ£ïÑ´x*J„™2ÓÀŽ•ISÑze£®2¢­Äè{̨ÅýÝu Ùyù V3+¶Äpö[†Þ)!™Úb•=söí±öõ™ ¬{~f/‘OM(–n¿ðh)8.#1_èsWè™”Á¬j[µN8%صøM©&7Ï6Wl^óSK¨!‰ýø·n×2‡½“2ÊÍîWCù2h1 ²œ¬ráh‰.OVR¢¥Îˆ C± /!ذù¡ܪû—‚[:ìRnø)á( ´f[\(4 4Î{ úhSµ\Êp­@ÿÌ6¸ä$ñv§êdŠ Â¼>›_¥KU+^1øÌÖÇ$MTøÎ£­~P“‘¢méÎ_Ë:–uNßæ `Bê΢PÚ¡,jÎζ°eq7óº òf²¸v_˜°fVʃbºí+º°Çûr¼‘*ÈvW€Œ¸\Ôm1¬% f Tr£ ý óaX¹Åw°-†F£ÆÄÇsK¥iHB ÆBj‚·£àH[3!è–‡"™õdµŠ…;¸'%lÙvYìkªÊg®“Cuø ^êô² ¾ßk󥬸©Æ¹²¦-áN¦ªÈ Î¥¯ž]Ìe¾ìyx5Óä«ÝŽ€œÓv1²ð¿U3’ÐØótç›_UФ:G½ÿâ2”Ðß2zµ-™1FëVÜõt¯·¹îôv„x“7æ‘FªGÑöôRlíE-YŒä£³û³±‘…Iönëᦻz^ï9”ö<Ëô#ã‰ÕÄXó±9Ò—Œß'ê¥ß൧°¼KrŸ—®ù™µ;ÏeïapfôýIøC…Bû|¢UÈÏ6Ý›ú[v ]s)8’‚üÝ:8:§\Ì|uèU!ùìfÀª^~Äãh> |èMÐLœwÙЯ–›M©N Sú²@Ï¢¬òäw)à}eótè ~,§PD'3Àܨ•º4V 8Röª'cZîÏsÿµö 8ÛaöÑ­ˆu&dõ—ƒŽ`ÉÖL€= í§‹ ­î½¡OfÜs50™:_¼‹Çfñ2lnb‡-H.Ì›¹8…¸JÁV´Î§(Ròz-ð0_Óv‰ÇõäL:ÈÂjz.%˶_=Ó?åë+ë®K»(ùó¹ê‚—X9%»µó˜ ebGêÖu£Ó‰Â+ûªÚÉÔ]‘qœO¥O/±LTlµfF@\Щ®l[•DwDD¨9Û¡ãüW9š/Å:ÏWëX ëñ5v ìÕ™˜ƒ¨=o]öÞ¼¿2 ›ƒËD¾ ¦ÍlL°ÎXÔÉY‚ ÛGN©—žXñð…i •®r{³SlWž¨¸úÌB¿Ù:úPTK3ñîÎn—àQò½ôAû˜µ& Ìñ¦8mçÍ‚¥Ìn;ŸÄsžqŸ"þ1›,wåêÉܹ½+¾¦c9Qºøùæå9Nè`öƒ{Öj#:þ‰ÆÃÕŸózf|Å›ï7tÛgM©ñpÏ•=@÷%‚…Í}e÷‹¾¿|M²Ü  K+æŒŒÚ¤Šø§$Bpò§H.¸yDôsò“(*éÔ ó€›ÊÕ]žë­9öe6Ÿ_”/±~#°õJ}Ž>_,Àœ÷+Î[²HûÆõz$ÒÒ:¯ð˜I¯1¾5*˜Ž0fþÚ2º/.¤~ËXóìeoº;Ísªø™ ÚKíY·qä5ç #›xe/[𞡆zÙ§²R` ã·2?.€W(³¨*<Á?ìëó"^'Š|oÐ\­°&Ýç•îB„Ž~|'úIEGÕ÷íÚóÔêp¯‚BÏšð)d¥Ý±ÎIõŠiÛ}üY‡4~ž1#`«õÆN;NûĎ˼~töâl.(Þî3™ëàQBöŸxŸûVeií˜Åc?(NÑÄ/ ‹åûû‘‘Ñ&EÝpÚt'—7û¾ždˆë =̨YµÆÙ NšÏ8ÛŽ/BÈ ,W©ˆN"Ês¤º9·×%_p’ú(Šà½‚~êðÓÐaøÌ§ÕeÛ<bÙ³M.ñ ÷Ýý)^Ũ\>–—j†ƒë†¬ŸR¢Â IúU‚£#{c)ÿ*UÔà0UÈĦuË÷t¼ ú7¼ã’Cbðïë{µÆ4¥[ô4ܨ«}>d…üo1ßíÞ°! ‡I†mÀóýÑ*Ô*e ?¹¦Á,e”“R—w*ÊÌt0”ºÔ‡8¡ƒç$¾èAÌÆT©•1Ñ'Ý¡>/k·( UDð§õ螺ÊÕ#Œ,ÏTQ¶èI£ƒwÝŸ/¯^á#OÝÂ_Ú5ô9¤.õÚSÚ‘—‡`Ð<ƒL-õ¿ÍÆ`åЙrà磉õ'&ÅÀA ,2=Ü 52Ù7uËE¡N2CðÐB"©bHi5;3… i®‚p³Åù·R!.rï|c«îþPù+ªjL‘áèíÀÕ°3Ø ´¿¿Qö,Q§e‚[+ALPDTȸ?5‡jz fæ©âv#ºÓÍd³¾¦¯# ®M¥|ÝîX¼fbåJÑîZÒêïbSÕ^™ÀXöpYËÌå‰;Gý"ž÷{ [ª[Èìz?~YÆ.‹?¼iß-1RHA¯ÿ.Éað¤Îh÷#FiÕioýys³ÿ›[—¶™ÈŠçÈî…RX6öJ²Ì V§(g™»¥EûLË!RwX|7à­ïrã盯xƒàΈü]b¡«?÷½÷L¥ŠïŸðL8’QËßôsRü²?“E&‰ ù‘?iÌof‚mö?ù7WG„kôpè Bo}¿7Gr…)7Ó ¦äæ÷·ç2¹„ ¯Cƒ»c tƒN]õ艦{Ùµ¾û!äRE¦¼|£ „6¸rS?½Ø µŠ~ÏTfñûÐW¥€çªð<ã(èç7¿B 'LüŠZaG³¬¯VÅlZuq1ýC¸йHHb>ß9o :zhSÖ±mrÁE,Ýv’ ^¸X®Ãè&j„¹î»P ÀðeŽ{¦§AÎ Ò¥ tüh;Ù.’L­dl|~%>æ{¬¢`Ù2ÜR§gaǦ}!Òc¹ãZÎ Ö±t—X2ÎÊž"¨‡Ióß—À×ÞëAHæFÄS‚/(‹×@XuÃÍÊÕ™‹ÝqÊ…Þ¨mg( oï’ýC…%½°f-æ`há¶HÕf¾‰µuÜu$¢èó˜Îú g Bi(=!ølü7|c”ÔêvÕ¦0*Ö‹þ–Îuó¥®B‘©ª­z53î4ó¥ÎQåVÿ øEW eéÕïöýB!—lz(äúÌÎ-–Xbå8—·ƒ7@ý¨a†˜ò¦¡’­­#®Â ju)·OÊ^µJíYeÌÈÓ '¿*_éŒóMªëå,(0?u/ PE³îÍzÐôã< S‡m²º¹Š'fÚ×Üí*Ù ]CŒÓ"Øz½Ì'ª!&rgYG.ı™Á½V‚zÌžað³äITg)¨ýÈ„÷{RH¢ŸêÞ˜ù¹uƒÚÀa°¬s¸bÉ{ëbÑÁDföÔ«9ò’<¡VÜâ*èNÇ”Þ7½¤°I=¿ýK´¹Ç]¶B‡¶V¬º¯¾N™éëÏ×”í÷0ß©ÓñÄEÕuî¢w)©éÖè=—“Lù’úîåÚc+Hâ Üzø¶ÌO\î&Õ`µm-yÉÇî}ý‚í_Å£Þ©>17¾‡d‹bݧ‰ª.&:£N—™«å·H2(íÔ0ÁGHëNÜ´]'¹MéÀ¤ÍEÕfiúz$ÆßªK}.ã¿ýk.Cê;'=ðà}•©Ãš»0Þ´Ð)6‘áMéWÀÜÜôŽ[©_­uÊFxظ™ `Íì·ù“OÁˆGsÕ/CaŒa«mÝ¿¤âtÊ5õxÅ♣¦F…0;ÓŸARºåóÎXÜÉHSÝwâ5 żƒÞ¦Ë» ?°]¶ÑôÏX;k;Üè”è=ý_%ÇQ“UÉáÕH&¦ŸÇ"e®ÊEQâ”Т¹B‘õDñøfã O…ï©9¤]Õ¥X'uK•AÅýA9@ÃFž¦IÇ6º¡ÀÔN‚rp^õ©Ì¡k1É•7s+;¸?¬‘uaŽZÞb|~ÏljúàÆá¯5޶‚Èè‡ôú‘5)½¿S—bú©qj?¡ÑæCÞ{,yeߨ²"ºN¨¸’ã~z? r:WD±=Òì¬÷ð›Ájí1ƒ2•Õ}[ShðBÉîáƒ&”+fA„Œè&®bîu6F•`0-+7„á­?†’; Vf¦T¡ _I.#ôƒO¦LóÓ«1sͳHø$)-ŸH‰—‡ìíhïÏßÑîúD‘n†° 5«sÆnÏJ>È–Ž±i~NŸök«³«¶-7$JÝȆq'ÙÛn~Îê­WÉ˸èKÄSä]Ë©ŸùæÜñQ@§~iD´oaá”k5(Rà=¨’O†Z?Ûtýš6p»•ŒB1ÈšßøoP›Û¡áhÑåžÆG–Mxêûiqú“÷&"oÞ3µ@RÙ{‚µbÉŠ¼ œüº`Ò!{bˆ-ÓÔ>¤=Å]©1÷…´9ØÄhr*eι2‡Y–yNBÜe<3C®ƒ©q„³O=ÁšÕN»Ðy§™G³tÍ`*ˆ—LÃÕWê7-NGË^U£… nä W¿j´Çáa»\ qüxMÌŸüQ°, $ËÙ¿iP‡2ä$ˆ˜ÚlÌ~D»Âö+‘—϶j-Á§ÝÎhàC ª£Ë…¶ÀØ ¾'Ð 4ãw”ǘ’E–ƒ¹j¦Ñ«>M²‡ªef3Êν}Ê–ÓG‚vr©Ú&Áí)"F©í`´?ßÀÌ$é‹*çWe‹&í·îzùY14’: H&}rÎi°xÐÛæN}ã<û {$dW­ QºÑ9IQÎVûÇ“˜ÁMÓÔí7;ÍšÓø|Ph¾ª¾¬<"Óno8ÅÃ:ÕvŽH¤³‰ŒtRÐñ ËñtÛF-v¨ñùxÅUk°y|ž Û_˜ó©ó¿²ìí$¶‹æ€H‰w¤÷ä`@i»”ú…e>µä7Â÷M`sÔÍ¡š(EÍØ¸áC¼˜åÉuçO=[¬wžÝ÷ô»|uµ¨Lû|ä»:ãÚ¥wt¿©ù»Aà|–YÑvwCoâ|'®!›ÄŸ«EaÍİН°uÊ3 ^]¦"×ü¬Zpº†Ó39Wñiýú^‡4srìûQ¡'©«Kt{á€ÎË”I^A3ÖÛ¡íL³6ü«aŰ™ÝÕaèæõY ¡1ç@0¹&ÝïpúC>ÄïZ§²ÁÍãŸÊ9ÍF«lv`ÐÔâ±Þä9'ŽX…·C*ã6á Ž=¿” ë>äòÙŠEù„SÙvN^‚ø€ š^×°ë`ñ~v óŒ!xµªÇ-›aH|%î¬å}á?öE†ô‡Aù„íòM# ÖnÞÎK)qÃH7<åš“ ~(±_›ÐÉ`ý~þ|’ñüe6¯+£{üëòqÞ”œÜÍ4 Ò@AAOˆtI"”ÍZ'•d0ŸÑºÀ… “ž‰à1a. [­û~å!! Uª½Íì«—^àÊ?Ü'uæâÄ^8F“O_r¡‚>Á’À‘­Š>‚H?í«4J:,áÅóùvÁ¾Éò×#Lâ q~ÉÃhGaÔB:GÒí þ„îD¥§ 5ö?pîPÑï7öΛ]«úÏ †ÎÙ’@“)G7ô玞t¡ª‰éÔ#±×µ-VÏ““þ¯• ;ŠQ9-@w—) ÈGÐ.ažFYñ‘\\þüì¤89Û%ŽïÍX‹N¨ÂX.*]ËýüÆNè«&ýÇiT‰0‘”%D*‚öåÔ¸¤åìÆø?Aæ\ÒÒ£2°¥ûdÉžaG,ñD~ båNqž‡öºø…C?ÙŽ²ø…™AØÞïO¨“W°Müż!Kû0ÛÌe‰˜s„i¨UÇÁ×Gpˆ:Ï׋’Û¡Ö(®íÓ ¦D– :5¿y˜OACyHŠ•ÎFsÕÌÞ­wØ+èêa9?H(iÚªÄEt <&Iu.Ô°T…8 Ÿ*m¹aúã|SR ļ,S,ïŠZ²<óIö8ÕÜÛ<“¿ Wx’gÜJ “r1ïèäIa±çYüýE YX‚jõ÷yË!óݣܙç¹eå ÝuÓþÝñœoZ;±jê¶„ å0>ÍÂ>¾§[·Üú4Iû[²„8îÒþ hºeàÛµz3pÿúxÉ,A­•ŠÏŒ2Æ&cÃP|¹Ú~òðÀiþð»ûÂwÌ–TÚY§Ë}/¤´§™¾ôi‚“§gýsÒºWõ8GönU/ †¾eNÂT”î(½¦!Ù¬gÕŸ¬ÚÜÛ*E™¸™[W:Áð6(à \£RÚZsõÐÅ–Íå~f.y#úk•LîÏîÅ2hMÕ"¬F;^ž=7õ àXUJëÞ¯u É‘[®¹¥ÿùý¡â6+}P׆øö$°Å¶_èòúƹ[LtÅ'¡Õ?/”xG>æ47©#²Å®^/%J"beÂq?Ÿ|(Ð+o„O®Iâ ŒP6s.P\¹ºÞ2ñ6K¦lnnqèàÒv"^1 $51ˆS¬b¨IM!'°F4?H)[c Œã²u\Õ,ºÓ,É€iÈ꣣†$íïõ„w¶Š+Ý ßw¶ŬÃäܾÝ~ÙýEóW»)þ¡ße¤ £œ‘%Q$)f —'IuÊ€¦cœžÓïÓú©‰ghÚÕ\ty“j+å§|€âƒªÝÔJd1„èFóæ¼ i•òët ?G/ ¼Y8%̆‹!šòFÉ–>Û\Ε7m÷Ð'Ûp|4“ˆ·2 . ‰ö¬ºq½8çWd ['ÿ˜‘c0KA›ñCfu·ê‹}—h[R( qí»ï³’Œ‡!DŠv2pˆ¢¶|UÃ~©~£:ôæ-!Ì×õ%4›‰šVþ‚Ë4#SÕ¯¶zü=‘ƒÒ˜ésÅ&‘Aÿò¤2"*Öôúäšy.J„±McÒãv)5$rjÞ‘SÔ¹%ã3¾½µ9vLÁ9ÿöce1UYŒIÔð›Ï)$weCšF¯Öoø|†+Q.yÖ¾dª% ?Uæ­KV +;(¢j©ö¹Ö7ŒU•éòG #FúÅÌ¥¢ÚÒ‡_3ñzO5ÁtUutùü˦ qÜm(Aô_á3áý”±¸öÌjMñ1áàJ¥1ô¾7¤Vk'Ôk hÛe®üÍyUjA…ÄÇQœ–"Ð÷bÔ²v,[?`ó‘ì?œaôÄ.ÖÀ ƒŽŸP±Ä ÖUå.˜¦|x¼iaêmÎÙóH§3ÕkÂ}=Ör¥À+[ë¬Ðk¥®òË¢,÷)XÔyZ_—äJ•ÀI7æ&/ËWÉà”pï Ìú ƒ8EŽ‘.ȲSÓ5‰Ë šu,W>}>Jç®O¼7ÉDË­khbqOÅyCv²“A“ïYÂs•æ;ý’Éý× ¹óøÀa¦3”©ÄØ F~@g“ú0ÁÒõÎk:&2ôJ©!¯)¿a{˱¸¹N¢Ü{F!])Ù´öPÍfOO¼Ì5ÈCn(´–ób$¹XË<œ‰Ïâ¼2'qWµ¦#3WXgh˜³¸ Ë?Cx‚>@õ°Š¢|Ð'´ˆž<'‘XTA¥\ŠL~C»g3ûxG;äåõºÊ™Æˆ¾-°Œ|ˆ”¬4å‘=Øöà&6ËT~_oƒ·íïzÃ’kÚ‰O8À$ÐÌä+íÜÈ'@ï€7‰X,:vKŽ©‚±ƒ|Ö Q”ìÙó]Àgþ”!3E‡':ò> E¬ÆsŒ1nIæO,0 ]M~ÔB×&¬Æ„]£ç-}CNÃTlê°^Z0¯~šsɪª¬xD’Ê³ÏØ?Øfh—êAcѵâ"åIõú£þg?;GǨ0½­G¢â9×›hoNz‹¢ƒãY',‘(Ø/DÓ`2…LRȾ[ì½uñn õˆÂíi>“%jU£¢7À›3þ0é‡qý´?0Çç·Ÿv2® ¶=Žíö¿G¿4È Éù•¾G1ÔX7Þ"vzVcö£´}K„m£wÐ0íz^E–Ìâ´nk¹bW¸ìÞð}à¯Å¨j,ýkE<°0›Pf“ii¨Ì4ûlòa\yÍö‹ŠÙ/ªÜg™à1Î`œéM•«áæ¯áÉa+Øjÿ. ¹Ê¯\Ô¶.D©^jýþÀüÏðŠf«¤›Ò`Ûõ¢Q*ë=„5>S[¾ñ‰ÖC‡tXxì©t˜“^ò(Cªà;“rt“hÚR)Á7+õ±js92•.lòñ§Å>.­è÷{»Á™Ä׈j-…dœ§l{†ìíÇu„ÂÈ}bï†æcâÏ»úÖÙyÐññîÞ¡T µ?(È‚jœ¤@^« û|\,W`ƒÌ·Ïi– ‡â@ŽmR%HIô÷ˆw…GÐJëödßïqÝ!då]íE :ÊÕ‘Ú'B7ß/=j•u-CËÊr}?®ýwé|OöJ>Õ8ŠÅs0-ò ‘ì,×àÝìŸ_' y`Þ÷vÒ=¼–9%Ü~G zŽÂjꉚj)ˆüÁæì¢9—¥VƲÌçNw‚FøŽRbR¤:ÔGCº÷ñË ®àb‹â@…¦~f‡E•ÙþæÉa| )0/Ëtª‰6ÜŸás'\ºbÒK€ùSÄBÍ`ÖÐÛN³¬3ÏQÔF˜Äí­Á"å §x/ñÓÅÞŒ¨d#¶¹ä‘¼)̈…Ñ› ” UÌU7üpªeÔ€r»ëG‰X­™aÏÉÒõ„®3«‰Š'ÕU&Á·DàfÁN+K¦Šì©o,óŠÏ c>o†ÒÈÄMçRhjmj×øŽA·vË×°#Uù&ÔBñK´ìõg+á©ÀÓ*R­ÂÙªáB˜ˆgsåØ^“¦ÌŽ‘B ˆµ›c•Ü»¡Ž º.ê²Si“ùcp§ßžFM·ÜÍ.y8ÅÄ‹åòá”?¾úDë qzÞP=äÖÏÀ|êziEàpŸ¾ËÂѪÐSwOõ°î$?ˆÔåêÄm`²Kæö±ã× ¾ŽÍÄÐpw®õTb¤îìõÿ°…Lï,šÈ!\7¾J8:óÒQRû­_ºYšàÄýjã$¬;Ú±I·&¼ƒ.TB­ak=†¸>1“5&©…o3:÷½! ßÞ¯/ÂiøÜ¼¢ n”BBܮ󓵂 Õý$Tß9r2%,#Ý—ULP˜ÊSñˆìü¡€ÂiY}YÛzÒÀÎQñÉãHÁÛÀÙ,ÄNZ—ó‘ªSÃæ«Ù7§\ü$UT9áAƒ°anLL©€ÅÍP\)V/Ž!\¡U')ÇÉ+@HòVã}n·ê¡_س1¾Üßø½Ëæ«ÀݰFÚ ‡q Üµy`‰4¤†F<…DÐ}JŠ B-X¸Uõ2­™D©ºƒ‰îWÚé>óH6 ç¯6kÅKâ_ 0Kðò—Œ¹@Ë )CXœÐ=ü}Å÷#‘ééBŽ‘,®8{‚F_rB÷ßÇñ\+f¨_Ȳ{1̆FÁ·´Êí¸¤&ìÞ²ÆPò^ë5²e)³"# Ü3ÞøR8Îm‹ÌQ!¾ªà œ5¸ P´x¹Ôr夤ÎoÙb”ù3Éõ?hæpzš«“顳Éïq±gòóPus²wj€ÝæÛóÖÞîp*i»fÑ/œü+° û~5½ ˜Œ<|©PW0-XßcË\5­2´AË h w‘qaÃ*} gúzð»MGYš$å׆aÐdÆ[³´»U‚ŒRe*V1õÃrìòOy%nÒ¼sËa¹„Ò5\ÿD*ù9„™—SkÎöl3QÝåñIÚú®ß%ÙAp…nL  ùO©8ÐBÞ¤$Z\,òAï¶ÝNü`\ÿ(uÍ“Sï‡çÐÜ#ÎAòÞÙÃÝÉ Y e\”IX£Íþ2¦:€1ÔážÚ¾L÷Ž¢Î’€ÿ©ly„@O“|Æ‹À¾Ô]w¸W÷Gh{ƒhÚøî3ßè­Ïíö‡%.ÓÝ¥e#œŒ|áfWzI+ãe±Í7?{|Žˆ@¦µXà3ᙵ%LòÁÅŽßÕߘ¿ Á–ÒÓŽi‘¡Þ/«=UøQñ®à²2ØÎãŠu1¯á”‰yï ¬¶à;c¼«­Þ³sû$IÂL©~ÒÞFâ°'v?Ó§s•ÊÁAÊÜ3Z:x¹±,úOß‹¨z/©:®”iõ—fÜ´M_Céïã#×™>—“üz²'°“ ƒ÷ñž±¢ô«tå7K^mOmvaÀ¦ =¤Ä;›M}ç~_ñ2î´.h–J"<5mÀ„ ³Œ¼‰m],<Àà %³š>%$ñ„ÏéÐPïC^?êåßsŠXƒ‡ qa>³ƒ¨”Øb•2|xCmMã%Frب4ÊLȯšcœRø7›û.°KÕ"~¬@kÚÍÿLZh Ãô:Pµ†íü@ïÍþ¦Ç…>ÚÈÎýõ‘\ñÝ«ˆ"Ka6lþ+€ÑiápGÊ…;0-Îy™àÆZx0`¿9lnÀšF\sXaü¼DÊÖQrFýÐøh>)Œª™Ÿí)òú:|Õ#^\m8ïv¶MÞvðÐ*¶ÙC2(¬`g "ÒÐ{¬É¾—Þq8ü•¸ÍÔø›qDZ ˜} 1¢ ïèןøH¹G¹$÷×Õ³óéy[!ÓYC'§Æ]ù˜OŒë¹;68âp¤ÝK›GW«¾'á –*ÛøAï1ÊâÂ;¨vg׿z…’Óôï1Ž…¯ðºD+7ß?L¶éQew¨dƒ×Mê²kŽŒÈ/‚̦ô»Ç>ºNçÖ #eÑ×p=ÁP9Füü{xÚ˜« §“f:«Ù[!SÞ_bóÞâ n§:Y¨l5Oå' <¤ý ´ƒ»RšIUõ|n,Œ:09]•aƒˆò]‹À±WËaHÚ Âü¦Š[T¥¨ö„a韄s_âË)-Þ‘Å.}G))¸ŽL…ZéR|Ï,\-mò?HF´•È´­ü<Ù Ss¤‘×B><5Ô퉧lMÌ"üõéß–â ‘ů£xc /RtH™êËdlÉF÷Éß„n/ ‚R!½¹ o ‘¼ Þ¢*=qÊ]Ù#…8°È…Ðß…ñ:ÿ×o.áÝðØ×bbØgg\À'ú˜µ”;³‘Q>w§ÀoÚâXHéŠ Î(„kW% Í.ÐCçe M~BÛÀ½q÷Ó¶7_ IëÑ÷ @£u‘ó@UQ _î,^"JÝøtÎ!åçÛbä¡·Ó7Ç»b}ª99½Í±Xæ¯U3°K«=vhçz'È9=ý)J/â’î¬/çü~«$ÂzNÄYP)3‰ˆ=}aeÂŽŸ‡r–R:LR‡³û}µ€¿êÁ(ÁÅoÔ†˜-ô*-ùË {™l‡B?ç—¸"B,$¡eŽv–è£2"Æ|à3}T탼qZ‰’c<•²N?[°&)ûCñ”ƒ\lí¨:²ý>0pM¯†D÷ü‘Z%½#8ÁDÉæ¥mq”××gŸ³ŽÊ][4Ä…{‡bÂ/&Å>Ñ@·K"¯.vrö:qÚ…ðèMªý¡`ŸDh¯±å`%Ѓ¸S3K3+:8,ÿ”²$ÛÔË"«ŸÇ-‰žh/%Ï\Û‰ÀÔàM-5p'‚zۇͅ€~Þ‘)däTÐ+TBfÝႎŒ½a’y²¾[ÁÞNQ½]WÄÍ:‰ûËîl¤†Ø§ÛY·…cO»ágôP›ùk•¶†V[¿`ͪ´BZþ­(zQµ a¯jP»LÁÓ™"Y òeÈÒW&ÃîSXWöÛäH¶?:“J<>îè–2â»6* ‹)ݪ$ã‹™ç²ðWF=ØJdH+ãŸÙ"ÂÔkYiK·Xg‰© ÇÔ·-X¶é(#Ø-ZÝñú̧¹Ì ¾.úFEM&ù ¡ö-`¹1¬ g# U˜:yŠÖŠ•MyY—Oü× îÕ",/M+ïé<ÒU²iMÅ™I>=“¨„“gŠRS(´x‘hùZÞüšÆý¤¸?3šÄÝv)?™XèПµ¡ ³ lƒ¢‹óÏnQ—×ÅNσm‹±“BÛ´,Ôyêr¾±=/·º:Öêa¥bkËk°½n}p®Wìì›”ʇ½öGwH´¼.@X†STš½ö)\à+dQÝ3BÙ )SÚ&#äƒXDqÚ›5«l±¸Û1¿½üî)Û=˜ƒ©ÐºgšdÄòÉ(ú^8ª}W>C‰ek— –¬¤rWŸ¯µ‘‘'¯õÁ–pijL/?€ r‘¼¡bºuñ€’ÃC 4:!Ò 6´Ô¶—„×òødúÜØ ^nê>KÙMÒ—rj~˜6Ùîùk€¢ 9Cå,F¥“•ºª…Å9´‚h2ÅõoÓÙßùÉé’UçXâuð"é¿IªR-™5©SN Eº¸øº*iŠòYÉxù™y§‰þLAˆ¶ÐÃ`F:½eº%}OÔýy‡Iž-8³ º0ñùgû}?ƒAž¨©Ëû0c,ü¬až “@lI53÷~g&Tb^Ä©´¥÷”‘ûå]NAûdÓ»Æmb2g—{*ì;zF–1(1¡Ý8; Hãw†”Õ.ºÍ“f³ .FÄE—Ê-Y˜J5 „¹¤2Ji$ýèAœ,´¨ÚªŸ¿Ü® løÍ•oê–nÒ®¬;)BR8’—i>Š+Û2D Z6ž}|)Æ Nó%ÁvsWËÍÇoô*•̸ ^Ñ„‰¨ÓDôÕÑ|,Á@ó©‡ìüeZEiÃSp{€…ؽ« ¾™`X"þ€¬šìüŽP“ 8¹’ëÔº¬‰†¸ḸT-aŠ„fBbRý÷ ]ÚÔyÄŒ(]À“S%=IKhzì5ÿn²Áo½_ý¹Žço¥ód¥ÖÀxy›f2ÊÂêVÕ]‚„ï)bj‹ÈD¬Šˆ›/͘ÌüõË® ÕŒÙáèf&KXéöëeéòô¦9_î*4U®Ë+s,ÈòеÐ).®Sß7w•Q0 ɵË8ÊÇÀK$„Óª ›í‰êÜr`©FC²‚Œ|O&`HB‹A.$ÒÏÀǯ ^Â0 ýV:%ó2I Ÿbô$§¼‹UT$9"ÇVú¼/ÌÌ)>á¶½^F:¾Þ¶BÊ4†2í·B îöˆ2®©u,û=9üœ©h")˜†gD€ä fQd¶¼Ç² 5†ï€~޲ (ÜpfO·ù~ãÃÖë&Fbq „r j÷N¨·»È1÷ïÂè`ýáÔDÆ0¾‚z!9…m¡}öå娰C„½ø=‰î ›ø–Å.Ç–aÃW²ïOšÉú+\dcýY‡ŒG<ÔD8H¢1Dð°ÈNÆ~TmÏëYù;[Cáªi” –jp1­ÚŒ“Zm½»7ÖÜÜ•'ú‚j”¿áè*Æ1ÿJÚA_ÃWíÖC[)‚öcU¶²ÁCpˆq­¾Ê²XP?Ø‚fuu@§92’/TÑVŒŒ°¯°Cß„ªùÉ¥Áœ>ÙñCèÐs,xë=ZæuÛv½ˆI“ åõ¨´§÷æèlÜ3[äE9ل٦7mâÀâ³ä¹½=¤N;‚ËŽi{"ªK± ‰ïGžýf`‹#ÀK¼+ðÙTŸ‘)‚:M› †Ÿm“ iÍár^1.<1• ø¤¯Úvà7±– ó“a–!ay~ÕZüé{DÇBFj‹Ð1—Ã-·Ë‘cqRöªŸHϵ§m1ZGÒý›¢zµA£–žÃ|/‘PS0 ~HÑý=Um(² SØ©ÁtŒ ýír2u8.î>8“â€ë˜ªŽp0Ýï´™„u‰§ÏÁTŒf?rôLˆ]»MMÅ 8Ÿ'úK•*´KXHï»Ï£¼›Õø>GMË¥Þ©p‰ô‚D¤Feq䛢‹|æƒ+_º):âÌka2Ïä\¥µ°™”|&(±Ï-/%!Ûj¨ÿ+¶Ê§~ýä‹wŒÛxãàÕÃÕÍá;ÊûnžX{Î|ÈO-v‚0!Ðûžmª7ŠòÆ'øåÍî"”¾‹ ¥Q4FÔ‚˜aãµõP+!«’E¹Aºì'— %äMÑP‰¨ƒ?¬ÞKt9Û+à©â0˜'9C‰¯Tª8L^ý ž™å°b%yÑ—‡Z讹üb¤BtÙ&ˆVаV¶ ÍNþÈ—HÉkÎU±£‡]ÆGO•Np,eÔ¿Á¥ »å%ØÇÿ [\Èò•m>¿Ö4¢¶Cž“I(Á‡ž´xª Æ[N²¬+m$HšM‡óáq0§ßö>vaϧ²¡yw3Ò Þ—gB•ä‰\Ž]‘ö!7ŽéÃ’@ÿÏuÏ3b™3|w ×A‰ïFP ŠgõeĘ® ¯…ñq-ØAΣ1ã »íö|{!œ‰hyq ‚V«n',bÝáàÃ< Ãoæøm[+ó:ˆPõ‘yra#Øî;á¾c™3IÕæ¤N‚9{µ÷#²”m‹Ü¨™%–¥qðz*6Xþ¬åêõøï>¨QØ'E—WË(¯ÁÏhÉÔGH‹vñ_;B~õ]kCêUþ"ÛPÖ«Åùq5ÍÝ6NAª¹…l>‚Ìvqݧö‚‚Ló?þ“mlýjQVW”O© Y3Àªó#ƒm ¦›"B+ôdÀ/î†äóØ¿Q„"|¬t„ø,løWYëæ3º‹Ž­ÏÄÏ”wçõ.=t%š¬À$á½ß}´È<e¢ùÌ|• LE`øÖõ7˜î<;\k³Òw[²ey„Œ®|ÀòOzÏt?èN‘Åïúµ¥ÙÅÇï;>¨‚(ׇ<üæÏ.°oç÷"Œ·_žÄÛÙ/«EGw¤çsAû¿EEîˆK H •Ç{@’#+FŒr\Æý"WvwËT•‹tôJóèN1œðå!ŠpSÏߊÿ¨ÎëU8¹Kú'ÈשÑñª¤´f _ÌЀÚŽ˜Y´ø.£œ—Cð•°*½/½ 6}Æ»Íï$B+ <,(ïÃË‘@ãè­ÎJùØžtPÄl¿k"3Â54ó°Ìä>„çY`[jÕÆýjoÏÜg—>Qœù­ÒŒòš}Wí6[Û›ºúé… ëS°ïøŸ‚俊•zážjl[ x¬R+øn^^B_Ö»þA'/Åeÿ͘&F@ê|ÙKGi*í.Žä]oìvÃ6â0ñÓ”6‰t™¼;ÊÌÎÍ]Xir þ{…m$Õw,oËŒ:C?À}‘âø¨c+è=m:n´ÿÚ燂SÇü¸H®E”8qN‡’2.·´S+KÇ›ýéìu‚N%̘Qˆ£­¡Æ^0×–Z-§¦µ ¼€×`Õwb‰9˜9¾Š¦7b€ÊÉRü¯!-«Ä+¥“ˆG•?cÜüca Ò’´kÎtäŽÓ.Áâ kô¬ÊÜV¢…ÃZa ’D¬—æõéƒ:d'LÄîa*¡…ˆ.¼¯ëÛ ŽB¸UŸ’ LÁ>.pÙQA5E-¢.âŸï€)mæ±v¦áÃVˆ"Ô$ËËLò€;ÀÅ`ÛÍ›.M©%¼fÒ8$ŽAÿ60¡žh'¦]½¹Rí0¼®²T À}@÷±ª û´ÀbÃÈRtÕºJ¦VÛË‚ à©JÚøŒv°ªüŒ×38! µ{+;DL(EZЮ¥uOùZèL¸¢àµêæ Ðs²<Ÿàh È•èºý¥ßQ|2ìâG~dŽoÆ—¨Ev_Ú Dç’¯;ÛEïP_ÌõÍaÁ»(b™äÆc»¡9²ÆÛ.œžÃ‹P¥I„ú@ä·XªØ~ð-ꕉÇÁ£®IURÎòíº&Yïý^ñs®$‹FuÂ?ªØ%¨wè4á»mRN?|Ýv[½b"uƒé“Ø“:læ:U‹bsìsO^67Í‘FhŠÔB²ÑÂkPK0Ànj;m£[‘¤)ؽ™Ñ½ÑÎöNy=DJñLÖgËuφ²¶MC’¸’6<­UUì;äIÚä­/’¤ž·FFF‰zt, Ž"ÆÆ–Ò8ÏU%²$ŸÈ¦º\b¯ãÕîì1ÚâˆNMjQïV{ç->â1öz ®÷ÖÈá ²]b—Öcbm«hMÜëG®ÀÝ%ެ¶gÏÎ…ê}¡ #¶ƒ–pæ5%)¢Á”U™Ç©)Û ¿µZZÙjØ`ýÕARúi=ªÓo¶AªG‚–MmÞ¹ <]ÁüA­§’²1Af¥"­7åîýBÂJµKð†×Ÿ©øO h‹Ã?¹ûÌ–õµ?1WæB‡OÇ·€¬³,sqǼsM† ˜N¡’ÃpÉF7Õ–‡¡½F–}µŸ¢¶5 ~;R(Ûõaˆ*¹^"NÉ Ìµ×ûxÙZž3Õåâ‡Æ¶k;¼%[õJé›®»w‚3Q1"y°I=©¿=øÓÈ;p·VV;M…“ª?È¡·ù Cîøô²²³÷iØ]~d§3¸ó¸€EP;Cf|ÒO­ã$ª;èŽùÊ?út„pëµZiñu¶ÁÚ˜Tï}ø2WµzpXÝÌ@àlä } 8v4l¹5`/GÅ]³­y,púÔ8ýNÈëz<ä`¦ x?æÍ¹Uýöéÿ –"iÝ£w=cܾ²¶ÂwuЇ¹¡…—íõÙÖKîYr€ˆÙï°Ov‹ñ绌ß{‹ZB›¼Óñ%"ÛÄÃ:Ì"ÄtÝ…è:K8mmfL]°0Ú¸÷Äå5ßï§,M&uï¸,}3`a£ÌÙµ×øïå¤r©/"_b—ÃÈ÷¬t螌àÂ_W–åCÇfcKyò¿‡ª9¯UÇ: v3âƜԖ‚Ê‹Ý[\AKËå÷ P@­JkâFPÕi¢Z }ïJxÄÑ ÔØ¹C1±– ‰IyåªÐÂ{†1ððÇ¢ 3ŽÓ&˜=ªñò=£ƒÎ]ëèÅæ0q8â÷`;E´q¦Ü±?ï<3ƒCĹºwªÓèMW´VøIê@G¹T»™ûî¹­íU¢pYjžÜKÝ$q°bOAÐ4²ÌèCc\s`×q–id®óá~/œê”daÿvi”•;á蔪lï{mC¥j'l-¡ý¾“Ú¸,Õ><Áê¯×½½›u¦CVV)¬8x½Á¹|%yäNdj ]røèåy;Çæ¬Öñ_ß=Ö.D=[L†‹¤Q‘=>ÒÉ÷eì/7„ót³)Ø”Iÿϼl‚ü¿Wl&âÄíA¤ÓôýI á¬Ë9á}¥!5ð=t‘‘Áߘiž#ú8´öùÏ9rï÷ôùl–G?xgH©Ê‘ ï<¬PšnS­âƒÚKôu\ Aö1Né”`6«?»b3äuó…sWS3k;8\sÍÁV’t§µs󰱚â(ÐúíaLyTç'ÏÂ5+$½Dþ7Üu’Õ€n?q˜dÖŽ(ÍU?OïM½J¯ õÐRco\r´w£÷ZÁN¸þÛ÷í}¤§ûj3ƒG™.°¼j{'>Ãd ‚û“ヰôÊÑ¿ê%?¼Å‡°ÝA»Ìƒs¬R"0¶ÎÈ/>·ªç\C»2A§¼ã@9öC‹…´-xïÅ'QÅÑ\ÿR§d[‹¡ÁÉûH‰be%Ž%ç«•#`ÙTqj‡ÿМ!f”êgð&}g”üJ-ú?‹Yúcùχ^‚â7 ó<5«¹{’Åò¡K´ª š–“Ú`» >›êÝJèðrg#Ñ?„¼åÀÆ™(Ϙ‘rzáÞ{)—ëU™NižjœýàlKà· EA¦«•ÓE-[ìWo1Þòµ5Mœdy?¤.ÙjOÒSa)ºZ"E3ùh›”¥ãÄdOUL8Ã0œLœPyý¶xÊ þ  ¤/ËR< Úp~MÑ~qU»¦ÈC&ÓmR‰……Î<ík0Ø<àœñà*q¸uϳ‰×¾Y½šOÆfg|ìý7‘×hHýãH6x.\\ü !j—™p]µ‚ ¯ÊOf2ñsZQ¨ZWNRè¼BïMD;Õ—¢Yt ±ÑŸ)Áh:Ù8€‡ -œòµîËô*pÐ ð–Ž*YØcÅÅ©7/¸WZºß¡»AÙ„´Æ5vy#÷Êã ú¹#ŸDÙÅ»ñü'ÒŽ–p®V_ùjiÓWÇŠé„¡ìgížÍʾée|ÓÌZ“»"V„µ²™pW`HôÈ"¤|ùvѼH ­:_: íf,]Ð2®û_y»½§EÃò6T{‡õ± ÛFMµzY£S´¥RYOOy–®4¯ÂyÛLŸ7êl9,d‘Œ6ýI¾ûùlfáÝè‹×|}vÎd"éïÛk'œ¼¡œ 3¯ÐÛ»J‹ÐØAb3bÑí:ÙȰ™Ðwp÷ÎÈÑÉ]<ïh@"·)ùk†$6Ñì4ÚçÙR n\v§bSà·‰˜¨P7+õ>sîùŒŒs£=ª 9&ÜuC1.ÁïŽ(y®÷{ G1ù4xù l¶ô.,×Ëà=ë.’˜b(—QÄ–W”H×K¼Ãhè+`+ìÌ{'_¸ìý¾…`óOdÞ¾¦yO6ñ¨ÈPDöÇ‹O·&âò˜!fÐK|âêÝ}YrÉ›Œߊ‘ciÝê×5Ñ8…¢§™y1¸"¤’ÛØ%ßè’ TšçNOò>hSúiêr°úCÜÐàÒCÞ› Í^Œ\”ÎEtL%V†·u©´l׈aëŠ †\ÎÙ®Åfh±-$ñ‡à˜ïÖb!t½ã«9ŒbÅbO–ÿ)Ý¿[ûA4øQ®n2hï7)ζõù—¸‡I äòí˜|`~õÀ8ø¸ù‡Ÿ¸è'ƒF¿WlûoŽëxŸ×„jÆ”&NÁÏæéò»ªÏd>ϤZ920ÙŒ Íä¬ñsÄ„À‘®_,6pzãé"?Èoab³p,®gLáFoQ‹IHoÎvNþ’$dæã¼P{çË7 À å¢¯Ï•<”{Š 9Éð¦»ÏÍ_žƒôÁnŽ(­öû2k ÔÙðBŽ#åâZÕRà7'ÿæˆ1×ÑÈ•H\UdY7ªd‘7\ö—ÙÎÐbÇl9õ„Ÿ´2BJ:ã1QRâÃ, ‡mŠ46fx0ȹŒOL2{’‰#}eº i?¸ œÿÓ>oýo„ a®ë~v]è#[(ûh+Ô"^bÓ’ÆÙžÇJiÎ1uFÒ¿]dèÅx2^dL[ ”™ Î VjH:÷`¶MµìÔ²•0OC Ý?Fš£Ø%+J*2xý«¬{e6ÚV9k­ô ¼â–n‰”šT¹"¾µ‡ ¡5°è¢7³»%`ãµ4g˜\ª ‘§’nÁÇ“_RŠpëÅ*â•냴ǿû…élt jÔ&‰Ò»U=æ©¢FÁOL‹_@[îJáÊ Êœßg($Ø'Ÿ¿~X÷Üûœ™ç„bææz’p‡ú[¢‹$}¹-ˆô˜ë 9%cC¤ÓOšØýIã&v÷<€!Xdr«TkóènªI¤ÕÖc^ìË-¬æc;ª½ˆlƒé>’®À#ìýÛB,¯VCf¥é\ºÎà5ŸÜéÿv èrö{ª‘CFB|,ÑCmǪíy ½I÷®Ê|ºÛaš ºÂ÷(º_üRI …­~ü¡Iµî þ;dh0JÄãdz.µŽ 6«+!;( ìD(©²¼ËrÖâÝxŒåëû¡±×Ôi»²°l/ïߎÊm© [Ò½6tðó'8(²t‚Kõg¥Ä_†ÏzÞ±QžCûª_ Õ¼C© ±¹Å+¢ÜB§ý^n¸lzÊN4@wæÓ‰×#ÂMl+sQFý»˜{Y’qNÍ;òûÍDŽ$>ÌJbÎq盡NS<Ä<"ºÆ`Ú~t~ Zyûþ£ÐvPÀåa£qæwüû™kK€\¸w!=-ç]EB…1Ò÷ò¼¢Ú…"ñý7Ô_²I"½ÄÛÏ•Ìð,Ã^À˜—.•àÈ] Æ"_§âÀ¦=ÿù/pm«½>Lš ¿ò§:Îm¦ çÍ:°+¨æÖ‡­åö²T'L9CÈ”½¨×~,No2e¯¹ó½ôÆô¹Ç¯Í™†—Kˆe'®Z®c@ĺeV©B×¾Ö.ØìûMU†ìO¯z—QAÁé€-{?^¾ö=Ÿ‹3»;¼v=EcÓ¢§»àö„‡¨¬ü\ „ßìó$½Ã\DY„éÑ©J0:½`»â™2 Ú`£¬é³îºç%PóÏ"6]\v`Â~í`;S`µFðtÁSéB×&-Ù›å“Bnú…h?££¬?i9Ú„E-òY*Æ—ÿyP„ñp®\wµÿÉhfd‹ _y™™ïK{§!qo¶lS±\ûhláý'Cí|ÙiÒ‰Àålu›A †L(ó̪É\ø²®s€(~ ÿ'~æÕS0 J¾+Imdìÿ=­¯Ugô1¬Ë‹œëLÙ‰óóKàxÓA!†\¾Ó¸·ž *‚Ö›ÐùTÝ ‰ij¼ñ‰Ù‘w'˜­ô<Ü­ˆwêõ·ÀM[(i}„ÂÔÏW[Z9 Œ ïz3ÞÀ‰›Ì˜ µt«ƒØÕ¸«¯›•úί¿¾§¼MOÓåÀ”¶rð¨Ðß¹x©|Nô/ö±Gª±3±¯6•›–:u¹¬Åptþñ1Á m•9®Ã½x×dËÀðü‰¯– Á3>¥é`ü´¼AðÒ¾¤‹X9Ãòh¥¾æÏ+ÍóPIl7¸1Œ'M‘Äó¥J܉gé\»|g¶`—C¹°Ý¸>ÓÏcœ¯K* /èU´\¶Œ+_G­ß]À·õéçDÒ‰)ÔÊ‚>¥OŸͯ3VÈ©ØD‡·»êÙåêŸvÙ". áK’€ìb̛ݑ¤>˜7¶‚(¤ÆÙ½›×ç¼'H=VŽeÝvï (©pÓ$E;.IT¹J^¥E É&‚·UiÌZËpö¿¸~…dY­7 lŒì^ØF^”¬†S9Áa «µYÔµØ+õM¡× D/‘èÔ´˜(]»ÅÌœ²àm€ÞLÄÒ]+MÍXæOèb ‹š‘OFÚëü8äe^§ÐÑžJæã¦jž±×™~Љ³K€å¦*‡*Ÿ¾FXòcNݸùòâ“•"ôDj¾ ÿÜø©–„±¶öJvH…9jõNåŸøý•âk%_Í,SFÕ G‹(í}“IÔí-ºT8˜³g±¦x‘= µA2×!-^ñ¡»û¨} èŠü˜ºà}˜‘hã’ <’3€vU…øœ?œÖÔåó™5þ[jd¨è;ä³9ŠˆfÚ‹«möÝë‡'EO… tâðz :þ$©„ÒútP0DD¿W‡JV|¾[Ê»m™¢…ÉØ²J®Ú½*SiÂÜN¶bnuºÁÄ)b¯aê8’ùjˆ«—WkDBQmCîGÚÍ:õ#íkÕÞp•£»v.ç uDÀŒ·™ŠÀ¥ê0fÄí9 m¤/œzž>­`0½¼–ƒ±+«ûôˆ'¹“b`ti¶ädrþÓþ瑯ô ä+Ñúc>7áçìlýv—ÿœj˜ikSüd~Ýá–þ~äDwÏÏ 0ÕÁ ÎNOS®02и…unn£…Ïéf¢þ…nÚ8,!V,,m¸\²¦öed"´4êûv’4ס²=…=^«w¹ž´Ž¡ü (Óm·áŽÝ£º•…|POˆ»è¼Ä¸}Ÿ °pê7ßÔ1†ÛÌ!&–8’济¢-ë}©Ubp꺎w4K½š×n["'?Z$~RF§·;p¸N Å2{µ}“•B¤ØReÜ@•‰yuá¯áíIû®t‰šcÈh‰ԃu *I»>×±# —Öæ–…ž}ÚfTüý"iòêyQŽwÇͱ­°ÿ£à0μÓÑ6ZF ÌÕ]ó)> elÞϧËüuÎüÓü§[}×5Î}=˜S²ßÏ0},×}J"1cÁ©­õ¯Ð O&D;…îû‚¬ û-™?vÿhk"z;üê(Sldà£÷Î…©6XÚ·Y_–¤ÒøÜh™ÚÄC'Û+|—§¡‘¡=ÈÇî¸ýœù„“¹Tí4}Ÿ=˜O]ÒóX>?)ŒmÍ6Z7¶ì¢ÉcÀ¶ s 4ÏMúù*Жî I3è@Ó4r@¶¦ZmÁÕØªä7€ÉØ& Yvá‡-ôîòc_®Æ“ çÐï“]3¨»5:•&Ú‡5/˜È)z*].j»_]^º‘.+Â+S#M¥˜þÿKE÷¿¦a a^ œøÞ õ²ZM¸ö÷·ýw”ƒñæš B"=~,9Ç(1¦Ð©Åï{Õrêê½u¢f™!²ËÊ«W—0M>Öá sÃÞ£MŒïŽÕ€m­ñ­ËCÑg¨’‚ZyöCݰ|tˆ(®»Çz-tì6ä²Y·EÁ9Ìœsíå}™Ó ý9Å}8Æ‹y*IÙÕm¦£½Ê<¨~c$Ì݉Qu6Ty†#°"s_ĽY§§R}ÜÖn ì«ð×Ä3Ñ”HÜÅÞ†ûzd8&'ý )÷à£J ±ž€ Ëûo% ±Sªs¹žÎ §&ü@}Ž4 (\íŠ#‚yì*4ê÷2[÷‰b»ºòŠZ÷‘oË–r_nW¿­±jÓþ g2<\_§Ã$¥_ ˆ§"qFW…n_ pöe¥¹<̳oK¶xjß…5ÑI{tf$VðXj½Zt‚jÀ…€G€P–ã\¯U\G¼à(ÝÞBE»=2’Ð#^ù\Ä !éwñQqk¥l01ì(üãí\œ`)«»£Ô×Kz.ó5Ê—¾º&Ùæ»ÌFòl»}|ŠƒÊ-hËÁ<Üûû°n-â³e‰JÊR© °ò¹n£² ÂÄølîª=ø¦ ÑÒäOE&oEòö«‡»êŸ‡›òÔסöUzÄmc™–Êf]ZXyðz{‘ý¦Ÿ3æñ[XieNKL~¹ûeŒ‹ü£õàd*ÄÓsT\á•üàÂÏ"«zÕ‘O[ã§1ÌïÖÇÁ&‰Q¼»I÷6ž’ìY“xn`Y _®3ûAŠë×ðè)'ó¡Okª(Æ’ÉHoéùwjú4äóMøûÝG¥¥lr-#º‹x¶[pº^¾&`õ^(ß[Ÿ©°OÚî-þU.–êç>îùäÖªÑSqrcsö‚á–4’`‰²½_ãûQœ¸È/¹m­JkÝ‘uÁ b0<#¿‰!ZÛSÓ»ˆF°:\O 9ºXG!Û:,^€ûO·¾`0GO?’­×M ôLû¡Öµ­kR28,H÷‚zñlça-pž½è‹!÷Wÿ€X.!ÈÂ&šªƒ‰¶´zEC`¼œ¾F²@ D‰'ÄòλHhM°7-1¯7+ÅÉênž‡,I†Rñ‰‘™žLÞïN EéÔüÍy‘°¯3†Y$ƒàGŽY¹‹=—šŸ|JÆ”ùò5;EZabÛÇ °Å^¶˜­Û°=ó+ÀŽ]ŸªÁWÁÞvõ¼9LK×™ÐÞ$0Ë?jí„@»Ù_Q.‰ìÜDd:ùñ¼Ö•RuîÃqª&]q&4£å¯Þ811Jê"ÅQ»˜bÕtçe4“0à|R •^(r$Ï\ÿ€ÇŒOþ.³r&ü 2E$kå£OnÇ ý ƒ†3c|…í?Cš\Þ »K”²j]¬bÅ‘_Œ$yÒÂË(‚¤¹¡½ïæ4Á£µG çzv!OçX 1Kü˽KrHRh&æðñAÇyZ2¹IÖ/¾S-Û\j#¥þ 6í ¨‚¯tG]_RŒQصàÈ»èÒ‚*•Rb{Cðò39r§‰%¤~ú5SýNöC¾žÀ}XÝÄnƒó}GÉ:ô'½j×­8 kuˆåÔÞþŒºôV§ ¥Žºëú ÅJ–öÍ*|Ùõ”CðÃG²fÑ¥~üÙËJ0 ÂYßôs\ˆ„ƒÅE³7ÊÒ§âh¦»/Äè¢ð/['/Ê,¸Ô¡ÅËd¾þ°mâ”U;CÁµ¤OpaÌ€ñ6¾úÎØ„n}‚Æ;uDFó~°K›ê‚ðÑèêäQ±d¥½äVb,á¦ÉZËZôÖñLÜ…¿Œý-'f…L ¼<p7'Y°‡ö–$ \¹#„Šk'¿5B§Ö?2᫺S«„Y,Ðþ¢¦µÙØ‹v¾°J‡³õ6f˜ä#gx0È9ؾ§Ô›–>ŠrH†ì‡“´{B¿%Xx+åA»§ytÛgÒj ݰr²¥VRìwé|]ÔCj†6ü¬ÏJ«¬2DÿC˜anTÊÛó„ä¥tîø³´CÞôDOAfG8È1˜P/F«r%yø¦} gi…ÿéã?´IŒÚ©ò“ç†IÞ7}8Lé¬c‰UA„noÞkBr Æ'¨sà0w2,oûÑ‹ÛDÌ ÄU B×®Û©:žWÓ ²šŒ’ØmBïƒgöœæt—åIÝi!ŽBp”ÓgÉMF{ü\‡¢åšÁëoB‘UÈÜ« ”Ì€÷‘„ŸGE5.èNê®@VqñðÍ„¶x&1Ò‡Þ¥.dÞd˜ºÓúØð\(.—jð{Áy‚o0Æ!)ÞÞ³1¬/æpVz 7r‘›ry±BúåªQmèrCt*Õ{h‰X{c«ÅËóMz™ûP ®Õ¦õÿÚªÍ|VIòoÌ µÌÉ>°Û{U³<‡ IΥ΃GùR-h±çÌ&ÿÁüƒGeÁ@Ø^%¦?wÂb€>3…j(ËåXúÛ³Ø9Ž—¯Å6Yˆ=™`ÔƒÄ vw`Ø/[OÁ¢g+òÊ£LôÕýÃcÊ.ú{ˆz‚[WÐJß_Ñþ`¼Ž½‰iDë‘ʧ—5´Vª?¹™´k~|J¾öÐQÔÃP’úðK‚PIô퓲¸Ûì³)?ÿ(ZA­À#¨ä¤¹“/@Ó‡I ÝÞ ëÏ3D³±6ÿ) þX“€pgÅ€±µ¯˜R^Õûø¨î³~)ØŠ‘×µ®)¦Gwå২~Zt@õèK(§¿¹tÌL…¹™AþK[ôÂt|\Ûœ‚Q¸déÆLÅBè]÷áº;Kƒ^ÕÿpG¹Ñ#A;¾g³Vœºbüg«¹{=nTCµêRÁ_Zï¡aaðèDF\cMùRÃ7…[ð‘¦lPŽníèÌD$OBOzÂZÅtE‘õÝÝæ‘¤z©ûv/$Á ·ƒ ©í€3FÍ ÆÝë{ G4âž#p·ÞXÈ´Ÿâ’ˆ|f%Ð{2ÞEfy.ñrÔkk”A.Ày-‚pñvHMa#Â¥ŒÉf–r1/YÖ~ÀÀ{Ôó’« €ÊÀŸWQ¥²Åµ+lA/ÖûÅÜD˜Û…üu1 d²÷°ª¨")-"¬#B¡W5ÌÒ–ï²³ORHéáµÜ<_}z µ«æµÞ‚ÿœ8zÞh3ëÉÅJޓδŒXûDR†öHÅú5…í WD‘õwÌŒpÑE¶hj:Nt}I;iª ø>ïÉWÉÆÒѶ<AVQðç‰ÆCåDæº:&…#[5{véæ½²#?uÁ†Èqà ŸCÏá¾»B$«_%DÒ••–ü¨¢úŸjbÄéLÕVü¼  <ÄtNÿ­}Ïõhe·Ü¢+Ùg ¼ÏI'zf Y¿ö$äO—ßö—4fU˜«`Mš')¼|X|ºcjï¥ÛêTË·îdƒ‰*©l{ü<8ÕŠ|RLå…áîÎ$6õá —ÊªGã Ëå:%áB/=ûê(ɲ¿¡É!HMØS$™hs´•á•#¯½®'‹7p`&­ E…‘XF¶í’¼–xˆX_õ‘R.Ds0´öÄeqÇ­¨„­ë#Î^pÉüí¹[aƒ˜3ü«&Xþ­xù—ûæ¶f£ß ኡ3½ oA½ëA’ 4žaÞ¯{ž•nGêüZòršØÀ/idœ—¿þEåQþÆ~Á˜ÿ4*7 ¡•[Û˸pp»Ì,¯b6 œ í«~©'Ô96Ùw*(²WÌDŸ…‰ýâ-?z!˜^ñÅÑî„UBp¿=6CÓ—JDÀkÌX×7÷R+ ¬* yø¼Gõg1 Òú%~CfÏ+­DM½ª.pUö€¶YŸc %þªÏò…I«ä…n ºì,&¬9ç;ÔªwiÎÌêaCb*Led…Äf¢ý’y‚?…èËEòÿP’%JdÂçƒ ìq¿Dg¹ CÅçаÀ ¥X4¶!ÅÙ -P½õ1äˆ6T,FÝ8”ë´e%~05 endstream endobj 1302 0 obj << /Length1 1620 /Length2 13034 /Length3 0 /Length 13850 /Filter /FlateDecode >> stream xÚ­vUT\m–6\ƒ[ÁÝÝÝÝ] (¬Â-Á Á]A‚»'¸;'¸{pýù¾þ{zVÏ\ÍÌÅ9ë¼[ž-ÏÞo5…š&³¸Ø(v‚0³³° ´5tÕÌÌ­@`%fyˆ¹àMÌLM-é 4‡€ÀNRæ @hZ88ìüüüÈÔI°³·+ÈÆ {àgddú—ä/€…÷?5ožn 'ÍÛ‡Ðììt‚¼Aü5@İ9’ªjúò*²:Ym€,Ð èúV„š»…È ²:¹éÖ`W€Ã?K°“è¯ÒÜXÞ°ÄÝæ7g %èÍ èe tþKÅpº:‚ÜÜÞ¾ 7€«¹ä­0ädéànõWorkðß 9»‚ß,ßto`j`7ˆ›¥+Èx‹ª&%ó÷?T@'«Oÿ¦¿“g5ÐUUS—`ü¯÷êßvjoüC´¼€ÿDWlõ‡¿P$$À^_fv^63'÷ÛÚ½-?»ÿño ö•Í!® /€! ;àíýÏç_'パv²[ý51šs'«·!ûÁ_jKwW×7nÿÞû·¢ÿyþ{Ü@/ %òâ/°¥`ˆ]Zf:¤?§Tʰû';l¨sQVAÞÇJpg`ZÄ:™ÙSU(Ký¸ÀK³÷Ü¡óó¶ÃÎàO<ÚÎài.‰?}WÖ*M+/ãN«IZú‘îß³Y¥ßp¾ÿpãŒ`™Z‹Û†Y…]xD“¸{CÛ;4Ðß×yßµM̘‹D-hŽ|H‘ñ6s½ª³|ðàuGyè L³xŠ*W<4ô!ºÐü-Ü$ÍxÓ#«~.S˜Òþ¹é(AWGùé¸ÙRŸˆaITž»5L‡¤–K÷ë¶ÑM~X½º~Œ+H¥gÑ“î dî‹ç%†B õð·y#_Mƒµw¤(¹z×íê‰h°ï¯ô]¯<$D:¾¤R¹»]b|Dþò£,‰.LŽüz†yÝ>ÿÕ«@Ðf}ÀqSïÚSW{§ŸˆuyÊþ¢tŸì›âÛq~÷ÑX}'É 3ÌDl‚-Ðà Y“ý$¿áëW3$Oóþ9â zã—åÞÇ5wC¯ßU4ñ¢*XßhDu¤¢¡·>ªý ƒªkeÁñfÆ7?ÕlrÀÕ 9Mãy1´*J_•2½lAô%Hm C6f•°Ú½A+l'P¥b>;Õ…¡lbøÔVq#Ä 3^åô´cÖŽ5—¿d+×FóëÄ„§W3î3¥´ZíV1c²ę́¿GÖ­˜ì ¥„ÙžŽ:Ii}n×õKuƒ–Uô¶ã <,›xl ›ÏG@Üo΋ªŸÈ Ém8ƒD$X˜1òt ¾¯ÕLsÈ_Aeæ¬Ø-÷ÚñqZ+q¡ãnÅsn_{¾W²b@V_.¶Ý`úãô†„fÃf`ÚÒÇ’ …U-°å¿Q•‹bÃg¼Ñôî?ŒÐTð^§~ }8ôöþ¯µêïåî@6Åv‘p4+4ãVÌ)æ0pØ¡µ2ŠˆC˜<ºxú8w4vž`±¯]ŽH˜!2Ï1R*ÌŸÖ?Ffʼnt‚߸ÐTb& )7«áävÒyýDÜÛ/ë ØÐF—〬vêfüÐòÉa¸ ¿GH!xn~àK[X†Rf4lc™ƒj‚­x¨iÊñ=2Äaí +ñäÇÉ«sѯ&÷ª;-}[ă§!*q€N¬I^ËçÑŸr…¨lÃßÜ“Ž]ÄID†¼ÜRù£;>³«zûÏûÇ+“” Gâ"¬ã^9$±Sl¡)}ø)s½ŽR×/öÝçöšA½[‚ŽGܱÃSÍ0jj@‡‡øö¢ÁWµW¢ä§+úë,\‘1„Ý?”îÕ(Ø‘ëLù˜ëR@"hCêSx]©bó»4¡rËåVS^ç~˜ÝÕ¹ŽÝC<} Η o1®áîÏlW<¼H¸ÛtÖ¶OâªRȘuéØ@ÿZÒµ5â‘À|¤ ]U5ôGþ •ÔÀrOn›VJ÷…R¬º9ÿê*鹑jLÌxVwûÃ`³½K‡¼–$7MCÛ… x´ãû¤3£ËèŒýt­;Ô®„>eOþÒ‰¦ŠŸCC¬fØã#CS×H½7>]‹;ºM5Ç4 KeÜe;Ç ëŽî´ª+&ÖùÓ#K2[5cìÏ®NEœX·~p_Ä„ÙsDA~/pžô»’£þ&þ]‘¨`Š!Öåc‰²É'.Õ*½46ÃÆ¹K…äyäh%w-2ÔŸÜ4¹ ÃíÑ;WŒPw\“zß²ãÑ.Ø• _g 2×sBOÓ§‰Õi½7(ìÅ'›YcÝ GÔf%séM íX†Ï½sýNÑD¬™Ì&ë½”<—§È‘¯;÷éG…e¾›YÁ­µCøÜÒ¬Ìgó÷+ö\ EHàÆKÚ.Ònû½"Õ‹”CmÒ:Qn_ËëT Yâ ÆDˆdÖ\Q“g³%ÑØª›ŒÊ&³Ó,žýs.c·ÅZÖïþ÷ó+Ä} Ì4L8IyóN|‰µß\”%GRFV;!ØáÖìªQMR -ïØróbîš‹±ù"_*´²¤µ„ÂQ§#L.J´ „ùh6ª÷;·®JDBFh4Ožz)˜ç M“Ÿ»˜ÂÓAÆ4íœ~G]wŸg&“3oì‘ xµµ8é.# EŒË4äe/§—j.ü8Êað]`¡9QIíwæíMÍ%ª™)‘X½í÷fÃR¦w ?Y´ÑY¯ñ‰¹Þë=¤1‚Z¹õ¦{Ž™×^jÃ=Gûô$Û3 Z6œÿlž€NΉ9/~úú®˜ [²¤Lgcè’×+î>\¢±5ox·&÷ T³ÎŠeÆwG»¹ˆrŠ…wÆ{ÒžpïPSã"*ÕÐg~Oö”Þï`ºÙ¥àTH±¤ÉJ ÓÀ×/TΡìl¼»ÿZø•ApÂ7™àÄ­&në°âþú™ZãÔ$Í'󢲋ЀŲ"·‘%”èú‡z€q÷±›bÜä‹9xÿŒþªîQ '=Qáñè&W—(þ«QoÓŽÀÔA×!rÌï0óe ô$œ¦F€‹l3•êÒѹ{°à4†Õ®³‡ªÿjEcùòûô/„~Ð4–*ÌUêÆŽ‘1q;ŒÄï~ü›Èh0èßᣦ%†‹¾WjˆÑ§Pm%U¼xS©Öìäÿ¬Öˆ“ðÚ„¯ŒÊ¤…öíj‡®êŠwÆ~ÇÞLJ7ZëÓ³þ=pújš¸U×ÓÓ+t¨ø¼19ëmˆ¹Wß•c¦°n\¾5 Uát¶Cä[ŠÇÁ¯å¢ËBýP ®õ:éE‘,@³²E»[¹ºw¾ÉлoÊ–´ûä0&ô[º.Ʊ°qÄåŠÀ9ö’SX@ØÅIÌK@á„“xí^”ÚÕ:P•÷QЃË=O5Omš茘6,ÔëÉ‘4èÍúûK?FÚ¦âmQzÝÇ‘& -¦:âz<;Ι*G$GȤa(»4̉Òz¶Q \kÚ0ô¥†V 4m/ F=£ÒÔ»ã4À_Eœê½Ÿxþ\Râö´N¼»¨l˜µP6 >ÍéG&ÄdeÒòˆfŸC.ß>¸!õ=,[všþ=úès¸{Þ×=à>ó³ý,u‹€ãk™r%ýÌÈý•!§X‡Ðàó¥u<«)¿Ó©Ý|‡$ùy‰6ŒX‡zÈ|AÁpÿW).{pKÖøWccþ•Òª«iF¡†nQ gÕèXÐDwä,PCGgɸž¼3²xb£=¥–Ô©"[’Õƒh7 WR¡d2ø®—êvçVí|)˾¼ ÿ°~šÛíäÖû³dê“=Ýíï¬zÉ…í¢!ïˆGD‹i?‚ˆ›5¦3FÍÙ }ÿx­´ùç‚JÆ”8>Ùät=þÙr¼"‚t\B8ÕW’ÕÊ ÒÜìX ¤{Q?€¶gØSƵ‹Ö‚ë W«¾Ðç^OÁ„OÑÆîB0x’¾‰Õe›:ŽH°2L,.»ÞÈ£AMYÇ«æFݪ¹I˜§á`ÿRmjÄÁÃKÓ3tŒÙ]C‘bo["R>ÕfX/dnØ Ÿ‹ }b»b®«ÖM>ÅctK«êDÓ¡³?ûÙbæÎÞ+Üôÿ«d-†i#W þádµ´«>]lRO±"Ɔ”RáG™mÇ,ûµ‡\BÉÀ;¢{AÃÆ&¤a«õ´ÏÂt§$‹JuÁ©Ð¬ªóae3bC †ÜsÉ”œòAëW‘ó,7ÐÁ —EEÀ®Ôs°à qc}ôñOoά¥ƒ ªÇ¸7Ëò¡È“„Ó½ËßdDbhò7RÆ6¨£x­|©IeMoŽáp ¥/Sòó*˜îf¬mÞ~´ûºÕQÌ  NPňõ6^®Yeò¦SéEÿ‰šäÙ¶ Üñ*kÄ/j^º‡Ý‘ï¹%)*NQ=4õ°€™*„½¸8Ÿ\¶h/¬ªjÔ¿º â¬Ç«0T,ëâ¼Ô»“²Ó¾Ÿ7<Û ô à,¤}ž¨cê„M¶+rJümà=¯†òg!+¦œKf[`¥M­9%ð"¶#nôWñf3/¬ž@}íªåý.‡WñÂ[§´®¸Š!„jx'wó Tž»BV+ØÔàw¤a%†<ÒÉa²>L8p„›"¡Šº;ÑHþp²½×ƒÅ©LØP£=]›{F¢<¼£ òúÅböH¶Ý/‡ÉÁåÔ+zï¾éúÒ/ aIè9–TF|LþæN7ÈÅ‘°á•&R¦Öó ²Ý`–•òTô ŸË7ëAã*ßòýPPÌO}êÝ¡\rv9j]ŒàDJ»Ñ×DÕî%ÅÚäb¡ä‡œ²±ªF‹Ý ‚'®ô Ä‚¤K•dÛ«‹¹m~èõ9MÜqqoÄà;/“ýR¡§À´§N°þ~ó6•j!…˜zB=ýNÔ廓w¯¨]ŸâÁbT—.Ñl§¯5¾(FMAXÍ¢ NŠÛm”^CC-., Ìˆ'>ñÄ/›¡¾š@q0~Œ™>”¤?³Pdf'[Z>,Ø¢jÈ.ï‰è9W ÔÖ<9öß÷?à·˜Ž©HChÕþÜp?b–WûcÆÕQa8o±——™Û¥Ït­éÓ™6ÎåÑ¢3í"ñù»%ôL)ÆkY‡Ê¬á N6±hŸGèiW$'‚êãÉ)9æ{êQ扬Ć´\]b×ú{;:5cFþ×c-z\MU¦¹\¿nîy1Ÿ«é DeÌïy‹¸ocÈ#õ‘7J¹#ÿBtâæF„X" ÌQ¾ÉŽ]H†"#× çdôY±9×FÙë&”AÊ“lĦ±êÁüÃ/iÚx ÆéYPW|ÙÉ}b‡%ŽùGÿkŒC³Ù,H1hgøæ ¾ m¥òÈ: ¯b·3â_ Úûì¼W… ƒfÏ½ÇØó#–,<÷;†}Ò©þ÷Àħû%+MÜg¸x%¦ÙŸ´ö4&º½õÒ,ÉEΛ@ÁoÕ€^ò~ŒÜT/e‘€þ•³ÔbþvµÐÓ;çå•nóƧDQž¸€œÈ¼¿NÌ ÍÒx^P£Ž&y__œÁ”›nÜ:º± i |ÈQâ„ë[ÖËi]¤WOëÁ!ñ}e¯Ò-=ðŒ\te!G>Li9¶dñœ¢•ë0G–V‚·Ôônn•·¸4F[ ¯¥;¯‹ÈuIÀñ;ô5{ø¦±½Ë>æ3†°wãÜcTòBÕòsY˜ª¶·ç¡Ûʰ]€ˆ‡gm¼ÀÉÝØÉºK–ÒV2ÜmyÇÚDæû’É}©b.Ev£òÞ <Æ/±¾‡Èõ§¨²°³g¢Â`»÷¾÷$ÆÜ€Ë/|¨‹Ù©N7á$X–¦„{N«|Áü(Wyâ°H§Çe§¤ Jã\šÓ¼Âáð\éæçBrõ`Æhã¿ÕôV‘($‘`ñ>wJˆå wœs¶´vý~–ʪNpì\š©YÒdF‘ÐÝ™ÇBÛÆsZ7ü?þA½õ“Û£qi¾^¦ðÕh!ÆO×Õ¹b©V£ÀER § E§:åeŦX.Óâ|eòq`>賩rè› ª¯©“޼ ÞÊÝ+ø5ø³Hmš&º÷»KoE:)±2â0·[åo² ¡_ªÈC³6C›ê{cZ>ûs|Z7ƒ5¯Ê\C¥¶?-WXhÄAhÆ1ð+q|Kž¤´'d §œ\¤Â‘Dlš#³àtã’þ&\ìiª>ð>Èí ü{5g0Ö^‡¬ WêäO_2:.TµËîêÄks˜G³—€#ùjH›"tàÃç©I6¢wˆ‰CšˆÝû>Ø’ŸÆ£_ð®äå3*ÖiÉò±W]¢Ó®3P"£x½3™·]_uÚˆõSbK‹&Ø{|2†ÝQj?f‡mÂïm­êa±ÈÙÚ&Á~Ó:ƒ ^¶¼¨ðŽ øO?ÉV’„ÊеÐþÌãpusÖ’‰AX)¸ÌâÙŇ½À’½)÷ëy|éí6‚¢ñ'³CPl.?mXŠQýZ•<ž …$¤X˜DÒ.¡ß¡ÞS{ç)+9ðçGpÇЭ—iLš@J0Dÿ˜\<ãîkëV} žÒê@Ô¥Õ¨ÛR®ÄŸƒkšQ¦ºñ*£C?J|.Nqqr„ªu¡VǤ >«ÇP6¸ìmJ—‚›ÆÁvÆ ,ÍrFÛˆy¿D!˜òÀË€ËMÚ“Ï29¦ñõ’z±¿tÓ¡‚ °b'UO[– ³u.Ø·é©É™Î˹çǨ.ùW®’¿+ï»!÷êB³çıuwPÉCQYÐP”bO…’бK¬ïðܨ¬(ÕYúBÆD¥*ý½3v–ùšWRb#![‡Î¨pãòÝ“¦ !4¥¹WP·—n,]/®5Τ¨®«Ñ-üý>{B–ë6Qßo[pl\‘WO7NP_Ûó:Z>²ûúM5¼&™9t~Üò%飙óUDl¯íèÄéà¯=¬ÀÄÍ–:Á‘$<öJ<ŒƒaÔŸþóËH7+Ý—îÃ;|FÂ#ÈKé«Pt®!­£œ¥ðØQ 9 –Êë"âÆíX„ Ãã`…=’Ï+åýBçâ‚ÃN­ÇëE^#äö Ô†“ÁÝ÷ÛI«1ƒ4¥ä7@YA,Kóhj¿R›'¦ÌX‚»?zCÒž(6, ,5ão/¼q{4M%椘>´'^¼ÔÙ™±Or¯Ø‹£VH±‹µ£ùROEŸã( ÆúèÏaò>Ô3œ±J€¾á¿ B%ôl÷t¬}QÈŽ¹~±]h(à>;KWSÁ]ŸÀ™ªïYñW5 ÉϪ@ØYXJ,‚*{fe`øÞ±;ïÂì¼UCWMý‡œþáÞÖ»˜×xR^c¶£èþÌãÂ’D©^=œS;âS{1Y¶.—5MÝv㛼’ŽŒˆJpLâ‰¬ÞÆ ih¼‚jÜ0cÑú)Ùm/ê¹@…‡÷$Šô—1½æ} ??q‰$w;jl“sç% Š3Á½*¸Ê¦À3ou†È ½oò×ÌÎþcàWIþ†ÚkëUÚ«—¿DQd§Xs`¿ìÌMÍñ¬Œä ÛŸ㢼VÈf¢iñ9\v”ìaµ :QÙbM #Î*)GG§˜Gõ !K•¡ÞÉø¬ 5Ýœ ²„޳²çU³®N°ÁÞ£ OÏÜÇŽD"¼N 8ãŸYÉC´ÉÑ6¡‰øî${®^ñ& "Z‡íùKÛtƒ„,ÍSî–£p#–HÉ¿Y¢ñ&šØÞõØÉÆ2„‰ÊIèY²B]À¼¤Þ}µé‹ï¡ËåÍ`ë[Òßi»%ìjÈé«­ ²SléΊs4ø*üÐv¤™i¾µ:Þë;ê°½³wÕc< ’¿êbòúO÷>¹õB0×…/.­œXûÍÚþ°ò=Á—Õ/%ð”ÄÆ 8æ›ï²’Ô—ž¶'NßÀ<æHxà|Š[}м ÍrTÞiúƒôìOöîOš9bWÎýƵóàGì9¥ÛõKF¶vM‹iŽØþ½£ «Y•ýp­5‘ÌÍÌe/sˆgÖoÒ@}EÈéw9Ô-L0®g‰vŸ#?N‚=ñƒiç%ÍŒ[ºÙ­öÂ4ÅfÒœi¤]Sïõy‡‡ÂéÞP$Õˆl¹¬VŒÌëÖê*ÞRL¯ÛÒÇhd8#í§¥í¦˜à„wKµ’Ï‚FéË¥•9¶Žýojnè–€ «º6¨)bƒtLЫւ€c=©ÑÁHx–?6÷óeކ-fù,´pœ¦̺@£œÂdä7Úrº9jEí’{F+‰4^c@/—¥±D"ÇůüYЕ$ãw§»¢Jñµ„S2Ÿa))…¿&¨Gv½»ÂìoïFÔÜÒýq h2Ü Ç Ú1µ.µŸûx}Ü«XYû$̓‡¦FR–V³c´°à·0Oeýh%®ÖÛ>§¬bÑ8 ù2Ð ‹•·ÁÌ.ZoÄ…EœRnÐÈÖ‘ÓºÂT¡C%ðœA'ÝJ±¼ ·ÇyýÞØ¾×f…X2øáÕ ö²°Û§³jùð²ž~ëÓ¬Wõ´ <í¦#Sþ~›F‚€nBÓñ™užMÔ÷ÛPRâ牆:%3ýº i 4ôB ÷8Ê4+2;ËËhÅÊghÁ£ë aWôAû,&îîdQ'Ó¸Rè¨{¼ø2,©×Í)ÜdUïã4áè;©m²I­‡#?ño¡ÁZõ´•c‹•ùÁÐà Iœ—jPJ+´ÒÜ[D;´BA»õ·Ù}¨³e½øZ´)åpëÊîtXH¡^Ûæ5#³šîæšè«VØ—F¡-Ÿ{"XšýÂùAgß”E—ö÷Á$sú|ÛC+2¡{z_ “СOQ—PK2Õ¥tð»+4¼ó‡ó·»)IÞe¢zúŒÁ`¶œ;]û¾<`´h‹× ×Qʇ¸´kïàÞ>å(ï2õÜÃΦ% t¶òlH,Š'í¹À]ty)ãCÙž‡'ED3vdï)‹Ãn`6 Ä3½£MšvO~6ÐúÓTÎŒó › ( ¿Ô%ÖÂb#ŸMò†rèëó˜³3¦8 KOб² Æîmm‹Û†Þ?˜41?"Ú}A&5 Ǭ ÑDïc^i®Q|!íðJ Ú‘ï˜j‚jàCqØSÈjb`3Úz˜låÍ@=?.Þ{´{ý`{a|¾„Ì4肬qrâJ…:Õ¦å[Ÿ¨wN*›‰«°½÷ÑÝž·¡öŠNŠ>›Ãý^¢rŒVñ•ÃA×_‚Þ¶ó®(Ësv`ZÔ PË©ÉéñAÝ|Ìn=ºK9ë¶ûâXSJæ;µ8ßô=&j7±«M®gž›ú^õ•âl(hf-ËÐ.{Zs1±¯¢¿È0(a VÑžÅ==åEû€B¿©&Ñ|óÁÆÒa+)2Lâu½š  Bè{®n£’õßìfëÙ${ûšt²¼©¤ÂMÿ54ÔEjh¯Síj?ÊÞͽGŒžâÎÌ:%ÒÐ`Ü=]{L4GÊįJŽ¢³úÝìá”»‘Ÿæ—÷-U–`ü£ˆô" r Xÿc+r÷ûqcØÁEÆ"&ºî.¹W¹!³Ü Ïf9c²i¹v$0 lO¥É£²2'›ÇD¤/û09æ÷y£(˜G©OåYùÛ/u8‘ñè…TÃ0ÝCÑ0å÷ßF\Çœ)µ;$~΂òî(­z™Í·ÖÁú„ú±í}¬ Ì…A«Ï‹Ü‘ßÈÓŒfœS’v–~Á]è²§šÇD„öÑЙ6ï¯J«]šûœóØíXWÑNãòêÇïåU™#Ë(U<-ó÷ÞßvP«¹È¨²é>~døœxP>1Ãjƒ*=¥µN*NZÔ¤JýgâB•«\¨å>²XÂàCïî>†cßfÀÖKA8ƺÒ~º vãzúƶ(ì±c¬…~Æ;Z$Ì{‹øæ?ôgWÊÏ]Æ—¹ƒÈ»!;64׬ òß(?F$¶ü=§µï#>8ü ²y×ÙpÅvÿÒhŽšX›Vâ˜W2{·§½¥=‹Äm¬´RÉ«àúH,–FÛø«ž¥ÈÁ(OÖÌì #'8ˆkâ_ñÞ6¯|UŸ]©¡A[s44Ø” Sì4ûšš1z¿Ê~e=‹z5yåöŒÙxÿ\ZŸâ€Ëgau¾Ôó!yîƒx€xÖ0C—»‚úОVÑTàmÐZwõJ‹X‚1}]\8óÂ>úvŽD¾šHt‹ô×ê=#—šÍFwmPö:‘’oøöõ¬CÒ7Ì©<Ôîv½ ­H~öÙ…{ýÙJ>ƒN‡Á\NbÒ½ >y†á_g%ò¤A˜ …:½ÊïÚL”4Fpþ#Æö±œ„)©G¢d ŸÜ¬aðé²?Õ9çh¾o!‡­)I‰Ñ„#Eí½¿çÙ \œÞæ‡â@¨sœ¥´Ã+ ª˜½˜B»§ýÝù°¾t#uYá¬õ°ìWâ"ÿ•Þ¦Rm<ÝbŽ¥Õs)Oêmo¼c"=ŽØo;òcR[aÇ:/gêÐJ<ñ †(r;Ì%º ˜Úêóð_Ò>Fí?”ùO¤d«Û|ИÓÕ}¸Û¤ È’ÏrÝî²[ê¦f¯)ªxcY¨ønÖª“ÅV KÑR¶J’P0Ƀ$9ÐtÄÀ*ù€¥3?íkÆwáGŠÎmFîn¼Ð~…b¯$E™:¢£ Y j„ê±"¤ÝãÌt¥Ä/×Ô—\ïÝr–rF._ÄÒê>VÈúšè8ú­ÿE´¢dòˆýqFÆq6×#݃m—œ‚ï»7£³Ln ñ—çÄÄfÇ›C>ËUVôzÜþT²êùàaËä|ðùeón GÝÎýÆ]vóPîÄÁ®sj™DË#fj­º7­¤zÀ {;2õF»” uñò¬Ù ¡°ºæwÖyÑHzåxÑÏ9üƲÅi ñÎyvYJùÁçqãºÈxb¶æÂ¤,¬‹öu>·}^&Zt£wt<æScŒO쎇méŸSpWÑ­ô•Ä>ufOû)ož¹˜+$ÎJÕmÀ~é 2^É·×ö*³sEj8Å{¹ÞÆ…-G¿Z·gqƒ.ÛÐdîÊ2çc[»ûT2(œ }Á]c-¤¡¾ðGº”Èi $j5Ž`¸ÙôqÑZ–¨}i%¼W½<šoÖUv[#vÎCªî:± =įŸªŠ'hK2Àéð0O†‘lª¯ª®Àrá]íÊ}y`NTúÔž)ò{Ø=m也@à3Ôàf;ýs7:’J8l!tÕ@úq:–'](6¿›^‘§ \Üìae<…9Ì2I$&ÛÉI™àç_ð}f0‹OgSÐļ‚Ô;W:ë¸JÔödcPVÑ 0N;0&¢ŠÁr';ÓaᣲD§g{Õ3yWU?Uj¦EiÊätb-Ku?]S³ )FÚš¾ŽýüÅ{§E[?¨ÝxüP A&¡HòûZ¡y“¦R'ö°kW½¼UÅwJ€+C«¿„dè©ýidv¨´Jv&X•}¦Æn/ž‘"Ï“´­çœÕÂcS#'1#wFþQ#Õ@´ï>馒Ø—×W`f\ôÏ¡ócŸšý×Ï|c^|aDeßú˜‰ŽO[v¿›øvVÝKE§Pl“}q€WýJ›Ò8Ö §@Õ…ý³²*Z½häz•Õšút Æ3|S$7…sÜcNÇýk3Æh¾‡ÒºN.Of‘C‰)ôÃõ8VÈ`—¯Ãºo æ…þeF#šÙá…˜÷D¼ÎŒ paÁôÉ5'dx‡¿d5Þ§„\/Øÿ‹ ©áâxÇz0á'CR’N«B ÖÝó|=3°úwÝÐã{'X#%Aÿœ}ä…NaÌüÇŸPÄ,N¾¡jŽBÈ´¯,¤˜ñù¢»dQ<2Ba•t“m„(P˜¤ž‡Ô:oååß&´MöUP½†ÎõUy(Jw©/ +ñz÷™ª™©3§ê^ù'%ÕB:ZåP˹+M]÷Š ¥zø[lÚ„!7œE–$DÊóÊò ›¾Ë ÙÈFÒíÇqëñØÛ¨Z~"®{ø”?CÑ8¾¦dà¸h\,¤– |+O“ûz·PåÍ- ‹+´COo_Ò E—jÁóúv^„–­Ðø"îx’Û*Rœ<¼æ¥Ê×å‡,8}›š#ClAÁeºD©u,è§>U¾… Õý5 4"•δã²Ð ë—¯a¦¯Z­v®{ð¸¬JP”¤­Þ“mlœTÓáQNOVð h$)Õà‘âŸM23–[më^rPÌ`žâ;XUžÍ8ÅJŒ¥µÏn„+Ÿ°—qµéeôè–ÖŌӢÉ|^Ò2DJÝD û’?Rs9Åz¹Ö¢$Í ° ¹úÆÑ‹h‹vlæ>â_hZT T•®áu¦ÍU¬/tjŒܽRÇð§o¯ ÑòéÆÛ2Y'Þh*"ÿm¹Ïõ²ø^!Ù@ #‚ª¸?µ”ÌàÚ_¥þ‹E‰Î(OåXs²E3S-Àè•»Íõ^:…›£My¦ªÚnc£c]§äE0Ó" Û´J:õålþ‰õúÂ…™hÃ8ù4!×4*ÑÆYmei¹Cä‚Ýî—ýf¹“5Qê$k=áØaqB·ƒúúÆ©lüqºt#. þDç´ƒºö«hǧ'3 25#ðú¯=´¦­ê¼kGÙØn¬ýµ Sä^0ñâÄÊ2I¥Ì¬#oo}31a³7¼go¿I ü£Ç(N·™G‘nJ|§§©ã6``ß/Œb²òvô”Œ‡í/ g&z¦%;êP‡Á¬—:2 ô¶~áü\·5ÑæNÄÙ/\1ù§/ÔCƒ­¹¡ÜÇ:ê¥suІ’îŠ*+%øA}šgP _­Pä"¥ÅØŸú@ù&K"Dz䊬æ7Í—ÄZv/wï×úïä×TºÉwKæ'ûˆ%ßËãÌä|J^Àóo¶×W ¢'oþXÖHL=5ðë(“ûå*ç`¶}f.ØqRvÃÙüh´ wŒÐ!J$°Y}HÔ´œ>Ã(ô$´8µ›íÛ2,õ9æ®6ÅkK±ë–˜SÉ`{Àl¾4è&NÓ¤fz,ÀÜ*Ã>eœa¾ÌↈWöùsz9â¼éDc õÕýþôP ÊZPû¶¨µÙO9œäÌ'­,ÊUWÂH9lª}Aôµ/½…Sæ¼ûá’¯ò¸AMÁœFÊٸƈ渓¹,)<¥‡úõíÅ9öWƒx2LåoJÕ†–ãi•'ÊA»½ù<5h¤8»Ìc†Zƒáß\‰ÉÂŒG"L*˜Êhb³C%ïÉ}O]0áp  KïD(ó~ü—¨X_Yo;\ÔÎlå¯Ec£vxÕKr_çýþg&Þp”ß­w2¾Ñe¬Àmè¡o “÷úê ¦y‹s´ŒÉå‡Yu#M£lhqÜÙœJ¿à=ëU?¢pû>6 ó þùîLÿå¥<C„ÈCÈD0ò®¬B=ˆ²*-ã¾›‡N€L[¡Ì%5ííú–É©Ê̸•a¹æÊõ­»ÎtA¯WÊBÌ,ï²Ð•°slÓú˜Óôæmë“e¼6 ’7qqLæ©·Åò.™i«àráðÇóõ´…ñ¶ð2?‡n©BIcÉp´cdÎà™ hS6|[Š¢zõk]ñ'(lüN}1ãJS[ȼ¹Èñåñ¸?"Yã}sym³a¸•Þ˜ŠôçT dì¶câo€TKû ^GÁCšq°è7®mûc‘yÕ/c9PqUÇ{f)¢¨?Ñáf`Œ3WfæâM¬Êô˜Èï:‡°È}4 ê¦ÙÑ1ùo—®Ó†>€Dp:'£­†™'¤Xð°3 «dÇE9£Ñ]êtH+qçc>+iˆfýÉÀ} s/KŒõö)Ô ·ê¹vªíÙ1ŠPk·ÀJpApGt(í¬ìc%üp€uä5[V9݇©rƧGq=8 ¾x Êrž­ü ÆN•VÙDË?¤«™6¦ÂņGkŸ*ºH“ŒsQT·†_›>Q© ­³RÄÇÒÅwÃüJ+ã¾5—³7>>Ë€ÆÀ"ÿñUíë­Hô1F‹_$mh“¡ì'§*¼ú¯DñÅL’÷ Êæò•E É4wQZS¬Å,Ç„Ž´Ñ”œe].p\¶™“aº4!T™]ˆP²aPúL¬—¼i8 Šrž, ×IùìÁÑ6š2-œ/Õ€Ü%LGøš³X›b™ÒGyÅ,÷,‘·7©¨'^iPJ‘Í¢bT¼?é‹QTw\5—–hRâÆxÍwâ#D—ì,·nΧ÷ÍTöez¯äL½D~ûòtnº%Ð †Ÿð³8gçp½‡ý*mQj“ò‘±m9̱l@U+H ŸRÔZö¿ž± €¦•fqÖ$÷ÞØw܃¿€ d¥ «#+üð"Þx¶Ö`‹¿H\•óh‘5oK=ͧ«²®V–•ù©9‘õ'°˜Š›ú?´” ½Êpa£`ty™MŸÚTéŽ&ÆßMÂoºª‰’þ2Z»a,¸òá-Ì‘véÈß½> q5ñ½œvJú|\)týDZª–ʦOfcÚ…éWã&€ùÀM£ÐJ'Ç”ÎXKn*³@ßШx½asl<&Oïz¡º }-ý£#‚ú>všà‹dª´mäû„Ræ4ü'Þ£/Æs¶Å2xÐÈÓ•¯ šV#@Þø-ÇÎN1¾³öÞ†CSL®2Z“‰d |ÅšùïW&C+Ñ­­&ŠôÈvã˯K{ãe¸wpc|ï©N0ì`cÛ)X¹¹Y”î°tÎXt2Þ‡z= ºÿÁ™ÈKæÝ‘¡%Fì.ÚãµV?y4Ô t«¿í’ª²²·’€{>îaš~8aN¥ð^Zÿù–ʽôhôÞ=*§RÍ0ã;«Ú»ÄI±ýÉ €MÑ‚¥ßKR©Iš,'©%a-ËGÔ³0GÒHãOÚ‚Ó¿‡OQ ®f3éZ‡Ê1žéèJêW} ÖæzùØîJ†‰E¾û+§ ŽMf»hë…¤É2‡ê³&_èYó/ŽÀU☠œBwˆ+á0»W÷˜ÝIr5 åÙK,y°/„MƱ98f-tÏ`ªd)éèò±*бúRÂV˜ñ»Ð@…Ÿ|M ?=»ß Ñ+_Fݸ­Œí> stream xÚíZ[sÇ~÷¯ØÇs*åû­ŠJ•8WÀ1$(dy±dÉ‘dùõùzfö&¯Ö‚øœór Všîéùúëî­…´¢à…VÚз*/¬²…P¦PZÂÇçºÒà+¤ãxà %¢FA¶ÐÜ á *ÊøB‡8&FS$§' Šz0кأ ç%5lá½§†+‚RÔðEpš](—ÔE6qK£<æÆ?jAZK-Âcì—:öÑ$!öÑÚ°@« ¯ ¥ $µt˜Á;´\œFxš-‹µ,CX¬#hµÆžà±X”×4ÆST9N=˜Ê ê©NñG󺲇"giùÀZJ®“i҈؂EqR°¥Á0jF- k£…¨r‰>…¥C)® ‹ùi_X õmðä%Y8,>q…“Á“îÁo¸q“+Lë´;øÙyøÄ«P¸àÌAÀ`Ïá!`¹¬P°ÜGÌÇJqÁ(†ë€Í‹%{Š(@â‰@ —·0_Á½(‰ÁxV(àì>” "lW4àà‚ ¦€zÅC$$hÒ@@P`¡´G]0JÖÁB‚ÈEĘ€Õ‘`>ƒÔQC€±ZpŽOŠÁ…&ï8bÌ‘0­íš© &ɱ\ãS’ç¹ýR‘„M’xÌÉmd·àrN‚{|JI‹ íhÁ0 s„ÕÁƒìqñ¡B‘zZ°W¿½ÆÚJ‹yŒÖ%Gx,næó·_=>X†R(½ß`ÄPIa¸ß`íJeŽÁ—iDÈÒÃùûiæªTbÏÁ¹Ò!°ÇK¥KKˆ;oö\!’a)¤#,Œ¾c¬²¶4”Ëö [œ¢ïô ¶²4èØk°”²„ H¥F¾c°±¥E­(ØÂùx¹Øì)‰rM”:V1…å¤BJ>ùÆÅÜ”oDLBt}ìdµœ>¯6Å›‚<>.Ø‹êã¦h¦zñéºÂƒÉEuÀaÚj±YSÆ6$ÀN«õòf5­Ö)‹Ç¾'Õùlòpù±xÃÑa‚*yDÈ—Në·˜p²‚–”ÛH ‹²`®H¤ª2Ày·áêÎÜqð4ö?1بRj½ç`,v_‹%/‘{V"Úœà%¥íý4c0Uë½[|£¼î9˜›R©»€ÓJ–Tu ¤ŒÙfcôŽ\Ø£† ¡‘fU)ü¾ª…x~W(uÂb+zúqÕ ¥Nø‡Ò’Ìlóöwk±]úÿõß½ÒÿÏ—èÉÁw´—Áb#Ê÷ºâxÚósÛ\Ôï4íI%* "!ïPk©M1JýtOqK=§þ€ÌGýõØøŸÕ÷$Ç‘n<£+êÀ¼õw´ c5ÆiìA5w±OÑFýíh×ãdž—æ¢{zN²$/Ê1&¯ s×:4U›7q•tf©­A»^]¼±§qÞy6üŸžIéÂd%ogõž¶*ö×zÈjšÆGÙ{ð$錞̞ •û¼>BƒÚtÕ(ÕžÃV›7©ƒ¾·é@WXûqÌ—ôMÂq•4{ög͆K¹MÕVÖÞ¡ñ$¯¶VȾU"!P·¾¨qŠ&×7*ž²m¤?Þtÿç‘1-â¸Í}"‰O"tJ¤sÐIè<6Jè¬I¢œhI‚úâ2Å2,[$©a¾’P˜r"‚nÃÝd<%ªÈƒä’  ~Œ÷¤G§o”@C?KÕ„ûgqÄÕpD»NÜzÁÆa!ÞSÛ{û&`,öŠŠæ!#qø6–ÎÁÁ®áÈÖüÉÝÌr›#‚r ƒ±àw·(Ò¬%J“TÀ¢1ò2±€èO¶-1ÖnGZê²!ÛÓ6¦ ªœVF§ZVi—ÁD)ÛfÍP÷ÕI·–¹ëªõŽúŠìÁé ’Î Ò)õ”;ÚCm%Wëéjv½Y®ÒÖòéä O^|÷ìÑ·_=]ݬŸ,?žV71Ÿ\¬ †>ŒG¦C!‹Cú1ÃúåGà¼t´žÒyÉJƒ#Øäú»jvq¹¡¡°Õ­Ò³C¡q÷ýf2ŸMóªàìù¦ºúµÐ‽Ê2ZZ¨¸œ¬h“ú/vıÇìö-ûžýÌž³ì5›°ÉzS­fë÷ìŒM¦ï×óÉú’­&Ój^½Û¤ÖŠÔÅæûjÓt£LÙt9_.ðyu5aç¬bq~Výq3™³wìÝìCÅÞáàÈ.ØÅªš`BvÉ.?]_V 6cïٜͫõš]±[Ì[Ü\U«õìbÁ–l‰ŽkvM§È8ul¥‰¯aÙòœ]ÏoÖì›å¦:?›74$ޥ抭ٺú€9׳,-tÃ6—«ªb›?—ì†Ý,Î1ít¹ªØö'ûÈ>±¿Ø_Õjùïä³ã 2èîAö.6œ¼xòìÕë̆ç°áár~¾ƒ 8T*N¿T!ãZ> "º|ˆ· ¤ØÅÑ%„QrˆÇ Äw Åì'ö„=e' Çi$È/ìWö’½b¿EªœEO×>&¿Ö^Ýò&9²q\ÇGä>â Ì[ÛÏAøôèÇ£G?|õËéË“É|>9Ÿ-G16’0Î[£ Lcî»[×ÃØÚ]KÕ :'ú?ÜByãn8Þsî‡ÿ,›Ø;›¬«¡@ŒÒÛ!ùÑM^ÿúÍ/G}šœ.¯&;hb-|îãÏÑ´CRšˆ^j¶žwiâåšxÝa‰ ã,ùý˜™òl„+ÓÙl:[Mo®î?O—ןRÿ6—ªÓùäªK©ù@øý‹ùD8îàÕít~5KFßGbWœŸ^¿|vòóÃ>ŸÈñ»R;§´C/&p  ZìLí6¨^Ú}>†šPÎwå{©ý(téOv<Ø/Æ—ðSÇG[)»_/·óö ~ò6~ß,¦ËóÙâKš½{WÁÑôóñÙ3Çf«æSãã–*8DÕ„Àq ëüAî41Y“hœù˜—”¸A†Ç¥ÒZÒª" šH¡¸ê„XD¦æXͶ¸]IÑ)6p_Žhìn—Ý0ï¹M¨©2§šœœú{§ _#~Ráí”Û65ЋâNz€e/cáû©‡˜Ð¦‡··Ãü¼9ÛÄ[êBQüéu÷Þ¸féµl|½p<[­7DìB! ~šäXuÀ^ÎÎ7—ëôÖ7®É–^ÒÆò˜q;·j·ŒsÛÆÉ®mÏmAÝ‹m#›œ[Ö…QëzÈq?Öí®­[Ö).F­£ÂØZ'ïź‘L}Ë:µm¶»]Ë÷2^§­é}Ú ëÚ7bôŽ?Êÿ8;GJ¬»§wýô­Ó9#žEã·æé;=ûE“جԩ¾MzíáÒ+>Ô”Ôïù?˜Ä‹¤ÔëôÅ»¬4í닟%¿|’ ’òàÒ™hÿh 72‚§G_6@GR#ò»!BnП¤†©éMÔØTŠ7·*;#ã•òë§úé–¶g7›9ªÒ:³´ÈæIãO{ñ®^V-9C)”ÉcÐÌq˜—Ýž¬ªñOLº<ÎÒ¼‘ÖnT:'ï§ôFŒþnå¶.m[]jT—èê´KËF— ú²´N> stream xÚ}XÍŽ&· ¼ÏSôÑ{ȸEê‡ A€œ#r |ða Ä °Þyü°ø ¥VKîÓ|”Z,²(±¤¡Só ³­ÚŸóH…ޤö›ÄþÒÁg>’ÈÁbs’\ªý=Âö­©§}×訒ßRscßÕ|Ûwö[“}gkÔ|¸ïÓ&S6#eû2›AÉ>…A†—°ŽÍQ@Îæ•|%æíãb±%Õ@V6wf†$>=lFÜjIQ:ù@R©Òa DœßlŽØ·¤ˆ-KŠrnND!ö]=“ef‘¡}×2[ÆIʇ%Ebã–©ð›…ad¥ÃRbÄcq>,!&·|˜mÜÒa¶qˆs1§ö]Aüö]õ4ío“7û¬I;ì+)Íè8ÛÁj¸öKÌ9)~é‘S±5l¹gj ¿,V³ Jò1>r¡lr+ôf¿Ê‘¥1Æê‘UýW;l0’Xbì¿ÔŠ]ðË‚+ÙWP:Jñj‘Ѓø(ÂÀµÌŠ–dTŒ®æßÙVIŽAí¨Í~‰‘êQY2µÌšƒZÐØ¨o꿌üWæÌG;¹àW>Z*¶;˜Qª×Šz´|º+cÉ>†-YÁëÑš`wYešž³²ÈI>F‡Õñ!ä+2¶±4ÃȶMÊk…m”úZÑiÙÇä©>¦‡¨ 2[ÛÓjk[Ž}Ì6{¤–ªBF¬V«Ç?¼}ÿ÷/ßþù›ÕŽäßÞ¾ÿ£möúù×_¾~þ×7ßæ/ûëçÿú©uë§ÏÿûfËØ­¼ú:ëðE¾Î«/ï ‹¯¾ì`=øBû¸ú*«¯¤møÊ¾hòuî|ñðu>ù¹øŠ(g_¢Ý—ÔG_yòE;_eø¢G_Wî#Ê›¯Á}{ä¾ÕÉWÞøjƒûöÈ}£É׎û6¸oÜ׉û¶ã¾îë#÷uâ¾í¸¯ƒûúÈ}¸¯;îëà¾?rŸ'îóŽû<¸çGîyâ>ï¸çÁ=?rÏ÷yÇ=îù‘{š¸ç÷4¸§GîiâžwÜÓàž¹§‰{ÚqOƒûôÈ}š¸§÷ipŸ~‡ûÕ;¶Ó`;ÝØ>ó5’óÊ6G‘þüåëoßF˜ùÅ,Öÿôïÿ˜Ÿ?ØUbÆ"æ;d/×MCo§zCŒoZ<¯œê´Sâ!Ä7žüL*¼ ghðM‚g7×"ìôwÈïM}g7×s°“Þ¡¼mOëkåfÓ½Él¹"û*y„ã’£âõæ¿Ó}“ÞÉÿ$¼;ݲ{SÝÙÍ•îäŽ îìæJ÷Nm‡ØÞ´v:(;mÒzWÖùÄÍý&Îò‹þz¥? â×Ù¿‹ïµ7îDmhí]j§•WÂw:;dö®²óÅþâfM'ü&°±ÒÝì„kèéMNç•WºwZ:¤ô¦¤“›IGwÑ ½‰èìæÊêNA‡€Þôs"r§—¹”߯Ǥ•yÚ|›o¾|>m¾‹œæ-ÖÇsiwsÂôtJ®tÇÑûP¥«ŒÆ5uæè,OP“šnJ2¤ôw¼¼:ðuáO¿üúù·ãߟl¯ø²ã»ôëã÷i¿Ûk¿ÁJ°â»“`QX Ë×ýlIùõË·Ï>÷óÒ: äX^`•°*¬VûdÍ£‡¤6—6˜ËwX÷î³+pBÜ9Ü%Ä#Œ„ r„‘TŽ0‚Ê-,ª,Àðî³+°ÍdÕõf_˜Í}–`a Ö ¾¶Ó„ë°6¹E%ò%dÑkM VI‚œ"CF„5Ö1"¬+Ñðî³+0#d2Ò¨A&#Ú¡b 2¸"Œn ætfÏØf7À Áf†df¤Ñº…[‘=Š>¢^w™W?½fWÜ 6[ÇEàÒÝaÛJ@p+Á{AL#ª¼ºÎ ï>»D.±‰ "×îAiÌÙT퇧" UPóºhN¸Å+\w®\£ŠÖ¨[E…ûv¯æDzj…ÕÃh°–ÖáÞ}v nµ;@½SpÛbdß,)¡ÀmVÿ¸´%8÷Ù· –„sR0+'¦ºåEŒùR- k%º9®Í®¸ ¤X¯Ž¬«g¬+Á ÖÕ¢Þ¾Ù¿Ú÷„ëZw­CQµÞâYp$¯`¯÷o1½+2ŒþM”¶¢b[anEk¬Ñv $ÜÂB¶,a!Ûh ’Ba!Û²ì*8÷É ,²-9Ö#ÛRÂB¶¥Ç„lK Ù–ˆ)¡Ý¤ÖÓÝ"‚„IÑXŽ£ ÚºD·&h•D·&46‰þLÐ*Yû³{÷Ù 02ŒþL87ý™°·%ú3a‹Hôg…R#`$,í\ë§×ì }“h»‘YAŸ¤EVÐ'iQè“HÿÒOýZ`x÷Ù˜±±")(šD×%ȉD %ˆ•HP± j V"ˆ†sŸÜÀBÐR÷íY™®U)\«"ÁìÏÐ*Ñ•gvžmvÅÍÈBƒçŒ,´;G!±íP¡F³&h•nvtö„Ëî—„%y+)è+s qÑ3¨)NT¨xË˽ûìX±$ \ì‚hˆ]IÝ"|HP.=û\…¥+.%fW\H˜¦` ¢¡GuÒru ¢¨MÁ´Ji­0œûìŠÛF5îÔRǵ!„©QêÀˆÖÞÑü$µÝI‚ôÅ • }òF‚Øc‚*EEÅé"k´\±àÜ'7°¸¡öœ Š½û«ÉC5” Š¥§ å)›tÅquw‚!vš;øŠ[CÞ¢ØüžÞ 9Î3¿“›z1ß-Z»úK˜ _Ù†+Ƨ1}à‚]| £3‚‰«Ûç0—FÖ±>>ÙS°2˜Ç;¡†ì2äZ;äZãŠÄkk C®5×Þ½ûü U×há QÕ8á ÅÖ8[ ÅÖØ¢|úÆ è„ˆ—¸óïvO†ŠjO8ù¯a!¤ž?tXãµÅA+û›‘VÂ]£y÷f„*Õ¾ž<ŠH«q%a¨žÆ•„Éc bÈcZš¿ 0¹EÜq a4-[ãxjÜB‡PãÙ¬=@l ]¯$îÜgW\TFkwàÇ6²€îj\P˜=ÂÈ—aëëa[wû¾Ú=2¬-Üe¤Hi´ˆ¢¬ñÆãìv ʹâB80»âBx5ž…Œ'žÆ‡!Ã%z²¯½¾.°qwa¬ÊJtv¢Ë®Ÿà‘©qÁahré¤x‘¾«°t`ð—Ô –¥»sL®°.Âñ `—ݸà0Œª‘.Œw†$—xz°+ª.7÷î³+0d7îØì²wvÙ2Có wË£š ¡º¾Ù[wÛå‹'¦Æ™›4¼ABûƒh¦3®L,äf„5s­pshÙuJH­­éhÅͨ*^¤fFdPb3£®PB3ƒ/> endobj 1356 0 obj << /Type /ObjStm /N 98 /First 989 /Length 3794 /Filter /FlateDecode >> stream xÚ…ZM«7ÝûWôÒo1vK*UI` CB L& “Ì*dá‰áAìgì—ÀüûQºGîkõe6µJ­sêè£Tê›J­Û¾¥RmKY½Ð¶\QÓ·b2 ºoÒÍ iÓœ½7o¬ekæU¶mê–RÎÏFi4ÈҽΠ†Û–¤ãÍaPúûÖ¼¥-õÆ9e¼V¶œñÆà’Å»/V·¬E†é–Û^¼Î¶Üam+I’—†Ù¼ç¶oEv·¶´•š£åm°E]žšc4ÙJßÝ¡V·ñ‚Œ¦›$øÑl“lŽÖÚ&¬q©É­}ȤâüzÚÄuyH·;—^¶ºGë²ÕTýÝ^·Aŵ]UÙñ®mµf¼Û¶ªÕÑzߪ™K7¨Uô'ãGw×ÏÝÒ$°–1>>d²Ë¦ÞUòN‡“éÙ(é¦à'»mj†^Ú¦­£®o¶»’öÍ Ÿ¤´YöÁ“4Ü¥²YMŽ1~L£]݆|C+Iº¡D;ÛÚîIj[KÉùî[.þFÞ·æbRÚš&Ôå1—|%YÕá[ÓjÃ3Juë¹ØÀ†îƒ2J¶uñ‘‘1 ½g:¤ëê3GʾuKÞn@ö¶£”G©;—RÆ„C6JƒÆ¾7ß=Ù>PÊh>ôEíèm/Åñê.‚®î^á¿ Ó®pÌŸwËî™ Wö–œŠ ö¾£èhÝ§ŽøXŒ®›÷˜’OUó„…âƒ:Ö!ÚS¼!N$Õ‚bò"ôö‰œÔלøœIÚÚ³W¯ž½üþÍ»ûOÛ/ÏÇ`Þ¹Ge¬ömã1ãQøXî¶:\º<)Œ•†Gåcãùã¯Ï^~÷ðîáiBÀüë³×¯¿@ïx§]ºàªÆ‡ýÎg?Ÿ Z’J,ÓZñ˜Vì~æ;Û1ô—.àÍô57GO—§âÄȲ@²É¬@2Y±æ»À!v›O`"’]…CR ™pÄ™õÛܸB deçD8†Y…C U踨¿J"DVlG€yý©Q¸º{u>BÖJÉ+d­¥‚Y%ï fUðzÖlŒw¥p*Wz^1Þ4ªó$ bEƒX[¡1Þz6Þ ™+'—Â¥ç gtâ¨ÒsÑ9ûÓádÈC®gËL1TSxsïØ½a¦³g?[á¬Qvƒ¶Ê®Xgü‚œ5JkpÖ¨»ÁY£¸gΜ5êfð£í ¼cÀ¼Â7w–üœmt¶ÁÙFg[ö9@f DÛ|DÛêz» ë 4œiô¼aŠÑÓ–z!t‡êm6…êmqóŠÝ±¥PÇL¨c‡¬ó¡CÖ~Q¥¾€H…O)½ˆ9±À;, F}Æ‹iöëù…û–ê±®D]?Ö êr9ÖÕ¨³c¢nÝù hvƒ¢EWŒZÔ]1ꨓ#£¼GÝ‘QN¨«G¯sx{ÏÊ -ωæ©IåI¯H…LzE*d²+Rá¸Ïá¸õS¢ÁàÐòÑPªI•PªI•PªI•PªI•p¼/p<ïrN´&Š–çDKNÚ±c(5²ŒcEë TÎû±®GêdºvJ4Zž(•Ë‘”ä¨;’(•åHJB)¹"ŽËÑq Ç#à.DƒÁ¡å ¢¡T½"Jé©PJ¤j(¥GR5·£ã5²=,¦z{1ÕPªIÕPª]‘ ¥Ú©Pª_‘ ÇûÑñ Ç˾Ÿ­‡ÅTo/¦Ú£“#)Ý£îHJ¡T¹š·š£îHJ[®æ­JÔ/¦zXLz{1iN>“*¾ÿ–9iwx\×;bÌ”pG„éÜ1vÄ¿~ÎG/kf?c²{¨ü¼vDAn»ÁH)÷†ÇI©ã‘b%§»Î1pã Lê&½ bÚ£ÄH% "#G!³F­ó ÄÒÓIÕ³Lª"“Jsø=“⡪z&5lÅS©:ãÒ ‘Dò¥5R!“róŠƒ/•ËA…èž;ñ@W‹g<ÔW¤A~ py QÖàã0¯ØÅOiiNçâ) ³„Š\)eºê¹Ï’U J&ÁèÅyã Û`^±C<·{äR‰éqõ\ªÖi„®…Ì’•iѲª.˜êg¹”g@:÷/äR#?æcrl2Aî”f¤@î” ÇyQ*k€‚ãUÏ ÃJ‰Üiîùž;ñ„]‘;¥;%™V(&kÈq˜WlÅRá”A6ôùP†D'ÍXç‰O÷‰NbŠW=³©uk ·ílsAê’˜ T¤.ŸH]ÒŒ©̨‘§.uÆ@OJÚ:× Øg˻ũy¾áæn๬›-Ã0æZã1.jŠ‹r…:f]g‹š1Ö]®xŒÉÕíª ¾]î µ ;›Ý`†åŠ>xrkF•ß]_WÄZ:V!áé!ªqªœ“kwŸ›“ÃßZR?ú›ã`XŽU„¯ª"$è¡Ê#]?Vø"—sÝpV¸4:gæ+þjÎøª·£B¾òûQŸánÛ-†Ìíj<<<ÜmÒõ”S½;´:'õð—§Õåôu·ÍÒ|ýoG‹T.¼¿šŒB¿.1` ˦äq6•¶ö{ÙöGAX¨,( Ä^·øäÇŸçéxë6{&¿L~™ü.{ú(}ÝËq_ŸsZ;Î$˜I0“`¦8™àe9^gŸóÏÇÁhí¹`!ÁB‚…ò¢¯ICöÔõyy!/NXr,äXÈQ(*åîþž—³A²²²j$Ä—e ±´rÒ39Vr¬äX©Q%úz)[þØ’öµçJ†•Ä*…©U‚jºñ¯¼ã?¥Ø³’˜’˜R%¨Rµÿƒê'j(*e1¢[¹ù‡“/?–³#O#O#O£JF7þäÀ/Ý'S°‘l#ÙF²R5RhõÆç»z²pi6Òl¤Ù(U'zOçßK¾¼¾fç$;Iv’ìÔ©“ÀÉÅѼ˜–“iØÉór5‚?#_ ‰…ÌB¹qx¼¹ô,—,ÿU¾”c¡±ÐÏï^l…Âý^¸ßK"AîæÂÝ\Ö»¬Kª«í¤gäŽ/‰¹› wsÉé4ÁXŽY윛¾pÓ—L’ÜÑ%ëÿ9%„Fáî/Üý…»¿’åÎ.å44žíªÂ½_¸÷ ÷~)Š»º”»êÉ:nüÂ_¸ñ‹P$nérò±ëðt¹í‹èíÁ9ÓÑA„rç—ºŸ)¶ô4ÎôÈ?oæ¬ýù¿î·—_½yzóÇãïÏ^þ8Þúäg8õìå>ýñð>ªjTÅy ÿüƳ¿òÏÇ·÷/ÿýéžÍQùÝ›ÿÜÿñéÕ«—ßÿùîÓ/û(üø<ݽ~PÊ£”QòÓØë×%ʢ̦u6U”Ê(J2J ¥:J%õÎǼ@͋Խ@#âò ¬+9X ´äp)ð’¦@L™39h¾8ç¨9P“Ãæ€MŽ›7;nÜì¸ùâ#ôÜì¸9p³ãæÀÍŽ›7;î¯c_þðáþýß{zx|¿]b¶íÿä±Ó/ endstream endobj 1455 0 obj << /Type /XRef /Index [0 1456] /Size 1456 /W [1 3 1] /Root 1453 0 R /Info 1454 0 R /ID [<3139AD4F7DB8A7E848F752C9604DF8F3> <3139AD4F7DB8A7E848F752C9604DF8F3>] /Length 3310 /Filter /FlateDecode >> stream xÚÙGÝVÆñS§«Ý.‡vl‡vÎvÛíœc;§íœs‚’ `À$#H|Ä„ j†°™2AH¨JBW ñ\èêêŽJ/Bп5ùÛû9§ëT­g­VF£ÑÿG£ñ¨V-þ3êŸG“³§hŒÇ£ñü2ÚIà (i7i' 'a í6í¸á,¥%Ú1à –ўЎ.‡‹Z¾C›7\ Ó´û´#†«`5í)í°áXKûL›3\ëÇ£‰rL;d8h“´ƒ†am=í€áf˜¥¤í7Ü[ihû ·ÁöE­YBÛk¸vÒVÒöî‚ݴմ݆ñÁ^ÚNZ|%.ºŸ¶—¶Ó0nè í -.3G;KÛn8B‹û‹[‹ ¥]¥m5 ŽÓ·x¬0ï$-ü˜5 ãOÓ""$gà,ím“á98OûJ‹p^€‹‹ZWÐ6^‚Ë´iZXq®Òfië ¯Ám-l¼7h—µ†7áí-Rà6Ü¡¥­6¼ ÷h§h‘>÷áímÚð!$ZäZ¤Þ#xL{L[aøžÒ^Ñ"mŸÁsÚ{Ú2Ãð’q‰”¯Ç£²š¢-5|oi+hQ.ïà=m m‰áøHÛ@‹RûŸi‘C¥áøJ»H‹2ýßi×hcÃJ|±Ø§oäƒ[>Pç}`L{I‹©ÀUú’¶ø“åëú%4W¾ºû~ñ±ÊÚ—¿Û{ò~­¢}5µ~mÍ=÷"ÞOÓ¶Ð>r«_MÛEó¼=§ûµ´ý´†²¤_O;L«^†õ._Ÿ ½7”ý&ÚYš8÷2»Ÿ¥Ý ½5TýVÚzÕo§=¤½6TýNÚ {•Ü尿£½4Tìý^ÚšÜè{ïZ Ï {&ˆƒ¼ê{?G[I{j¨Øû#4äd¯Øû£43<6TìýqÚš|î{’¶– {švŒ¦zÅÞ S{†öÀP±÷çiiê¨Wì½a{vÏP±÷—i·hj°WìýUÚÚCÅÞ/ОÒÔo¯Ø{V´*`¸e¨Ø{—jUÀÃørعvÃ0~èžáÄ¥â&ã'·Á‚a<`Â'ø _à+|ƒïðögÄ®jb/5 ±ƒš‚Ø7­» ù˜6_£©[q˜Oc";oxnÁCxl?DË÷àI1šùáÏò'›˜JX' VñIX«`ìpâ{WÀšÜX +bó¾XóF°6ƒ=Rç*L7öÝ9ˆÕùÄzùÌ“U¬.ËÁPÍ€‰¬ºæ’ê&˜|«X¿™c' V¡UsûÎbtîM´µY¯6ŸÖ&ÆúŒX­›Êù5`+9¿Öà l€° 6Ã,l­° ¶ÃØ »`7ì½°öÃ8‡`؇£p ŽÃ 8 §à4Ä#œ…sÉp.Â%¸ Wà*,ÀŠb´íÏ#‘LܵpÖB\›,ëç`_RÛ`Ô?L¾&ÕֲݮÍ`El-m­¥·E„=mLõìiïð"¶d,þº¯p½íZ7tî‚Ü¿à<…gð^ÀKx¯á ¼…wð>ÀGøŸá‹‚W¿£Ã9~ü;(Ófeš•iV¦Y™feš•iV¦Y™æ jòrˆ£ŒÒÍq€‘a9Ž-2,ÇaE†e–eXÞPŒ¾¬‰Ÿ¼ ›ŠÑµ°'˵,ײ\Ë–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kY®e¹–åZ–kùH¸|Ýñ=öä[ÅèÁ¿ÅÞvg&gÓWNÀî»3»3»3»3»óâtø»ÿhº)—AXñ¶½¾Wæ~æ~æ~æ~Ï%\þìÎ?üm\e–ÀT1ñýhhË!ÎU1ú…ÐL‘¥®œ“e¹ÖÀZ˜)Šÿþulîã¹ñgN|åÆb¼ó?bOSdÇÍ-‡Ìm`Ú,ÍÀ¥e{`G²ÒY²4Ÿ–får_1þÕù¸žÃcyæŠñOEh‡Áɰ4ï–΃å10ï–‹·ö뿌ï9–§ ~ÃT_:ì•&éÒq®¼q¥é¿tt+¯[y, åMp8+oƒ#Yy×Ý_‚›÷Øš<†xG²ò¡E$–§ûÅÄË÷qkÎe¥¢´Ø”OÀŠS>'¯òX?ÊWðÞÀ[°¦”ïáX¨ÊX?ƒ%«ü ßà;ȈfT”[ÿÚ4ŒjÕȃf\LüÖl| _ i3K¡éØÈœF‚4Ò¢‘·Ñ™hfЉ?ú¯¸JÈ7?›‰¡dh$C#ÉÐDŸaD'aD¯€ÝMt8ÝD€ÓMœücC¿»˜øù¿Æ•YÑœ(&ºˆaúyÞÄQŸÓ §N7œnøÛð·áoÃÕ†«Í}¸[”ëÞÅ¥dSæÅ):ž÷AQ®ßŸ2¯aTè†Q £F5ŒjÕ0ªaTè†= S¦4LéÆ ì°w6,‚íÐ1 c@Ç€ŽZíThg18˜ »ŠòԹئØÎtk‹rϲ§cO7ìî6‚è˜Ò1¥cJ§.;¦tLé˜Ò1¥S—S:¦tLé]ÇéÎv«ñN•uöp•”ï.åÜÆmDû$ú%J­‹. +ºè°¢‹ŽˆR뢔.ºw!úüèÂ5ÓEßBÍtÑ­`EÇ€Ž:aïÔG§>:t èÐÙT‹Uñø_¢k`¯”s5.ÊóZ´ &Á¬²I­ÿ+Ç×Ê¿²-«œ\«uàà^mGóʶ¢²i«4*ÇðÊÑ­²Õ¨L‚•ãuµL•i¤Š ÅY¼r>¯åÂlÜÐ!°á«ìP*Çæj–+õQÙ VÚ•ƒqu ìd*À*ì9޾•9¬’CÕp|­,m•CkuK+^å0ZÙUvUÕ#x öCulkSQÞëãN9«g`÷U½‚h½hÙAUѲª¢ù{åhù°§Ž]nº85+j;­Úö­ŽNŒ=uý¦ÔÓEù5bUG_…)uì ×åË_ÄÑ\áQíÕÑ áLm;ó:œ©£Ý!7j 𠵨×b_‹}-öµˆ×"^‹x-ε8×v‘µÆB-ⵈ×">@›ü…¢|÷Ïq ¨í6km‚š 5jÔ ¨0 f@ˆx-âµ}-ⵈ×"^‹s-εÃE­÷U‹sÇ m¯VœÛÅäÿ“Ÿ¹6z2eQþæ/c(ö­Ø·Ê ûVì[eÐ*ƒV ´"Þ qkÞêZµ‚Ý v« ZÁn»•ã­2h»ì6úH‚ÝF÷H°Ûèy¬v¾(ço↢? !ÙÞFHìÛhôˆ}ío£ÁŽ·Ñ˜ÝVŽ·BÜ q«èZ!n…¸âV`[©Ü:¡µÛ ìOþª(ÿçqW2»ÝV>·ò¹çVœÛˆ³Ìb/%âƒÌDwˆFYœ„EwÝAfÑ-›.&ÿóïâÏ¢¶º(ÿô{ `†hž‰ý öƒØÑûAì‰>DC-º’}°Aˆ!¢åÈøa1ÎVÅåå?ý{üïXQþòsüïx1y*þw¢˜üÛ-ñ? NJõbòW0Òq= g ºûçà plotting application. =head1 SYNOPSIS veusz_listen [F]... =head1 DESCRIPTION B is a scientific plotting and graphing package. B provides a command line interface to its scripting interface. B opens a new window (with an optional window title) and listens to stdin. It executes Veusz scripting commands, writing any output to stdout. B is now deprecated. Please use B instead. =head1 SEE ALSO veusz(1) =head1 BUGS Please report bugs at https://gna.org/bugs/?group=veusz =head1 AUTHORS B was written by Jeremy Sanders . This manual page was written by Jeremy Sanders . =head1 COPYRIGHT Copyright (C) 2003-2012 Jeremy Sanders . 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 2, or (at your option) any later version. On Debian GNU/Linux systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. =cut veusz-1.15/Documents/widget_doc.xsl0000644002344000001440000000275611734662204017412 0ustar jssusers00000000000000

    Veusz widget API

    Can be placed in:

    Display name: , description:

    Setting API nameDisplay name DescriptionTypeDefault Choice
    veusz-1.15/Documents/veusz.pod0000644002344000001440000000512611734662204016424 0ustar jssusers00000000000000=head1 NAME Veusz - a scientific plotting and graphing application. =head1 SYNOPSIS veusz [I] [F]... =head1 DESCRIPTION B is a scientific plotting and graphing package. It is designed to create publication-ready output in a variety of different output formats. Graphs are built-up combining plotting widgets. Veusz has a GUI user interface (started with the C command), a Python module interface and a scripting interface. If started without command line arguments, B will open up with a new empty document. The program will otherwise open the listed documents. =head1 OPTIONS =over 8 =item B<--unsafe-mode> Do not check opened scripts for the presence of unsafe Python commands. This allows you to create or open complete Python scripts with Veusz commands if they come from a trusted source. =item B<--listen> Read Veusz commands from stdin, executing them, then writing the results to stdout. This option is intended to replace the veusz_listen standalone program. In this mode Veusz does not read any input documents, but will use the first argument to the program as the window title, if given. =item B<--quiet> If in listening mode, do not open a window before running commands, but execute them quietly. =item B<--export>=I Export the next Veusz document file on the command line to the graphics file I. Supported file types include EPS, PDF, SVG, PNG, BMP, JPG and XPM. The extension of the output file is used to determine the output file format. There should be as many export options specified as input Veusz documents on the command line. =item B<--plugin>=I Loads the Veusz plugin I when starting Veusz. This option provides a per-session alternative to adding the plugin in the preferences dialog box. =item B<--help> Displays the options to the program and exits. =item B<--version> Displays information about the currently installed version and exits. =back =head1 BUGS Please report bugs at https://gna.org/bugs/?group=veusz =head1 AUTHORS B was written by Jeremy Sanders . This manual page was written by Jeremy Sanders . =head1 COPYRIGHT Copyright (C) 2003-2012 Jeremy Sanders . 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 2, or (at your option) any later version. On Debian GNU/Linux systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL'. =cut veusz-1.15/Documents/manimages/0000755002344000001440000000000011734662466016513 5ustar jssusers00000000000000veusz-1.15/Documents/manimages/winwithgraph.png0000644002344000001440000015337011734662204021733 0ustar jssusers00000000000000‰PNG  IHDRUYÎ(ˆ^sRGB®Îé pHYs!R!RsëžqtIMEÚ(æ:ÆtEXtCommentCreated with GIMPW IDATxÚìu|GÇgövÏr’\Ü<ÁŠ(R(úm R¬·àîRh¡xB …"Å¡w×à’ˆ_.ç;ï—\.—“ݽ½Ýß›Oßcwv|g¾ûŒÁ×Ùñ"R$„D"CˆDNœÜ#XB|…\Fº9`Xú’æf!·>‡JcÒJqÉ .9•²>Bãi49ZÓçà"Rä%”`<\¯Í1’ÀÁ_éêì![='tCL!£` ýØAÚ)®¤BуÔ_NÊËv}€ô¼…†t"H+ ƒ²t¹NØîŠ‘+ý5²íQÞ¹kÄ¢+«ÕÓè5èØbÖs!Ú%I%ɈI.!êbÑ &¢á %1.ˆœP*î 1®Ó(9³ßÇaa€\.”Ò²Cˆ&º£8ÞÂ…CÇÖ79.t/ï¥ä ›ˆg\N°Ó}–øc¥KFìTrîM/½ § —1ô¤ÑÀJ…"!&Š :u‘¼–Ð$½AŸ™‘®ÓiMÿäŠä£zõœõt¨XB-Îïà⬈IüBAle²>Èæ ‡ ¶ŠÌü·“d…ň~ ñ*ÞºÜüºÆ?6£‚е‘ùÈ:,V¤Da ÃÈ „ zPèCˆIƒû£‰iµš×o^f¤§©TÊœµP(ôðxz*BBÃ$î»°¼±°äÆ„õعä!­‡i8¦i´ãsÁË´,i;§a–÷mÛêìß6,ÕagjÝd¿ÇB.~ Ð…š¢…¿¢ùeç’Â*¨h‚ùXJ”(Œq12¤Q¡ŽioÎ_X™/”JõßV6lXƒ6šù'O›6›‚a¼sgç–-ãg?žØ‡÷ÉÞ{òäIzzFv¶Š$I¡X,òô”GF–¯T¹ZHH"…¹Ž"ÅÏqÅdEнÑEÈj ­áÍÒ‚€€"Ú9¡:fXÔ, !´XÐ] «ÌWä÷ÑÁ»3ÿ˜¸+~*nfC%/JtHÉu)QÃBdB‘¸ƒ×²|ù:÷ïÇ'$d›º;„Ä€Dƒ!4mÚ1ÃFüÐÔ>ü Þ¿xvûÖõùùú5iÚ<(0Xáí­ÌÊJL|ûðÁ½óç/¦¤¦Ö¬Y«bdeÄÍ1(NªrGÈæÄ,È &´f¦Ñ‹ÚGŠ€…è Ò¶\*†@z,³Ý9Ç9cA;³n-fÙÈà *M·ˆ~•¦«þ UÁõz\Îá’’%«g…@Jư„î ˜ˆrÕ?ÆI’8ÏD~ƒƒpüÄëËWPxxµï‡5¶‹©/==ýÚÕË?mÕ²U­Oêšoy{{{{{GEE?{öôÀþ¿ôz£\æˆɱž‹ˆUÂL€E‰€4Ý~<(QZa/pº.Øís³á„6 tM§X]¤—C.ź['´ˆØIz©†?\{ÉYöëîÏŸ'V¤Da »‘Á„T®\A”KL$LM'„À@BI†œ¿ ‘ ÊMŸÖL$âÙ‹ªÁ@Æ=~øðaÜ—]ºÔ¨U›$…ÿÊ•+×»÷·ÏŸ=úô±Z£±±D!–(ìýÓ¦ûÂ.m^t,*.mº¡ÄÇùé†Ü¶”f7ΰ/qŸÉ„ŽYw%´VUxQZR0DɵeT[P^ÒAcˆ ËDœ%†/¹ŽèdbV²¨ð›ƒJR.)ð‡XoÑ .8ãp'°‰1¬§èÅ‹V1yñâõÈ8²ÿ•)LÚwïd@PŽŠùÈô!½qSúû÷M‹?oámT±Œ´7oܬP¡BDD$IÚ…P…·wÆ ïÞ{V.,<Ìuv¶¯œì´ÂÙUNvš‰ö¬Â2ÿÓm¶>h2å^4ÍÛG&ó‘Egro@Àt å™N̵ò@KK™ì¾üÐf \hhÑ4ÕЈ+DÔãOsÛs‘‹±¡5ÃaXȽäJܘ/`wÎŸË eµÓœöWò>° È›7ñuë6š@ë\½b2ûb[íËÁ²"ÐàØØñsç.D ÿ~ÈÒ¥?.YºbÖÌ©þ~¾&>±I …1ÆüàÀýVý´Æì¡ëC7E¦‹ß|öìù³gÏþyób,YËidpeÉãÑc´zdÐkŒ ¤LÜOfPáiy"Fx@ vøéxÁ' Ú$u:ƒX,ðùY¶P3Ô£çfX´¢=ûÃÐ&:É›ÿgê¼MóÿL“ü ,Ðîûü?Kš²µ§/È›?B«è)ìL’XF åj̓ˆUo?¦ù®8…n)/væÿ“:ÿgÿ¢‰%TìÆ!ºùi“ŸÅOû¦uȶÙÏÉËeï;%·¥W©D6w7¢V„æÿ!fvd«¤©|4;«ÈçÿÙ aC!Í›·èÜÙ çÎ_˜1}Jÿ~}Íb‹@¬=y¿dé óƒ&çÏ_Ü­Ë—þþ~nÆÛLµlùÊsç.|Þ¢ùÉ“§V®\ýý°ÁÔ™Ê ¢>Jy¡S ƒƒ% ?™!>ûíÝ´G2BâïÛDH«"¾ƒÝeH/…‚$¡*'[*•[Þâó «aƒ^1žT*ƒ†ŒE½4-ó¡™ç(>EË™™©FÒ8xð„ÐÂE˾ü²£¿¿…È ÌqxÙÚìë‰×ï%ßìxºåBÂ…`q€‚ÈÑaFÒQûK’F©DÒ a£»wïgd¤‘ö¥Qçܾ}·ZT´—§§½…Ê&rK¼MYÊ®cê-ý4_t5“UÏmºÌ37æ'ÙŦ ÑnAÝ󆛓a0³›ôoXÞ¤·«š“°ll´ÆdÛ6»Ñ2å.BÜ#F¡äíÿçlW=‡á2J©Ý]ø?à¶?›Á!ÒGÅ Di·DÄB)Ó®NôSjÎ7ª~ÑßrÑõ·ž^˜…E©=tü\k¶J-ü9&Ê•+Q$v1†Ý]ºôoß¾½Íq4¨ÿÅ‹§|}}(F­:ÓÞí£/OŒ;=9Ú'zbýQvw“ ¤çzŸ”r "ƒò0¤7B>n  G»výêí[ך4i$‘Ê »Ñj4×®]©T¹få*Uù8àÎÿ kA¹x˜¿ÿŸi@ån ò§ÂÜ#+òçÿåßrmþŸ“æÀæŒä²]R“E©,O؃TrÒˆ:t¼'™­ÊB%ÆÐæL0HáYÈRaÙöˆYuAÌ ¸cþt¹×DÔ÷ÿc²1×1+ôÂÖ>:û=9ÝYÚI.lqÖdA§óT m“™¡Uä°%¤’?öæÿQßAÒQì;wÞrïþlH‰Â##ÉœÌÿó*ž§?×4ÃOŽ~¯~?¿é,ÉI IˆóãEÎ$iÀ Aõµ0 ;zäxtõja¡a|À4]M§Õ&¿O¾ÿAý†MË—//àFƒŽÃ:½IÞ´>‹ù¼¼ Hòïü¹æ~æ»ùs]›ÿÇd¡Ÿ¨è6t“ìY ¨ìDãÄli5êÎÀœVÞRáWf­Q§T’бøJ±øàÊ oÉÀ¾N~ t¯ó(b)QãJdŠšÿ8• ,ùX±D•:dÄ‚€EÓ UO ,IUå£[S÷ØíÅ­‡@îˆC Æ>P" ~n%?À™þqËrý‡y39¤Ó,Ë™§|¢Ü£" > 6  €„^¯Ï½‘ÅÁ•oÊ=›Z=œçlj|÷¦ga¡ÈæG±àa ÿ裼` ¬æBY“”·r6ÿßù½ç»…ægaÁØ TðÈ%dâa^b†òcj1)¸@*‘U,ó3™ãS K-7»(”YæBÐ2Ž!Ë2±|Ö*›-Ê,FX°HsÿÌËÊ«bÖ¥R@J¢@æYVëÂæÄ‰§5[rB‹c9rË™ÎI²øô±ø sÛ°ümö°ÚrßÒ@ç»Fò¼Ö3ÿ³ æ­òC–ဂ<ÜüˆÉo„,ñ¸—Ô‚k ó;³ß¨À…rÊ"dñÏÜÿ7÷ëÐFì­Ï¶`ŒQÞÐìÈ`0 ëÒÌï( æp/Ys’p7ŒÐÜÏ,P«æÎØì‚Ç ÍWްÀ&ÐFöå÷k¹ 4 }AGÀjÉ&ÊwŒ@Ã7sà ø„Uâ…ë½@ t`T ö1eS ¤úñA}ë—L2‚ahk”-(ž‡”&¡AºÆJ–CšI€ *]ËdœŸœ8qâd·bm`1°ÇQ±bÒš ‰è&ľïˆY~ÚÏRĤ˜ ÌJ€ Ë(ÿ& feî ‘@ÏÑIj¡|Dtö³‹h¾pË€ò€t5ÊÅBR|€3ÅÍw-­;¥n¸8¸ ¸e,˜hC·×"ÚÅõ_ftfG®¹Ñsн 3Ìq‘¸\ðÝ•£YJø„?ÀürbXoPAþãôŸér Å;4wyróy±…>PèG‚B í:qô,rÂ/ï;sM¡!†T]¶ °0AÒöÑN;>iy^‘±[Ç‹8\àÜfàfàc™ù8ìãÈUáEZu9•X ´Õ¯"·ùϰ‹GvQŒòÐvŒÒóÈÂ9M_¡3¤‘pÐù| Ä KÙ;{Á©ÏÈÍ;¬|\o+3Z+J³÷ÁRbém ã®Å”s¹¿z"ÖžDÿÁ·‰“3þãô_¢@`ãtÈ Ç±j´»?½m¶‚ÌâIóyäŒ1'…£“(´Ð¶qò™pZJWg3u(¶ K'Ûû';4P3„±1·2_‘aË(…Ø|¹7®œ8þãTº:!h«\æ8öÌ”gYÁ"`A·ã ‹DèÑKÃÞ`cÆóš‹éÝaá9TŒ‘¡I{Å |l0_)Â>Àü8ÿ!º¦‡ÔÛòŸ*eªé¢ÕVd–98+7¬Ä¡ðS¦+즈VŠÄOÛ=¤ŠÙžæ¸rˆ%Šœì4ZìæÐ7¯œìtWìð™ØÃ3G•Î ºM×l/ï¡4‘ϕΠ2yÞéÄA*ž¹ݲÇ]QžsoX?ŠŽ!Ñba>Plƒ¼Àý?ŽüþãB–üÇD…‘Â}„DÝg7Å¡hà¯ÄÔ h‘ð4ó5‰B•‡w%b\Ø®ÖÔÆdÑÛ,È*ºŽiÈîšo¸zãOKa׆ŠÜKwœ:ÌšI ¹ ÄÊa§Ò/œiý°vé!õQ)S¬îzH}òH"…õÃäyžÏÈÓu©7Ý@UÊ˧òR„,“f•"«[63H¬eB,ý±™Ÿ…/Z>N?+líW„€J™ê!Q¨²SMW=$¹vÙœìT€Xâ rM€ùÿ4ßµdTî¹WÄEžÿiù—ˆ=9ª4±‡g´Ï†®×GÊ[ÓÂAF™®Ì±ë1t…Ô†ûÚp½‘K”ÄFHlBì%ÞEs+»ûå 7e˜ëAaN#÷¢q¢ÄlŠ%râ¹™r bËÁAŠ$Ðk:P×â‰lNÓòz«òÀN,ñÎÉNÍÉN5ý0_±¼ë°I¢Í–ÍÖƒÎÄŠœì4ØÃ+G•f/:gß-¥¡Zd TáþºhLµ·ŠƒÎþLŒ}Œû¤’¼R•o›"Æí¹ øJó¹û8òãäþcɰG/,›!ZºŠ^fÖ,ü_×}f|—Õr±9.œê`wX;ÀGÃ3Û1‹qA£ (¸þ”]tºD„,@¡ÓþÒŽ-k»û•¶.­ˆFwÝœ-¹!ŠøJóqØÇ©”óE¼`…ÌŠ1íñœ›RäJÜ çIQEÉÌSX­Ë)ÔfšG‡™¶l0ãlá m4´4r±É‚îÀA—‰ÐAo !«•Áq‡ãò1|ÿ½^ {F äž$–6Fn¨A¥Šù8ìãTìüWÑ­È ¬$„n‰§E%ó„HÛ h9:Ì­Y³°ì,4v/ ²ƒ€–‰ ²°k³Û¹ÐI*vPôå.M WÂúi6öàF®'±œW´F% :pØÇ©˜„M0î³Q±î3Å\KSœÙ½û¦RI©[ ¶Ò…òàÏÖt#” ¨ð¡ÓôÚ@GØ4Ò°æ{BN#‹œFˆîˆº§È¦kDË4àà¯èÀÉÑbùÏÒw6}ÎÿŠŠðØ+SûÉ “@Jéåe×VEíÖ¢pí]´zÒ¶OE^8}Œr£ýÏÞx¥‹ÜcIZÀþü?wp[)b1t*Qbœ–k郭Ì7-[öV)SM;&räm/ÈÆÚ[ÈÉNµ^ÿ[P9Ùi&´Í„Vk!hZí¹ƒ¶¢gå£a)džóTp–º3'B× 2GC³ w>?+nî@E›‹J"íqÌÇé#7þË©8«Å;ÐÝÁEú`‘ÇSöÀÒSB¥%ŠksiTä©.FÚc'H:gÑqÌÇ©xÄÿr*Q¢]Ó ÃÓ2¨WéC´ “xiZY;³×³G€Åâ=*¦À‹õÜ™vÎÈǩċã?N%³w„°Í=8ŠÈü䯣»l±­Î ²R(Ü;Q‚ ±ò(*Ý honÉÕjN%—ÿŠd¾'NÌØ"ÀÁâ#B`ÿ\'öŽá…,äMYéõàÇR…KIl>Œ>ž2à€ÓÇ×·rö?N–H"tÌ…€öäB·-ïeÖÕA÷zÏ¡¢{CB%²€Üléä€ÓÇ%œ«ÄœJmIa€²Np%ÆXî Ý8ò Ýû‘Ÿ¥œöÜ|z-ú¸ó1÷˜ë49}$üZ(qYó_V)©,}¶0ÃmÓK Ä8í­)¢½:À »smƒö•’’zóÖͬ,%Bˆbü_€T*­]«–··Â}5»ø+ÌG‡Îˆ¹ßðqúHdÚÿ%òÑôè^TtœÇÍüïÂßÇR˜¤‹>%¬<|x'ºz=ÇJm¹ÚVZZúå+Wüå2+‘ÉÌÊJN~_¯^]//¯’ÇK¥V{=âhÓ[B±'4õjU†¥QÀ`DB¡È ×pôŸÕGZ +a1ƧXê€EFƒ–rjJæ^¾|E¡PˆDbVçc0´F£ÞhÐ+Ùá”Gõ8q¢ØVXÚÿ,¥Ñ¨ãâîEEÕáY â¸àɳW&NÈÎV2k(/ZQ.Ô1Bˆa<» “IÒ€HÒµöòÑ£GÞ¹}ÓÜL¿ML  °éþmbRppÀŋף££oß¾­V¥#Ä0Æã <¦Î¨Ód“túiʼ‰O=ê‡ìì,€N¯Oùègåìýû™\zõê­®[·N•ê¸7²Y ‡;rÔ¨ììl€N§OIùhåìÃû÷2¹üúÕ«ÃÅx8Ä»wï1yëT_|ñEHHH«V­NŸ>}øða‘HÔ ADjŒ½}¸aÂÄ»wïnݺÕÛÛS¯ËAŒøL¯7<{ö¬råJV×SSSÃÂ+,]ºpð E€"B‚/ÎRªvïÞ}þü…mÛ¶†Ú²eË6mÚ@`t³E¿(pÊ…m•9ÚãÄ)_"«ñ_+ŠåmÚ¶MÊxSí“HáÝ»ú$Ô¯üßû÷iÔYú~½»té’½†¸¬ñ­0 IDATvíÚR‰À#"Nöí?óôòô”ÉB@«Ñ¦gddffU®\ñëÿu  ×ku øÊÄÎ]{?~j3ˆŠ#{tïF%ˆ‡E–Ïå?‘‡Ç3eÅúõëcbú2 Ðë Ïž?«\Éÿ•©°t þ³PŽ^}?ùqÝô¾-y8€ÄœÙs—.[*“)*FÖzÿDáåOăG×ärù²eK¾êÖU«Q"†6È"å'׎ÏàP'çüçhý¯yÐRÊììêõ*~=¸Máe¡ž•@oÒ%)_v°€„û™ŽGyø|qß¾½wýù§€Ï/|W«Ó5lPÿÂÅK–#IÒ WÓiÔ R©Tx ¦Œ 7ý;#Ó°rCrÏÞßš]\¹tqûo[pŸ5wáÎß·Žÿ>lÖ„²ÇþMëôí"’$çÌ™­UgÒmFqB¸qãÆ_ýuÙ²e6ܹsgþüùZ^À‡ôƉ Ðj5µ¢ßö3Ýüs×ïëYݦ]ûððàîÕ¬­²zóÛúóx¼=þ§ÓdÓ­ÃÕ–«Õ}Ä8À“[7âÜ)nhx™Fí;ùqí‘ß60ÀA¸y¾ÂG 0`üøñŽÓM„L&Û¼yóªU«ª” Ø17æÅÛ”.£F;¶{÷ÿ9~6;;û³Ï>3f ³—çÊ•+Ödg§Ä´ ú¡CGegggffªT* äR)BÃ0™L¾lŪéSc©|ZÉŒY³ÌŸ_£uÙï7·ö‘¾{šŽððÈ}Å‚”xåå?ŸÖ«WwêÔi“'Çê4ÙŽ¿a,êfþ @€ïÛwààáceÂþøâs©Tb¾ûèÑc^_¿~Ý'OžÍœ½°cû6;¶×j ´^œÂAܹsO*“V¬˜ÿ ªTf3 ÂhÐ 6ì§Ÿ~ 8pË–-k×®­X!R§U1¶ñSUF*:´ñì“&èÊ?æF ›“zÕëþÍJ ûJr4Øà)áO•£kÝúów‰ú÷–œüæöÝóiéï³”é¡ÁÝ» ×hÕ11ýΜ9»rå ½6»Pþ0¹¼¡Çyœ81^¸Ñw<ägO²ˆÏò;Ì‚vš†™¬|Í$F&%½›4²ÿ¤ý ßܲóÀð‰  (ð1]·nÝþýc49™,æË›ø×qTãƒ_4SìßR¥u÷õë×oýEs{¢»ã¯¿þÚ»wïnݺÙtpæÌ™V­ZyÊeêœ Ó¢×éZm¾UchLðõ;Ê­[6öêÕÃfÇϬ8 ·Mï˜ç÷îüºe‹Ípˆ$I ³4ÑÒåË—ì%óøiÂ׃3×”-[Bz WŸ={–––V·.Ý)üÎéµãäà߇ù|V«U*•*•J.—c†ã8A|>¿R¥J'OœrÊBB ‰éßïÐÑ}—7 ®”»ÉƧŒz ”Ÿ|Ò®\ûѵ¢[…-›³øõ›WëÖ®ÓiìZa5õ£Gw£¢ëà<ž³œ@€¯ß°éÎûM?kdµÊ£G˜;éÄ­¤ã{. õ¬ìŽèÖ©Yíëέ•© æ¿ô¤Wƒ zúô9†ñŠ ¿¾h¦ ‘¦§§ÓÄ„ñð—/_]ºt©OŸ>ö¬AüñGïÞ½ -»qöôôòòRøøøX^ òØkU×*ázùûÓõçêÕ«C‡-ÜçI¥ÒwïÞuéò¥Ñ`øql×@ù̵‡ïlÞ¸æÅµM»ö¡çÏ_ø…SŠ-£: U(Þ'%mÞ¸Þ"Ü¡—ÏŸuöñc––ãÇ;vL«µÎ‰Db0ºtéòî]ÒÔmêV+óÛ᫺ݠAƒŸþyýúõú÷£ŠF£;v¬ÕZǵ¼L’4ˆ™cX´†¼}›Èãá¦Ñ^‰D"d2™T*õÈ“X,Æy<›Ýʳ0òpþÍ[wÌŸßwySOÿ<÷õŒ†¦­ñáù„Ó[>¾”¸yÌ¿ýW4ï2¹Î¬ñ3;vìX©BY{ËAp4UEÛûð°7¯_<|¬ég<<Ä6áÀ³0J¥’zu?ÙàPÍšÑþ~N×jðpžÍ x<ž-­€…‚¤²Ä ×ôéÓgÉ’%>¬R­ÚW_=mÊä§Ož´jÕªgϞ˖-óVÈõ:ŠÓKC@€´´4…B-:A±Ô|Ddì±…‡âNMl:´WÎ Z „NéB,3KµtÙÒþ1Ó~û}ùò…[ÃBË]ºòï’§’’b§š=}u“F_df¥;ù{‹fÝbc§œ;÷/ÅiŽl/«à`ާ’jÿ³èÕqq÷ŒÆbx]­šZ£‘Lþjïï}J 555#3'+[§Té…"9ݱ?±°¿oÿkÏ.Óß›7¯6j\·~ýû÷JĸPàê.¸<Û¶m½{÷¶ç`Û¶m]ºt ×ùµ|YÑŽÕ媄ž¯rÎüW%ôüÏóëצ±ç-Ý:^vIJÕÐ?øšÿ PÌôyjÖf––æÍ›Ï›7Ïê¢H$"bèСW®\éÚ¢FÏ6u®Ü5Óñ  À={öÄÅÅýþûïtC¬7[Ññ”Óþm1åôçJ”èŽz.‹ôz=A^^^ …ÂÓÓS.—›(P*•’$ééåi× ‚@€OˆÆ[£uYKË_ϭH¸€Ý"|ÈÚ–Ÿ|x•õϦ{aQ>ÑŸ—™0q<Î#guàÑ£»†B Eð‰»ö” ³gù³ýeâë²k×_ß¹] ðßÿØ]8KÙ´æÁsf™B¦½qQ7wî\ÀêU++Vª¼ïïCcÇO …;vì¨T©Òæ_·ñ…RNPð͹fÏY°uÛv:sÝ»bnñæœZ¹ûþáá cÖÉ›Q­¬ä| çïÞ½[.÷~ŸœÐ´Iëð°ˆ·.î=°mä°éÕªÔêÝ}Hë–]Fï“üþm»6ÿKOO®YëâÅó¯_ÇCŒG!w$ÚÕ,âĉS±ÙÿL2Ë&ÀfRZzf«¯=yþƱûÖ­[›O?nöì™:­Šzp|;±«Ê¶?êµÖ-”‡'wWÁq—fÂñpþµk×^¾|Ù£‡íyo™™™û÷ïÿûï¿]üÍQcç%¼O±MŸ(wîWÊepî¤`*¾1¨e«FI<=#ªUÏ¥‡Õé½*3Ò='˜TP7{ò ‹Ï‹Å«V­Ú¼ysõ !Ó¿k—ø!cÄâÝ<ÿ믽¾¾¾«W¯Fl첦ԥèI Cû!€çËn ô(îþý8€Âçóy<žP(”H$R©ÔŒ€'Nœ(¸nÔ:Q†?{þâÌé³ßonmuëÆ¡WÜ·»£XžËµ„ï[oyÏC·¿n=¤Fý®åW<ŸàïëI’F{ÓBQµèº¡ÐÒ,að}ròãÇO¿øâsêðgRd…ò'NœJKM•Hä¤ýœLA<}úÌ*›he4á!‘SÙ+J¯×tìØ¡~ýú—/_þuÓÆaÃ8hpë¶mgN›vîì™ï¾ûδ.¤RÅHNÅ`ó)µZݺMG3êíÝw aƒú=È3*/=·~ËÍ=ý>ùzDØüÇŽì$…b¬|U'_ð~þü… ‘5ïÜ;ÿý I;woX»iI‹¦í:¶ëñøÉ½¦Û”¨¼ê—9CG~µpöÆÏ·ŽOx¢ðò½páÂWÝ:“F»ûLý²v=`ÐwÌM1Bhõšµ8ÎôÝÀ‰§…ÿ iOqí§¯$¬ŒW5!‘;.#è â€ÆårW9hôª—i÷RT â·u×ßÙJå¡•C)Žº[ð§2;Ò£}þJ­Ö8czØíãïÇ©DB,¢ Am‚/Ä Q^Íûí·ßZµjåogöÛž={¼¼¼š7oŽaà‹ZOŸ¼ÿ>žzþ†¤{OGCŸÿÞ³íÏ$§–?Jë êí‹gk&mئƒ™ÿ6ÏÞ⫞iÉïö¬Yù¿ác\¯¯<O"‘üûï¿£Fòõ’®ÿ•ÑH[°3C™³iÓ¦ºuëfddèt®n0nª?f+2„м‘2+oÝ›·‡;qìÊM[)•ÙAA8Ž›ùO&“Éåò¸¸8¡€ßòóæ¿+ˆƒGøz‡˜¥ðòÖûƒ+nÈ‚¶[‘”ï.K|œ–®õ-#óS:thà€¾¤ÎXØ4“_0hÐi­JáÎ{ž^žV«}Â@.—I¥’;wî5nÒÄÿñpAPD@Ë ”p ôê 4mÚtúu=zõV(¡¡aë7m>zøðœÙ3/\¸P£FqãÆM:…à½NMÏÐ+=rÀô{Óæ­qqË—@-:A±°úò¶5W¶u¯ÞqRÓaÂür-B'±„„/ϲ÷î]:þϾƒGv®\;ûÝð/““ßÞtK  úÔ´ã¦ôë×gĕ뗼½ýß¾}k1HbHVVÖØq“Z­vÄÃr!uÙ3fÎôêÙ]"‘Nœ8•J!»ö?Šk?½Ä•üêì¸7ýCN¼ùâ;å3ÓøÌ‡³Î´7_÷óï5óîÛÓ´£‰Ð–?tiâCÅý›¤ô—o?ÄÄô3PÞÁkùºw‹~z/—;oÎ22”“GúïH « á•«·†}?Ô4¥ !”””$•JÃË„ÙtŸ––Æçó#+”7ýS"‘ü²fmíZÑt¯z= îÒµ+€ÐÞƒqîêõïóõ¿ª¬¬/}Ÿü&ÝwÏÑåÞ>OnÝxþà®+4V©R%ÓOOÏW¯^}õU7 ‚Ÿ&|åë%³lOÜ«äáÇÇÄÄdee‘L÷oÔ¨‘Á`à øÏ”—Ò5ïiêÜ\ºõîX ô9@Ê÷©ìõ™Ví’öüù‹ãÆO<`¦\®˜0vÉÒãßU¯mZð!•JÁ™3g4šœßwlqjû¹yë¦_dÚ›ô,ã÷©ç´1Ú¦Ë1¤Æ+q>ÏÃSˆ”ݸycÖ¿°S[uZ„‹½zýÚÓâüÜ‹¯$&¾£˜^^ž¯^½ù¬)V§ Ö5X(§˜žžQ¿~ë l5~…G"õzmãÆÚ´isäÈ‘_Vÿ;eª©¾µi×îÓÆ—/]òûößæÏŸ¿sçÎÕ«W·lÙB£VZÎÓ°7º‰ ýhÑ¢Ù7}z!ЙÃ(¸ÌÖÔ»ËέëX¹åÌ£!€–^ ju Æ£2ÿÇ ’4à8Ѿõÿ^½~zÿáÍzušL¿tñòØ/;ö)Q¹e‡ªbɼkŸ¿xD|£Ñ@„ƒY™\>lذŸþyò”¡!!]ºtÚ¹k· þ~øáþ8qú˜ìùM‰P(¬õ‰@(tLÁ²òGžýr=ñ°Í»ZcÎëŒ{æ¾Î¸ç#©åßÖ&ÚÄ>ÓË7î¾xýöˉ](&fÏ?·ªU­\«VMµ*"ÿ^º®;³]ûN=ß¿w屢?ïoÕæÛã?þ´éSÅ>XÓ®L¶¾|êîôÓ9dЫé>U½ŒBŒF%F$§¤Òú0‰b(¬ 5j=º~¥€±ÇÛ'þÙ“½ë¾èG§áÚŸÏŸ0a€ @“&SSÓæëX½BȺ¿.¾ð Y³fK—.U©TWiЛo$“É©†W?]huëÔË­æßcn÷Ç*3H…IÇŽÿ3köÒ¡ƒæxxÈ>/ Ðû·ßöŒ5àŸþ©T©"Ÿ/ÈÎVf+•ú÷ýöÛ^†9öb011Aâ/4_I§Ú:þŒAO~Ž4¢Ë®ksô5Z•áÀC!xû6BŒZ@DÒÓ3yÇÓ©T9Ôá Ó32LU{ !´ ‚ŠÞªsª‰Ä¢A ›ó&Qáëz­jÞ¼yGŽÙñÛ¶¾1ý‚‚ƒÍcúÌY:9mJì㸸֭[/Y²ä‡áC Ì01yh#,”dÞ­°ÐÜ+zÝ­×·f?¾Ö²|ãEmbyùeçÉá?H Ë;ÙH$+F^½ü04´ü‡Ô¤‹¶¯\3ëCJÒøÛ©/^>Öéµþ¾A çl ?|lwpP¹+×NDFF"ÒhËJ´xñüôôôcÇŽN˜4%>!aÕOk¼½mÛ¶]¸`®Ñ œ8qúèøjk?@OÀ LJUž¾þdùöSááa»ví"IR£aa¢ªXÅ·qRö3‰È M2@"Pð1ÀSà'* ˜Ö¯=øñÇõÃÍ ÅÁ ðô÷—Ÿ=wX(Äo^?Ÿ˜£Î   ¼EB”׋«Ò5[ÆœÖæ¬ÃE½¸‘|jóýÄ'éžþ-¿‹6_ÇìŸêÆV õíQˆœÜ„mkÁ ŽªÚ³gÏ;v¬\±|Áâ%–wkÔ¬9aÒä~ßöäÙ˜Q¡P¨Xsu>"hÄÝß:ûV_©ž£DÊL˜­!æÇAp§É#ú–-[þºyK¯î£ÞÕ¸a«ÑÃgíúkÓБ_.\9Eü³Ö…«r²/\:Ù»ÇX½^ߤIÇÇ’ͺµ«GŽ÷èÈ‘£å#ÊV©RuÙ²eüqâô1óŸI:.î~TÔ'62·.?(C“ü.û¹ùâ;å3Q ðÄÒóu²­"Ü¡9þ›­ÊùëÐ?³‡´£èþÜíç陪޽{ë‹cÁJ±K,7øäëO›6/[¡Cæ?û©>õÞ{Ê=Ó3³˜ù鏨'¢|Vؾl¾Lá½nú¤ÊŸÔkùu/Ó(8X>g|ìœ*µV(îÛ·ßÓÓ3++ËŸïß¿îܹš5k6hÐ`põµ&ÖOÑ¿šyº `PíUe¿V­Z¹=+Ô™YD­vÉìñÓÏë:ó]ÿé8NðxX@€< @¾ÿÀöû÷nn\ÿ3Ž3Üd˜$ µkÕ>ùãai@9YZ«l¸{ò5ñ²5üj·/WµI¨¥MùÝ“¬Ú_Õ&IÝ:@’Æ2ááåo—mZ{AÓÓ3š~ÖØñLMST”˜áÏ<}–b…e4êË„‡4èçŸ^¶tñšµëÍ·Öüü³©üôÓO:­Ò•Mš.¼¾1üÀô­Në·‚Ðès­}¡™µšzU¨C±fA`1†Q2p"ýŠ˾éóíïÛ|øðôéSÀÉ3—¿ýöÛÄÄÄqãÆ©T*Æk>L’J¥ .ŒŒŒ¬R¥Jrr~5æa¹uæTk-^²òĉ+ýúÆâ8aÐ?@îàùÇ®OŸ>øeÍŒá`4èÚ·oÿîejÊ›,¾YÖŒ/Â!–x]'×´¦å¤}¿YüYÕÏ À_ò‹Œ”·mÛ¶µ·°ƒ:`45kFgff)•Ù–UÕiœMOU¯e4&ÍX½z5« (ÂÅ ì™§L™"‹O©‘2©°k—Î2‰Àí"sâÄ©xùÏ4ÜS,Û>à¹ôÌk·tmQƒ¢ûýgîø·hÑÂàžÉ%V¡L»sKú·íÉ÷IÀhF#äñÁÏ£C ª•¥ÕöîÞ±{×Ö¦¿¿÷ÿYNäþ×Î>} ªU?þúþ½ÕÝòr±ÏÖÍ`ýz0fŒ§€p%u½{÷ŠD"¥R)‹Í§€8p`óæÍr¹<--­ðÑ tåéérùòå«W¯~þyþns>D™/+m_axˆ¸ª+þߺ}¯U‹ÿa!ôûùË6ÿº*5%qåŠÅV‡kµº­Ûv´ëеL¹Êe#ª ün˜S#YÙ2aŸ·lqy÷3€w¨´Ï‚&˜Å@m™h߀òž¸ÀÆÑˆ—v=kÛ®Mpp IéÖ’$}|}+VŒ|òä™åu*øøñ“¨¨ªr/…³¤!_ßÊ•+Záþ¨a'\ƒ·×èÑ£K-|úäIÇvm:´mýôÉÓU«×ðp|Þ¼y+Wþ$Ét?ùq¿=ü%¾[º-“ ó·ìyŸpìØ?•*UxòäY ¥5[ÖÒª³–.]<ðÙE3 IDATxÈ «Æx+ü»uf0â÷ܹÿèÂ;¼wÏ1¯ÞÄýöû² å«÷ï;e˜Uk×®ïÕ«Cg_†ˆ4êu9üqâôñÉÚöàxȯÈtû^\•ˆ *e¨Y‰ÀîîÄô„”°Š{מìZw”úBެ´ìoGu¦Zx„¼;kÒ€À `Ðã«ñë5Vÿ¹ €Çã7n.ýe[ÜÑ˧óÈ]!“V¨RÁãÚ-¥½õ%Žëç ¿Íóö¥#ˆïz÷ –uFRˆc!Bÿ#Óñ»“''ç¸ gAܸqcÆ ëׯ‰D"‘‘‘á¢ÙÏRûöí;räȆ LKŒóHÛȯ@«qéð·ÃMž²hÈw3=ýüek×/ á³­lH×oÜì×È‹/+UªØ­ë—$";÷¤J•JŽìUºœE ×­['ºeXX”Op%E÷™Ÿî˜rÞj ˆ•^ÞzÿàÌ›í·ÿÖÛÙ&Ýi; Óé{tï6söÂð°P_oKö‚ß'ˆOH8 F¯u>“L§Óý¿.…ƒ -ÞñÂðG+;æ4ÕØ±c׬YsýÚµöm¾0]œ1mÊÆ_·,\´xìèQ£FòööîÙãkZ³Ÿ¦¼ê»{¬\(Ùö¿å>^–·ÂBC6nXݺmçysg0‹3B¤V9kæÔvíÚNž<åÇŸÆËå^>ÞF£ñÂÅÃ$2öìÙkÞü):u>{þ@“F§NÚ°pÉÐ=zíÜõ‡^›Å ìrâÄñL×ý錹0÷ŒW…(È´„i[†ýºÕ´\H¸rê®\!¥â˜Ï§Mþ’á8¿~Ù¬9þ!2™Ì˜ôV{ü€ð³6hñ†Ÿµ¾~ýºÍÇ'MêHÛdê€ðÑsм9P*sÿ]¿þW_~ îÜÞÞ %?ΟϽի×ga~…OÎ4i’½p ëĉ»víÚ´i“X, ¥9h–ôàÁƒùóç?þèÑ£µjÕ Ï?"Ùh4ºîÿñ'ïÞ»¾ý%±±3W¬œì;~ÜH+øûûà‘¾1 Å®ÛÚ´neº«Ÿïœ;]Tµ*Ó¦M_:wÑ7Ë+‚$åëtWgï«öIWîw}îÜy+Dh5v3Óq;`4ƒCÂ:¶osüÄ©&M>µÜ¥Ù-3P©Ì¾rõz×.µ-…¤ƒCÂ:ulwìØIË Ì'p†?ºAØ1ÅbQllì˜1cmÛ¶ Ú°aÃAßmýmÇä©ÓæÎž£P(Zµl®ÓfSñóuÆÛoþE`øÖÿ-”ú¹£©AiÔYµjVûçäñÄwI.\HHHÀq<22²Q£Fb!ÆÃ—-[:vì¸À€2‘å£'Ž[3gþÀÉ“§Ì›7[«Îœ8qâø/ÏøAoí盌¸ÿUÅ ֵʄ)Ì÷ûMKO×huGQtÿçÉ[u>©ªV¥Ó +"œøuãO'ŽþåÔe||—_0™ª\ÁûóTÓòâúû p?•Ò9=óx„1î¾þî €Çà±ÒñsÞ%%e&&êt:qÃf Gå¹aÏ;w/^lóq‘HT»v-ÆQ»o»ôí 23­¬g`ï^ë‡ÇŽ}‘“süøqáZtçÊ¡C‡vèÐaæÌ™ÑÑѽzõrÇ; ‹'OžlúÍx^—=ý¶ý…‹– 4àóÏ›MŸ1ò‹V-~>ÄÊÍ_íé?¸R¥ û÷î²ÜüÅÒéèÓK«ŒøúÍ«­£w}9ù“ð(ßèÏÓ_f^Üõ˜‡c…-{ç]ïݳϘѣ´çæ+í€N«éر]jjÚÙ³êÕýĦМ„÷É®\½Þ ~Ý6_´ÒP¶Ìé´šíÛ¤|H± ‚ÇãF›–?AØ6js†ràÀ°°° 6à8®V«·oß> _ßíì ‹Ó¯_¿¤¤$*ü—©QöÙ9JO~ÿzU¸g°[t£A«6h^âÎÚ˜¦÷‘¤‘4ê5j-„pذ!wïÞ[¶rÔ¤q¿„…F2ÑÒáÝ»w¯\©iÔNœ8qüè'e½u©4ð"åîûì× ¿bIcPPP\Üãúß.¦úüñ3ãßì !ꦩ)¬M‹D­š*¨§C(<㼜,ííã¯(>¦VjŸÜ{¥Uë¤_Ç{ÿâ„Pý÷.€è«o=ÆÎÌQ¥yÊE^r„3T3L«Ë©Z9ÂZu¦ã l×™3A×®B`~µ<‚ Úµ#gÏ "õA áZD@xæÌóo7½eË–-[¶¬;|Ž‹{2ü‡1M›6Y0Žã­¿hYØf³vÝÆñ¦TŽÚ¿o—BáÅÌð£Ug­ýå—2áegŽŸÕ"¼~·ò-F×é!’ñÍÎ’_d\ÚõìÁ™7sçÎ3z”VCiÔÏA;€Ðht11}||}ö8Y¡¼\.³DÀôôŒÌ̬ǟÄ'$víÒÑDfÔGmáïïçëãmµÚ÷éÓgÌ‚°. €þÔ©“!­6Û¨›6mÌÌÌsÈÐÁ>|ЩÉk¿ÇS5x¨ÐÃk¯üü|—ü2àw4€ÅÃùªŸdsVi4Y‘Ș d®/‚qPtm[ñÿú |õU.ÿñù¦b˽]­>Üøm­.ÛÅ>8%%åÕ«|nV©T©©©„–ç¨Úéá¥RééÓ§™-¹xñ¢¯¯¯Óɦ.\Òëõ[6¯+¼ÈW­Ñ9|lÍÚ —.]iղŖ_×I¥RÆyešþ5qÂØ:L˜8~ÍÀãþaŠ€H™‡B€R¥i“žf}HÈhÓ¶õo·TªX^«ÉtZ:T΀Fi4ÚvíÚÔ¬½k×_'Nœ’J%^^ž Õj233Oœ8Uuà€˜€À@dV8¹\†ã¼;wHOÏP*³] Â!Íâ•ÎÙ¹sgÛ¶mM$³fÍš<9–âVS© ©ÓÚôM¼›œœþêõ«+åÊ–+ÇÚÇ "]öŽÛÇŸ°péðÆÚ¥¦%edHY7{sâÄ©ä zËaSÇþ<\ðâeÂÄI³³™ÌÁ’Je ,,ht|>/„< §~úB$Åñ<ï!NHÇOwûÖu©¨ýI½óèuJÇÝ ÁᄈY³Š0èÕŽ™ˆdšŸóB˺:˜ÅÅ þý|ÿØ)ЩܺEV­ ?¤B)F±Ð × .¾á„pÇï» Àl…†a›6mêñuW½ýÓóL5966–ÙlB™L6þ|§5ùùóuê5iܨaÙ²á|¾ÀÓSîé)OII½{÷þ¹óU*UXhȘÑ#bbú`47è±›.Ÿˆããß:tèÆÍoß&`<,80¤víÚíÚµ ÔkUF –ºÓ?x<ŒðÓSSïܹ÷êÕ›ôŒ €—§g¹reªW’{)ôZ‹“)y<! ¬‚ðV(Ê” £ÄÃGq‘åËN?„· Z¿~ýaÆhr2Í«bõzóçÏ+W²Þ¬455-¼l%WR;i\ì¤q,7úˆd7nÜÚ¸qcff֌ӈý×vNàÄé?.‘ØËšÿ#Úöåá|‚¦\£×«]„vZ@ ãó= Æcö ­ÓªŠ}C ã„HVÅúªgÇu OžK—¢ÝæìgAˆTj³#Ý„B¡‡X ×å8é΋¤&ÿûïÙÉSg$&¾Óh´ÙÙÙÇ+VŒlÔ¨aÇöm7þ”-ò³ª<œa¸i˜éÉhБ¤‘Å:`“¼y8Ï´Ó !D’¤Ñ`dq¥¶ëA0æ?|±é¥³lìñŸ^¯¿uû®+é  rGÂÃù8.À ¡V“ÍàœqNœ8•~þ3èsTif×)œ8qúˆõêÕkÿ«ÓIÕMrÒû2e¸ìåĉSI–yÜRì¡À¸ìàĉÓG~~¾9.Ÿc)’$sT*_.o9qâTŠ„ÛCNœ8qú(%‰ ™™™Ì†€ K¯7xxˆ!×~râÄ©´ò'Nœ8}ô EÔw¶çĉ§RÜø/'Nœ8qâĉÇœ8qâĉ'Nœ8þãĉ'Nœ8qâÄñ'Nœ8qâĉ§‚ÿ˜mºË‰'NœÜ-®}æÄ‰“[øO£Q?~|Ÿ$¹œáĉ§’\û̉'vTàü“Œ$ „z=÷•ɉ'N%K\û̉'×%öPØØÿ‡Sã¢Ñh„¬ž’ĉE!„´ZŽópç‚à‚à‚(² ŠW´Úg’$0íÔ ›ÄøŒç<{IÒ¨Óf#¦æJ!Á'x<^áCºBzÞh0²›·†óp>—p¶Ñh:8ÛÀb8ÎÇX '!ÌnEH¯S Z®3ýÏÊî;¬Ñ¨?~õ‰Uã Ñh¯]¿A=>AÔ«W‡ºûë×o~Ýã›5«lÕ²…=7ïÞ%uíÖsĈa_ÿ¯«+ýÄ¡CG+Tˆ¬P¡|i,¹´´ô2å*/Yr̤&vëÚ‰zÐB‘|μùÇŽwê²~½º Ì×k³˜Œ" N:}íúMƒÁšdRI·n_úøúôìçóÏŸ¿áë7lùn`_›nNý{æê¸3g/Õ¬QŽ"'=w~ÞÜyß šB›6¯©[·nçNm 4G«!B‘èÎí;îÞ[³fu ³èÇÔå+~^ºdž!­€BPvëöÝñÆþ÷LP„y©‡ŸðàýëS«ŽŽ5ªy‹f‹-‰Žª¢Õ(°¬9ˆ±ãÇœ=}.(Âǯ¼Dâ/ÜO~ýÏÊ##GŽlñyóE Ó ‚ DãÆŽU§e6®]AÂÏ\¿¾âœÄy-µA?tÿÔkoïœêÿ»Ÿ¤äA- H%fT¤E¤B `|€‰O€Ð½ÄZ|ñ†§Óƒð`ÒSæjƒ !:t˜‡‡Gá[ÙÙÙ¿üòËû÷ï ‘gmºð÷½¬'*3Ój_Ãäèõz½^OÝ=†ñ„B—ƒV÷$©õj ¿ýYÁ^E¥Ò>¦NŸ[«q×rkÿ¼vJËÏ›–-[Æ:c33GÚ¾wlfú‡cbîûƒb­HMM{xÄ uÞ~þ{toJJ ¤9: !ù.\¼|ùª¯¯OÆ6àæ}ò‡¿ïÚ·ïïš5£ƒ‚Û †… D²™³f/˜?¿Fë²ßoní"µr“¯¼üçÓzõêN™2uÊäXšÍÐ bêÔ©“c)a¶2»E݆ DbBCrºáC&²Äçñ¼¤˜X€t}r:X½âü£ÛtÇŽ ¾ã‘zÆjÌZ ’6løéåË—-/®X±bèZM6àTRùÏÜ-A*à €ÐÐÐ5j`F^P–Wd2YP ffVŸ>ýÞWØ4U\ŠŒ,ðÀnOEd6s¢ôhî”Éa }ÛŠ…†a#ëÖ«ÇÌ“gÏ^ü}ø°9¯ÂºtêP”©ÐÏ‘’—øð×tT]3öÈÜó¯¯Í}þöÎ;0Šâ‹ão¶\K. I!ZB—^©*MfAÅ , ˆR)‚ÒTëE,€€ô*U@z€!Ò¯ßîÎïMŽËÝår¹î’÷ñ ss3;»³³»ß}3ofZøÙšÐÚâkNõDÌ¥–\°ê©h¡”Ë^| áª[–Çœ›GOñŸ~¥Ù²Ka¶äŸ£.í-Sßѵh\ÚÞ±mGt'/m_ÔVöé$¿ÝiµÚ¤¤¤×_½œjòÚeö·eê¿Ô|´$·×@/Ý&½ÿÓ†-[¨R  +(¬Ta¥ Að‚¤°P…ER˜©ÂBª´HÀª¾ÛšT­ZH±[Þ{vGXPõÈ(7i2òîÜÈH±”]ÿš‡÷ç¼¼¼ý‡Ž}iÒ¥ïˆF¼±uó–eíÓ¼þÖ{-: ÐV ÐV;¶û÷ .Ö¯ï#aJÅšÕëþÞ¾‹ãX‹Åz9ù q:ÚÔÔ›„aöî;°~æ9OW©4RÉGB”ê ^|ñϤµÏÎëR«¡k^[Ûïí–ÍŽ™?ý“«)W¾^ú•É˜í¡‰N.âù_\ïYó¦r5åêW_.õ¼àkT3¯ÿ9gìKAÓ©A¼£2r ïpÕC¸•ñ·åæmÔ=Ï·îîÅ©Qi‚ÿþ{çÊ•+£££Ç{—c‰(Ú½\QªÑhX–E±ÀBÉ+•JŠýÀ>öŒ²íÅv+ؽ(3\qB8Žûí×Ý{ömjÓö'0Þ‹Ïœ;7º}ÝÐù7¯O>]8 _‡ÇIù¾¦[ÔåHf“H)(UL@ ¨gÍŒFE¤}Á.Ž‚RxcJо;Ve&Ö˯ m ékì×ÓôÌèà—Þ :¶)#4Dòª ÷ÕWßW_í®{÷î#GŽdæé§ŸvÚõòt»bß¾q kH¨X6-ª[ %/®œvÆ‘âîϵQ÷¡Û¶ëß|ЧWÏF =ýö”is}nŠ-Õ¤  ðz'y–Ô S©ŽVº¯—.‰‹åj–Ë…DÁjÑgÙÊÿ•c¹.Ü_£FÄùó¯_¿Q§N\ƉM7üõ·Õ­Z¶HHhP’áôäã”ÇŽÿ;{Ö¬çæwuÇ6]^÷éaxôÝ6ͺk°¨8pbëiïNëß¿BƒØb}5Ø‚"ž×¥Xñg+bÐÄ6ÓÆMëß@ƒú1ºƒH³ªg?Ëþ`1K7¯géøÎ‡#ßµ\½„(b#ô‹fè>›)ß2ˆB¡zðÉh.ikçê™3gÚtö† Ž9Ú¤q}±À¼- f½î¶Ru·wÛlʳZ (³üEÿÙÛ“ŒçÎý×´i+— 6ýçÜÿkü÷ßÙW^}SÎ¥ÓérróŠÓ¥žhå±ÿø÷dÊ•klý`¹uëvnNnãF ##k„<3uj\½úÓç|¬ö«Ù|¬_ŠßðŸä°šà«~þ꟟Ÿ¾ïÑ·:>˜z¥íÙÜ´yËÜÏ?üö“¾óèK×>˜;«có6cG¿QÊ&AD,&ªÏ³³„Ü\Ñl<« bBõ ÁÂR‰|$)½pÙoê¿v*wüš&}ý“fë^…NOB‚èØ—õ-[¿™“Û²wØ¿«ßzÑûџ篙SïÜí≨Æ5ŠSL›6íêÕ«III?üpùµ}Û”=úâDw%¾?·lyŸš·Þ¸r6:.X–ë6à•_~kç¶õ<Ï|mL»îÃTêüûpæíé×O?ÚV‰v@«b¢(…ó7 ñÕŸ|<óÀ¡ŠéÛç•—G YÅn?##sÚôÙjºI£†={vmP¿žÍ©qã†ç/\lܸai*W¼;nì}½â£CÅß¼ÃùVÒOþ{ Û´z³ž±ïoÃú?ʼn3…2`Ü{ïÞ×+¾¨n_—Ä4 oÖ#ö½÷ÇmX¿Þ(dz’EÌÊS}VÿÙLñö- PÐÍŸ&œ<¼èG¤ìÍ»¶æ?ð†TT f¾V2£,Ã¥§ßY¿~½}ä7ß|óÅç EœA¦òé?•Jݤi+¥R-¿k:¾Ù°l±âã¸õë×FQAX¹råäÉ“ßÐ[¹¹¹ûö¬4úïñÇ÷¤ÏÔ÷ß¿¶jG“6>žó¡í¸Ú¶k—تý ýË\üuíòP€òîHùvûÝ1e¸}¦‘@+Àª“ëçîúªÞt£LNÙî½û¾øá»I?ÏP(P·iýw¾šðû•3ç|2ñ=ïƒK !„•¨(€^'ÞÎ0gç ¢åŒzIÀ°Ã.ŠB)E§'S> \<#7PCŸz#xØ£¦Ÿe3 œ8Ú´dvN³DaØ£¦íû¥Ô‡ÏÞ}ƒoRGÝ(N•=wîÜáÇggg—þ\H¬[©¾“ÆhiDMéä~ô”<HKe/žáÞ™fýbVàñC|ƒFÂu* -áÆEÞÍ·EC†”›%eåJ:ª2RÎ,dXÌ·­†L!/GÈÒ‹“¤²Jf©tSʉ"{àw๞ ­™ÜΙJ=Úæ—K¿€—G¾`ká”ÒÅK¾æ8îå‘/”èþLùîëE}û?©ðzµ°H¨ßjÆÌ¹õêÅë-ªø„rJƒ.çïµ_üòãR…‚/ÑÖQ~{ìõ›º‹ÓºýµråʹóƵê î4•Þ|ó­Ö­[7kÒ@‹·liµLzÏ9¾QÃÄ-[þ.Õ½…á.]º¼sǮ׿ïåBüÙm%`û!õ–ŒøëÚµÔˆð@©èÖ"±cûN‡"<¡ýú‹Gl¾ví†û"îÖ« 9æàË2Ÿ*(KÌÛ’îto ’(Þ¾¢`3{„,Z.dè©X²îrŽW­Xñ½lüëÞ½ûÎ;EQ\µjÕÂ… aн·òè¿ÂþeÆ"7ãÜùëS½zu[9ìáÃû“y‹~üé—?½úÊKñq±ðÇêuçÏ_t™¦oŸ‡zÈ»±Á`Ý$E°¼Z„KüÌØ}åÐØ¤ZG7ýì‘y–µþ ÿP“íç}·à»?xvúHu€Úù§g§ŒühØÚ¶õúÑÂh€Õ€BM*F¡`XŽXz1Í äM”R•‚Q0šF¡!\ £¯{·íSô{Ð|á2W-XÊÌ"ô•§ «#ßü¬ÉÊa€a€/ÝÔƒ& Ä»{ȱD¥ æþûïÏËË *åÙž=A#öd€@-Õå‘û{äw'íÙªlÔÜúâ½ìhÔ¦“åôq¾¤Ûÿî»O+¦Ù*¾]®øt¡Ø¦¥qÊx±M+à8øc÷›–Lú–6V¿4Êüþ;ÖáÊÊ!äâMãÇ¿Ÿ³o<%4Ñ1qq±çÎoÑ¢¹í­C¥R ‚`0š4nxöÜyï/R–;zìhDýÀbÄ_0²AБ#GFŒxÞí#’;zôhzÚðXí¹}©`1y4dÅl°Æ4 çÙGŽéd÷-Z<‚åê­€7'YO1ïÝž¯ù¬–»S{ð åƒ}5#Þ1_L-ñ‹ §ø÷ä©S§NÉÖ„¾}û¦¦¦îر–/_>pàÔ•Dÿ¹ïVp°ÿy.þ@6«·Z¶¼ïô©ÃÅß}n&—Éñ—Ÿþ£”Nú`ª×Ùà…þûrñB÷gMòý¥ã-D:Í“0 Ž¥žµvrý°ø¥~¤â”À½ gbE¶‡÷#Nöì;@‚ù:M\»:r<7üׯMš¼ü›¥^¶( 奖 d‚‚ØÐ .4ˆT¤dog›kTSæf‹º1PÇXó§Véå\Ia{w3<Êwmo9u–³ “W@Y ç“Ùzq¥êÐüv}FÒ»&·ÎÍÞ{*B„ÔÔÔC‡½ð ^oüßø¿Ö¨’ŽÞ‘¿^8éhó6VÙ.xp—âƒOsm^æ©棭—KúKñéBˈç-£GÙO¨&±ÌîPói³b¿ÿM9ûSUS(ÐIη‹¹sffeemÞ¼éýñ“¯_¿ñÙç_†……öéÓçãÙ3«¡D÷gM5üqÙ’§‡¿ÒíÑ×ÃkÔ¾sëZ½†m¶oXÖ¡ûà+ç~6FçN½;ä,½µyüýcNŸ³÷‰EŸ/ÛsJX8MųMâaĺ:O5›6í©Ç&¯øÏk Ø ~ÝÿΜ³é?•JµeËß7 kØ0aóæ­]»töö¶Ï¤¦^—g`.Fü¹’€¡Ê©×¸/âÆÍëÊŽC€Â–¯ÿõpß~¥yûAõ·|õïÅá¸VÑ’r;äËUwº6’2nS«];§`£cƒþ`¹z‹–|ºlŽW-_¾\÷éÓG£Q0`̘1”””™™ f%IÄßpѼìýËÜÛÿÆÿÙŒ‚.E•‡ö¿ ƒ–ó¬D¢(vîÜ1-õ’ý§C‡v1ï{{ÕÊåö1›6®yõ•—¾\²È‹Bccc¾XüÕÁƒÿ¸ÜŸ% X²nmõ`á&ïÈËË2üå>%ºözúì¹oEM•î(Î8{çÒ‹¼_CñÝà9Zeþ›:©-²ÏéAM½>é³>ùô±±O¹I߸.qûöò^ „PU5 cC¸ˆêʘHuBíÀf±Ú'JÔl•Ì&Él V#HFÞJ `1JÀh&f+ŒùÝÚ¿T5mÛÜJ)¬ýKÕ£S©Fg¾~N¼íóÞS™™™™™Ù¢E 7·Oøq©¦{³68¿ Öþ¤nßÅÂ+(;È[L¤c7KAë…3ÿrôÅ‘æÄhT~4GèÑÕAüÙ¿x™ß~S|à~åôÙPôLÅ¢`\ºô‹¨['.)is½ºñƒ \¼øs›øóüþlO£† ¿®üvë SSÎ_>ûZ˜‘~ý÷ï¦|µx®×â‘ê|:Ï1„IÍ=?y{j5öÌ{±Ñ¸A ,¢Vü;ù–yçë}ër¬—ïØ ‰ Μ9k_z½þСà …¢Aƒz7Ro¥0;Bz$þì$à‰¿®xQTÇÇÑÌSñ7¸|§b†zop8XB B^˜H¸+VÈ_‡J)oÚ´)È#û9ÞŸ< wúOFžh´(k‘›Éÿв¨yhÿû÷ä©Þ}:äΘ–vëég^ú«tC€å‹¿üì,Ëþ¹ö×€Âü¶ê‡˜wÞ~£Gnö1mÛ¶þpÊ!pF§Ó V«°ôóÅíõk&ûgýª£ÇNäåå•ù!_¹šr85ó@­Vòç¾ù¯íÞÜ™C¤ë~À«– ý$TZV³gìÙ»?ªaíà°bfw{üݧgÌãõÑP>ÂHHÉÇÔR%Ä4ŠÓÆEªµÇJA’¨h¥’Àâe ¬%>ÉwleYþ»ú™AÆY_~±L³t…†céŸ[•#‡ÖmQ¥Þb†ô){ÿÙˆˆˆÁƒSJ'L˜PŠ08¸SÙ´U¾sñÎMÊý;¶Îß½Û”M[[ƒ f®Ù³E ¥r7±¯ÁnÛA2³,¯p·”!–×F’Ûw¸»Üµj™3wNûöíëÕ«Û±cÇç|L¨Ù‹û3$m;Ñÿ™yÝÍürÙ¶ÚµcTJeÒo‹ë4lÛ¾Û ~O¼É±lbBýUë>ôøÇ=‡Î^öËn/Zûµ;¦þ £5\¢Þšó¿ãïý«Å/ç^þéäd9Áâ^U{ßìWç¼yí åyÅ©¶Gˆ1¿ÆÁó;ÁÜ:î¥2XÂná’/Ÿ™9¢Ød!áÕ¢ÆìÚ½·Ë¼ÔaTie‚†J<Ã…‚ æLf‘!$(ˆS( CPB% ¢—NÀu6¿7[ûþ(]«¦Ö·¦½õ¼!4DÚw„ÿîÍ©¹Ù̘©Úéïè¼T±í;´íÕëaÏ öf³yì;o–HÿéuDŸGàøAþÈ~´¹ß²g«²SOóžmŠ~CóÕžQO¾]ðñ×9J•/NÕÉž8IµZ)±Ÿ±I#ªÑ°'N ÷ts“TpôwÆnß±£[·n<+%ËÜߟ—ÿ²gÔøÿ5¨¤Õ¼;õçŸ~IŠªÓ¢W»^?/œy«u§~-Ôkð»'/‰MÖâYöõñË’¯¦O7¸D®7‹Y¹êÑ—ÏÝû„ YDI0‹úSé;XÂ1„‘¨D©´äŸ×^m³dP‡–¿ì¾îEÝ&&Ö?}úLtt”l—uäø¹sT*ÕK/>»îÏ T*«u÷î}:´å «¥øåþDÑÚªe«m‹’ÃJl¸º•œ“v>§ÕàV’(SD«V¶ÉÞ PdG°½ø€´ ¹­‡´v_„ƒ…O[C·pªyïß ¸ÈE-Ó¶ ÜÒ¹šçß6_*Á@žWÛrHpøðásçÎÇÅÔ(´â«Øî(´lýÝ?*|ªÿ7&¦ög ?©ŸÐ¼rW:uÜØ‡¶¾™©›?{ö»“&Ù~š;ç£K“?˜1%ZÃÜa´¿ýúcE®Þá Ò9Nܬâ_Ôç°¹Ïÿþn–1çÇÇæ× ¡ÕERS$µË`pÉ•+W%„GU÷$qÿW-»Øký\uIE Çs* ÊæLF‘Rà$@ËòjÂ*€”âjòˆiΗϼ²jIö‰ÿ¸ +% :´²®Z’õïYþé7‚õ6==Øû²1oóÖ¨ò;ã z˜—~ph·¢÷ S½V}¯ñ¶^ ^‚˜•Á$ŸãnÝdvmVN™Ÿ[7Qðµ+nã‘5çnœzùHj"1YúŒs‚Ô¬…80tºbMSAZåàA¬Vu5¦ªØûó팼w§þôäÀ_Î}aÈ¢o6OœùëÓOõûïèß±µ"8óõÝ›–5iûèºMç_y¶ÇœÉOB>Z°vö¢?‡ôoÛ4±v±»¥ÓåÍŸñÃ0„)ãÇLî²áÛ£c.g`N”¬"-t¦®fŸªàåÊ“ ê×Û¹k¯ìÌ'B­èè'NŠ‚xâÄɘ˜ÚC?Ú¤qÃ5"L&“^¯ß¾cWî]y…Âj)frQ°ôíÛwôèÑNhÕ´{Œç¶OBˆ$J;–ŸîÛ·¯ûyïDÁÒ¯_¿1cÆÜ¹–^[ë^:ˆ¿[É9·¯g[Dá»MˆõèýÛï„W0ÕÂ(•¤ì¬|wAÐ}2EÑ®3W«±p;Û³ãeòt†µk׺OöÃ?LýðÔ~iÿ³†&“ñüùÿš4mÅwâÑyü_±ÂÎæyàþZ“¥n“Ñ‚å ¼ÂDzœV+_“ÄOW't¨„'NN˜8¥n€X=8°zpàÇüöHÿ¿ÿö³Í”X§nÙK¾îÝwàß[ÿGˆGGM‹^f…R§8W§ƒRN7P~þT侘ùÞÕìß šÛ8¢¥Í¬Š¤;Å6OŠøvùÝŸòt²â°Èp.XuæìùDÇe}Zþ³›º¶6!¾®ÿ@¥R7nÒJ©T»÷èvÿç‰UÏ×ü?lïs•ãD6oÞtÃú?Æ~ëïÓ²ô}y~äËN'W¥V©|ó¹‘:S#ý›9ãNÝ:ÿYÔì–+;Áhpevó ”îܽgæ{ƒ<ÏÒë…G¾üö»sf—ÂÆ\e”„Óe±ÑB†§¼šðZ¨)(¼?Æ­»ÏøèóÀ> 4Ì!VMêÛݼçÌúñ‚ï7Z¥’¶{ ÿ9­ÒÐÖ÷zfGF‹‘ѾëTøËؤü—ɣǸg^ZußXÁ­Ã>·åoEÑ[—UÏïÏ'þK ÕF׬–_.Ç6ks3-ã«%3äkÚ‡ã_üøÆ­¼`MÁ»ß8±Öé3uÑ ÙÏ?÷ôK/>_ð,`}öù+o¾÷Îü5Íâ^ïÓ&D ·ÞÛr?Ü_{ð#u§ÍúíÂí/wx^Q§NÜßÛw*xþôé3Ù¹¹Ó§NÊÈÈLM½)/|ýúa°Z¬;wíéò@§b;‚-&Ýœç¶k×¶ÙC11MÂ=ÜŸ+ÇÓOm¿ºüè³¹øá(“î“9Ÿ¶k×¶ùÃ1µ‡ÛKÀç2ÿÛuzÙQü]>ž~zGÊÇÖyRD~ ¬¦5®úžfg$ÀvìTÍKo[®ÜB‚—þÆ}1K·`:’$ݾeücßåQÏ %Ф¤üvÞ©S§_~ùE(XxšãU»víêÒ¥ \»víÌ™³uâ¢Ðè_áÿkñÈÿ×sñûT ý¬¾˜LÿäÓÍ×E]dcgñ'3ú­×}sÏÅhË»÷^?2ûáq]~{XüQCo”å«Bò•+‰í“’¼~$´L<~òd©| åk,Xâ£A Ñ$0Š ¨ šF\e´n\°<"&Z\:+'å@ú™·/ìº}q÷íEÓrýBüUÄ÷‰­Z*çFrr‹|ÏÌÊV.ø\ìÐN,ݪežÜŸQâ ñg†çö÷^µZÃ6!2 =[‚Rj1ë†lù#÷A[áXrö²¿¯Nÿå,-"Ödæ¬ß/¤—Xü†aDADA…Ä„{öì¿}'£S§¯¿6R§Ó·nݲn½:r: …½`¬ëÿåŠB-–¦M}ðÁä?fü“•ªód·2®ç­žyxúô‰ õþÔØ±ï˜ Y‹Ù˜;iâÄ«)W—½ý›CAÕ5eRP‹UóÄ‹D¡T=øˆjà0!Co¿ÂDó¥T.®iõ]g¿ÿhÞ¾Q=ôYjõèQ°çÏŸW³fÍáß1²íoÚ¢`Z»víÒ¥K›7oÞªU †°<ÐeÏž=r‚1cÆŒ3füøñS§N6rð>á;Ï[ýç÷ ëÖ­KII)‘1oß¾}¾cÿKMMýhæÇmÛ¶®i‹ÌÉÉá¼F}÷²L»u+²ÆÝ)æ&£Ùd ‰ŠŠôÙsÛ´i“àà ÿj[.íùWåÏo1H¼@ÊÚ‚uýÆú÷%”4WçÁÝ¿}ç‹Òë¿! ¦ï?•©v-Ãÿ¾RðÈ`k¿>B—ÎRíh`R®s;vñ6I5# ÿûJŠªYV%º¹?é×vú¼5Ÿ,Þ8{Òã°â÷½7Ó³‡ ì`ŸæñG;Ìû2iá×›&¼5¾þñïœ\Ó…Óx‡ Ò>Û ªúùgôfo^áÌfKÇû;Ô‰Ë)Úžj±Xsóò´nÝÒ`0ܼ™& T«Õ]ºtÂxSJ†ì¯¾\?mÜÔ¦=b; ©_=Îñæ™~9gÿ¯Om¿:}úŒ±cß1²=ï;’‹øzéWq±ñÓÞÖ¬Gl‡¡EqáÔö/Šë­l¾M ŽK³ùBš‹µ})éÙb&Ëw¬ì;ŒZë-ô¥ÕjªU}þ¼¹”JfcŽÃ|4³¾QbÝÏ?[ I¢QŸZiÆSU)û_!aXÈ¿ÌÅ⃔eÙÍ›7oÞ¼¹¤…±,  ˜Ù–(¬X±rÿþƒ.©c({ö%ÉH¡ûÿïF»@áHû„…¶lYhC —åÙþ µk.ÔEœ-Þ!Ò>h—\·Têð‘Eؽµ?Îm‰‚cÛ¹ÛØ8–¡o‰ IDAT)ÜΩÙdÌÈHÏÉͨW¯ C@ Í[{'#óøñã¹¹yró§”â_ü‹ñ/þÅ¿%ú Z­¶E‹ûÂB«Ù?bçÿS(._:£Pª¢kÅ‹¢U‡õþq&=#×bÜ/Ÿ Š’BÁE„ÙÞh†c9ÅÍÔ£ÉP§N‚Å|w-¯Ì¬¬C‡רQ=((«AñšÜÜœôôÛmÛ´®V­š½þ+ÔÿK›“Õ¸I+A0K¢€µ†Tqë¸ #·+ñGÂCµvú﮽š˜EQÊÓÓ3re H)E+¥4¢FÔ©ÿ!¤ÐÚ .\¬['>ýŽÎ`̹'GÞµKß9 jMˆ›_†ll©‚øÅý*))éžìUøø /µmÓÚ>’§åŸ ÃPÉŠg©ººI¢·Ò3róôzA¥Rk«WQðœóàP‹EÕª”¼]ÆL9 w2óìóJ’À±>ë A{KNNî;>ºš®Z­ tˆtдào!£`PH”C¶ÜìT‡˜ (9ÒM LpÞZÙn©Òâ0f“õè±S‡²Z­±±1õêÕÍÌÌLI¹võêU^ߦusm ÒÎ ˜±BŠÍ¨RñΗÓÕWÔת(þíÛ·Ÿ2e²íóÀƒgÍšéGûüä“Ã"##çÏŸç³{8aÂD–Søê •:?Vˆ(XlþJUÐÑ#{š4k ’Õ¾ŸËUa² õR~°¬â¿³ÉkÖ¬‰©=`À#f³yõk† ¬T*wìØuôè±¾}û4m\ŸÉfÿK¿“ªU)Õ¶Œ;u¬)'Ðét{vï=uú¿¾}û4L¬{'3;"<ÈÞþÇñšOjѲ£Ù”k‹}¯êÁGÆÿÉö¿â„ £G¸ý¼eóþûãgÍšU[Æ}¶iß¾ÃþýûýkŸgÍšýꫯ†„ûÑ>/^¼¤oß¾±±1~´Ï+VüÔ´iÓfÍšúü>»›?ä^ÿkÛºñ¥Kɉ‰ l1šÀðÒÎÿå`”¿:ÿµÿêœÅ!¥r³Sí“Ù[¶ì&lŸÒöÕyǪfüÓ,ÿýwF£Q8À`0<öØLžòä“O †.]:תUëÂ…‹¹yF(|…Ûg|àNï`Æ¢(æää<öØÇO—3êõf÷·Ä›·Zz÷ã>æý÷Ç›¦ûR¥¯IÔ¨Õõë׿rùJ½ºu¾Zú刑/wêÔ¥M›Ö_.YÌqÜ¥KÉqqqZm Tx‚LI4ê9ctTÍgŸ{^£Ñ,_ö¿Ý{ö¼õÖ˜ æ…„T“3fdÞÁz.)öSÀ´nsÿ©S§ªÎ±8°Jëªv¼½zõR«ÕUçx»téR‰ÍiJë®È’Ç{ 9°cç~_ÛU¦L¶b3ÔUØFlÆ¿’n§Lv©¬×-Ï3M›6ÉÍË»x)™çù/—,žç8.-íVnn^ݺu• ÖMÆäËWÚ·o'géÖµë¼yŸäåéäŒ<Ï`%—¯<ù)mÛ¶Åã­ÄÜwß} …¢êo£FªÚ%ìËxjÿsM²ð’E˜}À9W±vµb7â¹´ß·¢6èœRþZT_3R¥°ZŒáa¡ƒ Z³f͵k×ë׫ûàƒfgç¤\»–—§{ðÁꉢÁ}Æ-Zeffi4&£I« `ºuëVÍbÎÃöyÚ‡) <@vŽ#‚Tˆþs#‰ìÅ–ç7ái¾bwÒ“H|ˆ J%«U]ó¹çž;}úô… ¬V«J¥JHHhРZ­,:{õÁ2Œ(J¢(Q®øŒ‚ Šbñ‹ 2¸°/‚ È=Ö劽‰¥rÏ‘DÁlÊ lß¾]›6­%‰2 aFÌVsžýâo2<ÏçéLÀ²¢Õ*¨5AíÛ·kݺ5¥ù­“AŸ-BžÎäµï‚ â_L™<¥Zh0À‹¨ÿ\ƒšñ5¨$š 9„0,§ „ˆ‚d¬ÎÊO&"L›ž‘—‘•'¯ Ù,Ë©Õj†aEQ0’$€(J<ÏG„i±zAÔâ«*J‚ÕäIJYÕ‰’ƒ@”€g4ªüÉ]±Û·Lh×®ý°ñ}¦N› /Ìñeý‡£©¤´ ÂCA|›»zŸX‚ø ²ÿ/‚ RJ¸¢"¤ÂÞÆ®8¼A2|Ð8>Vpü‚ ‚ HãÇþ¿·Ò3RnÞQ(Õx¤ ±šµj†EF„cU ‚ ¾¥ÿÒog¦eèyžW²Ø… e åù´ ÃdD„‡am”ôÿEÄ_ð ÿ_ÜHÏæ8®aýFx ¤Ì9sáü[Ù¨ÿJŠÕj=rä&$$`… âkètºsçÎ@+_ÞÏ"ýYŽÑjðD"Hy¢Õp¬롤=z¤uëVÉÉÉ©©8o<‚ ¾ˆÉdJNNNNNöñý,züŽCï)Ÿ ã'2ñ–¡C‡b% ⛄‡‡ûÅ= ‚ ‚ Hããþ¿8ÿ3‚ ‚ HÕ¢ö¿°°°ŒŒ ÷1"7 çHl*HéAÿ_AüõÿõP"˜Íf¥Ré,õ°Á[W‚ Rñ”¬ÿ7##Ã¥iG~ÒËØÇØâíӸυø&“éüùóž987Œ*Ø$<©+Ä ¸þ/‚ H™P6þöf—aùqn ; EþŒJ¥jÒ¤‰R©4›ÍÞ5›ªÓ$lu…ÍA¹ÇúÒ¬ð!›ÌE=§íãQÞUb, qk–±5ûÆS5›D±u… ‚T|Ðÿ×^ïycÿs)½ë°ÃžßJ€ÉdºpáBÓ¦MËdk•»I”m]!‚ ˆwÒh@,Ùš¿Ýv%’’xü¯û«`“pèÿ¥äKÒB¯e”–ø¬" ÿ/‚ þ‚ïøÿÒì#½œÿ¯(G¯7hôk,K±âÏÙlìþ¤WÖ&áI]!‚ H¹âýüÏcûdŸÍol¹ÐùïñÚ§Õ¹ÙTú&þ¿¥ýAÊ„Œÿs~»tïp9ºß“0⧸éÿ-öüºoT•¸®°Ù ‚ þ¡ÿ¤(!Åöi¢•×óºBAü\ÿAœ {¨Aõ‚ H1´k×£ñ ¦N›:zôÛ¨ÿAAŸ ˜ñ¡¡¡XGRæÜ¾“†•à²ÿ/šAÊWÿíØ±ëAÊœšQX ^säÈ‘ÀÀÀ„„¬ A| NwîÜ9håûú¯È·éöíZà¹D2çêµX ^“œœŽúAÄd2%''ÀãÍéбùÿÚô-Õü/“6÷éý0žlA*’¡C‡b% ⛄‡‡Ë÷¨Ó§Nùò~2nb1£lJ$þÔšlU_;õ¾±?¶KÌ>àðAAÿ_Aü_òÿuñXAÿ_AAªEé?µ&ÄhÈÆê®R¨5!Ζ69Ò!Þ!ÆeXا,j;ÎÙrZ£ý\ÿA¤LÀõßòUüjÌö`ŸÀ!àá6ÝdwSÑo#‚ ê?)Gd½åaJÏx.àPê!‚ ?¬ÿ[òáæ¶Ž6‡î¶I›m¤‚qÙ¹Œ ‚ H!½G½µÿeS‘=‚qR䞀¦¾JO»ví8€õ€ ˆï3uÚTxydŽoîS”0ÌÿZàp«ªŒçÖ¸2´Ûù Ðùù_Aò{è~²p€Ï¤° û³ÿçvNéœÌ‹ÁC ¾“ø)¸þ/‚ HiD`!ýWÞ ´UV:7—Á9Ò}Œ'a7¿bƒDAª¸ä\þˆ}PR— -âµ ðêCñs|Çÿ×å˜>Εöç‚Tô«*@A¤Üž5¸þ‚ ~ ®ÿ‹ ˆ¿àKëÿºàžé?œ#AAäžpÏÖÿð|ŽÀç_ùÏ?‡m_ a <Ï/_þ]ãF ñ\"H¥ýAü[ÿmLÚì¡L¹š2tèc‰‰‰Çñ<¯P(xž——/_ž1cÆ£Ù±}KttžNAAßÕ%Z#$""¢N:J¥RQ€f& @óÒ‹Ï 0xÛ¶MÁÁÁ|8‚ ‚ ÎøÃú¿>˲ ;lBçùÔÔÔ«))*•jĈW+^üaûFŠçÈ‘#çÎÃz@ÄÑétGŽ9räHZZš/ï'çµ)÷öÚ°}Ÿ>}†(Šì… çÝ5çE&ìœs¤œÞïœÒ~›HÅÔ9‚4o~ßĉ““!<<Œ¢ÄC X†uŽ *•jèСX‚ø,ááá~q›òƒþ_BˆƒýOîÿåyÞ–†RJdà"ðd…1ÏE<ë)sdÿ_A¤ôú¯¢×{SkBì?¶xyF@—órgïüá þdýÇ0Œ×{R†)¬sAñIîê½{Ðÿ[”™Gövö –íö&@ñ’$‘š\5+é>#Xç‚ âŒ?úÿúÜ*Àãÿ\ìqqý¿nԆ˟ŠJ権=¬s·/aPØúîüAA2|âØ)+¿xÐðS§M_óÿ¥öúÏç!„ØìE¥ñ¤ÿ×;‚ýO.ÕFQ¥IÅ×9‚ ‚ ¥„+V!ú{÷îÍÍÍu“àðáä,Üí»QT UºÎiá~`çx¤0¸þ/‚ ˆ—Ïê‰þó%ºvërö̹ ç‹™îÿþŽŠúÉsUš¯¬À:GAŸÅôß„ñãðôÝW‹Aô…ÝîÖ&nû?WJúRÉ*‡W±‚(°xf½ýAJ' é¿Ê†(I‚(•$þ€Ë=y°}]ŸÚI¤2UŽ(<¡`7ïOQà88‚ HùÁ«ýò+Š‚ ÀÍ×!rØkzwJ€¤=çzwJHÚsNŽqø*§”¿Ú'°A"m)K¹“>H¹Ö›?7NRÄ…V…z€V‚vVxÅ&@ñq|Éÿ×Ep%µÿè¿ÌŒ; 4Òê‘®ÿÜqÚ&Vl›êÝ)áϧA°%°E:„ÝGÊÒí¤RÞõæÏ“ÁÛ"‚ ro©œúOEQ ¾^PK×{(g_³íßG{4³mjͶmñöÛwNPl¤Ë¯•C…W@½ùcãÄûN±È @ÛþÚâÑÿAÁ/ý}ûµÂÀn`ìló EIöùRJÿûì¶°}ä‡[¸Oà>Òå×J@ÅÔ›Êbœ–²dÐ>Òb±ìܹ”JeDD0 ‡Õ… Ƚ%77÷Î;`6›ÓÓÓ ‹/ï°Ÿé¿¢†É²Ïælëÿ=´'!¥íZµÏn ÛOôm³rÃ?¶°s—¹À©Ã·òÿ«˜zóCý‡Sÿyú²ç,;Úµ+<ýô3AAAÍ›7…BñÜsÏau!ro¹~ýúž={d!xâÄ Ô÷ꛯÿ7m¹ú—ÿ•‰þ{z@÷ŠÄ!Á}Ûü¸v?ê¿ò®7lœx£ôPüAVÀ~ø«AŸ¢Q£F5²ùñGŸÞáÊ>ÿ !@Hifùß{dò¿?ö<7¨“mS¶@Q ìãsÓœ&•lþ— «7lœx£D©ôøÁú¿•PÿŒÿ‘Rz%ùB±YjFŰœëÚøfÕN(v&ÿýfÕNûQh¶öñòW°¯fÿ«ó*ßø¿ ¨7?mœx[DAPÿ•õ!±Äjµ•Q:r`wõˆš›Ö­I a! ‚MíÉáLJ¿¦Öhœ·öʰ¶ð—?mó¼ó±òõç–¬·"'c±ZY¦ªO-÷êÚÂrÀ¾Ÿ×e@ÿ_Aüðÿ­\ÍÖ®”–©Ô(†tïÙ3>>¶^ƒDHKKU)U!ÕBàüÙÓ ËéíÃάڰϋ}ð.WeëÍ%’DuclÍ J@÷18ç3‚ HCô_å€ê†WÓräqbÁáµngê€UY)Èáj±rÀ!Œ å ËØšAê„^½fÀÚð\ÿA¤L ç"žÚü†ÆuÃ× sù«mŽ@¹÷×`QË¿Aå^þ A©Ø'ŽÓª¢T>èˆ @‡¸úŠ ‚ø>¾þo•[ŠtcÒfl”‚ ‚TeªœþCûŸïà°š‚K»víqð‚ ~ÁÔiSG~õŸ¯€ö?ß<Aäžé?Z•FU˜ýÏÞ¸U5 ]jMˆü)ªBbì#ˆKdÿ_AÄ ¨Ýíå…m[çÕK«ˆø3²å\n*Ä91‚ ‚ åW”0„JjôÜþ÷øOoÞ¼¥Ødf³û1]Êß Îèw/aPôô/8 AÄßñ©õ»y«œýoø³/BÐù¯=ƒH»™"-œg ¯þcÕ¡ƒ{äðñc‡V¯^åFÊTM㟌s—nQ"Çcÿ/‚ ‚T D°šs²Så/š€ÐcG÷5nÒRŒ§.¤…‡hâcc°ŽJ#€ª¬´?j[Ø¡B\ÖL©®ËWS2sŒ ëÖPªO<Òü¾v}–í׳çÎ׫[çèñ‹÷j÷ºvéà›õÖµÛøþ/‚ þ!°€Ñ;v{Òò¾z—.]NH¨o‹ ‰ÂñH¹ Á2Iƒ ‚ HYQ ×ÿuOEúÿÊv¬ªitãöá\!ön8˜q®ÿ‹ R lwOŠö¿r@.ÃUJÚŸË qˆ±¥G÷$''§¦¦b= ⃘L¦ääääädßO®8…XÙÀõ?Ÿ|\ÿ×s¶nÝÙ¿¬ A|ììì­[·À€þºuïâ³ëÿrUíÄlLÚŒAüš‘#Gb% â›DFFÊ÷¨Ô7|y?qý_Aü\ÿA×ÿõ-ÐÿAA*Pµ¡ýAüôÿEñ»›'ÚÿAAª\± ±’ö?ß¡*/Žw÷B£…àê+‚ âoøÐú¿Ô…´CûrÏÀÙþA䞀þ¿å…ýšfUs}3µ&DþU!1ö‰Ä%èÿ‹ ˆ¿àþ¿´êt8¡ý¯ÂÄŸmI¢‚s“AA²æ®Þ«ró?{nÿ{ü‰§7oÞRl2³Ùì²Ó¶ÊmÕåæõ!c§0âôÿE)'a…¥W6¦MŸV@ç¿ö 8 ífŠüY´pž-¼úU‡î‘ÃÇZ½z6#—8w饆åø*Öÿë|‰Ñ–xÔ8‚ H9=qªžýoòã¡À èüמÇj þ”-Ü­[[8..6..[–KñgÓyöCÝH@猂 â§øÿ¯+¸ww@˜ü¯€‚ÕjŠ®ŸqûÎI"„ w‚”„R&3+++óN­Zq‚Õ„5â ²ÿ/‚ â¥ü+ð.Ôÿ+&¥J[¿^£›i×®_¿*Š”0„! !@† a‘#€–0€a`€P’w&„ÉP @€R @)¥òW[hþ@ä(b—,ÿ7J $Ë? ”%”Rù DÎ@€ä”7O)%@©.HV …í·‰³KT¨"@)!$¿é¹Éÿ ·6»´àWBä ‘›‘³S Ä!ØrË%æo‰R aÀ–XÞ¡  ù¥äï‘üÚD(ù*’¨D$ ”Šr J%J%"IùMK’D*Q (HT¢ðš:õ0 ˜Í&W×(‚ ‚”©î+€sH`6êx^U»vÙøGá®>‚ü¬¶8 ®åÒݯö¿Øžßö*1ÿy Ķÿ§àÿ‚ßmÇ­»owó’BpþJ\mÓ.+R!Í’Rûfcÿ•:·@Û´ðïòÏÔa£Û)´Í»é<Ú¦mß oî¶çÂ폑ÛZœc›¤T¬&³ÉàúREAüòÿu5 ³ÿµZV«Ïœ¿`¶X” …Ë‚ ‚ ˆ38ÂÏÏÅŸÙ|ùòe`ά¤òëÿ"â/øàú¿öàú¿þR©lذ©B¡Éâ0›,X?‚ ‚¯ÿ”*­ ·Ò® :«U$,Ãä“gy´|Á°y†CX`B2À†PBJ¡À2ùþ üaòòÀ+Jåñö´°GAˆRšïRàêaç¾@)ÿ»yå?þŸÎ…ÿÇݰ]Øû ûGÅB@öÕ(Îÿlƒ3í>î&#öºëáA‰mxhÁ&ì¼Cìý:ÙEþ—¡@B ü?ä% {1 ˆ’­qJ’Xà¸$Ñü&)Q T”¿œX¢¢(1„H’„ ÔFFF±,k6é°%x®ÿ‹ Röúã•¢(]ºt.HSW¥,pâEÿ¤Ü¨šþ”šM†ŒÌÛÉÉçêÔIä8¥ ˜±- ‚ ÷@ÿñ¼úÊåsAAÚèè:‚Õh6çRIÂ:B2‡†åø¨¨Ø›7S®_¿_õ‚ HeÂÇ×ÿ-äÿÁ0\Nnnõˆ(‹EoµšPü!U]¤Ù<)ÛŒ”J‚Õl1ë#"¢ty¹ ƒ#qAŠƒsž†aXÁ‚« UY÷1”ÂíŒ\Τ7”JeVªå8†R© 3Š¢À)4%—UšvíÚ8pëAßgê´©àsëÿR{ý‡ ˆýûg4Y/&߸pábvv¶Á`P©T!!!uêÔ©_·¶6P!IbÙfDA †+B2 †×¨oŸèέ åºOá5êËEØe»Y71H•¬Å*8yáСCV«56¦vݺñ™™Y×®]»zõª^ß¶å} 5jÖÙ˜çuF—¯e¸HQ ÿ/‚ HÉ)bý7Oî¥ö:©ÂdŠ3¤¢õ«¸v%uïÞ½1µkõïß×l6¯^½vÈA ¥b×ν m˜PØbù IDAT@r“ñþû;Ô¨!Çëõú={ö»Éˆ ‚ &ï>¶J³!Ù.h³†×¨/œ8X‹Mé°e—YŠŠt£&6h³2ºÜ—aû”¶¯žïâˆ5™Åÿþ;£Ñ¨ èg0|Øä)öŒÑ`ìܹc­ZÑ/^Ôâ0ã‹]ÆÎ;vêÜeãÆM¢(æææ>þø°ã'Ž•A©¬L™ìõùúû_2µ¾ýÃ>2:º~ýº3gÎÄ)Q®B[[[ì+wÝý‰ï?ÿk wÝÕ[¨àøßìOeiöPÑdÇÌÎjßÌê–œ¨‹vpÒ}¸ƒÓèí¬lOñ ÓµH)•H$“ÉäœÙ³ÿñŸmÿþ—^ú×€ßÿÔ“O\ýG×ìÜù 7¼»¡¡!‹+å)T0N¿ûÝ×=òHß'>ñÉ•+W<÷ÿþ?n—«µ5· JÅø_NaËñ¿¥ç¿ò&žÙ¥÷–Ž/2xò@ffÎîšoÉÜ—´3…6—½*8û’ÖÍÍÍ@  Ý|óM?üáz{ïþÚW»óÎO\yåúnx÷èhÄZ çš7ÙçÎ=‰<û?ÿ~åÊGŽùÙ‹?¿á†ë D©LÓ·Û¨v“J¥Âá°ˆˆ´Ûy?«sþ—)¦¼E²3\I­’SÛ™ñ›#üÕT¶H§¼^ÿÒ¥KÞ\¼¸ûé§¿qÇw¾ó]ׯ_¿nûö§Ü.×áCGº»»~Ÿi† \°`Þ_|ôc^ŸïÙ¿ÿö/þÿ—6þO=þØWÚÛ;òD©^yåWÒ×·­««ëöÛo§BØÍ‰'ž{î9Ùdçý4D ô —IñÖµé4ΞҋœÆ¿ÝU8‹Ö¦Ûe^²fM8>|èhCƒ{ûö§>ûÙÏ|ýë_s»\'O†Â#‹/npë"޼¹¡gƒUäÚkßõ•G¿<22Z¨ ¦fÓ¦M„?öÔÕÕµiÓ¦M›lþ²Ž4/[û_¡îÑÌüÌÌ"ý¶Ùa+ïJJ]ÏÔv;{f‘%­‡ÓߨG,îèœõþ÷¿ÿùçŸ?öÖñ%K½ç†‚áco‡GÞóž÷t´·$âgмâŠõCCÃ>¯7‹·´¶)Ã}Ýu×*¨I6¿þ¯»X<ÌDŸò¶æÌì•ÿ††‚¿Ýõ»9sfu_t!Õ”Q(þí®ß­_wY[°4Œÿ€™Í^´è¢U«ÖPG@ÙíÛ÷êÁCGÖ¯»œªØ(ÿ¹\ÆÜ9³¨ `&Ì3+ QP«qýß<½)Í>Ÿ×ëåýf‚×ëmnn®ëðõ}G#Ã.V|Àädòžæüªþ2‘.{ºÈb®ÿ À)lyýßsÜ’ÿ:Súüœ ì?ÂÆ_óM ?¬ëŒ˜þ7Nö÷ çÿ`Óðçõµeº‰-Öø_À4¹‹Eª˜±ßb’õ¯l|K ÿú2íã9âv»ý~¿ˆ(¥8€ª‹Çã‘HDDR©ÔÈȈˆˆ,²Û7θüÇW ›)Òù{÷Ýw·´´\~ùå"ÒÔÔ´qãFª @uíÛ·ïŸþéŸD$ îÞ½[DV¬8ÒÛûWöÿ«sò8ÇO~ò*€­\~ùåÖÒŒ·l±ó"]PÀŒþÓã:€õ¸ù8×ÿà6ºþo¾¯ÆÔúTš5¶73mMdóÈ»€pý_ ÿpt,>‡“ÿùÀ1œzý_Ôsyñƒñ¿œÂFãóqç †œ‚¨Ì°Bg|á_ ìß8çÐþÀ1¸þ/”ù ¾0þ Ìl>þ—ö?€úBþàŒÿà¶ÿ‰DÉõ"‰îÚµ'’ÿ8ã`:|>ïW¬õz½ä?€z¡µÆÿ”=ÇÿZý¿³."ÿp˜¡¡!·Û¨ v“J¥Âá°ˆÄb1‘V»í^¦ÿ—üÀ1Ö¬¹äÃþð7¿)]]]·ß~;ÀnNœ8ñÜsωȶG¶‰ÈÇïÚméÿà0ÍÍÍ›6m¢ØVWW—õgjóf;î^¦ÿ—ñƒñ¿0Œÿ¨;^¯Gèÿ(;®ÿ ò”Žëÿp Û^ÿ—üPȃñ¿@þ@Éÿ PfŒÿùJÇø_NÁø_ÿ tŒÿòJÆø_€2cü/ÈP:Æÿp ›ÿ¥ÿ€c˜¦944$"n·;P!ì&•J…Ãai·ó~’ÿ8Æ+¯üª£Cúú¶uuuÝ~ûíT»9qâÄsÏ='""›ÈP6›6m¢ØSWW—õ7jóf[ï'ù Ìÿ ò”Žñ¿œ‚ëÿ€ü¥ãú¿@þ@Éÿ PfŒÿùJÇø_NÁø_ÿ tŒÿòJÆø_€2cü/ÈP:Æÿp Æÿ€ü¥cü/ÿP2Æÿv‘H$©‡ mÞ¼¹»»»··—ª`7;vì‘k®é¿å–›m;þ—üØ%ü =¶|ùjmƨâúúú¨öÔÝÝmýzpË;ï'ý¿€-466._±¦©©‰ª(‚ñ¿œÂæãiÿì" ê4ý¿€Gû` Vÿ¯2¯a¨p8ìn (§T2Õìó555Q€êç¿t:ÕÒÒ2øöÉÎŽ¶T*îñxè¢fŽËÝpjðDÀ0Ó)jc2¬ñ¿›6mîîîîíí¥BØÍÀÀÀŽ;DD¤Ï1ù/™ˆ,ìê>tà5\†hÍ!€ÀLPZ\ÃáááÓ/^‘LF©‘Éëëë£ØSww·õ7jÛ6[ïçyù/•Œy¼‹—¬;}î;<§Ô¹çÏ~½Ÿ·ªÌ7¿d­2{U™å\88q6wËyŸ03õ¤7}îC¨³?xç­9ßV2ŸêsÓ™:ë·€>ïy+¥æ†Øì;ÉʉRäsnêt*‹ÇFøª™ÿDt2I&"TS$‰ÆÆÆ¼@aü/§°ùø_®ÿáøð7pô˜2<ã'¨@þ«AËW¬ijj?Aå öpý_(‹Üþ_·%•JŸ€#l}hëÆŸ²íîåùÖ1 W2Éù_PÏÁÏÐZNF¢MM-_G›Ïí6´6ËX0J646Sá€êç? ž.w,–>täŽD"§­­íâ‹/^rñsƒY ]|Ê1yÖø_š ùoÎüÙßþÃþ™Û¡9óWXë?QÞõ™ƒú †+™T{^=ô«_ý*™L^xá‹/:sfèØ±cG}ÇåkWx=yó¦\›æ¿™Î|¶Ý4êËÝtä艗^zé ºþøoŒÇãßþú÷755ýë¿þ¢¿¿¿££cŲ…¢E ^ýG=óæÎ±æŒŒ¾ôR‘‚€šdóñ¿Ó:ÿóœù+¬[öœñ÷™r¿†œ"Ù÷yW˜³Úœù…ÒdÎgZó¾–¼Óã_ÔøâpÃpÅâæ¾×^óù¼ï{߉D"·Þzû–-[o»ý#‘HäZ«káÁƒ#Ñ´:ÿ AÙÿøêï¼ö'?Ù™N§ƒÁЭ·Þ¾{÷®BpXþ³’“uË›¨²—±²QÎÂã×`´æH¾–¿"+™Nö*ôZ&³dæ¡ÐTépÊp%“úäÉ“—¬Y …o»í¿y}¾þ—¡µþðûèÈÈèúõWœ9s&žH+ÃU¨`[[ëã=z×Ý÷|ÿû?°Öp×]½… ¢TŒÿà6ÿ;Ùü7ùö­ì ThºÐœ’V>ýµMi¯ÖòŸ2Éd2™œ=gÖ?~ÿù×öïÿÚW üO>ùøo»kçÎZ[Zb±¸RF¡‚étúÝï¾î‘m_úÄ=#Ñè·¾¹Ýív*€­ó_v+WÍh·i¡•gšîrš*'³'™¶Ìñ}ÐÄAÇÓº¹ÙB¡ðÍ|Ó%—\Ò{ç'N¾ýöÇï¸ëÊ+×ßpûGG#ÍÍÍ€?wGVÁt: …Ÿ}ö­\¹âÈ‘#?ûÙÏÓétÁ‚(×ÿ€²¨æù_râWÙ×_R ›üÂ9½Û„¿š‘N'½žÀÒ¥KÞ\|q÷Ó;¾~ÇÇïz×»Þ½~ýºíßxÒív>|¤»»;ào6SÃ… .˜?÷/þâ/½>ß³Ï>ó‹_¼´ñÜ÷øc¶·wæ-@Ýå¿ì Xá•XÍxyC[ñ0GÔ«aZ›.Wú’5köîÝ{øðÀ…vmÿÆ“?ùéΛþËn·ëÄÉÁPxäÊwlp»Ò‰TÁ‚Ç6ô¼ããwü•ÛíºöÚw}åÑGÂ#£.wSÞ‚˜šÍ›7www÷ööRìf```ÇŽ"rÍ5ý·Ür³3®ÿ[’ì¡S‹DÙC( ÉpÍ…V2ý×’w…ã—ÌzL:t´x4ÔÑ1÷ýïÿóÏ?ì­ãK_üžnÃÇŽŒ¼ç=ïéhÄ£§Š¼âŠõCCAŸÏ‹Å[ZÚ•ÑpÝu×*ˆ)èëë£ØSww·õ7êÁ-[켟*™Œ °ø[æìÞõËU«×&ãaíÀQv4Ρ ?‰ÜM>ÿœH4¾wïÞ$“Idz|ùòeË–yšÜ‘Ñ·Ítª|•Ç×¶ïÕ]—¬½r4<˜™ûúë/îþݫǪU ×^s•=ßk¯ûOýýý|JØŸu°r4‘Ÿýüå*îÉÚ5>|tٲř9í9þúoÙMt„?L_* ý¡ÙßÙ³áëׯ×ZJ†JÄGFÃ'Š à˜rA*Ìñù̇²3ÍT8tÒ0\ ^e¸L3•LD'à¦\“Äõ€üÌh LÇã#•,ùÀ‘jùú¿pòÇàú¿œ¢F®ÿ €Ú0ÁñZët:H$gt'\.—⺞Šbü/ÌxþK¥RiÓ5kv·Çë›ÑˆF£§ßt)·›ñ(UÊ©t:Š.[q…6S±hHDD”ˆž{ilð,ìZöÆþ_·¶úÝ.WfªÛúX­Óð @M²ùøß‚ù/ wξH›©x,<Ó;O'›¼ŽÎ®á3ÇÚÛÛÆh*‹§:guÍñ·ÎèÖÃáàÐéãž&Wvëce¶žwÓUÈé´ †–,Ÿ…M®À~$‘ÎY³ÙÛÒÒârVëã¢ÅkRÉhp踈Ì\ëc“'° kÙ‘C{2­ÛúøM(bÆ®ÿ À¶>´UD>~GÐIùOk3§RImšÚ¬ÄÕ«LÓL¥R±x\kSİZc±ðhxp¦7‹øs²[+¶õñ›¨NþË‚•¹z©ÎÑ—i}|ûäa³"é3š5§Ûj}‘Jn={Ó.§ãŠaü/T"ÿ‰èñù¯­ã‚ ×;|æX‰ÐÌV룙NW&ÿ¥ÓÙ­RÉ­Ÿ¿iò¨vþÓ~h÷Þ³»H©O^Vê~èüÙ(mš•8ú0o+•Ùzº"/T’SÇÿ‰€½½½;ž¼¬PÜñäe½½½º=4f:m¦+Ñþ§Tzò[ÿþ^,²ª[ÞwÝô7 PÅü—'Æ}éáÏìØ±£H™/=ü™’w$_^L§ÍtÑü·lå•o¼öJöÄT`º¤­ß|Ó5yçÿðÿü<]j`%ÿ“¶zõš›nºióféîîîíí¥BØÍÀÀ€•‘¶mëÇÿ=?þåIf…š­Æ¿ݹ%3Í‚=°+V÷ìßÛ¿oÿ²•çML5þ¹JÚz‘~áR»Œón@^~¿¿¯¯z`[ÝÝÝÖŸ©mÛl½ŸÆDñOçõÅ/l.Tä‹_ج§ _d4MÓÔùoû^ý·«{òNLé–žüÖE¤ÐzŠmçæá‡>=½=Éßþ§ t¾¾ï—"*ïÄÿQ`…ÿçÇ¿(¸ª÷ö?`ò¸þ/T$ÿlš;çÔÛEDÏ@SVÅzGón¥ÐÖo¾é3½i€ªå?-º2¿µ _/®"ùH«ªm]¨5¿þ¯]½¾–ŠOªZ[Ï»i€ªå?­+4F!oÊôx<©T%â‘»ÁS­­çÝ4€¼6lèéïï§ØßÖ‡¶Šƒ¯ÿ+ºRíZD<ÞÖH$*"±X\D+Qé”Ym74(‹Å+¿õìM»ÝnþÁÔ¼ÆÆ—ËŸ€ó_…$‘ÆÆÈêU«¼Íí"rñÒ+DdîÂ¥•܇þã-ÖDå·žÙ4j^08tæÔ1¯§¸?Œÿ€šÊ"2:rJD‰ˆˆQÜs_‹÷âõµ]ؽæà¿mmõ»]D@@}ç?K2á]A K&"­í ;:»†Ïkoo¯ÛzðúÚ¬‰hdxj €Ù|ü¯Á;TX42Ô9kv0J§Íú¬¯¯-¶n™œWÒòà$ZK*•ŠÅãZ›ÔFˆœ¿Íëÿp ›_ÿ·´þßÓg†öìÙ ˆˆu6÷Ü×É}ÀX»vuG;mQÇ+!ÿ »kÏÜÙÝÝPq¨7áPø·¿ýÝúu—µµµRcugZã{z®jmm½ì²ËDÄãñ<øàƒT€êêïïþùçE$ îÞ½[DD^®‘üwðБEÝ®^})o3êÓÞ½{X¿n-UQ]/¿ü2•ÀVzzzzzz²çØü4¯%ÿçr©¹sfñ£nÍ3˘è_ÌÁCGø›/¼º÷5ªkúrÿÀA¶|nËO½éþ{ ƒKýæÊ>«_vÎËnðã´`Óü'"W÷0ühg(<ºté’;ïºgåŠEK/Ù¾ãëCáþè…?yß{©Ÿ¼°ÈœB=¿\ÿÊ‚þ_`ÚQ&íïÿˆÜzëmË–,ŒŽžY¹²ûƒö!ù·—_‰ÇTÀVÊvýßÖö…Á¡ãÅçLþYÀA|¾æ|äÏEë×­‰E†E$ ½ó®lll4 £ÉÓDƒÔ›_ÿ×]ÆueGºÖö…Å&ü¡fhm®_·FDbçõ`½cý%Z›ñX˜*ØJ…ú[ÛZ·ì9Ù9Ï΋†bÑи™AÂ_y1þ€SØ|üo9ó_pèx&Ìe7ïY­[Þ—Y€à¤üW$Ns³ãÓä®Ìfhب£üWÒ¸ÀA‰äÛoŸ:2ðæé3Áh,¦´¨¬++¥¼Þ¦ŽŽ¶ŽöÖEÝz*€=uww[£ܲÅÎû9­üþ=ï40¾Í/oû¦ñ¿œÂæãéÿ¦kÇÿ`§o.ª˜¦âý¿öWFŒÿ€² ý(Cþ£ÿ@þêÈ$Çÿê‡Í¯ÿË70ãéJØ íÕ§µN¥ÒÉd²øb n·‹ƒÉlˆãÿ*†ñ¿œ‚ñ¿(&™LE£ñ–¶Ù³Z‹/‡†Oy½M ¼kNÊ´ÿÈ8'•JË­0µÇ‹/ìóù››G^ookq»9¥ˆ½èÂhÿ+#kü/'ÒòŸƒ…ÂáÖ¶Ù¦6c±ø$ÂbÊãñ´´Î …Ow´·Q{ö¡”"ÿ¤<=S­í ÍÿT¡™õ&N‡ƒ³’ÉÔ$‹$“ÉŽŽYÁá`:¦”©¨7[>·å‰'³íîÍÓ9º§IDATxû_pè8‚¼LSGc±d2išæ$¯ «µN&“ÑXÌ45•°UÂ+ÒþÇñ»™ño¦ì¦¾Öö…y[ ó΄þßÊØ°¡‡ƒÿ8ÂÖ‡¶nÜø)Ûî^åŽÿkm_hµæ$ÂLaöt¦‡2.† ãü/g)[þ›|^pè8­}¨±üGÿoe0þì•ÿršî&ŸðÈ‚¨íüGû fó_¹‚c²RÂ$—ä#kÏüWäYÚÿ qýß\…üh„ÓC<ý¿G¨\û_æ°¿ìãÿr¦y?àDŒÿ¨˜Õ«×ÜtÓM›7Kwwwoo/ÀnvìØ!"Û¶õIÍ_ÿw|tËÌÉ~ªøÌºEÿo ä?Úÿ*Ãï÷÷õõQl«»»Ûú3µm›­÷“o& l!>/*§Œ¬ñ¿òŸƒ¹ ×Ñ£GÜzrÜGq\úÃvá¯È³¦iRE[qSU‹Þ†jnö½ñú¾ÎÎY^¯7•šà’¾n·kddô×÷-¾ø"à Ä^ù¯òÔ!›ÿ%ÿUËåjkkmokù—Þ¹lùêîîEÅ—ýõƒÞØ·pÁ¼¶¶VWÿ%ÿ@þs"ŸÏÛuÁBe¨Ã‡öïÞõJñ…[Z]]ó.\àóy©:»å¿)?‹’lØÐÓßßO=°¿­m•šÿ‹©1 £­µ¥©±qö¬Îh4V|a¯×¼^ç±Ó4‹´ÿ¥Óiª@þÃ9J)ŸÏëõz&l%RJ‘üì‰ñÃõ€üWS)lçèüÇñòPGŠ÷ÿ’ÿ qý_ ÆÑÿ pò0]´ÿŬ =üÀ¶>´uãÆO‘ÿ€šUüø?ÆÿÈ@­)ÞÂGû_qý_ ÿvÉ´ÿ„ñ¿ÕwúÌО=¯†Â#J”RJkÍ}uîEüþµk×t´·•1ÿÑþuˆëÿ¢˜¡áà®Ý{æÍ·dÉRj£ê‚Áà®]{Ö]qi[[ ñ¿g!ÿUÙ¡CK/Y°`¾×ë£6ª.øýþC‡ß\wùÏŽ¸þ/§àú¿(ú¸ÝóæÍõûT…øýyóÌááá’JOxÿ ÿ!'pøiù³¯×”ǭí+ø©³2ÓšÖ•5þwÓ¦ÍÝÝݽ½½T»رc‡ˆˆôÕ~þkm_:>ù…­‰É©Ø¾Ù(…øÚ¢‘á¼ó³æ]•W$ÿÑÿ[v}}}T{êîî¶þFmÛfëý¬tû_&eRàLshø+ôT&𠈨<ÎÿÈQ××ÿmm_hÝ233ÇGÀœgs¦ó®0»TN‘œæ]¿mÃß‚××–­éì™9 Ô§olúÛŸÎîÕZ?õÔöí;¾9Í5«ÂèÿØÍ ¶ÿew¼ZÓÁ¡ãÙíã[ærZs–¿Âñ¥Š7.ߺML&üådÄÌÃìùÙ33«­çVÃ`(tß}›D$OlÜx5óÑGÿÜ–‡Dä÷ßø§¶fŽÿ«Æÿp ›ÿµ×õ?²3Ùø|V(±MgÉ“í M×­Ö–Ö»ï¾[DøÌg¿÷½ï‹Èw¿û¿­ðwï½÷N9ü‰šÌù_¸fÀFföø¿²÷´Ú¼ëö¦¿òè#CCC/¼°óÓ÷?pìØ[_ýÚS7Þxã—é3ÍÄT²ŸR îx<îr¹ µÿÅãqwÃØ³˜&kü/-ª`ëüWÞö¶œ>ߺ}Ïù1õ¨“ßúæöO~rãþý¯ýøÇ?^²xѪU«üñ©…?q¹Œ–€ï׿þÕå—¯khhŸÿ’Éäkûöü^—‹kmê#ÿåneYÛ4 Ö-Ã/?úèÃ_øü©SƒsæÌýÌßü­aL½5Éå2Z[<ÃÁ¡Ÿüø‡©Túün^%" .¿¿9à÷.Úÿ ŽÔËõs"Wf´GæáôCaf…Ùk.iZ vÑÈpö ¥½ž†ûþúÓ/¾øâõ×_ïirMs|†Ïçñz=ÉT[<žPJ™~^%J”×ÛdF"‹'T= ¦òßFf´1át¡õLr‹ö’“ä&Ì1Éå ˆ"¢µÙÞø¯út:©uNÎçñ46»\J)ÃP†1ÖÏ›9à/‘H&1ª½,ÿ À)¸þoåd·ù9e¨/G²W+¦RqÞ@}ª©üçÄÓ»hæSÈ›‚ñ2§+Ïn´fü/ÿjAS“;•LYCGaÉdª±‘Õ\êf礨²Ù³fC§SÉW‰¨:­u*™…NÏ™=‹Ú€<ðÀ¼ÞÖ×× ëçõnß¾ýÍ7߬Ÿ×{Û‡n»÷Þ{l»{´sT™Ïç5 9sf&@;H&“Í>oS“‡ªÔ0ò_õy<^ÇK=…dŽ´d_K…†sö1þRO_ûÚS™éŸþô§öÙUúÔ…§žzÊqeó›ßðz+°ÝgŸ}vdd¤òeó›ßŒŽŽV~»ÿðÿpâĉʗݵk×àààÔÊîܹóàÁƒÎ*ksä?uáøñãŽ+‡y½Øî‰'ÒétåËŽŒŒTe»ƒƒƒ‰©ž‘~:eÃáp2™œZÙ3gÎD£Qg•%ÿÀF8þ€ƒuuuuu-üå/™™³aÃÉ;§¯oâe ÌùR_ßJ‰ˆ¬^ýÃ_üÿJ-•™“9šÈAû–ö‘9ú÷êg?™ü‡ó wíÞ3oî¼%K–RU wíÚ³îŠKÛÚˆ€€šEþ«²C‡–,^²`Á|¯ƒF ÕðO·hÄï÷:üæº+Èö•\cMƒ¾®{á“|9^_[½½ÞÚx‹ëíóì¸-ù¯Úo€Û=oÞ\¿?@UØß˜7ϦÃÑXk&LæuÕØ ŸäË™äi kéõÖLØ­«Ï³?´Œÿ¨zàðÓòg¯º^_ @‡íÔáq5 x½üS­Áü×Ú¾0ç–ýTñ‚“œ9*¶¡)ü°nNÿyQÛ\îÆ]@½…¿âùk,$Y¯”Ô[-eëÿ ωVÖœœù¶bÏ}›L³9\[üx2ܦ6'¹°6Å08ÜeþÁˬɗ\'¯ÝŠ€„¿ª}…ÍôrÚ­‡9 oÙM†…¿XÞ•Œoz_$g[y×︿‰9¿3¿«23ëçgeߨþô7¶?­µ>Ë´~ê©íÛw|sjù/kM0µIþÅóP¾Ë|1UKå¾2-‚ãs[f~pèxpèxfN‘âÙE²' MÚz¡ÝpÖˆ¼¿³gf Öó­`(tß}›D$OlÜx5óÑGÿÜ–‡Dä÷ßøKZaC£·Ùç™DLT"ÒØànhôF#CüÑÁh’¯´Îã`|’i¬–²µÿåÿW$Bå<5ɰ•½Øø"…V2%þó±ÐtÝjmi½ûî»EäÏ|ö{ßû¾ˆ|÷»ÿÛ ÷Þ{o©áOD WêU—(5A RJ›æòå« Wï“üS–¹ñ ˜ å?þ¯ZígöïºEµé¯<úÈÐÐÐ /ìüôý;öÖW¿öTggÇ7ÞøåGúL3Qêê"#§–,»¤¥¥õرñh"mæ9ÐP†¯Å×Þ1kŪË"#ƒ¼(Kø£½€]ò_v¬|Ìéóå}Eþ¨“ßúæöO~rãþý¯ýøÇ?^²xѪU«üñ)„?IÄG•¨yóÍQ*™Ð’'ÿ)1Ü JŒ‘ÐÉD|”·`ú²ª¥ ”÷uåä¼Z:Rj2¯·æ?·Ù¯·Æ>Øõözs~ª9âÃ\…Òg4ŸM¸òÚN‡´ LÈ0äË>úð>êÔàœ9s?ó7kzÊk‹ÇGâƒ#Ôjå¿ZêäuÕöQÅ_oí½ð _o½Åõöz÷ºf$ÿåm´ffOOi%µ N¸ò’¶î¸ŸÅ™"„¿IÐ^OÃ}ýé_|ñúë¯÷4¹´ÖT  ®”'ÿf1ùqy‹_Iñâ4™äN:âgS‘’µÙÞø¯út:©'}?È%Ënu«|ƪîÖ‹f>„Õ‰€©Tœ7@þ›YÕM]¶=½‹Öi>…µô¦Ê¥kAûøO²iêH42{vgñ²ét22Â1€ZÉ>Ÿ×0T8v70ju'•L5û|MMMT ŽòŸˆx<žâý_°9ÆÿÿDDÄeGhôø©# ¼š<þ£GßpüúTGþþ_¥ _³ïµW÷Ìîìôùg'#ÔPMþx<ºoïžî‹.PаMþs¹ŒÖÖÖ@KpçOºfÍê »Y±PDsÏ=÷Ó¸—ƒoìß÷ÚÞ¹sç´¶¶º\ä?€mòŸˆø¼ž®…ó Cí{moÿ¯~IMeÑðÏŸ?wÁüy>/C©6Èétª¥¥eðí“mZÇ[[[guvD£1j ( ¯×ã÷û½^RÊån85x"à˜éTÎ2yϵ@©ò^¼@%“±¡Sca°Á£Œ†C^kmë.C´æK˜ J‹kdddxøôÅ‹Wh3™Jžû•‰DcñX[k[¡s­0¦©‡ƒÃ^×›ÕéÔÞyÑyùOD<Þ–tZŸ8ñV$:j¦µR†2”¡”R†R¢”RJ†QJ‰¡ ¥ ±kk¾ˆˆ:;´X‰QZ´¥´ÖZë±mÖZ´h-¢µVÖSç{zì)«€ÖJÆJk­Dim[‘Ö¢ÆÊYψmíˆ)Z]½µ%™Ò’ù*•ƒD´(ëÓe#§”RJD+kZ”VZ)¥´¥•R"çfŠ(eW§”(k}V)QjìÑÙO¥Ê,¦´ˆ[Èš–³¥Ç6µÖ³ÿ›9¶«Úúp‹˜b*-¦ˆhѦe}DM멵˜Z+ÑiÓÔ¦õ±7uÚ0\Í^ó Ã5þr#±X,Op®uÀt¤’)§)çâyòŸˆjhô64x¬‘‰cqÊ ic)jìñØgŸ’ì'Î>³æ±h˜õ,Y_Ù"ç}eŸ>÷žSêÜóg¿ÞÏ[Uæ›_²V™½ªÌ‚r.œ?Š8›»å¼O˜Î™ÎzR›>÷!ÔÙ¼óÖœo+™Oõ¹éÌ õ[@Ÿ÷üÙ‘ç‡Øì;ÉʉRäsnêt*K&bãþ±0SÚ;/ߺ “‰H2¡vj§Ÿ ÿ€üòj%ÿÍ™¿bÂ93!³•ñ˜ÁüW­àõööO83’ÿÞþÃþBpÎüÖ­PjÿlÞ"ãgfŠçÜO¸<Í„…Ìž·<ûaÉW˜3E¦M.{züã'&œ™=sÖ\hù¼+@&üíܹsÝe‹2sŠÿ(ÒX¼TŠ”¥,@=„¿œ™Æ„éj†ºV­5Óu 0Óá/»ñO¦Ðÿ[ÞhMÐu 0sáoö¼åƒ'^Ï<5ñùÿJmœäÂ4ûT&üåtOªý/;æLY8ólÞ"®'ï "Ë`Âð'"*™Œ (×öèɰ[øË>þ¯½ó"®ÿPãá/çüeÎ4þØ-üåtÓþP/á/‰’ÿj?üYÇÿE"Ñ]»öD£QÃåj¤Öj8üYÇÿù|Þ+®Xë´©H$<öc„G;Y»Ú€Œ0ÊQ5ëúÏØb™l憩_­ÂåÖ(„4€AÀ¸w^‚ÝJkgÙ«î%x®Þ 0¦(’ªÊÑx&‘„d2©H7ìãÀ(¥„J’422\­U(¥F5c-i3Öâ³”âò"ÎøL N»ƒÓÜ`ÓãÔÙ d¦éß?þ M+X—2@ öi(·Îf†û*Iµ‘áÁj¥BÕ/™n$xf&„/—WJ(•$idx¨Z­h?ùŸ®lÔRÙlZ^‘*‰D*B© ¨r\!Õjåà¡ÅáÁ±ñщ‰r"‘H§3]]=K–.Ïd²A[âÌØ9B·H9ÍÁ‹vY˜EÌ•k½Elî2hØ0µ‚q:·û‚ùjFW—77ÊAK°»ZfªPZ­”Ø;2|rlLÂÙîKWd²yÂT.¨”Ø72bÌ$ÓÝUX²ly6“«ÓÓ&žÌäàþâÈàÌLz/5É$TÚNÙ–,uT6E•£Tˆ0¦Zù‚—.ûÓÒhù?~ò­õëϨÉÈܽçµ7_òAóÄ­+V̳¬¨ ?vô¥—^ؽ{÷ððÈØØ¸ªª”ÒT*ÙÕ•_½zàôÓ×-^²Œ1•Ð)q“ù¸Ê®Á)á£2Œ1>ï°Eõ'†¹°,l…˜8:ê•ÔA‹ÉŒšêÿ§–ºÞm™­Œ1Wåúwôº“`æå>Ä»!Ì!” G¾¶ãÅm–CxÍ™K—®`¶BL©pìèë;vØd²né’éL˜U&ÇŽ¼´c»˜,Y²œ1¥ ~ e;cñ’¥LUíNS#6§aÕÀù/¾xøÕWÇ!ŒÆ!DeL ”1vóÍRºô“ßb+Áâ¾}»ŸîÙ;^ž7oÞ›/¹´á¢BOoi´xäµ×vìxá7¿ùƒçœsî©§žèe=ls8HïpBÌ)i>b.Õ[‹n)MoꩃÉ€iüŽÌ õ7GDð¼gÏËÏýá™;^ž7oþ›ßò' û …R©øú믽´cûÔ.~Ú:«“E©°wïΩLæ½ù-²°QO¡·T*yýµ—^4drê:«ë8¥Â¾½»Ÿ{n:“þþE…žBi´tä5cMÎ=͵˜¸~.g¡l…R©täõ™ÊvÊ»SÅXÄF'V­Z·kWäµ×¦2*RBˆÂˆH‰@)!ìç??ôÔïÔeËθæls­zæé§víÚó§oû³7œw¾©·§··§wݺ³öìÝýã‡~ IJ>ßÓß¿ˆ15 sØù™õ ×3…ØŸ_›)«W5n£˜Mj¯Ë+ÁŒÌQG™¹‹Ù¦ âùœ»ʼå¨þ6˜Àþ|Á®$xpðÄÓ¿ûí®]{þôOÿì¼óÞ¨m{íõ×-\X(ô®[wÖÞ½»úуÚ^´hqcΔ Cƒ'õLfè@¡Ð[(¬[wæÞ={zHˤ°há"uF&“5|úé™™0ML ½=…uëÎÜ»w÷CSb²°‘©mˆ¡g¡lÌxD{öî™T¶\O¿Me›’V®X®|ýõ¨–ŒR¢¨”¢ª¤RQ¿zÛ«ÑèÊÏßri2)ZUU–Õ»v¼ôÒÎw½ë]çœûUUÿ­Z¹êýøà¾½ûöìÞY.OXµT"•O¤ò.‡„ë±Odl’Ä™†3:K,žöV“X<‹§cÆ%£±¤‹ Yã³"ææy³OÎf Ìnk|¾ÅÜ>•³xìÆõ|Îýã;§cpÞyz3sì•Óuäìš ^ãø=§’$¿üò‹/½´ó]ï~÷¹çž§ªÊ/~ñË‹Þtñi§Ÿñ§~ÙöíÛUUY¹rÕ>ð·ûöîÛ³û剉 “Ldùå“™XéÀÊU+õLÆË&™È²²s玙( ™¬\õþÉLvN˜e!\¯lû÷ïWUÙX“ýû÷¯Z¹rRÙöì,WÊ6ÙEl¬µåËE£•#GsÚ)›¨ÑTLóÓ“þuøøñþ?¾8ýÖ·öZ_.èðàÉ?üþ§žrʪU§¨ÖΑBOïúõë·¿°cÙ²•Ë–;x—\ZÄ.üñDÖ“cÒ"Ž%Ò®QóœkÕ MˆkÕ Í(ÖôWª•½Å3­J+c„¦¨De*S‰Ê˜¢ÆTÕº/R³ÿÕgMël׆ç|Ôª!xæS…UësÑ̬-µ4À-rdt:9µ˜#]ß!©¥ç´ñnƒ1cÑV¦Ñ®¦Ö'ajcŒûîQ3?ýá÷8õÔS¦†ð‘£GÎ=÷œÏÝòÙ­÷~ûÓŸþì~ô !¤§Ð»~ý›´!¼bå*£ÊS* ×2Yµjµô z&ËW¬d“*ZW“ÉL¦Jxò¿ÿûMë×Ĥ ‹Éò1 dŽp²8xðœs/¸åæO_ûÉk´_Ûtû¦Í·ïxá¹ æO+Û²eV•‰Ø¶lYw,¶âøñ˜6,“ÑɽŽaÿëçãñî›oEÑR³(ŠÅâ±cGß~ÙeŠªØØo¼pûöm#ÅârJí¯Ó‰TžR™(%R9mMe¢”Hæ!•r‰¢/VRBH"™Õ3©–G !qãW+£ÆƒÒìßje¬Î.žÎ­2›úK¤k•q¦DyRdÇÝz'Œñ´MQ+ëk¦–„©VÑ—§RÖ¯!„ÈRe†tR"PA(¡"œèC)!„zpÄúxcŒÚßxò„ Ø´.§7ºôüškÇÕqx4¨«jPZ,Ž;vô²¿ø yj_ñÞ¿¾â½MÿЇ>|üÄñB¡05„Ÿ) ˆáᥴX9vìèe—ý…¦‡½²lÙRcyú=Jé”fM^JÅâ±cG/ÓÄdª‚Ÿýìçîûö¿\ûÉk>ó™n:SL‚šV§l‹/üìgoúâ—¾ª2öÉ6oþú¦Í_ÿò—>ß7¯WQ• .ЕM°²/#6å’ÅÉhtåР(U›²BÿíÁôùçGÿâ²ê²ee¡T(•Šñx<ŸïRgªðøøx:=ãæ]„D".I5Y–ÁÙG›Håt!Ö—Éœ&ÄškB¬/B*åQMŽãɬ&ĺ"ë¢<õs²|]‚²OdtQÖÜz‚Ze|Z‹é¤›"OÏbÞá0Sp“ÑXÒÂ:fS)'Å7KHµŠö“P&t…ñ%¶IsƒñtFÊ?ˆ~¾F›'ÂŽû3w;1eqÏØÐl僧ÅâäfªbÜöøã¿ºë®{Î;ï ÝÝ“£[õ!,‰‚`ÌEË$×Õ¥ªÊ+¯>ÿü7}æ3ÿ¼qãǵí[¶Ü±eË7žþé æ "­Óƒ”Oe¢(úŽ÷}û_Þö¶?ùÆßJgÒ¯ù¸©˜0Ó»ˆºƒu7;ͨl2!äšO|Œ1õË_¾õ‰'~ýÄ¿ùÒoùÈG>4Õ,SG¤È5/0Òx÷®ËߢEäCJe2¤R•R‰É#¯ÕÄ÷¼'šN«ù\ÅàA7ÑLUUkµZ6›Á8Wc||â²ÿqù£?ûq"1ÃRëêÊ˲*ÉR<s´+Eý@*%“å’ßq7ó¦Î(Äš›:‘µ:Oj±¥›ØäàŒN‰X<¥¯!.Ü‘⫯ŒNÛËF㊺9ã¬|EntÞS?âÚ¼Wj˜¯’½¿Ä\–†þ’Æ Š29„A¨ó$|éK_Éår7\ÿ)ãÐîêê’eU’$1×sQ6¥”©ê’Å‹>óé¿ò•Ûc×\sõ–Û¿±eË7¾øÅ[æÏëÓ²Ò3‰ÇczΊ.&tRL¶Üþ-·ã‹_¸å£ù»oݵu2ÃO\m“Ú”˜(Á3•ê‡͆ýú‰ß<ñÄoÞöÖK?òáÍl–üäÅ¢¦—„ˆéPÓ’Š"ùÔuU‰ÉRU!„çå•qqPο&Æ2”¤„™¬™Œ ‚Íæ$©V­T¢Ñ¨¾-™ˆÿì?~‹FõkÚ”¾Ë©T*OMÿö2kãñåmyÛ²Mmdã_<‘ÖLc£k‚ßSlDÓb‡§(µ²fOù(ÊZÎF—EÝ4uÒ/Ìc^ýÂlÊ·Á¬ÔU÷•2fbUS7æ6Ÿ_˜¹ô ót ¿°ÝÔ‹ß3¹u^^Vï6ìg2©„Z]ëXÃŒljsE4=j&ŠÆ!<­‰ƒƒCÇŸ¸ç®;W¯0ŽâZMš†G™løø?0ƾòÕ¯=ñëßüú×O~áó7øïþVUÔ†Lt`¢.&ÕJ4{åðá-·ã Ÿ›ÜqÃÕÿÀTöÕ[7½ç]—ÏŸ?ßJL‘à™ÊVÕ•íö;¾ùįŸ|ëŸ\ú‹_þç7¿yÏ'6|¬QÙ´YÌïáG¬äF[÷òÉ—Fj¥ÅÉE‹2…¾¼òÊè«Û‡^ÎÅ2 úÖ«ŒTk$·™Wƺ{zT•ŽOŒå²¹²‹Öù($I¢‚˜Íæ*¨Îõæ{ ™£§x¦F§ !Õʸ©äê6r­:Nì§M™É<ÖÑklí»0>K"Ä¥šGxÒ%L]ú…­g­ïð ÿ-1Ÿ¯ò{›žÆköòÌOž2È]Ìz4ßÂaŒu÷T•NŒesy}[wwî±_<ÜÓÓcÅúaRšµçdRŒ™|üê o½÷þë>õÉ¿ÿ»¿Ñó‘¤Ú¤­o¦Õ¤GUéøøX.›_¼háïŸ~rþüyúŽ¿ú#ï~×_õõõªª"É5+11u¹{è%uÊöõo|ëëw|ëó·|úÃÿ·wßsßWoÛ̘ºáãÿ`<"*LQLë™nr3!>2úÚ‹ƒ;k½ÒÂt¡T®}ׯå‰ù¡x”B’q+›ŽBT¦äóÝK–,Ý·ß™ëδ?®×¾6¯oA6—cª25é€ÙöfßSÉ\¥\4Û‹6Ü@Ö½*`þbA<™á®ƒ©òLßÑת¦òÍjÕñX<=厯+HÓn]‘c™Ec)BˆT›jÚòL·2³f¸8»sèzõ×Ò 3 VÓYð æp Æ|‰‰61¦jCxïþ½gžy–¾~bbâ=W\õ“ÿ»Ñ©øú‘Wçõ-ÈårÌ ž„0¦*S™ì3fòÏÿ|Ý_þåÛ×®]c|tÿú‘×&3Ñžï1}j‡j“³!½}…ºgþú-“l.gxÆxÚ‹×#1SÙ~õëw|ë–›oúЇ> ¨ÊÇ>öa•±Û6}ýï|Çüùóf(›ES öú6V{öõg_8¶RòÿvÿŸß¼úäâtOl!d¢F[›•©J6“½hýmßþÂðȰjM¹\~þ¹mg¬;³§»k²áüø³¦ljdž¢i±ö7‘ÌgSÔeQ-—!ñ¤ÑlgS+§ŸæBª•QBH<‘šÜf\™1:Žu…ùôY§©¥Ú3=íùžþSb̓l0„'!ÑXJ—c=gí±žõÔcC…ô)ÃfÓd™Õ^®fâÛi¾uÿHpÿ3gÞÀqOæ0mÚuy–áô¦2Iì&ç†ó®i_.›Ó†ðˆa'‰‡~ôo±XÌd÷ô0¦ÎŒdÌdxÈ8ðO?ý4SèîéfêŒGÆLU²Ó5²“ÊÄ”˜t[Îyõw_6SÙ†-ZøÔoÿÐß¾_¯ÃÇþáïûßÿÙ××[.—ŸnûëÎìžV6Ñ¢Š"W’Dg˜¶øÅõúÌÞ³nºèÿÇ¿½;Ïýæ¿ÌFóe²BI G˜•E#¢Ê„gžùÝóÏ=óÇ|q6k23¬R­<óôÓ§~öékÖÆ£†ÉÔƒÉDÉôT¶¢Ó>ÔCæ.6¾"<е8*îfcŒ2¢*Œ0•©Œ0•)Œ¨¶3ú©{“™6:âmç SÊ|ù…ÍkÊxýÂÔÒwÏqìÔá]ì)ŸµÕ!ÒúžÀÌfÚ«AgÜÃQ+¥î˜™Íyý!PAQ˜6„ßüæ‹33]‹SöGåé§wÚégŸ¾æŒdû\AùÙ£gžuÆÒ¥K㱸惬֪ǎ{ñÅ®¿d`` *Š4£½(uùTË)#uΆ#s·ñÀ¨ƒã”5Žb·ïéRÓnF©UãLú€*ˆtÒ<=e˜úzK˜Ù¨Ãj€ó×ç{½þ"9yy<’æo~³úÕÌÞ ï4gH ÆÔH4zö9ç ‚ð³Ÿ>zæYë–-]‹'´>R«V;:5„W§’1U‘M²U•Hd*“Ÿ=zæYë–.]Oé@µzl:“d¢>“©ŠÈ‘HL“ŸM‹IbJL*3Ä$›“ɇå4¸‰èŒ¢ª²‰O)ÛÏí•-(ŠdîÈ4·…gr²|réÖS´å úÏûÅ{­IB&ÁTF4+XViD00­Å‘H\eÂÉ“Çó›'^=|@UÕ\.;11!F¢K—®8ÿ‚‹zº{D‘(r¸1ílåʃ?Ó«iìÂ~å6S½[ÇÖB½TÌK¡Ôk+´Ì] ^Ì÷LaÇ gèNQŒ* ±Â…î1"¨SV”™ÍGÄHLµÍ¤§»'"Ò¦XcM"qU¥Îb¢H$¬ÏVLg;¥l'l*#ˆL‘j6ÙÅ“9M…‹ÖñVØiÿrÖxuœPúýw9›0ªOŸ|®®=˜a‰X¼\­é “¨1¬“a7-Øžá7eŒÅ㱺ìI(%lƦ©©5Z-¡õyNWq*r#Œ1B Õ[@­ ÿGµz›6Õ†ÊéÿMÏΟQ©fcÆÆ1¶€Þ:¬>žíjøÿä/ª½ð8ýc:¡Œ*Ë2¹UŸŸ2c…öƒMnez*JcѨ$Kú{@úDJô¸Q“;i…'÷Ô·BÇcµZ¿V>ù$Y[šš+B)aZ6•˜Rª^’HÄg&ÑŸOfl•©F™<,6™éô±Z©U§ÎІúÆèŒ¡djJK$“uß^oðHPB‰Ð0¨nª 57øôffêäg9ê­&ZŸÒμR-@Ó|(uc»S[ã•:Ú¢f¿©+Ó–çw_Þ¬³ü`§Íݰã¢Ñd`/sY™ŽÖ¥ju€qïl:sÛÙúæÁb¾ ê65–Ç,oS&ß@©óHDHÝKŒ0¢8¦ÿÛ^êAr‚&êÏíëÕcÜt‹vñ&ÏÇEk] zx-S¹õ¸’÷NÆšyú‚ý‚‰‹š²Œ4«S›u|!{X`ƒYwj¸(Ã6¤: h&2o˜3Ë ™l06ŠÌl$•9¿€à¶ÓfõI_#'Í _y ×›Ó.ô×½Ó¸}ô—pÍ9l†?0V#˜LƒÌCÖb“ S&ÊäâÝ»}˜©{‚ÙU¬AŽÈZ‘)ÿ¤î2Gõd,ÜñÓ¾–²ëX1žz EÁÜÎ\‡ðgÔ£YýÇ×ǸÃPá¦Å&æñg_»jr³s¿¸æî%:f!Ƕã­ ³QdJËx RÓAïüílõHx Žè3­ç @ሯû1ß›MúÛ|n-&VoŸ!Ç”ÿ<òyÜÉ1iüŠgEv+ʼºìhqktÓTÛ£²¦è‚㤧°•7<ñ Óøí`ýÕU˜¹°g¹{3½Ë±WÙµˆ:‹²]vic; 2Ý•×ÝBt°àvòù~´ucº•]WhŽê/k¡-Ü®Zìd‡!Ç^ d÷62‡({Óež¾ë;‹vñ³p2D <ÞÔw²øv¼ýkã‘`­ëßhq@§ÕâËãÜrLÍŸÑØºMY}èGN×õòÍ–†:QWÓ\<Ì›e¯œ°`Å5ôXpâè¼ÅÏÛtöO‡ÜfúÛ¶pàZkeÆ×?©ûb,¤×n76S‘ùý3»‘ëØ3+]¶+šñؾ®ú7muÇ }'ìàw3%ˆ9o>gD¨¿^¼-õ·ÝTØZ‹‰›ˆ¾ʱ.PÔY;©«š8¨¬¥"‡/Ê67Ÿ|“9û6m×!°ã!¸ãò%»ÄëlãŽ_'JÛÝŸµ› ›i11~ÑÕ­¸áÕ0Ú‹ÔAEøg?xPdâÚÜ8kúÌISiÀcªÍ]! c¡WÌGðâ&º4~Û·ÛE:¡Ãûw7 ÄÉ@æSIEv6©—Çi¦Ã›Ò`Ç sãs mÓÏZs¹ðô~ øP|*o‹Å·ƒœ§Â¼nŠöã Ù™ìÉ}®4gzq¹6:Áºø…BŸ RÞöߎ¹çê¶5ÛEŽ §™[‘mÍd.]æxÑØ›dPÚ~½b6H­Ó!1T BÃÏEñíDö%Ç'O>÷üó¥Ò(Ñ‚’2†¿ø‹¿žÿB²Ùì¹çœS(ôp_ž˜,Ë’$Í>ñF£‘H„zµN¨¢Hõï=6>ãBã_BÈÈȈ®¼ú²¾Àï-á¬3GúéFjÆ|aNmõlÛrîh:ÈM;¢é Î}ùMFÿƒ©‡Ž§,ž‡H²õ knÛ„³©Û¡ªk_svòFlÞ²å /Xµ~ýú6Œ\Z IDATM›7 ‚½Ö0_žŒŒŒtuuiÂj\&„tu9¬c‚æy$8ÕÜ4™¾R[+ÐÅq¬t¢n«iʱ҉L®Ï&%OþMðHè[íÓ“·òû:ëf“ÒæñºibmA¿ ˜VÞU{Ú´›‡¦n‡ªèþråûr{7V×DV=°qZŽY),™ˆ^÷O×?þøã—^zi".šZ|öf ›d~[ب‰ÃÃöððð°žÆ˜ÀÊF6Moº²»»{xxØtËf„1×aõ¨¢HåñY>yv×î=«W¯6ÚÜÏ:«U›_ÕÎír” ¢U‰±ú÷$IÚ»wï)«lv—$yÏž½gž}~­Vóð^Y§ Šb4}aÛ³«WD£W#±ÀÛM}ÓzQkKgL•å*ú@“<Ždr&ر҉6?Ⱥû HpHtPú­ªÿCkùìð—ê|û-ˆÂ¡C—.]*˲}ÊžóhÅCCCmÞ£¢Ñè¡CÑË“6w*Üþ‚kJ"—eÅCèO€ ²¬Äãq h&Þ»gg¡Ð›L&dÙÎ)ц“y¹d4Û³{çê‚àzªÙœðHÌ›×7Z*ÆbQÛàk“̘"+£¥‘ùóf(‰¢ØÕ•ïîÊÿçc??åÔÓ—-[>ûÚb÷î]{vï\¼¨¿«+/Š¢ÛÝçÄÓ9BH¥R®Tª®¼æõ™¥Ö뙯Í.‹$ÉÉd"O8¦TUµX,½öÚëGŽ›•¡yr¹\ÿ‚ù‹-Ìçs‚àÎ)‘LuÍž“ÐB¬ƒ—W×úÉ:¸)+—˥ѱòDyöèd*™Ëf’ɤ‡OÏés$ÆÚÜêjt3O„Φ N0Ùlí‹”ÒT*•L&c³òèÜëït;ÌÅ™jÀÍ€§~T‚uº³¶Ëhî©Õì'‚¾‚–:ÛUĨÂ@¤h*š ÂÐt<ú…c²¬HR­ã8E""Ô:X…%I®IJ>_èJ$;•rqäd,™ïÑæ€ ˲<OiIšY cA°nÔ1Æ^T5~KÿF‹Ï¼yÛÝ~/jfÝ+²¢6|Ü0Ä Qu{œÒ’:4³Æ‚ ÂÌTa6ýY6?O±ë„¯¿°ÉÌ(ª¬šyº->U[xíÕ]¦ëõM‹ŸÚ˜¦nÍ´ú›­l¬ƒ±”º‚ê6Õ%¨[cg5LÛÁ´n® `ÎbôèzöH0DÝÆ’éîIE&„h? !úóÀdº»<>¬­7>$œ±f*[›Ýù×íʼn,›HϲåkÜ¡‹Ž¾l\oÜÔøqC«Ï 㬃± =7Y–M+#˲UÝüTê|¨WaÇnŒ1- 3Kl\316Ä›Jez´emAWÏÆe}AO©©ª–­ýîÆü{Y¥†caŒ¢ªŠªÎ°ØV¬Zw`ß ª2¹òÀ¾–-_{`ß “Æ2#±ª¨u êv7½`9Ö¡± «Òùz¨FÝèËÆ•u‡oSЬŒó @0*ìÊ®C\ãÏÆ¦9Y­¯Ë“÷ºš˜Wžš€¬ÁªÌ|Hµw÷súã&«õŠõ3.ª ¦F¨cLWÚü4ÖÍO5LóoÌÙª,Ó‚$ØøÂFA4𮯕~ò÷¹{€ìÝýÜÀ)ç†W–^g)¡Ömà”sB=X Â.Œáºg\éLa|lp¦ÅÉŒ ÆéL¡n«é.žw'à&í² ÂÅH×&Mõ¿A)~ u´'>îÙÌÄâgÃB:S0ÙÊÒq÷ Lÿ™J3èLÈÔg²tN9§n¡1%™šjB¼Õ¡qeÝO›ºy®†žó¾=ÏÛäl[<j 7JÙøè`:[0þœ6“³…ñÑA=A]Jb•1¥ãîüùsÕ¾ÝϯZ}ö¾ÝÏk?Wrö¾ÝÏO§aæûNïÅýd‡–eÉ\ýôTúÃoHfžÀ±  Âþ,á:<Ù8@§V2ãòøèIÃcÊú½ìW·ZíeVuFM @ʦKïÛ³mÕê³ôeÓõu›ôìì›Ïtj¶i̳ª[iøé\7÷ÕÐ2\uÊÙuyêk¼ ~æHtîl#ÊÌeÍT)öïÝnš‰ÕzûMö¶acL³ª[Ù˜†«AWÃCAâoŽÄ´˜e²æQÇFOt8·Aq´=Žšv~ó0<‹r¬tÂI¨Û fzËìó®yŪu+­Þb°rð×ÁUqn=ÆõáÌžPâW…îÑUq>ëàª8?Õ¯ ñS­Ã[6H¥šU’Š‘–ס™Õ0-@‚#ÑA:L‰Ä’åò!¤R­BD*(M< ˆ jE·°ͬ†± I‚"ƒÙ†Ÿ+RE–&Ƨom‘$yÏž=gŸûFE*+ŠÔ¹M‹§ !ÑX ½àŸÑÑâhi0•Lr\qÒÒI¥{æ¨UR«ŽB¹†ÞðO*™J§—½rpw·û+FLå™ÃÁfÁÓ9E ‚“r-‘êÊæ ¥ÒÛ+âí~©6ÞÝS)ÅÝ÷é¡ÂŒY–*åɯ8B… 3€ T Â>Éæÿé+ëš@3ËŸ9_x´xÔqMhI¡àÝÖ‚B„ÂÔ,­³—Ó×mÕWšîn“’'±ÕŽºê²`maN]ÖmUãrcӔ޻»* F4 ½<Žæ°n2›Úκ-Y9Ú˜gã‚i‰¥‘£)MWjË¥‘£Ùü‚ÒÈÑ\×=gâÆÀ«}K¼¹‚´…âkC®Ë׿«Ý}–Mµ…›§X²»Ï² l„)KºÑyL U~ëUO©{Ü&æÙB`Zu›m kÂgc¨ê ì·òع®kJ E´Ö#ÁÙÒ(äFÉ;Ò¸»¾Ò¸µ4rĶ86•€™ælº{CþÌkb<¤bçºóHh í¥ÂӠ ¨0¨0Ì‚|wîäàÐóÏ?_*B(¥Œ1üÅ_üÅß6üKÉf³çœsv¡§{ö¨ðÐðð³Ïþ~þü¾+VàâhsJ¥â³Ï>{Áùçuw·XˆSá={ö®Z¹bÍÚu8»€Žà¥/ìÙ»ï‚óÏkm5ó ‹‚0þ<œW@§0þ­º˜#&LLL|ø#Ÿ˜(O =…BÏGÿáår9$[ØTžíÿë•Ð)X©Sùk_»]Uä3×­½óÎoœ¹nm­VÛ´ùUUÜk£UJs@þýÁ W¯øÚ¦-ïy÷_ÝvÛ×V ýà?Û…dªKÿ[·¾î_sjà.üÖ£«šØ$vÛna7²· Ý|O>ù!äÊ+ßwÆÚÕµêø™ëN¿â½ÿ“òįŸ¬V«Áa†ÏÅ;=- žòÄÈ\8©ÉT—ç#µ—`·Ù6­ÁçÈ™³’t:ý7ó·„Ò‹.4á·ÚôÄú.Æ}M—µal“¸±t«Iq/}6;¡z$ˆóÓ¹ö¶…[èð§ÑgàJ›×Ût³±n9¤špîhU?Ñ,]½$ hDÇû…y"[ò›ŠŽÃÒU,…@su.¦á;lš.Ô‡i0x<­ñHx³‰8­HÇâlîôç‚.4ú웎óÜykC¼ ³t¶“žÎ%Á¡NYãœæG—MÅ…Ý®v ÜÔµúØ] ç€YFë#[òGA Ê#ašÒU4ËV5q®Ó1½1,'qÔªnÆæòSg1h ÌšNòHîÈ–Žñ!9Ÿeñ?òòõ‘'^%q®“¸xiÚžn?ÇytöÍåöˆ<×þ Ðn*xYxƒ\Š-¬«{à¹laÓüa €×/ì(óú¥ ñš’Ç–Z}W@ßZ'zÌ<^=ŒïŸUb7Vj £}a[£VÖ1¬`:Çè51Þ9¦èJEuDx G=òé1hC½ƒ[˜ß!AZþss"0„mŸÂþ*ìY‡#Vòl¿Ÿ©Âí›sP.@k½ÄÇ+¿¦oÎ÷¯{xƒ9@n0„¶BZÿ³}à4ÃÖ`t¸•ó…›0— p­Í¶*¬ªj°Åµ ¾pØQ"õüë®{(Â>Ê¥ÿüí©Â”RÍÿK§Ð—1܂ȖœQ"ýçFæœùC‘€ w€G:Î#Ñ‘¶0t³Í#e Û¨pàOçà‘€zö¼¶0À¬òH„4Í>OŸÓx=ä‰ÃÌ&[¸É*Ì‹#á?¶¤‡<}¾ â!„² ·…èKáú…Ã0íólþËr°‚˜­ SÂöHðÄ‘`‰Y;Ä‘h~LaH0kõΔ²@âH0·b Ó!®Á#ª-LÂó 3K[æ¼…ÜÊ8ü æÂ!CЦ ÷žÔiÆ!Ñ”™j`láÖ¨p±Mó P—í£Yæ6*xY-þúgPyÚÏ ¤Çü¡ÑÌK9€Lšé‘x$x=á<œ€¶T[OÂ¥«°ÕÇ7½}ý3ü ÃÐ9„o 3s[iþÓ9Sy¶5á |€Î‡™Feðú³iž®U8xx¦©ù™fŸ¿ÿIrˆr láfÚÂÁK°ã”[?a!íó÷WQ.€ wö7˜›ᬙÀ `.z1:K…=Yw7˜Ý¶°)æ‘hšÚ®¤)~áVÚÂN@ÇÑ‘ µM¦ºê¢ó@Íí¬³ù f+‰ $b$ÐѶp„_`; ¥}þ¦1*](—ÌMµAUÕ Ê¨·…Ã’až(”ÄÇÔÇüýË%¢\0×PU5t¶Œ#ÁÜäÀìÔÚÞè%><Ã#ÁœM]>„‹]G0[e˜¹´…ƒùðœ€óuÖ®=Á”g¦ZРÂ0¥À”F£‘J¥bc—ËåHT ð%ºp¿þIZyÒø2ˆ·"¼Õ³ÙèDDQÈeS¿ýí“çŸA4k”àJ¥²}ÛóÙLR}™°FWt+#[6!ò$ñ7oÌ[ýÜ€ÎUá|.122ø£>(ËÊLË”BbQ1“Ig3IA Ôn”gýf‰÷ c^­}a¤VÖ±ñ]@;Ó8›!•J$“ IîªVk”RA÷GÂq"APÞÛ'h4À• 3¼Á ÍezÎ0žÎ@‹masy¶Öo·_Û€¶5H[ÿs„ÀM—x$ •¸ûú§•!îÍš€Ö¤>''0—ëM- ­* Pa˜«„þÅ#õfÇÁ€–ÛÂòl£ßxi0k ÒÖ¿´1i 3ˆ(4ù 011AðtZÂÄDùÏm/—ËâH˜ÚâÖ4  Ò ]¼b˜J%ßpîYÉd¶0´è"ÀGZ‚î‘€ @ €GZ <Ð2¬æHOã7Œ=l%3?ܸ‹Ï•ÄìËÊœ»+Ù˜Ø[ž~šÈg}Üînz ®Š¶?¹6y0;<᪰qìÙa«ÏËóìâseãPwµ»Õ!øÉÓOù¬«Ý­jâ¸/ÿÉu<|:—d2ÑÖ‰¦:oÙïbš'††Tsþúpîî³hŸõ`6¡ÙÂmTÍþf<(Üf®ÕÊ^•š&(œÇh¿»M†œE»:¹0„Á¬cúH;WÓþVݘ¬qH›®t¼G¶ÏÓÊ“à߆5UvŸMÔ†·ü'š æ–-l°„ùƒª‘°¿M»¡±šÜ’úК+@s<Žbg³†j—Ï•„{€Ÿ<ù‹£‰üׇ§‰š³/³ ÷_<2ÚâÖ9tAøÃ9âñ‹GZT ÂÐ|šGÂ~¨ÍVþ8‡ö#ÃHl\MÏ[C éé3 §UL5ŸíÀ,‚Õ©pˆ8¾(Ì£ÎöqmÂ?òŠlBM?A,›Ò“çDØÔ¶.‡ Ú€9â‘øí9ÏV°gkÈ~[Ù˜øfÈ?ƒ¸9ï û<„o¶øí¹pma·ï#øqq4†%㿽›7ÂaDøÕNÿײ¡Â`îÐÑ|x 4Ó»Zþ؉œ!4½­ êhÇ+–ùŒÔcl1·ÇÂSó@‡î0³Ž?„&ÿJG DelŠP˼EõVs(/€ w€!FìDWêÌïZ Ü kÌÐUþžëã*N¦iY>kÀ,¦­ç uûì(ë>}­’àæÔ‡ßÉ[žÑþ7`¶p›*/œCþ(”ü±%}® <ˆ¥Ïšû é3ȧϚÐ4ÄHÌmzµVî v5SÂñÛB~aäI‚ Òh“Ïš»šèÿëÑ<_ÿ ü‹Ôøõ •©œ‰™J!ýÄÌ0©ÂŒ{ưÊT¨0I4–L§bM !±h$KB… 8[XŒ®Y³ŽR{˜RÊTõÔS× b4r#hz „LŒ8e].—?|x¥\ST±@…T.ÕÝÓ{Úš³'ÆN@… 0jÕqJè‚þóû—ÉR¦DˆDc”c¥cµêxg¨°ãÜ#W‘-Ia›³{x‘-‰§ð›Ž·(£ˆc Ú™ju¬zb¬É…†«ÂŽïìºlé?lcsv<²¥£ª6ÿpø£Œ"Ž%6„ûtÎq>xcÏgæü1ƒj Ç[ôΠ¢ß9¦„ÚÐlD«d ³U¾o‡ã3’$ÿå!+h¥ {°L½ÅNlááð—îÍNw~“Sò\…Cs%£VÑ|Œ ø…Aû³wßþõÿ^õ¾wŸ±öôYh ócǯrpÆ– ÉènBéüá7é}:"Lû¼„x8¿´ŠcÇN\qåß¿úêëüëwvl²¯¯F)Íxk£ cLåÕ’ÃqUz°­ÑÌç?¨*˜LL”¯þÄ år¥Pè)z6\sc¹\éHn€l¦ýë3Hc°á%]E¶lZ C‚Á,@Qԯ߱•0v溵wÞù3×­•eùwÞ§ª,ð²BŸ©F\>V²÷úŒˆØÚ ö­aÙ²µ­8–`®ñÐO)ޝ^=põǯ9ý´«W l½÷îá‘Òòèåõç¤Â>iáÑUDDŸA9?á?¼$Ï.­m¦Å å”Ëå§žú=!äÊ+ßwÊÀ¢òøÐé§/¿â½ÿóûÿßÿûïß>óö?¿4XÞ`€¤Ré¿ù›*œ÷†3*#„J¹tñ›ÎÅb‚ Äq‚ôK@…`Œ©ç½á BHeÆÍèðç­cL­VF;É#H¥\2[Y £,Ä€V€VÒâÈ–XÁ Mø—¾ó/|‡f¤1Æî»ÿÿ<ð¯ÿ·ó<þÇ$§qÔñËÿ«Y¦QÄüDhtuDMÐAþ&ò_s……ƒv Týôg¿L©Õj¿úï´•ßüÖý_¹õë„÷^qy&“î[X~ö#Ѓdzw\'ÚSí0½å7 ÆX·»éJ›[éÀ;Wñ|Ѧ‰¼\-+Ç3îØn´|>¿aáBD IDATBÈ翸é¡ÿŒòà~¢IðÆ3ÙLçÙÂÞ""ZÝn›Ærôÿ]ŒÆJò¿³ËÙ²å·Û~ýK]žSoýê燇‡}ô‘[>Ûk¯½÷¾ÿ](ô\vÙe_ùòÍåñÁÙæ‘àùú§£8†dNúô ØÌ^ðV?†pP¾‹kÿh[*C[ï¹óºënعóå_þòW«V¬Y³vóæ¯.Á¡{$m{#Kžëá:ÏT„@îˆù 2=¢¦ù"ü¸|ž 8"@'ĵÊðm_»í /Xµ~ýú[o»µV"¤Ó"[ÚG/4‚ÈyK<côw1¨ –ü‡f¼Ó'ÜÓ«?FŸçÂCß üTýÔ§þé¿~õ_—\r “‹Œ©a˜ '“ YVøïÖ‰×`‰­»HK‘'ù›1¤c ü\¸­*#ËJ"‘°I ªrT‰Dc çr¹ZÌqJ¥ÒëLJ •UXV”b©¼úÔsS«ÕZ›Ô?“Ée2k÷î~.ŸOGÄÀì*¨p¸‘XOW¶««+¡5À\‚P©ÉƒÃÎ!zGKc…¾¥ªª´kæy"ï),:ÜݬYÐ3Â&‰¢Їƒ£_UQÔb±T(Ì3 –ÛZ$Y.ôö‹%E ,Öp`¶ð¼þÓf܉Ùé˜Þ1 g¹ähS+Õª$Iªª¿EoE.—+•Jökø·:ÕI’\©VSƒ²bƒôHÕâÍVá:E6 ±n)kk´Ÿz‚º­¦»ðäc¼h›ì3lLɳ—ÕJ¬>Ql³lÜ¡íÌ=[ï#„\ý±ê>ÆØÝwß+FÄ«?öÑVÕJ0ÞhÿjF±–À³uܾ*lå4Жݸ•eÖ7–hŸaã•€g/«¬xÐ>¶¦ÿåIß(Í´!ÅRéºën$„Ôªµk¯½F[¹eË·|î‹„÷_õ¾l6ãÍà-™¶&ŸÏ‹E]sõe=½¾Ò¸Õ}\ÑŒ§sö:å¨b<»Xeb\HAÞÐ?… µJ>—ß°a!ä¦Oßüàƒ?$„|ï{ß×$xãÆÞ$ØEé3qqñàPÞÙ` “†gwŽ[uG•+ ð:ø¯›[sØ›€v…ݾeÓððð£>rý 7>üêß¼«Pè¹ì²Ë6oºMUÃpV'¦F!v%ÊsÅ#Á¹Õô®ß³íi_·Ö®g„.©ü #ÃLºÿÛ[?ùÉkwî|ùá‡XµbÍšµwÜq‡g fŒùôHBFFFW6.pz:É#aå®å´:…Ò³QìG?öx°62í† Í[¶\xá…«Ö¯_¿iófA`mR·®®.Εl ›Z²Æ[xã½<™ù„­ñN¿ñ®ßÆMÁã\6­ç^<ÕpeØrj®î,Ö•ã´¹AœLD¯û§ëüñK/½4ýþmáááa]g‡‡‡M`=qk“ma*I•á“9S˲¼wß7^ôÖ‰Ñã’TFsdס₾žÞž<Ḭ̈…Áì€RA£Š"16ã³Jµvr¨xüäÐê¥y'9xÁ…o­ÕjŠâësÝÝÝÃÃîvE1>ó»ÇV-¯‹é¥’éÞ§Ÿzl`Õ þp?Ý…eˆ#ºiàÖL ã;=Se¹ŠvhG¢0õ6tÄ[|`ËdÚ䄃÷/]ºL–åð*244äÖÃˆî… Ÿ¨Í6n7Á©ZϦâR­ÖŠ£ã”88(RéÔΗ¶õöö%‰ãæø•KQù¥m+–/¢40!†G"\úºdž«¯=ѕˢ€N²€)V*ÕþÞ”ƒí" ù|.—+þü‘Ÿœ¾æ¬å+V¶É!ìÚ»gçËÛ,èÍçs¢îòÙ” ”‹Õ#Çá&sxTX4/‘N&S¦’‰Å‹úîÞµíÙgŸl“úç²™… ûö/HqT¸È¦“ðHà Aòùl,í-ô”Ë•6©U2™ÈdÒÉd"ØûZ¨0 ¡”¦RÉd2ÁXûT‰„áW„ ÚZ‹gýó|ñ Â¨0¨0Ì‚Ÿ©ÆS¥V“ì“ÅbQQñR/tB0wUX–åJEîé]¼ ßmŸ²X:ùj2áÄ :!€ Ûö~E)–Ê«O=‡©Jµêð¥©L:›É¬Ý»û¹|>Eœ €N Â~-ú–ª½Ÿ"Ë$‘ˆ÷ îîÎãLtB07 ì霢¨Åb©P˜'˼_(‘$¹ÐÛW,–L㇆ô‘M\•hú1éVÕ„× ëþ…ÚÇül $™ÛDWo/[˜1µR­J’¤ªª}øúýû÷ç;ßùà?¸råJI’+Õ*cêlš­ÑŸö˜•ðwBÃ.̾ÖMžOÍ¢ûÖØÂœ nذá?øÁ¿ÿû¿»ºäjWÝ:{³Ñ;„¾¾nÁjÙ*™ú¾½U2}«iÎÊÛÔÁ¦b ØNè-™Í™å?¹V¢Ù¸Õ¦ºê-öÝÕf«ãX¥GOn6EUÕM›6>|¸««ëÖ[o]¼x±‡[-cOå?Á-ì ŠFÇm7ƒ—ÿì8nµ±[ý”Tmùw´R[ôä«ðÑ£GúÓŸ¾ýíoïïï×Ö|÷»ßݶm[&“Ù¸qãºuëÚdPñ'3#ׂV]GM @ÏÌgGmŸûýÆ«zrëU˜1Æ{ì±Çžxâ‰íÛ·ßpà ýýý¿úÕ¯~ùË_f2™Ë/¿üâ‹/ÖnY+bèsö «Û·@„ØÞpÀ}\P0XDãÙ·¹Ç÷)”öùUJ€ÊX%ôd·„õtîmo{ÛÂ… %Iºë®»žzê©|0N_rÉ%ïxÇ;Â6cÃ6ŠÃ¨eÌ&¯…739¨]š0 ì÷¡?´‹G¢¯¯ïÚk¯½ÿþûK¥ÒC=”J¥–/_~ÕUWyxg_¿Ç±w«ñÜ 9&0> 1½Ô{x@ÇSÜÇuŠw¢®'XǺ­6ØÕ.uýÓqDð,W½Ô*½éØŽPIª Ÿ<È™Z–å½û¼ñ¢·NŒ—¤ræƒ\øÖZ­¦(“sæxàI’‰ÄÕW_J¥Œ»ˆ¢FŸùÝc«–s¾È{àÔ?ë;¡=:!¦Dc©dº÷é§Xµ‚¿/u–…Ûí …Â•W^ùä“O^|ñÅuìíö €GÂÚ¬„ƒ÷/]ºL–e}å’%K®¼òJbö$Ü' §ë?¡ÅÀU'´³_8:!à›˜­WaJ…T:µó¥m½½}‰DÂô­üG„±±ñ—_Ú¶bù"J-ÇÎ+hy'›¡Â¢(äó¹\®øóG~rúš³–¯XiŸ~×Î=;_Þ¶`Ao>ŸE €N Â¾I%‹õ ݽkÛ³Ï>iŸ8—Í,\Ø·°A*™Àiè„*‚ äóÙX,Ú[è)—+ö‰“ÉD&“N&øÞ @'PáÀ ”¦RÉd2áøF¥]„:!˜Ó*¬ômÐr-F'q‡&¨0@…ùáС=±Dm:ñDæÐ¡ÝÞrçÖfÅ¿üâö¾B!•é“jchzˆÅ3Õjù¥Û—/[âö w*,ŠB>ŸÏæŠüìggœ±véòš8Âðñçìß½»w¾ôòŽùóçåóy·o¹‹©FQUµT}ýÈÑcÇN”Fa Ée3óç÷-ì_Ëe7N /1ÕAÈçs±XŒgV<Ì’ÉD&“ñö—ùÂÚ¬øT*‰¦Ÿ`¦@…* * PaÍÂ{L5Ƙ¢(µš„FÌeb±¨(ŠžƒøyTaY–+©§wñ‚|Î`.S,<œLD#/ŠêeYQŠ¥òêSÏQ•êøØÎ`.“N¥3ËÏØ»ûù|&"ŠÍPáÑÒh¡o™ªÔÆÇ‡qsY®¦Ó==…Å#C‡»»»Üîîú霢¨Åb©Pè«VÇÑú@©ÖÆ ½}ÅbIQÔÐU˜1µR­Ê²D¿êsƨ,Ë•j•±ðU@€@…* :œîžÅÚ¿&êß&×€F"hà_ ‡‡^m\î:«¶¶0v¬‰šÑºl´‘µåFÃÙ&¥+s›§DcÎAÕØÂ í¬K+Y_n\pLionsæ£]*ê2ñ_[ Â õv1Ïm>¿lé)}*ŸÝƒ: Â IVpã½+÷œ ™‡Éy½* ÚÅ;á3e }Ž  à<†6ª”QìM‚ƒn<”°…Aû ±Õ| Gïª}J=Aè»­‰q¥Í쟵ÀT’*Ã'òï ËòÞ}ÞxÑ[k•’¬ÔЂ ÍéîY2ßÖ*,ËJ±8ºbåÆÔjµjŸ8•ΤÓÙ^îêÊE""N3>Ù·ÿÐÀÀÀÂ…ýÉd ­,åòD&“Ù¿ÿ•sÏY×Ö*<::ÚÕ=Ÿ1µR©rH¶œHÄó]óFGO„aç0׈F" ÌÏd²hŠÀÉd² ¨###çäÓ9EQGFŠ==½’$ó»/zzzGFŠŠ¢6níîY¹’sk°™ø,ÅCqÝ=K´ÁÖÌ"¥ÈÀ d2•Í… ÒVUµR©H’¤ª*cÌ&åþýû¿óï|ðƒ\¹r¥$I•JEUUQäº$ÌÙØà+–{âÞ’™Ê1†k;S×O8Ï®»s„°æ 3 *•Ê7ÞX*•úûû¯¸â {“™³›êZì(Á‰õ•u‚núº½8W:æcŸ®’Ù´ƒiS€v¶‘]¨ÑÝÔø×Ô¾þ¹gë}÷l½Ï¨HŒ±»îÚºõÞoÏ*[XUÕM›6>|¸««ëÖ[o]¼xq€&¿a”*{[ÃÑñ`ªÀºV7v悱·èk`#N±Tºîº !µjíÚk¯ÑVnÙrÇ-Ÿû"!äýW½/›Ít¤ =zô§?ýéÛßþöþþ~mÍw¿ûÝmÛ¶e2™7®[·.¼ž`úÆ­V"n“¿î7í¨AóÍÞÆ1mò¹ü† î¾ûî›>}ó’%‹ßýîw~ï{ß×$xãÆ¡J°/f„ÕùØÔ ÍùðØc=ñÄÛ·o¿á†úûûõ«_ýò—¿Ìd2—_~ùÅ_¬%ú;¹‹©gÃfeÝ탶04øŠÖﵫܴåžÂR=YOai]ÎV[õ•ÆRŒÅ5ÖÐtÎ#²IÖ˜¿U£buvŒËF¥¶:ÑCƒ¯hý§8hØí[6 ?úè#×ßpÓáïÞùÍ» …žË.»ló¦ÛTµæ(JÄÇ 1ŽÄÛÞö¶… J’t×]w=õÔS>ø`:¾ä’KÞñŽw´¶½{ K9WšnµOišÀq—°t„RëÿlNîÐà+8Å¡È0“îÿöÖw^þW«V.øá‡V­x÷»ÞyïÖ{ê$¸Ã<}}}×^{íý÷ß_*•zè¡T*µ|ùò«®ºÊUàˆºgÓAû´Ñ’Õ׬VêÝÝ~c}LW6æcuœÉìÖÃŽ  ©ë· iÙ¼eËW¾ü¥“'OÌ›7ÿÓŸù¬ 4ãž#¬h>šÑÞÛÛûÑ~ô$)‘H|àEÑþ¦ÛQsM=ö+Ýîë!gûâìÅÑm2þ*2„¯£º:7Z (éJ&¢×ýÓõ?þø¥—^šˆ‹Íñü„>G¢P(\yå•O>ùäÅ_œJá­0–g½£Ñ€‹nSüjwWö¯ßónE‘S›ShÀ*,ŠÂ¡C–,Y&ËÓ/1/Y²äÊ+¯4µ|£ÑÈ¡ƒLßš3u~¡ó`+"hƒ„X–«67úm­Â‚@Óéô®; …Þd2)ËŠCÙqlllç®W­\&‚ fVOÒBêÞ*SÐæ¡F ©Â¢(æó¹|Wæ¿øÙi§±|ù ûô»víݵkÇÂ…óòùœ("²%˜CEEI’£Ñ(Z> $Iކƒ7`D*•\²x‘@…½{_zOœÍe/Z°hQ*•Ä À?½½…‘‘ÑèüH$†¯Øê¦`²\9Þ××Ûî*,B>Ÿ‹Åb½½…r¹lŸ8™Lf³™d2î@Pf ÁÁ㑾_0²,§RÉx<Ñî*L¡”¦RÉd2áèÆ¦”B–D"™Hàæ²“ë‚ ….š Â¨0¨0@…„ ^°™[0ÆdY‘$ MÑh41;@…3’$—+µ®®y}óº4M&„â¯Ï¿¥RqdäX2FÛb4 ¼ðâK££c”PJ)c ýÿ%”dÒé3Ï\Û݇ ȲR,Ž­\µ†1µZ­¢A‚"ΦÓÙû_êêÊE"- 82RÚ¶íÅ ¬XS,ÅbqÛ¶Ï9g]>Ÿƒ /ŒŽŽv÷ÌgL­T ÁÁ^ÞäD"ÞÕ=´t¼»»«µ•ÙàÐÀÀÀÂ…ýÉ$¾k0åòD&“Ùà•sÎ>* \£(êÈHqùе°‚Crõôôô~eO.—3ýpLÓˆF¢ ÌÏd²8)“Éd,PGF†Ïs$檪V*UI’ìÝe³Yã²ñgcJûs Ƙ$I•JUUÕV+EVpx$“©l6x¶>ma“!ÝSX¦- 2®4þ –P3÷SŸvªÓN–þylA!„är¹R©D,¾²¥oÕäX_†ëíl5(êÆ…UÿÑ’™%Ð'šse+<FÝi7ql>í|øF%Õ–s¹œ¶Ü˜h%Ø‹¶¼„ƒpÂëmÆ‹¹žÀtq¥¶¬¯iLP—cúº¯/Ïâ‘Y*•n¸áBHµZݸq£¶òŽ;îøÂ¾@yßûÞ—Éd¼C²êÌÆM°‘CéÒ¥Òu×ÝH©Uk×^{¶rË–;nùÜ !ï¿ê}Ùl&¼Ò›÷t®±ëXu&}½}oãäÌÄñ :ËqaJ>Ÿ/N¡Ëñ¬$ŸÏoذróÍ7ÿð‡?$„|ÿûß×$xãÆœ¬I§éýh—›<Ñ7}úæü!!ä{ßû¾&Á7n U‚IËgª9vJ·½–?ýÜÆÛ纛/««|L—g_smÚ´ixxøÑG¹é¦>|øðÝwßU(ô\vÙe·Ýv[­Vãl@žTÃõTß¾eòD_ÃM‡¿zç7'OôæM·©j­ƒU˜óA„•&:&hŽ æ2’$Ý{ïÖOýãµ;w¾üè#¬Z±fÍÚ-·ß¡K°ÏQàv4AˆC»âJ÷{ë'?yíÎ/?üð䉾ãŽ;–à€=uãø»‹7‹Ø~¯Àâ`“µÜ*éšÂ¸¾î§ã¾###s`|’M›·\xá…«Ö¯_ÿµM›g±ù?—²yËô‰Þ´y³ 4ãD»·…)D¢ª*3Umœ£~òÄ]SNž8`Lи¬'>yâ@oß =¾`šÀ¸ROÙSXvòÄ«Òùëi_ɺ5¦+­Ž…ÿðÃ@eªÊÔ:ÄððpWA[ö}ß¹ GŒ1QŒ|êºëÿë¿Ë[.¥T¨;YZ#hlßÙ¬:’iW1.ôö­ÐvÔö:yâD3ŒSLD¯û§ëüñK/½4M»·éPUUUUåH$JÜG×£’T>yYQ~uÝYÉ•QI©á¼B¡oÅ`˜ãJ’å½{÷Ÿwþ›kµš¢(œ{uwwÛèïÜ3”„X,V«Õ¡(ŠÑhô÷Ï>10°2iå³–ƒ‡Ž¬^½:bLy†RA£Š"1¦6º§öîÝ»lé³î'Ó/l{jÉ’ÅÑEP§îÂ2/=F !*cªŸ×E@¡o¥¾/Ð)ˆͤS»v¾ØÛÛ—L&ec50"qlll×ÎV®X" a}scðÄ~®ëA„J’ DHH’‰XbF˜J/§ß“GB aŒUQqb:Ji6—ÉåR?ô?N_sæòå+Ñ&A±kמ/¿Ðßß—Íe(¥­ «ÖÓÓ5xòõÈü…ÑXœRDL ÒMQ«UO)ºMO± Ƙ x óïE…E2¦Æð­­"J-Y¼P„=»_üó¿EƒE6—]¼hþÂ… Ò©TËGD:EáäÉ#‘B‡Œ,Ëét2[È4#„‰Í±…)¥” “þi8†;Ò|.‹F …îr¹‚ Šd2‘Íd’É¥mñ¨$',”b[­gL¥Ô‹O*⾌ c*c*ÉsLd*‘HÆqõ ²Q)цÃÜîŒMµÏ†í‘©6ŒáèLÕ 8sn"3¦Šb“<D ¢6_—~ „¦2F*zx4àÕVUB)Á Ð<M³… !“OçT¼;S"Ìmλs„Jé¤;îE DeªHToS½ÌTEQUUBSá’B©øÿ·w¿¿mqÇ'à¤@lçǶ6M'MÓª©Óþö=™öOx郶ë~(Ò¦mÑ,Û8NÀîö€ Y±ƒÿÀy¿D×ë5¹/µ>9À>”R¦i®âjYB+-ŸÚ!„ІÐ+Z ‹OïTÓÂÜ€O¬õÊÞ©f†©´Zsw2J+ÃXäŠÄ"wô¤4Ö¾*lÒbX ¡åBûH,’Âòyyب ^ìº0{ßÀ:‘Â@ ) …àiá±(@åi­Ó4½½7ª;;µRó­Die‹"…­’$I%Öóï`óg½nç/ǶŠ<¯*¥•*j’ÇQ¯sÁK¨d§iÜ|õõ÷Z«$I+pöm™B¿}l{Þžeš…JSi²Ù{·L) Y¤¨IÍ—¬… » ›‡§Z©h4ªÊÊݶwÍçýîŸÞÜÒ”JG£Û/JØ»;EŠšŠ»s@U¥© ‚A³y'I…¦ÇI³uƒtö 7/­ |!Dœ¤s‹š¹”æ¥ T”Ö*â8VJé› ¸®; –7Ÿ‚ß_kÇI4i­f-Ë––O kŒOcÙU/Š°ÍÆÓv5ÉûX¸"<ÅÀʾæ‹ÇñöÔãÃîygüZ"8[ OÉäüKu. ka`;®NèRÃ\× ‚ Ïͼ5î ÈžçMþí䀹3Ñ%wd,[Zf|&Ycêü‹w>nQ¬…§nÖÉû¥e|ßï÷ûE¦šóŽå¬öäçöÌÍß•Xj†Å;—„+°N¬… 3¥¼¸øãôôe2o+‰F£‘5ºÝîÜoÛívõÒž³^³¬‹‹ßM)½´©…¬æ?¢V+T) lÃõ½ú‡wç­Ö¡c;Iz߯7ò®^Ë2‡×Ã÷ïÎ_}a²Hi¶mß¿EÎÚK³,9Î/жn!lJÏs]7øéǾDCt¹;IDATyýÝÙ«/7ο|øõÃû󓓖繦)·£´‚EÍü•Ã.ï@u)¥ƒáßÿü{yÙ\ 7Âî³ýããÖ矸÷ü½B¥/j»¼Õ&¥ô¼g;;µV³†ÑæOØqìýý=DZç>¥­B¥/Š+À2 £^wÇ^Úí´Ç­(UU)­TQ¤0°µYü€ ´µžÐðòRHa) ¤0€R@ Àº¹ IaX[·ÛoÃ04”RËX±0Œ^œ½‘Qtͱ€Õ¯…ÇἺ „asD`5á+òË·âúúJ&J‡Ãp¤9:°ìn·ßFQT¯;¯¿}£’¤¶[7Ú?Ÿµnà :i¢N_œp¤`IÂ0ú¯Ó7-éûG~óØ’ò‡Í#d,Ê©NIEND®B`‚veusz-1.15/Documents/manimages/customdefinition.png0000644002344000001440000004501311734662204022575 0ustar jssusers00000000000000‰PNG  IHDR×Â2ý¾sRGB®Îé pHYsHHãѾŽtIMEÚ>§•tEXtCommentCreated with GIMPW IDATxÚíyœæFyç«$½W÷ÛoŸsß—Ï™ñ5¶Év ÄY†c°›À.æ6Æ&!x×€m®pÄØÆ‰ Ä ›Ý|6ùä 8²€m®õ¶Ác=žñÜxÎOO÷ûöñ^’jÿÐÛj½:J%½Ò{þ¾žO[¯T*U=ªúéÑ£R‰NMM0JS‰T:¦TbLgŒ‘V@[’%m£ÃÐ6-|<°È“³¶-|ؘuºi(¥†Ò–JÅrµBS¥ÙLŸ$+j¥¨é*ñ—`Q/§Aò¥ BE‹KCTŠŠ ž’ðBF‹T(+![S±²P±Ó@ç#Ô¹tïd,DdÜ%~nÌ'ó/6¬ó¯Ô;^Ìw ¬o,ˆq¹IZ€o4‘)<¥²¤¤Ré„¢LÏÍ*©DJ’•jy¦U.°p­ÚÇŸbŒˆh'kWFŠæ]þº-žÉê7Ìÿò± sÊ­Ui€ uÖÎòWƒKp¯Þ 0¦iU]W©l:™”Òé´V-‘à*F)%´Z­NMM–+%J©UÍXKlÆZ|¶¢rC^Ä™˜K!èwºl¡Ÿú;„Ì5û'ž¡km)#”àåÖ9ÂÌr?B«ÕÊÔäD¹T"Œš—Ì \Ÿ ¡$Ìå•J«ÕêÔäÙr¹dü¯>‹\Ù¨§²q,¯UKétŸB©¤éjB!åréÈoç''ff§çæŠétº¿?;44²jõÚlv jOœYGì© ;ó¡Œç åj÷ˆÝCŽ ó+˜` ‡/XCf ty r£µ+ed® ¥åRñðáS“gffÌ.<0<<²zõºìÀ aº”Їœš²f’]µfí@6gÓW×29r(?5QŸÉÈÊÕ.™Äê@ó”mÕj_eÓt5A%…1Ý+¼z͵…éâ¿ÿÞíÛ/–¨KÏÜ·ÿøÕ¿ýiI’}øËëÖ-ö,¨$?õ ÏíÛ·orrjffV×uJi__fhhpӦ矿eåª5Œéâ>J‰«å(;GP¢Â0ÆÄ¢ÃǰŸÀ³à 1ñ "Ø•ÔG‹I]IÍÿSO]ï¶Œ+c,Ðqô“`æ>$¼#Ì!”J§Nßýü³ž]ø‚­«W¯c\!¦T?ub÷nN&[V¯ZÈ„ye2~ò…Ý»8b²jÕZÆ´&Ä)”móÊU«™®óNÓÎiذñòçŸ?zìØ #„1¡!„èŒI”2Æn»íJWì¦k¸,<¸ï™¿Ú½{ÏâÅ‹¯þí×,[¾btd¬0?yüøîÝÏ=úèãg&&.¹äÒsÏÝée=nw8Êèp B,(i>1b!ÇUX‹·”®7õÔÇåÀµaa{fÔúK "z Þ¿ÏΧŸÚ½{ÏâÅK®¾æµË—­-ò'Na÷®ù.\8ÿ¼-^'‹RéÀ½ó™,¾úš×._¶bdt¬PÈŸø›_>¡¯Y³ùÆ^ŹVLž=ûÔ“¿|ñÅý×þÎï^¶í sÓØÈØØÈØ–-í?°ï{ß½¿ZÕG–-[Á˜‡;ìÿLˆ†ëz!n,.DˆÝ”‰ÙUMØ)f5íÕbây%¨ÛÈ|u”¹‡7]Ïç‚uP.ïHõ×á7 $Á/?ùÄ/^|qÿµ×þî¶mWÛŽŸ8±bùòÑѱ-[.:p`ßwÿí;F^±b¥3gJ¥³gÌLêt`ttlttË–­öïÿîwLFW,_¡×eR+ÉÙ³O>YŸ 3ÄdtldtË–­ìû,_¶ÂÕ7ÄÑóP6f­ÑþûkÊ–Y¶l§0çHë×mH$ÖŸ8‘0’QJ4Bt”Jú—¾r,‘XÿÙÛ_“ÉÈ^EUU}ï‹»_xaï[ßúÖK.½L×5ç¿ ë7¼ëÝÿíàƒû÷í-çø–J÷åÌâñ8“Tf ñ¾cÛ-™îO¦û9…I¦ú“©>óŸWFÉT_"™1Zp"™1V&’sÙ¿-1ç³"äyã'guãcÎç[,èS9ÇnBÏç‚?¾ó«ƒÿÎ ›™o«\(£àeÀ…hìqœxä´ZU÷ìyþ…ö¾õmo»ôÒmº®=ôÐ땯>ïüÍ×þ‡»víÒumýú ï~÷{8¸ßž¹¹9—LTuÏÞZ&^:°~Ãz3“Ù¢K&ªªíÝ»».Í‘Éú ïªe²wÎ-“ˆa»²:tH×UkI:´aýúš²íß[,9Ù)omíÚ‰Dé䩜qÊæ*´/iÄéÉÿü›ÉÓ§—]õêþ×½nÌûrA''Î<ýë§Ï=çœ Îѽƒ#£#cÛ·oßõÜî5kÖ¯Yë]J÷ BJsyB¨!Ä¥¹‚Ÿ¯¹°'Á yÄ!ˆ6! žœ=»hñ˜¦kW\a*›äå_*œà媕™DbýÙ³’¦•I¢ÖIU~û;ý—_žxÃŽòš5%NÜ“R©PȧR©ÁÁ!½^…gggûûû­k$IJ§SÕjEUUIòuŽÜš¤Åž69NgL!.§!Ʋ)¾¦4BRéri:•0¤ÖbcÁ)Á¦^Â’é,!¤Rš%„$ÓýõÊ;ë,h2Õg±±`zĦ4[ ›mÓëD2]­” 69‘L'éjµdíØ´¤ÜAÞþ¨pÒ螯Ñ&‰°°8F/Ô<Áf _4¾ @ Íçk]˜éšuÛÏ~öÿ¾þõolÛvÙðp­wK²Ù…«²$Ys12É éºöÒKG/¿ü•Ÿþô-7Ýôcûw~íÎ;ïyæ™'—.]"ÉÔ¦)ŸÏDÓÌïû«oýÎï¼öž?»·?ÛÓqæzáz‘Va‹²©„?úaÆô?ù“/?üð#?üè>û>pݼYæk¤©u? â¼#7åoÅ rÝu}Ù,)•«}éZÍ+ù÷~/Ñ߯æJ–º‹fêº^©TdI²ŽÕ˜ÛñÆÿôÀ¿—N§­é‡†UU¯ªÕT2)öTËg{:“+ †‹›¹\œ6Ó–KÓ>‰ë„ØQ&î1Måm°ÕG-˜éW+¥šwÌ\~1Lä˜í>Ý÷"i]A׿½RÃ:rø7€XÀcÇ¡¿Ä9DÓj]X’$[$á _øb.—ûï7ÜÚµ‡††TU¯V«r*eæ¢kl^(ÓõU+W|úSÿã‹_ü aìÆ¯¿ó®{î¼óžÏþö%‹Y™™¤RI3gÍZ“;ïºçλîùüçnÿàÞ{ï׿YËð£×[Ť2/&Jp½²Q³ú7ÞðáG~ôá‡ý×½æï¿®Þ,ƒµ%®—ŵ«Ie™|üËU¦VË!ìÔ”¼xP›•'ÔÁãr2KI_}”ÏÕb’$ äªÕJ¹TJ$æ¶L:õÃÿ×d"a^Óæõ]íëëK¥Róÿ©˜»P* wØê7Ç•J¥û…¯±¨‹ °:Ŷ³Ë(Óˆ°Nt¦3FÆ…ù‘Ù:fŒçOS뉦 Æ…Y¨¸0ÿŒPê» < @©‡øRjGÛ~¸E(õºÐ1LjlJ¶ëìž\2»tÑv‘r…dRœqelxdD×éìÜLn gÝ’L&l1ŠjµJ%y` 'QI÷¨û˦5:l8ÅÎüXÄ Llä ·²QκbN±­ãJT’ej¾gI)5ÞÁlôâÄÜ$*škM3üáFßãg¢;†9ˆ¼Ÿ7ÿÿ£Ý·0BcÃ#£ºNçfgrƒæ¶ááÜOú¿###Ö^lvaY’jÒl<$5°fò‘ë?099ùÍ¿üë?þøÇÞ÷ÞÿjæS­Vj:`õ¾™Q’]§³³3¹Á•+–ÿúÉÇ–,Ylîø‘ë?ð¶·þÇE‹Æt]«ª/1©«5 ßPlÊv÷=÷Þýµ{?{û§Þÿ¾÷üÅ7îûÒW¾Ê˜~ÃG>d­•$¦i®eQLî&Ä'§??±·2V]Þ?Z(VþùÅûgÕ¹o½.•`„LÊ+JË!:Ó‡W­Z}ðÐÁ­[¶òëuâÔñÅ‹–ärL׿‡áÖ©87•éJ÷ –æ¦!é¾!Bˆ±lÑx|g].ó¥b>ä\íÝ–™p“eFøØ¹ÕñL¹8)î2ã–ÍU2êR&’}†›N±K¯7žÔ0O/Q,j6^K£Î0ZegQgèó–‹ä(Œ5XæÖTu£ 8t`ëÖ‹Ìõsss¿÷Ž?øþ÷þÅTyÝuïÖtíÃ~¿ÎØWî¸û-oyó’%‹ë”ÍÃÔ¿ûÏTf~uâWÏ?K)ù‡}ûè±ÇVö/I.%„ÌU¨ÆõY™® d~kû«víznrjR÷¦X,>³óÙÍ[¶Ž Õ çf£â¼þZ%x^”Íád~(E:3hHp©˜7ÿ¦39B˜åÁ]-dÁûj_4âÅÆs<‹l¬Ì:‡U0Ï~ÈÜâųd~ˆ›ceŸuX…ksªVæˆe 1w¬›­€–!ÃnÃd™×^Fâî0_Û?Ý?gæ,| |÷d>æÏsz#ÓT^íV0gÇy7´/73ºð”¥ §ÓéïþÛ·“ɤKaL¯›ÉšÉäYkÇ?ÿüó\u`xd˜éuÏŽ™® ,”ä,OLJsób2ìùFuc7hõÊvvÅŠå¿üÅÏ®{Ï»Ì2|øCïûÅã?]´h¬X,>³s׿-[‡”ÍE/©¦©ekֹؔ¶?:üÐÍ?ûÔÖ±‹>ù[ôÆo¿-—Ê=úî$%ÊTJ«j4¥0/F’dIO=õÄ3;ŸºêªW ¸ŒØ-•KO=ùäyç_|þ¦–Á4„Ëh Ÿ…¨Ó&vˆèzÔÊÏlŒQFôZ\˜1ƈÆÜ¿>0ÊDJ\‰ôŽ ×eï=ÚJ(.ìÄf¢qaÚHXœú½ˆm$ñ¬%u ±3×°…ë·e'Êqíi}µçÆÜ^ *i3ºðÕW¿:[Zœ÷?JO>ùÄyç_|þ›3)ÅEû$Y73¹êÕÙœK&¥Ré©Z&¦“.™PIÖu²‰[IJåₘ$e—’D c’¤X”íUî…YP¶d‚2¦/H«¥©LNáÝS2š98y°¤–>úÐ.žþòoAaƒºNˆD4(2‘)ïQ”®k²¢\|ñ¥’$ýè‡l½hóêÕ«SÉ”‚,WÊãããÏ?¿ûÛ{ãÆ™TBÓªuö¢¼7_i£[ÜûeA‚ÏF½3²ýfåØ%½µ™Q·yzÍñÂóqa*ö‹m3—Ò³°¹ÅðŽ…6Vö=¢Èìîo~×?ädnOÅ©hÄ×­ž%fº’H\|É¥’$ýðl½hËšÕ«“©´ÑX*åò©ñSó]xS_&©kªK¶º¦(ó™üð­mY½zu*5¯åòøB&3i{&óQ%iˆÉÄ$=/&¥:1I'kbÂjÂE7Bt]••Ô¼²=ÈW¶tJÑ´*'äéð…ë9S<³ú›çËW,ÛöÐ;¨T¥lšéŒ^°ªSEr}²¼ÐP%¥3éÌ™Ó>ú𱣇u]Ïåæææd%±zõºË¯ø­‘áY&šZñõ„]×ñ̰®qÿUØ»ï{W„†)X˜ƒÒ°Vh>³eñ5|]á‡}‰ðÔ²œÐ4Âé£Ã#²"éó^”›ÏGd%©s3QdZçŠ9K¢¤tú‹‰Ví- …lç•íeNa$™iÕ '»T&g¨pÞ{¾vÞ·.š-ÏJÿùÍß¹xñ% 7„Q‰É”H‘¨ó~ªî-ÉŠ¬¤TUU«j±T,•J™L&•J+Š,IÑ«:ÿåºZbKÂM“cîhs¤’Fž1m\FÃ#‹¼ W²†J|ë‚ T–d¥ªjjµZß…Y’ÑŒ§OÌCŒEIRd9ᚉ$I„©¬N\ÅœPI‘•¤›˜(’D SÝ\é¸.Ó’œ•¤ªj®ÊÆ´Š²fUaRC£îP´VbY!DbŒIÕu]תºøó­ÖâVÊqŠÜˆ(7Á…¥¦ úŠ[«ëd—pFb…ù³ÇC©,B‰Î¨,1]×uÕöZoÎ’¤H’B(%:£’Ę¡šoaê¦$‘YR•˜Î$IÒ™¦kªîâG7ãlJrB’”PÆ•(Ó5M«z\ ìÑ$› Û.`¢÷ú4òžÖ.ZLB}×3b9ŽM‘ýJ›xN;ÁáÞñ]Â)/ˆÛ‡¾P¸½&ʬaËÆ{$ÈäôîkO猹‹js-Q½4WbædzŒ²ù7®ÌéÃkC± £æ¨ÓÚvÆ%„¥“©b¹D6ÔþPë´N–ÝŒÉö,¿)c,•JÚ²·&¡”°ºMóCkŒR2Bíy.±Vzc,8¡„š¨½ìÅÐÚlUn›7„¥pæ ãÐêŠ1o6f5ŽÕ¦u˜=¡ýjùí¥tá…Ejý%”QBUU¥õ[ÍQ*u+Œ¬¶•™©(M&UµjNe¾†hÌ!E²4žÃÏïin!„šJ%+• ±~­œÒ…¤ó/LÕ¡óïÌ?p®Í5—N§êªIÞcdV«Ì¥V-VË”X¦¼*UÊ †³ÂR~­«%ó»PBX:“±}{Ý‘ „É1*È6T†º;|¦™™^û,‡Ýk¢ö”<÷J÷÷]ó¡4ˆïN¹Î+õõEÝ~Ó@®-%B¯¿±[ç9dƒs7Ì \8]æ2^F|äüü ·0Aç-dâî;{ßúK„Æ6#¨Äe—Ê8`æ\ŒY‹©³¶n?:oæž`¼‚9äØ¿BÞŠLÅ{ f<÷×´hDm©ƒâà‘|º!tÂpSQ° g‚ŸˆjDƒ?UŒªý4ô1î8T¸©N±Ë‡yó¯™ÜíƒÁÂ/®{‰ŽyÈ1·`¢eaE ´LÄ!uíôu/ ÇûbH›E$BNŽØ`ÚГÅ#¾ÁKÄ.X7éoóU¸m´˜x}¼½NŽ©øy‹$“c✆!´"eQ]öõ„5ºiªRYStÁwÐSÜÊŸøÆéüv°þš*Ìx]®Åá\ãðrÖA,¢þ¢F—úؾ³¹îJ£kn1†?Xt;5ø~tuc‘†•¨Gõ—µÐnW-ösãã0rpY@”Ãé²HÛm8‹v‰³x2ˆD BÞÔw²øv¼ÿˉH°ÖµïH´8¢Óêñåqa9¦îÏh¸aÓzY<ô@Ã~ÄVÕ Ã<Ìë²WNX´âÀ¢êz,:qôßÒÈÛt¶ˆPUn3ým_8r-Ž(´R÷qqü0ÒËÛÍ>q¡°"×7£À_³g^ºÌ;4ñ}µoÚê†ûN,ÚÎd JcÞ¡þ†­x[êo»©°·“ 3úF(Ǧ@Qí¤J⣲žŠ¿(sn>Å ¶mÚ®]"âÀCtõjHvIØÑÆ)¾~”¶»?k7vÓbbý¢kPq"ªaõ©Šˆ~¡È$p<Ø9j6Þ ˜Ÿ¦ÒˆûT›‡$b¨ c±¬É‹›vˆÐùmßf§tBƒo*ŒHyÛW|;æžKi E1UU«ÕjÓËÌ›%6ž[E‹íCÁ Nc«0¦|‹Ñ}?¸™Å[°ØÜÞŽßD"¡( 븄Tájµ:7WÌæ†G‡– Ûz™™™B¡p¶¯/“H$š¤ÂªªNNV¯ÙÀ˜^)ϵ¹h7‚FtÐ.œ¢s|㨦ýa-)}Ü\ÄO_____ÿÑ—Ž )J`Q £Â…Ba 7ÒܰšÐö)J4YÓˆ3„F“Èßå QÄlÛNóX»$*e5™êË  ù‘‘‘ »KAwÐ4mj*?<<¬r?ïÜž]……é ¡öÏ•ÅTKܙǿÖýc¾)bªh³Z‹X½ÂV]4yœ¶lRkŒµŸ¶U-LMå5M‹ÝÖu½X*ªjµsÇ|rÛø~A/ú4¾êzFèu¸F]ÖI®3kEN¬³jUîÝðž;#ªZ-–Šº®Ë²{D¢[ï%ÛB‘cåàº+ÒEhu·ÎÓ+´5J½Az:Ê…"Ç/ÊÍ8KÁ§I Ñ¥h{·€ö–½Vœu ]Ú·µ)0FÔ¾nü®¬ÿ÷†Ú¤ÚË”éËYç ¾Y˜»pgúrÆVçBÇÉ[»–¨'>`ÕZ_8œÅX·X8ì·6ÛªºK3m7‘(ÎåëÕ3Ï•àAKOKçòu0·¯é¿ “ªÃÚ³4¯ÒÁj$äIn‡=ñgÌó_PœËgú­škü3ÚþZÚvôJïÌÖ+×”8_±ô¢ìIÍô…AøOfvXLÓ±[áA[Ý^cÙÐh§³l®´muMïÌÖ+¯”=îÕÂám’ ›­‚b~шÎå 2Ѻ¶‰>ò3 Æz¤3@y#ëd ¾pžÎEØz=^uléèW{#¤¯ÈUìúú‡ZR´ZFø/K£åÆï ƒ&9ËÎÙhW‡/<ÄÎ*Ás³Sæ¦FüP›²‹gUºÈÏ…« îþ.ÀÕe%îhuöÕA¯5ÎþV×d^ê/X¼ÎV[ÒÒyÚT¸£: åog2Œá-h@\¨0h™4sÔݵR+ª¥\¨0ˆ·'Rß$,ª/r€æë,¤* º[ ‰wb»¼ÖOHm7«0F²ö Ð@½›…j ´7L`Bg{±{ÂF#"¤¡ç ]m$È+hØÔV,/Ï¡mA¬=`Öã6½Ó_ #|a‘ •à f€ TT Â ÂCv„³Òu+_¯”â‰C8tV æ&h¨¶"DQÛ³vš_;T³ÁG؉Z{ê;¨³¸¢DnŽÙ™³Æ_×^ëC¥ ûp„¥jà ‚n:‰íÙ‰à 7ûJh\ŠmÞŸó²(™×s³mµ&°¥q­—³lÎÙöõ* §Ì¾¹ùº"; "œù»L¤¸Ö÷T²*g+çì‡>¡¶”|3ò æÚÞBw‘³ ~¬ÆyïúÂá.ÅÆ²«4ÿ ÏÏ͹ՙ¹éà7”µ IDATØÄ¿ð*¤mAо¹ñí ¾£HÊ ÜE°î¾)9uw=•A­*xÜh-˜ÒvD›“Îß%Dà7µk¤‘÷®/lÅ0zKnÜij —²‘¢rö ‘-‘ }Ó=Dƒa¨¨Î­Zûªï©ŒéüF{è …lðDGØïn]EQ[ø8¥Î|%.¾V¥nše[rÐfF!ã®ï!‚Þ'FUæVÙöiQmr‡îëw7^æîˆE˜ª}DÂz³(q3…¸Ëngš\;ñÛó&—¹Ug¶MZT›4fg18OìC”9\뤈†o‘ DLlÉAÛÄ‘ šI|#Ì"ò.‘`}ómØ|S´¤Ù:h× v '¡-{:çu+ÇQs~\ÕúÜÀë2À?hwÇ‚)·€WJ§ ñmâNŠX@Äæ,ô¹4N„‰pç…cÆ@§#’ê8w·=+r>¤ z¬•j¨¦Vçf˜»ZU÷ïßñ¥W2­¢éíM í”êcônÇG$…Jʳ;ŸÜ´iS"À»íëÁÌ @K&ènzY†:¨î¸Z@…@…*  SUØœeƒ?×FNÞ÷(c›eœÓš´-­-aÜcN#o!Qµížš"µgs–¸Þ#xBífؤ×Îx8UêÍ)R{¶kÄ‘àOmG›eÑk«àlx®køî'ŸÐWò@¥ 1éb#¶ TB¾ ƒV*Pα6'~pjR|FSר¡S¤ŠO¶É/jP£‘€± vöNõ…E.wQÍæçê>¸Î¼tÞ?'%ª©ö‚ÎRH‚Oº¡mCW9D¥Â½ÕS•9ñš¨S¤Tæ¾̰…S¤†XéUß@F‹°¡vƒ/l{r& qÒà´Í¹Š|‚ÊHvq×1œm›i%ÎëÂæ1Ä{âʨšS¬õ w vž"5*ƨ£`QùM¨…¾°9µm¾ uâsS4ç(­š}&ª,›p'سÏ|¼am¼Iû†•ZÒaÛSu™‚6×øe&ò»ñ£0,ªYñ¹B—žAóÛy#Mšghš>vÄÙ—ò,øÏ5½{³3ýÙ‘Ù™ G2⶯øJæ·U(ÏùÖ ²ûÂÖþìˆmcï>¥õ;ŠKJg2Ë&ñ*/¬qî.x\2øæãZH—¶äÝ®DªÊ‚-$DÛcwJî{²˜x xr£5šÏÙç6¡ÿ…Ð…ˆDóïëGgg&<®]ýÙQs9¢ë¡KžæJÛVs9h>Ö•œ 6XZß•Î|øU/OÐã6X©@… dÌUöÍÐ<ã"§^°Žâp-°­$â¦lÒ+=¹-n1‰cfË囟ÙòL«hºÚ¸ìÐV4¹­vJ×h«r¶¡Ñ$I¡RâÙO„˜ÙRA; ù"bõ×`7Z3Tí tMh®Ø#Z^æ.–ÌæPa€ h 3Vû!ü¡$@Š6ñÌû³£Æ?[çBGÐLÕe­/Ü¡Žj„¡â(6y”pë‚•ìÀ(!dfzš›±Ò\oüìÏŽZš[]w 'ÃìÀèÌô„µÀœ*ØgÝ×ê››Dò7SÆa@@“}[6®¥/l çJsÙ)UÎÝEVúæÃ)ƒWæ®U05Ζ³W\ ⛄t±<³I•`Êp DvñÊ„“y$r:ÿ 興Dd3[zùȶš7ò^ W1ŒHt"A hÿF, ~˜ÂU/¼¶ºÞe z…¾‰Ž“àthD"ðœlâÞ™¸Cç«&®bÔ¸SÙL3„{ €qrƒ©h”qa#,àúËÕ?õÝÅæ;W:óq}BåUÁ*ꩈòóÊ€Pd:ª©ÕÙ™3â;T«êþý.¹ìº`fKÀ5:;ª )’œxæé'6mÚlfËìh#qa¡è¨WÞ1‹“{ ‹!Á»sMefú Œ¨ó£a€ TT Â Â€®E©ŽÂ"? ¾0´‹/ì.Ï\ gõ ˆøô¿Và @+ @+Q˜å‹ëç ~ƒ/ mâ {É3GÂk©ðp¬òh(d@a„/ ­* Ý¢ÂÙÜ"ë¿@»ðÓx-t=jÔPQÙ³ÝNGT剣^!òßÅ5¥xg Ý̺ãÔ´°IGüݹ™ÂËÖšXºV•ŸÀ™§×³€Ð] ´¶+I±ÖÄzI±¹½Æ‚õ¯õ§mG¯ô®Þ´k&¾~7'Ÿp»{UÙéPxíÈ¿D5‘kVüd"U·¿γ&R×½\}™ í$èmœøÉõ=•œê„h-^MÄ’Ñ6›eÑäûH ÛÄÈï'âõ…E®ÉƲ¡ÑÎkŽ¹Ò¶Õ5½3[¯L¼RÍG|wþ.‚e}ñã—G$™`•m ]0C~#1ò,-ATíÄ·—†;¹t“ ­E¤%x%pÚ¿ñfã[~kor‚FŽ)}{ùÂ-¼àg"âQs×£˜—ÖСk"¹ V!´ÙÅwôMië± Äù jçéh¤ñÄq"â°äªÔÈÙì#!ÎlsâÂ͘ٲµÏp¬á/Ï%tù½2¼±mç¨b×”'òºÄtrï”Û<Ȳ¬Ù ÒÏŽÄC ø×^÷\mk–v>eQ•Gð\D[˜õÝ¥#µáy`ƒ &üø®ÌæÍN;7es‹Ü¦Çtæ@‚,ø¬´ÔµðÖdüÌmYq2wÉs¦pZÜ,õ…aμsó©BP3:«ÄbÄû¼‹•ˆ5!¯¬)-iÿfa¼N‡ÇAÅNB·–pæmB³ñÚ×ãÌ;‰b}„‰_Áô‚ ˜ëÔ—ûÂÙÜbË%ñ´uÙÜd]"ÿpÙòSš[­ÉBìnÝê›g#;5Tˆd!,æ[g² V€&äÕN|-˜R¤0ü¬ø+m[êDÕZ› ¿.®­%D“‹©³ÄUÕÊìt€;ˆjUÝàÀ¥—m׫EMWq7ˆÊ‡k‰Fr\INHrjçÓ¿Ø´qc"À»íX„7˜ âÂÐZhm8* ð…*  ·T8ô,ξÙÜbã_ㇰå"Oߔ֢†+LËODó­Ú)M4Ž2Øšwˆò îÛ’ó´;tJÇ•ØVâ\Ç0—øÂö-¢ØVÀ¾–‰¤Á´­{¼;H‘[ÓvM3–­WW×K4/Á«7g//?ÂyP¯ ^‰ø%ÝÕI·Ù„__Ëæ®$¶]ïEøGtµ­óp¶ƒŠû†¾%á.h>áŽÂ1W vÈÏãärz¿µs,àš3¿™‰7ÂpÚD;Û¶^Ð\—­ â{ ^ä9éCgëš¹³:Îñ+hÍ0hÙø÷ü•ÄV)ߺ{•‡DN]5ßLDŠ"Ÿ]@p«ïI÷µ¿1ðÔâu ݃Z8hyº3.ìú2b¸»'ÓL¼Ýë5k Ã÷d„.|nã8®­‹Æ]©–Ù%ľV$Ä…?î–ß©tÝ¥Á5RVuÆ`¾0›ÿp3¥´%…÷*¾C4¹‚M+[T³yÊ<¦Ú…¨KT%ip~‰ËÓªVÝàqìÑ>صSp0U—´ÉÓ¹@•·Þ>4Ó¹ˆ©‚¶¢¦•$Ö‡™ÖÌã³| ºDhgA÷Ù·Tû´-VÇ6*/>„ŸÞ & ñ›ÖpWÝÀ™ ›[Ì™!.›[<]8mþœ.œ6Ó‹OiÝÅš‘³u¹‘™"Å+è» Ÿ·ÑÀĭî$ô$¡eàgί»`¬&r=œˆA|íl]Ã)³k[ò2‚`>$¬­øÕd1MxêfßÊ 6 ßÆì›€ß)Ì•ü†Ä‚ÎöDBcñ…§ §潌i·KŠ™À–’³×@nñtÃW'ß‚‰ R’ôÝE¤0ÖL89x•ÄÈ­A+¹îÎ?bs¬ÊÉD¼R¾5âoÌÇ™,´ÁU3PkÔ̓î.ž¹¯•‚Ú¼qhªª•@N¸1³åeÛ^©afKÐt"¹*£Ì¨lôQ9!Ë©§ýxЙ-³¹Å :6hÿŽmup`Ðe@…A»ÓéÊÛSW\&ÃøÑ0@…*  kTØ|¢â;â çŽD뀃1FÂ|‘£Ñy$œ£µ­ãù££½†÷“ù‘Õt@·`ª.‹r¤šõ…(ÎVâ6´Ûu+?Cè_Ø)Ï| gÞé!dº0>[bK6]¯Oϼˆsßú   }[V¯aT8^rKp® 5*<[2]‡€•XÆHðE6„Cµð…ý™Þ.,„ØJjdÏ]¬>5t… G6P­N@m â[]3ä'€NÂ2þ ­çT³ºÌ_@÷úÂNyæK8 ’¾Ï:?¬lÐBß6Ì@5ãÌó?(L Ím«/ €Öè°â%Ͼ»2 À¡A…1ÜÌ¢ÑaÌò­* ­$âñ§NŸ9züt"•™÷Ì)þâ/þâo×üUË¥•Ë-]²¨MUxüå‰Sg ‰D"]Ë•â/þâ/þvÓß’ž851-Itñ¢±vTáã§Î*ŠrþyçãЭìÙ¿ÿØ©‰U8ʸ°¬(ù~œ$@3œëW”d„FütNQœ$@£( ô7Œ‘€V€–:×õozDéfŽŽ:WNLLÀè^æ²ǹ¸6-+™»p›w.5óHD‹Ù|Ñ””ËåT*å+ÄÀËVVCùÚMаÎ48À·)F‘`ÂÿˆÇJQÆX6ÿÚ¼›Ñyºò\–J¥}ûö¹ª€W•qš®+Íèe+ŽÝlµ¶µ4/빦ädËi½ 7º­ *r$Ôô…Y‹'¶äßv¥{˜N§7oÞœJ¥ÊårÐ`…벡]iÆÆmeh´³ú^a×ô"§Žswc6ŨÍ}:gú,Ö&Û›m·R©¸ÊŠ«;ìe"›vôš­om’ èñ¦ØØât»ûÖfÿþý[¶lá\«œ¾Xoš‘o«l< ­›bè/µ!Ýíb4x—ÝSf j+ø§ 3"¤E WG/+Ýê×ðom¼Ó…¶FG›Ñ÷6Ð+& §tLD¢µ_3Ç)ÝÙÝ·66#˜&W–î0£—­¬vp>[k¼Ö®ìàk#"(8Æ]˜PµZ.äO‰«pµª8pð²+^­Vç4­jÝôìž#‹GÇFº¿îÍ6ÍK§Óq\Wa+bmŠÇO=s¶°åü5ÖM²œ•ÌÓO=ºqã†DÂ?Òkªpnpi˜¯6òpzj¶]ÚßµÞVg¥\-Ðãq•ôRpˆ,è_ØlÐίeNÊâÜTÐ|¬»t‹gˆ©³¦¾æ6¬d½PqRöÔ½hŽÿfßÖÌOàÜꕾ£»µð¶Š˜—«àö¸FX¯U1çå qa Ìî ·vnKÏžÀwIÐàóŠßˆß.¸&3[£Í› qþÅ#kbÖŒnæå’X·â”Aß™„ à :xjÉ ©ãé\˜Ï8ê‘ïÒ;ìŒq÷ìå'D҆õ|îáÆ 4 Í%Ti‚pXÛ½oœÁ7"a}ì&x‡Ø­¯œæí© ’ï S„™‹·[‚¢4¡ÃZÃyBåµÕõiUÇ=Â7ǘ½#¾íJüù­à³_ˆ/ˆ ¼ÁÜI·áºÓnÇn/Ý ³,À€VûÂNyæK8óNÏùþÇ`V@·i¥"¯Y:pÞºa×bÖï…ûú'h Û/ß #Ðhšvøè‰½‡'û“qúÂqpû¯%„üÅÿy°‘ŒÝÍ…®aºX#Û«.YõðΣ^?]×ô^ˆÜ2F†0x Q²jùÒgŸñ‚5Ëp¼*­nv™BTU;ç×\¾î§O¶è§O¾ê’U?}êpov'W›¼æòu¦n&ŠäXÆzÜà]C"-«š}¬ÃÓ FI0Æ«€ÔGD /Øb«Û|[§³l]ÃÉÄ&Í^¹µµ Wë„àÚí›!>¾ÿÚí›||¿±ÆöÓHiü´îe®4~¾æòufµªš9˜y:ÞY˜¶ò²g%Ç&Vã˜Æ·ÙÖ–Òvšl³&ëhƒBˆfÑK›úÙÅP@AÍ…}a›n Fl ®™¸jz'Æ.TM3—w¼ú¼<²×ìêæ¦k·oúÁ#{UM3˜+­{™+ðÈ^c¥¹‹ªi?xd¯±Õ¹¦;ƒÕV^và¯tµ‰±`5‹¹lœÛA­§ÏX¶¦7316u´ÁAM…õˆ•ÍÍsCöˆ„à( Nú`ŽV†ÑŽ YØnŠŸßûÙóo¾f³¹é{?{Þ\¶¦7;Wº.˜Ë®é;4˜Ã7ŽuŸÒš›5¥×²‘lÇ«Ï3N–¹Éõ(Ýap@Ñ4É¢r^ÓùˆŽ’˜›+>ýô³× /ljVR­¿Eµþ4—­+ß|Ífg‚·]{1g/sá;>óæk6çÁg\Òé¦sµžQe³úΔ®6ñ=#Nóºž,þJЙ×~9ÂÜúú2—]vq&“ ·/Ìñ¤þ鿲%xçŽmæÊwîØÆqÄŒ”o»öbg&]`:/뙕µVœoß<æôÇá w¾/±`ÑäöUáî—æÒÉ-Eóç¼éJë&gðÑHó÷ßBd/çî®v¨éœvðÚ*n¯ä\þûï?ñÎÛþþûO˜›ŒŸ¾‡ªÂz”$ê#ñO$aŽ^Oi5aýiÍ­£•ÚúÐüoïÌPÒ¿½ÿ±ÿöÖWš›Ì3±l¬÷ÚëÞtåßÞÿ˜±ÕØdìâ\Ó‰v³ÖÚfÁ•®6០‘egÁÌmpPSa5º‹(#}™Ìe—^œÉd¨Z-ç'‹«pUU8tùWWË3ªV±nznïÑE#»—q¶I§R®ëß÷Ž«ÿç?ÿ¿Há•U„‡è8b2/?Û^6x×0:Ô|üìÖu©3“3›Ï]iÝ$ËÉD²ÿWO=¼qãú„"c`5wxéŠ Á¿xÄê_™ ÝV/xFú/¯5—ïû‡ŸX75Â}ÿð“÷½ãêûþá'Ö•ú/¯ð‡«MBà4£—I{ÜàÝ"KUµ*K”§–A&’˜›+>½óÙkF–×ûÂÔ|á+¯®–¾ð‹ð…ƒ±h$ #Ðè:›+•—ög“¥3“3›Ï©÷…•d"Ùÿ«'…}aB!ÅbiÉòõ#ÑJ^>;#ÐÈ]³,wÁú‘—Žˆ*ÏL&MyƒÙ‹7]µ' ¸,„-0Ë;´’po0ã©xÉc0…„/ ­* ­Daø34_ÚÅö’gŽ„ãÙxÉcP…„/ ­* Pa€ h>ÑÏ#ÀV¯Âah74­ £Ny &Œ |k@£"Œ§sÐR Â¨0¨0@…@… Pꇷáå9h ¾0´‹/ì.Ï\ ç¼ÁŒ÷ê½àÆ2•¡Þ`F<š/äˆH@+ üõOkbæ·ºÒeÞ¾-¾þ T ÂЫà‹GÐtØÂ|ah¹/ì”g¾„3ïô$è76²—6j*Œ4_Ë ÁÓ9h-áæ‘à¥ÁD¾0´¨0@…* * PaPa€ ˆ¼Á Íga:øÂÐj_Øâ šT ß™À™µþT ß`€vò…½´ž0ˆxÂ$'ì¢Âx8-RqD$ •„úâ/… (’ð… •@…* PaÍóH@óað… |ayæJ8F«€—<SHøÂÐJ Â¨0¨0@…@…* * PaPa€ € TT Â Â¨0¨0@…@…* * PaPa€ TT Â Â€îFilw&°º æ¦u᥾0´¨0@…* * PaPa€ € TT Â Â¨0¨0@…@…* * PaPa€ € TT Â¨0¨0@…@…* * PaPa€ »‘Éô\6t1Ós™êf¨D˜×ÒEÇOÏìÞwh ?SèB ž-U*ՕˆÚT…sY*Ñ—Ïξ<1³è>Ò)eõò¡l¦MU˜’Ëöç²ý8U žÎT Â Â€^@ ÚƘªjÕjµÓ+’H$E¦”öN•»ØD½y6Åk îªUµXª -Y´ØxQ‡B;ôo¡ŸšϤ“‰„Ò#UîbõäÙ$‚µ† wªªåó3ë7\Ș^.—;½:ýýÙþþÇ^P¹ªÜÅ&êͳ)Rk¨pW1===<²”1½Tꆬªj:^<]8=<<Ô UîbõæÙ©u8ðt®Ñ4}jª022V­ªÝ`Y45UÐ4½GªÜÅ&êͳɯ5|á®B×õR©T­Vu]gŒù¦Ïår…B¿¦å0ƪÕj©TÒu]–¥N©r3-­‰|ËïºI°¾‘ŸMÛšvk½"µ†/ÜëXÛ±³M£Ê=Û* …BG§POOP¨pOtEŽxÙÒøŠ±àµK§WÙ«j"ÙZÓž&âT­#°«måo¼ 4 D$ÚÁ{Ï|>ŸËåòùüàà ñ×ØÑøi¶9cÙš'cÌ5WÌe3¥ë.¡+ÒU6·ýS<[ÛrËMd¦4Óó«f]Ùä¢:“qÌn³­Ñ¬+#iöÔ¾0ð¯ÁÁA[K —O×W™ŸØ–­ó­5‘Y³l¾Ukaëñ-‰5H±£jöð…AŒ}UF[{¥´]<šP‹6±T¸ÝÃn蜽îšmi¦¦¦|ï ]s¿S¯HgU9h1ši"³ŽCCC"µhùÙ4Ê<88h=;®gŠ Ф 4-"\…)U”„®«L×u]‡PÆÎt¶íÔÔÔÐеa…H)˜C ”Ϊ9[N{VyhhH<[ñÂ4ÁD¶’8ËæZ5ßúÆz6cjrÑ6{¯Z뺮몢$Hð‰&ÂøÂ²Lc”1J@,PBhXW¶099iv­ÉÉIW“ƶÕu/ç.œRKíÚ¿Ê“““ÃÃÃâÙ .7ÇD®>¿jÖ•­:›fy&''9fçÛ¹ñ6´ÖL'„0Y#ŠaTX¢!:cº1Ɉ#(µÑ^]lË^+]ÓønåïÅ­ë”*ÊVd¹9&rQÐbÍ/*¿´¶FÛ‚ÕšjŒé 3Þ!° SB%IÒuF(aá˜4˜u­mª9k×ÅUîbõæÙt¯5#ºÎ$I¢¤) *Q¦ëLg!‰˜"”(²ô›#‡V­^£ªíø&þÙ³gƒ>©H$”#G)²D©Kè¬ý«Ü"4ÑÈȈëY딢F^àøkÍÓ©Ô´ˆ„$Â%º†§sq$2™ôž=ÏŽ-ʤ3ª¦uz…Ež™™Ù»g׺µ+ #.Ïu»®Ê1›(­r{_¬Cb;¨¨M«µ$Ƙ$…™ñ2ÔÓ9‰2¦Æ(œáØÎ÷ÐÐàÐTá¡ÿïyçoY»v}§×èÅ÷ïÝûüò¥‹††]çfí¾*w±‰zólòkÍ#„É¡|aZ­–&ÏßA×Ù™3«×nÒª%Më¹ÏÒ4 ]× …é'Nyzz¦Ó«30]ºdÑòåKs¹I’z¡Ê]l¢Þ<›üZK’"'ÒGs`llT ¢ÅãkûÂŒ1*IŒéŒé c$bƒJ478H&FÇFŠÅb§W'“Éd³ý™LšRêÕlº¬Ê]l¢Þ<›>µ¦ŒC ø€.ôxaBA<"^!¦´¿/Ó—IwÁãfJ‰Hüª›ªÜÅ&êͳɯ5c„1½Iã…)%•ñÂð„›$Æ]q¹c½Wå.6QožM^­™Î‘¨¢²a}a]'” ‘#2Ð4_˜B©dŒQÆ»sPaÆhsÞ3î0jᆀé2ÑÃ_BÄ…©,˺®ÂæTB(•u]—e™6gN5J)%Lg„à­ „F k’/Læg¶$”àéÔ4Wb¹Ü `IDAT˜±¦T£”Ê:Ó cx::Ó) ‘óDO’¨Ç¤¢Ð›Î0#„I¡æ‘£Â”bja° q¸¸°Ó@ T Â ÂÐ[(¡÷dŒišV©tÆç6’É„àË…U¯î®Z|¦Í¡ë›\$ /¤ «ªZ*©#c+—w„™òùɳgŽeÒŠ¢(ÝT¯î®ZL¦Í¡š\$ /ðwç!ª¦åós›Î½„1]U;ã[¹Š"BìÛ98دÈr×Ô«»«‡)@“$Ølrº¦vûÇÚY"T ×ðÂ|wŽ2]˜]´Z×µr¹ÒA—åt*52ºrêìÑááÁ®©WwW-”)’|S€æºÉU¦§OüüçÓûö1Uͬ\¹ìꫳ+W¶}Ã#ét*tà ütNÓô|¾0:º¸ã\ªªªŽŽ-Êç šÛ•¹sëÕÝU n c ÐÂ5¹êÜÜ/o»íÛçœsðk_cGŽHãããÿò/ßß¶íÇï~÷ôÑ£íÞðªjè†âÌz©\®V«º®3±÷˜s¹œ¹\(Ze&ÆXµª–ÊeÆtçå'h½r¹\Tui<«È«f[ßY‹ÐŒ"¦Íêkµ&Ç„§:˜{ùåÞò–ôÙ³¯½í¶ìêÕæs® ßñŽÝý×ßÕ«^ûï,Ù¶­«¾áÅþÃÖÍ"ïu lç¨ùgí¾ûî#„|ðƒ4Ÿ;3ƾùÍo*ŠòÁ~'¨s䘉±¦ª?~×»2/½tÑG¯—I¹rìa”hD׈ªi›ÞøFúÿøÓ·¿ý>š]¾\Äè,Íi¥³›Çé‚YWZÓ´?®æ¬ì¸~%Xg2cÙyÆ]]ï›o¾ùæ›o¾÷Þ{Í•wß}÷-·Üò‰O|bffêÖeìý»¿+?þøš+/)J3Kßõži2S)ì¯æLŒZòû¿_J¥]xáÀä䓟ù GÓ µ5è¬žÕ 3lÉòù¼±Ëåòóär9[JcÙ™¦Aâ«—W¥|Wͥͫf®ñ­ '™ÑO¬+óù÷9BÈM7ÝÔß߉)@3ÝaªªîÿÖ·†e)1TªŽg–¯ÚôáOkå—Î^Ó'6nLo¹ ìèÈÀÀÉý×¹‰ ×LŒ¦eÕkó&3°í+¸2¦†{D"ŸÏZÚl)Í¿]Ó ùu±Ù§M°Iðtø&̇1vÇwLNN>ðÀ>ùÉ[Ž;öõ¯ß;::²cÇŽ¯|å+ÕjÒÖMÌ=[|î¹%i&ùuñ‘=‘Ê+oüÒe·|czüÄðÆó÷>ðÝ}ŸûÀØé¢V˜ÉèdüñÇ×½éMáZ£UXÌeñ•ñ¡4Y†lÕ *JÇÕ‚ÓëÒ`•UU½ï¾¿ü£?üØÞ½{~ôÃ߸aÝ\x×Ý÷@‚»P…OžL1¦¤¥å±¾òÉ'¿üû“¯ÿøgÓ¹áçöய¼}ËXµ\&•~’š&¥'Zå0µ£ :áCCCSSS¶m뇆†Ì¬Ì;ã§ußo6w¿¹pMf])RÓ6¯ÚÔÔÔàà õ,¸žg¶¾É8ûè:¹ã«w~é‹_8sæåÅ‹—|òS·ŠŒñ@D¢ Ã>‰d9IÕˆ®U#SêÈ¥×¾ÙØ²îâmûÖoaŧ“%"Ÿ$‰i"+ŠW†œqZ ÑÂÖêFˆ4ªF^ë‡ò Y»®SµÉ:×*tn½œ%¶‚Œ1EI|üo¾öw_ÿGÿ„$ÉPØ®$³bE¢¯¯\$Å)rxbäÊÛZ¹ùÒ—~yÿÓKÿðÈëï~èôà¥Õ4©V %${î¹t7W¦æ±i‘IÇû“““ÖºMNN{™ë'''‡‡‡]½$ë¾æŽÃÃÓ““® Mvm§yrrÒµÀ®5u]Ù>U³&3Šj÷‚¶5Ö•^¾°™¹MÓúû³oyËÛ*•Š®ëAK:‚T&3pÕUåýèÌ1²ì÷?¸äüKOþâþSÿøŸûsÕýßV7½ýÎÍïýÒ®ÿC¥@K—]r‰—’Ún:ÕÓ7¥xV-Paq¼ú˜¹ÞšÀ–ع¯s/j~¥Äk*²ÐÚªqNÈ."u÷B×õR©©ê∄¢(K®¿þä\šPÜó§JñìäSÿkp¬Jt2ó³»öOŒŸúÉ¿—^$²F]}2•òÊÐëÚo^›ù>„ˆÓåSOZ ¥tÙ•WNô£Ê=÷äO²}öW™eDÍ“D–PJNÿúïfv“¾YÒÍ5+ßû^I’‚úF!|ˆ&;@aTX–¤#G­^½FUÕ:Ù E>rä ì};´^Ý]µÀ¦H(|S€v J¤Rk?þñ#„¤þâ/òÕ™IRH"©JÒ2JHbÇŽå_ûZ_WV?° S*õõ÷í}áÙ±±EétºSæLQyfvvÏ Ï®[»‚R©kêEQd©[«Æ33S€¦ôŸÍf7Ü|óÑW½*÷7“ýå/µ³g!J_Ÿ¼íâÔ»Þµì oÈf³¤ãþ‰„úòf~á™Ù¹#GŽž:5qþ­]·¾#ÚÄáC÷îyvéÒ±µkWeûûº¦^Ý]µ8Lš€¦é§ÆOÿæ7'_uÕë]øu]/‹“““³DU3‹ õ÷÷Ëí=[´¢HssÅG~hÝÚK—,–åZ<<º&Œ ëº^(Ìœ8yj|üLaº3^êÏ d—,[¾li.—õ -ub½º»jq˜4‡¹ð7~ù©ÂÆ}A±Xš™™-;ãùu&“Îfû3™4ÿÛPW¯î®ZL¦M G.ü_þë°Ù±;et&¥D¼gvÖœ0]\µXMâ¦.ü_þC~ñÈÒ軳Íwk½º»j  [__&“Iwý›4 ^þ1^€ +Á € TT Â Â€ndn®€–IðλŠÅ"ÕuýÌø>XšL±XZµv«T*ÍÂÐ|_8“I‹3ÒÌLÐ4,Í_b†#*dvvZJ$3333Å2¾±KðλJ¥R__æ‚Í[uUM¦³tròeF¤üäé©©—5U_½j),1Q,–^>3%+ÒÐÐâá±e©dúÿmzÿ=¹mIEND®B`‚veusz-1.15/Documents/manimages/defaultstyles.png0000644002344000001440000007246311734662204022113 0ustar jssusers00000000000000‰PNG  IHDRμØnÒ-sRGB®Îé pHYsÄÄ•+tIMEÚqántEXtCommentCreated with GIMPW IDATxÚì]g€ÜÄÙ~GeÛm»êr¶Ïö{Álc:I1 „N $p(ÁJB3 ئ…N€ä ð™NÂGMÆbv ؾ³ÏƸ—+»w·MÒÌ÷C·:í®¤Õjµ{»wó°¬çF£i4óì3¯fÞA]]!‰Ç»\.„B0!ŠTüœ”P©ƒJôÊ borR²W^êµKÊ·RB2ÆbQAÂÞÉI„T¸\ ˉ‰¨„EȳȦrÉårÈìµ" w„©Ë@fž„uÂB™Ad*+d¦dâBLÞ9²ÌâÈB‹#Æ]Ë$#s'“ìù“T‘ŽXgsZŠ˜»ïœxŒäR­†II÷Nro+öÐ8B,Ã9.^{b1ÎÁñ Ë ñî~³å bý§‡bšmí»\¢Ë¶Ùo%%…nòÔõ¶F”µ½"í&LLÕ1±|P‡]óæYÇ _råÕÍ)CäX×’$`,òN¯ƒ—Ë% 1Ó ÏÊF!A:;Ûã‰8BH‘‰¹u’c¿±ª94*¯ØM’˜ëéľ’LJ “"‚ôi™ìσèÉ•¬Ÿœ2̼º´”vð¬¯ß„°#½™|µ5ÔÙ–šIÕˆQi™SÎúĈ¾FŽ’éËàt ‹jÔX0ì§1ûöîþòKƒL¦9ư³„˜}ûölør½cŒ9š\=k‚¾¦Ž9Š`lTõs ±é°/¾Ø±sg7 Ä`‚ÄB/~ ¡QW,:ÎgÙÖÖ–uk?ùòËuuuÇ{ü°áõÕU5á®Ðž]»¾üòóU«>:ØÖ6sæ¬ ¦&iÀsK¡õq*Ûš´[æÅ¶D—õPnfѾþjh·“„Ûw€>w Qš¦,®rµÇîbmÌIr9˜N•ƒ±ÎŠÍÆ*ÏnÞ¼qígk¾ürc]ÝcŽûÆðá#ª«ªÃáÐîÝ»6|¹>ÙOÓ&NÓ}uŠ˜-[6%3©;æ¸o V_U]‡öìÞµá U&¦é ËBLë––µkû26¬¾ºº:ïÙ¥¾’Y'LÍËúfŠg5é«:ïÙJ_ã'=!B8ƒb§57s»vIÄ"°„àí··üÜÐ0õò_mðƒÐÑÞ¾fõÇÍÍ›OøÖ·g:G9TSUSSU3mÚ!›·´¼öêK‚ UÆÕ‚íeÛ[­‘t1ØÌYÌË[0˜–`Špsä\Í–Ž²“ZÞ/Ærëa$W³›d¡ÈSBmmVÿçßÍÍ›O8áÛ‡:WŽßµ{wýðá5Õ5Ó§²yKË«¯¼(÷Óúú™y"Ä´·T2™}è\åòjª«kª«§M›¾eóæW_•3©®^O’¹u&ím«W+™¨£º¦ªzÚ´é[¶´¼šdŒáÃê-ކ‰žÕ£/¢ÜÑæ-›{éË_5lØ0ƒ‹a ;¦‘çÇîÞÍËÉ #Àb1é·wîäù±·Ü|¼ÛÍê]ª(âMÍ_nذé´ÓN›9k6ÆRæ§qlãÏýië–ÖÍ-›¢ÑˆÞõ¸<—ǯ|²V“Ëí“?æåˆÓåuº¼òJ kOp¸*ôžœÃYápzì1¢ð7ïpeDºxÞ¥Öˆ&_B©—d}YAÒ‚Ù’¦þmöu˜þû.S/Æro¦w`æv•ææòDÒR“BOí"‚ nܸņ ›N;ýôY³ÅXzçw?⨉“¦~ûÄëׯ—°4vlã¹½ýtc$ÉÌEÅ›z3™9ëPŒÅÌÎ>¶ql_&ÑHæTQ”6mú2™‰Š1$U&*ƈD#³Ò¦Ó×Ö­[1–Ô÷µuëÖÆ±c|îOZ·´nÞ¼)‹\‰‘ª=ºžçc{öúå‡I ƒÈ"ãɧ:öïvôQßüfþoêh;øÙ§ŸM?¾±q<Ö·eTWUÏŸ?ýç_64Œm=†¬'Ìb‘0Èl‹„õœËí€X´+G£-˜ãÙ¾ t¨SXÛ ‰¨Ì¶B"*g(3¯ˆé¨P“ö•ÂÍHN`B0&&„ LtŸ$2øG.%eú.“Òêµ ÈÙfP#à ÚHç2Ò‹Ë:g©þ'Z£Œô«OO¨y­½:ޤžDôo[ɨ mˆd\>êÍÄÜ (¦½}ßgŸ~6aÂø¦d?ݳwÏìY3o¹ù¦G}üúnzå• ªºfþü#ä~:fl£:s„˜Ž¶ýr&ã1–ôÊ«ª®V2=flZ&Ê•46Žëkg>üè£#æÏ×dŒÑ£5,¿v˜RèkÛW_Íœ5çæÅ7\yÅår‚»–Þ½tÙÝ_~¾vèÐ!}ôÕ ûΉ3 ƒ††J‡cÌþýLÜ|ïµîÝCþøtÀé¬\¼˜eY#y(Ú·oïw,ô«^Æœ¹óÖ¯ÿog(4!’E! X$¬f[µÈEÃ2ÏÊÂ6  YÛʈG»Àéöi†Õ<ëtyã±nuÙj ŽÇº•?{…-@"Ö#×£ÃY‘vÙê˜D<’”½u¤ÜìLOKHѶ*FŽžw€ ÄŽw€(Ä€ãJÊ̘ddq Ë’&_ecª•—yÌ%6”Ë›¬œ' Å•Å,‹²3¨ù ´…Bûöí]pÒIb²Ÿžuö™g}&tõôœþ…û쯮®NöÓu¡ ˆ¤6A„½™È3š¶oÿº¡a”º%FÉ!†I¡„˜p(´oßÞjÆ pÓM¿yìñ'®¼âòo¼.“1Pc˜œ ™­VRèkĈú›nºþ¶%¿Å„\±è²eËîYºìžÛ—ÜR[W#aiÎ…¾˜4ª%iT«É¶#G¸y~l{;#Iqà{RE =ÿbÅa‡ñ'-ˆ74Ä $BL8r:@0íW®§§§¢"…Œ†q¹œ‚E‘ay3©Ì³±hX³.·_aÛd¤O‘·.·ÏéöÅUj7ñX—Óå“É4“gåHÙÔ °m<ÖãL²­ž%ñäpzNO"‘yV¦]%Rñ™Ÿ™ Ö¶¼ÃÅ;\½:7£‰¥q.Ç;E!.ÿ™Ê¹êÞŽ””Æ}7'Aj¢¥ÛøN,¯ós™E,dz2„é !¹±< õöS’ì§rï½÷þƒ>|衳++{»0Ã*ýT`&•¯CN§Ób,}ýõŽÃ;âÆ¯[´èRùøòå÷._~ߺu«‡°HÕÙUW‚P(r:þ`cI¾ˆåËï}ìñ'¾õ­oÜwÿÞŠE—_j̶ð¬}‘Ëq1!øöÛ·rå+W®ZrÛÍ]t~²Z’w$‰ Ò.Ëä0…Éêëáüó=^/Äâ‚ÇÕû  öŒ3øŠ ðÇpŸQ[£g`Œ‰„ÏçcF=¢§'²à»ßë×\®Ëc0E,ˆ‚ÓáÈ6ê&æÇè±h€(R7ÏŽ*3l ™v®ŠD¬G¹JÀ8w™a›ÖaÒ7iCpË%ÆÌ½ôýºòNQˆ) «æÙ´H•m!ɹ<”ƒiaÉ«X‹ËHŽŠdeH©ýTÅ’%wøýþ_]{•ºÿƒAQÄ‚ °Î¾ñ–”LÁxäˆúoøõwÜ „\~ù%Ëï¾oùòûn»íæ!uµrVÉLN§CûJ$ Ëï¾oùÝ÷ÝvëÍ /ºàéÍð—d2†Â]¶ðl}õ²ßå—]üÁÊU+W®úÖ7¿èÂóS«% W‹ÓÁk^ §u]½« X®º:.QˆKdo'[zØ61°‹uxx˜¾žC2zaÆçó B"‹ñ<¯s»œoüýeÏc)Eê&¢Çãq:ɉ{Èü´ãeŠéÖÔ[2þV4¬ZÞæÔ-R­ D¶jyk†p ´­ñÝBL6,$å­,~‘>Ϫlµ&ÁLr²ÕM¦EiS lµÈ²­–躺зÕêÍ’È0œMÖ+.“aQº»U¶év‚¾´i¶ZÒ7QnŒ 3ÄMËööÓX,Æó}Ä×ÖÖ¾ÿ‡üý¸qꮚH½ý´Ï^MؾΗ;ûe—þœrÇoïZùÁª>øðÖ[_xÁO±„32ÁZ™ÄxÎñõŽËï¾ïÖßôžxÙ%?'˜üöwKÏ8íûC† QÃátÊcv­%ÜÄò¯¨&}Ý}ïïW~ðá7¿qü;ïþó÷¿ø—]œI_„HšÃÓÌÆƒ:áîúzoum@úºkçúö~‡whí|L ž·Ó`e©¬ªÂõDºý¾*t8ø4“‚ ˆa}>?ƒ¬2vdc[¢~]fȳa+›PÊËÅz iÉ5Rܽ֨ž´ŠIA‘·jµ›fÃ5æ\!ÍNø¤ÏÎ ® ÄT‰5*»ÏV‹å?”c=Â[(ékÝ 1­{ÍjجžlôÎÎc>!•UÕ£HO·ÏP¢++ÿxçõªª*uWUú)Ë0XeN%ÐÛÙ#=]J&—^rQGGÇ#þáê«®øÙç)éû:;è߱*™ôôtû}õÃ?]ýá!uʉ—^rÑé§R[[ƒ±$ˆ Mưƒgµéëžû¸çÞn¹ù† öÓ‡~ì·w.#_véÏ@z/1 ‘$ý×b„èÍRßӵ닶M‰axEu8šXÑüR¹xúùNž€Û©×E `"•#GŽjÝÚ:}ÚtãûÚ½wW]íPŸßOúž«æû.Ob‘ÎŒøÄ"!V‹2rVˆîB’Ü.Ř›FR²ìU4/ÉÌ@àpzeÚU ¸JX]t/÷²€¬…“ÖƒHÚ5(V%Re^ ÊÑ>³Išz…IWj©ÔCRç Øf{-æblb+cç8æ'ÖÊ#¦¢¬8Ù"˜`¹ŸnÙºeúôJ‚H$rÆY?úÛk/¨ }»÷쬫êø ÁêÞA°”̤uúôC”ô×]wõ÷¾÷)S&«_Œ÷fâ÷"©íTcc5µÕioÔ•˜Ý{’ŒÑÇ³ÄÆó4úÚ±cç=÷>póâëÏ?ÿ\ K_|!&äÎ¥÷œzêÉC†ÔЗdúþк¤îD÷'»?ù|ß‚g[þ´jç‡#*†U9†@$$lØ´°äóúŸäúõŸwtv`}D£Ñukÿ;uÚôªÊ ÁBa™dSy–È—'Á³Š­6.w@m®U^£Éô7cÑ0$g&(•uÉ„«p®òítùŒg?&bÝàpy“<Û”´òÜÛ =n5z6}z6¼CÍ¿½l+[uÓ”¯líMþÙ›3Ï»ädŸ‘šû¤U0˜òJR>`ë'-s«snÍܵƱ´ÛÌ­+’üÆTIG»ºwOš4Q³³WVU¦9T Xò¥\‰>cÄ"É+©4ZÂÏä•új¯¯þñ¿ß;ÿ§?V®áâŸÿìßý³¶¶&®[»~ê´é•2}é<$Ib\M7(Eÿ½¹íkß»azÍ!×þËï>ºßé_uî»>>À "Jˆaˆ !'GôD ð˜0kÖügÝÚ5G}”ÏçÓxm­Y½z⤓&Oqòª©:£T3îTMA³@Æ/Í|f\”ͺ™DDvÊÆ”÷¹²­À˜`"O°Íj«%Yï ©çžÚj‘ÎÔ¦ì¶Z*&ÙmµÈ¤­6=-)VcÍû@(‹Õ›ô1·Ú®«gÁPMÔË´Eè·b$ Ëýô˜cŽòú4Þ|Äc±Õ«ÿ3qÒŒI“§º¼Jöš ÃJéÍä裼~Lb±ØšÞL¦¸œ\ßk¥¤1,ÆÐ—‰Ö•ÄâÑ>Æp°ÚTk‡KØTú:Ò§}1}ôåàQïÅd¼sºýœvÝ'M”Õ®ªÖŽÖ˜ûÅ;¿ÜÝÿ»c—p$€1Ž•'\ê¾JÂXb9nÆŒY üùÆ[Ó™:jÔ(§Ã)Ûã‰ø¾}û¾øâËyómjjr;yIr{W•ÑfL!šQÙ³ÐI”4)È:W{q*Ñd²”nc’µr"z]—äN»)sd[­l¢MN­Eæs0gÇ.ŒMÖÒüb墲ÎE YÍ º\‘òÓ¥Ûˆ²\%1²b"qµ‡îÙ~Þ‘‰ˆ¦5è^’çf¹Ý… ÄȽ±@p:^¦r à³U ^’Ó XNh͇B®órÚÜÒÚ±éÉ‹©Iÿ´=®¸-eþ•;}ZÝW{WmP¯ªDæs1qÑá\}9jve1 ÝèÔTRM¯?YVˆH±Ö5ó!ábÐ+XY Gl¸Žb9u#¹KÜr§Z#ÛAŽ„›‡¼%F£þ W¥!óä\ÈyëY´›'óšà_­“ô6ë?‚²#îÿf†Ýye× KIVj‰ù^]’„ké²õýä˜o!97G©«%SóaÞœù7'¶Ðµ\#[[Y¡L6ïZf5S+ƒñb1¬¶‚<ÌýL²¤¿Tm!×^‘K4I®pœ[8ڵ¼yñonúÔæ%©ý¨…ó¸=Û^z-ÃR’Íb@ ýÔ m!\;žfßdWbI?# “^¶ f*©kÞR€,<¬Œg2ß¶s˜à5`_ØìwŒØÕш}$˜åˆåuÄÄ>:±t³¥D²ý®jm'\;Ì Ê; o/ÙÊÐ:n¤DSŽåb)H{K…¬Þ§F(׬µiÔ¯­°'{û9ÉÇŃ…Y¹åİ¥O²%Eµú„ &ßöÛdO°Ÿsm ÝÂ3¯Þ¸ÑÜR6“•d°ÙN`—)8O÷96ÓkÖ*ÉÐ7Û¤Z¢L^UT‘kçæO»y3¯Eò5– Y·?(ñ–_"+<±Z¹¤<ìâ¥Î°å@²%Kµ: Ö/,q®Ý´ ÆoÇÌÑœ]ä›Ó8­$ön´‹–ú‹XÁÚdŠBÑk2l¹Ø l£Ú½û|µkŸ¼Ó*EIALÄF>|èëÙHƒ…˜e€ÊF'Û¼xŽX­lûèÕæ*à26_ªÝ·ÿà®<Ϲù~‘&¥ºÉf¯9o3JøûÚ ©«ÓË‘"ŠbBNå—¬DÎëüÂrkWGú­ÆJŠayžç8YÕ"9S펽9Ž›5m*íd¥‰Ï>oÞ¾kߺÚ4–”[¥ ==‘ ¯¿²ªŠÖ…ytw…ÃávÇÍó|1¨–aùJ­÷’EUÐ×ÖÑ¥ùû/Šb{GÇÈ‘c!‚-Y[ꀺvù¥!E¾îò#ž §¢bÇ×­U•AŽË™9-Øj Çñ´;•,8–×X߀Âá°Ï„^žÍËV0˜¹˜ØÎÄî û“ÛHÿ_Baˆ‹§Çë †ÃáªÜ…L)܃Û4Ia±éD©££3XY)Ê»(“¬Æ>ƒO–Ã)I˧Ör¹#K7m6y!k±PçœcÙµÅxeeuggHʶE®-ªÖˆ1£‘N»r³1+J¸€±‹FEQHy3nÑbN= •i>RF·eWÖ¤Ì{•( ÑXc̲l¿Qm4Òi#Û*YÉYäª3Wd/%e;<ʹO Aѹʌ^È ¿ÿÒ—Zù {ÙVvÓt!JH„€ìéȆ„J¸šJ˜ÞúµdRn•R¢-ÌþÕb…`[ʤù?mR0·€9°))×~¢/© 5TÐËqW¢=!€¾¥×òRµ¹Öém›)uéó3¤ÖÌxR¬ÒÔôêöìa[£•f¢=%RÕdpµ bùv¸‚µ¿‚³-•º¹õUB5Gñ s®Â¼ŠøUˆXIäÍä‰É7ÉHwE0ÚÓ©Ä+ª¤¢IéµtTm™±­"o)íæ&x5iÑ… ö³-’B‹ž@fXIF ™au¤Éd™§~¨BéU‡j•~g{_3϶iÖ€¬§(s¨¼- ¦tl¢ÆÔæÚhO§Rê°v6Í€ùp¥µsIÎT]ŠÞ‰¢ÔL£Dªê…)JˆŽË”­Xì¡+ÿ|NQÌ\^1ö¥ ªÖŽÆFQrŒ¡iª%I?M¤ø4€+ÜD¼fX32¢bFbîtÍS"*BDŸmsñ–EÙéÝÕœÛ5C«‚¢_à1°žÜå­rŠÞ¹¦VXë&§°OÕR h±E{Kÿ ÒÓ©pŸZ`*ñJ¤^Ê4>ÕÌD/ŸžžNsÛÓæA©–¢àƒ] ë4j&Þd²¬1j2U"{’aõÓíéî4|æ´%Pª¥  È‰‹ïÙ–‚R-…ҽ贬BCÏ$1ñÒ_/Maw£äT›ë¤ÚÌÓéįÌNGn/¤D\PëA^tHlȃ‚R­.(W|ÀIRÏ0E·”‘mãМI’ò連ê…Öí)§œñö;ïÊá‡~ìê«=uÚ,åè[o¿sÊ)gXS»ò'Mÿª#•ú,S»¨ Oþ&¶ $‡í Òœ \ïšX©ÁP‘´3ö>M{Tí]wÝñ“ŸüìÇ÷ÖÛï¾ÿ¯÷Ÿ}ö/Ê!QßtËŸÿü”žÕôH›æ²¶hžÜã7­e-/€GåY7%–Å€4 Xl'N8âˆù×]wÓÚuë^yù–e¿}â·äCxâ©#ÕfbÈБ“'O~áùgiý–*è[, Š¢vœ‚Pm{Û^úLÊ ¶­]»6BˆB¿éwY€ÏçŸ5kfuuI¼–§îf( ½½cÍš5C‡;v ­ Šƒp8¼fÍš¹sçTVVRª¥è´´´466Ž3’VÅÀB}E…·¥eóܹsúýRè†7À²ÌСu´(†­c˜,+Ä7oÞrõÕ¿^¿þsJµ…EE…×í® õ@1ðàvWx½>ƒ{÷îûÞɧ?ú؇Ï?vÿþ”jéÌY ›‰Dοàç‘H¤ººªººêg^‰F FµH¥\)tò,…$é·¿½ KÂôiSî¿ÿ¾éÓ¦$â±»î\†1¶«5µ–ŠªMsM«é£V/1E®Xñü ãÆ5-]¶ü¬3O¿óÎ¥MM|áù Q—ÆÁ&©ÚvžMsG«x¡Í\•«çÇ–¢ïégTúŠÑüDQ!kJžç9Ž•'À.}ÓAϪ>€sÎùá´©“!6cÆÔ³ÎþÁŠÿ}öý•œòý“N§­j‘j­áìs~üÖ[ïdMÇÍ/À¥VŠb¢­½cýú/Â]Ýÿoô ÄçõrÈÔªJ1¥ ˆÑhܬ­ª d­ó®®ÎpçA·ÛÉó\ò1€Çã=?BóçÏ“Ä8ˆbâÇëp8X–1àY{T­5œvê)zúÉ^M¾â…³ÎêÝÛæßÿþ¸¾~ø¨Q£`ïÞ½›š›…m&jŠV9žR-…]èè ­]·~è¡MMãhmdE(Z»výìYÓƒÁ”>+ŠRg¨kô˜‰„àxï¼)áãŽ;F Ý0ztƒI‚:Rm©Â.´¶~ÕÔØ4|ø0·ÛCk#ûP2ñz½­[¿ž=+…jÃ]]`-!8‹›ÉGE—ËéÔ„»ÚÔÙ®|Lœ˜ÐŠŒ¨ÞJkµ˜™7]”^)lî7tèãÙ— ¼^ßС¸³3¥J’ê j˜hF‡ª UU5»wn ø},ËÚ˜O ¢$f (®i3åªÚ\–˜rn¨¬/\ž…ÈÜwx©žÍM¹=>_Ê/Æ$‹ ‚€1&¹@„h,†1±7ŸRüE/•QIªóÙL¿´šžj)(((ÊcðD«€"eêØ¥Ä„:vÉñJ¤’R>¤$Ãi 2/ªd£³ ¿ß/»èìwÄã‰}ûöµ´4ïß "BÓ7¸GˆñxÜuuµµuuM.—‹R-Eòlæ…5 {š<%-LQ<üÈcpÉÅ •™ª„‡z”åØK.^h&½ §šs½-ä“OJؾý«/>ÿ¼­½3KH”ù¸zz¢===ìGM™<™R-EÉÁ€=kþ”þRµËgþ ‡¯¾ú׈'®¼òr9rùò{oþÍmðãýÐçóæüè¡P¨o!ôùúµ±¸4løÈC=ŒãxBÆc,I’ŽF£_|ñy,Úܲaê”)v-ü±Hµ˜MÛ:¾ÞÞ¿o¤§»~ähJ4ý Žc†ú&Ž)’ ä½ÑR“2•´Å¯ø—]vÙC=tý ‹GŽqúé§>÷Ü ™g-Zdgn y’¬BÙyæ#I8ÜÕsâwNfVvw€’Ã.—kÆŒ™ï¼ónÃÎjí´M[;ºhÖ´IÿÜ¿}ÛŽæ³N?™6Óþ…$IÛ¾Þ½i[‡×QŒâ J‚ԀРw/_ÚÑÑñÖ[o^û«ëwìØyÿאַ®Z°`Á²¥wbœ}b¿ÁŠÕœ ™ù(ÓËr5Dd&Éår'º·ãv»EÛ»Ý"Õ~½/|È” qÇ’ ᮈ@iÿ‚A0røÐu_4Oip³\“lh^—½NëA’‰„?<þÈW\¹iÓÆ×_½©qÌäÉSî½÷^3\ýÎ–Ö ÐÝÑsÑ…×2LnÓu;lô{k¾¢t™'x+JØölÓX2Ô±Ëü_='AŸRBÿÿl3°lùò;n_rð຺!7ÜxÃäð†Ê–×Yvå£FTËqv½ÄR.ŒXÏQÂX”$y¢ñ¶ÖÍGœv$|øÊ*I’pîw.JmâùÚ0²=OB4ˆÑ §Å˜?…¢ÈªÏí⯾æÚ÷Þ{ïøãw9ÙçÎXæ*-r¨PI’DQ앵ʓ  u¾÷·8ž€I3æŒ7ÑÕŠâwŽœo¬êóJ#Ǩ#¿sä„7V5Ëß´Cd<dø(I2’d%¶—2qÉh&Ðé´„àÊ ïÌ3N—$l¦ÒJöV4›·iU›v.1È3'ªÍ©cK¢(bŒ é£Zä«­›®ñOª¯Ç„<½võˆ1MfrûΑþö¯/Õï;E¨#•°(Š”[3žHÉm_Tú”ô]¶ ³­ï•ÌJcvûöm#G6˜ï›<Ïoß¾eØB䓡ެS-–$I’”¥Ê}ñªí"!’9ËÀ+ÿX¯¤”ê%23žBýDh%X€Óɉ‚Èó<­ “ÑáàR™ UTxZš7TW׸ÝnQÌÞ9ŽëîîniÞÐ8¶AÙÔÖ®|²ÂØ•x 9i(,Š ÙöékIJ¡ÚÅ×]^Ý4 ö7~ÛïÔ7 H™á3¾=33ÒLíVU‹i%X@mMm(ÜÆóu,ÇçêÌðI]"‰B8ÜVW[“"EY6 Týÿx÷Íñ¦Œ=&kVÍÍ›6·l¨>4 (*Ò®|”ªU ²®U jɉ1®lœ¸µ²jb#õ!9|ÎI‡=÷ú9朓SPÓþ¡kÿ¬Àãq3 ´· ÂÖœ¤*`&ð;ŽÚšêh4–5½Ûíòù|n·+m$aW>Y¯¶| òd/Òk¬UFi¶Zµ×`F×Ó/­’)õé—VÉÉÔ1?=íHå\:-Ìà‰ÐJ èG „<·Ûí239L’…ËÇø,ƒ£¥¦j±(J¸WÔöMöR[ qŠà%zfÖ'V¼/ŠÒ+Þ‡TS¬#'PRÒ6­÷Dh%P”áÚbò¶+ T[(U«ð¤ùûâX$,á^a›dÚ[-1m@ È÷A²LBX†¾Õ¡ 0Eµ{:ØEµ* jUÕŽâßÛÞãõ8+ÜŽý»¿ŽÆþÝS EÚk;ÖØë÷¸ ‚Ôs{v@\ªÔV(`Lº#цa~€­ Š|¨¶àó+@&7V!Û÷†¼ºñ“ga,Éb–©aX–ëæÜòÔç¡O­(Êt8Ž;ÐÞMŸqÀ2¨a˜òت»öj>JBz#•@ß!B€Ns¢x ¤·y[?¤ü‰’‘¹æiQÕ2Mi¬žÒXÝÚ SœsçΡ•‚‚¢|(Ø…˜Ì“¯Ib嫞þÓ_è“£  (#ïÈ[ªµèD1ÿøÇªÍ[vVN#À¬DX8‰°©¬¬>éÌÓé3¦  (UkLÄv•“¯!ÏþÏï¯û®8a»0a»8a»0q»Ï¹Bslâ¦è„æØ„æØÊÀÔ<©Ö¼¿%ꙉ‚‚¢U­›¤j,ËóŽð ¸:‚‡…˜†ƒœËåéÂÁƒbÝq¤“qÓUÅúÁÖyF„VÚÚ;Ö¯ÿ"ÜÕÉï¦éwy|ñy½‡2µª2X\ª-¢Å AŽ±Ô……‹’ž8épB{¤xG¸["Æk™Üž ¤:¯“c@Ç£]æÑÌ]NÍä9h÷F¥PÐÑZ»nýÐ!C›šÆÑÚ(;„B¡µk×Ïž5=Ìm EPµÖ1wþÈa ¯'$Âfùj ƒ&qáE²¿|PO*]Þ¬ãýÌ€¦5@ï¨f8kž”g9Z[¿jjl>|˜Ûí¡µQvˆF#^¯·uë׳gå@µýn@°ŽŸýì6ÔZ~”§>=3+½Ì)Ïrp7tè¯×G«¢áõú†Źniü⫤©¶, ((T}ÕKõly÷k·ÇçËí—²ŒUmYðlš—"+2·Ëµƒ|Vf€‚¢¿`Lµ…ðÖRZTk£ÙÔ +ʳ9AM‹XÒxo] Šþ¢ÚÁh@ç€ÊlªÄ€ŽÕÕx~‚qžêÓ)ò¼i\œ)³³¯„ÕG ÒçL1ðð#À%/TÜBzèQ–c/¹x¡ål%I¤ªÖÌ[,óo½ä°AšÌÀàAš¿vDv¤‘»Y@Ï2¦ÓÖ “¬6jD#ªpøê« ‰xâÊ+/—#—/¿÷æßÜ?þÑ}>oÖÖ) É_&lµz=ËUG±†>NЬ¬ª|Ìs\>lh|.åÙAÔöüË.» ®¿añ‹/¾ Ï=·BæÙE‹eåYM „xž‹F£FÊ/åxÖ^Çä6¨ÚOÖ|zÓâ[̤¼}É-´õ”Ÿ²È…^ 4Ì/\Î%>»{ùÒŽŽŽ·ÞzóÚ_]¿cÇÎûÿ`uuÕ‚ –-½c+®™Y–ñû<«WÿgÆŒ™<ÏgJÚx<¾iãFŸ×Ͳv*Ñ|©vöìYgŸ}&mi+Ov@wcÚZʃmE1®Ç³ú1³ÍçÙLòdzY6«ÚëïtUúÉëîÙ°™åy8÷ŒÄóç/¾ÞýÆ_zh[¡(Õ>,ÑJ ±Èª6/üûSnïæäÅ;píÙ>\ÃÞ¸ÔuÖ÷I‚]{™¬â4MŸÊ1êH9¬ŽTbŒOI §E1Èátr¢ Òz(_‚èp”º;†XÝk7xÆñ«Kâ+^ãž#"AiÝÎÌœ"}½‹1æÙh¤S½^V‰I[D›–R½*Lïƒi¥€ÚšÚP¸M„šÊOÌQH„Ãmuµ5%yy}ÔjçOÁ×»˜®n˜3KâpÜa"!ðÛß»º{ÐÁ¦¶Ú”%;+ýÙÅ”g)xjਹâ^ïîêF, wÆ ËþjØB.û9RPPP”ÕÊðyé¤ [©–ú«¥   (,ÕRµ&©ÖºÅéÓ¦NŸ6•Ö#E ìD‘‚¢Ì:!¢( ‚@«¢ìÀó<ÇÙ¼XáT­›¤ê4”šoAéë€è<#õº\¬‚ F£q°¶ª&@™«ìÐÕÕî<èv;yžÓé-ßLo"Z+ç.CU-Å †(J¡®Ñc&‚ãñ8­²ƒÇã«ðø¾úª¹2èç8¶\T­uñóU5× ¤¥T«N%,Ô)3óÏÌ\ï,êÄ‹Âá®®@°–‹Qž-ÓKÑårú5á®¶ªÊÒ]¯dÕª)RsäžÆ¡fè/-OƒÓ ’™—ò,…$I u†F5L¤z¶¬!bUUÍî[~Ë–¨°µÓ‚yRËÉY¢ÙRP¨1‰Æb‚ POµe Bˆ ÑX ãÒ}Žåj«¥ž ŠeŸZê½óÙ0\9·Dv'„Xc[¿ß‡Õ\O¤$(Wª¥¶hУW[(rÀìƒKy“¢xóJÓFMJåm‘iWѹiõŸò'MÆj¦W'Ë‚o@ <ŠÕ<“fÊÛÒ¼jfLcÌœ²ÊLŸkI&…¥Ë–Ï›7¯©©qþüùw-]VŽ¿Rœú7Ñm) 3+Ϫ§(¨§…É‘Ór5O,æÍ”·z¢U}ЩޡlkmÌÁqüUW_û¯½wÜqÇ3 [.T«¾Nj@ °h Л`«yŠ^š4¶58«ÐÓ”^ÑÑÑ¡î!iÝF@>JÑŒLËD,­ cH’TQá=õÔÓ‰ÆØ€ÑJZÕ–×SäúsšÆ&„ºP¤(C`Œc±XÎ!¿æ­y.IÂ:Õæ Qd>]Ë|¹ÚÚ€eÉÐ!xö,2~X²KXpRCA‘¿ª¥ (lz-&ŠÜtýMî¾ß£Ý{Àë·›ÙÔâ¸êWΓ¾Ï¼ûO[ ¡zßÈ ëtˆ—_Ê=ü¨ÕÚnL ”Ja{ÄÈzÄ ­­›Ö­]C+¤ìà÷ûFŒV_?ÜãqgÇäÈÅ ËJ". ªE±°,TT'#5Õ¨#˶ ù“¯±‚BˆÀ~§ÃQ[SÆh…”Ün—Ïçs»]f– Ä`b–: „l[ä•/Õ¿ŸÔ×3}Œ8Üh¤öæÛøðy…Ö¡š¶ªp)²u?äñ¸Ýn„P¦ÏüÚ+„BÌ&&—Õ€pËbþê_ Þ‡§MÕ¼^ö¥WÙ¿½žXñWS¿Q9ÚjÍŸHmµvõXŠ2Ëò>¯ÇÄX€ÓéâX‡ÑR¡Z|è,aù]ü/¯ÅÓ¦J'}‡L›Jª«@ÂhÏæ“ÏØ^–IüõO¤º:×±¿s¦ë[ãÈA½E ”h(jk'Ie§NŸÝ¼i}Öß]BȤÉ3ävëã.õ|‹ 6;þ¯²ƒ{á%tËt° X– †gÏ/¿ÏŸ—uC&fZ2Ýš±BP†¥  ‘ˆ÷Œ3Þï lßÞéî‘0Ö’´ŒÏ¨2bܸɉxOÞDß°oµ˜Ã!vŠtÚ)ôqRPP”&D1µµÃjj‡aI$Z*bXÄb]rúBLÊã~~{@…ª‰§CôíÔ€@1H  бîyšõWKAAAQpäk@hmÝzðàÁ¹sçЪ¤(cåCˆ(Jåµ … žç9Ž-ýÙ#ùRíÊV­^½Æ˜j÷„÷×TTñ,õKQŠ1ûƒµU5t°òCWWg¸ó Ûíäù’f˜B]éA‰‡ŽËâ{Å}güùçwuݱ‡Ì¥Í‚¢Ô ŠRg¨kô˜‰„àxëìÀÊ>´ïiµrzl|ª&Øo îÃ…%ïÞ·³sϟϾŸgy`ÁyK”>×~±üÀÚµc®Â¼jõ*GfjáÌ”¥ ¿ßápX˜r@ý§œR‘Ÿj)šy–:¥æéêBiNÃc= .TfÎByäŽã.\˜Õ¥ÂSQqÞy?„æÍ;LHD !D;ö(‡ÃÁ²ŒÃéÒ\³Kòè2ÖæêÁï×=¹zǺgŽ~ý²îݯŸ)׆‡êØ%›”ïLû€V4- %kU @  )ܧXü~("„( ”Hõéia%Ð<]¯PŠp8|íµ×@,[´h‘yÏ=÷Üzë­ðƒüÀëõZ«1Bȼy‡™ge$‘ùóç!‚3Ñ¿úca®ð¤Sz—ꓯmxûÏçÜïxQ#¦vƒÁŒr±Òê^’5ÿ´–‰íé<Àe—]‹/~ùå—`ÅŠ2Ï.Z´HæYë¬%D…Dú˜[HDMð¬Ø´„ÁC^ ÿíÉægžòTµ«<ØùË|/—¾P}†yUôA+Á¼ö\ºtiGGÇ[o½yýõ×íܹóÁ¨®®Z°`ÁwÞY^ ©í¡Úúîò7|æø‡«¯i 'v#onÒ:«kZ *r Ý¥•A¨¦)@32kÍÈÎÎNóùS‚ðè£\õË+7mÚøæoj3yò”åwß›H$Ê«Æläïë:xÝw>uæò¦)£tô0@ sÒ%§®¸÷ÇŽ3dH±ͪj3iÍL4‰{0›X†!„ (õI{ܧO¢Ä  b“s(ÛRPX2 Ì +“½lXˆùäSOýõúJIs”g)(¬­ù«µj/8ÿ'¨d|ø›œ–;h…-åÙÒD<× P”ÛZ£;¨–¾Î¦ È“g[[·p™Z9gL™-›·ä¯jÝž âsV­OÕ*iÒd*YÍH ŠÒ„Óéœ<å§Ó• •3``ÃÏæÞ={ó¤ZµY½xóžgéÎ7e8*ãò>+™ ªjeyä|»®ÆÌZ[ƒôƉ)(((ÊXÕBŠðZŒlÓO„ˆ¢$­ óàyžãXË\ÇäëÚë¯Ïx<î Æ 2$-¾»»çðys,8e °ëùêÇ:¡põêÜ£š?°GgÞ‡QXÉ!Ô¹'¦$‡†:÷¨™ëeBaßs7U¥‘Hô³Ïþ{\åð|Uíc?‹Ån_rKZü_þç¯ë×þÈÃÐgB1P‘ä»ìñꘜÂ9 ÂýÇ={ö ·Û]Z“½ÌÛ¨õ€‚ÂÐn0Œrn)úYýÕZHIAQ4.+AR£ôZ"H5 ä·á]lk£¦“m)(—Q”¶ Û={Vɨ¿Z Š ·Û7¼IÅ+¯¼ÖÒÜ¢ŽYpÒwàý÷?8ÿ‚…êøyóæü|á…f$-Eþ?§1$5@AAa¡[Yr‚¯ª=õÔS&Œ—9n\ïpp\zæMôaQPP BäKµ.§sÔ¨‘sçÎÉ<4eò$Z¿ô`Ý9øÊV=ý§¿ôþ!I`Z«)ÔjÛk1Ü B;H1\pÕˆ© A ‚ÕÍÉH„î=8Ú)‰q¸o ëÎðõ¦2¤žgíþ!…lÎÁ)((lЪ–©ÖjÉ $öàî6éÀ®DW—IJ¨²†«ërV&ç˜ì§Sž¥  TªÖ"„@"FÚÚ„í;¢Ñ¨8¤ÖëÁ»ª9Î1’˜Tµr@YÉ)·j.V&áfFÊç*ñš))(((új‰‹Å›°nÄqc²¿=¾a[¸&èL„w0N7 úÆôÚ4´j N35dF¦yÏLIAAAQLCƒÍªATQÅTù ‹xuKG¥¯­qkXw­ÃmšjõÖãšÑ§Ô1B^Í‚Q”!QÐRxÞÁq,BˆV8Åà5 äóÒ„B|íl]›³)ä‰F«·îé€hGºp"Lœ=Œ]¨÷¯\N•ƒ ¢(JÊ:—Ë]Ð ‹Å¢ûyŽåyŽv?Š2í\ý¼c®kªp’TÁóÌð!NQ·‡,Ä TØÃ³™¦X3™Â¢(u†ÂcÆNÀB"Zв¾nÈÈm[7VƒÇ–ˆ¦ð¥ÓÁDi©Ú<ÁTß8±¼Ëƒjk±˜äp²~–uØï3L“gõÈ—Újî ‚µXHÄ ]NH¼ÃíT‡»Ú«*«MÆ•5Á*_AKïééê t¹\jM=J×,š¢\©Ÿx'0¼—÷Õr±Œ8+G!²eR§¦¿ZãH*o!IR¨34ªa‚(ÄeÆÑB¼ªªf÷Îm€eYYS9c¡§»½°ºÒ᪩±sG«¢©Ié™ES”7ÕrW8ë1Ì`‘0¨vÛ¦çì]£¡yC_}¹ö÷Æçm ¼lLþÐgoh…JùÈÂVKÒK ClnþlüÄÙidæK·vÈ®ÒeŒŸxè5×\×\sÍø‰‡Ê»Š¦(?ªmoû\ïPÛþÿšTµr Mu¦ Ï4Yš–,Íb¦€ÍLfœóà¢Y]õz›ÉHËÉT ù7aB°¥å³´€Õ2_:€n>‡ì*]þliùtùòå°|ùò--ŸÊ»Š¦(?ªõÆé 6Yž2ë)áLYš™é¯V/+Y==«™ó`ãZ¢ÿQ [YÒÃô=R'i~67¯m?K3`á£óÚ‰ ò)téÊç”ï§|ïxk·l\4E™QmãäjRªÃœ2óróùä鯖>ÑØ,»ëVuÂewÝjÑx £G¾[ZÖ›03-0H yG ¥CµÄ–YÍ<ï=áÔ×F; P}qÔ·O{Ýëe¯½Õä0?WƒÀ 7 d}ü²°Íâɱô¦ñ3`K˺´@1ï½kž¢üÅ‹½þjÝ5G~ëÑ™³nxéÜÃf_xÛ¤“Ú{áõW;èEq–ŸÛewÝ"ϱ]v×-ùñѳWj¢uó:½€XK5ñÊ«ïêgUðÒó,.kÑEƒ=›“§Y `"¶€ò° þjËÜ¿mÖåbÚîf€¬‹¡=ïš’y=€ò¦³Œ9”~Ú©ßìÇÒÕØºe­­ES%mVÍjí÷ëU0Î_-õo+³mÿ•ŠÕÑ`-ªÚþQ=šªÖ ¶ulyê¿RþĉDó´.8°óÏÁ·?Vâ¿1òˆ c¾i»bë¶íŒ¾é½½½ý®;—0Ô6O‘¥ZŠÒl–žŠê@ ¶«ë`eeeÞ¦²Þ€m yÏž½7Ý´Øét*1‰DâÅ_t8§Ÿ~:Ü~ûB]fRPP”<ñ®ªªš];[ýþËÚ£-mx£7*Lßýu¯>q®¿’ÛÇ·lÙ"S­‘b/ï%%‰ÜW0Ð7Ó½}‚ Q¢Ñ–0›9ÏcÃ{1¸è– †ªTí˜gŸ}¶¡¡>¹rD[[ûºuÿ wuBˆB¿éwÉ~€Ïë›9óªªÊ’"mU›ÒTíÌ&vĈ;wîÌ)E᦭ÝJó~@UpAÑÑÑùɧŸ R;fÌhZepWø“O?;ì°Ù•ÁRô•ïæäªVÅ7>ÜÏRb-€Á,6oÞ2vì˜ñãšh=R”Z6oÙ²¹õ°Ãf¥å³9¹­ªvÎ$×G±qãÆ\yV²å›¶ª‚‚a™!uµ´(Ê CêjC¡P±¥õxúÜŸ3mÚ”~Tµy!MÕF£ÑÖÖV—Ëe’g³ÆP n·‡ÖEyÁíöx½^ƒûöí?õ´îܹëñ?ü±¥ym]mñô„mT[_?üöÛµ§Íþæ77@gg§ÁZ·L»•®6"‰\´ðòh4Z]]?¿øŠgþçI·Û]fT{öÙgê—*É¿ÔzÐøèãõ´(J 'žxb¸s·™”’„—.½ciú´)?»ð¢'ŸøƒH,_~ÿ7þª8ëªJËë„B¦Ô{l bþ¼é´(Ê/¾ôjg(4n\Ó%—^>mê„qMM<òP[{ÇË/ÿíôÓO)”Ä2Y=w´ò‡¶ ŠÔ±Ë8ÆüQ ŠB£¢¢â¼ó~ Í›;;ï!9ö˜#Ë0N§« %m ÚOÖ|zÓâ[̤¼}É-ùDy¶4Ù6PYoœ˜ò,Eÿ‚2wîl ó¬ŒD¼{þá‡B„D´hªÖúÂÜÙ³g}ö™ôYR¨Y8a^–rʿńˆdF&â‘B“¼ÈWÕVx•ãÕ‘t9YÉò¯:@AÑ_@ˆÙ¸ac,. 6â°ÃæpOÁcŒ%I’ÃÑhôóÏ×Ç£ámÛZ'OžDH¡LŒÆ Óø£?¶|û~ý'ì·Nî_âêêBðÁ»<Ç÷¾'üw5ûò_³­²*W½0W¯DꥤHäû^¨øT/ ‡å?³&£ÏŠ¢Ð$îêùÿöÎ<ÌâÎûUÝ-µZ÷\žËøÆcŒ „…ðBò“¹L¯ÃÃ’àÅáH–µ7ËÖË‘6 1áȆ}HH€$dcs$<\ ØÄ0ø߯ØðŒ¤‘FWwW½ÈhÛºFÒH3ÝÒ÷óøÑÓSªn«ª«¾ýí_wUqÆ™²ì ¹p8³gŸŠê:+­—©–¹¥v|zHxáiû¥ßLBÂ!:ï,M×ÉæâuËâþF~b· žÈçOá[åÎUU—åBS¹:MeÕ^d¶b„ßþÒþå‹TJɆ7¥qíLqñßgœ¢ÛeBùèoâuß/j qñfPŒ!åa !i?›ÞEÑsSìéºçh}…_=b?ýlíã=¯Øþî|•Ò»EœØÅN˜¡@(RIau9z']Xj “(Òêý”ŠIí—¿¦>õ3ùßJvÏÑÃaÚÚÁºçèG>¥¯¿,%cô»·ÅK:Z†ÎVêm0P6\p*XÏЦ”ÒPƒÔVÑÛVlqò/-P¿´@5¦|ñµÔƒ¤Ÿqe<ì2¦g¤ÀÞŽENÀ €Ím–“Íãj«¥õÄ$“(曯ָ‘­§Å¤@Ñ„*RÉÅÉ oŽMÁùuêj‹ ß¹ÆbqòQ›¯,@¨œÔb¾Z¤¶êR;kf÷¬™Ý8—‹ k©MÁ9Ù±™ßMqRAÁ9×4]U‡yÆf³I’8:TVj­´8ù‘Ã俪¯|LøÂùÎ+¨TU‹Å^Kc³¯pÎp88<¢(²Í&¡ÞjIˆ+Ee”±¥ÜÿŒpË5lóûå<ã6N"ƒL‚¦éÁP¸£sŠÓéJ ‡Óénïœ …5MGÕ™PLy~¬$µ„“O~¸Z¼ñ2}ßÎòß(‹±À< †Ã> ã,OhÃ'8'^_ó`8Œª3!µ#µ„s/¤7Ü!,Y é+Sm3ì­âôgÐÍN¥µ¹­.;ð<¯ fÏBk\ñ˜ü–@×õP0ÔØØ¬ªZÑѵ±±9 é:Œ­YÚv1!‚t¬ÖÐú9!„gŽŒ`ö¯ Ç•.¹Zøì_²@âeÉíÑ¡Ò3doÌ{`Jji ZÆx,WU•±b§×㜫ª‹Ç㢈æW[5W›bñrñäyô{_×’ Néû„üñér.PRËa4°¾†Îœþ7g:U¥¦bµ†R‘å«Düë·cDMòn×*up¬¢¹©µ2Ä7•h¹µpxÑàÔ›VjËþÖ¼RKEr÷Ò‘>~ß¿°Ž‰4™ }º³L/rÏ;¶JjüW'!P«+½W›ì ?}Vºú<õ‰Udæ\aÓÞÚYÉKbµcH†€oQ:u@¨ŠÔ~´ž¯þûÊBáÇOK×}M›>›~´·`¤O 0Gmi´…ÜP‘9qŠ­@W[•ÿcêtzáôåçõÿwŽ6q}ûUöÖ:^XCómdo#z`­Pì-€«­–«uºÉ…W^! É_þÀ¢ºgcŒµ[7î5%¦Æ'`Û¨%PW®¶ºãµ½~rÑUÂEW‘ÀA׈`ÇI¯Á @:ÅøUáD\mUhhÆŒG€1¾dŽá¸¥ 7¢ îÛ·Çf“Š|©Öf“öíÛ# (ªãjÿášÅë×o0xuáó B±Ùl¿üåã3NšŽºB¨Ëåìݾ¥©©YQ”açë’$1êݾeꔉ‚€{8ËXZÎ9cÌR»ßþ… /?ñÄ%I²Ùlv»Ýf³¥6öìÙ³råÊ‹.ºìµ¿¾ÜÙÙS,ciEÑï÷5ø½¯¾²öøfLš4¹pþíÛwîèÝÒÙÑæ÷ûDÌ€©­†ÔBÆ7eÊY–ퟓÚÁår~û[W/Xp髯®õù|8ëÀ*8Êøã:©@wïÚöÁÆõ…3{½žñãÛ;;;œNUgB©-û[sI­(Švi͵ÙlÜ·¿Ãá¸öÚ%¿ùÍá¬ëÄ¿Ï+Ûí-ÍM±X¼pfEqx<Eq`͸Ú*Jm*\&ýçäÉ“W¬X©ë:!⎽y›©ÓŸ fžslX:[ÆœŠÙ9!”R§SQǰÆ'½+°œ«µ’ÔJ’dÏE[[Û’%K!ëÖ­»ï¾{ ™‚âf§5ndïK07¨ŽàBF-MjÒá±uµBEb†«Ml6›ñªR¸±fëcaÅ„žê.€ I’ñ™X†Î¦ŠZÆ{ÂÅÏK‹l5@H¹Z£±ÍÐÙTaJ½ËŠ…ÉT6€0:kÁUf´XF¬6çU¥°ÔÓœ_åË{ (Ij-öB:€ÏÀB“ý‚1Åø‚ADØ[@-Ò®¶@a† x2–oúZuäjßzë­ÁÁÁ6lØP‘×eŒñÈ+ú==›ÃJhj.>|ŽÕ'¡ÄãvÏžÝÝØà¯ ÔŽN¬¶RûÅsÏÙ¶uûŽÞí…³}áÌ3Š÷³#Ï ÀÈ C?èikmëꚆÚ0¡PhãÆžSçÌòûKPÛ ÜòƒA 5É®]{»¦vut´+еab±¨ÛíÞµ{ÿ©sêOj¨U$Ijkku»=¨ “àv{ÚÚX0XÚÝma1…Ô3’±cykØd,2ækèÌØ0MÇvgøÙïºd¼h˜ó­pDÀFŽ¢8=ž’/~éÑÕôsÒÛ–‘ZéáXhØlŇ êk£–!ŽfÓÓ:yÁÙ6Šù Œ ©kù¤Ö2“(>þòÏVÿy•$:”Æ´oéÆïÌ_ZFÌfxÓJšRU£¶¦òd§×¬CC5‡ÔÖBaóÇ=+Ý?î%ò¬ûÛ^\ÿ;œòÚÖÙ …Íéak^a³µãkàj‰Ibµéùj3&HÌ9Mmz~aÌÝk¾¨B1éõfø4² ×Þ|IDATÚL/0ì4µ±h0c^p0ÊdÄj K0t•€‚‰¤¶È‰†k•B}Vf—ÚŒöŠÆQcá…ºâk‚ØA1„ª¯²aF©EÓ´˜¦lêy—q£&£Ù ‹)¦c™¥Ôf“âñxããñ¸d«½¦‘©‡0ä|˜‹ö:¶xÌe”Ý™ d3ÿ3´Â+3aÚ9³!Š‚×ã\¿þÝ9s榖,ÈpµªªnÙ¼ÉãVDQ¨ê/‘LØ”³]@¾õtшAU• ¨âyEÁçuC?½ø{MÓPBˆÝ&ºÝ.[DÓ»Z·ìyúõ_¼ôÁŸ ä92Ø7¡er‘N¡k‘£q_¥Ø5MÏ^À Œ!š¦;d{ñùN‡¢8TÍŸH$)¥B:P@ %TQdA’Éx"™4»Ô._¸òõM/ ›íïfœV¬ES£?:‘e{‹‚jÀŠ47—öäÜá°»D‘R*4}*ÓÁÙdRM&ãÕþå•pµwá¡bXÖÕ*‰D<°Ërµ›€"â<™H8dÁn—-÷ã1³…e‡,£LµÛ‹þô‘ÞíÚµûÝwßËgõ9+ÀÈ¥öõ7Þ|ò©ÿLm‡c±ÿ y<þÄS}}Ÿ¢Š ’Áþ×_sûöíiK»sSÏÞE@ŤVÓ´ßúËk_äœ÷÷÷¯¼íÖïœ6îðÛÿý‹ÇŸD- µ```à»×_Õ¬¦9ÎðÒ›—I’tÒÌ™ùhï'aõ‹çœZ@j+@ccãmwüðéwv½¶'øo+ÿÍçó]vÅ7âç^vÃ÷§NRx_ãä2ŠÓŸñ'Π¨ØË^míó,’eÙå:ºìÝìY³c›6o™5³;ߎéÉgÓŸ8+¸Ú¼L›Ö5iÒÄôŸgœ1¯¥¥…sþ§ÿ»øƒ¤ÕS µ98î¸ñ|سuÛvcâ3kíüp÷ÎÃÊ+„PADÑVÕüc&µ„-=lß´9ýçƒ>ÛÓsëŧÝ׊íÛ{óí…Рòê&Œ»˜ g¤ª3]T,V;4ý¯§ž<Ë=Ø»ñ¥ßèúÂË/£”Þxãw~óÄšýþõe·¯˜0iRI1˜\`ú==›ÃJŽÎØÏ±ú$”xÜîÙ³»вe”Šœ;sãŒRÑRË9%Û'‡Ã‘ÄÌæ¦túåÿpmïÁþ:›VU(ì(ÂÝæYéüØÍ™'ß#²|o ˜3Da`3F–ÃÞš]g÷Ý÷SÎôY3g|ëÛ×þâ±5jRýÉO¼å–¥UeR Fª¶Ã&“§øôc"ÀÈrKðÜó…§MëZò»g?­«kõꇂ/<ÿâ%—~ ÆŽ)wjâ¼D£Ñÿyû=BÈW\9}úäx|pÆŒã/ÿú7!o¾õN"‘„«ujM‚¢Ø5M·Ùl8MæAÓt‡l/i—ËuÕUWJçvJ"!„$“Cçœ}¦ÝnA–åQ˜ÇR @^šýÑ¡ˆ,ÛG'œ†…164in.í $çü´ÓN!”¤t6E">ãôS9gÉd®€±uµJ"ì²L)E…ŒuÜ€' ‡,Øír©û&“CÙ‰Få…Ô0–ȲC–Q &ÚmŠE:n‹R ZZ ?_óäÏ×<É oärÎW?ò‹5=5:?Å5Îà`xùò;!ÉDò†§ø‡W¬¸—ò+.s»]pµ0"|>ïõ×_O¹ý޽ð‹„gŸ}!¥³7Ýt“Û톫€‘Â9¿çî@`ݺµÿz늟zøá5MMóçÏÿ÷»îL$Gá7ÀÕjŸd2üÈ#^|Ñ‚©S&½´î•®©“/½äâ‡úéèè,¤P/èÚÐ=÷Þ{úé§wuM=óÌ3ï¾ç]Ãh1Lsï©iºªª¨ 3`³Ù$I,o4ç\7ÿóÒ×^{íÜ/žKiœ³Ñ›º R[7P*I6Æ4Îc,Ì8cœBŒé²CV“I‡ÝQçu¦ªZ,–ðú[›}hAf  ?SÙfË¡]j2i·ËÆæcÌá,˜Ÿˆéš–7çé®qLgaŒ1M’l¤t­‡ÔÖ¢H9ç”sc3¡„Ðc7!ãš›cѨl¯÷­(%N§MKý˜óÛ,86B˜¦%³e4¶Žk.¬‚\×âÑ1´öçÝÐ#ŒÙRžDËñÔÚ:B !ŒsvììœÜ°Á?oÍA áð $¡…k išËå¬Ðä³9:!„Ps&Ðrü:RÝ4BA`ŒzÌ$öœý3½q4† ;dÙzÖ¢" g{?Ö–0ÆA P@mÊãŒM”;¥7¨÷žbèÇv N8£ pA ᜦÿo¤Ÿ3F)Ý·oGgçøD2†ZÀfSöíÛA)Íx†,„s."¤B(çŒpn|WF·Ë¹m˦–¦&§«QU£¨(P×:kw%Ñ­[6Mž4AcgáœÂE¸ZPðžˆR*ñâO¢ ø|^¯×³níÚÝ'M˜89•ŽO|ÖáçÎÞ-[¶nmokõù¼¢ d<ÙàœQ*P¼ìòÁ9§‚À9ãœñcÑ*Š£³³RºuëÖwß{uê¯ÇÓÑÞÖÑѪ(ŽŒžB('$Õ8)ñɤ¶ž"M]¡if`Aðû|²]nnnŒÅ®u¢(·[QÙÖ•sÂ9Ã{µ pTL½WËs}­8Å© ¢8*¬9’çD bïê@jëÌÕ2F(% •@†…¸ZP„±R/aWb8 u©´œSŒÃI-==ÀPJ‡q&VÞ¼bÚ:ÒYQc„pÎA ŒN$2ÆDQ—½$ªGb*Ý´Dbš@´|jK gœ`.eÁ)á£äj[•¾€ºiÛ·ÓŽŠ7ÎF“ª¦·7ç^þ35‰"¡Å(ShùhM¢èó8!ÖJôñ¦émBç8‡Kqä¶´TdœÎñX €ò`œQ*ŽÒh1Kñ¸ð¦õš9ÿ& [Ë Mõ£Ò{j¯~ ”Vf:OêXmË‹ÕBj ú÷”¨€Ô¤¤ÆœòærÎu]O&UK”Ón·9œÎZåªí¢U¯*@©ùvU‘ÖU¦ÔjškÍãÛ| –¨¦P(0pä€â$Iª¥rÕvѪT ‚ÔC»ªH뢪Ù[Zåêz(vÂ)œ3MÓ­áÞ%‘º³w£Ïç’D±fÊUÛE«FU€Jêlº]1]Ók|¸·$ „ åµ®†¦‰åÈsx0ÒÔ21=‘HZèÚë實ñÁ|5S®Ú.ZYUa/\ ‚”Ý®’áðÁ×^ ÷örMSÆo?ç÷øñ¦o]ÄáËn]%?Óu 65³œ9R5­©¹%Ôs]~­[®Ú.ZéU¡¨ PAÊkWj4úÎm·={üñ»î¿ŸïÝ+ôõõýö·œ;÷•o~3üñÇfo]ªVvë*ÙÕrÎ≄ªªŒ1^Ü(O¯×›Þ«j✫ªO$8gÙטRËåõz+U–‘ªâEËH©ÞY«`5S ¢U}´]ñ¢G{G?ûlÝÅ;λí6÷„ éL3.¿|óš5<ë¬ó~÷»Ö¹sM\äò[WÕŸdô¥Šw-P 2ÎÑ蟵G}”²xñâô_ÎùêÕ«%IZ¼x1NÙ¨µÕ5í•E‹”ýûgß°D$‰ä„S¢¦M×§}õ«ô™gþ²páWß|ÓÝÑQŒ°–°Œåeßû9ÙfʘhÌc~rþà‰–ëWE–%;[j;ûŒç4ÑË–-[¶lÙƒ>˜N\µjÕòåË—.]‰D nVdÛ¯~•xûí‰óN‰ ‘¶EW‡I$9¸C íìïÛÝzå•qYn™1üwÇ„;%©)¬Õ}F$µ¼2²…B¡Ô†×ë }Ž×ëÍÈ™ÚÎÎ3BªW®|…61Õ\L^´tʰe)-ÕOŒ‰¡Pˆ’nÆj¼þúë !·ÞzësÏ=Ç9ÿõ¯}çwBnºé&—ËU‘ª7¶Ð4mÇc5ˆ‚ÍW›”Žã¦]wKŸžØdÏ”›–zºº3O ø¸Ñã9ôüóÑþþœIµ£˜[—1[ŠŒ}‹L¬Rëªz! ù|>ãŸ9ÓŸ5Ó —%£~LBÆO*òt ›­ÈãpÎï½÷Þ@ °nÝÚü`ù~ö³›šçÏŸ÷Ýw«ª i³ÑØGµ:¸päýØ[ß"É/ÜxשË÷lèš¾mÝï{ï¼¶ùÓ˜>Qé{ûíÉÿ÷å59£z¤·‹O¬Ò(kMFñJíóÅr¥(Ћ,Ë‹¬iÚ£>òOßûî¶m[×þùÅ®©“O:iÆOV=µªÔ:$s.É„ÒD³3qè½ÿó*ûWnþ¡ÃÛ°é¯/õܽpf³šH¤‹Èa?xp¬¬¥¶H;í÷ûƒÁ`ÆŽé~¿?}¨ô½@êOã¾#¼7,r÷âorf3&SR“- ú|>ãYÈyF²;l¶û¦`ŒÜ{ßïúÑŠ#G>7®õ·ÜZÌ ŒUô`˜L¢h'„ê„iDÓIPkœó寥¾™|òÜÞ)3yìoö8[˜ˆ’”ï€þ£Í,ÕŒ³[¦ÑÓrFÒºÆþm¿ßoìŸÙҜ͢ä,‚uË•ýË+[@ι$Ùnþçe_þ¿_ù§›— ‚µ.Jg§ÍéLÄH,Höô7λýåñÝsö¿óÜßž^îjhüʪ—?õÍQDU %Ä} #éS9 ~N†à¤±¼« ƲÔ^éô@ ÐÐÐÓï÷MïØÐÐrnŒ²õË8Í@ çÎYÒœ‰æ)š1[ꧦ~ưÌH1&æsµéƒg£ëºËå¾øâK“É$c¬Ô_̃¬(ž³ÏN¬]{äi¿rqëô9‡þç¹ÃÏ\áòª;žÕ¦-üq÷5wõÜxArØÚÚü§œ’O.3î‹”Èas¨1ÚâÉבÒéÆ ™³÷ÍÞ«1ýB_Òb6ƶhNP1»Sö|0Æâñ8¤ÊêI’Z—,9ôÊ+ñ~mï÷H±Àú'|Í*a$òןìèï;üê‹ñíDÔIË’%vYÎwÀ|WñôU¶°(ÆTéBŽ U‡RÚ>o^ø†¤â½ÿñs¥h!bsJɧïÿ*²™8‡ˆëÜsÇ_s ¥ºœ2ÜÀ([™r¤V„½{wO˜0QÓ4 l›$îÝ»KÌ-Z®Ú.ZÉUa“ W³‚,Oºù潄È=êW#2("A%Ž$i"Ä6~Çý÷;]®š,~ÉRK©àt9·mù°¹¹ÅápXeRI#CC[·|8yR'¥BÍ”‹"‰B­­œªˆD T¨,¥^ÂÝn÷ÔeË>>ë,ïã»ßyGà„HN§8÷dyÑ¢ö /t»ÝÄÄw›M,ûB^Î|µ‘¡èÞ½>Ü?ý¤Ù“&O±D›Ø³{×¶­¶µ5OštœÛ嬙rÕvѪQ Rè:;Ü÷é¾}‡Î:ûü’.ጱX,†úû‰¦)--~¿ßår‰æžbX’„h4öÆë/OžÔÙÖ:NK܆¦‰åH-clp0rðÐá¾¾#ƒak H÷zÜ­­Íím^¯;_$ÈŠåªí¢U£*@©“KøÈ/äeJmÊáÇbñHd(³ÆÓaEq¸Ý.Eq^Èråªí¢U©*@¥¨“KøÈ/äåKmº÷Zå-FJIñÝÏZ“–ÔpѪZ "ÔÃ%|äò2¼1´ìÚlصZ®Ú.«åt*Šâ¨ù±##¼ã½Z.áUR ZZ€Ô¤¤ µPßD£1H-TWg7nì‰Åb”1v¤¯5Õ ‹7i–¡. J®VQ±XDˆD u F ‚ KÒу$ v‡+‰ÄXÒ*£³7öÄãq§S9©{Ó4»ÃMÃáÆX°ÿp0ø™®± ǵ¡¦`$ÄbñÏŽEIðûÇ54·ËvÇÿäT3é²t8BIEND®B`‚veusz-1.15/Documents/manimages/createdataset.png0000644002344000001440000005706211734662204022032 0ustar jssusers00000000000000‰PNG  IHDRä.q8ésRGB®Îé pHYs2v³ltIMEÚ/SsǦtEXtCommentCreated with GIMPW IDATxÚì½y¼$U}ÿ}NUõÞ}·¾ÛìÛVgØq .¨É“Éc¢¢‰?5Ïc4à„ƒbA8 âÂ"åyþÉãËÉnĨ€ìËÀlÀ0ûÌ]ú.½TÕ9Ïuoݺµõ©êª¾Ý}?o¯MÍ©³Õ9UŸúÖ·NCË•jM­&d9NS*qÎ8çdñ ‹•%ím­’hKB<ð8Rð?„öhlÞ D)5¹R)Wk5Âu¥¦V³©¤$+Z­¬3)5Nh¬iÀºÐ5¦áR¡ÊPÁî ¯tÔ¹)œ›P£Óp꽃꘺Ö~ññЗ'w‰Æ¯w^'¯_4?N* å&lºq±*€<@·„ì^$:Ø~Ý (•%%•J'erzZIȲ$+juªy5­ö‹‘²¬î`9áT w® öxFsìà„ÐÙ3–Ö=§©ç™Î…–G¶?¥*¿†“Ž?ñ!usZ•s]WÓ©|:•”Òé´®V*uØšRJ)UUu|l¬Z«Rꢼuú1Ê›bÅ5HÆ”îbq×IÝãq¿ yºUšÛ4;B¥æ^ßÑ]b'¤kKP©Z«¬V+„PNh¨£¤jM­V+„R¿{¶OMf5g´Z«J-ÃM×SÇGçŠô‚®VÒé¬B©¤3­ •¬V+¯¼ºbìäÔôäÌL9Nçrùžž¾U«×äó]ÖÓ"˜a•Í<›OÐì8't1lv·zrדYÌîô5®…¬ö°ŸÁìØ7PßʹÆh VW SUüI™ Ç {oÀúR¯VÊû÷í?15e^é…ÞÞ¾U«× ]"7Jiµ2³ÿÞññ“–Lò½=ÅUkÖòâ™”÷¿²ožIßÊÕõ3á‘¶›Ÿ®ZÏü+£3-A%…sæu3^½æ÷K“åÿýŸßÞ²ål‰Ú.SN}y÷Á·½ý:I’yø«ëÖ zVT’Ž=ò ϾüòËccãSSÓŒ1Ji6›éééÞ¸qäôÓ7­\µ†sƒ6s«~ÐERQñò9çžë:z=·)®×ÄÇmR_Mµ»DêH6YP_ó¿Ô]{ã~œòÕäÀ/7¨ƒy?¢tUyγJ’täÈÁçŸ{ÚóJ?cóêÕëü¯tJ¥£G>ÿü3Þ™lZ½jçŒûgrôð >™œ¾i•¨æ4zŠ àV®ZÍóë Îι×Õ¼aäüçž;ðúëSœÎ ç„J„Â8—(åœ_ýƒ”®¾bû;|•ZÞ»÷å§ž|üùç_|ÛÛ/^¶|E±¯¿Tš8|èàóÏ?ûÈ#ÿsâäÉsÎ9÷ÔSß`=‰Ûظn%½ž3Y…3¬gb×—l» sÝå.V±Í8¥~Ösd+öªäAÒû=*µØ½%r¥æV}ܽûÅ'Ÿxìùç_zÛ;Þ¹|ÙʾbѸÒ_xþ™¹+½túi›¼zRiÏž]s™ ¾íï\¾lE_qV.^xn>“ÓNÝäõ¦„Riïž—Ÿ|r.“·¿sÙòžbi²tø µ&çžfÑœ˜Ìj,–J%»žr†O·qΟb6lØôÒKÊÁƒ:gœÊ”¢s"S"QJùñ_ùÍoÙš5o¸üoÞêsK}ìÑß¼ôÒîß÷œ÷Æ Ì]ýÅþþbÿ¦MgíÞóòüðûªªww÷-[¶Bà^§qÝ1zMl&6¹:&v0+Û]µ…„›ØKñŠGQè°—#ž8.™&^ĨÔ'Oô·¿~é¥Ý¿ÿûðÆ7¾ÉØ}ðСË—Wúž=/ÿðßï3®ô+V:K¡T=yÂÌd¡\û‹ÅM›6ïÙ½û‡?42)._¾‚sÓOÍç3=ùè£s™œgɤ¯ØßWÜ´ióž=/ÿpNs– iNȳÄM¹íˆvïÙ=+€]}Ë–-ó©ŒäSõë6$ëJÑ(%:£„ÆH¥Âþþ惉Äú/Þpq&#{UUÓØ®—žá…]ïÿûÏ9÷<Ætç߆õ>úçÿ÷Þ={w¿¼«\žñ?øt¶;íNg»ÒÙ®p—¡5b*SzM¥ÒùT:ïcÚ$Ó¹H®Îy"™©-‘Ì$’iG`:‘LÛÌSÁyÉæþѹ=[¨Ü/¨nR7‡\ð/ÔKHßÃJl/],¢ l8b7|Ö…{Ë­ªÚ‹/>÷ »ÞÉ%çžûFé?þÉO·¼å¢ÓOÃü[ŸyæÆôõë7üùŸÿÅÞ={w¿üâ̌˕®jÚ‹»f3ñ’‹õÖÔ̤<ã<.MÓwíz~6“s<2Y¿a.“]3͉ά¶ à¾}{m5Ù·o߆õëgp÷®r¥ìS¶Ÿe½víŠD¢røH—Ñ}35šMrÔùÇ;vlÙ[/ʽë]Eï» ;yâ‰ß=qê)§lØp óöÈûú·lÙò̳ϯY³~ÍÚY—–ÓbLg» !•™‰9ÕîªÌ”„mV»}í®ÔÙ×I›Ž7`_'SY[­• ½Vke#OC»ÕZÅÕÄ&A­lÓAÜ—8á:#Œ1ιÎ8cœ{;CØ]·lÖ²´àDõì]ç(fÇrû'³ÔÏÅCýÊ÷ñÎÍ,¹g|K­8™O⨕«§‰RGçDÖÑÚÜ™µ0³áç¢Ô­>~6ÒèÉ£Oüî‰SO=edÃ):c„ÃGŸ{î9_¸áów}çžk¯ýü¿ÿû}„¾bÿ–-o1®ôuë7Xo ”Ò±“ÇL6lØè'Å¢™ÉÚuë­¦(¥ÒèèlM6¬_ɯþçÞ²e‹EsЦæ¬];ë§‘Ž­¶ àþWöŸsî›n¸þÚ+¯¸Üˆðµ·î¸åÖçŸ}rxxh^×xzÒýX³¦7™\wìXÒ¸3‰Ùš9ÌÿùÿéN¥z¯¿^–eî%=”JGùí[u¦ûØoºð™gžŸ˜XK)ç.U²*µuÃjbWfJ„’tÆRžU󅓦R§2…jyÒfeWË“6½6¬éjeÊfeÏ'©L™ÿL¦sµÊô+›ZuzN…­3Vi6Í&SY#Ž˜W„Ûìk‹¦W%‰Dš¢ªBH"‘"„hj•¢$Ró†‰#d6ÏK ¥D¢’,QB乊ÿmÄ—þÅ oÂç AžÙx€´a>á¢Ï‚žweÔóåïÿ‘èDiüèÑ#[ÿè´¹+ýÏþìƒög$„LMOìcuìø±b±8w¥?5>1A¨D¸n¹}ÌÊÅÖ­dÈÅ«¯¾¶fÍjkQfˆ™ µÈ…¡9¥ÙLhÎç?ÿ…»ïù‡+¯¸üºëþΩ9”RÆlê ï ¶råŠÏþšoú{ÆùÛ·ÝrË×wÜòõ/ßôÅÁ~é\`  dkî*Ö¦2™¹je&‘X?:*éz•$foÍšNÿõ¾Üùç'þhkuÍšŠ©H©T*M¤R©îî¶P¬§§§s¹ÜwŒ$¥Ó)U­iš&IDØËÌçeÚ"ʆF§3]éLW¥\2~ÍéL¡RžLg ³º<§Ôæö¬‚Ïéu*]°É±¡Ô¦v¾3Bµ2Å IÍI¶¡ÚÉT®V6”Úîd*gh±!͆('SYg éFvd/°¯ ¯È¬­í¸J 75ZI¤4µjüÓ©Ús×µ¡œPQ¡¤AÌá Ù5YŸÃ«3 =®ÚG£›ãžöË–R:11‘J¥ºWúCý÷í·ßùÆ7ž×Û;»K’Í+]•%:ÿ"щÒD*•êêéaLíµçŸÿ–ë®û»íÛ?edµsçm;w~ã©§’dj‘ º &%{Mvî¼íî{þáÝï~ç7¾ùí\>·ýòOÙ4G]˜I$Jí*€—ÿÍ¥œ³/ù«?üˇ~ä¦oøÄ'>6×,sG¤kæÐ;îeY[«dD]±‚|ìcÙ|žTªj6={䵚ü$r9ÖÝUa zÚ®«Œ±Z­V(dI²ŽJ™žžÙú¾÷ÁýG:½ÀÙÚÓÓ­iLÕÔT2í;S»û#¶–U¯ ¥öð_B¸)ÙÎÜ áž³µƒ\én’=ç Éfˆçñ¹¨$RšZÑÔŠ5¾b±¾½=$އúú ÙðfÍÐØ'ëˆaŒÏeB9c«V®¸îÚÏ}å+7Î/¿ü²·~cçÎoÜxã CƒFVs™¨©TÒÌÙY“·~cç­ß¸ñK7|òùíÛïšÍðo.shN2Z¥^(€Ô<üË·]úˇyøáGÞý®‹?ñW[Ø,ÝÆ%“ ×2ŸSD–Éß^UU¹¦VuBø‘qy°[Ÿ–OjÝådž’¬d¿Ì¬¯ÿ¹$I…B—ªÖª•J"‘0sϤS?úß?H&L_p®Õ´l6›J&C q]èÝvè²iqÏír1‹ü¼Ønµ0íh/É6”ºZ™6%»V™6|#VÛé !Ùöu¦î5¨Ö*†®Ì9Iè¬R§½”zÞgÍ9gLç$¬ÏšSOÛ›ÚO(Ÿ5mÄgÍÝ|ÖDÐgí5€Åm¦™:Èm_ q›ÏšÛ¬¶zOÔî-§ÖóÇÓŽtú¬ëNúÁeÙz¥Ï_¿'OŽ;vüÎÛ¿¹qãˆõb¯ÕÔl6›J¦-Ïe\–èÂLȶOý5çü+ÿµ‡ùÈ/ù«/}ñú¿úË¿`:[I*E›3Ϲ¬ÌÕ¤ZI(Éרyë7¾ô…Ù„Û.ûkÎøßuÇÞÿÞ¡¡¡yÍI¥gRÚ2° `ÕÀ[oûÖÿüÕ»ÞyñO~úóo}ëοÙv©]S)>ë¢nbÍ=Ç˾xâ…ñZiefÅŠ|q [mòõgF_ìJ懶0Nª5’IqO×ç½}}ŒÑ险®ÂõL&¶Ç%UU©$ ]’$1] þUà‚:Ì¿x¤v߈†įê¢Ô^Ñ-ô¶Õm˜Ø6§¶¦ ×ª3¦eä£Új­,rË3½%„D"m8µ}üžó>ëYO5mÀgÍãtFÇn„74C0¿ÃÛÑÜ«ªÔ1Ê4¨5í̹·¯È™ž,tu›á½½Ý?ûÉý}}}֋ݼÒeIbLŸÚO'Ä‹™é)3“O]ö‰±±±»¾sïU{ÅÇÿòÿ2óQÕÚ¼\06ÿ˜©9ÓS]…î•+–ÿîÑ_ š ?uÙ'.yÿŸ ô3¦«Ú\&Tb ÞéE4[Ó¼Nvº !_ÿÆ·¿~Û·¿xõõñ¿¸ãλÿþæ[8gÛ>õ×Ö#¢’ÄuݵÍ«#Òy­ž<øÜÉ]µ~uy®X*×¾÷Ò÷§µ™K7,•à„LÊKÌ8!„q½»»wÕªÕ{÷íݼi³ÿq:rpp`¸ÐÕ5û\à¸ù—gÆ3Ùžt¶»23NIg{!ÆöBû|lï$=ÞjbsJH*ÓE©º)»á¼^˜Š»eå5² ^«LÕ*SsCGøB9¶YÜ …ÞMÐ9ç†{Î2c«I"™](ÜÖAÜôœÌ;OøœË[­Ø&ª£¶+ŸÛ ¾¨}ÐÍÿ’ŸG­ùå–‡.’¹áE0ûÝüIÆYwwϪU«÷ìÛ»yóYf„™™™üéGþó?þÍêðWSsÎ"„ôm£Ì#“BW—å…ÐR°à¾Í›68ðú×oûö ×_ó±ý¹ÎôK/ý+ÆùÍ;¾þ¾÷ýñÐРEu¯6—Ü=ÖsLÕ¦?ôø3GŸ¦”ü/ÿ¿¼þ«•¹e}ÉaBÈLêÌ×T`z!_xó–ß{æ™gÇÆÇ˜7årù©'Ÿ~æÍ}½=>cÂËs2íPêùÒ-ûæ•z60ÓΘ·}^)OÇ IW¥6þiŽ1ŠªV& ù¶(ø| e;o4BI¦óÉ9Õ&œÔŒÀTÎªÔæKHï‹ÇcV#Næ$›$’Ù9¥ž!„zmx·mÖ·áõžûçl¶‰DÚøsXÌýjŃŒéömû#‘þ9ó;[°xAç!Šô(Èl0±Ó¦N朸 Û7oÜzW¡Ë¸ÒÇ-Wz:þá¿ÿk2™t¹Òûú,%³:[03µêÃé§Ÿæ*½½½¶'ÎôùLÆGý4§23§9½.šÉ3Ó ùüœŽ®X±ü7¿~ècñQ³—þõÇý??è/—ËO=ùÌ6mî5»ù¾t]«zù(y`ÿO>ûе›ûÏú»7ú=ÿzIWªë‘?ÿi!Ñ-Q®éT’¸ªÓ”½ "I’—{ì·O=ùØ[ßzQ¡àâ®T+=úèi§Ÿ}úg¦ †­ø;Chd{hø\D-?aÓ1Ì<ÍžñC¶_½Rf}ÖœpÎ㌮‰˜Ïš‹$8)⳦”7à³vó5[/Ÿ5÷Y›Í7—ÒýæÌ=ÇY» ç>ã¬yΟ÷Yóùž{è TÒun\éo{ÛEù‚ËËüj¥ò裿=íô³O?ã ™”â F%™™™¼õ¢|—K&•Jå±ÙLÎL'‡Îr*ÉŒÑùLÜjR©–ç5')ÏÏD)‰l¦hN‘$Å"€¿Wp¯Ì¼&Ôk†T¦Kñ/®˜îÛ;¶·¢U.ÿɧ•}õí7)¼›1B$¢3¢ÈD¦~.^ÆtYQÎ>û\I’øÑƒ›ÏzÃêÕ«SÉ”á5«ÖªG}î¹ç/Üòö‘‘‘L*¡ëªýþF©—º¡=ABm¯½OGëìàNÿîÜåÒõnJ¸gßmJ”ÊÆ·T¢Äü_3K7ôÝX„s]•„‡Î\(¥ÈÌL<ø%âé õswø¤æœ)‰ÄÙçœ+IÒþëÁÍgmZ³zu2•6ΈZµzä葹+}c6“dºæ¬gº¢Ìeò£7ŸµiõêÕ©Ôœ\T«Gç3ɤg3±ûý™®(ICs~4¯9é9Í©,МtR×jÑÔ ‡-1Ø„TõGŽH’$É UÓ5U]x¥+’$¢s÷+}AÎ’¤È>™p3½î4(’¤ÈJÒMsI¢¢šQWJrBV’𦻠×kõ*ÃS™.S¬­§½÷—" ]hT’²¬"qÎ%‰2Ƙ®² KÄ!ÙQÚÍTíè…{1ä»á""Vë&|rZš£è5š¸“)I •eB(aœÊgŒ1ÍM\Œb3X’IR¥„q*Iœr¡šþU’YR•8ã’$1¦‡ÑœˆzV’’œ „rN¨D9Óu]u8sÈBæs–µ]¬Å.‚Æ$;¢ ™6PLd†v“U;6áŽD¾ÛÅ¥ÑZî“8¥¹.7ÌØ@çôV"ëÐù™Ò4þûô5v4®n.…XFËsNå•™ 'œpÂçÞG›óA˜3#ÿŸ‹2;ÄÑlïT*U©Tç‚øÂ(”š;èÜ ˯¹•L¥ˆëŽ[nY̾؛¿bþè¬qddÌngn[£³-(' ‚4'„.ÈÚ–ÐÚÖæ$¶ãu¾¥ªÔÙ>Døø2û]Óì/Ÿ`cÌOAF¡„ÌM9÷JY×´¹|¸j&š ž·Zf‡æÌ}DE M(IUWÍ14³Q¸µ^ÖÄsÙÏYÆV2™TUÕ4+,ó‹-8"J©ù’|6Œ›™ÐT*e–G-Ùs[#n­Œy€–Ú‘jµº°*s;æþË9/xnôåó™¦ÓÛLVËÚÌSrûŠ—ÚmE¯÷j”Εë›hu¬5^×póûNØkp“g€¿JÅý$T(¡ÿáÀV‹ý êÓ®·þ,¯gÜÎúò1®yݯ‡Œ%Žê|zèæ±Y˜5[%©ûL½ÍfɳÕ\Ì.íSÓ²ž 2ZŒ½þñÖG«#7!ça‹N¹ÂŒ‡h§Ü$â%ÑzKÚú&$>§]|þC8H¨uÊ‚z9‚ªFð÷¶œ7¹+£]2-Xý,¹*Í:¹ë/=%vùF·ì­9 Þ»á½'¬Ü$Ô¶Gt˜µßpiû>›­R»}l+ÏëˆÒFNnÚ¬³4‚+*i&±»ž[@£›+Ó$Ò™ÍðO*16E]õ£ö•@xè¬B›†œ{º#ꟚT8ûäîܳnnª]ÿ°|…›Öy *Ž^‹¼ñ¸e´…ìnÞvŒz†´ <ð²ïáîl<Ò“‚‡ÍíäTãýÔdi’³ÕrŽ_üâ¿-þKÉ çž}V±Øò™Q×ÕÊÌD»(õèèø£?6<4ÔÝÝM }˜˜˜8zìØo<¯··7hÚt¶»Í,ëÝ{öŒlذ|ù²L&‹¾´åòL>Ÿß½gßçŸ×ùnY‘†‡‡òù:Ð^äó…áa6>1.¹ÔfG›Ëæ´)™L¶ÖÖ ,Ö¹BÝönÍlOè½Q•®bÍN”X¯ÄÀ¼8‡qƒLOžðÒë\¡ßø³)»5ÐÁ5UP5“í±ÙªFˆhÝëºíÌÁ+[ÿrEòtwÞu÷wÝÍ-cê9ç·ß~×]ß¹'’üÃM‘Ê‘9!$W˜žG©UkW^y¹¸sçm7|áFBÈG?òáB!\i]Ü !Œëã¹Â€3Ð+rÝ8 ¢z†>šriÝöÊÊ?Ûº…B‹X:twuoÛ¶r͵×ßwß!ßýî÷ ¥Þ¾}ûB¥ŽÆ²¬×6ñu*¸áR‰¸GbÍĹ׼ ØÄ:~ëÎccc>øÀg¯¾æÀ׿ù­Û‹Å¾­[·Þ²ãfÆjшµécip‚‡…ú»Xæ¶ oÄUm½öºº_`eÐùjÍÕ{ï¹ëŠ+®ÜµëÅûï¿dú3Î8ó¶ÛnkD©ç༱qÖ®ÎF,åXMl«§ØËkìj‹Û×u-h˜Øt0’DnÙ¹ó /Ù°eË–·Ü"I‘Í¥¸«¸ Ø2U:žï0B¦J³Úm ´Åwn›©ŒíH¦'órGÔoKè $ _z%±–ëãÅ @‡™×™tâªÏ|ö¡‡ºøâ‹Ó)™»)Z8•£º®–§ÛF2^zy÷ƉN @kB©$Ë ]W9g¶]ªªîÙ³ç”#!< Z¢´®9Ó´jäÙë|—§Oyªt¼)­Àq*ÚXÊYH &ÖÍQdÒ©”¦épƒÚMÓS©T¸´m6ëÞààÀdiB×µ¶_ °ÄLj]Ó&KãCC!Ǽµ™Ï:›ÍJK$àmwBEÑŠ­sq7´;p<7ªªåó¹T*½$Äš’NgÒé :>Ž MQ…Ãh,¹óQö4®Á%«4Ð…ÏÞ/t)µit¢ -Æ•KàLä€6¨!¼d›·h^`)XÖ8u@|âB—ŒLáòÍk a´"š Ö Ö±Ð"„ÁÈ9×4]UkhD¨K"‘T™†]‘+¤X«ª63S.tõu÷£ .S“²dxÐ IDAT¥RéD6› 7[F˜4š¦O¬Y»‘3½ZB@]²ÙL6·æµWw÷õö(J`í #Ö¥R©«»È9«V§Ñ BU×Ré|¡«¯Tïëë š<ð F]×ÇÇ'z{ûTµ‚Öq4µÒÛ[ŸÐu=v±fŒW*MS)m€ pN4M­T*,øz1ºm€Bøü†¡Ç”ˆÁç°¬Á,¹|_Ý×½þÑÄ‹ó‰®Á D•¹`sÅZ X–µ‡Šû(=–ï<½žžm)Q°V)&âÎ *¤°¬A}óÖfZÝiÿÚ’ˆ˜ÉuKqÍÄͧ\ל^uv­¹ëùˆH 5Xº–5XšLO–¬ùëjqOOšqlœ^{ëÚõÎR¼r èš³ÿø×Ùvóð).Žjƒ%,kÐÇXűñÚÎ-UÕ&¿8õ/Îk¯Ó +¸7˜7ñ|4"±kN&A#Fk,JâX,kˆøCº—¬»&1­{ë–âÍ5N rE^Ö ÖÙV®OqþI\«-Þì`)@uM™p¨ª¶{÷î³Ï}×k:ÓÑ‚ ãJ‚È\’B%åé'ݸqc Y­³¹>XÖÔñ?@©Ü ´(hÐrV9š Ö"Àpƒ˜ó‰`ŠThLqæ°¬ m,k§Šû+=€U3ùB!–5´k€X€XÄÄÄ: …b.ˆNñ#´ ¦8sXÖÐ6–µSÅý•Þˆfn´”éœxÕÏFæ³æøÜÊfHà€6 ©S¤2Æt]×4sN)UE–eI êXâMZÖK×õr¹B¨œË ]YYVt]+—g¦§' ×3™´,Ëèð·¬]T\DéQUujjf`py"‘TUµZ­R#„$©|­V=qüp>ŸM$èÀ’1™[lŠTMÓÆÇKË–¯¦T*—ˆÄ@Ó´r¹,Iòð²U“𦹿/ô× ñ€v'^±fŒM”JƒC+4M¯ÕjÜZ­Æ/öO”JŒ1t 4[¬«Õª¦òd2¥ªªO´Z­–ÉdÕ«V«Î½S“'¬&s¾Ð?5yÂØ0þüMlÛ¶W׽ƶ-¾-Ð'•3¡3Cÿ*€AŒ/9çSSÓÝ=EUUy½ñßµZ­»§ojr4NSÏÞMɶm‡Nâµ×5së Ã?•ëF#G€e½djjª««[ĹÁëîîžššô„4M×\ 2½ªáS=(2 µ,kÆX¥Z¥”2ÆêZÖŒ1Bh¥ZeŒ9‡ñžó×éß$oCƒyÂXd±&„rÆ*•Š`ìjµÂü0'œjGnÕ6xó€¡ $F7¥DV”ãÇŽI’Äë!IÒÑ#GdEñòWÛÌê-ÓÈíYñ 1îÐZ–5¥4“NxýÕÁ¡!‘ø¯<08ÐGÅ&Õ6‡ˆØÆŠØöz:Eßo¸jÔ®PÄ:JdY. :|øðÐЮ랕P”ƒªµJ¡PðùîÜ&gη|Öþ/="™ˆW#D• v±&„äó¹çž}г½ôZ–åÇ?ÿÜÓëׯÍçsÚÐV» h-±N$CCårùw¿ûíÊ•k7nܘN§9çŒ1J©$Iåry÷îݯxeíÚÕCC®Óƒ¸:4Ú®¡!ЀÅšÇ:Ej.—[µj%¥ôµ×^Ý·oO±Ø?44œJ¥ªÕê‘#‡Ož8žÍ¦×­[³råŠ\.™ Mœ"U’¤îî.E‘óùܱã'ÆÇÇw½ø¬¦ëŠ,gsÙU«W ô÷÷s¹Åz½àmY»«¸¯Òó…u ”äó¹t:U,öÎÌ”«Õª®3Y–R©T6›Éd2Š¢—`/ž"UiZíE) ù|Þü \’$XÓÐZb=geS, Åš“ùÑaæ@ë`T ‹Õ@;XÖ^*î£ô³±8Þ@p{ÙÒ€ú ËÚˆ5@¬@¬b  UPÐ-‰'žxò‰‰‰!„RÊ9Ç/~ñÛâ¿„®®®óÎ=·X,B¬—£££>úÛ¡¡Áuk× 5h#J¥Ò£þö /ìíík‚XÇ8E*ᥗ_Z¿~ݲeÙL­@Q.—s¹ÜK/¿|á›.Œ!û&N‘ D$:44˜ÏçÑ´ù|~hhp¢TŠ» fL‘ ê’Ëå2™,Ú€v$“Éæóyaý$-7Ej¾kpªtÌ?¤Á £ª'!Ä?瘊®ËW¾úÍ뮹ÂblXÃ]DÌ æ.’ÆuF M vÕnsÛ5ÐaÄ;Îzªt̰[]YÛ¶±‘ï´†8“;gž^i%ŠæS\ÝŠ5bSšÏÝ÷üÓÝ÷ü·ÌºÄ9¿ó®{ï¹÷Ÿ;ʲmçZ]Vã×Ág/©ç„1¶{I hu‹«S(5‹N©4yõç®'„T«µí—_jÞöÛ¿tãÍ„ÿ¯6sP@ì_0z×^‘]·ýëfÕHå#‰Ùâ¾ €+ÝÝ]Û¶m#„\ÃM?øÁB¾÷¯ß7”zûöíù|¡Ó,kW6Z{<¦»‹k‹«› ÅáœïøÚWÆÆÆ|ðk®ûâëÝ~ÇÝÅbßÖ­[oþêMjmjéºAⶃfèz an·þ‹V€+š:}÷wnÿô§¯ÚµëÅøñȆugœqæ­·îl²R“¦MäÈÚˆnB^1]ßRâD ìkVÙqËŽ /¼pddÖ-[¾¶ãkœ•›_æYÖ6·@8-3š†ª¸·A0¦W4ëËFçÞF*&Îu×\áRíˆÐ¢Èúß^õ™_üâïxÇ;dªr¾ßRM«MOO ªÚî={Î=o SË:ÓБ‘x9ž{þ¹‘  |ý@‹"Ir"™Uk3ŒéNUÜ»oß™gœY?9!É©'ŸøõÆ‘‘@×{®0uh0…- czµ2¹ˆ—0ˆ€Æ½j`mÕk>Z|±ÈårºŸm‰®kÙl,_ÇXÅ–uK088X©Ô8‡‘ @›™Ôœ“J¥644wIŠMÅ•ž`‚ÔHÉd³T¢“SÓxÇ@{¡ªZ.ŸK¥Ò&H åÌ€4´ ét&Æ21wàˆ5ˆ5@¬´ á_0r±Ñ#æt3¼f²€6b kQ`ø¬M7 ¾u€ÖÁgËÚÆ²vª¸¿Òó…¢0ÆtišÊ9§”*JB–%I °¤,eB?›47ˆ®ëår…J‰|®»»'+ËŠ®kå™™©© ÎÕL&-Ë2ºS¬UU›šžZ•H$UU­Vk„Ô!‰djphE­Z9vü`>—Å„sàEì.MÓ'&&W,_G©T.—5M3§Ô4­\.K²²|ùÚRiRÓt¯Lºº‡­‹ÒR®åŠWf±ª½XÄq¼¶Þ7¶—ZÃXÖ±À+•JCë4]WUÕ5N­VK&“ý+Nž8ØÓÓíåÂ.M±^¥Ö."‚Õh ·ZËRj×ÿ£eÍ9ŸžžééPUµî,"µZ­§·8Y:‘N§)îm5©\·E"˜f O*›ùæ4å\Ó6XÉɽ¢™õ¯@ƒ1ÕßÕ †q –ž$Ò‘{Lg““S+WÔjªˆÃ¤»»çÐÁWz{z\G†Xm¨Òø£hsÃVñö8Ü­)¸_rÛ†½DŸ„us&¢‡àS¢K)¾9xf+S¼1Ã׿n4ZÙRëÌPHlß/2Æ«•*¥c¬®eÍ#„V+UƸë(¾Ò¸»ÝÔÕSçA¸n„S…˹4~Ä t=Ì•ñ*Å«©jÝúÑv«e×çæŒ±J¥"¹Z­0Æ‚J’©®ŠV7‚1‰ŽW}¬¶¢Ã‚³þF>ôÚ5¦kU}êˆD®%§‡£îŸ`|B©¬(ÇŽ•$‰×C’¤#GŽÈŠB(u-Ñ«&Ɔ¡bÜí!C0wóñ }ö·‚|*ïZkÒÀ1rú[s<4k&®Uu $Â-ì_0þð×âáÄ–„ýÜ\JI:>pàÕ¡!!{ðõ¯ö÷÷Ò ÆýÄø‘îžaëFÐþÙÛfxwÏð„‡Ùh-Èu—îZ¯âB‚3C[œ•lñÀº¢ýÇâÓ/´°ÉÌiM5­6ä5ºªj{öì=ïüßÓj3š^çÍáØØøK/í^¿áÔ¡¡a]÷üæEQ”C‡¾²÷©§nìííé¼niA5é$ƒXƒvA–J"óÄã¿Ùè›íB×P¼_0æóùâ3Ï|øð³Ï>50PÌçóÙCìz¥ÀŨ5÷DBœ)Wì׫V¯ß¸qc:æœ3Æ(¥’$•ËåÝ»wxmßê5«]o5^2×^—h«Õ¶cJ ÖÑËeW­\A)=ðÚþ½{_îïN¥RÕjõÈ‘Ã'NˤSkÖ®^¹by.—ÅÕ‹#Ö’$uww)Š’ÏçŽ;1>1ñ G5MW9›Í®\±l`°¿¿XÌå²”bX$±&„PJóù\:.öõ•ËåJµÊt]’åt*•Éd2™Œ¢`2kXl±ž-I‘ …|>Ÿ3¿|‘$ Ö4´–X›V6…€ `ýChËšÇ77€à°¬ Í,kw÷Uz± ­xªh0ýTà€ÖUõ9à€6 e½„³—ƒ ),kh;˺Å8yrôÉ'Ÿ,•& !”RÎyÿ …sÎ=§Ø×‹ÆDc.©Æĺ5ß/ŽŽ=þØcCCƒë×­] =Q*•~÷Øcç_p~oo/¹DÔõ—´e½ûå=ëׯ[¶l8“É,…^)—˹\n÷î½\ðF4&s‰4&éi©÷‹²L‡†;uí'ù|~hhp¢TBc¢1—Nc.Y{9„* Uº…!¹\.“É.©.Ìd²1IÙš „}–Gƒt÷,‹<ϯ|õ›Æß’íºh[u)·dÓõt]Rçp"`f4ó•OÓ‘:-k¯gÂ1CžG{1\wÍÎí¾—ÆÚªKC/üÚjbüP|ézº¶ù9Ì›˜ÊG—ªû‚Å/bbüPwϲ‰ñCbã‰Ë²îîYnþBÈÝ÷üÓÝ÷üçóÝÃ9¿ó®{ï¹÷ŸC·jwÏrã¯Á;ßÒlL[Kší‰Æ טβîéêÕì‚'vÝîóÊÄY‡º5t-4ô ‰eã‡æn€.b¹k- J¥É«?w=!¤Z­m¿üR#ð¶oÜþ¥o&„|ø}PÐ!hmUkóÚšZ„ö—¨ÓÃ^Cc†lLç )rº:#øŸØ^½ã“`‡º \#;E/Zš÷£ÑyÖ¿¥cVwwwmÛ¶rý 7ýàÿIùÞ¿~߸¶oßžÏðä±è¹¤NÈÖiLÿ˜‘ä¢gmI w#oË,pSq¾ãk_{ðÁ®¹î‹¯ÏmÇ8ÿÐí±×5¹ßûo)®Z7D2tm¥º‘£ª†u¯qtMÝ|è8Ë:bŸššîêî6-èßüæ7Ï>ûl×O?ýôoû[ÓaÒÝÝ355Í÷Ò5럂 6-yLEG+Cþ¹…®†uoè"B/”t1~nΫT«”JŒ1ò>|øp>Ÿ·Æ9|ø°itJ+Õ*cL–%q- j=9…ÞÈÄVŠk`ôîãJñ:@gþÍ9Fÿº…èÿ|šPÄ o†dV¯+eóŸëÖ­ëêê²FX·n¹]­V‚®íõ€Ú€µfÕœ§éÈKñrbxéuÓ<6…à^ÿ®iN“ЂnP~o(¥Š¢;vL’$c¬ÞYgõæ7¿¹0Ç[Þò–Í›7»$I:rä°¢(‚ëp¶›kL/¯ë¢[š ¦m—è/>óôï6Ÿuž—^˲|øð¡gŸybÝÚUù\Ý)þ࿈Ù"Õ ãÝ%áÝ ”Є’àLçœÕ}(ËRqzfæÑßþÏšµ6n<%NsÎc”RI’ÊåòîÝ/¿úÊÞÕ«—÷÷eYræÙ׿Ú5óѯ9C‚¾¢l;¬G½ˆÛ"Õ ½ãLO( Ü6³º¹$Sc¼Hq¹|vÕª”Ò×^Û³gÏËýýƒÃÃéTºZ­>|øÄ‰£étbÍš+W.Ïå³®Žž| } èç„pI¦!fc ó‚‘J!Œs!{J’¤®®Âš5«òùÜñã'&&ÆŸ{î°®i²¢ä²™Ëú‹Å¾\.Ûà8hq(aœ3jÿànJ%IbŒJÃGH>—M§R}½=år¥Z­êº.Ër*•ÊdÒ™LFQdBDs€6…—$)„mƲ–(åœqC¯ÅKJÈ…D>_È1Æ¡„„¬1´µ\K¡t/”XK³«ºpÆ–¨4kpsÂaM–á|^BCˆu°Á{²,qÎ8á°‹ €]Í !Üuú#oKÜ*ÖA´šRãÿœ±º9C çœ*lèr»eí¶ÇóÎÀ Ÿ5ãŒp °q!†„¼§ÔjB8‘%‰pBaU@ 8!œÔ1bè‘$‰Æ‹áuè\»š1I2ÂNf4ˆñ‚‘Êá€Æ.%„yÁؘX*BŒ!h}kÂù¬„6C¬Œ„ 7Ã#R³>Š¡”ʲÌãÆ¢*1ÆdYnÒçæ”’Ù1Ö0¬@\< '„‡ûš0ô FNˆñe !8%„óÐ/ƒ­C)¥TæœN^0@µæ„Q*‡ø„1ŒÀK…R@µ&œHR/Åæ§¦„¼Z€PÖ5 cì*$Îs4`‡Ï#¡9 õ ¼`.€æË Ö¢@ ’s®ëz­¦¢ .Éd"܇æ±>DÓ´rEíë[>´¬}u)MŒŽ̤“Š"l%óù0–µ¦é¥™‘g1V›™E@]²¹\.ÆÞÝOwwç%ðçÁ—õ"d²4ÙW\ÅôÚÌô:„Ì\µ–Íõõ—MŒêíí š\šiÑÁtML”ŠÅþZu­âÔjSÅâÀÄDI×?ŸçÀ£A8g•JEÓTŒÊ€€úI4M«T*˜™);Åš‹ýhH·Åvf¦üēϔËeŒ³€Ö%›ÍœwîY™LbÝÉôô­°þ‰ÄªÜ¦¥ê¤Î ÜpÎI#Å€¶`|ô U¬ÿ-ÛS˜nÞâjXÖKK¬æ›Íâ66¬ÿtÚã"¶|¼¶}ì}Ÿ´‚UòÙ묞ÏQ8“øçºÑê©kéut’–õÒ}è6M9cÛr#йW<Кxé!*\7¯½"1Í£h¤)bª¶`> ÃÜ °¬ñÐæ‘<.˜½)(qÈŠxž¡KwMØ`¹QUJÝÙnr¹ ËzI×öZ½(6Wxä¥7X¥Hê#òˆ®†QU;ÂÃpƒ€öpƒîu}è!þ¥‡³Lô8Ó:Ý Ö0òjà ÒÁd2iâssо®«ª>¼T¯A«<Žk>yŠç_ÃÅ" ÚŠùd`Y/!_‡U+Í'hçóõšóA[ü‰ÞßÐó*Ý™g#þq/3¦Õ½nÛö± k_µACUµ2vòÕÙ$Ð4mÏžý¼ùjeRÓkhA°dn{+ÇG_G;€F•T"™{ì7?Y'2«µéîèíÃ8kh¯nnú¶áäKбÑ×qƒ±ŠgÐÓ)ä²^š¤î„|mAðe½ð(›Ìµ–5´k€X€XÀÁxÁˆ±{ЂpXÖÐ~–µ‹Šû*=†ï@#ör`!…e mÄ–˜X÷ö­´ýùGnÇöj‹j×m|ñÃŒãxEªGšsB†È§É'Uã熑ƒÈeÞÌš·©¤ˆñâc>áoë`oßÊ+ùñF[½Àhvi³žèÇö¶¬ýïxÖ›°b3ÉI¼Œwç½ÔšÄ?7W‹@0жíUHæA‹Ùkk/CÕ«¸º™¯ v«³h‘®ñÊ‚çO¬Õ)*ž‰ãDuM‘Ø^6†èè¦Ñ«›7b2„x?ão¶X³ŠêGܧÑH’%E#Ýá߀zDÐÄó÷-4Ò•‘[‘¡Ouç•(~¡5§iŠVx›ÚZã¬1±ÉÄË‘×̃òz··UÕî{#m%n‰5 ”Äéö¤¶Ñ¶C„}ÔHAñµIÐ^kGËÚåsIÛ‘Œ°D&¶íÞ¾•c£ÆFXž;XbrÇϳ·o•±aì2Š3£¹ææZœx ™•Xµýc[m{ûV…Nâèš™,¬˜g ­e™íôx}ú+P4[‚7~ýæµ5 í„¬Û#þ5wÝ;wƯ ûôˆ30ÄIèzÎÔ=:…šz}‡IDATóg~Ãy%¼¦|ìÜhzÐÑòuŠ ×Î¥„Ÿ«ƒªjeìÄ+â 4MÛ³wÿ›Þü®Z¥¤éµÖ³³„º€~Vp9‡qb·…2Ô-EVRÉdþÑßüldÃ:E `+÷×(èE@+ˆ©«ÛÅ-º¤õ:òîø;î´åSq±&r€6b kk\ß$tX‰ÖÙZùÞ¾UÆ?ÍVk¢óñ9.ÿœ«þ®½m¡-Ò5­)‹{P `¾!1!uê ±¶>.Œä[Ü«£3-k/Ó̱Y ®¿‚©\óo~=m!Îäþ™ ‹OB‘@ÿœ­ás8âlÆ»W¹âíßHSûŸ`Ò†î‹@õ÷ê£Fúºi‡Ö`‹_.™ºg¦W’@ÇÒf–µÓF³Ú"Û‚1]7š\Oã£)ÿäAA¤Vâ®Çë´ B´†`kæ ¶è¦v­ƒkƒø§m¤/ÄOŸÞ©{u,ú¡5ÞŦIâÄ}fÖí‘ 'aXÖžlª'˜OþÚ+ªzFRœøá æU+…H.˜$=ɳªxÿFuì×?D#·Â¡‰tqL—XƒEÜ'a ù¬ãx7Ò:õŒ;s/[Øv¢¸ÆZáÈ›K0ÃÈT¼‚}áŸO$õÑøM8´ö•‚ EÄz.¦X‡8ÛSFËÖ³ ‡ão;[sŽö¹,k+ª&Šé Ô¿^{ýû"Žú‡8švhq\q¿ë W±¶wƒ4ÙùbÚqÍÙõâñ‰&˜IÓº Ú<›lчhL××PâùGXÿ§q¬‡Öü§Ìh[5&µiË:Üó‚™Êš¼Á˜^©|®±¾¯ðJîZIŸ:ø×*„$h+Õm@Íå4=üÝ罿xS“znJÿq¾óO"Ò>õdù·Õ"ZTµx‡6x!ûþâºA:mŠÔHžzÚ€8h‰)RÛeÊÁvœ"ëvÑ»p£ÁÀbõ À9ÄÄ Ö‹O#cuÛt†ÆŽÁg(q»Ož)x Ñ~Ô'Rh#ŸâÂX/xa:òŒŠéÑpãA»Šµë´!&tÞð}2L"8±ªHñ˜âsK Fù~,Pû“ ÓBíAóœ^²9³kúµÿÙåß#!Nç®@E‡˜yöM‹ñŒâ3Ö×Ôk–EgDlÞK"0Á£`’™•Ÿz1Ð $rN!hdŽSÒ¬Ù5EMð,jä4¹ ‚îò?{}Žt¦¤Ái<e÷ÄžA“„›ß5Âv ­ñ)(ã›^R0Iä³kšCª¬ÛŸ!â *¦¸Cï`Y/‹î\ :ç@¸ƒ :µE›^™±NéÕ:§M|³k9Sµû):D¬eÆTƒ%Ó&¨Õ)XJ¬346­%ã>±e‚žAûÑ™ª­O £¤És.ºR‡X[|ŽÊ £©Zç%~㋎7§8ßzã®ÛHNƒÈK°¬ïEa·QN‘Š~mf; µALgN­øhdŠT|nÀÒÅùP¥nY¤h; Ú´v@kƒ8N$œWKB¬@¬b b b kkkè4AæÙç''§(¡”RÎ9~ñ‹ßÎû%”äs¹³Î:³··bÝ~Œ—žzêÙááá#Ñt6O=õÜ9çnêéî†X·{÷½:22²|ù²L&‹Ö ³)—gòùü¾}¯{ΦkñÞ–ÚL1 EÊç ¶ðL¶§<3îšÄu—x à^qå㌜ÉÎ>ᮬ!f|3 íB>_fããÑŸ·°¬›Ðyy§M•Œ¶Vu©±]ž7ÍøÆ† Úø*Èd …B늵ϪÏfÛu¹£%b€Û O×½®¶j]Öª•®¥ˆÚò©kS;õÚï™Ñakwêm twÞu7!ä²K?I)5B8çwÜñY‘/»ô“±¯ebɉ¥°J…—6ùø¬ èèeÀ:K ´æ#¢¼‚m+ÑV.ìkЂL”JW]õ9BH­Z»òÊËÀ;o»á 7B>ú‘ ùøJwœ5æÆ WÙ²úëZ]Õ[DY4ж‹y“@׃֡»«{Û¶m„k®½þ¾û~@ùîw¿g(õöíÛcUêˆ-kÓÒàºãXå³AwŠà^«z½ß á·‰ã¶@kÀoݹcllìÁøìÕ×8ðú7¿u{±Ø·uëÖ[vÜÌX­sÜ °Ç›æZÜëêie3€EVk®Þ{Ï]W\qå®]/Þÿý#ÖqÆ™·Ýv[ÜJ½Äg½äpZ“ÃÇÚtÝ+nA·Îó‹Ž$‘[vî¼ð GF6lÙ²eÇ-·HoB¹ñZÖærœÎu9E;ÃÄæœ¸úL_­WÁÜk‹&H¾$ôJBŽ™óò„3¨]3ôÉÍ¿Ah…k:“N\õ™Ï>ôÐC_|q:%sÇEÎcPïèÅÚkýM熗w˜„q=¨WÁõÍaˆ@ñ$âõQçÝ&ÐhÐòëí)|ð—èºÊ9s»êµVwƒ'©¤¬ªšý¸ÔÀHjÐz­iUW¥VU-™ˆÞŽÑ âêq^‚/ûûû'&Ž'CŠ’4Ò/eûJ :WÁ¹¦Õ&&Ž ô·“XcP‡A6›‘$rrôxBÁÇýt8ª¦e³™T*ÝNb LÒéL:A;BŸ5@¬@¬`‰ÞgÍ çœ£@X7Ãk&,kh Ö±±ˆ5€V_06ƒÑÑñgŸ{arrŠJ)åœã¿øí¼_BI>—Û¼ùÌÞÞnˆuû1>^zúé熇‡7ŽlDkÐÙLLL<ýôs眳©»»«¥Åº¯¸zÞœ<ùZ3Û¨¯¸ÚY¢k`“Ù·ÿÕ‘‘‘åË—e2YœÊt6åòL>Ÿß·ÿµsÎ~C늵M[A(½„„’Êç þÑ\gyÜ+Žx>QÕÇ+fÝ…c0A6hòùÂð0‹<çØ_0ZmmcÛü5wY·E"˜!Ö>¿^™xåæŸ•d·ÎËæ®+Ó†"›6!¶®Læ%å´ÊùœÉ ]‘g£ÏÚǪ5nCàÌm3‰gÌÑ“¯¹&w-Ñk—ȶHdAyò±ÍõÍpSò¬ÿ$ŽÕëÚòñ9„ÆëcÔ@6’@¯AÓ¸ó®» !—]úIs!Îùw|GVäË.ýd¬EG)Ö†bÚÄÚ”Q«œYuÍUãêF}ŸÏ-P‚*µÏÒ±Ni«H.ŒëÔ¾ Ö|ü¡Áú8e·Áõˆ‰R骫>G©UkW^y¹¸sçm7|áFBÈG?òáB!_é»A #×4ucõ®ø¸57·¨¨»š­¿TÕ²H”.ªú˜k½C@ËÒÝÕ½mÛ6BÈ5×^ß}? „|÷»ß3”zûöí±*5‰õc¬D[P+¼‡ éóõ8÷Z®n&×€¶‚ߺsÇØØØƒ>ðÙ«¯9pàõo~ëöb±oëÖ­·ì¸™±ZÛˆµ¿{$&A š³üVÁÚ"¸×ÕùñúÐNjÍÕ{ï¹ëŠ+®ÜµëÅûï¿dú3Î8ó¶Ûn‹[©I|Öñ©¿³©5“8#xå¨hßÞÒJ 1"ÂË­\Wp£­$ìkÐH¹eçί|ù¦'Ž]{Ýç%‰;®úèçú§ªZ;ñŠxMÓöìÝÿ¦7¿«Z™Ð4Ñ›I{Y¬Ñ²gï«§zZ"‘p,W;×h"Ð:HÃ' ©7DÄu°‡ó¯øýÀëý$ÆYƒ&C©46>ùÐC]|ñÅ=Ý9›4«ªúÒË»6¬_ãb+©dªðèo~6²a¢°•{‹kšáYÊJMI&dUÕlbí×7u!Å“ˆ×Ç•¨êS—ºuƒRƒ¦;CXoOჸD×UΙm¯ªj En]7ˆ×P cdÈRî×þþâøøñDbHQ’æØÌvÁÕeq€s¦iU§÷CÓjããÇú[W¬—¸"ûÍf$‰œXìêš]~ÝÚU8§èHJ¥Ò¡cc‡M´´Xáu[D’‡VüVC’}=…žžžt*i„d3iœÓt$’$UjÚɱRô97ç\—wÛ^¿6ûÝIrñÌëFŽà–¨$p°DP”D3×7{¥Wÿ†ÍY`×'¹ÏÞ@5Yâ ÜœÜy×Ý„Ë.ý¤)Çœó;îøŽ¬È—]úÉxï‘ër†à2µ¯’n»%4X+À’b¢TºêªÏBjÕÚ•W^nîÜyÛ _¸‘òÑ|¸PÈ·Xè Ë„û܂twuoÛ¶íŽ;î¸æÚëW­ZyÉ%ïûîw¿g(õöíÛcUj²¸C÷t8ø'] ˆ~ëÎccc>øÀg¯¾æÀ׿ù­Û‹Å¾­[·Þ²ãfÆj±–½h_0µ|}| 0±MRk®Þ{Ï]ï{ïŸlX¿öþûïÙ°î’÷¿ï;wÝ·R7ɲvõxÔuƒø›ÛþÉùXwÈÔí_œât ’DnÙ¹ó+_¾éĉッC×^÷yIjÆ5¥Xûk«H UÊqlñƒºAe/ ÀËüʤW}æ³=ôÐÅ_œNÉœ·›X÷&–ÈÄät!7?1H¥ZC³ÐÖd³é÷½ï½µZ¥\©ZëÕÚÄä´D£—ïØÅÚÕe¼¤ìÖᮃǦ=ÑÓU0FfŽOâ\ MnBÆK“•Je岞È3]¬áOèîÊS‰:|ô!ä‘ßüöÐÑ8­èHÒ)eõòÞ|.ú)6ái]ù\W>‡v„‹ÄÄ Ö Ö Ö±±±€¶_06ƒÑÑñgŸ{arrŠJ)åœã¿øí¼_BI>—Û¼ùÌÞÞnˆuû1>^zúé熇‡7ŽlDkÐÙLLL<ýôs眳©»»«uÅÚ•ñHrnÇÇ÷íudddùòe™L§2M¹<“Ïç÷í휳ßÐÒ–uÜbÚŽsø%”ÄððP>_Ày @Ǔφ‡ÙøøXä97ø‚‘/ü#£'_í+®¶†˜Û}ÅÕÆŸ5‰b ·Å4fĶF³EöÉß§tg†>Y9ËÿËçó°©X:d2ÙB¡Ë[Zòö¶¸×Œž|Õ¶mÝð‰9zòUk xþu÷úתn¶€¥ÆwÝM¹ìÒORJg-VÎï¸ã;²"_vé'c-:z±ÑV×$æ¶x ¨5mÝ| Ô,q&J¥«®ú!¤V­]yååFàηÝð… !ýȇ …|;‰µ—^[Íçºfx8ã=ò<À¤»«{Û¶mwÜqÇ5×^¿jÕÊK.yßw¿û=C©·oß«R“fÝ´Lmž‡¨ò‡] h~ëÎccc>øÀg¯¾æÀ׿ù­Û‹Å¾­[·Þ²ãfÆâ];®/­žºF®kL¥öñÀÄÄ¥Ö\½÷ž»Þ÷Þ?Ù°~íý÷ß?²aÝ%ïßwîº3n¥Ž×²¶y¢^i3Ð5¦MîýÝÓþ^ï@>ñ tÞå8Åè$‰Ü²sçW¾|ӉLJ®½îó’Ä›pÕG)Ö®*éïˆðôÙp¦ ê ”U#ªÍ8Ãù @'™×™tâªÏ|ö¡‡ºøâ‹Ó)Ù)ÍŒëídY×Åj8w°O9™TUK$8Çègëí)|ð—èºÊÖ˜ªj %zó¢Íºgó÷×tª7¹¿¿8>~LU+&6¤×šVµ]Ôœ3U­ŒèïËzé ÏÈf3’DNž<¦(˜6 €GÓ´l6“J¥;G¬—ét&Π¡Áâ±±ˆ5ˆ5ˆ5@¬@¬,$–b8皦«ªê-‘H(Šl®Ž yb­ªZ¹Rëéì1¤›êú[*MŒͤ“‰>¤€&е¦éSë7œÉ9«V«þ‘s¹|.Wؿž‚¢Èè h’XONNöö sÆ*õ”š¢iZ:êéœ,ëííAg€Q¾`Ôu6>^êëëW5M0‰ªj}}ãã%]w™>4Är_>{Í]!² Z‰ný0ÁUЂo'¥ÚÄ:Œ±J¥¢ª*cŒû²wïÞ/}éK¯¼ò ç\UÕJ¥Â˜è\Ï1Í­ŠuÛô€XÇÂÉ“'·mÛöýïÿßþíß1£Œ ŸU lK8:Cœ¬»|²uµv­á¶â\3 t­vÝœEŽ·‘Z‰»³® ÞxÕí;¯óÄÿ h5â†áµ^dµZýÜç>W*•–-[ö§ú§ .+i®¢ë\N×b[ ]0¡Ï^¯Å|mkquËõ t.â^7gÁãm¤VþÍeÝ`‘T&ÐáËzÞO²cÇŽôôô|ík_[¹reLÏ¿u/?Á‹Ó=uÁÜDÖSoAGA´µ_.¹iÉáE°¬g9räÈý×ýáþá²eËŒù—yúé§óùüöíÛ7mÚS¹ >Ø&aÐË8èËF¯RšùTU­¼ò $‚‚• ×;Qõ2&ÖÆ[ÄŸýìg?üð3Ï✠„:Òšgròø~b‰IfbÝ8y|q`ÝÉ…ÊÒ?R7¡µ8[Œ½Æõ4‹óOb-Å«è~ñ¯ªÿñŠW@°A̽f³[»ÀÚΖo¼2T`¯x/œB¸,‡™‰ªjeìÄ+â 4]?øú¡37Ÿ¯–'5¶`ª&UÓöìÙÿÆóßV«Õt]Ÿ½NžüÇüGUUÓéôe—]–Íf꾜H$~÷øÃ##ë féLŠëO߇v@’ÉTþ…g_±r¹"˜c£·¸&°>RB%IbŒJlgΉÓ],?ô¡ýêW¿ºè¢‹lJmKƒû´ŒA΀ c\’$Jšâ¡åŒqÆmþ J‰"K¯¾²oÕê5šå‹óU«V}èC"n¯ å•Wö)²D©Ý‡SXï,Z;‚^`^­9£R7H±–$™Î)a¶ =8ÉdÒ/¾øl± “Nkz—6Š"OMMízñ™ukWnÉsüèg¼´/’D8ç’f’ÑP/%Ê9#œÛ^*ŠÜÓÓÝ3^úÉï?íôMk×®÷Ï祗vïÚõÜòážžnL‘ è|»šsB¸ÜËšRJ©D7J¶íÍfÒ+W K”ì~éÙ'ÿµV…B~å²åˇ³™4œš€%¡ÖœQ*Ñ& ÝãœSIâœqÎ8±+,•hWw!‘LûûÊå²V™L&ŸÏe2iJ©3+è4('ÄPN¾c =Κ¢(¥¹l&›I×µ•)%X€°” kÂ9 7Î:„„HT&ݬýâ‰è0ÌiÀRRkÆ9‘¨ÂL kY3F(%š¬]BšfYBf_02Ë‚i5甆™›:œXÓYÎ ãL&,Ü»º0C÷dYfŒÂ9>Q€ú)3ÆdY¦Í™uRJ gœŒå€pJx“,k27E*¡/ ˜TsÞ´¡{”R™qF8Ç FãŒÒ0n0/%%‰¥€ ¦5'„K¡æ #Ö”RLåáô:œÏZBÓ@뱈5ˆ5@¬@¬@¬b b b kk€X€X€XÄÄÄ Ö Ö Ö±±ˆ5ˆ5ˆ5@¬4M@ÇÃ9×u½VSÛ¥ÂÉdB–eJi'`Ѓ‚X°´Ð4­òÿ·w¯½mZÇá@pÀ—¬Þ֦餩j5uÚgß›i_ÂJµµi–N–:m‹fL®¹ì…%jÅ7b».ÿï>ë<ÈzrÀVFÖã¶]/Ëœ¥tû½¿LC×u½2ïj–:‡n¯Ë¨fSDZ”·ß?ÿ)M“(ŠKsɯkŠ¢^]vlûP×´\“8Š“B‡Ò„¢Š<¡fÕ›OYYU6ôüæÉi’Ä£ÑÇr] æãAÿC½nW#`)ÆÁ~žPsñ#PYqœHé5›_•hMGQ³u"¥/^/—.à8ŠW†Z¸0ç TUš&áh4“$IÓ4çQ–eM6<ÏÛp–e­ý"išŽÇQ8¥i²hY¹Å€›Lu»¡(k÷«×Ýô)k-„=ÏËF&Óç.N§'—Rœ€y–Þ9)k]€¯±›”2±,kòжm)åô ¢(³ƒRÊÉà¢C¶2á-œläL·hp»¡¦ñ#€\–—Qžªzç•5€±m;ç`Îg‹_ÐÙü§Ëz—¡(k úÒ4]ï.ã8ƒÁ`ö©l0ŸÞÓqœ;÷æ²Å;Û 8½ÏìümÛζ?k¨;¸ àS;Ž3·È™ÞÑÈ}w(TÀâ„be °²þÄuݬw\׻Ɯ»O6èºn½^ÏvvÇuÝ%/»ã•õÊ€ËÓ­ü|+kÊÀÝ:[9¸|ŸÙE‡3`ž3°ûPÜ€`e T™&D·ûçééÓ(ŠÖ~‘F£‘m÷ûýÝÌ|O׺Ý÷šÛ 8âŽÝ…ÚÓ󄢬‡EUEí°vñæ¼Õ:1 #^÷?ˆîþ›Åº&ü››·oΟ}«ªb+¿ø7Áu]ø¾¿2e <¼eµ&lÛ²,ùë/?¿xùãÙ³ïÊ2ówW\¼=o·[¶miš¨FÀw¹B-üËÄ–$‰çùÿóïõuÏúe™¶u|ôèQ뛯ۖu$–Þ4(QÀü¡fñã@Å !lûx¯ÕlAX–i›¦qtthšÆÊ_,,QÀü¡æ¢¬ŠSUµV3MÓØà;¾»Ÿ³’¿ÑÊð^¡(kàáVöEAÀ\$ñ&Ê@Ye  ¬”5PÖÊ@Ye  ¬€²Ìím@Y@Ñ›ºÓyš$Iïú’3ÅᓳW" o8PØ•µiAà‹áP*ªÁ€Bu´’Ýù¨Hé U¾ï£”³iêNçu†µšùò‡WIù£DýpõûÕð‡®”½8JNŸ´9SðeAø_o éâØjE¢öÛåûÿ€ €%;³IEND®B`‚veusz-1.15/Documents/manimages/mainwindow.png0000644002344000001440000042257211734662204021377 0ustar jssusers00000000000000‰PNG  IHDRÁ¼σ½«sRGB®Îé pHYs%Š%Š7!ÃëtIMEÚ;b„@ IDATxÚì}w|TUúþóNz%)Ò„¥¦ €Jµ¯îÚuWQü©`Á¾ö¶Š]¿Ö]]{ݵ¬])ÒS¢”$H“&$žLyÜ3É›;3÷Þ¹3™$çá32gÎ=ç½ïiïsÊ{ˆ™!áD”àUQgæ·ˆh*€'<ÉÌoˆ4¾PÃÌgKFeOð$€G˜ù]© uçAgj‚*ìð€OØ†Ž–ˆ ˜¹Xj=jë½ÎðfÞ)õAÿÀ,¯2ó³Aâ> àt³ôp€;™ù²fuª:q>€;ÜÊÌßHØ¢Ó‘^7ýTfÞ$½kÌp3¯!"°ÀOÌ|¹*^€!̼N–BÄÊ: Àr‹™ù:©‘Ð+U©F¨ð{€x âÿ ¿‡ê·aª¤*£Þ2ë.U!aýEÝÙ  QÕäpø3€5Dt.3W„0ø} à‘®Dt¢Ÿ¨ R€^p-€gƒv³4Xàÿ =fJv:d˲µ)B§uP&^Áa ½^"½TU˜×ö¶Ù¾ðI‚#‡(‹R’GŸ1óEâý`,€mRe]ÓÔ+D`€7ø†ˆF0sÅô§¨j–è(`æ2"Z àD":–™Wú‰z6€t/0³“ˆ>P \jQBÂ0>aæ¿„¡3P£ î Wª\¢3Ì*HØÛaT2ó fÞmô"J"¢Ê‚ˆR ÆK3‘&Q¼ŸßR ¦‘ ¶ÓhÃãQö|¬Ñ¼Tº$«rù‰›.k¶„É~ÀÉÌ›L°HâYêX¢Ø^Jf´NhïÉâʼng ˆó׿èéÇOx²ÁöžLD±&dN6Úߌç0ÚGw2x] ÎeâÿWDÛÙ+ÆÐýêiJ{½‰±6ÕDš1zã¡Ñ6¤$QŒçãÖg“m$ÑDšr¬mŸ:d<[ÁÌ?Û•—{—ˆRÌŒgê¿Q[ÕÀÞM¶»þŠ6ŸŠ\¡Žs’‡£39ˆ¶ÑLû6"*P  –ˆ–‰7£y !¢‰h3€":DD¯QoM¼"úˆªTÑ^"z‡ˆziâ=DD‰(‹ˆÞ²µæ- ¢#…Ñú"Ú#ä-'¢34iÑ|"/Vªì%¢ŠçÑ·¨'¢oÅÖí`ü°x¯‘×."z[ýnD”-t}CD?A™­÷‹¶VODëˆhš‰~ä ±ú¶@í ¢Ûµ†3LD+…¬Þö¯Öxíä"CD E[ûˆþ-Úv"úJôuBþÕóÞ÷<‡ˆ®!¢ŸÅ{mçÕ@D$¢Õ¢½×ѳZCˆúˆ~o§¹AÈ|:.+òËýÎV!×N"š«ó~1Dt•Šx¿Ñ~t[@DßCÙJXCD»‰è}½¾©“âCQ//Ô3Έ¨€©2s™»T”ÇÙš¸ÃˆèQŸj…Þò–S"¢:DrˆßCóÛ«bÌK ÒFF‰¶ú«(ÇJQç2udü‚ˆªE¼]DôŠN¼D;éCDÿ…âCà }-Â’ÅØô»x×õDt¢&-¢ýFD+Tù=#Þy¸j ®'¢ÿQO¢ñ,Uˆ:êmÿ/Q–ÆÖØJDÓˆhºh“^=¼¥Ž«zf†(‹j"ÚGDψ׉חˆÞ:;DDUDô’#HØíÕ›‰h¹¨c¿ÑZ^ êÒV"úÈû €÷ÅÏ׉ß& ´ˆzZ-úâˆè8íÄ‘èsw »¸†ˆ~Ô‰w²È÷8"ºß[E;¿@Ä™IDÄ»U Ÿ ê4žýÅaDô€¢ ~ODƒÄäÑKDä7ÑÉ:ïv!•Q­¨¿…Ìc5ñ>m0WŒ ‡ÄûÑ :éŽ$¢· µªmùH?e#¢m*;âg"š-k¸1#M~|LÀÞ5ÿ<ÿ&UØ4ñþ#âmp€Û¡lÿr8Ç@>CE#rø€«|À` €$/GŠMžp€çE>û R¥ù‚Ho=G÷øZ¤ù‹H¿ ÀÓæhi÷Ò¼ë&ÑýG¤±I¼ë[¶Øà^_Šðo5ï¶H„ÿÀ °V„-WÅë!ÂÖ¹¸ Ê–t°©âŸÀ `3€›Ü e‹é!ÿUÜ%"Íס¬à=`·x¾@¶ ùõäuQwNo—ˆ—)¾'‹¶ÐeËôlѦ~ñ^ñúŠ6Ô(êß}οMmp›h“³„qR-ÒøƒùgЏ»E›¼Y´}0_ïr¶]´±Y¾a‹8Tq·(}Ã'"Ý Þ~T¼ã&ñ.ŸyÓP=Ÿ'ÂV‹þæEñ~U¢/š'ú³¯Ü­JûFU}½ÄB¬0^ àeñž àUÜËDX©0"Þð€xð´FgóDøW®ð¨(Go?’#âõ2ïò_à ѯìÛEÚÈ B/gèüö7ñÛ_Ta³EØ%ª°ÉBÇÍPœÍ]-ʉÅxáñÞaýTÏŽa àBUx¼¨£_‘ÿ8‘wƒ(ûÙ¢îyë~Œ*Ÿf1ö=*ÆÚ‰:»Àaª4?uãW(Žnîi±ø¾P¿xI¤±@š* «ëÅ{?(Ú‹º¾_´¡»Ucê[ªçc œçtxÊÙíÇUãõ§ª¸Ç¨Ú¤KÔý»¬áK4:û³jlžÅÑÙ.U¹PÄ‹ð³hw¯ˆ6òœh»MjûD~üÖÏqÚ²5øÜ­Þ²uúÑ_zËh¼ˆGâû2U[ü§ªíÝ`p€|â‹ø_ˆ±ãÿ¼“YF¨êÂ*•íw•;öŠ:w±*Í?©êãnaŸ½ìì¶®S´³ûEûcçjÆîzQß‹E^߉x? ûô€hó^›¹ @¶*;Düb¡¿EN1f÷TÅ-…r–w¯ÈóA¯‰t]jB9—í¼+ú»ïUeó¹*îÝ"ì{Q¦wªÆðÛe Ò¤ “à¢aê}&›!ÁÎq^ÔÝü&òJ "——DŸ© ÿ·¿T|÷¬S4ñÎÖiL^ce€Uø7"ügɪð‡u ‹="ì~UXº à)U¸C â‰"ìDï#¼•Û[C‚}Hˆˆ»@„ab`ý @†*n¶*ÝkDØñýŸÎæÝ²mÈI\$â%¾_-¾? ‰×S´•JMø!¥š°oDÛÉׄ_!Ò~5ˆL "ÝýšÁ:I]—§0Žvè®IãE‘×, f(ÞÖÕi6‹ð罓SÂÀ*áéÜ à¾•œ¢ ?Âk¨Â抰™yó½FŽ Þ§1pz ýR…*â~ ™`›ª’-GSÆçkdðùÇw‘6Rèo"YL`Tzû=,Èê&a€Ò<¯ˆ{…†|ÍÔûÍÂ8~Q§Ì®"ÿRÑÎÆj½“@§~0~Gkâý?íx"Œsd:Fþ“Š˜Ä«Â_á“5$X;y{„ªÞ¡ië¿iê²·=ýŸFÞda|{ÄiH°[Ý߈²ñÊäSEøª±c„Žÿ/±ûGo=á?ª&EÕýÒS"ü,Õ˜v@Øi~ìðéÌPnŽ‘~@æ] :Qc×þWÇnß(ÆëX]»ÀÙF$ ¶‹ú\n’{g›zêä÷Œ¹Õ‰·@‘NxoC9ƒØK¤õ¥Ÿ4–‰ß³4$x†&Þý"üfMø…"üœ¥‰»Fm ªÂßṪ‰€ãüèÆ;Ë?TC‚7èĽGüö'ñ}¢ø~§NÜÛ5$8C ¢³'Ùä'D¼TĦ"»'ªD•Á±F(„ à8¼rD^‘)ߟ!àXg‰ö8]Ä»N'^¶0üWèàÞš¸ÞYþc4ᯩÃU$x¥&Þá"|³Ž”iV‚'ª'U«_lÕ!ÁótÒ].~Kßßótâþ¨!ÁˆïŸ8B]Æ]°¬Ä(MSÇ|vø!Á'‹ïOè¤ÛOµÅâ{¦¨ï«â|+Êf1€rQëÖk4«XMþ§óÛ QÆ} 2¾®‰oR­ägûy>M~¿LC‚›Ô$Z„ïVO*©Â¿áÝTmw¼—Xø‰Û]C‚ëÄ}NCš.ð7¹ "júª5ÚÉ<ù1M‚›E?èïó„Y;Ý1³K†ñ ïÐÁì¡ól”k4€ßµŽîXajoŠö㓺„ =[:ãL¹‘1†™ [[ÏAœÑq¦ZÓ~œP®?L#¢?8ZŒßù*û]Û~¼GaÙª¹âo½ö³^`5ÇþàDôc>Cð+³º<¤c¬öA/1k×MçS+¶¨4x~€¦QúCOMÇ?ƒb–ŸF©…Ûàûí ð[À4„s“„qñ”3Áñb†ü;?éušìGz:«Ô1\npš˜-Œ‡²Åô¶Ñ8Y…%Œ‚ˆº‰ œýÌ\#ÂR‰è (G ^ƒ²ÚZåœÐƒéB9ß¾Ê=¬Q½Õ hvö#éÇG–ûi¸ 覗pÂR e+éùP¶ƒþ-€a`´q2sC°~D”õ$(Û¿$è.aì/Ô:iêäxSŒy—ˆòIp”Õþ`÷ŒöR@½1s”•V/>ƒ²"\(p"”•Mïø1™ˆr¡LFÿ×`9$^‹cí¾Ûˆå±Z8©{Wô!oB9«éð$×YÕ™kõtv@g¬½LL޼+Ês¶(«D4BŽ aA}ÿ›ó``ŒIí4Ò¶j¨ãÌåP¶"åxà`a'Î÷óH3{¬ÚªÌܬ§˜ù3(×0>&ÈÿYPÎlï"¢keU ¹Ü>Ø e Õa;4ƉÿF´®lôó“N?k¤q“0¿ãZïl°j ÆBºkÅÿ½ Úç+_ ã~"ï¾Ó„Q}´¬Æá_¤ {NL²¼åÄFöîa"ºRUÏÝþ X1Ø¦Š šÏ¼+ZD4LD 6ɹ=@?ÒSô4ýÈz?ýHUVm8„g߆²ÝöA¯‰U;oÿø˜R¨MDÝu®ðé¡Ó‡4Bqlô’˜ 9Sù܆VÏábÇÀœ'v2Dõÿ*þ‘™Ÿ3ÿS(ÎlN†²ê_+HAÙf8Ê‘ ÁÛý£t¾Á±¶¿øÿ÷(*–‡ izÊúõ^{D¬léC‚µkýøÚúñ1€E¿v”IÁs¡LîMCˆíX+ÆŸÞh»ÐÓæ¼¶«&“´m(Ê9õrqG0[5íÇÒ8#v½ÅWÆÅPœÃÕŠß® ÁV]`\N‚rS„¶ýü"Ɠۈh8”•üÌ'¢OÄÎ È•àö뀒õ:wqõÆb‹£?ü&f‰ÆëÜ3vZ=öm‚2û~šöz 1€OFÁú(ÒÍ©âÿ9Ì\ª"À±P¶4ÊŒ¡Yx·ž®óÛn&Ñ""š.:3'f¬pd¨wºJt ÑaPÎÔŠçU/Nƒ²%÷fÞ "À™P¶LŠ3/\šþ:ÀaPÎ*¾¢ÙÒ™o°xWœ'ëüög^“U†ì™:ïw€îPÎûF‹Î“¡œ·ÞÂÌ÷x °ÀÑB/VÛ¯n?"¶ÞNЄÝBDÅÞ럘ù3¿ ehÝ’ØUðOQ‡ÏFZ­ `FÂ3tÊz­×ïx  ÑOO}ûâÞîf(gƒ§Šºü³÷Z¦¨€²…q¢Î¥Ó 8°º­Ûÿ #c– ý{!ŽCDÙX;‹™×ªp”Û'¬Žµ¥þÊKÛ×ÑÙ⪨?вkfæÿ r¾§ ¶‘HÁpùË‡Ø Uì\R—}6”ãfߨìâ"Ò[`ðŽ=«¢H‡' ’;™¿ÒlãÎ×Ã1r惢ߙ¤s³¶ýd‹+Ù^Q=¿Ž™ï…2ù Ù†$ ŽFüÊ Ï Dt”ªBŸÅÝy!Gþ C™¹í à1ï}žDÔ­NžÞçkŸ†rÆ`ž— ÷¼xþif®"ÝÔ™âÈ× x¾´Ô±ˆNå (÷U^¢Jû$(Þ;ÕØÅñÃbÛœ7n>”s+«ä¹` rÄ]¢£Äý¸gÑÂèeK½Å°J˜“Tõ«”ÕÝt€~DÔS´o;9žˆÒTiBYYjÀ2óa„\è5BUÄvšÈó[(Ž~0“ˆÎUÅÅ™^³†à·7ÅçÕyf¯/…B0îåÞÛ]ö®¸ rô ÚÞáØ Œ¡G½÷ÄŠ•è Äï‹»XùVô­‹þý]?çÿ´õt”A§Š‰ºÌ€rÖx ”‰a5>…â`1¾Çh¾…²Ò5 ÁW!ÎÕ>+Há]ªº”e¥Å)Þc›£Æˆ‰l¯ŒiBÆ4s-îü ÷X{šê½’DÉ¡Añ1r%¡Jû|UÝW÷kã hõ0¹K <ñù›*^*ZïâüŠ'¹Jñý?÷9© ŸÍUHšëBn ô®"|!T^Výå e £÷*•MPÎ;Ö‰¿½^(§k¼C¿§“Ÿ÷ZŠ‹5ºX£JÛ{ç¡×Cµúª‰»Ðêý{ZïoÝ áÍP~ä­&ý}*Å`Ošç.¿{Dû_.êý(N.ÀUü7Ti~¬ñ0Û ý21è= ÅaÈñ)ˆÛ/h½ |3Z¯ž¯¹:Ãû[¹˜‘o2Ÿ¥Is”UXm^Þ»UÓýxÄ¡ñ€ù¤ÎÕ->W!©~Û BõÝ{݆Kôy«…n>Eë•o½5^?/p]†ÆCg­x÷"(Ç[‰ µwè8U¿çq«Ðz¿f÷.Ø^îWÕã1~âèÝ<­w½Ù)¾ÍËhõËFjê±7¼À ÌÝyfQ·½^^Py@†²3c¡jœXŠÖ{=ïÒ¤ém»Ú[‚æ*$MñWwèE:òþÕUHªðá{W¹×Hf(«í?Š6½­÷Àž¡ñýœNºÞ¶6U£‹-h½Ëx=Zï#Ö^«8_U&kEù²˜¸>FŽ3†½Cù\«¹E`¿(—RÑ¿7ªêI ïÐñh½ Äç* ?2Þ(úË1ÖÕ¡õ®õXÍ·N(;W¨ì®U^Í5Þ¡¯×äs4W!©~c¨® RÝýýÜr–ŸkE¯ßÓÄXÈB_CÙíQ-ìGÚ6:Ök—ý¡ãY­žâ÷ªtö… ßjïÐÇ‹ró^óçëœÐx¸–éÚ ¶ŠÛè–á_D|õìËÐ8ºbæç‰è[(Û~Fˆÿ%/3ó>“MÎ%¢³Ä¬i®0&ß`æRU¼ZgŠ•ž‰P®uxR’+4É~.:6íY·~tP.—zW×„à ”'3¯g®ï´­žïÒE<¯Ã€ºeS"~[§Ö…ðây*”mkÍP\ï R¼J÷ïÂËÞÅP¶§zWÕß×ñz+Ñuñ´žYôÂ-êô(×Ôë´ß7ˆh‹0nû‰~æQׇЉõއ«‰:Š3-ˆÙñËŪV:”«->fæb±b;\¬Bÿ ÙDDyþ ŹF&'9/¨û!f^CD#E~bB韾Ñl7Z¯AÑâ 1˜kwQ|)È¡wK÷n>bP¿ú^œ½×Ýxå}ˆˆVCÙzÛ[´í;™ù ±ò±Lê»Å*÷ýÐñì e»î¨¼I3s©ØÁs”í¶ß e‰2ªñœD4Eôñ“ xþ_)â¼-¼‹v5¼äì+¼zX%Êc­JçÛÄ…¿ˆ•ß#DZŸ0ó7:i¬};à{.u­0´™™‹ ."¢“E{;Aòï¡Üû»Aïwq#ÃÂ0í'Híwê1Yà=16iÇÊïE=×ö)k„NÔÚï‡þöê@çÜ Ès­7OfþRìnš)ƸbÕú !ûvU›Ú'òÓ+³ÅÚv)t‘e»íéÂp¿^È»A=f3óuâ¼ø¹ªÉŽïèÜ2!Ñ;Ðzä&Vªô^!¶Ÿ å®ë¢¿s‰z²]Äc"º*¯ÆÌÜLDD]ï¥S_µmhž°§ÎQïøš™?ÐÄ{ˆ>ua¸è§¿ðƒÆáFñÎZv¿N;Q·—_uÆn­ˆEÜMšð "|¥µF´Ÿk xÀöˆ טù7"ªƒ¯wèÅøªÅ!?ãÏ5¢<Îíñ-Ñç]¯ÞùÂÌKÄŽÅ+Ä$@¼x·ÿ1s‰lAb&ABBBBBBBBBBBBB¢ÓCž –$XBBBBBBBBBBBBBB’` ‰"Ê$¢™R’KH7¢G GSÇ¡í•%¡Ø†‰(^jBB’`‰ÎŒ£Ü$Õ !Ñ!1úÞ^%$$Œû}‰èx© ‰–6AP®å$© I‚%:3¾01ÈEéщBI‚%$BÂYP®m’PÐÀ~q§„„$Á̼ÊÝrùRr%XBB¶! ;1Àf© I‚%º¾p’Tƒ„DÇ ‘™÷HmHHH,!a†(“j$X¢+à;S¥$$:œñ^$Õ !a D” € R-+Á’Kt,P@DÉRŠË, ë(°š™=R-+Á’Kt 0s€R'HmHHH,!!Û„D—…\ –$X¢KAž –è ÞÜG(–Ú° é]]B¢íØÒÀV© I‚%º ä¹` ‰Žƒcì`æ© Ë+Á¾`'3»¥*$$ –è*(0€ˆ“ªˆzÈ, ‰@D½Ä1óv© ‰ †<,!I°DW‚˜õ[`ŠÔ†„DÔC®`IHÈ6$!a7†@ž–$X¢ Bž –¼„„lC]r%XB’`‰. y.XB"ÊADIr¬•Ú$XBÂFÈ•` I‚%º˜y –ÚˆZäø™™RæAD Ò»º„„r%XB’`‰. ¹,!Ý+X¡a€Ì\)U!!¡€ˆt°SjCB’`‰®y.XBB’` Ù†$$ºrüÊÌ,U!!I°DWÄ÷&Š Ó%$$¤/!ÑÙ ¯“h yXB’`‰® fÞ e+L¾Ô†„Dtˆ²dBžÙ’r"IB¢-K,ÑÑ+UÐùqý—›ù/CRpLï $'%Èf±keΜ9èÞ={¥Çã–Š—èX$±#æh"‰/¾øO=5_ýµ‡Ù#K-š%¤®¥£ö…ñq°¹¹©©©øý÷}ËtŸãpæÞ1tÔÅ«HÔè0Ò%6cÆ Œ7ûvÙ§HDÝ8LÔÂ}àt{/IpWÀ_†$#¯w8bbálª‡ÛãÂtlc cñÔÓÏâÆ9³¥â;ƒyNá6©)¯H6IB6¼…® 2þäŠåK‘ŸŸgsCÀÉò;Y+KÅÙn˜êOÀúK¡åIV$¤^‹BÐÙQÉBmb–¬w6û€©èl*µŸ~Zƒ!C#>>º½l^ópHŠeK*´n/XŸlgKQ§×I, IDATØV È¡W1³:d{ÊímdA±yóFüåâóÑÜXVJΖÕ"*K"LpÄÄ"!! qnêåv讀czgÂ‹æÆ¸Ýΰ`8þøñ(-]úú©øN 9”tL— ° úN*„Õ ·¢l›!Ì!HÇ!è„>ÄÚëû0· ¶*—ÝŠŠV¡°p B[æª'w6¶-U(+«@nnŽ´=$¢×ve†ÛåDsc 1±ˆ‘$¸+ 9) ®æ†ˆ H))É5j$–-[î'‰m §Ë‰C«ÐÜÜÔ&!élx_‘£Hl£ ÁŸ,.*FaaA8ùW‡¨ÃlËK±©ÇÛƒs PFdØ8=5û±+ÿÀ aÝ1’âªUEúm(ܘm¨¯–SàÓô¹ûÿOEÓVhûWkkëÐP_Ã;LÚ:!#œ6rG¶¿í“™ár6"11Yn‡î r([ #„I“&à‡ 1uêä6r455bÛö_q°ªuu5¨¯o@bb"RRR‘‘‘…¾ýú#5%UÎ w)"NKîH¾’Á¼¶mÛ†„ÄDôìÙ3ŒåaýYfa’¯ÎÏ­A!¾/“áÇ}s2˜/·ŒÿÁ?î³*LZ;#L†%EÖ858ùevM¿¨hnºéF‹ D!ŽÊmÐö¼o‡äO-‚˜CEErü®K[ÎŒ­.¹#ÛßáÝãv‚(E’à.A3Øt tÿ#NAMM>ût>Æù7Ó6—íÂÄIwÁáˆÁ‹ÂÀ#zøü>yÒD\7ç&߇¿ïÛ‹_~Y‡Í›7£ªê jkëàñx@DHNNBFF7 œ‹£†Cß¾ýÁÒ¹VÔr½E]£ZXÖ¥(dó‹•   Ïp:¬w6Ø`]”$¾¤Ô8™µ‹ ·¥¤ÚU¡PHq;ða!½¾OUWWã·ßvá裇FüJl4JXÎÛòT“@›D+++ÇàÜÜöµSpìpÚÈÙþŸìÌ f$Á]ƒï%rs ±~ýìÜY+žQ>$6Ì{˜á 3ãž{¾Q\ÝÄ6 ò°}ûì߿ݻw‡Ãá@Å–rü´º?ÿ¼=ë'NÆá½û +;5ÕÕØµë7üòó:,Y² ûÀèÑy8rðP°œEì 1Ò2øÏÏœ$v­‡øþ>ë§UT\ŒÂ‚S‰vJ"¬!v­A¡®€ÃôªpHdÚ27Ç\“â iFrǃ ØN*SR\‚Ñ£FÁAÓéXÚyòØÇíâD¨S`[Å55L±lBy…¿óÀÑ`¿E¿ N¹#Ûßá–™% –P3h6mŠÅ®]x<@lŒ—üÂ0ýæÛmX±’1`À0\3ûÝ´bbbp ã±`áb\pÁù¨ªªBѪØ´© 'Ÿt2ò ÆøÄÏÎÎFvv6†òò2|úÉÇp:Ýè–ž‰^½z£ó\çÒ¹xrÇ^ Ž6"â£AˆpIq î¾ûN FtG$€µUadÔ_¬zÞ4§esìÒoôÐIqÛ§Ù䃡–_$M\6ôÓªUE(Sfâkùõ› ‡W›]™3ÂõêÑëÚ‹²² œ|ò”ÈÓØN°FBŽ˜°ÙÈáL»#ëŇhK _ ÊA\Ü ìÚ×bÀ.ò·Ç46z0÷‘]ˆ‹„{¤¤¿éMž4 ,‚ËåÁÆM¿à—_6âìsÎÁ¨¼|xØû»ù7­Ë#‚÷¬(‹UÕ–pŸïÊ3Ür¾TùÞJ|&‹L¼ðË/pxŸ¾@\ jÙÐ3äl 8påŸ0QomªÂijær3ûΡÖÿ wðF`«…ßÛª)œyX}ÀÈ=ßaW­Z…‡y ÍÎÐßPo»³%}YÃ7š…B€ƒi#ÈÎ2ÿŠòFMö“7ùÿÉÕ .‘ѪJVK?ÀeG£–ªSVVŽÜÜÁà–ŽÚÏèIÖJŠLMTø‘`¬VFÐ998Xù{Xld#i/]¶ ãÇ‹:û[Oö_·nE^Þ±¸ûî;0çúk[â>öØ“xìñ'±~ÝjôêÕÓ´ì’Kú÷ÏB|-Š•|¿C·åo•õ9SÑÌégó/ë1ö¸ãÝ-5D£ÚälÛž“D— k_ìÞ½.— GÎ ñ캢mV—…5uŽ`9¶á>l²°RÞ`¶I£ššm¬k*+«ƒŒŒnAû†–{©„ràСê66ò¶mÛ1`@ÿ6ñÕáÁldi{q×]÷⥗^Åõ×_‹;ï¼Í²ý)½ôíÛwÝu;|ða03®»n6|||ð>Ö£;Ü·iÙ% –ôë—ˆ¸¸A¨¬tÀånD|\ë*„ËMøð£ÆãŒÓ1`€±mS&O‚… ‘”€ônðè຺z¤¤$· Šq 11Ng3\.7:¼äÔ,’§þ®þ[Kµazä7V”–‘øþdö\0½èMø—)³…{ɰ—'§d¶!ÂþƤ$ ANJÉDRJ&êªtI³]#œvEXoÕX/ =Bœ”Ü õ‡|¯!ö1Xe¨i S¯EB‚'±bùRäåDsSM½ù—…¬½—©ÜmY%ËÑȦ÷ÐMÕöëvYJî€80ÁY¹r% tÇ0«¤×ªŒ¡Ýãmà jæðh@ŽÚÁ·?Ê6j’ÃÞŽÊ6oÆàÁ¹bÅÃßì9j: úNBuõ!$¨läíÛw`̘ãqç·áÚkgµD}≧ñä“Ocõê•èÕ«gpY'muZ/½ô*N:i žyæY¤¦&ûäeÅþ·^àšk®³ÿû\,^ü#/^‚¸W\1½%ŽYÙå™` @ŸÃéÓ“1kV"šš|«Ess Î;?sçzpúé†;˜I“&bÕÊ"¤¥¥!Æá{<>ŸÚšZœ~úÑP_ßæ7öx‘Ñ .—.—ÓP~ZR¨GµDÒ(ôVZ­B–™Í¼»zÀ¨N‚§]åw|ñâä”L°ñÕ’á Ö‹odS\=âk~”e]2ì%Ĭ!À¾«À&ànYY|)**AaA¾ÍÆc€³ÂlìùPŒp{Î #àÑQ¶z ÔF¬þX#ÿ2†v¢ÖDÆö}Â($ÐEEE(,,’¼ªìl¬ZÍVa;”io›3Åž³ú:=©…$;æˆ`À{x$Ààñ0š››}lä~}ûàŽ;nÅC=ŠùóÿöxðÄãOá‰'žÂý÷Ý…ž=3d#륭NëûïÆ¿_¥M^VíïpëÅû¹föU8á„ñX¼x ¦NŒ+fN‰;È•à.ÖÎ"&¸ñ¦&8Ùg£cÏ¡XôHw¡.¶®n»àˆK!ÙÈ4ÆÔ“¦àú97àä“NDsc#bãâ} Pb"¾øü?ˆ‹‡GgËBs³ ÉÉÉHˆ³[•¶}$Ô¬S)½X£ihW©í"ÕíVu‚…z;t0rl9“ðë ËŸ¼õ[¶KkW…`´žÙõž fåj1&F²øL0|Î[; 466 ¼¢G †šF#‚…3ÁÁ®Ññ=ÄEq“uÔ3ÁlknúøÉƒì{có;ØtJdŸBÚ`ÅÊU¸nÎ46¹üÚ6äNÖVHͼˆÙ3ÁVÉHàÓ§ÆÏ+G§ÙƲfË=>kÌ“:‘ÙÈ"¦€g\™ìЙ’HYYrssUçCëüjÁеal†¨;̈‰q --Ng³<ûê+ÁÆÃ?†/ÅK–â¾{ïÂŒ¿^îc+û·‘õÓÞ¾cžxòiŸ´¼yÍû8Î;çlôìÙ#¬öw(z€yOÍÇ?.ÅÔ)“ñÝw?`þüçqÍì«,sI‚%Z°aÿÏ8Ø\ƒ>‰}Ð'5 =Ò]ØQûÖVn@z\*z6f49¤øàAú£wïÞØ±sêêk‘–Övi||œî3—³äˆAZZ:Èá»í¹¸Û*ù D˜Í¤¥·bDØ Af€« ¯w;´1›©Jç é7°ôL½ïÙaïvh3¯Ýz&9üœ2ù;ŒVC¦ÅZ2&xéú56lzuÏ´¯Üm² :ÿ™`;ˆ’-EîZ¢®Qlbý‘«KKq寋ϮZÂíX47G`Ûµ®$ắ—mÓLàD#ºý™ìxmñËËËqÎ9gÃaÞÙÐÎw¼ƒÂÌdfeÁã¡66ò¬«¯@ÕÁ*¼ðÂ+¸ñ†ë0ã¯Ó|lå`6²^Ú}ûŽâUKгgŸ´f]}Î=ç8ì°lx<î°Ùß¡êå©§ŸÅ¼§žÅ½÷Ü™3.Ç?žsyÌÌžu¥%î Ip×ÙýbwÍ.¬?°MÝ8<% Õ N¼·écÔ»êqÕðË‘§Ì@%ÅË‹=nLž<ûöîAÅ– >Ò°´»vÿ†‡õBZz:؇$³‰w ôÝÌoJX}í$§f âz MüäÔlAt覥þÝ›VÛ8Y~Ÿ&³Z¾V9|ã´æŠ>ØwKw]%’S²ZœdÕ×U‚ÁHNÉd´ u•HJÉjy>Iõ›¿¹__²Ìº[9gœ¡"ÜmåÖ_qfß³ÈÜú½¡þ Ïª0À‡Zx¸eÕšS¨@ ½+–/A~þ(47Ö´’½@Öc-–…lЭ%9È®ÃdØQ;åÛ^cœE"µióftÏÎFFF7?g‚m"1.dÝ0Ìw·I'$bÆ6©ÁN§S‘™40w’&r[eåÈÍò™`ë[ ;î${ÜèÖ-ýúõ×µ‘ÿvÛM8ó§á˜cŽnãØ*˜ì/íî‡eë:©U‡[³¿Ã«—;vbÞSÏâž»oÇôé—Âíq㪫f‚™ñècópöÙg¡gÏ&egy&X¢µMµ(ÞUŒu{×€x»ìu,ݹ}’{!+¾—Bpšp{ŒG§žz vþ¶ k×®ÇÁƒ•ðx¾š{3›O |õò ž¶µ¾¾®RÐ, ém…÷·V‚¬NÔZ‡«uÄ¥Sÿ¦]‘nù-9£éyÅoIÉ%4øA£OGÿ,c1 ôc…tTи<~ï,µùì¤ßÓz꣚AŸ·ëœ±ê\¯Á+iÙhºv˜ z—ýÐNŸ@2…élqѪ"Ž)4•pИzºW= ¹MØPÉÔåc)‹²´ÉÀZ?j:–aqí’GS°Ä–Š-È4Ðr]á.J€½6rZj*ÆŽ;Þ¯ø9¹Á®vac7p&˜ÃQªa;Ì!ç-ï Žd åL0[Êâ–›oÆÀ1köls¢fßmÖ‘±sªAÏáÛ—mŠ ï˜ð¿rL¶êI#XaßÊo°¶mÞ "Ö‡Ô [kÓž={p≓Q¶égKmË(ù \Dá=ÁàpĂ٢âUøiu‘­6r8Ó7"!{BRºÜ-ÑŠìÄ,TTU ÑÕˆk¿»ûöaîÄÇÝàñpn!6† »LOJŠÇèÑ£P[W¼ücñÕ—ß`ÄÈaè߯?âZ®;injÂÞ}{±~ýÏ8nÜDäææ"1!nW³,äWÜún½FdÛ€K#?û„-9C²éiëc=›b"¬".­g‚©õB!{•s¾­g†}.¢ÐÎ8p‡DaÞÈ× Ù0¨sØR†ýÂQTJ)2›«KKð—?ÿɉ á•ŦU/nÙ¦ÜììLŒíÀ‰îmÏFÞÁüøÈ65Î-<8×ôyà€=ÚÛNö@˜àñ¸—€‘£òàp8lµ‘ÙvGÖ‹E W‚;?Œ®ïo8€þÿ7¤åû˜Þøö‚¯Ðìt %‘áaåÎàøX†ËCˆu«;syuuõ˜;÷ìßÿ;–,YŒ;~…ÇãAzzêëë‡þý¢pÌXdefÁáðHÕèb«Â&Ȱ9IÉÆ}#|óÍwxfþ³øß矄.…¿ÌŒ­>SÄë1Q´ÕO{ʧs˜á¥ÉÍMÍèÙëpìÞµ‰‰‰œ81AÛåÚ˜àN«8¬%èÑh;ïk§L¦ >¬õó_¯½ÒÒÕxvþ¼pÏ£D°íµbãÁì‹δ;²^äJ°„²“²Ð/½/êšê>áq8]1 ê›Ä)Þ¡ÄppPp#qò¤ ¸þ†[ð0;ѳWOüñOçÀåt¡¡±HJJBBB"bccà ØÓ ·Ë% ¤C˜”d:J‡\VdçU@&Þ'hTßEÅÚóÀ¾&24È̃ÆÓ3`ÞèoOc›IqðkIüÙšDfŒ4 CÛ –m`Ï\á4'£ÖOµÉ­ŽëÖ­CnnÂ$Ýdá—3‡ÛøJòkFÚ²²r zÔF’_#p9ሉ ‹δ;²^éZÂÇp!lš¹Fúy€‚‚rŒ0ÖcÁ¦ Mn7l¯±\TT„ˆÊÃÜ~ï«›f{“^ÝÇíÞôåÄ7Êȯå[0~üXëïÚÉ_™…ÇíD³Û9œiwd½H,vÄÄÄà„ÆaÁÂÅ8ÿ¼sàv;áv;¥b:6¿*Ü)ȰBllu84B\\\ŠþïYC¹Ï‘m$Äá ÅzĘlªßÆÈb [•Èlúá&Ê‘"-EEÅ8á„ãmIæhR¨±ûx9Òòv2âkI+í~\1ÈÁeå<87 ä·ë`5Âi#wdû;²KÜň «® hnvùt6¬kàúº°õ¹8ž}ŸVþ ÄÆÅÂét ^¤<3~ÜX|óÍw8íÔ“4 9Ü–Bµ¤«Î@ùÛæyŸUfݱ“Ûxšl›v«<ÔúNÐ{GÕ3¬þ•4ß[ß4ß™Ôù“¦lTihÁ¶ßµñ½Æ6kâhÓ£ é³NúÔ¶žhiV{/fãÕwÒÓ¿~ÓÓ¿þw5ßÓ–©ÈEåɘ5ò·H¡­C¬© Ì‘&=ùîÕ'µÜ™Uk)À¶íµõymù´5öìÝ"Æöm›±}ÛfÙõIHXÀ¢Å‹0eÊX¬Xþ½TF—©&¯H3¾z¿«Ç/´8íiu–Hšô4“^gŠªlt¿‹±j{‰|=Z³pÊèû<ùúîWÿ®~žI+–êòùÓãalݶ WÊË7B#,Zý€“êêR%Eš¤©udVéôd$_›¤mùøH¯Ò?k¾“ÏĤw|ÕO:éiëˆ:O•åGZý’ÏÊ%û}^[MôÊTõØØ¸\¡v½çSî*ÙõÊšºÆŠm¯.SRÛz:uzuÌ[T¶—º<\Ng˳þëéÖ7Íù<«[?¤c¬.ÿޱm™6ÀúêœÃ'ÝM›6ã¬?žƒM×j¬ÍšºÏÞ‹ŒF´žŸm+UúÏY—5è3†ýò˜užE!HH&³¦Ð4cùîƒ5B+·>øï¾û.>úè#ÙJHX@uu5úõ뇪ª*8©‰Àvˆ*8L¢ ­ ‡ä§ÛÄêûÖ­Ûpò)§`ó¦~b²É×ecÒr(:d‹êe{ê ûŽå¶ï`@9ÂèÐ}6¤Ýºî!Ô6¶Ç”ׇ µyV:ÆêJ]¶îÎMÑ>Ý õöI`ÈàAp»Ý(//«ÛÛÍHD(¤(Ö{´ErLax¯åË– /o$š«#”+…©(lôÈM‘®+‰êí ›•+–bÔ¨‘ð¸›àqÛÎUÚadÃÙq{ÈÎÖÓmŸ-Ða¤»Q¾ 6lX‡œAÑÜTk‚øDC;\¨rzS"b˜&—î8u,¤”Úǵ5s4?k••U 7wP[· 6Ô1 ‰HA®KD 'NÀâ—ÂãñHet92ìg–W'Øþùà­nš ç0Hæ›Ê† Ñ·O_¤¥¦Z6"Õÿ,‹xÖ–·mM)@ú¶-¼…¾Š§·ÝfQoźS¯àFgUTTŒÂÂüÀºÖYaõ»Bl©„l,ç É´ýÑÞÚe¢Fª¿òÛÛÛO™"ÒŒC¿òŠ äæY æ@¬XöWí¹,1ôìÙ½{÷ÂêÕkŸ?Z*¤Ë˜@›Õ-EÓoß1“8^Ño6úìy_Fqq òÚ&j1a6±RðUÛÈ`nE×|6AoØã—3Ÿ°Ù•òÙÖ*NQÞŸØ›Ôî={àlnFÿ~ýøâÎí”,G@šð­pr;Ÿ?¶öÖÜ.U!œ•—U 'gÉ…sIz%¢ r%¸ËÐfÞRÒ²Û|Ô¿éý­‡)“'⇠C~#=yåmU^3ò ·+ŸpÊÙüÌÍ„{ƒ’S³Ú|Ì 5~xg¢“S3u›^rJf€¶ÙФ” ËÒ)$8á:§òÊ‹ÉÕ(»z½Àë[tdÛRXxVrý¦dæ kÐéÂó ´¼j$ºÁ•Ú¢¢bX,î0¯ØJÖ_Íç0ì0¹ #Òý Òùc3Iý%²›7ìϨÙéÄÞ}{Ñ¿?CCº\õ•ˆN>$I°„ ÔÕðù¨ÃbÒÄ ¶`=yŒÊaF^;u'ar€öóS]m%êj+Q/>f‰p$ŒCÙ”ì½T\RŠB•CŸ€)…G†Ûo¶"Èa%ÉfI”}uÖ4åvúDH3EÅÅ(,(hŸò0œ|$‰.¬µ‹öèK"HzMõÔfìÏLâÖ­ÛЯ_?8È!·ÅÅ%(ÈϳN#_XáÉÅŸC*ÇTÜQ*AFCÙFoƒ-/ߢÜC/!ÑI¸$ÁíL‚'âûJEHHblÑüñxýMMM "$%%áÜsþˆ+fþ‰‰ ¶Õ]‡#Ž˜88±-[g™ n·ìq[ï_c-8`} P·‚#Ƈ#¦¥*1n·¨±‰\·È@_Ø\†@í2Æ^½z 55­¥ÍÅÅ% ݺ RSÓЫ—‡Ôý½¸¸×]; ˆAllt™FDÄÄÆ#&6‡"›ÛÕ·Û ·Ëe¤Œ£+GItmÁï¿ïGbbRSS¥2$:’*èâ3!Â@Û¸q†/DŒf«ill6—mÅm» µµ5¶$?ö8rõƒÛÕŒ˜˜X?~,,XŒsÏý“_#À¼ºz<.°Ç~Ç&DŠQûö[ï`ûöªü<8t¨ݺ¥|þСjdff ¢b úö탸õõM¦ÉU0ƒ>>!Å žÜhn¬…'#ßK´¶m߃o¸µµÕ€f§û¯DïÞ=tŸÙ·o?Ò»¥aÕªÕ¸âŠ+ðÒK/¡®ö€á“@õÕŸ|sn¸µµµŠ|ÍNìßÿ;zõî­?ØïÛ‡ônÝP¼j•%ù1±€#ï¾÷aKžfpÊ)§ oß¾8óÌ3±páB|ñÅHJJÂØ±cá„Ñ«`Íšu:ô(ÄÆÆùÔ%o»½ýöÛ-É ßnSñè£"7§Ë–ÝX²t9f;©©iHLL‘D„øø¼÷þÇxã·ñÖ[¯!7''Äz‡¸¸$”WlÁ矎Òեصk'@„¾}ú!?/gœq <Îæ¸C dÊVþ5>b bcås ..ûöîÅš5ë°uÛ6TU).;; G €‘#‡¡{p6»àv{L‘k£$5.>ûö´•!33G €Ñ£G–!’“S˜˜ÜRoâSpÛmÃÚµkñïÿÙÙp6×ÛÖG&&&#%%µMzÌŒ’ÒŸ0pÐ@du?O>ù(®ºrfTp""B\|2ªkêðá‡obÉ’¥xãfÍš…“N: §vîí‰bȶŠ)É®(«À9rcƒ„$Á ‰‰I6¼ :[McãqÓÍ7bÏÁíV0Ø–üÖ­ÚŒ[n½Ÿ}òßcz’¸/X;bbát9°|éò Q~~>ÒRl7Òcbc°ºô'¬XQ„qãŽm w»ÜøqÉ2=ôÈ€Ïoݶ#†§³Û·ïćþçœó'466Ûbä9Ÿ˜Žûï¿o¿ý6\.WÀø Àÿû@h$866>ú(víX޳NQVñwînÂg8nü{\qTlÛö F­Æ¯Ùqxùå—1vìXL›v šª é"P}õ'ߦm;P8åÀ=»Q^QqãOð#ßvô<éYÙ–ä‹‹Oµ×ÎÁûï¿ììlSúœ6mˆë®» .DbBfNŸ†[ÿvvíÚ… /8nס–øÅ%úÛ8cãqóÍ7£®®ãÇ·¥ üøã¸í¶ÛðÉ'ÿËOû eí±¸¤³f_^½z£¾¾„ÛíF||<ˆÙÙÙˆ‹‹ÃEOÂï¿DZZšÑÖáC*âRPºz n¹õf,Z¸}rCÁ©Hí™cýÞmø~þ—˜3g¦LŒÇ}#F ƒ³©ÎR[U¶ò[ù›‚N¶Å'Äaû¶mxçÝQVVŽŒÌ d¤§#A¬€ïúm7~þyÞ~ç} z$.¼àôé7Φf[ Tµ ï½ÿ6m*k#ÞÝ{±aÃ&¼ûÞ‡8òÈÁ¸ø¢ó¬É º†È'Ÿ|ÌŒ^½záå—_Æôé—ûì²…9iä+/«@vV232ÚÈdõά߻ cúŽ I\GL,@q¸ï¾ñä¼'‘žž…#ça@ÿ!ÈÊì‰âUñúëo [·n˜7ï œÞ¹hj¬±PG£Õ°í"Kæ*ÊÊ+äy` I‚%:Ô[ôÔ[Mµ¨©­ÅÈcÄ…Wæ7­Þé9è—qÆöª ØSó«ß¸oعþÏv½É“&`þüçuãÇÇ'ãòË/Áû|€„øx¿é657cÜØã°tÙò6ïâñxàr6„@8I\㔄ɓZÉ]CC–.[áCŒ ¼b –-[ ‡ÃóÏû–¯X…‰ÇI'MFYY9Þxó]D¸)dã56.¯¾ú*^{í5Ì›7/`Ü5kÖ`îܹhjv"!žB[1" ©©yÃSpß-GV”Tc嚃¸ô²¿¶Dûàýwðò Ïã´3þ€úà¢?5àÝÆóÿú 3gÎ@LL .¾ø47Ö†\_ÛÊׄAÇ ÇE×ßؼº;~^ãW¾~ŽÀñønzæE|ùæ¿0sæLCòµfIذafΜ‰[o½Õ°*ãââžžŽýë_xöÙgqô ^xû¡éØòÛ~œsøùæ›qÑEøÇâRœrÊTÝôjkk1aÂÜtÓM¶õ+W® hS2*’6p:˜5ëzôî}8jkkqèÐ!ÔÕÕÁáp -- Ì ‡Ãôôtôï?óž~÷Þ}‡)˜È¸„TÜ÷Àxdî\Œ:u ®ùשÈÝeU`R2Ðí°d€€ý;j°âå éÝw߃;ï¼͵`öXè_0!!!ÿýï§øü‹¯qÄ€þ8唩HKóÝ~¸aÃ&4;8î¸1ؼ¹÷?ø(ÎúÃi8ë¬? ©ÉÒ¤š?Ö¬Y‡´ô4y¤ïDhMM­m2¸]͘={6ž{î9ÀW\×_/¾ø"Ž2ÍMu†uojò¥¸yÆ"<þß[à˜88 N¯üÞço·Ã¿X…Ìõ¥8jÜåH­o„㪻,àºúfœzêTìÞõ;f\~öîÝŽŸÖ.AeÕ>T×T¡_Ÿ\tÞµhljÀôéÅ¢E‹1þÓp6ÕØºÃ(âW’ܨDE…²,!!I°D§ ÀF·”CïôôÍÈÅ{??á¢á÷€áÁÞšm†Ó2d0\n¶lùƒ ÔZfسg7nŸ3·_?Ão¯¿÷)®ýÛ#˜9³íV¶1cÆ`ÆŒéh¬?ýV¨Âž={…®}ßÁƒsqé%áµ×ß Aq̰càr†¸"—€×^{ —\r Î;ï¼€q-Z„“O>ÝÒÑP0"úp6++9MM¾+:³¦÷AñšüûõWñ—¿\‘újF¾Ó.™ŽŠukðÚ믔Ï<ýí°^²·bÅ \uÕUÈLOÁs·]rîé ÀÀAäû|IéjÜy‡q¢]^^ŽÊÊJŒ3Æ&S–LÛ¢zDù³Ï¿@||šššPSSƒƒ¢®®ݺuƒÃá@ll,ââ⣎: ß}ûƒ ,¶•&¤búŒ¿â_}ŠËŸšˆ>Gµúxõúàv*+159½PpÆ üáÆ<Œ8¹?æýýqlÛ¾/½øšýí Ÿújf 4‹—_ù'Ö¬Y‰Ž×õ°aÃ&üüóÄÅÅ!--ùù£0 ?|óí8p Ó§OCS“Ó²ƒk2üþûþ–þLM„í”ÁÙ܀ǟ|ò vìØX²d F…[o½wÞqbcaû¶ß’ÒRäç#Áž—V|úã4°Û n¨Å'€Ýn¸ëkñiù"¬‹=€G‡ôs®öì´4ŠÃ©§NEsS ÎýÓ•xó§0iÂé˜3ûn”¬^†üÑcQSSÏ¿z;ÛŽëf=†W_{©©©˜ûðƒhj¬A{ <W’Üh@Yy9.¼à<©‰N‡TAׄzKi¨è—q>øy.~Úó-Vïùýò(úe 5Τ‰ðÂE–å(= þéTÔØéó©Ú³W^y%ÊÊ*àpÄD…þÎE·né¨oh@¨s1±øõ×­X¾|9.½ôÒÀ†¦Ó‰wß}—\r \Á¼ÖÚˆŒŒLdff¡{÷îm~;¼gBP#ÇÎújV¾Ìž=CNÕªU˜5k–_£7-- »wïÆ9çœ ·Ë…gn>½»wÃý/~5›ÃŒ3põÕWûùäs$$&¢±±MMMp:HLLDRR’’’œœÜòwJJ Èá0eÇ'¤âþÀÿ¾úÓæïC€ÿÏÞy‡GQuaüw·oz!´4Hè)" Ò¤ƒ é JGzoR)Ê' ‚ ½‰  (½i(¡†NzÝ:ß»„$$!eƒ€ó>Ï<»3sgæÌÛÞ{Î=‚«¢t¢-³ÑÊùý7ùvè~{o?7:ϩÆM?2uê4ÔZç§æ…N§³›@ë²G­šM›6ÛÈg½:™àGõøò{ç£nÝ×8~â[¶nG­ÕäèKf&ƒÅb›¤;{ö<.\zâÚ'ePg¿$IV” ‰ &Ø ¶+mÛ½ÉdbÊ”)”¯P¿Fçäž%¿YEv4ÁB§Ctìøp ¢d=>C|8kpYLbhþxÞk?Š2#–!-ŸuïÆ,}ÿ”›Z£gò¤)ܹý€zuZóãO‹˜÷ù*ú4Š7¯°bÕLž1„Ò¥*2eÜbºø +VN¥ë‡£˜7o.‡ÿR¥qhû˜Ué³–ÈA툌gŠÐÐ+Ëš`/dMðùãçÐÛîƒ'¥–hÃäýˆÄ;h”Ù¿×oÔeËÖíôèÞ%Gr”)QŒesÇ>q<>!‘Ÿ¶ýB\\BQaWQä…i]ÊAaÑ¢EP(D†Þl…Â1sP*µŽÕ«WSµjUJ—Î|òa×®]$&&Òºuë§šgæøÝ½Ôܽû€_/Mö¸ дy $I"4ô  øÿkåÕÕË‹ûwï²âëeiäk‰$I\ ½L›ü–»wïf×®] éç·‹‹ f³™¶mÛrçÎ]ÆôhJõr|·ý/~ÚwŠW_}•… ²lÙ2ztlÂ}üÄ)ªT®”ê2CRRC† I×Q–J¥Â ŒÆj5ãDþT$+÷Ãç§ãÖ­Û(•ªd³g´Z-nnn¸ººâì으999¡R*mq‘yú’R¥áÄÉÓLŸ6.sëááó$‰}w|­äÿfƒ…¿…±åß\øã6+ÿJ÷yõi;ª‡M U«V”*øTgY¶òútïÐJ¥‚ׯ³uû.ê½^gg§L °íšÔ“y®®.Ô¨þ ?oÞFåÊð)P0[Žª”*e¦2(•ÊTDxÂ4úI eÛY–Ù”D§N˜5kÿý7eÊ•£ý»ï2vô(.]¼HãÆéСsæÌ!Ÿ—;&cfK\¤Lˆ– F£‘¿ÿþ‡ŠÊ“&½?‹ˆÀËË>!Р5ÂÉ5Õy«deä®l ÙÇgõúÒ±’ݯ…o1„V—­÷BAtL<³ç̦{×±|÷ý\æÎX‰Ÿo1þøóWf}a3­¾{/Œ‘ãz3iÜ"êÖ~“è˜Hvíýžo¼ÍÈ‘£9xð×L×Q?;¿Q2} IW¯^'00PÎ /þSš`!Äf!Ä)!D¯^¿M±=‹i¿²?Kù¼çË#SS‹åßí°Þ¨÷:¿ývk:ÞÓèX,Vî=êvÿaáááDE'g$6Þ„Nïþ„yiv¡Ö¨Q©T9r”£ÇN$oáá§xñbœKQü•‚<¸Ã/ߜů|~*4 `øgÃPkœ²U^ÿùç æ ¡Z£fíº øû=Uü´‰7ߢ¬[÷jMöÚµZÅ÷?¬ÏP†´ÈL#üX%ÙÕ‚J’ÉbdÊ”),Z0Ÿ’¥J³iË6† ŽN§cÍš5”*UŠÿ[…FçŠR¥&§ÚÃsçþ¦xP1ôúŒÉê¤ÉÓY¹jµ­¨ØŽtöÏTýÒä}óYn;ýku¥gµK&ÊUC”Êžs,¥JÃúõëqwÏÇý{aÔ«Û¿â?ù;7¯b@¿q”+S…ÞëC“Fm8¬÷îߢyÓwˆŒ¼GÉà*üþû!®_¿‰P(ÓÏe)›Ÿ%Cr›ã‹e¼`¸}ûù¼¼žN†ŒçÿM°¢Ð0Äˤì{( d•Ô*âyÏ›ìxÛÍKøø P¡‚œçH©¢[×Nœ>}c¢1“4f¸4WX¥áèÑ£\½z•÷ßÏ|Íjtt4?ÿü3[¶lÉSè„D #§†qÿaæäú¬ý9w7Á”Eþ•òX¶<./Wñ1YIˆgÿƉŠÄb6Ù×hdUªd-ZJh4œœœX°`+V¬ b‰¢Œë՜ۢøtæz”*?ý´ooo-ZôÄDÐñã'èÒ¥“CdŒ5>Ädµ•ß(à *½ŸY½ÿçŸÎ A«Õàë[FƒR©D§Óáââ‚««k*"¼gÏ>êÝ=Kƒl…BÅåÐ+Øÿ¯h’nšãÛ®°uÞq†®o…“ûãAžZ§¢ÝÈÌí°S»¯Ó¤O%j¶ bQÏ]ܸ†O,…Óêô”³›F§µ¾P(÷ïÝãÂ…Ké:8Ë*~„àAìÙ³ˆðp\\ÜÓP|2l2\ºt9C'kaxR#œRg÷l‡¬3™’hÕª%5kÖäÈ‘#üé×ÿzöþˆ&Íš1aìXþv€^½z%;Î*U2£1>ÛÏ:vü¯¤Yœ˜˜H“¦­RÝ›6SëÕš7› …•ÄìƒËøöĺ½ò.ŸÖêšúæ;ÖbÕ9¡*›!L¯¼:t˜Á•9}ö÷ÁÚõËYòÍ,ÔkN«æïsáâYêÕiJPñÒ,øj2}´gƤ¯y½Nn†]ÄËӛÇÓþí6X-O/Ÿ_-Y@ï^ÝSõ±’$±hñRT*%½{õGËÿa\¾Jñ Ù3´ ™¿È蘙Àh °û¿óú)»\[G—o»Þ.~x–C§~¬ q×=9`ör*Hb($™â¹q–‡ñ™; yÿ½öDDD>UŽ•ë¶˶ù}9˜fè7ýGbãâ²lJšÂ#"°˜Í¨TJªV}úŒÿýû÷QªTäóòÊñ3Õ*µ>Å IÉwß}GãÆñyÊÚÕ 6àééIýúõQ(D²6ËlLÄä€É-»Ã9tÜ…÷:tËRú­[6°êÇ»Y¾޼C§ƒ[W.³xÔj5m™Š¯˜2Ží;qïÏçþƒó¬6*•J\\\øõ×_8p Þž®,úy9 IDATÖ‹ÅJ¿ék‰ŠMà›o¾¡zõêDEEa4>9ÁâééùÄ>“ƒö_EªcŽçµ—Ùã'NÑ·ß>2Ÿo¾ýœØØ8 .„J¥JE‚ÝÜÜpww'$$VC£†õ³8I¤fëÖ­)îM¾¢O†Tºzò>[çÀšŽeÞUƒ·¿·/DiÀ;À ?/¶mÛFÏ]2%©Ê«B`2R6¿¶ú«TrúôY<<=Òõ àî« §OŸ¥NݺY"ÁJUÆ2ä„§•Áœí¸ífS"Ó§O§^½z,_º”÷;~€——¾¾~,ûf;·ogò¤ >|˜J•*1tèPÆŒZcs°•üZ­š©¿¹^ÏΛ“{ÍoV¬$$äŃŠ#ÙÍ¡%`Ñ‘U,þsïUlňzý@ˆÔÄÖ·huO’])³ú¨ ,, O@ΞýƒÝ¿lb뎵üyô7zõ‹{÷nqhµ:Ìfá:ºÝ:}ÊŸÇþ _>nݺe¯Ã™×ߘ˜† ؼèúI¿ÇÎ|ÆO˜ @Çïáâ₌ÿ( –=CËx)¹Ð„ !ôÀûÀ_À`Ð'+$X¡“$))«Ï‘$)ñEÈ“œxÛõt*H©ÕXsvn¦:w'öròÿ›Ñ3ñ@‹Tç 8ûó~ù œ¹µ?Óg ÐÿéEX’øö‡Í´}£Å‹æÏö»ß¸ÉÕ[èÚµ[†ñNŸ†ß?Âo£×go]jbb"õ^¯CÍšÙ÷ΫRëøó¯“ôû¸oòºNI’¸{÷.®®®øøez}DD†àAÉÇ\\\øjñªV©k+“I¢¨oÚ¶kB<5VqÈ?g0™îäYyÍñ11¼ÕûcîÝHí½¼Ã ÏpÏ—Ÿ‹'zþŒ£Û J•*•üßÃÃk׮ѾýÛ(|9¼=Þž® ž³k÷èß¿?]»v%&&&C"³tÉ—Ù–£víÚ˜Íf4Z —cÿ 2É–ÿ‰¿ÃÉ;»(ä €«&?¥=_ÇèxËC‡~gè°q|Ôcîî^ 2‹Ùó†rûö*V¬ìËÕÕ­VËHJJàû5ßfù …Š'OP øÉüÝËQ|?æ”fÀŒ fÂoÆ¢Ò(qö°i‰ »qüÄqz+2ÖF'%%rÆ^^öÁ …‚kׯãáæ–¦}ù“Û·ïä(_===¸ví¯×Ëšó0‘ Ù!‘‘QÔ¬Y-[2df4k2¨S§6M›6eÇŽ|µh!#GI®?M›7çµ:u˜;{߯þŽiÓ¦±víZ-ZD£F HJŒ}b¹GzÏ;vìŸôïûÄñ”s? ¼AçNm lG*ÀÊð3Ì9¸”V¥1¡Á ≑ÊUC(”Ù^€«R©±ZͨTjZ4y‡k×/qîïÔ¨V—QÃf3sîHÞjÕ‰ â¥iÔ²,ÎN.L¿„Ð+ÿ Vk°X̨ÕY³bqsw§_¿~,\¸Q£Çã[´(mÛ¶fíºõÉø“O>‘ ðË1‚e¼¤ø¯h‚ÛîÀvI’„–Bˆ¢’$…¥3`- ŒšE…g n‰@3 ¤â¢}ÿ¹ž Ñét”+ÿ Z.Ë䧈[;.űۙ/‹6X¸u6Õ±ëQgÉïT”*>Í2•ÉŒø>‘ãg¸rýo}Ö6Go¿á—“”+[š*U*“‘£Ù£7ÃhÙ¢+–ËÖ³Ÿ8Å…‹iHpÖÖO©ÔÆŽƒS~õÚ½æ’pd߯ÇÎÛ0›r?S¡|E¼ÜÀb!V¡æÞÃð\ÍÒ=BNÊkF(Q© ÿûó‰ãîùòsóòE6.[HÿϿȖ|OƒF£aøðáÉÔºuëÁ”~­¨X¢(K:ÌöÃçyã7˜={6ñññé8¨ÊÝZ;7;Ñ 7_ãË¿z¦›fßÕ•©ö×Z¢´Còávíþ…‰“fÓ·÷dœÝPk”,”ï¾ÛÀ€=øå—_(Uª$–¸¸Xâbcéѽ ;w´/+ÈÚó…Bpûv.>©×|FÞ‰gå°˜MÖ oeµHlžs C‚‰JPªmdÖÙKË­[avíyFåU›¥ò*DFF¡M±Ö.>>!Ç@«Õe³É‚&_ñ„ ÙEXØ-Ê¡wÒg]†G T38o2Ä3uêTvìØÁšïVÑ¥k7 )’ª,›0‘ÖmÞbìè‘\ ¡I“&Ìš5‹Oú÷}r™Ë£gÙŸgó@®¥TÉiäH]Çü|‹>>n2ròúI&]8J£ Ú|ÞtJ…Ò™¨Úþ’Þ ”õH V«…’%ƒùëÈßøúñ ü.ó>_ÍüÅyðð.‡œ"**œ+W/`4ðñ.ÌŒÉË)RØŸí»ÖS¤p1þ<º‡àà`$«åéõD’˜9s‘‘‘ìÚµ“á#Fs3,Œ_.&_>/š5kÆŒéS°˜_ˆ¹}yH‚ëÕ«ƒ¼Î[†L‚_Lt³÷R«ìûßu^v²›r@ ~ªË€@=``î§H+ì÷j¬æ•€•@Âsÿñ³émWBB)rSdr§Á{d¢¹jíV^­PŒ"<²}‹Åʦç1z<–g"È1$ Wõ§ñÛŽ!ÁïEv.G-]¯Q³±c?%qý*¼ÿÅàæFTLÌ¿R^³‹‹'³mÕ×|:k:'ç<ûŠjµšK—.qóf !¸Ëþc™»zþþ~¬[·«ÕJRRÞ­Ï÷T¢ŒwîÆÙ,8¬’•¨$[X­…yè R@hó¤à løi3_|±Œ~½'£Ó9¡V+)XÐw~;¸Nʼnc‡ »MBbE ¦`Á†©’°›©>¼ÅG&ñíàýÌHV)Ý ·+Çï±oÅ9n_ŒÄÃÇ™F½*¤:¯ÈBìß¼.¯¹™Tz‚þ+2dî@Él6P¡|Y:tèÀš5k˜?o.ÓgÎz"]¥Ê•>bÝ:w²IkÏ•RýªÕ*ü%ƒÉ¥ôe:T¼0ŸžùŽ6Þe™Vª=ª„8¤ØhD\,*šê?* ÈVNY-&5jÄÿV|KÇ÷±uû:êÔjÌ þY÷Ó7ôЀÃîC­Ö0}âRŠö'>!ŽÃìåƒ÷‡`2™¨[·îS½—'?ÓœÄÒ%‹0` !!ÿ°cÇN‚ŠR¦LYæÌ™#`\–Ã#ÉIð‹ !D1;‰Ý+IÒ#/Jë€ù@!ÄDI’RÚmNjý$IZd?¶Tq ‘æö½ìxŽ$IƒS<ó8°üEÉ#›©é9Ê—%SSÓ°¨ 4 êMTÒ=îÄ…¦:w'ö2F‹­³Ô*(蚺Áôq¤qñœ~Š9ôÓŸÀOÛ~aRŸæ9ºþà©P"£ãùàƒ²V899ñê+ïòZ½ú–hIô/?Þú5òm<ˆp÷ 2:æ™—×Ì T©Ñ¦1e_=gn^ùX:n¥_©A£w;æI~iµZ6o¶­9œ;y#'Ï#>Ñ€N§cÓ¦Ÿñðð &&ÆáÏ=wî¤råʼúê«|TqIòÄÒCÓ5&ìo @ïª p®’Lø C*™¬\ù=+Wm oïÉh4Z”* ºS  ;;woàðáý¬üß245¥K—Ì=ý’¬-âËÙ»6Ów³Ñ·C‘øÄà=ËÎkäVH Ñ6Ö_´L>Ú~5•ìøe‹ùfÙ»zfåU’$<==¸{ç^ò1gg'Ê—/›¼Þ6»0’ð)àåõÜ’$‘/Ÿ·oå\û\±Âc-ð# ôÉõšr£!ž‰'²nÝ:6mü‰î={\¢Dª4'Ž£ßG¶€}ôƒ $)1Úáõgoèa‚¿˜D›Æ¯ñ™±ÊÅ“¡ó@8öÖóÇQ¤ývÑ¢C¶ža6iÚ´ žžÄÅGó ü¿ìßJƒz-x§m7Â#°v½mh1ðã T­ü’$1Ñ$j½Ú”ý7ñþûïãâ¢')!;y`bæÌÏ™2e > @FŽ ‘ñ߆ÅbáVØ-üü|åÌ!“à]±©¹V¤èôã„ëÎ@`}ŠôõØ´À)1æX}lSÆÓÒ_ÍùVÀ‹A6o»¯<ÕÛntÒCÎÞ9DËЫ¯r×y3å·6\‰< €¯{FÔÙ@tÒãØÁ‰¦8Îßý=Õ±œà§­¿ V)hX#gä ûNÓªu+<<ÜHJˆ’[€ÜM1¡P(ùbölÄÝmàáù1˜C΂B5â!ámj“oãAp÷tÎjyU(T(„wwwî=Lý­KTªB‰JUÐ(¹;a°X™¸j}ž{;vŒ5kÖ ×ë>|8ùòåcÉ’%lÙ}€xûzÛ &P±bE¢¢¢îêæÍ›L›6ñãÇ3kÖ,4 Uª•JR© `Aw tççÍ«9wö_/[ˆJå¸nÉj5SµJUö~a[Âa5K$ÄÒU~žÙk#Ê •‚ÀJ¨Ú¢eëú>a$qçb UÛWÅj5纼Z­üýùçŸ ©Ž?r4•"E½×ëdÉ)VJ²ë„+%Nég '2d<7àïKïÞ½Y¸p!sfÏdñ’Ô]óâ… “Ëë—_~‰Ñëððo‡¯§ÿæq´l\‹±Ýæ¡N2=Öøò%ºJ=îHâ¦56lYƒÙŒ5üáM«‘_«F™ËQÙ)¯J•ýµÛ(á³O?Í0]uoWJ @ùÍ?âëªsx>I’Äøñã™2e ­Zµbúôé>>×Ä!=¸ºº2cÆ ‚ƒƒ)S¦ ÷³JÅãú”»%écæ¬ùìÙó'ݺŒD¥R£P| ºãSЃÖ}Í¥KçùjñhYÌFZ´hÁ«á<¼ƒÆIE×9o Ñ«ŠÔì¶Ý¨šô^܈›ÚðáÌ×)ûú“øÞ•(ÞŠ¢Y³fXžâT/+åÕb¶R¹r¢£cˆ{‚—/_6[ïûè>+–Çb¶d1,T¬X.]rB€s"Cf0â=z4NNNìÛ»—“'lÞ¼ïß»ÇÅ èûñÇÉñ¯ÇŽ‹Fç’+¯ÿiqâÖY>Ú4‚b^¾Œê÷5.ž……ü ¸,¸¸óã¡£Th×…ÿmØ’ê¸È6¶ÁdLä­·ZÓ«wO–}3ŽN†rèÈ~:u“¥+fS·vŠ–dæ¼Q|н1Z­•*ÖaÕšÙÔ­Ý‚Aƒ3xÈÔZ×T^ߟÞVYqsÕÑ®mÜ\´yG^Æ‹…K²S,2 ~aÑ(jÏÅØ4´¶íiê !©]=‘Ê*­—Ÿl¤}îpHÈ9,–g¦7<2š£'ÏÓ®A¥]ÿó3,èCƒ ò$Nî Z†µßÙ¹ÖûwÁb‹¡T‚Z“‚,+P•¯BŒÁÀïµâ½vM’·-?ÿDp1½ÃË«R¥Awé:”+_|ïýûé¦ rw"ÿʰl Œ‡Víð|B°qãFôz=±±±899a0<.{›7ofÅŠ¸»»‘êœ#áááAÑ¢E9räýõ ¦Ž›_À[¥‡Ð¢DŠ:•uøóOž:Kãï P(ÂF€ ø¸±â x›ùóf¦GÛ`0²rÕš·lG@±Ò/CÏ^ý²¥å ð£a£Yo[ÿœÏוNÓë¢HcšPÁ›‚A¨´‡‚ÿcÝeš5oJ‘"…2œÕòjµZÉïíMÉ’Á\¼xù‰óÙ%Â.\¤|ù²¸{ze#$ò{{SºtÉteÈΩ ™Ëg&>O À¬ÏgpéâEZ5oJËfM¸tñ -F©R1uêTæÏÿ­Þ@„ÏÝ»@· ÃñqñæÛ·çà®KfëÆÍ0víú…R¥Jpñâe>tL·oHŒaöì™|Ô§7ó &Ÿ—o·ë‡Ù¢âìùÓœûç<^ùüù Ã`®Ýá»ïçP"¨"Ý»Œføà,Y²ŒŽ;¡T9!²1ù(Y-˜Œ 2–‘ŒP9<’Œ—/»}〥±9¸J‹ÞØLœ?J’%„¸ Êà~i½³œÁæ@++iŸ+dÕ¤ôy©³!”)^˜2³}­$Áú_NÓµ[o$Éô œÁä-νȺ¥;sìÏ*&"ŽÎÛä¬ÑPë°ž;Ë>¶|T©ÁlBU²šuHüñ[$³”J4uêãúÕ*BNbî8%ðØá”›k Ê”pæèÉXžö"Y-¯J•Ý…«P?EYµš^t ˆ‹£ÅŠN¥ ¨³Ÿ[ ÝdÔ(î%ä U«Õ?~œåË—³lÙ2ôz=QQQÉáµ¢¢¢òDû››6mbÇŽ,_¾ü‰*IIj°92$™þìOû÷fÔèÏéÓk yPÀÇ%Ëf£× fLŸ”®æîØñtëÞ‡+W®RªTIÞn÷VÉÊî]{ ¹H™2¥²ôl£1Ïg̤zõjThä‡_ùü)åÅ{^cÍèCé:ÇJWOÞçü¬>µSZÏùh_Fï¿÷6&ÍÀßÏ—üÞùž ÂðtÓèû÷p3ì6={tÅdÈÞZN£ÑÄ»ï´ÍPkšö2#œ2ƒÉÏ!CX¼x1ÇŽ¥EÓ7“Ï;š¯ÿ÷-3>ŸÉA8p ùòå£Ãûïæjmð¥‡×è²~î:V½3—üΞO¤ñó-Ê×ËѤY¦Nï°÷•$+†Äh&NCóæÍ5j4_|9 wwOòç+ˆÅbáðïÛ±J:tèÈÔi£iݺ ¿ÚLÝÚ­3b93fõåý÷;²vݘ 1²i³ŒœÕƒË¡”)UJÎ2 ~‘ „È´€¤t¦6…_Ÿ…#í1~OÍ„’$]M‘¶àÜNq‹@=!Äë’$H‘Ö xå¹ÿøò^j´$á®+¼ï¥/œì$Ëqƒ‰óÿ\b@‡z9#Ðnr%ì>]»v})L¡/Ÿ»Á©ßCxíÝœuN§‡ñç¾3¸{¹fûZƉˆQ#¬’J…¦f]Ü&Î#Á§(nnnXîÞ°{3º×›"Í\Ϋ¯7áØ±c™ÞsĈ÷í!=rW^uÿ„Úplìãƒ5kÒþ­·àôIÈ—>„Ý»áСÇi:väu¿é„$z$߈§Ê—öìÙúuëøæ›oprrÀl6›RÆ<Æùóç™6mÆ cçÎT©RÿTi,Kž=÷ž½œ9{ŒÕ?ÌbäÈ Ì›?™¢E¼6t@ºxËÖtéÚ///Ö­]EÓ&“Ó™¦e¤[ÌFÊ—+ÃØ±ã˜=ås>œS¯Â.U+H›¡ÕØ8㯧Þ#üf,§cÊ”©”,QCR¬ÃÚW‹ÙB‘¢~´jÑ”Ý{öQ·îk¸ººdJ„Ó~«ØØ8þüëíÚ¶¢`¡B’ ÙÌ#› ­[5g×®½OÈ ¥˜¤ÉˆçV†Ì`µZprÒ3räH¶ù lÖ¬… fùòåôéÝ‹•ß­aÔ˜±L™4‘®]»âååEãFõ1â²ý¼ëQ·øðǨ*V¾3—B®žy/II‰1T©\Ž_öîæö»>|˜°°0T*ÁÁÁÔ®]g' ¥Š9sf3dÈP  8¨Ÿ ]Ìäi=5j4S§Nƒ ÙEèå+´jÑ\Î2 ~Á𠾓2°í‘$éšâ !ð63é…@ àk!D[»vØXšN~­z …M%Iº)„P ¯%£rëm÷FTB((hWf8Wžq¨Œ‘‘$Œ´¬S>G×ÿ¸÷$Õ^©Š¿¿/‰ñ‘¹–ÇÓÃýrúÌÙl]•< Í-Š”ÈGÃ9Ë+ÇîSØ¿ñ±Ù›¬P*ÕXBÎa:c[—çüÑ\‡MæÎÝ»Dß¾ÑhÄ©ÖÇò ?}†™3gfzO½^OÕªU0&Åä¾¼véÑéh6m‚Ó¿á!\IH`÷îÝ‘/51ˆ¥oß¾´lÙ’ &P¡B:vìøÌ븓“£FJ9øÌžýÝê˜ñù>ê݃† ß`Üø¼Ù¸Ÿôï“núŸ~ú™®Ý?¢T©ü¼qÝá‘Òj±³4QgˆeäÈϸ~ã+­ã­Q¯à_Þ› ý¹w5šß×]@©JßlôêÉûlœzŒ:tbð ’¢Þ¾ I´jÕœðð~ûí05ª¿’©F8eÜ¿÷€?ÿ:Æ«5«ÓôÍÆ$åPk4$ѲES>xø„ J¥‹Å’©Ø2d“!¾}ú°yófüüüX¾|9*•ŠÄÄDV¯^Mn]XýÃZ|}ý¸yóݺuãîÝ»Ù&ÁÑI±tZ;“ÕÌ÷ï.ÀߣȿÚ?[ÌÍ¼Óø|vÞ{ï=J—*†ÕbB†ŒìàòåPŠÊ!C&Á/™B¯|JºåvÜX!IÒ!D`p]q(‰M|pOA¢Ï !Þ6…§°y„¶¿a‹EüÜ#·¦Ñwc®€$ѶÔpÁzx†ûq×&ŸdµP¸paBB.P³óÌœÝøá‡¦nÔ¨>~ÙTh4j‚ƒƒršè´ZBÿ¹‰J¥$!ÆÀ©Ý×rt§ÄXÏ^ÃhÄUë Yô‹¬RëHܲÎFÛwÆyÈâ#ðp×ãé®Gê®}Ñuí‡Á˜@ÙÒY[KdHŒÎ² ™–×  ];ÛzÀ”&ÆiM…°kÞë¤ ¶š(\ÐË!ò¥–UÇRíÿ $0ðÙdBB.Òÿ“ÁÔ«W—éÓ&¢R©hòf£ôK·$±dé× >šŠÊóó¦uxyy:¦ ‘$ ‰1,ùê+ü™0l<åøSóí õ¬@µ–ÅÑ»iR]sïJ¬»Ìù7˜2eªg߬4+í«$AR’‘®];‘ß;??oÞ†¿oQ‚KáîîöŽŒŒ"::† .r3ì6íÚ¶J&Ÿ95{ÍLŸxçÏ—®èK—.;L†Ìå³&öíÛ ’„Á‡Åß|ó5ÑÑÑlݺ•M›I÷íÛ7˼Sbå‰ ¨ª¸²ú½yçÈÒ5 ¾˜•çuI²Z0g`"I†¤X/^HttS?ïMÿ>Ó)W¦:¯½Ú„¥K—±`þlŒ2 –‘  F> §h‘"rfÈIð‹»æv#ð?I’ž÷a0Þ~‹$Iq’$-Bü´*ÙIî2lbÏ4Ïv!Dil¦×µ€ÝØ4Äå_í„ø¹DJ½ ½íJn®®œú#$Û„/#œ?z™€Â%³¼.×hL`çŽüïÛ•ÄÇÇç虯¾ú*U«T”s“0½^O||<{ù5Wï3Ìæ%;>.'½>ËÜÊl20~Üúôýˆ`L´rôû›9’Á/ñ÷Ñk(àͬ¯Æc6eÍtQ©ÒÿåtÜ&/ ))I²"Yó¼r:–•òjlÖÍO?AûöI°Fó¨ =NX®ôï¥s' Ƹ<´?|øk×RORÄÇÇž£{ !²NÃ% WWWöïßï0g[¿ÿþ;ÞÞÞÙZOøð˜L&¾]±4CÏωIIìØ¾‹ÅK–óÇÒ¸Q¾ýßR\]]ú=­³üløZ¶lÉðφ±¸çn|ü¼(솳—I’ˆ0p÷R ¢hÚ¬ ßÜL©’A’¢³UN²Ô¾¦!2IIš7oJåÊX·î'öìÙ‡«« žžhµ:ûÀ4‰èèhöìÙGùòeéÙ£+ rùÌHww7T*%§OŸK–!22ŠØØ8‡ËiES7Œ ¬]»–fÍš%O8Mœ8‘Q£Fæ( ^xX8c›váö™0n–åë®]¿‘îñâÅ)V,ï' $«³1Ž5kV3lØpfÌîOÚÍ ¸KT”ë3µþñràêÕ«øøÉeGÆK !;Kxùaµš¥´&ÀY5Vª´\¹Æg#>#.Î1k]]ݘ1}þ…žf$Å̆=ŒKÎcI²æH+âñh5Z¾_»Ž7Â’~¼ûN; S–Žj•Z£º$ 0›1³f­Õ»‘´p&Jß@Ô-Ûeim¤£pVMöÕ=š]û ukÛ“'±–-xŽ0¼¼°8é0›’²^þ²•ZÇšï×Ó£G‡9¼R(|óÍ7¼ÿn;L¦§«GõväÈ‘[{ìææÆ´iÓ²UoCC¯P­F]êÔ®E` ?w<<Üyø0œ3gÎqðÐïÄÇÇãç[”Áƒx¨ùø IDAT>¥k×NézŠv$”J j­7oÞbÛ¶m?qœ[·ÂP()T”ªU«Ò¼ysŠ)„É%›´Ü.1Q*¨µ"ÃÃ9}ú,×®Ý 2ÊFè<=<(V,€ŠËãîé…É`Ì“õÜJ¥µV® ù¼¼ðË• ÿBpPjµÊ²ª1š}úô¥fÍšôëׇ¤„èT^ŽM&3—CC)]*ýóááø:ÞÐÈC9bè³Ô Z½ÇŸä믿&::†ñãÇè_XŽŠ #[غm'k¾_ËšïVÈ™!㥃ÞÉS&ÁÿU `¶Hè²`­TiP«õ8’y™L‰yB@òtp¡h4š'â‹æ8¬Æ<Öœ8 …µÖI²b4Ä?S/ÛY-¯ÉDxï˜=iý$¨Ÿ­A†Z­'>ÑHR’cÌïu:ÎNZLÆ„¬ƒç¤ÞþúëoŒ3žÛ·ï”d .ζFS¥RQ²d0µk×¢U‹fÔ©óZž“ßôʳR¥A¡P%¯µ|4af13 ƒäÈòšÙä‡R¥L-e“OÂjµb1[ž‰Wñ¼’Á‘$øVkœ’Û¦´®@žF‚M&'Oqxþ)\ˆ"E ?ó¶Z©Ò RiQ©u’â0›‘!#;˜÷ÅB"""˜8aŒœ2d,ã%Á³”ñ˜Ìɦ-2dÈ!ã_ĵk×ñ)èƒþ­“OLJâÞÝûøÉ™/CFðqÿAT¯þ vê g†Œ—)9¯“³ 9KdÈ!C† Ï x“ÿL´ÙV«•„øx||¼åŒ—!#‹¸z… âÅäŒñÒB%gÁ²ö_† 2dü›ÐëõÑÑÑ3‰Î&“gg'´ZÜÿÉ‘U|9”âÅ‹ÉuF†L‚e8Bˆ/€¢ÀeàRŠßÛ’ÜÚÈ!C†Œÿt:}²m2d>dë 2 –áX|…-„R0ðÐ\…WÒãG¿·d‚,C† 2dÈ!#/!›BËI°Œ<=vññ‹íñƒìä8[ÜáíûnBˆÐtÈñe L&È2dÈ!C† 2r‹ÐÐ+É$X†L‚e<;r œ´oäG$¹&Ðɾïn× §GoZÌ&9seÈ!C† 2d<—/_¡¸¬ –!“`/Av!µ¹ÐѾïQ¾Bý)^,âÅ‹QÌþ[¤p¡g—S† 2dÈ!CÆó‹ÐÐ+Ô¯_OÎ/5žË8ÁBˆ)@s ¥$I7Ÿ’vðð®$Ižl$I7^ˆ,„ËñãÇbÿ>ŠÐÐ+„^¹Fhè®\¹Jddþ+H?"ÈE‹– ² 2dÈ‘HJJB÷Œb$Ë‘]ÔoØŒ™3¦Pµje93d¼”prözn5Á~@E@›…´EìióÜŤ¢!ð#Ðx!H°$IqV‹™A¾i:àDNŸ>Ž^ïÉ•«6R|üÄ)~\¿‘ÐÐ+DEEáïïG±b´kÛ†wßi'×2dÈ!#×8‘ ÎS¾ü+ÈsÍ2žGÈk‚eüð¼’àñÀBàæs&WÀãeøð:žWªÕF§ÕQ¡BÙ'Î'$$zå*W®\ŧ@¹¦È!C† êË•­V‡É”$gˆŒç ‘‘Q!pww—3C†L‚Ÿ5$I B3:/„ÐK’”˜•{ !t€!«Þ“…Î’$ÅçDn!„ ÐH’”ð"||¥‚ä8­i–““åË•¥|¹²r-‘‘²nb0Q©”¨T*YYYY†çJ†™õ¿OƒÕjÍòr¥ì¤Íxl£@£uF¡ÌÞ7µZ- qHV«ÃòMZ£F©T‚ȸšŒ&,fË3ûž … ¥JƒR©B…]+‹‹ÙˆÕj~&2¨T¹”áòåP‚‚ŠgLÔZÔj=ᨆ“1³Ù 7 2^|,„¨lVI’4&ã$Iz3 y< œ”$é!Ä\à-àuI’®ÛÓ`Ш*„¸,!ƒfPÑx[ÞD!Ä6lÚå5ÀXI’V¦H[X€Íã²›â&6³çQ’$%ÙÓ¬šØ/ùŸ"¨(IR´¢10 ¨ ¨„aÀ.`ˆ$IÑÏ{!xšiVR’£ÇŽçøþµš5ªåøúcÇNðîû²xÑ4nÔà©éïܹK»·;ðé§ýnÆ-IÛ¶í¤D‰`J”úÏ5‘+ͬ™SéÝ«{–¯Û¶m'5jT#þ|ÿš ÏC>È2üwd¸sç.'NœÊÕ}ßx£.NNNò·x‰‘]ÓèÇÿ`î¼Eü¸n%" $dÀàÔ­]‹·ÛµÎ±Œ:½;“§Nc×®ÝÙº®fêLŸ> “!GùŸÑé´ìÛ·Ÿ£ÇN`6§OêÜ\]xûí·ÈïíÙ”·äS©Ò Ñ:z•mÛ¶qâä nÝ Há¢T­R•-ZèÑÅltü@^¥Am—aëÖ­œ8q‚[wÂBP´ˆ/U«T¥yóæY–!³Á*µ–K—o0â³Ïˆ‹‹sˆü...ÌøüsŠýˆ°ä£2 v,ÎÎ@{`LŠãoþ€Ÿ"Ÿ$Iáöã¯e€eöýüötê׎³oûþØÖXÓ!À¥ÀY`8àôÞ€kŠ´5½€ØìªºBˆZ’$™ì¤ÖhìÁ~È „xØ‚mðl&Üõ@y;±~®ñ4Ó¬û÷ïÓ¬ù[9'Á5óçÍ¢cÇ÷rt½Édâþý Æ,§?{î<†çI~½ß± #G eÄgCä$Nœ8E•*•Ò=·gï>‚‚Š;„¿LH4%qönÕ}+½Tï%Xlc ¡ÔÒ.Á§†ñFýúh4š]íÚ5Nž:ÃèQÃäŠò#;¦Ñ‘ôýdùýY¶ü[zõì’iú}¿à¯ã!øí*W*Ÿ£7J•†ßbꔩ|øÑà,o°M³b1Õ«W§Mëf˜siö-èôzNŸ:Íë7R¹rEŠôey𠜹ó2{ÖT%)O4ÂB´:7Nž:ðáCÙÿë ÏO+ÎlîlÎ߿ξ;8p õ¼ÁÌÏgQ¡| I±™H)ÃaƒùmÿA ÏO \|l–çî]ç—ù;0` Öçó3Ÿ*Cfš`µZÏÐ!CHŒˆ¦NÕÉËÇŽ0|Ø0~þycž“`•J‹Fç‚Õj{÷”– !‘˜$Yå†I&Á¹I’Õ®yí,„ðKáI¹k'¡oëíÇ›Û7ePÑkcíµ¹d/¡Bˆ-ÀïiÒúÛÉôàÕG¦ÉBˆ¯siÒ*ÅöÝ ’$ýmÿ¿Xq˜m'Üs$IúVác'Á«%IÚa¿G{@t$é¨ýú…Bˆ­@u!D€$IמǟÒ+¥iV†äïý÷©VͦÑU*•YÚFŽIxøCúõ„wï,irŸW\ºt™O •[ 0jÌvlÛ˜>ÙKL$>>þ??R˜ëß*” Àh6Ñ÷ç1½uš}Ý¿§€ËK0A`k¬KàÓÁ#ٺ釕ãððpœœéÚ7{ý߯;7òðáÃ,çÌÉž†Ã‡çÈ‘¿ðöÎO­Z°û÷°æûulÚ´…Ê•+P¨p‘,O¢gMZ½&Nbú´iTjÈÇ+š¯¨kºéÞŒåÈ—¨Q£:£Gaô¨‘$%æŽlåF†1cÆ0jdÆ2„†^¡uëÎFÄÅÆÑ z->ëÝmñB(œu˜ïEb~ô” ¡R¢ôtEá¤E2š1Ý‹„Eó8ôÏ©\›W«5zJ5HVLƤtÍ¿µzWjÕz#GŽþøãå»Ý¼ªdý·z¾ûʉ)‹chò–ãœ5þl ÛöìAÒš‘4&И4&$9ù¿ÚlEc”Эh ’ý¿„Öh¥ŽoöîÀÓ3{>)‡ì'Ÿ›7= g)}xìCn…ßÀø Ì1sÒÿÆÆÆòÇ_'hßãm‚×›÷¤[Ïþìݵɶ>6 >þt8•ë´ÅÙÕgWONÜÀ¥K— ~ñ–ñh´6mÜ̾_C¥Rb4š¸zå"ƒÌ»}ûB¡àðïGغm'ŸÏ˜„Nç„Õk“…hõntëÞ-;~¦óœ×)Z:óI“ü¾®´T…Šoú1wÒ,®ß¸Æ²%KIJŒÊ‘Fø‘ ]»wgkd˜3i×o\géWKÒ•áòå+™® N&>ž¶~Oô¸MšîÝnXÆaIGhPy{ òБ¸~%†_¶¡§+êWê;¤ŒèœÜÙ·ï?üðEŠaذ¡¨”‹%Íœ$áää„R©ÄbyLØÕj5Z­I¶Œ~É!¥úŸ—$x7`xD‚íäTül'µ ì•Ù›)ô„LîUÁþûO:çΦ!ÁUì¿§ÓI{2Í~ ûom!DzZè$àiöC«n@O ‡â(6ö¦ ä}.Sï” …"]ò›Ñ&„@¥R±þÇÕÔoØŒví;°oïö g¯_È$8å€ÎÀ”1£p·Ä1sò$†Œ‚óÿÃ÷߯Ã%2ŒoÕF’$>œ8ò5ë0v̇<{êÌ™©Y/ŸÇ€Y³¿ u‹fé ÿÕÇŽ Xü-ˆR&ÆìËŽ‹ûá>V‡ßÆZ3Å‹N‚­`MS$EI$ÅZ1ÄKXÍ ½«@¯@—¨@é-!´y?â°X`Â<–®ÖÓ¥}";VEQ"ÐLB’ààŸf.q¢qOÖ|M£:ŽÿùÜ”Jmííaëz÷îÝK¹råP«Õœ:uê_ût¾j74²z‰5_wlü²+JÍ:Ì~w±úÝÅ싯ý×ÿ.f¿»zHÑëFünð·ÿúÝ0áÝÀøHLJÄ3ÞªñZf­­YÿûjƬôÜö¿'O¡h@¹dmY!ß`®y1÷‹E Ô?UÚM?o%ìn,k¿š|¬P`Eþã…$Á ¥‚Ë¡W¨U«uë¾ÆÆ[غm'Ö4Œå‘.WW:u|—’%K0yÊçܸq“R¥K;„ku6íëÖ›é<·>OZz„»‹Õ"\£PêzV6?çÔáÛAë ðdäˆa$%Æä\†?ÓyN< :g½®'Ëð#þŒøìIB¯\¥x±À§ƒ4*·¬ fââÏÂãËUhJVÁtã~²VX¨•hü}0Ÿúƒ:c £”*Ü^mœû¾U­ãøñS4mÚ4ùŸ Ýì¸!6Sè¿°­Á)„(ÂcSèÌì¾|ì¿éµiöMû¦ç¡9­CÁGm,ž/øcv“¡7jI’„5€.@[lÚîêÀT!Ä@7I’ž3Û [Ça3ÁJ´7 ™y§”ž ÀJ¥ò©ZàG$øö;ìÙ»^=»2múlš6kñ£qqqÉæÌDÖd7}vʵ5Ïîý"΢…„\dͪ•´/íN‰†íØò×?Ì™=&ÍÞ$Ÿ—uëÖfÇw!H’Äíˆü‹ñNû¶¹Ì»Çתµj*Ö®L¡€ÇÚžmK6=ƒï#eð?%ûéº hŠ:u Ìüm)ëÎn¥_Íéöj+¬õÂQ3ç‰ ·nßaÁWK8ÿ÷?øûùòQ÷n”+[&OòA²‚dK¢„1ÞJ|”…¸h+†$ ’Zg%.ñJ\ œÌ )ˆÌ£3ÎI‚þãÜøý¨†ýë"(ô8Ÿ]]àíæ‰´h˜D§îôêÆÉáxyX$ƒí·R°žJÁ釱¯_¿>½zõB¡PðÁdp_Éqe2ü¾_MÙÊ&<¼,y[/r|_É2ý²§¬÷¿68;éIŒO=@¯^ïmV/C³& )S¦÷ï?`ÜÄÏiÓe\ª´¦¤8œ]œúnj¥ h>:MúÙeKàZ£G©z<dµ˜1㳡}œN¥TñzÝ×ðñ)ÀÅ‹— »E±b”.]ŠreKóãúT­R™’%Kä¢,d0`Vi9yê Ó§M£ËÜzéà“;¯²yö1Ú ­FÅÆ©Î{vá­Q¯0qèDZµjEÉþÙr–¥L!Cç9¯g‹§”¡í¨jL6‘V­ZS"Ø/Y†»wïáææŠ““þ©ùeM0 kØãÀhÀz'Œˆ·ßÀeðxœ{ ÅxýÆ¿ñó'·`jrÃ,4tZbM4亞«5z¦Nšj’cÛ¶m?~‚reƒ±¤°î°˜ ÄÇ=@«Km6nHŠÅd|!»ÈxH°›¦@5; > I’Y±‰Mܸ&IÒéLîs¨ ®¦9—6ˆí£óÀõ4ç2H»R’¤i¹ üQÀ<`ž¢6ÏÖŸ`s v›çéç6Ó¬¿)_¾j¦¦Y)IpFæÐi7€¿ÿá£>Ÿ<ž‰ˆ‹#:&6$ø9¢€ööYÖÛpúÌYn\»‰2¸¤­ŒÁ½{ˆ‰Ž¡l™Ò,胇›3&L (˜IŸÏ@ŸP /2L ]°|íŒzV4ÊF6mÏң߳ôè÷|P© ŸÖêb#ÇAyc†»s×f~¹€vƒÞ§Ñà6Ü ½É˜™Ó¨U±CôÏ“2,3“$âc-DEš‰‰±`0ZШ•¸º)ðŠ×`6*‘¬* .(òL#üíz=»hÙÿcòYY¶Æ‰½‡5ÄÅ <Ü$†ôާrYË?¡JÓ|¬Ú çÓîŽ]»~ñ¦ÛS›äðTQ&@‡³³3'NäúõëìØ±ƒ7ß|ó_+¯¿ÿ¢¥As9fíóÚÿV©R ½ÚÄ­k! °^¥RÅ­?¢{ïO9ðËVÔj½ú¤Fýèôû׈·¸vž6­¦åJVW’’Eœ‘$¸x'@o ³fLåÈ_G³Î]´lÞŒz÷$1!2[Ï`â¤éèô”+Sš† ëQ"8(•ó¹²eKsñÒeÊ–-íðo¥Ö:3tØ*5 ¤H)¯ô ðœcÉû›fÙÜĤ%Âþ彩ÐПÏF gÛÖ-$fƒk´Î >”JMŸjüÊç§B†6Œm[·’h¶é“Ì Ý»}˜¥{X"cÑ·ïLü‚©XܳτBÜ܉˜ÏÇ}þw`µÕó- ¿í}<€R(ÞѽÕÃ¥»¹ú& …Šû÷²uëÖ'Î-_¾œ…_~‘ŠËñ,IðlާÞŶövÕ£þHÄî¨>ðÕSîó(>O3laŽÍÁVJÀæ5ú=ûÿ”è˜fÿlŠû¦ê„… À?’$u³~4ZU¦H7›ö÷MI’¢%Iº,B„`ód]áy/6Ó¬ªhµúä™étg íÚ߬`•JÅÖ­[ILLÄb±`6›ùá‡;öÿìw|ÅûÇß³Ws)¤@€„’Ð;ÒbAª€ ˆ~;¢(ŠØE± ¨€D±€ÂOAQDlD@šô"%¤ÒËõ»ýýqÉ‘J.É”y¿8.»;;7;;e?;Ï<3å‘ÙÙÙ¬_¿IŠàBüï¶[tS&?÷ÇÇÓªSWÞœör‘üéÜ¥ Í:Ä1tÈ  *€¯¿®†¢uzö¾'ŸwA~_iáDmêDÔ÷4‹wÿÈô51¨y/^ºá±óZ†Öþµž¾ø” ÿ÷*zƒç±aëÆŒÿè¾õ¯O{‹Ÿõ¯“7¡QB ¨n—òr]œI³‘™ãɃÐ`-–<7.—'MŠF‹¢h£Ô2×ü¬,¹y‚Io1ûÕl‚L*#«Æð›­,z7EûtŒy!„9S³hÓÌÉð›­¬Þ ?/"xËþ¢# ­Ð"ÆHff&Ó§OgäÈ‘dffž·òàvÃò¯H=­¬YÛÍî­:ž˜”Àé$ ‡öiÿŠƒÞbÇfMZ8yìÅ\Œ&µŠ¿í©žÂe;ƒËœ†;;wF6îÀ\T£UkÙfÇvÆ9ÝIN–“Œ<&«£ÃÍ}Özu¹ÐlÜŒvãfĉ$:*Ù\“nðØÏWç~ÀC£î+R¯UUeöœÑjµ<4ê¾*õ¿B>ýø] ºÓàG ‹ð²Õ¨UŸèؼúút5Š%Ïn$¶i;ïyæÜ,þøþ¾þr.z½®JÙV3ÔÀ'ÛåTî!^éñ _}õÓß™EL‡ø\U7cÇ>NÇŽiÓª .WÅÌƒxi³eoѼ¿þú‡ÿÛsEËáÃGø3~ ~Ö¯l\Ì  ,!7¬sü…ãÇ“ˆ¬„Û‡r^†øÕ–š†Š7¬1³\Åñã'½i¨Å3>ö‘ªÓ…3ËFµ™óIQ(=N'¶ßW’zCkp»<ÙUèe¯ªúîœiy¨®ª™¨kuF.üÌ; |à 7ðçŸâr¹X¼x1³fÍDEz|–\x¬ªêI!ÄV<Ë)ÀïùûmBˆµù"XP†Wèbbú0I±QUÕQü¾y¬6›-ìÞ³W¶Å`ê¬YÜ8`ÏOž\f˜‚¹lªªb6›Ïé5×ku¬Vè¹Ünú¼;ó‚‰`M/+š^ž‘µŸÄ3á×w¸¾ASû>‡8¢Çù³íÈ<òï(hJÊ&¿1•羜ìÀ…¤‡=qŸL˜Í²å?0dˆ 4Zv«gq“”j%Ûì$4HÅæÎ©¦ 3(h J€@ ÷o>|÷³‘:µÜôïicÔ³ÕxqlI§>þ?N'OÒðÆ 9ÌœȧoeqU +Wëý^v a`·Réõzìv;«V­""âüx$¶Û/<\ž­<ðd{¶éó¿0†Þu¶½_ÿ‡­vlÒ1ðV+×ö±ñÈÿÂP40þ•œ*ýþUíbHÏ|7dºQsÜàBÕ¸Aã ŀƹ.•$ºT4ŠŠ&J%ºm$áþËÍÚõ^›†rì8¸£kƒªÒìø 9ªcþ®I/àêæÛê†ÙÙÙ<ýÌ‹Ølvû°÷Ø;3ÞãåÉÐÃo+býTÑþ zõ¾\ð!·x>Þ$$ÌÓŸ¶ï>ˆo>™ˆ9÷{†?2ÝÞbÎ᧯¦ñþŒ×}rtTF½B¦5™“Ù8š¹›ÈÈH‚ÃjÒqðEóX¸Üe×çÄ¿àÔ©S´mÝ´B¿ÊÞ½û0›-ùæº%©Q£::žÓ§“©U«¦ÿ–µ~üñG¢Ö(á¹T\ŽŽŒ©FdÝp~úé'î¿ï.Ü>˜âj´V¬XATÃêüúñ.þÝ‘‚ÍìDoÔúþB‡Õ‰Á¤£A‡šÔ¬Ί+xàþ‘>¥¡8Î3™Ú]MУϓ;{šWìª;®ä$O²öÂAðø—Ñ6ï€íð)¿Ü—ùóç{·zè!Ün7ñññdffòã+¸i`v ÉÁù,:)]¢è7  ¬+GL§ !€MBˆ@ž9½ËAÅN¹%_X¿ ¼•/´Ó€/;óÅkOuð,Á4Jqh‡gÙ£•xÖ&.`7žÑàWò?-ðx›îg)¥QBˆ@K<ë$/á¬ì‹Lûf‚U¤.Cü–e"]šÐ-˜¤(¾»†5 Ô«[‡3)g8“ræœa322‰Ád2±gÏ?ìÙóÏ9ÃÇÄÔóYתU“±=Â+S¦þ§#Á³Þã×øš4nÈ7ö9¯i~ñ…³bÔnw0âÎûùnÙW\v¸Àþp¢ºÝëžù{kÿÝÌS+_£ctkÞ»éet Ž/L¸– ê9Ñ ô¯ êÓ/¾ÄÝSFPf˜»'âµá/ѵsg¿>(*&И@ ÐôzVàpª:mÆ ³JÓQ IDAT¢ª*F½B@ ‚)PAohƒôøÕ,ú÷õzö¶‘pDKX57é‚  •Ñwzô²sóþÏDFVþ‘ èÎChµ«8]E¯K«õ“ÉÄÕW_MNN!!!ç¥HN}!˜èz.nê)gAÁ*¹9‚«{ž5\÷›mÜ?.rºÛÙ»CWåßÿôÓ·/šê©ÿdú·gáêÔˤçquêùÓv–®ý‚ŸçLàµ%ŒÁöÜx#‡—gµjÕ3f |ð^šLÝºÑ 2ˆ¯ëÀcÇŽ%((˜•T™þ·€Æ0ÿ³÷yÏzGhxM„¢Ð{È–|öš·o2çf±âÿ¦ñδ‰tíÚÙ/ù—œiçÆÆ£yoÓÌÚx¯öü>½z~ƨ~±Ä5 åÐ) o~{ DÙ¯Rû¢(ÄÄÔçÀƒ´k×¶ÈË$£ÑˆÓéÄl6Óªesö8èß¶M£eÛömD6ò]—#„k5 aëÖ­<øà½>>ƒiÙ¶m5S½~0Ö{¬ìÖŠO©±™ÔkU>“­Û¶2ª K…òU5Ø&8vŽÝ[±ýµú¬èuØ‹®K¢Ócè=Óƒã±Jªú -­ž]»÷°gÏïËö””D||< ,`ÈÁRKþ3ü%žQà„bK-ÉŠTU-n²8L!§Wù£¶Í›ðŒèÆ‹¯Ï§ …=™¿¶pW mþ±?€áù"8³PØSBˆkð˜OwÅã úS`…ªª?p»…½€~x–a²«ªš%„hg9¨8íu222Xµêgž{~"'Nœä½÷?$""œþýûóæÔWq:ÌUê ÓªEs¾œ?‡;GަÇÍR½f]R“Ó¨y'V¯˜O×n᧯¦óÞŒW¹¦{7¿åaFžƒ¶±WeKaÚ_·óîûóY·ÇÉ[NcÔih .oÀˆ6¯ðʈۘ¸ð¿ á&òϾED°Ñhä×_ÿ eËæT¯AóæMYµê7®¿î?öù II'ªi¬˜>‡ 7p2éŠP|NÃÉS'Œ4ÐíÖ¦ Â¯ïªÔõôÝ–¸¡ùõ£]œ<é{J½4‡ û±3„~¸˜Ôë[àN;ƒê(fæ®Õ£‰®OµY_`?š\îzÂ>Õi‘ x·û÷ïÉÀàÁƒ7ÎcñµråJÒÓ3 Ðødr.‘"Øßí‰ÀË¥ì?RÚþücË(Å[´ªª§òæGÅm*$NÚàqLµLUÕx ™3 ! Ü¢.¯;_P/òázþ¤Ø\cUU­ÀÜüÏ%CE½SžkNpáQâs ÄÊŒ_Lü×K?»\.®¹¦ß|ýE‰cCnβoKá3? Cû«¸öÚ«‹ìß±c7ß/ÿ‘W&¿ä—´Õ¯_fDÇíèÒ¥S™éŸ3k&öŒd~[õ ½úö¹ày˜““ýcž"+ç¬Óv‡ÝÎo¿JËfM«y€ŠaE*B§²?õ0÷/}ŽšÁ‘|zË4‚ gGD]š{òü^6ßxëmÆÌïSøØ– !DËú ›éæ§‘"m¨Š1‚ò4„šµDš X¬nœ. Ò¢Ó(¸Ü*6‡›ÕͬⰀÁ"иý·~°š/ðT›Àæ³E` ðÔáï1b PéÜÖªz¶Gð¿wÐ1C«3fhõR¥§§“žžN=غu«ßû˹&nèo#¸ÚY1ûý¢â®³£ËÿÛ7é°[ÝzØ ÕSØ·KËc.²… *+`, ¯MÃÙóúRp±·tØž‹’pÔ©8¯¹ ç6“w9-Ìûãžx’ýû÷±rå*5Œ¥E‹–¼ýÎÛEpeûßâ´hÞ”o¾ú„¡·ÝõFqdÿß„„ÕâtÂa¾ýt_|>‡Î:ø5.•<‹Žž îaõ‘$edâêžü¯ÕK¼sÿ 2sUì.Ï —…»&2¦sMÇû+ûM7mÖ„5Vâžååå±yón¾ù&š4iħŸ}ÅâçÑ?!Póo…ð9„pUèv›§¿ª¨î;º-q·4ñöŠ¢øÑgx1 @¨…6ªz+ eáÂ…Þ}·Þz+ªªKëÖ­Ù½{·×ÍC£îÅnËC")Òt\†×dË× „µó+‹Fq3ð°Ø!oýY<¦Y{)opΗ5‚Ë!­ÌHð®Ý{¸qÀ6oöm4øôédî¼ë~9Î1 :Àÿj$X£ÑðÃ÷ßXâ³dñ¥îÿäcôìÙ£ÄþÎ;òò¤0ý7ü–››‹ÅZºy¯Ãádîû³‰ Ê㻇ó÷‹Ù¶}'9994ÿ=zŒ-Iél¬ÓÁûÙª«Îª_Vûç9)ÔÍ1ÇIî[ò :#óo}‹ðÜðó¾êʺ¿6Õ¼.Õ"|_OõOßɫӧùõåŽ0©èÂ!0B©%²–ŽzuŒ4­H‹˜`bj¤E«¨*¸Ý*.‡ŠÛØýW¯êF¹Ù²[G·v|À]C-¼ñAÌ71w¡ ­Få‡ß Œnfù¯F’’†õ¿°Þ‘###¹å–[PU•^xÁÏ/E`ÓŸZw8ë™úÏŸ lˆ×1…þëw­;:)´4Ôº_ «^êKÍïñˆô ìt+®´Ô’£À;®“ÇÈztú˜H„VSµ:­5ð믿’’’âÝ·{÷n¦M›ÎÔ©S1™LÞý ,@« û’h/· RUõ€âàI IñgàÃ@/õ tW¸ï+þ PQïЕÀù÷§Â"2++›uë6žîÛr f³™ï—ÿÈ5×tók&&áÙç'þ§"XFSzçQ֜€€ÒN‹Î“ ÓÓ3Øù÷ßäd¤Ñýê®Þ%² xáÅIìØ´ž;ŸöøŒk\#ˆ)S¦òÀýw3`@¿ ›-M…2Ãà·{šœ›ÊÝKžÂévñåí3¨¹©¶ñÕÐ>–‹öó÷&zÖœ¹ëõ+tNhõ0¢š×cÍÚ¿¸îÚîþ*‡BˆSAuëP^¯ZM‹ÕæB‚-zƒðxŠW…gu —ÿœD÷¹ÆÆ³SƒynL.Z;x|rßk&<ÔÍú­:>ýÚĬÉÙ¤e*Œ›Ì”ñ¹úïe@\×Îôë×·ÒV/6›§Æ­R›Ÿ—+ÈËñäèŽM:¶nð€NWÛY÷›î½l¬û]ÏÀ[ÏŠ]Kžà“Y¼ùqã屺fçnÔà`ܰôpµjj2¡Ù¹gß^>õmz­ÊøñO±:>ž=z Ó¸Ëž¾ö¿ ¾^ǘç?§IÃZ„›xzòÿ±èë•D5hG¿.ýø¿¹ÉLO¦c÷´¿v(ýnyšÝ‡]´j^FãÏÏ'ñh “Ÿ¹¥Jy™gs‘‘ÀÝ0ý¯Ûqºí¸ÜNl®<ö¤Ä£Z¡àVݨª›9?ÂÃæ0´k{¾^{Â/÷³Y³ÆìÝ»èè¨ü Vbıu$`4yàþ»YþÃO€Ç¯ˆÓá`íÚõtíÚ^Ãî¨Ðoº\:´ïÀïï®$(¢ê«$'fqú`né€Ûåô= :ðÇ{?Ùïëˆpq p:!›ŽÃ:úœ†’)èë×$wÖdlýβãQív¬¿¯@;w:¦{ŸÄv¸òó‚uº€"±^{íµRÃnÙ²…S¯&.—É•KáæX½Ep~g4^ñ³èúÀOx–eŠWU5C¼‡¿¼Cûú w)›C׫W—÷f½Eã¦meá)å¾N~æ)Þ¼µ#§Òs™1u*OO˜P$Ìôi¯qøP"/½:‰h“BªÌ’o¾,SÔ_*¸hq­2¢»?,M6÷~û4–,¾¼m Âë¡Öp!j»uÏß|¤ÿ=Š[Õ£jTøÜA£‡2ó©Ù~ÁÚnŒªBA«Ób4 B2µX-.TtzA`°]€@£áç&aØMV¦}È]‡²xN&;ÿѲân7tíà`ñœ ví×qçcÕz£•;oñ¯éä¸ÇÇ0îñ1ÿY¹TèÞÓÆÜ·Ù¼VÏC­4jš¿\×g&|2—5u‘‘¦x@Kò)…5« Lš‘MÃfÎK¾]úiëw8¹‡‡¶&ÑLX7÷®2Ã&eœ,™A›[vÐMH°[†Æá°¢žcbEúß3i9<=yw éʇÓïCQïÎ[Å‹¯Ã#ò϶?¨_'­íkžO«Î7³ü烌¾»'Ó&ÞŽ‚×f~ÏÔw`ؠδnV·B×Enn3^}EQB0éùqL¼nŸlÇ‘Œ(Š—ÛK-YnŽfî¡v`¿Ý×&ñçš¿¼N-N'u¢£Ù¹s7.§‹;wS¯^]†Ýr3­Z6§fÍH¬V+yyy¬Ž_CϮϾ/ÏärÚ0`O<ñ7¿ÐÖ7Ô«ôȺ·ËMü‚½ 0Àçul]N;dܸq¤Ï¡zÝ`Ÿ…pi891‹3'2+”†’í|(ŽmëÈûàÍ" Cèô(a¨ªwfÆYgYN'¹oMBßå´uZâ<“Y‰üSÈÉ5óý÷ßû|Î_|Áä—_’"XR´ü^Æåkµò—þ&Äjµpðà?´jÝEøö椬9Á´…"ùÚ„S}<ÇFõý7|A£Ñ\ÐéTõÊ,;ÅóvçÎݼðâ$º¨Q-ˆÕ‚xsén´“o—ü_‘Ñæ 0uÎÇÜ8`üö9BT< §¡,s4U-ãÐ9Ê„ªV¼L¢‚ó‹@Ü?±4Éæþôg9šy’O‡N§edO¸6ô+S+Tæ+š†O|Á #úV*žˆZÕÑV3²oÿAš5mRåòpVD€6ÒQ(zØ V±™5¸œªgtØ(0*h ôžsªR ŸkÔ«ÌŸ™ÅÍ÷‡7(‚ÇîÍã¶›,™Tÿ«å‘ ÕøîgÏ<œÇ“æUéþœ¯6§ªi˜:/‹›t4ná$¼º«YP»ž‹«:;Ðê<Þœ—É铎ÔR3ÚÅÓ¯åxÖzV/Í|( EÝ6¸Ü.R²R8¡³—ë 3={ïÜ´ŠŽuÛ ZT„ÕŠHKÇY¡kq¹\¸\ç¶ø¨Hÿ ðí›1[íL?ÔÛf>|w/Þ™³’?V­äšvF._ŒV«aÒä©|õÕ|%ìí«ž|¸?ïÍû…/¾ù‹7'Üîóõ8vºtîÈ{ï¾ËÏ?¯ò ¿õëÖP«V-F^5‹ñ[’~â`ÚfNfï÷š÷*BK׺CèQw,¯/I(­9ö-o‹…‰‰©ÇÂE‹½K%¹Ýn¬V+-š7c÷nÏ2†ÇOœäø‰“DGÕ&''³Ù’/$]¬Ž_Ãu×vG§7`·Ù}¼¯NbcëÓ³× lúöŸl_¥òùý´­Ü8 ÑQµ0çeT( =z^ÏÆoJ¤¡,!\šØðM7ö¯XŠˆQ­m5#gÆÝC‘FC£ÅÐ{ Õf-·›¬GîÀú窳£ÄB9v$5þ܇+#ǧþ¹0:½‘¯/Æf³å¿¯R6lX k®ýû÷³sçN¾üòK¦Lyåìó¤zŽöK"E°äòÆh  e«|,{å½CWtD÷RwŒåmüÿÃ%’.6Ú¶mÍŠ—òüóÇÎCœÎÈcÀ-øwÔCe”=#Fãe“‡ÚQ¹X[ä16ëö$佨©´ÿª;<‘ Úóß«ªªÊŸk×ñú³C+G¿ûnâÃO>eæ´©þMœÚHŠК†ìಫ  Š.@  (*èý›_íZ:Xûm¯½Äkïažæ)sanÜ`cÝÒtÇ:/Ûºi0¨t¹öìC¾Ñ¤Òñê’ýµ¢]ÔŠ¾¼<§~ýÔʳ/0·mG{×,¾ê)œ>zÂ×þú¸\¸üèa¸²ýïÎŽQ=<˜èÚagÓ§ÕЦe=NNã£9¯zÛÓW^~ž‡Þädr¡ÕL…^ éhÙ¬{÷UÜ$ÙbÎäÞ{îäûï-ô< áÝ÷ÞgôØg?ã;ÚÄ•.€ìHaoü1¾Ü¾¼ÂiðÖ«°`,‹?CÍÌ·›‚õ¾‚ŸšŒé'±ÿ› BPmî´¼AîÌ)±ìvã>“ŒeéBt×Ý\ñßÕèY¹òlýîÞ½;_ý5NÇÙiZ‘5kÖpÝu×y^Š?ξ}ûi%Gƒ%…Q$W*olµ–兩te„lec]L¨òUa™LyëmVp‘[«e™¸€'ô²¹nW´§_ä¯[™Ú÷®[Ò×—&Ô“¦yMü÷_šui‰¨Â‹¥¦í›±c÷nÿ{S-èhª¹ÑEC@4E ‚¢kƒ)RÁáñ(­ŸŸ©^´‹¹odqlc ûâÏ°æ ‡ÖžáÝW²/k,)TGÛ]…«C{ 3ÞCde—^ddb˜ù>®®]pµlþŸ÷¿N—m)Ž„EA§Õ—èOL(¥ 1+ŠÀ媸KUU±Ûò°˜3½Ÿóm‡ÓÉöÄLæÿq”)_ïGäÏêoW«Ã[½Îß&Re,P—Ó…ÓåÄérÒ¬iÖ­ÛÀ™Ô4ºwïÊ£Œ"77ŽÛÓ°QÏYùm¢ZŠ×1‡ÝÁ?ÿìG[¿N§Ö­[ðÒKYúêßd$UÜ{zÚ‰–½¾…)S^¥YÓF¸œö _† &ðí”ÒÓpëK]·hã ¤ë°&¥¦á»ü44mÒ°Âið–'“ëï+Pí6„^R»áKâ ¸{,¶CI¸­vܶ„$L£ž!ü«ßPjÔvl¿ýˆP ÇœB©3wß}76k.s–÷“›B·nݨS§Ž7œÙlF£ÕR½ÁTô…¡1˜À E#Ì+9|ÅPdÉò"G|5Í*<\Ù‘ÜKy$Ønw”äYŽú½÷ç°hÑ×U޳W¯xíÕI—Gc¢ÕÒ´ysêÔ‰.7lÁ\®K¾V©*{›_­aBÇܼê›Y¨'5ˆúfdmûÎ Ÿ\µõe…t¹1ŽV¬ä¶aCÏK:…NE„ƒª¢µ <«Ã« ž`Íù.ŸP»ÆçQRðÐüÚ$L·Ätßh,3§á®[§ô~îè1žxìv¬¯¼ä›7é*àKÿÛ¢I4_-Û@jzÕÃ=SrÜn•öŸàš®ÍJ†oÍïëög¶hòˆ ‡ÃžƒIÜzS—óv-™yv\Žž¾ú+"tíxá‹}œÉ®ú°Ëå".®3Ÿ}öñå¶yó–"+GT¯Á¨ïåúë¯áСDN>«”5ihÓ¦{Å^„Ù,Ù¼øÂó=ö/óŸü†!/v¤~kß|1ü»#…e¯oáŽÿà©§Æc5WÎEÍ’Í„_äè±£ÌrI©i©a:¯iPíL·ßÐ0ö¾ ãá8Óò°>U4œÓ…ípÚ˜ÖÔX³Ë·_b[ý·Þê¨ø‹H§ÝÂŒ3ˆ‰‰¡víÚŒyVsf‰¾Ùå´òý÷ß3wî\Ú¶mK‡íP„†k¯½ŽuëÖyÃŽ7ŽqãÆñüóÏ3yòD¬æ,Ùf^ZHŠ` ¾›f-_¾œcÇŽUiwýúõÞ‡îK¤¤$^{ýM:wîHíZµŠËÊÊB§×a (ÚñœNN¦VÍšEöY¬lV¡¡¡DEÕº¬ÊRëÖ­¨V-䊩;¿^Ç®¼y¬ëÝŒlç"Ö‰¸€#Œ'Nž¤ñUM«Ï5·ÜÀ'ã?8o"ø¬ÊTä„É…Ä]·æÏ?"`Ì8oºÇÀþ8¯»w]ÏK;åØ ´ñkЭøwíZ˜?ÿwTí‹¢ÿ6°3SÞùŽ·fÿÄÔ ÿ`á·q*%“áCº–ÿ¿›»ò·+™õñÏùó‚áã/ÿ +ÛÌ¥„÷N—ÊËÿ·Ÿ&Q58xry6ÿ¼´Ùìt»º+ bcÈòa$ßnw“C³fMèØ±=f³™S§N{ç pÝuÝA(8+è%ZUU,æL>úp.1õcyå™É´îYŸ®ÃS#¦ô¾/åH¾9ÄžÕG™2åÕ|ñ™Yi˲‚4|<÷#Ož~…6=ëÓõÖòÒÀžÕÇü’Gr&ºN= éÖ·Ù†-á4jY–*8S2q¥kÐÝp †ÃQíNÉᇕ:Q5˜ñÎtTÕÍ’UêOv[-š5äý÷fâv»°äe.§³I¤–oBJñNi7—^£Ñ°jÕ*V­ZUå_×h4ùƒÑj…Ò»páWlذéœ!o:˜  @VǯÁj;·™Y·®]èØÑ711õ˜3{&/N˜Ì;o;ù寈‰©O÷«‹>`<üÈÌ™=³È¾Í›·°gï>î»÷®RßL] egÿüö[éë0[-’O'W(Ö bhÐ ¶Âi8q"‰¿~\Kh³k㦟Iã÷ßW“žÍÏ;Î. îVUÒ23Ù¸q3öÌt8~èl”©§HO¬p™\·m#ú÷£YnÝ2óã|çƒÎ gÛ—r´Øµ”Öñ ’tú»÷ì¡u«–~+*ddÊÂb^öè?ûÝâ¥è–-/šúêØF݇ãÞ;QM¦óÚ6ûÞÿB¨0&ŒĤiKÙµ÷(!Á&~^½“a;Ó£{óélÖ¨ãêÇÔw`ÓÖÃhu ¿ÆïáÞÛ¯¥S»X¿\—ªº©^½:¹Éìøy®× ÷ ³\EdÝpj5 !(Ü3úž›nãôAæû÷ã‹íßÓ´IC¬æô*O­*HÃóÏ?Í Aƒxîùg ¥!˜ pc~¬œ>˜MÊñ nìßÛ¾§YÓF•Nƒªª‡óǦ¿°Ùý3Ï{íÖÍDD×Êï³|K“ÓiÅé,ŸÃa)âyÝjÉæÏ?ã¥,Gàv9±˜3.±g2IUrŽãåÛåTórSKtÀgM°.î2°vÝz ômmÃç¾O§Ní¹ª½ok¿<éž÷˜,$>’––NlÖ~÷ùçÆóüsOý§i9rï¿ûÖŸ2 2 —r|ÆåB9tå„g­Rw(ÜÂX²­2ý¯ªªÌ_¼ŽÙŸýŠÙbçŽ!]yfÌ@t:M™á?œÿ;óÆãtªŒ¼íjžÕÆ?S‘„衼ðÂ6lÚì³pB0è¦<9îq,yé¤ßRNËŽ»hÓ¦ p8üãI£Õc0qüD+V¬`ë¶­œ… *´ì‘¤<';vìô{¼QÑQDûhz(Ó Ó Ó Óp©s©ô¿ç´Šƒ!¨Â΄\.v[îu4©ÓëÑê´8ìvœÿOWQ-ZEÑqÈåv9p:m¸ÝçßGÄ…JƒVg@§7ý¯Ò3*›§S `‰Á’ $‚%‰D"‘H$‰äJÁr‰$‰D"‘H$‰D"‘\1H,‘H$‰D"‘H$)‚%‰D"‘H$‰D"‘"X"‘H$‰D" ŠI< IDAT‘H$)‚%‰D"‘H$‰D"¹¸ÑÊ,¸2Ñép:œ:uœ¼Ü,NBQPÅófDQÐEA Š@Ex–CP4UE;~!ª@áqŸ¯‚Pj3ý|oä*ªwyUõ,ƒ„ª‚PÏn:&„'|3sÏßž¥ÕEA\žùûó7ðÄ«+´í=FAPµè¹ªŠ*8·šRÍÇ›Žü4z® épýÊ@„€Šž¿½KGäo#òCŠüðˆüú¢æË?Wù‡óë“7žüˆ ŽyÏùu,ÿw Òä=¦žý›üt᩟'yÒ]Zó/Xõlº½õÁí)ï.7ªžºàR j¸=ÇÝn··¾¹Ý*ªÛ… ¸\nàV]4‡T£Víh´-‡U0‰D"‘H$çï9N.‘tùS|‰$F‡[…ƒ÷P-¤Õ#jbDˆÂ†jѿբ{J„QK+­d< ]^NœýÿìE‰¢Û…•ˆ ”cùñˆâ¿^(QRÑOM‰ôT<ý’Ë—³/jJ¯Åë€Zö1U-½ö¨%+—Z¢2ªÅ‚ª¾¥±àž¦¿¬ú\¼¾œ#D©õ[Åfµ––BVvµBžµD‹s:%•ãI©è …*ä·ü–ßò[~Ëoù-¿‹|;mêDÕ VdõÏrà+Të 9¼½ÁHtX\.n—UuËÌ’H$öM¬(ŠVÏ©¤cX¬f4hŠÝf..ùL§RsÀí¨×ÈŒ“H$‰DR&V» „†¨È`"«G”ÁÒúŠ|èÔ™•AËVp:m¸]N™)É•Ý*ÛV/X\ªªâr9PU•ÈšQìÙõ7B”¹'“3Ðjµädç'o˜D"‘H$’r åÄéô"äœà+µÔQ¡(¨n‡Ì‰äJ•¾Bàv«$§¤‘“G^®£Ñ@µjÁÔ¨Š^§ÅWk¡ªÆåv;Ñjt¨eL'Ðhu„…˜ÈÉÈ”7N"‘H$I¹„…’‘m.¤‡¤–H$’+[+òò¬8x„„„2331›ÍFBCCiØ M›6 ZH`¹S%ü×¹Ðje—%‘H$‰Ä÷çQÆËuùDqE¢ú.9JUêYÙ™I¥î òóåïóIY¿s¡~_"¹$°P°YlÛ¾‡Í›7ãp8¨_¿5$==cÇŽsôèQróòèÔ±-ÁA†2GqýWéí”D"‘H$‰?´Á’r¨¬`,ë<)@%’‹EÑräè1þúë/êÕ«ËàÁ7a³ÙX¶ô;†Ýz ƒøø5lܸ‘ˆˆpZ·lŒªOq]Ó½5kÕòÏÍÍeÝÚ¿|ŠK"‘H$‰ä‚< É,T†Ð¨RGŒ ö•õ]|_Yq”vž¯"¼ø9ÅG¤Kû]_¶‹ŸWx_Y×"‘\l¡g¶óÏ?û0™2d0f³™Ûn»—&NâŽ;F`6›¹îºk¨S§ ‡ÈαYF¬¬¸®½¶;Ý®¾–+~Âår‘••Åm·ÝÎŽ;ÊK"‘H$‰DŠ`‰ßPñ¬Zðñ‡ÎÎL*Up£…¿}׸ý‘îŠÄ_Öy…÷ëZ%’‹MÛí’““iÓº5ÙÙÙÜ~ûpL&›7o@UUFŒ¸‹ÜÜ\:wêHzz:V›£Ø:â¥ÇÊÌ™ïððÃcXºt™7Þ1c)7.‰D"‘H$’󦇊i!ù$")Søù2Êéo³iÄ}¾‘bWri‹`ÍîÀápÉÒo—±oß~Þ{oÁÁÁ¼ÿþ»lݺ•+¦Zµt:‹µTÇÅãr¹\ôêÕ“éÓßd̘Ç0›-Ì›÷Z­¶Ü¸$‰D"‘H.rNð•Ì9F‡³2J ½ÂኟSÖ1_þ®È1€jaQçLgV†g´6+#‰jažï‚8Š‹y_~· ¾²~³øoH$3nU%0ÐDpp0YÙÙ tß/_ÎC=Ì»ïÎdôèGèܹ½{õ"7/ÀÀ@BB‚QUµD/W-gMòòòøì³ÏiÞ¼9GŽaõêxŸâ:W{S¸½’H$‰D"ñIê¨g-b‹#G‚%—YIÞOUέÈù…Ï), °DrÉtn¦€7nÌ¿GþE¯×óÑÜQU•îݯ# €çÌF«Õrøp"111áv»Ê+77—‘wßC@@ß,þŠ3ÞæñÇÇñ˯¿–—D"‘H$É…BŠ`I•(,/¦¸ W5þóyÉÁªŠN§Ðºu+²sr8t8NLJsf3qâfð>Z­–Ó§“ÉÎΡaÆôŸâJ<ò/qq]¼qô¸þzÞyç-rrrËK"‘H$‰äB!Í¡%> ¿AYx4´øÈhiñTtÄÕ׸++Œ‹_Oy¿UÖyûÊ3Ï–H.6v Õ#Â:t(ß}÷ÇŸ q£†ôîÝ›ÌÌ,Ž?NNN.½{÷&"" —Ëìs\íÚu ==S  «ÅJpp Ð£Grã’H$Ë–-ã·ß~+±ß`0H“&MèÛ·/‘‘‘Þc«W¯fÉ’%Œ5жmÛVêwÓÓÓQU•ˆˆˆór] Ìœ9“¾}û2hÐ ÿµ¯O<ñ-[¶ä‘G‘H"‘"Xr©à‹+. +û·/Ûþ¼ŽsÅ]ÑcRðJ.UTÕÑGttmî¹çöîÝKBB‡£ÑHÓ¦MiÒ¤ œöÜsNÆõg\‰äâ#--ÄÄDBBB0Þýäää°eË/^Ìo¼A«V­ÈÎÎ&11‘¼¼¼JýææÍ›yóÍ7™4iÒyÁf³™ÄÄDÒÓÓýܾª$&&. D"E°DâÅGc¥Ð”HÎn—›5›ÀÀ ââºÐ©SGÜnE(Š‚ËiÃaËAUÝ4.‰Drq2zôhzôèQ´î»Ý,Z´ˆ… 2eÊ-Z„FSõ) ˜Íç×j¤qãÆÌ˜1㼉l‰D"E°Dâ3RôJ$ÕíÂjÎBV—ÓÝ騰`õg\‰äÒ@Qî¼óN6lØ@bb"‰‰‰4nܸüöBU±ÙlEF–/4&“‰fÍš3ŒÛíÆår¡Óéʽ—Ë…VëÛcµÅbÁh4"„\.N"‘"X"‘H$ÿVÝ8Ö‹..‰DriP¿~}9uêÔ9EðÞ½{ùì³ÏHHHÀn·ɵ×^Ë]wÝ…^¯`Ê”)ìܹ€©S§¢Óé˜7o^©BôÅ_$55•9sæ (J‘ý'Nœ`Ò¤I4hÐÀ»þüù¬^½š·ß~›””¦NÊM7ÝİaÃxùå—B0jÔ(fÍšÅp:ÄÆÆ2jÔ(¯¹wGŽá›o¾aëÖ­˜Ífš6mÊèÑ£K½öÜÜ\¾øâ Ö®]KFF4iÒ„‡zˆØØXÀ3ÿú»ï¾ãÉ'Ÿ,2—º`ÿ€¸í¶Û¼ûÿùçÞ|óM†Nß¾}eA”H*€ô}å<æúH$‰D"‘T‹ÅÂÆˆŽŽ.3ÜÆyê©§8yò$·Þz+>ú( 6dÉ’%Œ7‡Ã@çÎiذ!:t wïÞEnabcc9vìû÷ï÷îËÌÌdûöí¤¤¤°}ûö"áããã  ""»ÝNJJ ¹¹¹Þãééé$$$0~üxRSS2d=zôàðáÃ<û쳜û,ýû÷çî»ïfÖ¬Y(ŠÂûï¿Ýn§iÓ¦„……¹‹ÅÂþýû  !!‹Åâ=¶yófêÕ«wΗ‰¤t¤–oEäG~äG~.¢åì—H$ÿÓ¦MãÆoô~†Ê£>Êš5kˆ‰‰aüøñeÎqݽ{7 <˜êÕ«96|øpL&¿ÿþ{…ÓÔ¢E BBBŠˆÆíÛ·I\\{öìÁétz#@·nÝÊwðàÁE¶ÛµkxF™Á3x×®]´hÑÂ+Ø„Üyç%â[»v-ááá 2¤Èþèèhn¼ñF’““Ù³gB:wîÌÑ£GIKKóæÓédðàÁ¸\.víÚ@JJ ÇŽóéz$’+[ç”þü E°D"‘H$‰äœ4nܘ®]»z?={ödøðá<óÌ3¼ÿþûÔ­[·Ìs?à5s.ŒÑh$::šS§Ny«¯(ŠB§NHHHðš5oß¾«®ºŠV­ZaµZ9xð WGFFÒ¨Q£sÆ©ÑhЬy  xÌšN:…Íf+õšëׯ_äe€Åb!55•úõë—ê8« =yÔ¥KàìèööíÛ fРA!¼ó¥ D}y#Û‰¤t¤c, iiélß±“ììÀó6SUUù-¿å·ü.òL»vm‰“ §DrQ0?¶2ddd\êñÀÀ@Ün7yyyT«V­BqÇÅÅñû￳}ûvbcc9sæ íÚµ£iÓ¦€Ç<:66–½{÷Ò¿ÿrãÓëõ%æ á.6™L¥ŠèÂû •yí99žç¯öíÛ£×ëÙºu+}úôaûöí´mÛ–°°0bcc‹ˆàˆˆŸ¼qK$)‚%¥ž‘É–-[©Y3’±12C$I™dgg³uËV:uîHXþèˆD"‘œ‹ÚµkpæÌ™RŸ9s!!!!Ž»C‡hµZ¶mÛæœ¢±fÍšlß¾úõëãt:ýf:ƒÂ+î c·ÛÉËËónרQFSæµ§¤¤xÅ¿Á` ]»vìØ±ƒ´´4Ž=ÊM7Ýx̲—.]JJJ ;wî¤OŸ>r™%‰DŠ`IeIH8Dƒ±Ä6”Þ%Iù9¼‡„„ÃtîÔAf†D")—‚%€6nÜXb4ùøñã$%%ѤI¯ +p„¥ªåû mÛ¶lß¾ììlbbb óŠÆß~ûøøx‚ƒƒýæEÙd2Q»vmvî܉ÍfÃ`0xÌÙõ>hkµÔ­[—ÄÄDRSSK̉.0k.¼fq\\›6mbéÒ¥\uÕUÞïo¿ý–Ï?ÿ»Ý.çK$U@Î – Ñ(Ô¬)3B"‘øDÍš‘(Šï£ ‡yú™ ìÞ½WfžDrÒºukÚ´iÃÚµkùå—_¼û322˜9s&Bˆ"ë߈ØC‡y—N:qqq$''ó÷ß{cv:üõ×_tîܹL/Ó•aðàÁddd0{ölï\æÔÔT>úè£aGŒÝngÆŒE–dZ¶lÛ¶m£}ûöEÌš;w~ø5jx½?·nÝ­VK||<´nÝZ.‰¤’È‘` ˜dFH$Ÿ0•9¿­8§“S2ôŽŸ8ÉGFÂDFÖ™(‘\aŒ;–W_}•3f°páBÂÂÂ8r䇃ñãÇÕlРŠ¢0oÞ<æÍ›ÇçŸNÍš5ËŒ»K—.|ðÁ8Ž""øª«®òú4ð÷¨é Aƒ8qâ?üð›6m¢víÚüûï¿´lÙ²„™t÷îÝ:t(Ë–-ã®»î"&&†ÔÔTRSSiÛ¶-“&M*><<œ&MšpàÀ"×c0hÑ¢»ví¢sçÎ¥:Ú’H$¾¡yùå—e.\横ûe‡Ý|ö͇ÎÈ©SLj¬ª‹3©©„‡‡#½Ì,‰DâC£b'##ƒêÕ#ÎÌl63òî‡H:u “)“)€Íoeð ètº’’FGrrµk×Åé´9–œšM`€ÌŒ,™ÿɦvíÚ´mÛÖë%¹<„DDDЦMï\×úõëGdd$z½NG¿~ý5jT¡AË–-½b°yóæ^R¥HPPÍš5£GÞöÅ`0H³fÍèÕ«WÑ(„ 006mÚP«V-ïþÆ—j6­ÓéhÛ¶mÐ;v䪫®B¯÷Ç'‘H$‰DâOäH°äœ¤¥¥•;ò[X(Wp^Áñ‚cÅ·Ë‹GrebµZIHHðÙûeñrZ¸ü•Ve¹;÷#00‘#ï!ˆë҇ݳf¦Ãn¡ÇõÝÑëõhƒÁÈ)9‰D"‘H¤–\B”%&S€Á’ËWðú²¿,Då9Å’N³$çÂs芔_˳¤üûaqæ”æï-[ÈLOÇh2b2B (¡ˆB"øìßn·›3©ilظ‰ûõ•™ìG\Xøß.‹™3Þ㆞=éܹ³_ÒñóÏ?Ó¯_?¿]—?ãËÌÌdÿþýÄÅÅù%¾;vP«V-jÕªå—øfÏžÍ#<â·¼‹çú믿"òÎßåÎ÷Âét’••E`` ;vì¨tÞ9N233©^½:û÷ïÇh4Sî¹[×oåàŠƒ˜„ ðLÅ&Âu¥¾¦>æºfîx莋ê^deeñí·ßrß}÷ù%¾ŠäÝ¥ÞÞY­Vâãã+ŸVëB£Õ{þVJþKÒ7òÐèGùbÁ'RK$Éù@Q!ó[¹ÄÑ{?òòÌT Á­‚ÓáÂfw  ˆ!À`УÓ꫆Ýa—™ëGʼÑÑQDGG•Ø?wî<úõëÇ<à—tdddðÌ3Ïøíºüß±cÇX±b?ü°_â[´h­[·ö›ã¸eË–ù5ïÜn÷“wþ.wþ¸§N"==:uê°hÑ¢Jç]bb"n·›F°bÅ ªU«F÷îÝË=×f³qòäIïöºuëJœ7}útž~úi »¨îÅñãÇÙ¶m›ßâ«HÞ]êí]VV‹ÅoñõèÑã²è+¥–H$— ¥ `)Š/ªÛ‰Ý¡%33—Ü\3.—›’÷™0zôè+&ïü]îüq/rrr¨S§ÁÁÁUÊ»œœ‚‚‚¼Û=zô@Q*Þv=z”zõê]Ru6((ˆ.]ºø-¾ÊæÝ¥ØÞû5¾ÈÈHjÔ¸ô‰Ê§‰D"‘ø—ËMvN]»vÃh @QæÇ`0о}ròòGŒ%ÿ9Fã·øü)¨ýŸV«%$$įèþrF£Ñ¯yçOÇs{Þù»Üùã^Œ+ŠR¥¼+n5c2™*•¾²DðÅ\gEñk½¨lÞ]Ší¢(~o?ýÙWüWÈ‘`Éy«À‰äò$5¥4ÇX ªªâp¸0a6›½l…Åoáí   œwþú}ò¬¿©È(°D"9øÃÚívûmäÒår¡ÕJ ¹²‘5@â%>>^f‚D")—V-bK{Dó|©øü†Ø3ú£=_òŸ ਨ(¿8ÑTûï¿_f‚¼EHMMõz…® 999_r÷!00Ûo¿]È‹€èèhÂëI|>B4®ZIÀv`—ªª–*Ä©ƒ€dUU7\yEV-7D\—v²fK$’rÉÍ9·¸Òjµ¥Žüßö§ ¤¤lì« ÷ä"¡Àû¯ä"¨OÇZ¨§OŸ¦fÍšUŽçðáÃ4lØðÒ,Z­ß–3’T “ÉtÑÔ‹Êk!õâ²?B… €À×ÀKÀ`#pXÑ­ ÑëeÀÄ p…¯ËjòßðÓÊU•«ªJÓf­eJ$UD£Ñœs>°ÁF|$IåÙ¿ÿE‘Ž3gÎøe$øÌ™3Ô¨Q£Bç¼ÿÎ Fß}V«µÈþukÖ0¸O?Ž$&Ê‚"¹ä¸Ø&a}Ü,zÍNÀ“ù"vµ¢÷Åœ¡BˆšÀÀ%âc^-ô¹<ècßJ÷ɧŸsìØqÙ*H$ÿÏÞy‡GQnü3›ÞC ¡- !ÔFjBÕ+X~`¹‚JH¡HÇ ¢"z¯^.rEEE EDB :H 5@é=™ß»“ÍöÝ@Êûyžy63óÎ;3g'³ósÞsLºo”½‡;Ö¬´.Ù‡®¾eaz#ÈÎJ)3 ‚ÊMQQ‘ÅãxeY6¹$ÝÚ÷¢ñÎ)bÆ ç;¾XÇþñ1_obó;óX6k.WÄ—$¨ Ï+ÚŸ*Œ–$©Jø^^’ey—,ËçeY>*Ër4ðœJ¿!¾P>ÌõzmD¥­WjŒ·Gx„¬oSÞ{ºÁI ¨n$$$póæÍRË’““ùã?„qª°xµÆýN™@Ð2îܹC½zõŒnÿÉkñÎ)âù'zâã]郞%|ìxvíØIÌ×›Xøò(ììX3f"+çÎãÆ áHT*’'¸ {eY.ÔòÏ¿øp‘4î&’$)T!Ès$IzW’¤a’$¹š"À%IzM’¤å’$MÑv-I’³$Ia’$-”$i®$I}K¬k SÍ6•$i¤$I~â2{´˜ë ¶ÆŒt2ó¨1"8??_KP­8þ<×®]+žðà³gϦ]»vF÷‘’RÖ³Ÿœœ,Œ[AÉÎÎÆÅÅÅ¢>²²²JÕ6—+W®Ð¬™ñе½¼HLùûÚR á“¿ì`áË£þ>ÇÜ\ò‹Š*é8QÁŸ(Óƒ“$i $I¶ZDÊ@Y–É%Ԋʃ¼er0Øœ$©£øÿ€³ÀçÀ`1°_’¤Ï%IrÒhûpØŒæ»$IúE’$; x_Õ¼ ðÐO\f•ƒ‹—.—›wªäؼ’‚T×2mcùJ¶Õ%jumWÃ!§M†E°1äåå c ª-j¼téR<<ŒÏ´ºråJâââJ-[ºt©0h%##Ãbœ˜˜ˆ———U¹)Bõ¹aÿ‡w‡@Önû¾”ž2tXñ|jfSÿ½žÅ¼o•1ËAµÁ²,?À/À-I’ÖH’Ô[%0uñ5ð„jÛ¦²,מœï%IrÖ#€€ÿ Ì@= ¨ ¸©„ðkÀìm[ªÚ¦=dY®4@™Àk 0S–å/oÕ&ßȲ,ɲ¼Z\f•ƒÄ;‰å"‚Õ"4;+E«˜*)RK¶ÕÖ^__ºÖUUü8m*°Žž`Au%))‰Ù³g³lÙ2“0ÀÂ… ùá‡J acË’ =™™™{qïÞ½‹···E}äææâàà`òvφw‡@>ÜöC™uiY™ÅØÔd[Á¥…ð|•=Ô&¿$IúN’¤ö"ö@/àY–çɲ|CÕO,° hLÔ³Ë(ËD=+ËòfY–óTHÒÄ IDATÓlà20U’$õõ0Àx[–å}ªýÜUãE i…þ¦«^þ+«Ò½Ü÷¡M–\fH°ê[_]“ß›V0$å8ãE°,¨^Ü¿ßl¬zöañâÅe„° bb OpzzºÅBúÚµk4mjÞ£ªwýz<ÈH+³<'/ÉÖÖâó¹’+``Y–7›UaÎC€þ*¡; IÒY–ÿ£jÞUõ™ IRFW—TŸúŠßvG-Û|?`/Ê1ËEÀÇ{h%®¬Ç6OZÄü²=†Aû—ùÔø.+D²áq6­´úW’°³³%++«øJß”‘‘­H’%¨4,X°€ÈHó+-æåå1mÚ4–-[Æ‘#Gô¶õôô¤S§Nz…ð¬Y³Êõ|çÍ›Gaa!‹-_¾™dffš-×mXGü©xžë÷œÅÇqóæMz÷îmòvê,Ð%Ç«ñò¬Aäá„Oô'ãìì,¾pA¥AQQL–å[²,(Ëò?€Z(Ë$|!I’»êïªÏ€ãÓVÕºf:~@jž(×k™^ÑØ>¸/ËrAU{RU¼ÃúÊ‚¨¯ægIþ³qÓc× Ý›V¦ûˆw7göïßGvv¶^œ““CüÑ#¸¹:ac£({’ñw&;ۢ틊ŠÊÔZ5—’á'NXý\W¬XB¡ ;;›+Vˆ/ßlŽ8\·a' OPïåz|øÕ‡X|í)¦=ökÀ©™üx(¶x¾a/æ>3Œð±ã‹_€ êYEÇóC…ñK’´h ¼ Ërž† ΢%Ij LF™lê[ WÕäu@W¥îLËÕÛžEÈ´Ú£œÔ¬ì×€@7Ï=7”·ßžRî‚Ì’¶U5É•°iÕÀÆF‡»#÷ïÝaË·ßPPP¨%ÚY¹ÀÞÎWWÜ\PØO° zàèèÈ[o½ÅÖ­[ùàƒô†C§¤¤0cÆ £DVll,9998::ZMáääDjj*NNN¬X±‚iÓ¦‰/ÑÌæà6϶ }D{ÆDŒáÓ÷>5«Vprr25kšþûûÎÝ hß¹”~ëÓµtéÕƒõ1?ñFÿ§hP»N²Ä7hÕJG *‡ªHáÐmQ†>÷~ÖÑæžêS-’Ï«>sdYþ]ã†ãt®k5„,§K’t¨ü!Ër‘Æö(“d©A$-$Iª-Ër’FÛ (½ê¯ \y±·³/OpɤJ†,j«¹¾º"lZqqvvÄÉÉ‘üOrsó$ EÉÿ+ $$œœP(äåå+²D ª]»v¥Q£FLŸ>åË—ãî§§§Á¬ÏYYYLž<™ÁƒsôèQBBB,>¾åË—DXX±±J_Ÿ>}ŠÅqe ԫW¯”76%%…{÷îѲeË w¼š V³Z0³…pBB-Z´0ùXf/ˆbî;ÓQH~}ûá{¬ùâ3¼¼¼Øøå¿Yócú=Ŭ¯>ãÅ c…T*\‘Dð*ü±$I÷eYŽÓš~À@6°Sµøg`>0M’¤ÿ©<ÆjV ,—´˜©cŸ[ñÀ›ÀÚûªìFéùõÒmÀ`‚jŸê¶]€*!](I’Z4;W†KAD–y9ÂáÃGLÚ&(¨öööE›±Ë %u2f™±ÛUv!ü¸mZÝFtÝ7íq±QŽõU(¤R!x%_4ååå“——£µ/YÏýJ`=.\¸ÈW_o”5låB¡ [·n4hРX»¹¹™ÜOVVo¿ý6QQQ¬]»–¼¼<òòò þ#€ûõ+[ݱ² á;v0xð`7n\¼ÌÕÕ•ÈÈH&L˜P¡„›6l !œ’’‚§§é¹1$IbáòeÌž2b~bø˜QÅ¥š^ñ*¿ü7Ͼ;‡ ¢îÖMüsWaY½ZYð&66–^x¶Ò?¯T$¼e"¬áÀ>I’–(Ùý€P”ÞÖ±²,g«Ë I’>V Óƒ’$½§jÿÊé뀾Es€ç÷%IjüŠr ð( 6ð¾,ËWÕ÷&”µ#%Iò@Y+¸“j?ª¾e9[’¤‡@_I’V¿È²¼»âË_ñxùņ/ñkÓš©Ó”ïLòóóQ(Å¥'rsrp(j¦žÿáûÍzÃŒ´y–!lZQî!²ÙjîO•µúC«V-Y´0 €×o ƒ”àÃ?d̘1Eå–-[èܹs)‘e.\¸Àœ9s˜>}:Ë–-3IgffÎüùó©_¿>íÛ·çØ±ctíÚÕ¬óݲe íÚµÓ*€5…ð÷ßÏСC+íwkkk˪U«ˆˆˆ`âĉBëÀ–asÆk áwW­ààÁƒeìôÒˆWyvØÿY- _Pqñöö.ŽL‰ŠŠª¤Ï-T˲,K’ô °_%jCT(½¿ÇéêòD%P—(ŠDYÇW}¦?ª2Fºö™¬Ê ý!Joð[ªUÉ(k/)Ѷ@’¤` Ê1ÄáªU—€§5Žk>°e2/w”^eAgôë¯Ñ¦ukzõêÀŸç/àæêJÆ صû7Bûö)n¯9¯ k 4!ô„MÁ££ÿþL™2…U«VéÂ[¶láÎ;F àæÍ› †îÝ»Ïüùó9räH±À4DFFááá,X°€zõê/¯Q£ééé˜5nô¹çŒË@lìqVtìììXµjS¦LaÒ¤IV¾wïqÇsóêUzöì‰EØ\!|ãÆ 5jdѹH’DZZµjÕ*³N`Ae¥¢Õ .”ey­,ËmdY–JLβ,w×"€‘•¬‘e¹v‰ö Y–‡È²|¥D»ÕºÛß–ey¨,Ëv%¶¯%ËòbY–e¶)²,¿*˲}‰¶-eYþU£Ýû²,»©Ö—YåùAT `€6­[ ` Œà5F  râëëËÛo¿Í”)SÈÓ2n}Ë–-ܽ{—‰'ÝgëÖ­ñññÀÍÍ dY6IX.\¸°Œvrr”ÞàòÈ]U±··gÕªU¬Y³†K—.Y¥ÏãgÎÐ}òd‚¾ù†!ŽŽL©QÿE‹x{Å UmôÒ|÷ÓwÄ¥Ç%€K áF#1iæ$ƒm¯]»V|Í™Ëýû÷©S§Ž¸`U [a@ ÝBxÚ´i¥ÊmÙ²…û÷ïóæ›oZÔpp0Û·ogðàÁFo³lÙ²2ËÔµ‹k×®M||<………ÅCyª;EEE¤¦¦òðáCmæÎË;ï¼Ã¬Y³, Þ²ko;Çí÷ÞUr6ÊÁ+W¯rjÚ4v®\YÊ{ûd×'ù÷ÿ&73£÷•ðK¯ôWVó¼u뉉‰ZÛ={WW×âùV­Z•š7†3gÎн{wq1 „ª¿lÑZWX ÕCOš4‰wÞy[[[¶lÙBRRãÆ³¸o…BA›6m8}ú4mÛ¶µÊñ¶k׎ӧO$¾<àæÍ›|þùçx{{ëmwùòeöîÝk¶NIIaÎîÝÜ^²Dëú¦M‰0ikÖQ¼¼N:|ºøSFÏÍQO%„¯=Ì+Ý^a`¨2¸qÚ«Ó°ûMw¸uÊ\³™d2`ÍFOmÒK„¼¼<ÄÅ$"X ¨N,Õo¿ý–äääây{{{Ö®]ËÑ£Gy饗X¿~}ñºüãE–.š5kFLL ­Zµ²(³³oooNžwîÙÙÙܺu«Ôò   ºtéR)¾G{{{†ª÷x7nÜȈ#xã7ÌÞOô¦Mœ7PТ¿$'óž,—Ê”_§N>[ò™QBøðÚüüR±p°sÀƒÇ˜JªÉç•€¯¯¯¸!„ªÂ\ùqröI°ItíÚµÔXàÐÐP^xánܸQÆËjNùÍ}8p€^½zYåØýüü8wîFoãëëKíÚµK- áÔ©Se¤¥çû(qpp ++K¯ÎÈȰH»~ŒH@u¯ys­Â²¤G8d~ˆV!¬Àƒû ~dö»råŠÞŒáÁAEàÊÀÀT6lXfY³fͰ±±ÁÞÞ^ëzsñððÀÁÁ¤¤¤2BÔ5jÄÙ³gMÁ5kÖÔZîïÆ4mÚ´”ç²2aooOvvv¹ `€\c<=uŽOöòòâÓÅŸ2fÖ˜2B8nm/uy´8;;‡JûÝ úPúùe{L…9Íú¸šóÕ 'gÏâÉ}´-×ÜV Œ¡C‡œ={–ÜÜ\«öÛ¹sg:dµþZµjÅ… ,î§iÓ¦\½zõ‘ÙW–e~þùç2Ë:d–ÍÈÈÈ(W ÐÌÍ 23 ¶«{îœÞñßuëÖåÓÅŸKnfn±~±ó‹<Õÿ©Gz­Ÿ9sÆjcÕ!‚Z‘$i…$I_I’ô¦$Ií%I^ú BEògg¥”vÕÕé>wõ¤¶‰1öѵ­@ K=Ø·oŸUû´µµ¥uëÖœ;wÎ*ý5mÚ”+W®Xܯ¯¯ÕÊ#€gÍš¥µÏï¿ÿNNNŽY/4ÇF'%%‘••e5 ðúàÁÔùïõ7ÊÉ¡ejªÁÚºuëÖeýâõÄFƲÕ~^ìü"Oxú‘_ç<ÐZX ¨ ¡UqX< tÆ>’$ª¦C²,ß3ógE5 ÌÁÜ1ÁÆ¿BLÌN“¶ÉÍÍ¡»F¾ ¨ˆ} ‚ê““-Z´àäÉ“´k×ÎjýúúúòË/¿àëëk•$YÍ›7çÊ•+4kÖÌì> öööäææ–k†`µ2dÁÁÁVë·_¿~ìÚµ«Ô²Úµk3zôh«çÀ@žß±ƒOΟ§¨ukm'HÓ¥KÙ0y²Qýy{{³~ñz.]¹Ä“ÝŸ|ä׸9µ32292Š‚‚¿åEXX{&L&n‚ @i-$<Áåk‘åY–7ȲCâëÅÓš÷ß+5ÿýw›9[<âøa¾ÿ~³Q¢M$zÒÒlÈ>êõ"Z XB“&MÈÈÈàÁƒVí·{÷îV ‹nÙ²¥UB¢ýýý9{öl¥ÀšÂÙ¸};^ ê뢰Åñã´ç¿C†Ð ~}£ûóöö~,”õ…ýýýMÚ&99™ƒ›³uëÒâiÇŽâf!¨TZ%IRà à¼,Ëç ´õz—eY>SIDq°K5!)³´Bé).é-ŽÇlo±¬1 ´QÒlè³$ƽPjþÕW_.5ß»wÏRó>>Mðñib”ø«îBXóÜ5Ç#„uõ%0öÞÆ}C¶BŸ†îIâ>%¨XtëÖ˜˜úõë‡UúôôôÄÆÆÆjI²7nÌõë×iܸ±Ù}xyy/°lllx?<œ1çÏóåW_ñçÝ»¸ÙÚÒ'0Ë—[Å»ÿ((**"77×`ض@P9ŸW*¹‚€ï…@¤¶MUmWá@ ?t—ey–$dà¼júBµw X%ŠÇ$IzXBNäçŠgG Ù¡+¾ ¶D@ ¹( ºwïNll,={ö´Z¿ÁÁÁÄÄÄ0x°å™€ýüüˆ‰‰±Hƒ2„ØØ1¢ |ûí·eÄnjjj©òJEEEìÙ³‡!C†`kk«Wh?Êä\–к5+´…DWL© |÷î]Þ}wéé)4-µþòå ¼õÖ2[0zô³âÆ!x¼z¸ ˆà$ ¸T‘J’¤ºÀOÀ7åþ]*½Å;U“Ú[Üš¿½Åã&}ú†Ñ!ÈŸÎ;йsG»Ôÿ&P‘ê—pÕÙlL2,]öÑL†%±@ °¼¼¼¸té-Z´°JŸ¶¶¶´lÙ’sçÎáççgés ÞÞÞܾ}›ú&„â–v>|Ø(±ß¸qã2I§ÒÒÒˆˆˆà³Ï>+^–ŸŸÏ±cǸ{÷®Á$^ýõiiixxx˜|ì666ZÍ[_Ѩï[Ÿ?Sÿ4Ø.¯ ~M ×ü½rå aaaFíûèÑ£|ðA ÐUµÄ­Ôúsçþ͹sùª—;‹…T*­–eù80@|…¥l"ª¦ÏU?~3gLOÙûû.>ÿâkÞœ8…š5kÒ²ESÂúõ§sÇ@l á)ÖCEË­o¾º aÍ¿uÙGØÍÚ7¬?Š¢db„† ’ѦMöìÙC½zõpuuµJŸ-Z´`Û¶m´hÑ;;;‹ú d×®]‰`'''rrreÙ`ÝX;;;jÔ(ýÂ]¡Pàè舛›¶¶?~nÚ´‰ððpüüüôŽAçîÝ»4jÔÈäcwqq!33ww÷*yý½»ö]ëý¶ªjkfÔÖ ËÁâZâ…ŒHe#xŒÏ-IK’4(eù[åƒ/à[Y–3J,ï´¶N@?à„,Ë'4¶o Tòn @Ï1øýoàJ/î3@¾,ËÛ4Ú6Pí³ påÜý%Ö7Ô±KM%I –eùÜcûÎe9µ° ®]ÔóüuãÿÛ¼‘S§NóÙ§ÿâ¯ë7 j×–îOt'8¸ Á]:áåå%þaTT$O°@ *&!!!ìÚµ‹‰ÆòÄOpàÀ‹C­ µjÕâÞ½{ý¾7kÖŒ+W®Ð¼ys³¶wwwçúõ륲UÛØØMxx8ãÆÓéùvuu%))ɬýººº’‘‘QeE°59sæ ‚*ÏãÎý*°Y’¤Ú%„¤-° å¸×¾í—¿ªÍ³%¶UH’´8rœð;Àa`²ü!ÊpêÕÀ*qý?à=`žFÛ€3(=¬c€Å@¬$I_H’ä¤þ T@Õñõ«H_¸$I´iÓš§ŸêÏÚ¢9t`7?~ÿ_&¿='GG>]ÿ/ÛuÆÏ?ˆ‘¯áãuë9~üÕöŸD`@ ÂÎÎŽ:pøða«õééé‰B¡ 99Ùâ¾ÚµkÇÉ“'-ê£yóæ\¾|ÙìíÕ"Xµ^·nþ©=¬×ÖÖ–ììl³ö«ö óàÁ3²]âUSªÎuùùÂÀ!‚UüH@ÉÁü=  O ñ抲Žî6Y–ótô·eˆôrÀ¨ ¼ ×"Go¢»[S–åº(³M?2‘VɶÀF ÔUã»ÀH`€,Ë_¢ô(|#˲$ËòêŠðEë‹^tuu¡wïÌž=ƒÜÂÛרòí&zõìÁ©S§y}ô8¼ë5&¬ß æÌâ§ŸáÞ½{ÕæŸD[ù#@ 4©[·.¶¶¶ÜºuËj}vëÖXÜ­­- ê’5ƒõ‘ššJVV–Öí5_ªß¹s§”þøãu asquu"Xƒƒ—Wúqúþýûäæä'ÆÐ«W/vîlÂÎùÏ.P·î¿K­ïÑc);w>dç·|þùaxA…ÑB[ÿ¤ú,éj ²ã%E°J(Û?hëH’$g`*pR–åé²,çËJ¾>Ö²ÉBà0R–åt•ˆ=€v¯ñr”¡ãCeYÞ,ËržjšƒÒ“þèŽÅ"áòŸ¼óÎY¿þ3;ÑÆ¯]µð O°@ ¨ª\¸p‘9s£˜37ŠóçÏ ƒXŽ;rúôiƒBÑñêëëk•ï§}ûö;vÌ¢>8sFµÉœœ¦NªU»¹¹‘žžÀ–-[ضíï‘g666¼÷Þ{Z³DwíÚ³ž5\\\ÈÈ0Ï Y³fÍ*wnüòßìür#áãÆ—Â?ÿô¿}û=QáS¹yó¦Ñ¶ %44”2ß§§wñúÀÀ@qƒ¨¤$&&2cÆ f̘Á®]»ªÄ9=V,Ëò-”1ý4Än,ʱ¼ªlË k› l×Ñ¿ê|~Ô²î ‘W¨ü,ËrŽFÛo|Í{/J/°£$IA%' pPí¿Ò¿Ñ…‡‡a¡}™3{?ýø·o_cË–oèÙóINž<Ũ×Ç{‹gÏ™ÇO?m«2Þbá tß7¬•#K[^,QżüiÕª%‹F±ha­+qI—ŠFÏž=Ù»w¯Õúkݺ5 äç+ORRR´ŽMHHÐÛÎÎΤ¥¥™},uêÔáþýûzÛÔ­[—ÈÈH¦L™RF«ÇoÙ²…¤¤$F]Fô¿òÊ+eúìÕ« 4àîÝ»&³££#999f¿8¨j8ãB‘/$<ìéb!|òäIý²ƒ÷^Ÿ@ô¨ñ,œ:Ýh!¬æöíÛHRð°x***7„*€··7K—.eéÒ¥„††VÊç– %‚UüÔ“$©­$In(áw¿©Ö÷V}~S{mµ Å[Ë:͸¤vªÏëZ„yaÉö’$ÕB™òÎ¥wZsRß©›U ¹käÛ…¿6­ym䫬ûx-ÇÅ‘pùO¦M‹ÀÑÑ‘O´x‹;^)½ÅÂ,Sprr¢eË–œ:uÊj}vëÖC‡ÊäEqqq¥ÖGFFê cÍÌÌD–e:tèPìi-**2Ëc]«V-ùŒ›7oÔŽàà.t îBppç Ÿ‰Zd‡®¤Õ´ž²@ x|øøø°oß>žxâ âââ(,,ÔÙæôéÓF÷×½{w"|~$¾-ZÝï©S§hÛ¶­0° ?·T0¬â'”™Ÿ{dYVßáv>(Ë’e9Q˜¾\^UÕó-I„FÛûÀw@I’ú—À ”¦5Ù x¢Ì&]R0×AºýÊLÔê6Îâ‚+‹§§'ýû…9wÛ~þ;·ÿâÿûo©±Å}ôI…:æŠ4&ØÉÙSï|uÂÉÙ³x2dmË5·k£P(èÞ½;±±±:ÛlܸÑèþìììhÚ´)ýõ ,(NHd ÁÁÁ 2„E‹QXXhö8Yø{l/@\\dêÔ©:…ðòåËyë­·˜0aB©mMÅZ5˜«»þ3+•ã—/–Y7ý‹uL˜=Ã$œ””DÍš5±±±ÆTl+Èqü¨˜-ù»Î.ÀN`ÐXcD?/¨éI’æ£Lf5è¡¥íl•ðþY’¤M(½Ñ¡€º|ÉWsçÕ’$ù¿¢ô.R‰ß5²,_Q ìlI’¢ ã^ü"Ëònq©é~@ð÷#ÀßQ¯¨Ç(<ÁS—ôîªç³³RŠÿÖ­k[@P½Xºt)))ÿïK›6mÊ Ó™ G°Ÿ4bÿE€Èã_()p }–dذJÍ¿úêË¥æ{÷î©ñ@П&ÂàF `ÍfÍu†„°®¾AõÀŒ7??¿b‘µnÝ:Þ|óMÖ­[GÆ ­²/;;;Ú¶mË¡C‡èÚµ«Þ¶ÆxHÛ´iàAƒ˜6m+V¬(%&†ÎðáÃõnÿÍ7ß`cccVæê¸¸8~ùåV¬XALLÙ¼Æᆠòûï¿›,‚Í ¥vvvfÇŽeD°áááDGGWy!\»vm<êzqújAÍ[“—Gøç1}É"~ß¹‹ »-Â?Äí§ß`ý/lÒÓÓ±··7Ù›øèQnúøðÛ¾}„‰0jÁc¢:ŒøhÒk—‡@—À}œT÷Phm‚¸<Û ‚ªIAA“'O.%€Õ4iÒ„åË—3iÒ$nÞ¼i•ýååå1þ|233¹uë–Îv÷ïßgâĉìÓÏÏÑ£G3mÚ4½¨5Ù¼y3>4¹N¯š¤¤$¢¢¢$‰Úµk“””¤S߸qCû¨BaÒ1—ÄœäX®®®äåå•YîããÃôéÓ ·(QXeÀÆÆ†åkÞç«øýüóL±öññaä˜ÑÐÈ› »eÅw›ðëÓƒ§‡ ÑÛ_||<:u2ù8>þùg2Ö®å³;ÅH Dðcà`5ÐX |²Vñf`€,Ëéâò@ÅÊ-(ý"@_’+ͰgmÛŠ— Aõeúôé¼ùæ›´iÓF§Àœ1cS¦LáÁƒïÏÞÞž+V°iÓ&öïßOnn®Vi&wò÷÷çõ×_ç¿ÿý/¯¼òŠÎí¾ýö[îÞ½Ëĉ­fGGGÇ25ƒÅ××—Ë—/›ìMôòòâþýûÔ«WÏèmœuŠ`M!\ÕC£mll˜=?Jëºa¯¼lTG¥cÇŽÛ­ùì3>ß±ƒÍšPTXÈÕ¾}¸>x0=§NÅÆÎ€”ë×y®Kæh$r„X ú… ÌÅÜìÐå…ð^ šÐÐPbbbpvv¦fÍšV«W¯fìØ±Ô©SÇd TfY@@:·Ù¿?‰‰‰¥°B¡ÀÁÁÁâsjÞ¼9ßÿ½ÉÛyzzšå ®[·.7oÞ4Idffoðû~å•WذaCµÏ$­‹œœ qqq1ØöÍ‘#9Ÿ”Äÿ¼¼H5ªÔº´>}Ø×§òÿâë¯ù‡“ÓÆ<-$D°PÁTÄ1Á@ xt„††²}ûvúô郳³³Åýyzz²~ýzþùÏ"I’IØ\ºwïÎO}z™ýŒ9’Ö­[ëì«aÆܼyÓ¤rT …¬ðë¦M›ò /è\Ÿ’’´iÓØºu«YY³«yyydggãîînÒvOõêEMnÝJÚˆÅË]¶oçãÁƒ Æ”¯æÑ@ˆ`¡‚µ<Í Jb¬.’eÎ_}Èõ»iäç à ª¶¶64ñv£uÓ:î–ÞCd´¿¡*XP1ppp gÏžìܹ“˜ä¹åØYk ÛÇ…%Iµ4ñññáàÁƒEðõë×Kµ‘e™[·nÑ A«Gjj*Ó¦McéÒ¥BëáøñãtèÐÁ¬m7îÝKÚ¨QPT„ýöíäõïOæ€l\½Zˆ`Á#WÁB 0Ö|þÊC2ò$:´mƒ­­îâï'£°0Ÿ‚…q•†ÂÂB®^¿Íù«i(žU”—^zIïzwww:uêÄþýûyòÉ'…Á¬€½½½ÖÒE%Y¿~=®®®¥¾wwwÞ{ï=V­ZeääÉ6ܾ=¾Ä ƒ×ñòò2«¿Ë7o2, €u‹wvvfÓÒ¥løî;îÜ)W¼*j§.ýÜ&å³nó:ÜÜÜ,ê;;;›…³çбN}¾xëþ8s’ÉoŒ#réb«d”,°1’Î|LIŠUXTDAai|0v7_ùÞ=œhÞÝuÎ&Ϲ’|&›¥KÞ¡ÿgyê™áH&Ž13Íc+oº6gç¡„R"´äüãàQÛ *QX$ #T1.\¸ÈW_oàüùó &бcGþøã\]]­6.µºâííÍÙ³gK‰`}X-  LÚÏСCZ&ÐŽ©É°JRÛՕȉµ&hùì³¥æ—D/áÆû´‘l˜?}¾QBóÌ¡34iZjÙùúçyøð¡E"øÌéÓ¬]²œYC‡ÑØË€íhÒ”áSyöµWéÑ«W¥ÿîY½z5±±±¼ð³•Z ÉB †1%;taaañrNv_mø€ó—Oà;—úe³‰Ö pµ±û¾ÿ™ãÇòÆøéÔVÝDu10¤Ûc/00Dé}Ø{¡Ìz5šë Š·×ì¯ä¶êþ5ÛiëWßñl½Pæ!ÅÔ‡–Ênƒ*%‚ …®j´jÕ’E £¸qý¦0ˆ‰ôèу˜˜œœœ„ÇǤ—/ÈÈÈ(µìÒ¥KÔ¨Q€ï¾ûggg:vìHjjªÎÒQædˆèæÞ½{ÌûEE‘‘B^žo©õׯßd̘e´iÓˆˆ—ê7;;›äädÚ·ooTûÓ·N<ÛpÞ”s[ÎqëÖ­Çò¿'Ë2ë?üˆ¤?/±vôDlmJç‚©éæÎêQoòiÌ6ìÝÇÔÙ3±µ­¼²ËÛÛ»8±Ÿf–ûÊŠÁÕ‘ÚLò—Á¿þü—n õ¸Ø8èöî š¿êÆ­øxí»ÌŒŒ6JþôûÙ2?ÝË¿øoÍu%¨6aZr[µ˜S·3Ô¯®ãц5Dpe·AåÁŠ2÷kÞ—ôÍ#îU‚ еkWnß¾MRRR©e»wïÆÉÉ {{{¾üòK9rä 6ÔY¯W³L“À2.^¼Èþ@fæ`­ëÏŸÿꀑ®]ga\¿¤[·nUÊV‹æFâÕ˜±ÿ§¿¶ñ˜þƒ9û×U¦N˜ÄêO>ÙcÐ<ºž„ `Š'¸¨°BU¸m»ÁìÝ· …½aï™$\$ѺMûâíõñÃîS¥Úéú[ÛºvŸâé^þü°ûCú–êKý©^gl¿úŽGæ¾,¥2Ú ²S$BÉ‚2¨kïÚµ‹`cc#Œb€Þ½{—Y–ÀСCñðð G,\¸èèh½/fΜ)ŒY ^xxxxT¹DµêÔ¡‘™Äש‹»‡ù¡ä,ZôÇ_aÍš‰4iÒX\XV@!L ègÐÀþü²=¦Xëû,,,¢  ‚‚B¼¼àììNú_¹Fí'íBm;o¯kR޵ϻNý·9Ûš²Î˜åæL•Õ•}*I±­888ðä“O²gÏa +аaCæÌ™CDDYYY •˜ãÇ]™xnø0¶Š5ªíχðôóÏ™µŸ­[wÓ¹óÛ,^Ì?Fòä“_1a²³³ÅÅe!Â, ¤'XßçO\) йSNœÜ»ƒÞ}d'P˜]D½†MŒ Ö7ÎÖØuÚ‚­Ñ¯¶ùáƒ;³iÛ«…BWFT ÅO†@  www‰%$$DÄB5jĬY³ˆˆˆ ::Zkù$AyÜçOêg–@ d6è#€²¦ovv’Q¸]»v(LLúùðîC.î½h°Ý­?oAðã±Sݺu¹—aTÛ£%0¼ót“úOH¸Ê¸qïLzúZ@UxãÆlÖ­»ÌÞ½3™4©cÇ­ÁýˆÑtæbî˜`€ÀöÁìÞ¹•†ƒ]QèùoK=ŸKë€ Š ‹(°·møàÎ|½õ ÙBíë­‹ûжþ•gºYM¾òL·2û±•ÉUçáH„C úðòòâáÇ¢†°•hܸ13gÎ,ÂŽŽŽÂ(åHpp0±±Ê— ‰‰‰¼ñÆ–R%’úôùŒåËß F ý¡è999$%%™å^±˜››Ë‰'Ö®t¥A~~~Í^­Úrþú5Z7öÑÙ&=+ §šžZ³bëã­·>`×®%@Ù—?EE¾œ=»šE‹ÞáÅZ”µ»új!Yˆ`À&e‡Ö(‘äâæAÝúõI¹˜NM?Ý?ÞéŠéÕÑèò=¾‹-i¾‹-Þ®ärÍuP¶G>bq¿#Ÿ )%(ÕÛZƒÊbƒ*%‚‹D8´@`ðÁ¸U+âããE a=,\¸áÇӢE ½í–-[ÆÓO?ÍŒ3øä“Oxûí·K­¥©¬‹]ñË›ëׯ×K­wu­iôËýû÷Ó½{w³ŽC-œ·mÛFDD„U¢ÒÒÒ¬n¯ç†ã“y‹˜£G› mkë U—nSSdH·!‚Å[DŽhý˜æ .*ª¦]»âNþ¨Sd‘v;‹ÆM[–ÙV…|¶yoñß%Q/×\÷Ùæ½eÚêÚVs̬)ý–œ/¹®}šKe±A•ÁÅc‚Ëã¾aL~h r jëgúô鄇‡óöÛoÓ²eK¸]»vÅž>M 0qâDaÌr½ç'WJÌátçÎÜÝÝqqq1{ßGÅßßß*øâÅ‹&{bÁÑÑ‘K‰·ô¶9|õç‹‹©Bè!‚“0Ölk#‘ŸŸr%µlÓŽí?þ—ó”cGŠoι¨ß–H …Ñ!´–†ÚŽ{©/ë6î.Õϸ—ú–j£¹¾¢!lðh±µQ—ŸBÔ ŒA]CØÅÅOOOaØÛÛÍ”)S˜4iR!¬À ÆzLÔ«WwÞiHNήâeݺçÑŒgРAfïûîÝ»deeáããcñyܺu‹{÷îáèäÈC–Z—Q˜av¿)))ÄÆÆÒgÐ Â?ÿ;{­í¼|—‹Xá¹F˜@ бžàFuÝILÎÄÕÙEI¡àáÄ’U딂W¶R¾R¬‡%£ËjlÞvÀâsÒÖ‡5ú}T>|HçÎ øé§L›ö§Nõ'3s¨jíu|}£;6ˆˆÏËõ8ªº"X 0€)Ù¡aÏž½ääÑ?,LWPe°±±³p{{aDÁcÇÎÎŽC·nݨY³¦É}Ô¯_¿øï½{÷rãÆ Þxã ž~úiÖ®]˪U«¬r]UìþŸÿüG¢’ñÑÏ?ŸžŽ,Ëܽ{³£(RSS¹uëaVx6Ú³g=zô°ê„7npñâEú÷ï_®6MHH !!bkkË]øòËïYºt,©©Þ„†±fÍ<‘gÀ ˆ×Õ‘Ú ÖÀùùù̘3›á#_äµ7_cÂÛ“ÈÊÊTÎû†ÆýC!ÙP$›WBI.Rn¯­_q¦ØØX­Z;÷êÕ«$$$зoßrµç¡C‡HII¡_¿~ØÚþí§1b('O~À‘#oðï/Ø’ç!‚ãùe{ŒÅ}\¹r•î½zòÝMtû¸>]?ªÇï×¥C×.?~⑜‡“óã»i>Î}WÄ㨊H Ì-WX$!)l„„ŒŒ 6nܨ· ýû÷çÝwßåÎ;&ïcß¾}ìÚµ‹ ”ZÞ¨Q#fΜIDDÙÙâ%¨ òü8Wƒ‚È8e7Ò¶m[³Cs÷ïßOpp0vv–E?~œ àååeµó¼xñ"·nݲŠ@×E^^¿þú+õë××Y‡ÙÞÞÞärk¼½r%c–-+ž&¯Z%^©áÐ,õoܸ‰ÉS#ðîFÐsµQçE ˜V“Û¤3à™Á¼=á-¦OZ®I„ð”'vvޏº˜– CEÝÁÎ;;Gòó„(”?®®®äåå±aÃFŽ©³Ý¼yóxþùç9{ö,4jÔȨþcccÙ¹sg¬¦qãÆÌœ9“ððpV¯^£££øRžŒE²»;vªë5))‰ÔU«ÀÅ…£îî¼¶n]qÛÔ?ÿäßK– ªñ¬k×®áääDݺu-:¾ .дiS«ó¹sçHKK³hœ³!>|Hll,½zõÂÍÍÍjýfddΡ… ÁÛ»x¹tó&ñááÄDGWûaÂ\mq…æb®'8-=—G¼Ê´Óè°¤MŸ÷D³T\ýnt]ë͆íëy²O/®^½f´¨tröÔ*0ÕËu‰OÍå%çKn§­¶~õ“³'ÙY)å&¬+ƒ ªÍ‰Â?ÿvH&üªH’„\TD«6mQO°à¢¿6lк~îܹôïߟBCCIHHàêÕ«Fõ——§S«iÒ¤ 3fÌàöíÛâËT ÖÌ›GQn.±#FðûÒ¥œùì3P%K\´ˆß—.å÷ÈH®_}Õ(œÍÙ³géÔ©“EÇvëÖ-îÞ½Kûöí­v¾§N";;›®]»–›M/_¾ÌÑ£G8pà#ÀrÆìŸ;—þááÕÐ#\Z ,À\OðêÕ{îwº®õÆ£…î7ý޵m z·6™Íï0ìåÙY)dg¥”pêåšëÌíÓ˜~um[¸²Ù ºõ__:t芇‡;6£’wZ´lC«Öåz­¦á’XM¯^½¸uë—.]2ØoŸ>}ŒÚ¿Íš5_„ RàÓ¤ |ðo|ÿ=^_|Qf½ý™3t‹ˆà·7ßdÔsÏÕço¿ýfôÿ‹.Ôµ€{ôèaµs=vì²,ë M¶ --°°°Rã-¥¨¨ˆ~:°¦Q­¯i]-ÑÌ>Sr™@s³Cú ñ6N†ß5I K„…—ÑÁ i%?uµ1¥ÏG/º„ *Æ=DI~^Ù’D]ïFxÕmHaA²,I6¶öH’DVÆòó²pptײ ,Xƒ8yò4  o¬ŽBxÆ ÅBX›V¡C‡ÈÏÏÇÏ/—@PåDƒ­-ΚEÒÔ©lÎÈ€™ }?úˆ}~hô°®øøxüýý-ªs›››ËÁƒ­ZV+..WWWüýýËņyyyìÞ½›€€£‡X˜Bzz:wš7×)€K á+5jÝoFF‡àÊ•+tìTI®ZÝ:Gx‚ Ø¿8$ÚÐgIÚøS«Fm’On’—Ç3O=- .¨´äåf’òà:©É7ÈH»Kfú=SFÚ]R“oòà:y¹™Âx‚Ç*„Ö¯_¯S«éÚµ+ÙÙÙœ:uJNPm¹"ËJœœŒí±cÜ騑óªq¹†¸wï™™™øøø˜} ………ìÙ³‡ÐÐP“ò©ìÙ½›¨Y³ÉÍÍ-³nÿþýÔ¨Q£Üðƒˆ‰‰!$$¤\°À4„'X 0@IO°¡OMF¼ø*_íYG­@ýÉ2näQ˜!Ó©SGap@ðȨU«}úôàË/¿®¶vÔ©£»ab"ªa!‚¥ÞŠO±~,­üÚ+¯±ró"êéxžÊK-äþ…túôîe²´ÆrcÛÛ¯®$Så%„+‹ ªó}ãòå+|ö¯¯xååðÓÛ/âž$x ¸¤G)22’¨¨(£„pË–-±µµeïÞ½ôìÙSUÕ­2i IDATPåY1}ºNñõet4±±±½D‹°û:ºø¨YÇЪU+£ÚM;œŽuê³bä8mþÑ5„Î-Z3uÌ8Þž3ÿ¶m>޼¼< ´ÖÛMJJâàÁƒôéÓ—Göýœ={–9Ï<ÃÂ3ˆ_ºT»NL¤ëܹ숎®VzGˆ`ÀD,ñ<7tááS¹1è! ÌŠ«üCõYXDÏ>!8;;=²sÒ¬þ[Õ…œ°Á£áîÝ{<ÿ#¹yóŸýëßüy6Ž:uj ûž3gN¬&**ŠÈÈH£„p³fͰµµe÷îÝ"ÜQ (g<=ªT˜‘Éÿ=ÛÛ`»zµjórHo.'-‚ÿ÷¿_ˆŠúžü|{Þx£##‹C¼/\¸ÀÍ›7%??Ÿèè™1#\ÁccñâÅFµ›?¾Ñ}Ö¯_¿X‡……UÊ °AUâ©çžåç_vób¯P½íŠd™{[ƒ™¦§Næ¿ÿÍãöíÕÀßÑ{ÙÙ½8z´Ï<ó)}û^â»ïV>’óKMMeÿþýøûûT¶n¯‡‡Ÿ«r„LÆœp…¤¤$‚ƒ»è¾é!IÒßáÑAâû­ÛHMK§E _Æ¿9 ÿ6ÍiáëËÇHòÃT¶nÝÎСƒ…¡U ///:wîLLL ýúõ/@Õòàú¿®“zØp¦h7…[¹K§ÎÙ´îS^4Ðnßé„„öÜÆÇßãöí%:Ö*HKË… 3‰;FZZaaa"ó¼Áýˆ43æbŽ'ø}±>|¤X§¦¥aog_*ôó/¾ä©Áƒðö®+Œ,¨Rdegqèà†‰Ö-“ŠŸ_sþoØ‹lþæ¿ì?Ç Aa88Ø ƒ ªµjÕ¢{÷îüúë¯ôïß_<  ª-[¶äÇ/~¬0ÇãR»&ÙÙ¸:éλ²ãôq"ߨI¢RRR8pàmÛ¶uÊ-ÔBB ,0€¥¡ÐüK£†  j(½À—ÏœâDÝZ xêÂÈ‚*…‹³ ¯¾:$;µ#7' €Üœ z<Ù{{{ …*ôLÜœUOOOzöìɯ¿þJ¿~ý †Y ‚òá©çžå§í¿é ‰.’eòìþT–eâããÉÌÌQ&VÒB‚âŠ@$Ñ¥c‚ 8¹ÿ7®yÔ ]»@’““ùð½U¼ÙÅ‹ßü¿î%óú¨‘ÂЂ*sße™ÎÛ©„o鸜ì‚»!ËEäåfèø…÷$Á£'55•‚‚«=\º¹¹Ñ§O¢££yçwÄxà1йK>]M7w­ë/ߺÉ}úTèsHNNæàÁƒ´Þp•WÀB‹ë@h`ÃX"€“““™;s&ÿÏÞyÇGU¬ø™Mo„$„z ½(½¼** þôRT+ŠRéM¬€¢^½½^)Aš 4Á’Ð;¡†ô¶ÙùýqΆÍf7Ù$›°IæÉg?›=3gÎ윲óyç}_èQsW˜?KÓ¦KxúéNN©óõë×Ù»w/íÛ·§Fê‚*”mŽBQÅ]\½f "‡}”¢ÆÁ«¯jBgî\xæPÑþW¯ÂcÁúõÚõ0lXùûŽžžž¼õÖDžþ&,">>•åË'ФI£|Úå*ƒ^~™¤[ƒÙÙô«R…¥/¼+ßþýû‰ˆˆ Z5çEé9°'Õ=ðò·›'=%+\áàžƒJ+ ¨ýs„pAïö «Í†ï7‘˜”D³ð¦¹Ò¾øàŽÿÎÉãÇiШÑmí0—EÑ«(ŸxyyѬY+<=½ÉHW"¸<ßÏ®J£F aýúõôìÙ“ÀÀÀ÷9{ö,uêÔ)0_BB&“‰   2Ù6#Fh"8#ž}¶n…U« 4T]7•~€GK—´Ï‘‘P»vùý¾UªTáË/g˜ïòåËô›6ÃóçƒÕ3äÂæÍdϞͲ_dïÞ½ 8Ðé>¾úì+Ư_mÚhBHQ±ÈÊ‚—_†~ýn `€áÃUÛÄÅÅÑ÷ÕWm `€äþýù¸cGî7ŽÆÓ¥K—q¢ÌškØûæ^Ro¦æJËLÍd×´]¼7ë½ gz­D°BQÅ5…NIIåƒwÞá.ÿD~Û¿¿üONççé§ÇS)üfýï S^{ƒ¦M›¸ŒÈ,‹~5;ì$&&ñÒ+oòÒ+oòÎÊr¥-Yú¯N›Å‹/¿ArrŠÝ22228uê¥o_vôëÇ/üQ¢u fÕüUüüæÏ¤%¤åà¯îäƒÙTȵÇÊZ¡(€â® –RâæîÁ…¸$n$gЪJH®ô<úG/^§N½zN‚–ŸKr¿Û%ÐÍõSÂ×5 ¬Ä„ X±b¯¿1‡Zµj2tHë¾ú–™³çðÌ3ÏàïクGʺäøý÷Ãlݺ иýñç_Äü«':wîÌÉ“'Ù´i½{÷ÆÃÃÃf¾Ñ£G³fÍþýïÓ¿Ö­[Ç™3gxçw˜6m³fÍ"$$¤Ì·Ç°aðÆ¹·™L0glߟ ¨ë¦¼ò¯ÁرhÃX§}{ÍZE'ËÛ Ì—Ú¤ ‰gÏ–x}BBBX=5N~”ˆ©˜w€÷g½oWŸ8q"g@/%%…ƒµõÂÇŽã©§Æ)¬( ¨@ÀÅ¡¸3Áþþ~<öä|üñZZv®AŸÞyƒ²?òðÈB ÝÂ|.î~®FY­w…zâHÉœ·Þ >>žÍ›71íõY\¸x‰•ï®"$$˜¨¨(fÏzôÔø|ËIOOF*§XN§M›Ö´iÓÚ†xû?Õ8РA‚ƒƒùþûïéÞ½;•+Û¶š3f +V¬`ذa´lÙ’ž={Ò³gO¢££Ë…hÝ7†cÇò¦íÛíÚiáq|P]7å‰ädÍÚêÕù(\“¦ŸFÏN=Ùÿó~jÖ¬i7oÆ yÁÂq—™éÓ§—a=t e­PÀ÷7;¥œ–-[Ø;Y·nÕÐÅDÍ»i ¬|g ÷BÃõ‰ŽÞN£†õ>ì^–/[LzçJ™C+J›´´4~þùçóU®\™¨¨(~þùgíµAbb"‡bðàÁüý÷ßlÚ´‰?üÙ³g“YŽBågòš˜#G£jÂIQöùõWèÐ! yMå®Á7øöÛo™6ÖOà¥y/‘PaÛCÍWH¤Õÿj¦8?Š:|ðÀ/¼:mz¡÷›9cºjô¢t`•.ågHAÏ ‰13‘¹sçðÖ[oqíÚUªV­Æ‹/¾ˆ130å{//Oš5k‰§§é6ž] …sñññaëÖ­¤¥¥Ñ«W¯|ózxx0pà@öíÛÇ•+WˆŒŒÌIKJJbòäÉÌš5‹ªU«rñâEºwïN§NfòäÉ,X°À®9õí"ðÜ )‚çÌÉ?Ïš5ðÓOšùlûöê:+“O| ‹kë½32_ KYѲ¥æ8M¦ôtm@ή 7oâ^‚1Æ’““s➯ùﺼޅÀ¾ȨgGñÉâOò~_¶5OÞþƒš V]VEe&¸C‡öÜÿ}T ,ôK¡(?'B¦2éùÉôë?gŸ›¦d¤49¶¿)Sy‡V”*Ó¦McÏž=lÛ¶Í¡ü‘‘‘T­Z•7’••ERR“&MbÆŒT­Z€=zä˜M7iÒ„ &0iÒ$²\Ì{”.ê·qgqô(té j‚JQv¸z ‚I“tÜXi[›FoEý©S!;Ûn·£GéýÅ îÓÇéÇÏÌÌäÇäàÁƒ´nÝšŸ­ í”¶ÖÐúš~!~ÜñòŒzvT…˜¶ÖBj&X7Fc¶j;e&ØÏ×—®]ï$2ò»yL&BD Žþ)%ј···CyM&#Þž‚!÷ #- “I=kJå¹®;ºSËŠ&„g̘PàŒ0@ýúõsÖ ß¼y“3fP­Z5»ù›6mÊ„ صk½{÷v™ïí ø}€ùÀ G„³ÐÖ.\XpÞŒ xþy-¦ðš5 (\˜èh5 ââôQ’ÇW÷ìï£Dð-"ÛµãK!¸ÊNÍ›n¹í,Übcé³lß.Z„§§ó–þdggó믿Odd$Œ~f4­'µÎÀfü«øsÇËw0ú¹Ñ|¼èãr<#œ%‚+ ÒjD¤J•*¤¤¦â‘pƒA 2gP­jÕªœtU5†¢\a2IRRS ­â°eIvv©)ñEzN´Ma%|KW2hÐ ~øáRSS ÌNxx¸Ë}ï(à{à àaà À§€}FŒpL›Ù¸Q‹)üÉ'з¯ºÖ\‘¬,xõU˜?_},Ìc6lïפ ´j¥ÚÏ’ŽmÛòo!xàé§IhÛÖò‡‘ŽÿÍ7 :MK)ùóÏ?9wî:t cÇŽ >ˆì*ÙÄ~e?€ÑßÈàƒù1úÇr«y”VäÁ×׃A””ˆ»»º$ …}ŒF#~~¾xyy©ÆP”Y®_¿N||<†|Öê=ôÐCÌž=)¥C3¶îîîôïߟpùòe:wî\æÚ¥/à dŸûÑ,_›å³Od$Ôª aGh3óf‹-®Ðœ8¡yô>p@ßÐX˜gîχmï«fmѦ gÏæÆ¹¶×}òIÜÜÜœtÞNCË–-8p`®´/?þÒ¡åM(ÅS1™ŒT äÚ•8BB‚13ðöövؼQ¡P(œ›»W¯^" “ɨDQ¢œ9s†Õ«WS·n]*Uªd7ßùóç9zôh¡Ì–;vìÈ™3gظq£K™;;BЈÖ?E›~fkÁ÷Þ Ë—¶/óæÁŽšÓ¬† Õuy»ùì3?^ýëLƑۃÐ÷ö÷W¡‘ìS¹re»!ÕŠÃ¥K—8tèõë×gÐ Û‹üýýÕ P"X••FíÚõ9~ì/¤”T ¬„›‡Õ( …óH Ä߸IÂÍë4hNVfšjE‰Ò¾}{ÚµkÇþýûÉÌ̤K—.yfd¾üòK†Êرc ]~ݺu bÓ¦M¤¥¥•©uvQ" xØ,‚lì3|xáE°™4¯Ñ+VÀëkóvœ &h&êÔVíld¶c ]¯DD¨¶,-nܸÁÁƒ ¥ÿþùZµ(”®ØXó2ðòö¤Q£fÄÅçÂù³dg›a@–ïAû€íF“á‚ÜŽ´ÿeN^!nZHóÿ)õBó©}–úgiá=Òüÿ­w¸µÔ»Ðæò¤~xsyÚg-AêÚbhy«.z¡o“ú±,?ç”™«Nú')o¥å”«(ÿÒ $B¿OrßÚG@hsÞ2çÞz~=oÎ>hy„vÏaÞYh÷0ïcÞOè÷™@ õrEÞr…¹Öܪ³4Ðë­ß“Úý«ÕÅ<õ#„Ðï9CŽ{W‰ ‹[ L·®“4É„i2é÷ˆÀdÊFš$&$Ò$5Ò~¾¾Ôo؃A‘™¡.0EÉßÃBÉ7Ø´imÛ¶¥V­Züç?ÿáÆŒ7®ÈåWªT‰¨¨(-ZD5ÊL»ôG›´6žÜ‚¶$tp§UZ×®š£«+WŠvÌÄDxäÍÓòå ®ÏÒâàA-žó±cú†{9hfÖÄ¿Ú.GÍ—)))ìÛ·___zõ꥖1Q !•®°d¤'ãááMí°úáfuuäœf¡—ç*ÊyËÇ…ÌÓë°zçÞÜ1Ï•ž[ Üêð“+Ý,¶s¶ ³ŒÈ}¼œmÂò“°°íyŽ/©s.ÏÎÖe(Ê÷ÃôÖ@JÞûäÖõ/-Ý2X åŒæ`±MZÝQ–/9-†or}Æ*öH~÷±­AiUßÜõï¹FlH «{+÷ V÷³Ðe˜•AFzŠº¶¥Jpp0QQQþøcöîÝË+¯¼R¡Ûz `–{Ð,fëèÛ‡/¾mFòÎ;5‡YÏ?¯Æ•K‚Ë—aôhؼYßÐx¨_ÀŽvL¡kÕÒ¤)ìsîÜ9&½>‰Àü—F¤§¥Ó»Koù(p+ÜQBB;v¬P!Œœ¯}”Vgõ‹¢(™™œ>}šððV¤§'Ùü_š”I«B¡pž^µjóçÏ/±c¤¥¥a4Y¹reάð]wÝU!Í /ùEõþè̆={BPÄÇÿø™™0eŠSøã!Ÿ°ËŠB²y3Œcû÷ŸúÉ.ÈC÷U4wá6¸÷^ÍAšÂ>aaa„Õ훵;Ô¶Ý·JÉ`÷k»ØKóîüÇpñâEÚµkGhh¨jD'+auÉ*ŠBãåéIxxK¼¼¼ìþ¯P(ÎÀÎ 'b üq"""hÛ¶-›7oæìÙ³®Í«ŽLì%¢9ždyÀ!ÎlmÚXÌX*ŠLV–6°0p .€CÏ€×À›ìЍÐHޱðÍ…dïÊæü/çí à÷g¿OJJ 7n$00~ýú)\B¨™à ‰ÄË;€ììlââ.šš„1Ë„p3`0¯±5p  º£ƒÀ€ÐcpÓ¼Z !ú À€aÐÜêH)õ4¡;°ÒŽo¹~QÊÜë¥åºD³C*aá< ‘ã¼JJ©¯UÔaéž¶Ìåç:X9°ºåL+Ç–îLKJ™ãÈ+_ÇXÒìHK9ƪhèK §V·œ\Iav™evHe.ïÖšvÍy–ÔÏ¡gåë®Ùâ–+Ýá•°px¥ûªËµF>g=nÎòzƒVJ®J+p÷ðÆd’œ8K@@uë4ÄÛÇ?WG^ÚsÜ“ë:²çÇÖEgåV'_‡Vs‹ü€•C+‘K<äÚ#?[6ŽmË–õ±sË‘c+Ê÷ó´ÇXÒÞ½dy9âäJZìnËCy~­ sì¼T–ÇæÁ"¯Ès/[åÉç“H2ÒR¹qã*'OÆÒ A8î^³”9}y$++ £Ñ±8зS[Ò¾}{زe -Z´ ^½zâ\EÓÐÖ; `^wð~Ò—ƒ3G„¥„ùóµ˜ÂŸ«{ÉQÖ®ÕÂ%%¡Íø¾Œ¥p+ãâ½¶“† Ñ£) '„ŸíyÎʳTk^¯îdÌ 1˜L&ú÷ïoåDRQbzH5AÅÃÃÇӧŽ@­Úõ1f¦“žž¨ÏÈ( Eé!„7wOjÔ¬‹¸t– çOS¯^c%‚+8ÇŽã¾ûîcÔ¨Q®6 Lœ8±Dò2hÐ :ÄÖ­[éÖ­[¡wíÚ5ªT©RfÎA5 =p°0ƒÄ›h¶ÔëέÓÁƒÐ¡ƒFiÔ(uŸäGb¢&~?ýTßPÍùU›"¶…¼1³t”W袱àÍ<ýâÓüoéÿxu«ôíÛW…;R"XQÒÜÜILJ¤Y³Vde¤b4ªÎ¦BQÁ•h®IëÐJ%Y–))M„V­ÉßýŽÁMý4UtêÖ­KïÞ½¹ë®»hÕªU¾ySSSÙ¾};ÁÁÁ´oß¾DgÛ¶mKRRÑÑÑ„‡‡Ó°aC‡ö3LŸ>‰'Ò¸ Mc.¤ðö†´^ÀÀ3À.çÖ))Iól +V@¥Jê~±æÀxðA8qBßpZø#¿"hÇ+tp0ôè¡Ú»($''sñÚEª4¬‚o€¯SðéS§©W¿^ù®]»†·—7þþ[©Ë°÷{ ndggª†P(*¬ö5®]KæÔ™küñ÷9Ž¼Ì•kIdg›ÓK§¬lc77”÷z€§§'K—.å¿ÿý/çÏŸ'((Èî«V­Zôïߟ:uê°yófŽ9bÛÞ¹0hÐ RRRˆŽŽ&#£àdwww.\ÈâÅ‹9zôh™9QE¸#½¼ôUÏWqÌñR!ùôShß^| “ æÍÓBL8 Å±ZR œh cȨ@Ëä*€G?7šö/´§×ô^,ß°œ»w»ÜLJ?Ϋ_Í7O\\}Û÷eëÆ­þ<¨áv…B¡¨€ 9qê"ÇŽçæÍ›¤¦¦âííMåÊ•iРÖ"ÀÏSK%œYVEÁÇ·2i©7m~.-bc²öÓψ‰‰qjÙëÖ­£mÛ¶4jÔ(ß|111ÄÄÄä‰ÿëææÆüùó™"<–+V0hÐ ›éf!¼jÕ*Ž9âÐqëÕ«Ç AƒÈÊÊbÆ \¼x±ÄÚÏßߟ¨¨(ÒÓÓÙ¼y3ééé á… ²|ùrbccËÄu:¨ûx{[mh lJ ”NVL è¡* 7j¡¤¢£õžýLì· IDATà'`€ïmo®T úöUÏñ à1“ÆäÀfîzá.–¯_ÎŽwëu’êpiÍ%^{îµ<øÑ¾ÒîH;<)¼ÿ„êÕ«3gÎæÌ™CŸ>}ÊG_H]’ [\;šëåJÂïjÜQu‚Šâ<øÝ<9wá{öì¡FõjLÿ$ýúõæÔ©“ôíÓ“qcŸ m›ÖìÝ»—3g/ƒpw¸¬Á°{ïá®;;sÏÝQŒý0Í›:TVEÇ,†Ë æ™O{BøèÑ£¬X±‚… æz¨(B Y³f 8¸¸86mÚÄ7Jì»¶jÕŠ»îº‹íÛ·(n===Y°`Ë—/wúì{IUDœÇÁm° X ”ÀRÄèhMnÜXqžÚ ø Apå š ú¿€WpŽ z*°ÍvÒàÁÚŒ¿Â1RRRöè0êü£É\?}=Ï«ÙýÍxã½7Šm]'±V]ÈÂæࢠàòŠê( Mhõ&9ÂØ,H-E²¥HµÌkKÀ:ºŸ½ãæWFAiŽˆm[Çrô;ç÷Ùz?[ƒ Jì+Jap#=ÃÄ_ý¯¯C† &9%…‡F>Â_ÿÍ×_ÃÚOVÓµk®\½ÊñãÇ©V?‘g¥uY]»v¡ËÝX²x!ýû÷Ë)·k·®Ô¯ß ß²*–³¾–·¼ áçž{Žçž{.—^¾|9óçÏw(ö®Yç7«lsÆ` }ûöFöïßOZZ‘‘‘øû;_…ùùù1pà@¢££Y¹r%Þy¦CssùòeæÎËêÕ«]úÖZæ9cimÍ üîܺ^¹¢ Â瞃ٳ˷H;zTs~õë¯ú†ÞÀb ĉù°cÜ0|8ŠB™™É >ƒ´Xb'ìçÚu(îÅ—guëpvÕY&¥L"v_,m·UX‰àŠŠs;œÖbÐQèŒýìÕ£°å;ëû¦l{û•T=м¢À,£‰Ë—/ÓªeK“’xø¡Qøúù±wïž|r?2†×|DDD{vïþ™ŒL#þ~žÈlc¾e²há|ÆOxšùóßfõª5øúù1~Ü?¹zíz¾eUT!œßçò(„›4iB¥J•øâ‹/X¸pa¡Â ¹¹¹Ñ´iÓ¢uvÜÝéÒ¥ ©©©ìß¿ƒÁ@dd$^% ’úöíKßìDO:ÅܹsY²dI™8‡Q…Á ÍÛµ¯| ÌVâô˜Â j1…ÿõ/hRW0­YO? ÉÉ€'šó±Çq¾_Á ö|4ós…ã1ñŸKõ˜!‰!ìØºƒi5”Σ…¤2‡VØk–/kQV‘VTAçl!èŠÂR‰]Ei#„ÌÌ,²²²¨Z5”¯¿þ/Çİté"üýY¾l1¿þú›6o!°R%<<>4nܘӧÏÒ°A=Þ{wOþs<]»õ""¢+ßY†»»;'Oœ¢^½zøûbÊN(°¬š5«ñè˜ÿÃÇ×—5k>b÷î=<ûÜó,Z8Ÿ  |Ë*ïØ2}¶µþ·¼‹c///ZµjE«V­¸|ù2;vìÀÓÓ“¶mÛR©„ÊçŸÎÈ‘#m¦…††ÅÙ³gY¸p!ÇŽcåÊ•%R'N°`Á/^\¦°™ÁÀ¢Bîãí퀨| ¼¼…Ý}ûô!!!‰sçcHJJ¦oß¾‘~Ýá²Ú·ïH||¾¾>¤§gP)0að gÏž–UÑP&Òú÷öñ¡sçΘL&bccÙ¼y3U«V¥U«V¥b*½oß>¾ùæfÏž‚¦M›òûï¿óÇA•*UŠ} wwwæÏŸ_¦ÏS 4V§ ±ÁžžY˜™ÝVh1…§_8YÛei‚rëVX»jÔpÝöÞ°A3å¾zmÊê)`r)õÜw‰¶“”WèÛCZZ¿ýö‰‰‰4iÒ„¨¨¼ÃRÈÃÿ÷p¾Þï‡Ü?www†7¬ðZH‰`…M¡VØô¢ìS˜íÖ"ÓÑc;KHö;6M ^Eib2e“žvš5«1fÌþüóOŽ;FVVÞÞÞ4mÚ”&MšàíåNjÊÕ|Ã9³¬ ש±0‰¶|¯¨ š5kF³f͸rå ;wîÄÝݶmÛX*Ø\víÚa49pà¿ýö;w.‘°Je(àBrRø ÑÖ O’œû=~øA‹)¼zµë™öfdÀ /ÀÒ¥š§kª£ÅW¾³+±Þöf¸ç¥ÈåË—9|øpγ0((Ènޱώu¨ÌAÕ=»ÁŠÒ±5T}^…Âå0fe’œ‡Ÿ"ï "")%!0™ɤ$]FJSé•% x.•£g‰yØzmpE7‘6SµjUz÷îãðÅ<ûQ¯^=§çŸÿü']»v套^²}¬^½š%K–Z¡ÏËà"ˆ`ooH(ª€!@;`<ð«s¿ËÕ«p÷ÝðÌ30w®kÄŽ‰Ñœ_ýö›¾¡Ÿ>\š?h3ñ6èÝ*WFQñññ,yk Óßžžo¾ôôtf¼<ƒY gåÚn¶Š9yò$ÕªU£GÅSWЯ¸]"XáYRÊé0õ&J½%!„šSý$´Õ#þ@º”2ÝÁc˜Wá\®\R3œ …«b2IJ¼ŒÁà†‡‡Â`ÀdÊ&+3Í!ñ[ReUlÍÛs–U‘ñöö¦S§N˜L&Ž=ÊæÍ› ¥U«V6;…»ví¢[·n–{òäI¼¼¼X¿~=3gÎäµ×^ÃWÅ|)6@m 0+¦‹dmIà`°pâãDJX²vîÔœg…‡ß¾¶]µJä))€ð0ç‡>*ˆ½À ÛIÊÚ1’’’XÿþzRR˜÷Þ<»ø‘påÌm HMMÍ1ygò@V¢”¦c¬JÀuàw;é}€xýu¯<¿WuñÛSÏû²Çn¥ç}«”M!ÄÃêòR(eC g“‘‘LzZ"™)Å­Î,«¢a Â›B,¤ „‡‡Ó¿ÂÂÂØµk;vìàæÍÜ©©©Ì™3'ß²Ž?΂  ¥víÚ¼úê«Lž<™ÔÔTÕÐõo(Y¶p^B[#\ÍùßëÐ!ˆˆ€>*ý6MH€€Ç×pcà{àÑÛ €Á®Wh772DÝŽRß­> Ÿ%0åŸSl àQQ£ÛFˆOqqqDGG³oß>Z´hÁÀ©_¿¾jÄò"‚¥”ñhÆ, …Õmd€æÎ ´!,«M}RÊ=_šá†ëü@Ä Y ) …Ba[a“JïÞ½éÔ©±±±lÙ²…S§Ni‰hÓ¦ sçε+€-ZÄ¢E‹r¼4×®]›—_~™)S¦––¦¸Š27Ullæ.à ¯ó¿WJ üßÿi‚4¡”¢¸ýü3´k_~©oxØ4»M'×l´Ô½;TðÕ…¦VJ­·ñ䮨N6L]ûE‰Ÿ%2åŸÚ³å¡þ¶3Œ@“¶Ôßߟ:¨5¿@ÿ ¿ße¹QQh®§oj !Z[íÛ]߬ïã-„¨'„È3d"„0!: !"…äB´BôÖgqÍæÌ5läBˆúBˆ(!D!„·Uº¦ôÓëç"©ËsÄM…B¡({øøV¶ùræÎËþó’’’\º…4iÒ„P·n]~üñG|||¨[·. ,(P[ á_|±@sjÕq¼M&ѹN:ðšéncçÇÓ§5:{¶óc _¸}ûÂ+¯€ÑtÔ{¿®°üó{;çÜ Dpq¨™R“ŸÝ ií¦ÔÝ]—J¦JªQ\@ •¶Þ ¤[‹` ¿…À5û¤hCÇõÏ]SÀ$«ÃÑÖ ÿŒ6³|QÏkë‡óa!Äyà]|_B<£ÿÿ?«¼ôòNê݃À!Äh‹l#ÐÆÑ@3‡>¥?¦ …B¡È%€ÓRoæñ í*3ÁË–-cÆ |óÍ7lذ-[¶°wïÞÔ…ê@à šC®úàÀ/yCtì¬V£E±{X#„RÊOôGñ0àk`;ð&pÜ5GBrˆ\¿ÏáÃ’˜”¬gÍk¢zWïê]½[¾øЦM ‚ƒ*—г©âZ­¸ŠwèeË–Q³fM¢¢¢xâ‰'˜>}:&L fÍšÄÄÄpóæMüýý '88Ø%ÛÒËË‹ÈÈHî¸ã7n̰aà À Çé„ y;u¸Ãé®™Dge9{D ˜ô&‰Î-~ûvM¸~ôQÑc䦧ÃÔ©°|¹û·&°\oHWáÚ´‘ ”Wèâ à_›ýÊÊ+©S·aaa,r9mÏ·-vÙgΜ¡nݺª‘ ¥}n“Öùè DÛ„îúçͺÀÍBìú !*I)Ñfrv#—^ÎG@0@Jiön1NwÄ5Ôj—l —”ò’¾ío-R³¬òÎÒŸ÷wK)×[sp x[ñ•”ò²â'=ùŠ”rGY¸,âãøõ·ÃT ¡^½0uŸ( »$%&ñ믿Ñ¡-•+ª)güûßÿ¦k×®Ü{ï½Ì™3www-ZÄsÏ=Ç„ ¸ãŽ;HIIáï¿ÿæàÁƒøøøÐ´iSªV­êrßGÁ< N¬“qC›©ø¬ûy{—€63˜[1…8·èk×`èPxê)xûíÂÍjÿý7<ø ünŽ2PíA.vR7Ø»‡”)´³0@ï½–=±¬XBøÔ©SÜ?d(ï|ø!wtT]n‡Þ ÌF3‰ÞtAs `)p7£­Žèªß–æõÀ[ò)·1Zè¤÷,°™–"Xá´>·Àf–¢¹[°~´^µÀRÊ,!Ä×ÀzyûÊâEpüÄ)ê׫C‹­Õ¡P( äÏ?süÄi":´QQD,g}-Ã#•ö,plìQÖ~ú9111´ïÁ½÷æŽRh-„ÃÃÃñóó#""B«sZ±±±:toooš4iBõêÕÕI.ç .¢.Ñ%æµÐÌ‹KЦ:œ„”°l™Sø‹/ ™œ?ü&N„ÔTÀÍ~p´‹žP;"8"Ôdc°Ì+€ÍôÐ >€eO,C1Ö¼7g²yöb&/\ćÿú }¯Äˆ‹‹cñâÅìÞ½›ûî+û##·CÿÜäÖºà«ÿ;é·e7 FJy6ŸrÍ ÎVkü¶h3ËÇò>äd²⢅`öÓ«ÙB[½³}Uò*‚ÝÜÕªVQO,…BáÕªV!1Ñq›Ãã'NñѪÏxhäpZ¶h¦ÐBÛú¿4iÚ´ 3gLàÜÙótïÞÝvgÁBOœ8‘Få¤ùøøÐ¶­6£‘‘‘All,‡ÆÓÓ“FQ»vmu²Ë!w@a¢ ¹»k/cI·tG3‹¾ x »&¾EåðaM.Y¢…T²ÅÍ›ðä“ðŸÿèÂwôwWä´E61B]ëEá/ï¿Øºi+aul[XöÐ ñ¡à™±ÏºìíÛ¶VŸ €îìÊ¿Ö~ÊÈQ”è÷©^½zŽÓÀòâ7¡ÔE°”Ò$„ØôBМbý)¥¨gIµÈûp7…8ÍÀãhc}ÖceÿE3ÁþxF]f …¢"‘––ÆÞ½Z¨õI“FµHK¹A³fõøÇýÚ#ö§Ÿ‘‘YaÛÈì ºÂwB êׯOïÞ½éÓ§¾¾¾ìÚµ‹-[¶pèÐ!âããU#¹8ƒŠrýß.7$^hæÉ«p~ˆ"³gêIÜòRSøÞöæfÍ ysu}»©©©|ýÕWx$¤P«J¨Í<•|ýhW=ŒwîT æ î·ñدcÛ›³™ÇõÇÊaižÀi+qû†b1š¿?43곺h=g•w#PMQðb€`=9Á*ï&!D8Ú:åÖºH>(¥üÍFÝÆèâ»>ð—ºÌ¥Åõñ>ü‰IÉB¤”ê]½üŽ$Àߟ6mZT<æëëǨQ£AˆèÐ’tÝóqzZ"]ï숧§'ƒ/o/-îH·Ë3tA 0 T& !¨S§uêÔÁh4òàƒrõêU¾ûî;ÔÞEé©w R ±Ù$ú¶YÁÚ yþÙ å F‹ý[©Œ¼Sö{©j¸}°ë× qŽãǤ¤$bbbˆÇ××—õë¾âÇžÊwŸGûFñÄ; ¹³kW ƒ:!®*‚¥”ç¬E©<ǰÆHO‹ÇÎ:aÝ$ÙÚaá«Û-À )å8)åi‹íýõÙ(7Ø®¿ò«·-Tûu‰)J‹ø› üvè0Õ«U§Q£ÆªA…&!!ß~;L‡ö­©\¹èBXJZjÂ×J॥ÆsGD+¤4‘‘žT¡ÛÛ,~]U ›Ã•6F£‘É“'óàƒrîÜ9¦NÊܹs©T©’ºI] ùZÕÚÄÛRRncÅk ™-/¡-z+Ê—+Ï3eˆ|¼B+\8ÒÓÓyp轌v"C‹Øx ÄÄĘ˜ˆ¿¿?ááᑚšÊ'žLÿß ;/Oâââ¨Y³¦:)®*‚]¡ÿŒÕcÿ~Š»¸°¸|¢.EYâĉÓ4j؈š5kàã£Üä+ŠðPLKÅßߟ'ÏÒ¡}ñfƒÓÓóIKPmm#F°¥‰´«Î—†~þùç™0a¤§§3räH¦NÊÛo¿­„°‹2¸,Š`ÐÌ–ŸC‹)<Í[£4ÞÅFœ‘²/‚6ÔËõ ¤¥§#$+/•B||¼®LpP õëÕÁû¶um\‘Drï޽ʪ°‹Òð@‹%é(^^.dmI#´õ²3Ñ<Ä<æIº¼8ܽ üj;iøp—Ñu IDATB]ÏŽ ¥dÕ²¼ûÏgsm¯ì@˜§{÷îeèСxxx¨ÆR"XaÕRº~|„ø ÕÊ™ÍoöÕ2-¿äÂ\ëíæm¶ŽQPE©WyÀ–Ãó6[k­;àæB<"„¨êRi+AX@,ª€´¤%!Bó+³¤í*Ø­‰Y%vË•™0a/½<¯¾ú/_|ñïüÌ3Ï[›ÉÎ6‘˜”BçÎ]ðòòFa÷åííM›6mIJI%;Û¤N– áëëKÓ¦MéÙ³'}ûö¥[·nøùùqäȶlÙBtt4¿üò W®\!?c&³øýøãU£VúSøÙ³—hE)q Øk;éÞ{Õ¹p”ÅsÞæù{òŸåíÙ¦ûØNrr²j0¤ÂÏ !¼Ð§H)³JóØRÊ 4§ü[ÉBˆ0ý7d°Tq’[³Ä?K)·«¬g€=Sj9#[Úήnç±KKô:Ô2Ÿ#e*aìêHÌ›øøx6oÞÄ”©/qîÜy–,]NHH0QQQÌ{{&S¦³žideeãååÑXðãÊÛÛc–‰2°*¤bwÜÝ #,,,gÛõë×9þ<¿ÿþ;^^^Ô®]›Úµkãéé™K¯ZµŠµk×òÈ#ê¸nnn¹ÊR¸>Á@'`w¡úaÚÚà´4Õ~¥Â& Ûv’ ä8gÏžå ¦gùóÈDvî¤M‰àÛ*x À“ÀZ)¥9DûSÀ|à>`Ýmí®JyøøPá®ÿ–  „ÛÌ¢XÏ[êX Æ’(ÛRˆ–¦IòíÍ›·`Ñ¢ENÀºƒÁ3ã«?{m~6 ØÑQα^_Ÿ_¸$W$$$„œÏœ;wŽŸ~ú‰ôôtÜÜܨV­aaa<öØc¬ZµŠÏ>ûŒ‡zÈác4oÞœæÍ›«›¸Œ1¨"4“h%‚K‰ ¶7W©=z¨æq”Ö* %‚ËïÿüËb[6Z|÷l×ê¸J£þ;²xE7î f !®pk–x—>«\"XÏŽZ¯©µ·OaD¤µ)rI mW;viu¶ Û±vtFXQ60`ÞüùÌš9ƒk×®Rµj5^~åU ç PK§[öD¯mœwÿŠ‚­õø¥=°týúu~ÿý×®]+RÉÉÉÄÆÆ@@@&“‰K—.qèÐ!’““‘R²aî]»ÆÄ‰ÕMY޼‚]¿K6ñòÒf„•QH “üd;iÈpsSMä té2Œøø‚E¦¦žâõ×ïå±Ç(ñ:%''³w¯fGòäI:th«Dp#Ô†Ø\Œ6ÓêÒH)¯kµúŒv]OZ !vqk–øømÎÚp8e)ž-ó”f«\["ÛÑ2ò+³0û•¥Žv~æÐÖŽ²lícOT«ÙಂÄÇÛƒç'OaûöíôêÕ o/7§š![–娠µÁÊ$ºìzZOOOçäÉ“¶¿“>>>T®\™ãǫ۱œSèì+Ä>B¨ÙàRavcX)'Æ®ÃSObüxIB½ùækÞ|,#GU V¬¯EM—R^B„í)噼*QRÊKBˆV€§”ò«}ú°{÷îrÑÎæÈAà}!ÄÛ€ÙÌö´âo!Dg«}¾¾B¼  !þ¥wlü…K€KÀN`3pQñ.t-ù X"„X®çßœBü!„hgChÎëÇüˆþB´·Êú°Gñ¸žç'àýÿ¾zžÃú6€'€S@”ÕñÆ£ùÐÛ¯?6Î!¶ë–ùºÇÑÂŽÿO¿!„xN”’}Ÿ”2^Jù)åcRÊZÀ0à,0 ˆëß ï¬\ELÌ1õd)×"Gµ¢`!l4f(€‹{-åçÚòe¨ànHÍö>—¶mÛÆž={”®`"¸° ³I´¢„HÒ{æ6¸ûnÍK·ÂuX¶ìijÖ\j'5ƒÖ­70fŒòdæ Œ.Ÿîª£y(® |eCÀ6A[ò±øø·ùœ¡…ÿ èŒ6;:K/ûGݳ%#€tÑVÍ9U5àk!DˆE‡j°HÓÛøàìBÔ·*7X ||| ôöèéƒQùtà&+Ðf›Ç¡Íj"ôïá£çóÔ¹70ZÏ7\ø Û2®&¥<,¥|[JÙ ¨ñÔS8}æG›vÝ?þ¶oßMBB¢Ó]Þà ¹¶ÀÉV p‰kÉÑñ?¡z¼y(oëñ•®˜ÔÚú¹¡ aE ñ`Çâðáªy\„„þøã¢££¹té-[Eˆ‹yòU­º‚wÞyZ5X1±\ì <"¥4 Úo…©@´.d·È[˜*¥œgÑ™éŒ6K)-WhBÑâáNÞ²:æh)¥ÙWÝ:=ï×€‰º ôRà:ÐIJ§çýKqø˜§ j3~ÀB)åóV.³×ÝRÊ;²z]wH)ÍŒ¿õº½LÞZ¡™RÏ•R~b‘ï,ð&.b]J™l4fpWç–œ8yš=?ýƺu_ñÖÜ%4oÞ„Ö­š3|ØÝtî|§ê”–a¼¼Ü1fñððP¡(2YYF<=‹ç.¢(ޱ*‚¸uÔûºÀŠòÀ 4Ó¸Âàí ééªíJ„õ¶7ûûCÿþªyJ³óÀsçΑ˜˜ˆ‚ÀÀ@ÂÂÂhÞ¼9ƒŽ;Ò©ÓLŽ]`±gÝ»_¡mÛ–ª(‚ã€ÿX ¨­BˆóhN˜¬ùÎês7ý}‘¼‹taÙÏJŸ´Àfþģͺ‚6ëÜøÔB›ë·Wqm=¬5ß±Mz Í0f!€Í| ¼ôÖEðQÀŒBÄ_K)I)be^í*4lP6mï Kç¶4hήíÛX÷Õ·<=ñEnÞL¤WÏîôéÓƒÞ½zP¥JˆºCÊ¡UBIH¼Ž‡GUÜÜ=Ô€†¢PH)É6f‘˜xª¡UŠU–Zœ[kém­ÿ-/âXJɉ'”®à"xFD°ò]¤Û휧AZ»+ògذ‰¤§×,0_JÊU¦M»‡>}ºå~æ§¥qþüy.^¼HVVƒêÕ«Ó¢E l–ÄèÑ™1céé‘Z?¾á"V®œªNˆ“Eð1iÛ=ç  ³ÂCJiö)'ÑÖÑZbæwÌÆa‚â2šS+KŽÚÈ+…'Ñ< ]Ü'„¸ÛFýüw!Dˆ”òºU½‹Bcýýu}ݳ5n@C½®IBˆGÑbûÎæ!bÐf²?—RþáÊ'ßÛË‹ž=ï¢Fj4lXñ)lݺƒo¿ýžÉS^¡Aƒúôé݃¾}{Ñ1¢ýÿ³wÞñQ”Û~fw³ÙM/BI€Š¤#ŠWX®Šz ¢ "ˆE±‚‚€H±‚]¯¿{ñ"r- â±€€1 Ð!›Fʶùý1›° »a“lÚîyø,™wæÙ33»ósÞsÐKîüzMX˜²²Nˆ7X¨6›ð03¡¡µsG$c‚7©œ¢(Ü{ï½rQ1Éháré•:o´±©ÅÅb?¿òÚ€BHVhßh×®%/½t9v{Çs,7–‹.êÉ©S§8xð 'Nœ@QÌf3IIIôíÛƒÁ÷h«É“ïåßÿ~€M›zc0üƈIej´ þÁÞP—èuÈ6uiKFrؽô£º$>n³$sKIFçuÀ•øl§«h“p×ßhÉ´<ápíï+Šò5p 0Í+=xDQ”{UUm0Õ´“’Z0rämŒy6›_~ÝÀš5k™øÈ423ѯßÅ x)ƒö§Y³¦rõÔÇ&3&“Y !Ô¹òçrŠÔã¡•Áf“ˆ`¿ã%Úl†+®óøÂSOá‹/&²uëB¯Ëß0xp<ëׯ'>>ž¤¤$ºv­^=]½^ÏsÏÝÌm·½G‹?ñįÈÁ¨ÜÚË2­­ªªÚÏÑ×N·å”»Ñ CKxUÞ3ÚÊÃM‘âš¿Ùå.éw¿ªªOyX>ÌÏåˆJ„ï/ªª¾|®í¹’}YTU}xSQ”h`ZØôãÀ; ñÄ 1pqß ¹¸ï…Ìxr*Gç›oÖ²æ›ïxâÉgiš˜Èg+þIB5Ã&A¬Ø“'X„±H\ ¼PÉuBM@ŽØÎoX9S¥W\ááb"ŸÎËÐP}tcÆ|FNÎ5–pбãG,Xð†ßÛ† ¹”^½îcÔ¨*åE*Æ=-UQ”žånP¢Õ¾ÝèC_%E£<Å?Dóî®)7¿‹¢(i¾3ãܶ¹mŒð5Š¢Ä•Û¿vÀ)EQö(ŠâËYQây®(üÏh^è(Š¢+·½+ÓŠ¢¬r½¿Íã<¥dUUsTU]‚.ÞH »¼Ä&¹uÄßYöÖöìJgá+/‰Á+ªªžó%ãÖÏFên D gƒ;ç©N²Dû•µ@¾ç¦ë¤ÂN¥¸õÖatíú5pv¨B|ü›,\8ºÆ~×¾øâ5†¿LB ‰`€Eé­(J¨¢(ÐJ Yù>Üð¬t]j#EyNQ”DEQ¢E¹ íAàNàÅr«)À?EéæÚæ´:¿ÀBW¿§é@#`•¢(ÝE1)Šr)ðZy¢×}ðT”Ôî­(Ê%^>Çv´1¾Ý]ûÖFQ”pEQ®^E ë^âZ|ëJ˜ (ÊŠ¢$(Š’¢(Êãhñ/¼Œ³nØ'NG]åêADp5Ä®'¯o°y‚?ûì3òóóå‚ `®¬Â:’¨Éü×óìÐP¸ê*1OeY°`4Íš-.7×ÂEýÅEõ5P¼-™Ô/hÃç¿Aó˜^£ªêNû»-+ó4à ¼ì.-ŸÝÙ%ŒO eÑ/¾@óÂUUu¿ÛrKÐÆÙv~s‰äo]ïg«ª:ËÇýû°¡•/úª¼§× ®m^ìB+1¾ˆF«ªú¹ë&ïp»keÀq— Ÿq}íÜ/§˜ Á†„CãQÜ–yšLüë_ÿâÀDDDÈEÀT¥T†ˆ`?a¾òÜ4hDG‹‰*ËùçwbÀ€ShEu4Z¶œËë¯?Rgû´s×..»ã²²²äU÷âà*àb—¸üm\¬³Ü:c¹¼]"w˜¢(]Ð<©à[UU½ejÎE+›ô7à|´š¿Ê{O]û0KQ”w€^h£ÿSU5³\ŸÓÑê çyØ¿ïEIuí›ÕõàŸ.aý‡Ûr§Ey ­TS²KÈ£ªjv¹>—+в¸ m,ó)WÛÑ ,‚à â .‹·’HžÄr]²gÏš4iBll,QQQ5–Áû_ÿúGeìØ±r±8ç¡…Eï©Ä::–%ÚjûU‹ñ:¾úúëÅqqqr€ª ‚KÄæÿ\/o76›}¸ùùøÝÇ%;Zòöï|Xöç¨ÿ«ªêçh?@ÙÄ]™®—§ewãC©%UUsËé$‚ "¸"!ì.v=Õ®ë1ÁMš4)Ã999¥Ç ,,ŒØØØÒ—Ñh,øÄUhÞ‰Ê`2‰®6^²B‡„À5׈yªJ||<·ßÞŠgžÙHjê2fΜ[«Ûw:Ì}çÞܶ=ÉÉ4[²DLuD° ‚ T ‡®ºH®k!NëÖ­iÝúì‚………X,Nœ8ÁÎ;±º)½^_F GFFŠJZEœ›+¶«2¼†B÷ëRj¶zL™r/ï¿5“'ßGh-fr[ûóÏL|ûmþ¸ùfŠGŽ”Q ‰,‚ Ôâ öŒ{¸sCl6›1›Í4kÖì¬6»ÝNvv6‹…íÛ·“—WvDRTT¿þú+ÿûßÿ5jkÖ¬©p[:uª“ðB¡f8m\ÙJ¬£×ƒÁv»Ø¯Jü„6@Ï‹ª‡Á``óæå˜Íæ³ÚV­]ËÄyóxútzöôO²¬ƒ™™Œš3‡ ]ºpjñbmÌ€P%¼yóVâµ,ÁCцÏ×&—¢%ÃAEQ|Áº ü/ñòÖ‡Ðgß6jÔˆF<—ÎËÍÍeË–-rq1CÑJlø‚Ó ÙÙ"€«ÅÞ›žzJ{È0y²h©êP^ÿµ{7c.dã%—`5Š¢¢"¿lçñÅ‹yçØ12gÌ€©%_ÂÂÌtïÞ³Ù¬‰`UU«íPUuƒ Að/§²,lݺܼ|”R1"åï9ÿ¢A—.ˆ‹­Þ¬„Cû.†ƒ…¨¨(FE||<„\é£..Ö°Ó)6«2N¼–Fíá´iðÍ7ðî»à!¸C¨ùùùLZ¸•F#‡žÌfÂW­ªúás:ÉÉÉÁb±`±X0aÚµ ãîÝX{ôƒW_ƒ-ƒ%;‡Í[¶’Ø$‘ÔÔ¶b¡Òäää°yóVzt?Ÿ˜j>m–phÁ×_=Ë—/gÁ‚Œ7N Dtš¢ÕÏôüyypú´ØªÚü;÷bß|]»Â[oÁÕW‹Ùªò;÷òûïóÚÖ­ì˜0µiSŸ×µZ­X,²²²°X,•Š3NGtt4±±±´iÓ†©Ýº1Ùédæ›oòÞ'Ÿð×øñP‰m gð-Bg÷î}¤¶I¥Y³¦˜Íab¡ÒÁî=èѽê"ØWq«“8¼:gÏž½|úé ×wÈîZÙæu×]ǧŸ~Êüùó?~¼„ AA ‰~ËC›Ý®ym6±“_¨„òÄ ¸öZxðAxñE¨ÅO šïþ™)ÿü'›oº‰âÛo÷ò›ZȰX,dggãp8JÛŒFci"ÁV­Zy[\þ÷rúèÑ<˜ÍÃ/¿ÌW11¹ÿ~­–X-pâÄ –.] À?þÈUW]Ñ ÛYáЂ 4| ‰‰MˆˆˆcU"""’ÄD'ÙÙÕ ÕõuL°x‚ëž””ÖLœ¨ Ñ­[·ÕÚv‡Ί+DžDpA–Ú媋J…¡ÐWQaÁøþ{øøcèÐAÌX“fÍâM {î\ðò;æp8Ø¿?)))$%%Ñ©S'ôz}µ·ò'Ÿ$}ûvœ8‘̓“[ nü„„&Ož\*îô%Râq—SYEÀDˆX¨6fs˜×ò6"‚kÁþaÁ“ôäÚk¯å¼óÎ#??_|pÐØ5ít‚Å99"€ýÊï@fWýzô€7ß3VÄ“cÇrÐ|Þ<ð"õz=:t M›6ÄÅÅùE»Ó9-µ¯¼ÂÒºMœˆ-#CŒ”„CŠÎMtlsó<ͯ‰m»o§ütmìƒÜp BåE°/{8´9,æ¬W°qùå—!M †V+œ< ~Jž+¸óyuEÜ{/Üt“¢.œMDD¯N™Â·×\ÃàÇ'öÓOël_®2„__xG;u"^Š?Ÿ÷phÁB¥…pÉtŽåPo7Çr¨t;å¯{› õKK‰¤s à‚ì³^‚È8pê=8uJ›j€ÿú§›O>Ñ’f­_/&õF»ÔT¾zé%Þ¥Û¸q„lÞ\'ûa0xäÞ{ —ƒâËï¯Ù¤ÙML!ø[(çXyÊžDì¹Ú¼y¡=mã\}Te¿醻÷:¥î7ãÞ–s_¦¤ÝS›P?Y¼ä5î=ªŒ‡VUU-z½AÏý£GÕ‰–p賑kIT‚ÛnƒµëÐBvcÅ&~ç`ŸÿºÛ¿úõƒ'Ÿ„©SµÚÂÂÙ\Õ¿?C.¾˜YK—òÞþÃΣ4Ä,œw1鋸D¤º L÷õ*ÓæI˜zÚ¶/}Tv¿I»{šÊ Ywìi9» øÚÍÍeâÄÉLœ8™—_^X¦mΜyLzt*& ‚ÌL1±7 Å/?̈W^Áüþûõjÿj+û¿ˆ`!`…ð¹°/í‰çò‚´¦>Cmo»¾Ý|Wf~ùv÷›wÆõ›è¨hÆŒÀÔiÓù÷¿µqKü O<ù4ãÆ#2Ò¿c2N§Ož`§Ó´ÇÆý æ1ÁB`STcÆÀðáZ4Í¡eÈüÏ0`)5âe_»V ^±BÌ\111|ðÔSüoÆ ºvíZoöëÞI÷róè›Iÿ#]’ˆ`¡> ìÚNvU—Û®MÑëëM¶ÜŒ*/Íy‘#FǤG§2þ+L6øø8n¿ý6f¿8Ëÿ[õ1Õ«Ä)aÝ*ɘ`!Ù¾z÷†Å‹]ÙŸ¯¾z‹mj”!ÀàBÿw}ê ¦=ؤfs^ûög%ý;}ú4}ú÷aå+k}S¹à™ xqÕ‹Œ:–“'OŠ–ÓT¨/B¸¢qººíÚ¼áöæì.€åf<e°jã×—0|ص´IiŪU«HmÓšë¯ΫKãtZkD‹'Øw!|®y‚ÐÐxí5èÕ ÒÓ30xˆÛÔ MO€G©‘ì?‹k8þøCL]ìv;‰ÝùìðgÜxÏdì¨Ý²F:ƒŽž÷ö¤ÙÝÍxà…˜·dv»]D° ø‹ÊŠH_Ç×Ô¾ÖÕ¶k‹ªxuϵN‰˜–öòE¯ƒÙsæÐ§ORSÛpÑEñâìÙèt5ã‰ìûu&áÐB a±Àõ×ÃèÑZ©Î¾nÛÔ:z`<ðo …ÿ»OOׄ𫯊©+ƒ¢(tº®žÁC=Dv-×¢ §ï´¾K;ÆßÇü½N<ÓõÉ-øOY˜Kæ¹ d_Ú<õëžàÊS[E}TÔgeÖkH”÷þ–®î`÷¤YrCH¨˜M!L|dß}÷ Àª¯±pd_û ö1Áž"ÕõÃ¥íÛ·SPPÀ²eËÐétèt:FŽI³fÍ0 „††b4ËL{½gAㇴìÏ p'ð*¶©Sz¡…GOü¬u àþûaõjxóMˆ•Œß>b áÂqbÉ´ð©ÿà²n—1æž1µú}Úôü¦4=¿)_þ÷K>yðžxè Ú¦¶,žcu–­h}omžÄ®/m¾öˆ¢×›öe¾û{oÓB•Áª“ؘHn¼ázªê¬Ám©>¿„úEZZ}úôáoûv»¢¢"l66›ââbrrr°ÛícµZ±Ùl8œNgéÍ›û´Ãá(3?44ƒÁ€Ñh$++ £ÑHRRááá˜L&¢££q:ìÛ·¯t¿’““1ä–¥>âpÀ³ÏÂ3ϸjÿÆó€Áb›zCðИú·ûåËaãFxÿ}øÛßÄÜ•!¶E,ŸȾ_÷qí]×òÐ1èÒA—-**Ân·c³ÙJ¿›=}O[­Vå qçååy݇vCÛq¢Ý n¸í6üoF£QD°  IäÔÞ¶$ºá a»½¸ÆÏ%_³C—ÿ‘ê!!!„„„`6›ýÚ¯ûMÛ† ÈÍÍÅn·sÉ%—¢(DGGc·ÛY³fMéz#FŒ8+ÉŒP÷8 yøÁ5£/ð (¶©—Œ@ó ßl÷ÿ¹pé¥ðøãZI%©)\9’{'“Ô3‰¥o/eþ’ùŒ5Чµße“ÉDHHƒ³ÙŒÁ` $$„ØØØÒïlo:K¿\êq»Å§‹Ùôú&Z‡´æÇ5?,ø•9—¼ IDAT`ð¨ÖoS{‚B°œK¯/Ù¡}¹†< 9ôkË]\=ÚërF£‘Q£FÉÅXY¾î¹GŒx‹d›©ï´VÏ¢•Sòã×°ÃO=ß~«y…““ÅÜ•a÷Úݤ0cÊ zvïYÃ?ô°ýÓíØÒm¼8áE’ƒð`‰„!4Ô€Ýf'$$DŒ!T›ÍŽÑX½ŸIŒå»„†FALœè–) X ôÛ4œàààa Ë¿ÝÿðƒVSøÍ7áºëÄÜçâÔžS¤¿‘ΰK†±`Ù‚RïoM‘ù[&{>ÙÃý·ÜÏ »­ÝE B€Ð(œÜS„„4Fo©ñ/Q!°PU‡ÝFnî)'4ªV_¾†C»®I*J†'U%=n¹Å­4Î5À‹H飆Êehµ›Öù·ë’Lá÷ÝsçBX˜˜»<…9…lzmi1i|8ïC¿?)Oî±\¶¾¾•K;^Ê ¯¾ôI E B€fF§ƒ¬¬â ª„Íf#<ÌLh¨©Ú‚ÚW±,ÔŒv¾2†_ð‹kà¢" -œöf±Kƒ§ ð1°­ž³ŸËƾö¬[};‹¹T§Ê¶nCÝ©2gœZ E>²ã‰&òÆo#Õ?D B€a2™1™Ìb¡nàE BÀpêÜ}7¬XášÑ -ü9Ul0èмÁc€þíþ?´šÂ/½¤•T æ@µýéûY7mßù0}è[kÛ]óŸ5è%[ÙY§½ ‚ ø _Å­ˆ`A¨ß¬]«í\±­öï=Àç"€–ÀjàZÿw]TcÆÀðáÚƒ•`$""‚g'<Ëÿ½úô½°o­n[ðÙˆ'XAð;Š¢”ŽK/™.ÿ^DpÝðë¯ølå*²²²øã­VÊyxÂD1€Ý®eù9ÓUû7˜ Û<‘À ?ð8pڿݯXq¦¦pÿþÁeZ½^ÏÐ!CÌþfddðöÛopúôi6nÜÀÁƒ™4i‚ˆ`AApGUUŸDp0—H*I\U2V·üûš¤wï^ôîÝë¬ùwÜqOƒµç¡C‡hÞ¼¹\|~`ÿ~1Ö¯wÍø°mì¨<ÜôÒý}½Â A0e ̘Q#õ’:0kÖ¬³æÏ˜1# >Ÿ„C ‚ ~Á¾ %’Îà‚l¯µƒ…s³páB1‚øä-üyýz x øHpÐÒX ŒB ‡÷#<÷ôëûö‰©Á‚ Bˆ`wﯷW0{‚=YœgÏž-²RP÷Þ 7ÝÙÙ@+à?hI’äN1¸13€÷ÐÂâýÌúõЭ›öFD ‚  ÁSâõõTΨ!“••U7Ì’¿ªüþ;ôèo¾éš1ø è&¶Ü€VSøÿw­=€¹çíŒ ˆAí»vÀåßûC`Kà'žxB.¼J_§°`\pddáÀËhõb#Å>‚¢%Ì ñ÷o½¥=Ù²EL-ˆA OpÅxóøúË\"¨k[1B.€À‰píµðÐCP\ t¾nÛ>(‡€haó~&#úôÑÐȈ¡&‘|l‚`âÃnw`³ÙÄB¥ Á`Зfq®’Ú¸!ѹsgŸ–+..Æn·cµZ}šöt~•œ?Š¢G·n¿ë ß|ÿø>Œ–ìè>`*5âÙ˜®haóÓ€û·ëâbíÍêÕ°t)$$ˆ¹KÈÍÍå…ïpåÍWb2™\¿Û†2Ó‚ˆ`A*l6;……ÅDÅ$×(Z "Tš¼¼lr³Ob6‡RõŸ ‡ö]ô–ÏÝÐ…ð7ß|ãõø» W“É„Á` $$¤Ìttt´ÇùBõ±Ûá‰'à…ÀéПû‹m„* ¼ôs‰á|ÿvÿùçÐ¥ ¼÷ ”Õ¥ß)祠Óé((((}`h³Ù°Ùla0PU‡ÃQ:m·ÛËÌ×ëµÞ%ßµžÄtùiÁ‚ ÔÛÙ9y´jÝUU)..£•&,,’ððHöíÝAlLƒ¾Ê}ùâ f¼ÕnèãwÖÑjzzºÏ^è`dï^¸åøå׌þ.,6ÁÜ€VSø~àwÿv}ä  “'ÃSOA°?3\9üJ¿ôåt:±Z­X­Vl6[© ...&//ï,q]ˆí$"X„º'7/è˜TÕIQ‘`¡jØívL¦P¢¢‘›wŠ¸Øªy%}õð»'¸¼ .ÅÁÈÊß|øá‡Ìœ9S á>‚Ñ£!7-äy 0¿×}‚œVÀgÀ,àUÀ£]œN˜9¾ý>üRRÄÜþ@§Óa2™*íåýá‡ãóË)  ‡ÃANvqq°Ùìb¡ZØlvââ‘“ƒÃá¨r?’«,îÙšÝ_åÛ:S§N•‹¨žŸwÝ#F¸pk—H¹_°PC„ÓÑ2H7ö÷¿ü¢Õþè#1µ "X‚§S¥°¨›Í&‰†„j£ª*6›Â¢"œÎªŸO"‚Ëâî\ÑK¨qqqb76mÒJÍ,[æšq#Zöç.b¡è¬.õ×¹¹Úƒ‘#µ=‚PU$Z8'ѱÍ=ÎϱòK¿%ýDÇ6/3ímÔ–mêË~ªª*BX¨[ßciËKÊ‘+B¸jLš4IŒ€VRfÞ<˜6ÍUú(˜ \'¶j™FÀûÀkh!ÒVÿvÿöÛ°~½Ý£‡˜[¨<â |&Çr¨ôU‘8®lžúro¡è_E!$Ä€ÕjõÉlµZ1„èƒ6IVùðç@‡;v¬\uÄñã0t(LœèÀÝÐJ؈êìGmüùg@ Œãݹúö…¹s¥¦°PyÄ,øO"ö\mžDt‰7Ø“—¸¢><­ãiÿÜ—«h=_>OEÛñ´^]U:››[:¯dºü{OËWÔ—Pÿyíµ×5jT±©ª*K–,Á`00jÔ(¿lK¯×ÆÏ?¯§gÏÞÆRq\>;´Õje[úV"#ÌèõÁ÷<ÖS¬’I Ùܼys¹èê€Õ«µÚ¿G¢¹7F“‘Ú¿Býà|´2Ÿø·ëâbíÁÏêÕðÎ;и±˜[,Ô²v¦‰Y÷°çÊ Xo}¸o¯|[E"ÛÓz¾~žò‚×—ý¬ JBQ£££ÉÉÉ)#bsrrÈÉÉ).YFUU¯ËWÔ—P¿ÉÍÍ- -**bܸq¥msçÎå©§žà–[n!""¢ÌùSUe";'‹ÏV,ÇnwxˆvÖfCôDD„aF§—,=‚Pl6xì1˜3Çå kŒV·õob¡žÌG+Ï5Èóo÷_~©Õ~ç­¤’ ˆêP®Ég_û-¿\uöÇ“®OÔ¤HÜ@®»èhÆŒâE‹˜>}:III >œO>ù¤T7®Tûƒ°0f³ ›=†âb-,Zç‚‚ÙŠN§Ãj-¢Øj•ƒ%•d×.­öïÆ®]"#^l#Ôc†¡…ê6ù·ë£GaÈxäxî9©),ˆü(d++ Ë{]«+<¢êZy±å½»¾,/"¸a¡ª*/¾ø"‹…¯¾ú’©S§™™ÉÂ…¯ÇСC™5k6›Í¯Û5™Œ„ëµ±¾:‚Nw&ÜÙ=$Ûjµaµå±) }ö4_ÎÅ{ïÁر®ÒGF´0Ó{ÒGBà%ð)0X ø±d¼ªÂìÙðÝwZ)¥ÔÔà2í©S§Ø¸aGfÒµWO:vìHˆ< ,TªŠ×òãwë"D¸!ØÉ_¢§$œ5&&†ìì37Ô111gµEGG—YÆ}º¤?Omå×ê/6›W_]„‡Ç“‘ñ'_~ñ9©mZ“–Ö‘9/ÍÃZÎ +ÙÅkW 5Ë_|ÁçŸNQQ………8ÚÝvãÆ +³lŸ>}6lX½þ<¹¹šø}ï=׌6À “k¡L.Žù·û¡{wX´n¿=~O ™óìs„Úè•Ò– cbI_µ†^YÌÀë†1äÊ¡rΉjwïo}®êgóå³×5111UZ§Dè–ÔBÃBUáÅÙsxþ¹g8yò7aê´ÇEðÖCj;1ÖŽ;yïýÈÈÈhpör:dggc±XÈÊÊ"77§ÓYqøqãˆ%66¶A{D6nÔŸwír͸x“ëFhÀ\ŒVSx°Ú¿]çåi ãV¯†… Á-¿g@áp8˜2vS®¼ž¦ñJç·OjÉ ÷ç³_×óÞ±eÜ~×È*oãèѣ̟?€uëÖqã ?í¼ˆ`¡Ú¸'–rŸw®6o}x·ëKµýy¼eš®­ý<ÙÙÙ¥â×}Ú]ЖÌÏÎÎ.³LI›§¾Ê· A« !L˜8‰µk¿ãÒK ÓéEס¸­/åÚ·odzÏÌààÌzi»ââb²²²°X,X, QUÕn¯#&&†ØØXÚ¶mKTTT™ðûÀ¸~µÄW=¦%Â" x¸V®+!@ˆÞÞB{°ãç4ï½?ý¤ÕîÕ+ðÌ·xî|Ƽ²ŒvçšÞ1gÅÿqàÀ’““«´ÄÄDfÍšÀŒ3Ân"‚ŸDau–ñÖVQ’*_Xùk¹Ê$̪Ê~Ö¦Øq6‹¥t¾Åb)ó×}™’÷%Ë—¬ãNEmBýÇápÁðá×cµZKÃB=C‚ÿñµR Ô ®«ÕÊéÓ§Ï:çòóóKÆeggãp8Ðëõ„……ZêÉmݺ5f³9hΛ£GÏx²è,’åš m\û…ÀýÀ.ÿv¿k—VSø¹ç´’Jô¬ìè®=´íݯÂer5//]ÆÔOʹ&"X!¸p:‰!êÞp Þµk+W®,3ÏáppøðaÌfséK§ÓѦM®¹æš =G¾üG«ý;xDîÜ„§#ZMáÇüÛµÍ>ª=Tz÷]HLløæÚ·oÉqe=À§“?öíáü”3YÁÂBMägYäü,IyO°  oÞá@"--´´49ØP\ Ó¦Á¼y®Ú¿‰À´±“‚ ˜—€~À£@®»_½Z«)üöÛpÅ ýû¢³ÑXf^AQ»2–ÁÙ Û¶m+¬)ŸPD°  ½NÏþý{IJj‰ÃáƒU&$ÄÀþý{ÑëôbŒš¸·s wôÐ熂ÓéÄn·c,w#YìÜ©%¿ÚTR?u00ˆ“ã$!×p¦¦ðFÿv}ü8\y%Œ3gBhhÃ4Q»víøøäñ2ó"øî’KË̳;$4oF£FÈÊÊbß¾} ( ªª¢×ëKó+ÄÆÆd~Á‚`èt ááaìܱøøF˜ÍfìvÂB~ òóO³sÇvÚ¤´D§“¢£þ¦ÄË ¡Ï …ÌÌLÖ­[Lj#êt?Þ~|òóPà àN¤ö¯Ü$ËÑ<ïà÷šÂóæÁ÷ßk5…ÛµkxæQ}t$'s²iíýÁêû߯áÚo$11‘Dqàî™öwíÚENNNiîEQ'66–¸¸¸€‰8, íé]4±1Q|³æKÚµïH«V­Å0B¥Ù±#ƒ¿vn§y³Dbb¢ÑëÅ\ÓbX@«ÿÛ¢|ú) ƒ—;Àþp±Ku±Û5ÛÖo>DGáÁ|:Nƒ±gõ÷YY ×4ã§Mቱñü­wcòѲuï.N†@·ݽö¡Ó鈋‹#.ÎsØÉéÓ§ÉÊÊâèÑ£X,ââþI("X€°03-’š£èöìÎ`Ëæ b¡ÒDEEÒ¢ESš7oFX˜Y "µvíÁ Aðð4Pˆæ ª‡Ñ¨ÕŠ ¤T&Sà ݭ.»ÃSµ—Ëé]JDDŸ{š©3žf`Û4ú¦OtD;îç³?Þª¦M­Ö6ÂÃà '))É«P,B­£Ó鈉Ž"Ôh$¡Q<……’X¨‡Dü Bí²x8$¦¨1Ñ("X§Ó¼ÛÁŽ ¼ ü,Ú‰IJÃû_z)ý/½TŒ!"XDÈ‚Ð0dYµ‡ÍfãСCFX§è‹ #6®ÒºRÒ%„ƒ¼œOlÙ²…ððpš6mJ—.] «‘mµhÑ‚n¸¡fD<ÐßõüÇqàÚJ®SïÇÓæCÐ\™å¸vÌŸ/ǽª¨@ ÖÌ3f¿ÿ>Å%€ÿ>F{ Ü¸‰üü‹xçÍüýïÛéÚ5MNÁ‚˜œÊ²°uë6róòQÐJ%©ª*åï¹ÿ¢A—.ˆ‹©ô¹g->‚BbÓÖ4iڻ͊Šw¬ ÃbDAG~î1¬Å§1…Å͵*c+&,,Œ®]»Ò¿Ö ¥N¿ÓèüQ‰uJK×Óõ-°ÇsÓí—Èy$xfýúTµ™Kø¾…w ìE+Â6™Ã‡ïáå—g³l™ˆ`Á‚€X²sؼe+‰MIMm+*MNN›7o¥G÷󉉩¼ -.ΧøD~·.µ­ëä˜çæ²g÷žÒã/ …++)‚A ‰®·"øsϳ#"àŠ+äx gsàÀNžláz÷0Ê%€Z6 ãÀê 7*,,dûöí>|˜ØØ(Á‚ ÔvïÞGj›Tš5kŠÙ&ªð#W@DD»÷ G÷1HPÛâ‚BöîÝh!ÈÁÀñãÇùå—_¸úê«å„kà"øÅ*ˆàzù¬§Íì+®³$żâ[Ês‡ÃQ­­X­VöìјfgF“ˆ`A ‰‰MˆˆˆcU"""’ÄDgÀüÀ5Á[×$&6áºë†°bÅçkë#GŽÐ´iSMo‘™™YÚvòäIbbb0ä–¨!Ñhì¬Ä::–`Êj­gæ[4‡®¿^޵à™äädqò$À-h¡ÐîáÐ!@PLddkÖ¬ ..Žääd5jäó¶¢££¹ñÆøã?Â~ò/#`"Ä,T_˜™ÃˆŒ”)5·šÀæ0ñÀ×ï¾û.]ºtaÈ!eæ?~œÇ{Œùóç‹n€\Ì­ôw\=Áÿõ<Ûd‚+¯”ã,xçâ‹ÃÉÈ8€ª&÷óÑÆ§¢UK†æÍ_cæÌûéÔ©=‹…ƒ’žžŽªªFZ´hAóæÍëm™9ÁBÛœË!Ÿçû£oA‡]»÷òÖÒ¸uÄõtêxžÄMû2/iß¾=mÚ´ñ[“'OfÖ¬Y(ŠBß¾}éׯ'OžäñÇgΜ9„‡‡‹Ñ C« ‚ë]H´Xí¹éò˵1Á‚à… 'ðÛo“Ø´i†KøN.Óþ-#G†” `€ØØXbccÏœ‚V+™™™üüóÏØl6E!!!¤¤$¢££E ÁKŽåÐYbÕ_âU° 6ÇŽàï7ßMfæaÞZú>lý‘„„x1 Þ½¾"„Áh4ú½vï”)S8r䤥¥QPPÀìÙ³ú&/ÐICKÿ³·ëÔ»èï<ÏM -øò]ùÝwÏpç³ùßÿ’9uª/Ѓa3­Z­a„ó¹ÿþûÏÙGJJ )))¨ªÊñãÇÙ¹sgiÂĈˆ’““ILL,ÞˆŽm~–Èõ&¢=Íìß›l÷j)Ë|,^ò÷…¢œÉÀ¬ª*‹½ŠÞ çþÑ£jdÛ…Ü?öQ ‹ˆ`̃“ygÙBÌf“\›®k±dZ®Oÿ³}ûvV®\ hõ€o¹åvïÞÙl¦Y³füüóÏ|ÿý÷´iÓ¦Æê 5ÇP`Q%×1™ê‘þ›0ÉÝ&øBTTË—?ÃÞ½{ùïdçΕ Òƒ‹/ž\¥áMŠ¢Ð¤Iš4iR:/??ŸÌÌLvîÜÉ©S§$;´<¸ ÖŠ¼ÀÞÄ®/ë×”·9˜q¿±–ì ¼nss™8Q ²[?þÁÒ¶9sæñÄ“OpÛ­#ˆŒôoÌÃádî¼% ªœß¹#wßs/o½ù6›ù/¿ÆäGÇ¡ÓIY$o׫àRSS¹ë®»Ø¿?‡âÛo¿¥uëÖ¥ÞŒ®]»Ò¾½&Lcቫª(‚ssëÁÎÛ€/=7 1’"@¨­[·f̘Ö5ÒwDD:t C‡¬]»6 쥓SF¨Š®hnO„ª͘1c˜:m:ÿþ÷§|üñ'¥xܸq~À+V~InÞiÚ¶Må…çpí5C˜5óRSÛ`ÉÎ峕_ÉjN[ØlüùçŸ|ÿý÷¤§§“@Ÿ>}ÈËË£M›6èt:8ÀÚµkKÇÇEÈàËÉù@R%×Ñë¡^<óø ðòÌË•ˆWb²³³ù÷çŸsß3ÏðÔ¢Eüò믨ª*†,Ôú µ›'÷\Ë•¼*#¢KÚ=­+Tñ.3*/Íy‘#FǤG§2þ+L6øø8n¿ý6f¿8Ëÿç\a!?ÿü7ß<‚v©Í)<Åyçµâï7ÝÀúŸ6P\l úëÒ}Z®ÓêQ\\Lzz:_ý5ëÖ­#""‚Aƒ1`ÀL&?þ8 àðáÃ|ðÁ$''³e˾üòK1^F®¨Âzõ¢ö®—ªd\sÛ`æÕO>áÂW^ᦄ^Ÿ8‘Æ1èÈL˜À¾ýûÅ@~@¡…J às‰ÙŠÆøúN-áÐþG¡ƒ\«6Þx} =4žŒŒ?Yµj©mZ“–Ö‘yóæátú_ˆ†……óÜŠŽž=:Qä:ïŠ sù[ß^Ft:¡¦Pâ§Úî×c°_›~ø!#FŒ¨ôzEEEìØ±ƒãÇc4i×®;w>k¹eË–1wî\"##ÉÉÉ!× ;eÊæÌ™CŸ>}ˆ‘ØÓËUÀë•\§ÎC¢€—€˜~ý %\…ãÅwÞá™fÍÈŸ>Ýý‡•üæÍY{Å 6uÓ¦'Æ,ÔWá\U¡-‚ÿÐé`öœ9<÷ì3œ}šŒŒ N:…Éd¢C‡téÒ¥ÂíM™2ÅkÛ#<"_ œî@"p´딄DÛlu´Ó¿'<7IVèàåà¡C,>vŒü;î€âbø!çýõ–v Fq·nüùä“Ü3{6ËŸ~Z &"X¨i1[^œzó躇K»OûâI.j-‚Ø¿7Úâ fȚ&>2‰ï¾ûN  Õ×èØ¢¢ÂÜ Úrä@™pè`cùòå˜L¦³¼¿V«• &0iÒ$Z¶lY:?//?ÿü“ììlÂÃÃéС=zô¨Ò¶5jÄàÁƒå th!ÑË*¹žÉT‡"ØK(´NÇË1 V^ýÏØ?r$­–,aÍ5×ÐfäHœN'>ÿ<‹“’ Q#¶˜La2™ÄhÕøÞ„ ñ%t¹ü|wÁëiي滯/T³¼p"€ƒV«Nbc"¹ñ†ë‰‰ŽäõäútLÜu×]äççóá‡0dȬV+<ò'N¤eË–dggóóÏ?óõ×_óÇЮ];Lß¾}‰¯z­ô¬}I IDATé°°0Ú´i#'`€qeÖ©3ýàÄki¤¾}!€J± •dë‘#v;½ h“’‚Åb¡°°gx€–ï¼À‘ŽÙüûïb°j ž`A‘#6Î-„íöb9—ê‰.?lžáQ£FqàÀúõë‡Ãáà¾ûîãðáÃüõ×_ÄÄÄ––FTT”œ0Â9¹hœ¬ÌM°A{Ùíµ¼³›ñ»}Ýur,ƒš’“Q§Ãætº&ulÙ²…ÇSèúÖ9èõz±—ˆ`ATÕ!Fä\j ¸Mp÷ºÎÌÌäƒ> 44”ñãǰnÝ: )((`ëÖ­FèÝ»·œ,‚Oè!Àû•\Ïd‚üüZÞY/¡ÐŠ"ヮII|~ä4mʆØX~ݲ…Þ]»Ò³gOî~úiŽOš@Ìo¿Qœ€Åb!66V '"X‚—ÐPv›zQüPh¨ØlvŒFùi¨-! Â×ÄÄDFåºáWJç÷îݣшÍfã’K.ÑDx9„JrUD°Ù\"ø¿žg÷êIIrƒ™±7ÞÈǯ¿Î®Ç'sôh.[¹’Ο}Æ©ÐPvÞuÄÄ@V—†„еkW222°X,„……qÞyçUk¨ˆˆ`A$ ÈÉ=EHHcô†27˜‚p.TUÅa·‘›{ŠÆ Õ¯Íñæ[Ú­èÝwÝZæ\TU•×ß|ƒÞÀÝwÝÔ6Æ0hƒÁPêµ(..æÿþïÿ¸ñÆ1òå—_2\² Uä" È®Ô9YË!Ñ¿=7ÝpƒÃ†ÊÆMyã£7|Zöo}þÆm×ßæ±­IãÆ<Ú±#“>úˆœ[n!wØ0~6ì̹¹t~ê)^}î9"""èÕ«ddd°iÓ&BCCiß¾=Mš4‘#"XŸ°03:deo°P%l6áafBC«—-&77i? hÙ~¸ÿ®Ò¶¯¼Ás3çpÓ߇Ô8XŰÕjeüøñ<úè£,\¸±cÇ’““SåšÁ‚`@ ‰þ¸’ëÕjHô½7I(tÃ凟 é?šÛâÜ¡ÉßÍüΫ¸wøp¾ù†Ç'Mbw¿~uë§NÿÓOôÚ¹“÷Ÿx‚ˆˆˆr÷€atïÞ8S?ý÷ß/­ŸÞ¬Y³*6UUq:#"X“ÉŒÉdCuJtt4cÆŒaÑ¢EÌxúEš7oʵ×\Á¿—¯,ÀãÆ#"2"h³pë˜à^ýuyäZ·n À¡CZE€;37ß|“íÛ·“––&“Pi®¨ï"x•çÙݺAJŠ?AcØÀ ¾ðB6nÚÄêåËiOÿþýiß}>œÏ¦ÒÚéV«•¿þú‹mÛ¶a0HMM%))ɧhÁ×–/çÃßçˆÓ‰S§£©¢pEË–S]BD° ‚àWTÕÉÌçg`±Xøê«/ybÆ,>Ê«¯½M||C‡å¹g§SxúTPÛ©>x~÷ìÙ˧Ÿ®`÷îݵ¶Ý±cÇzm»çž{*¯+V­¢cÇŽ´jÕꬶŒŒ :ÄÀåâ úQ@n%Ö ½5ðO`ç&ñ«§¬¦yT󳾇®zí*Œf£Ïý„……qÉÅsÉÅWy_ŒF#;v¤cÇŽØívvíÚÅš5kÐét¤¤¤Ð²eKtº²s×MŸÎ7—_Îé§žÒfž8Áî¥Kùé÷߉ZµŠ'ÇÞ/"XAÊSTÅ’Å/3qâ£ddüÉš5ߓڦ5ii™=û… Àu-~KHJjÁÝwß Ào¿m®³ý¨î8àòðÃ3yòä2Bø¯¿þbÑ¢EÌ;W.Ê Á –Wr=“ NŸ®á“Pè  6*–×f¿VfÞ´ç§á´9¡ƒõ :t C‡8öîÝËwß}‡ªª´jÕŠÖ­[£×ëytÁþ;r$ö¶mµkjãF ¹¹Ü{/EÁÒ±#ä5øã¤“SUAð?*Ö" ³^˜EŸ>}HMmÃE]ÄÌY3±eRŒ¸<îaѵEHH111ÄÄÄÔi.ê–B2™LÌ›7þóŸgÎ@Uåã?楗^’< AÆÐ*Cµ°c^J#¥¥A‡rÜê N§“{o¸—[¯¸{52¦ÙívNœ8Q/?£^¯'55•2`Àt:k×®eùòåü3?_ÀÙÙô}ôQÞ2220›ÍôêÕ‹°°0@ËŒj4Å@AÈ•ÀÊ*ˆàìe’É€ö°î›o¾¡S§Nõ®–oß´4B32(nÑ‚=))Ó¿ísåç“)"X´ú² yyyBä”Á;v›ð°0BCCÅ~Ãîˆ0®6›ôôtŽ;FË–-iÕªo¼ñýúõ+]fâĉLœ8ÑcÖh!°„•ñ[ ÓÓߎº@ºç&IˆUÿpüÙëO ¡ZÿغÒB¸ÿ“ýϚיΥӱq±ôë×ôôt6oÞLrr2:t8+Ks]лGZÌŸÏîAƒ8vË-ü}æL†µhAtXgd°ûÁaãFÁBÃÄá°ʼnãLj‹Án/Æd2ùÞ(‚àoô†Nž8JdD$N‡=à?¯·ÐgÂç&;;›-[¶`³Ùèܹ3Ý»w'##ƒE‹ñÒK/•YvöìÙ³F O8p)ðe%×3™  ÀÏ;³Ê{“ˆàú)€ß[ý!!!ÜvùmUÂç`ݺu`ÿþý¬Y³†ˆˆºvíZÉRDEE1²iSžÛ²…®]ùíé§Ùtຂ·Ý€r예`¡ab³мE+vÿõ' ‰^ª*ã‚A¨mTôdgç’}Š”6°Ù ƒâ“—»"€+fïÞ½ìØ±ƒ¨¨(z÷î]æFÑáp0wîܳ²@—dÞ·oŸ0Z_D°—PèV­ G9NõQGGkI¬Þÿê}Ÿ„ðàƒùøÓ9ÉÉsnwØ€aeÞ·lÙ’–-[’››Ë† °ÙltêÔ‰ÄÄÄ:±Ë´{îaÏÌ™üg÷n²®¿59™…ùí·´øä¸b°ˆ`¡áa·a2i“zGfrèðAœEÑ¡ètŠ¢M+Zf;EQ\c…tŠN›Ö‚¶Œêº™U”ÒûÚ3Y×­Š¢‚ª‚¢¨¨ªŠêV!åÌ{µ´zŠêjSJ–9³ªª-û2Z“뽫UUQU×6Tµt=mZ9³M×6´÷ªëÝ ª‚ZºÃªkZ9³¼êÞŸÒ T×9¯(%'yÉ5¨Jé{EÕ®%íTt-S¦Íõí"R”Ò©3ý hóJÚ¥ôï™u´ëQqµ•\Ÿ%m% –ìã™M–,ïú¯LۙϨ–\Ѐ'Š N×Ū:Õ’ËBË­:QQpª*8UTT‡§ê,½æªNO¸ÙLJJ[t ÛŠ‚N—Ì!\›ÍÆÖ­[9vì­[·fðàÁC;vìX¨1ÑAêÏ%ƒÀV‰uBCý}Øä¹iøpÎÜ7 µÎŒI3`%4¶6à§ÔŸX¹z%ÑÑÑäää°gϺvíÊû_½ÏåÝ.gÐÎAÄ9â°¯¶3áÞ ,X¶àÌwP‡Ž<3õ™jíOTTýúõÃn·—†J·lÙÒk¨ô_ýÅþýû}ê»k×®4jÔÈ·ûEá­iÓöý÷,™>ÃU¥¹ÁÀM½z±¿OŸ€8þ"‚ƒ”¢Â=Ä]&TET¾­äz¦P(ðW@Ê*¼–E—ÒHuË“`ä×#i¼­1FŒ´?Òžg}–Ù¯Í&::š„„V­ZÅšÿ¬!õX*vììi»‡ÅÓל8s •>pà«W¯&""‚nݺ•‰€yî•ç~ÎþòN䑾3‡x¸Rûqu¿~\í–c¡ôáÁ¦M"‚…}ÍZ€ÍZ ¦ª„{ÖUoÓ‚ xÆÛ˜à`÷ïÙ³‡;wÅ…^ˆÙl–“E¨WUE›ý(‚½„B7kâPkäççóüô'h–”Ę‡Ç£( ‰‰‰,[½Œ‘—¤û¶î´8Ý‚Ì2™Ä$f¿6›-Z°ôå¥Xþi!%?;v6vØÈâU‹iÒºVö;99™äääÒPi«ÕJ§NhÚ´)¡¦PÚõkwÎ>,™øAÎÁ‚ øEïÛöí;R\”ëqZu‰¡¡Uðz¯žÚ¬VkiÈsJJŠ×gA¨ —»nv+“nÏht ©îø¦ãÀ¯ž›®»N »jžß6l`Ù‚…<~Ýœ8ÎØ‘wó䋳hܸq…B82*’½oì-ÀëZ¯ãÎÉwQëŸÁ=TzÛ¶mlÙ²…ܼ\VkŸZK¸¹¬WØØØH÷QÝå,‚¿0´ïЉP£Õéyº¨PD° ›xì±Ç=z4íÚ•õPdee1yòd,X€Ùl&++‹-[¶àp8èÒ¥ ={ö”Cð;±À…TΦ(Z‚¬Âêzƒ¿Ä•4Á³jUUy她¨GO²àî±è…ĸx:$%óä#S¸â–¿3øŠ!…ðá“mȦUN«RðÒUKIJNbãÆlÙ²…>}úYËus ]»v¥k×®|øß°ÙhnnÎ’—–”Yv䤑rˆÊc2Ga·;8vôùØíN¢%ÆPtèuZB3I±´—^Ñ•ÎWQÑ¡ èt¨ èJ’éè׸?\c޵äV’K ‚-1ª6Û©ª®ëÁ ŠŠÓ¡%¼*IŒUò§v%9œg.f§ª¢º²Í8œNm_U #<"‚ÄĦèõzŠ óúÜ ôPèY³f1qâDzè!RSSËà^xdzcÇbcc¹è¢‹¤<ŸPã\Iå£Aý"‚½”FjÜ.¹DŽKMrôèQžöw\x)½zõéª,þb­š0´gæÝýËVÁŒÿýiOÍ8K7;Ý ÀctŸ>}(**â—_~à‚ .¨“ï±0ó™ñÁ§²Nñõ×_Ó¹sgš6m*'€ˆ`ÁãA1áp¨ìÙ³“Ȉ’“S0™#PtŠ»*-‘{’Kc žÄXø%1–ªRTT@VÖ öìù‹””öBLØ4CtEÙŸ%3´Ñh䥗^b÷îÝ¥ó, 7ß|3?ýômÚ´aÈ!ò,ÔC€ixuÊz$4´š!Ñà'ÏMÆ^/Ç¥¦Ø–žÎÒ9óyþæΡ“'xþÓ¹çá‡ØñçŸLÿpÝp+#/»‚Œƒû}û¼²ôͳ„°×1À&“‰~ýú‘——Ç?ü@dd$={ö¬Tý`»ÝÎ/ë¡ï%}«ý™›4n€ضm±±±òpQD°à‰cû÷î$22‚æÍ[c³RT”ƒê”:Á‚ Ô.Š¢Co0Ò´Y2Ê‘ƒ:´–-Û¤>—È ¤IF£‘óÎ;¢¢"~üñGœN']ºt¡qãÆrÒ µNcàïšÔËw“&„‹ªúUô^"_½“šD§×ÓµU Qaáü¾gïn\Çì×—F·ÝÙsÑ…Œ~t ‹G£CRKbÃÂKs¸ aìœ3 Vdd$—]v'Nœ`Íš5$&&Ò¥K7ÇŒw|ÇÕwókC§åG¨¾¨s…J "‚/èõróòèp^'¬Å§±Û‹Å(‚ÜJ´ŒŸWõÉáÿ¾TÕ‰ÝV„ª:IhÜ”ŒíéèôòÓ07}ûö­Q¯Dzz:;wc 2´’"Àl®†ö  —^*Ç£&IKK㣠58¯S'ÂÂÂ8|ø0:Ž””7Æd ¥ÈjÅY&2%11‘w¿}k±•æ-šû´Í„„† Bff&«V­¢mÛ¶´oß¾BÜhu#Ú9Úñã ?ÿÏÞ™ÇEU½ü}Ù÷MÜqCQAÁ5wD33[ÔÛlÿ~-󧶘i™–i–•ß²²2µÝVÅÝ„TÜw”}Ÿ™óûcfpÀfØ^dädàëljÃGJÄíÙ³gÙ¶m¶* ŠÂßûv1þn£¢ÖTlHË–-3f Š¢ðçŸrþüùr°‡Ú€iZ!üÉâªå¶u²eÈûC¸ÖëZ©Gß×úÊ AŠ`‰D"‘”úò·´¢°HpâÔ%þÝ}€ÿDóÏ?QìÚõ/Ñÿî'.þ¹y*,,,ëÔ–¤qà™3gÒ¹sç’²víÚ±lÙ2Nž<)HrKh˜›,F?%Úl6ÅÆ«dTèºaì½X¿' k[ÒÓÓ±°° wïÞ\8{Žð ¢NŸ XX¿C‡Œ=šk×®±qãFRSSoÀYJ}"ÕÂnÍÝhâפÔÃÑÃQ^ v—Ü„W³€›Ê®^Ž¿¥ýÑ¿¼×uu|SÊ%’z/€-,).V8›HLL ÅÅÅøú¶¢mÛ6\¿žÎùóç9{ö,¹¹½è€½…6t-Û’Ô=IIgøõ×ßJ²ª‹-bÑ¢Exxx””ÙØØ°dÉfÍšÅÛo¿]náª`x‰¤"FÌÜÇή S¢ÿ6^ìâ#FÈóPtéÒ…¯–~ÀäCYôòÌçÔÊš'N!¯°€lU¦Võ)ŠBpp0*•Š˜˜æM›GǃKð™þg˜õÎ,æ?2Ÿ^I½J„0˜65º…W ö¿½¿Òv……<ñ`µÞKjj*_~ù%ÑÑÑŒ3JŠ`Iã¤!ˆ;)@%’ªaieË™³WˆŽŽÆ·UKî¼3‚ÂÂB~]ÿî­­-ÿüÅž={ððð  C E&Ùê׿>Þ7‚åää½Ç$[Ê_ÝŠ Xþþm˜>}GÆÖ˜6†^×43fÌj‰IDóª ‚ÍŠ ì(G„®¢gYR%Üšy“žŸO3><õæß*öíæžI‰ŒŒ$((ˆ–-[ÖZ_>žÿq)œØ7‘5‘kpppàßaö½³ÍÂs^žSgcÙ´iSfÎÔþ_íÜaRK(†žbC!ª÷Œ{6l[v»"»ú2ú²öË–Ud»ì~¦Ší²û”=vEãQÑvyïWŠ}ImaaaIA¡†ã'Nàà`Ï]w&''—É“æø‰üòËz¾ýæ+ èÇÕÔ4ðm僃a®lã¶ÂÂúÒ?lË>XÂÈ‘ÃKìFÿ¶ں݄pyu’ŠÉÊÊB­Öf2prrÂÚÚµZ¥.ßLFFFɵåììlVºIãÇè5cE(45†èV Ïñ„ òÔ%ãî¿i¯¼NçÖÆ£;H:ͺÙÓ±°°àÀ$&&Ò¿ÿýÞ06:¡OOÎ~’£GJP * a‰Á’:Àæ ½òÄoES›õÛå‰ÝÊúVÑëºs÷«Š-‰Ä KŠ )))tíÒ…¬¬l|ðÙ³;Š©O=˃MáëU_Úƒ¨¨Ý©qt°B¨UÚrsseé’Å<ûÜ ,~o_}õ5ŽŽ<ûìÓ¤¦]«ÐÖí&„%Uã§Ÿ~"55€»ï¾›ëׯ“››ËСCXµj…:µ2yòäZõìH&fŠ`ÐzƒMÁåL…vt„ðp9þuI×®]ùpõ×h4Æ—àØÚÚ–Lƒ !##ƒM›6ѵkWZµjU#}8qü×_£ƒºg›œeÊ´)Œ3†´´4NŸ>M§Nê„wwo2Îdà.ÜñKóã—o‘"XŠ`É­»zÌõšš³]•c˜zìòÚÔ´® ¤Ø•Ô™V,(*.¢¸¸˜¦^žüòëzNÄǵgg'>úh)ýûßÁÆ‘ 6 kkk Q›Jm©Õj†Ì»‹Þáù¦Ñ©Sß}÷ VV–¸º¸ThK"1…Ç{ !GŽ!))‰ÐÐP<==Kê§M›&IR!c€…UÁ™™&4,@ë 6Bx888Èñ¯ ²³³Q©JÿÐjccƒ£cÅ¢ÜÜ܈ˆˆàСC$$$†u5CywíÖ•g?|–¯_üšn»Ñ5­+_Oÿ{FÞ9²äûkÆS3°Úh…»p§ˆ"v9ÈêÈÕòdJ,¹DÙ­§•‰~cÓ¼«Óï²bÜØôm)Œ%µ‚8::àììLVV6cïÃï¿ÿÉÓÏ<ϲeïóì3/гg(Æ %77GGGœŒ³*cËÇ»)¹¹y¬Zõ :pæÌvìØÉ°aC*·%‘˜ÀÉ“'IHH ((ˆàà`vîÜÉ AƒäàHLÂèœ0c íZÞJ½Á;Ю 6‚œ ]û¬]û7_|±ŸS§lÐhJç¶²²Òе«Š3Â8°g…vºwïNVV›7o¦sçÎøùùU«_cï P"„»_èΧÏ~ ÀÈ;G2ã©d|—AËÜ–%ø«Í_áãã#OªÁ’ÆNMË[%ú«ºoEÓÆ¥–Ôju1övδoßžääs´õ÷ã+>fêSÏ2pàPBCCøô“°²²$)é ~~~8;9¢QeTj«y3o}ô ìXµê ¢¢¢™öât–.YŒ»{“ mI$qþüyŽ=Z’vÄ7J,1‹3E0˜8%ú/ãŶ¶Ú X’Ú#--Ù³8{¶ü`QgϹsopð`’8åáââBDDGŽaÛ¶m„……acSþ,¦×®ÃÝÃa#G˜%„¿üôK\þq‘ø óKê¥ ®¶M ªeªýú8%[r{ „KK5]»t!;;›¤¤d¬­­ùô“xýõWøxù2¬¬,¹’’JVvmÛ¶ÅÊRm’­ääóôîÓ«ÄÆw äýÅï’“[©-‰Ä©©©lܸ‘ëׯAûöíå HªMUô¨]% Š€ÍÆ«FŽgg9îµÉ‚«9{vb¥íNœÉ?n4ÙnPP={ödëÖ­$%%ÝTŸ““ÃôçžÇæÜÎþ³‡7_y•âââr…ð#Káh íªôîºÓrCK)€oÒ,©’4Ü®i»ÆÖíVÇSZ™íÚŠŽUÞ~Æ¢aK¯°¤¦)ÌÏÂÃÛñãdz~ýzÎ_¸H»¶þ 6ŒÌÌlΟ¿HvNÇÇÃÝ™Âü4“mõèJzz&öâââŽbaÍàÁƒ+µ%‘èÉÊÊ"&&ggg†^¡×¦]»vrÀ$f´ÌÉŒma¡]T^†·]@–ñªñãå˜×6‰‰)@ÓJÛ·$.îˆY¶5jÇŽcË–- 0[[[î?ÀŠÅK˜3a2-›jSž8—̳OaöÛóñ÷÷7*„á†GØ)€¥–Ô'¡[•zsƒbU´]צl×äØTdÛÜ:)x%uF£¦ ïÍ›y3eÊâââ8}ú4ÅÅÅØÙÙѱcG:t耭y¹W+LgT“¶$’‚‚bbbP…°°0ì*u¿Áã?.Nb6c€efîcgW.g*´µ5Œ+Ç»1еkWüýýÙ¶mû¢ÿÅ&3—OŸú/–8}'{{:ùúñÑcÏñÖÂÅtØŸœ\¡¸ °ÁIíPÖ+…¦D*U!9Y—qtjBŸÞ½ E…¢`a¡PT˜Cnö“‚XÕ¤-Éíz=ªØ¿?¹¹¹ôîÝ'''9(’Z%¢Š"8˘·WDßgèPpw—ãÝX(**â÷u?ð@Ïþ ŒFÿú=Šw232ðcʰQ¼5ù1þˆù—ÿûÏ™ÿÞÞ IDAT»ØÚÚÂKßZÊÚȵRK,‘ÔèÓ]½z•š4i"ER't|sfìciYΔèÝ@ºñ}î¹GŽucââÅ‹„4oÍ ®Á\ÏÎâ…ÿ-cüÃqϽÚðßþü‹ÿ®\΢‡§rg¯~>“@nnîM"X/„õbXR÷ÈÀX‰DrÛ‹a5……9ägRT˜[-ÑZ“¶$›S§N±aÚ6mʈ#¤–Ô9cª°Ñú•/šïºKŽsc¢K—.O¹À¥´TE`×.lÚ´‰‚‚FMß–ä®*ÂÃÃCœÁ‰D"‘Hng.\¸À† °°° ""‚-ZÈA‘Ü"jBk€r‚ M›Êq® ‚‚| &œ¿BC«e¾I‹æ\Ï΢K›¶Äî?H@@ ÀÂB+«Ò/_ÁÃÙ…ãgÏÐ%¤‡<9RK$‰D"i¬Ìž=»ÂúÔÔT6mÚDzz:ááá2ª³ä–Óhfæ>––Ú`W%ì®o+£B×Ó§OÂßÿk ¢à‹Å´ný;Íš¹TëXã¸õ{¢ð²w$-- {{{lll8qâÍZ°~ß¿Ü5A·¯¯È5Á‰D"‘4BìÜÈÏ˸åýÈÎÎfïÞ½¸ºº2tèÐ ÓU…Ÿ~ú‰ &­ûý÷ß ÇÆÆF^’›PÐæ ^iæ~vvP’ öoãm,,¤®K\]]Yµj8ï½7—¸¸B4eVâØØXУ‡sæ<‹½½ ›6mbÈ!XY™/…ùâòÜßo¯M{ 7WWÒÒÒX8Y±þZQ\æ!E°D"‘H$’ºÀ·}º# XkBÔÆÆ†%K–ðÒK/•*_³f 999RK*¤ª"8;­Ó±Ü·/4k&Ç·.0 ô2©­»»;6l`àÀ¸ê¬9x¶lAZfm›·dÅ“ÿ¹©>þ\2‚ƒäI‘"XÒB V«)**¾¥ý°±±ÆÒÒEQäI‘H$’*Ÿ—QçBxÆŒ€6ÝÑÁƒÉÍÍ¥gÏžµžîhìØ±üþû籠°^O:U^ ’ é xQîŒfã7ÏVÚ‡j/pÙxº~ãêêʨQ£Ø¶míÛ·§M›6fí?~â¬XºœQ¡}ŒÖ¯ÝµYKÞ•-E°¤¾£R©Pk,ñlꇽí½yËÏ'-õVª*MS‘H$IÝãîîÎáÇIKK£GuÕPûøøH,1 `ðµ™ûÙÛCv9^`E‘"¸Aˆ ++FŒÁþýûIMM¥W¯^&ï@ðè‘$åå­h"žžžr¥–Ôk¬V“™•O‡€Š‚|}&xí\Ÿº|k;Z´ìÀ©øý¸º:aUfýXCñXׇ~J¯ºD"©i®\¹Âh×ÃÙÙÙ1wî\vìØA@@vvv¼û®ÖâããôiÓê¤_cÆŒaÒ¤I°~ýzy¢$&3º "ØÎ²ËI ¾¾r\ ¡¡¡œ={–ÈÈH† ‚u©Èg|猽SžÁ’†LvV6Mš¶FhTdßòþª‹±µwÆ£IK2®ŸÇÝýÆ”>•JEA¡Š&ž-ñrr½µã–Iúµ‹ØÙZÞä±®/ý¬¨‰äö$&f¿ÿ¡½{¿~ý:qqÇ8wî@êxûí›ëdT膉‹‹ ááálß¾¶mÛâïï[G@@€Ñ4õ³¤–4h„ÐPPXˆJUŒÐhecÊß"4ΛZˆÀ¢Äc]PMnvê-ïcAANÎ^7y¬ëS?Ëë£D"iÔ‡HzæÍ›Ç;ï¼SgÇ»|ù2àâÅ‹øøø”Zl,X–DRcÌÁw™÷Ü,‚ƒƒA¦À®{ŠŠŠB`kk[É}¯ ''gggãÂÈÊŠáÇsàÀöîÝKïÞ½KêöïßOFFéï\EQèÙ³'...ò$H,iÈ‚X+8ëC_J';7ôX_MIBSOÄz~^:ž^~%k Þõ³l---äÅ.‘H,*•ŠÝ»wcccÃèÑ£Ù´i#G޼©ÝرcÙ¾};EEE2M’¤R ˄¶€?@wð÷‡¤¤uÒ |k8Ǹ§žâÙÇcÆcÝô™BðýŸ2÷³Ï˜8t(oT« $$„óçÏIF†%K—îæØ±žäå5-ÓRCÛ¶_–ÏW_Í”1X¤–4P\®vóhe–¥Œëç«ÙÍM]ï±Ö¨ÕõF«Õe=ÖÔ»~ÞÜG)‚%’ÆŽ~š´½ƒ[½òW—‹/røðaúöí[}Ú˜Ö£_§\«V­b„ µžÊIR¿±F?™Ðv´Áë{î÷Þ+½-©{zvïN»>}x£woÖΜÉý:0óñDZ±±áÇ¿ÿæ½?ÿäxD­xùÉ'M²ÙªU+\]] }ƒÓ§—–Û.1±'/þËwnâž{FÊ“!E°¤ÁIà2Þײ<ýÂa“ì¬ø(¸äxE¢NFS?Ö.«+èG}駺žŒ•D"©;n…ð3gN­ÙV©TDGGãèèHDDDz[®\¹‚Z-¿'%Ú)Ѧˆàƒ×&ÜÁ:AçÎroó'Mb\l,qK—òæÑ£¬}ùeU*NŒEîÇcqú4ãptt4ÙæÁƒÇ¸xqh¥í ú±rål)‚¥–46!üôÓO³â£àJ…ðŠ‚yúé§+ÔÕA£V£Q×O°¢¨ë}?+ê£D"‘ÔµëÂ… 9r„þýûˈ­’ZeàäTÐÆèd°Ý³'´jçÏK/ð­f`Ÿ>­]ËŽ‰QwëÆ‰?,Ußá³Ïx}Þ<³l^½šA^žiß;ò4$ä¼H‰¡.·æ·_1ÙŠ9m+PãåV©ÕšJm;„šÔ®úuµúy«û(‘H$õ•JÅŽ;HIIaôèÑ B;v¬Fì¨ÕjV¯\Í¢W‘••%/†:ÂRI›Ñe¶冸•"øÖ3oâDš¬[w³à9uŠq­Záàà I"E°Ä˜å>ôÞàòÐ{+²aú£|4u¥ø¸ÝtèÔˤ¶ÕzTàé­õc×@%‰¤>rîÜ96mÚD÷îÝ i0ý^³fMµmäää0.hÿ>ó/© Ry´Û£ìܺS^uĘJê#Œ”¯,ÇïVÓ'$„¦‡ÝTî¶c“‡—$)AN‡–h`Qá4æoÍbÅŠÚXðÖ¬™ -* Q¬ÇFØ—ã±Ñµ6fJy•Mígm£é –H$ ƒââb¢¢¢pss#""¢Æí¯X±‚äääRe[·nåÊ•+ØÙÙ•*Ÿ2e Ú;|ø0ÇgÒ¤I7Õ%$$°}ûvž41ž|L@\Nhuu;ÛÕKV3hè yÔC{ ßH]+ ÈHyÿþðÜsrìêK¾ù†Ä‡º©üú¤I¼ööÛ¬¯Ã4n)‚%ˆòÖßðרS“ƒ¢œˆû—Ný8÷o­õ¥ªýìØÏä÷P[}”H$’úBrr2Ç',,¬ÖrnþŸR©TìܹFë¯¾Š«««Ùö‚ƒƒ‰‰‰aÍš5¥„pBBK—.eéÒ¥fÛn7û™µÉŠ`*íâãvWøº6û(‘H$·š¢¢"¶mÛFff&µ&€ ÉÎÎ&22’’TKUeêÔ©äää”L…6ÀUÉO<î‰q\´½xc|(¢C¿•îwñâE^]ø*o-}‹k×®——ÇÇ_~Ì‹o¼È‰øòb3‘Ñf–XZÊq»Õ,ùæâyÒÒh=o-[Æ+ÿM§_Äòøq®<õÓ+™ÑX–;îèŸß~Z^§[7OyÒ,1›²ÞàºôÌuñq» 쫘5û»Auú·‡€À>ÄÇí)÷umöQ"‘Hn%‰‰‰œ:uа°0œëä˜çÏŸçøñãŒ1kkë*ÛÉÌÌ,Éï½÷òÙgŸñçŸrñâEÞ{ï=rssÉÍÍÀÅÅKUÒ˜ñcˆy"†c[¡ÎVÓ²_KæÌ«8ýÔ®è]üoçÿè:¹+ª"SNå­Çßbö²ÙM ¢eDK–ý½Œ1=˜úðTyáUÂ0À(2(ózÈ¡©·³rÇš_ºÄÀâbÞ{î9¼½½x%?Ÿ+WòCB;³³9rô(Aݺ™d×ÓÓ“¾}s¹ti?EE¡å `?¿…¼þúÿÉ!E°¤!¢ IU¹bz{þÿÝ´6øíùÿgÒ¾¦÷¥"O°¢u³š+„ï! sâ况~V6Ú”~Æß«ë×Þ›^ƒR«}”H$’[Aaa!»víÂÛÛ›Q£FÕÙq8€J¥bäÈêçò\³fM©Èͱ±±œ:u V­ZUªíc=FÓ¦MM²«V«I½Š:M“ډ̔Lr²s*ÜÍßkè9½gÉ¿Œ¾/õå¾»îãîÏîÆ£µÖÓÝéîND¿ÍT¤® g´é’6”EÔÈdImq$6–oo–MZ"~õØÛÛ3ÿ…x%?Ÿ¹Ÿ|ÂòÕ«Y:gNNN&Ù^½z6cÆüÍ—_Î&7WÜtÚ”×^{OOé –"Øøxàuàœb‰‘úÀ ÝæGBˆD#mþ ´Þ¼€g€H!ĆJŽ­·½C±¾ŽÞ¯³"»ÁÉ`݆†‘¢Ÿ~úéšw7VdNTAº|Á¹['2w×ÁpVÞO½(7èµ!Ö%‰¤¾@BB ÀÑѱNŽ©O·Ô¶m[Ú´iS#6Ÿyæ™’×»wï&::šçž{Ž€€œœœŒË2…u_­ÃòK‚5Úÿ±êÔ,c9ïû~¹ûä©óJ)4k;k )ÄÅ»ôÔòB )..®–üv!¢Œ-‡¤^Ú½;ëºw¯°½½=‹¦O§¨¨ˆM›61`À“bXXX0iÒ&M#ºQ—k‚¯“ùŠ¢ßcÿê÷•VÀ[ÀýBˆtÀW×¶¯ Çnªk[ë¡E±Ve1ðIƒ»ÌЕoÏ›aôu]tFQª·Föäñ=u´&Ø”¾ì­ôµ\,‘HŸþ9QQQFë–/_NTT[¶l¡°°ððð:ÀYYYlܸ‘ÐÐP£xÊ”)&{„ŒÍ¢E‹øé§Ÿ°µµ½i°¹ü¼òg¼4^%Û–X’|(¹Â}z¶ïɵ³×J¶Ïì>ÃS“Ÿ"î縒²¢ü"Z;µ–ØDÂkƒ›È^rH 666„‡‡Mzzº)‚kY_ióælCx¯G9ß7k€±¹Q='`“nû40›Ò?ÔÕ<€éß J›ó—v5´« fîeÚ_%?4 Ÿi\ ¡Ÿ‰DRÛ<ùä“DFFÞ$„—/_Ž‚ÜÜ\úõëG```õéìÙ³ìÞ½›ððpÜÜÜŒ¶ñññ1y½nYvíÚÅâÅ‹ùá‡JÁÒ áuëÖ™eïØ±c "Ý"½ÔfŸŸ ÷{bòXï°fï»{ÙùÆNÜbÝxé?/1Ôg(‡Þ=Äîww“úU*/O}™óçÏóöÿþg4ácÇøÔÌ>7V\}‡‘€Œ{Õ¸°²²bäÈ‘ìÙ³‡´´49 ·ã5PÇÇÛÜôb Ä‚ ÚÔl¿èDò8EQ\„Yû꽸›t¢:X(OaÍJàúE©ò\ÃõDÙ ¥þ÷SH,‘Hê†ùóçóúë¯ÆÒ¥K¹~ý:“'O®4çnM³ÿ~4M¬ÿ5Fbb"ùùù|ÿý÷F£@O:Õ¬›ë3gΓ“ÃÌù3yòÀ“dÎĶÀ–ŒN¼òê+îkggÇëÓ^çòåËœ9s†~ý´òíÁ òà„KM.((`ùÏ?³ãøqÖÍ™C“&MB0ïÿã£èhæ…‡Ë YÇ`§îYÒø°´´däÈ‘lÙ²…   ›ÖK¤®I¶êžÃÃäyýÑzy7ëDð´Áù~1h3íÙÍ:áˆvïoBˆ_ u Ýþ£tí·;ŒÊEqîAû#Ÿ°í”ëé@¡b^™ö£u"¾ì–!òuõý}¦¸ÞŠ¢¬Ö!65l‚ø¼õ˜“'¸Öûb¡Ôû~VÔG‰D"©-!üùçŸãëëË+¯¼‚½½}_¥R±mÛ6:t耟Ÿ_­ãäÉ“¤§§3bĈRåíÊä55PÎÕ«WIJJbèС¬Þ¾šÃsñìEFÝ5 ’•sèÐ!ˆXÃ)ÐvvvwíÊÆW_%ìí·y-,ŒUÛ·ýÀx§¤0yŒ”|zÂÑz[úÉ¡h´XXX0|øp¶nÝŠJ¥¢E‹rP¤®%D’¢(gt¢·ì÷Œ@ëåÕÿ§¥ÁŠ¢Xè„óa!ÄU]}sààð«¨Ý‰6xÖv xx؈@±þÐõe/p˜܉60à9`žAûЮ+V‘º6ó€IŠ¢ŒB\Ð ùVº]?À¥¡\ ¢§j§/Ʊ³³C¥ªÂÎÊÚ®Þ÷³¢>J$’̬ۛ,’“´¯33kÌ®··7ÇŽcäÈ‘u*€333ùçŸ8p IoªBll,………ôéÓ禺ÇÜl{YYYìß¿ÿ&ñÜ#˜àÁ&ÛÙµkaaa& æ‰ýú±åÄ âߟ§ÿú‹œùóÁÕ•6?ýTkãÖi¢»‰”©T7Š¢0lØ0vìØZ­Æ××WJòóó9~ü8—.]ÂÝÝ¥Á¿§[ñ¹Þ <¡(J{!ÄiÁ{H/pE9©Æz‚7ø¬ˆµ@[`¬â­öÀ?FÚ.Ö à…ˆè@;Ö8ôA»ÖÏ !.éÊGÐzµïBD*Šr¸lB<а.Q<ÁÚ~ØÙ»’——@AA!Úì» j•¦^ôÒÚZ…õ¶Ÿeûhe%ÿKn=66ÖXZZÊÀm·úÆ&/Ÿ3g’ÈËË«›Ë—/ÇÇLJõë×—š]Û$''sêÔ)FUkßs‡FQBBBjÄ^aa!;wî$<<Ü$ñZñññ´hÑÓnLÇ Î Ü¿?9£uqcXO¶D;]Pr{pÇw°k×.T*þþþr@ (**")IûƒiFFF£xO·ânx ð„N€žV¥9ÐxÇ ÍF࿊¢tBÄ¢ 7‚bû§ ÚiÍé0€â´¢(‹0˜~­hïºê°®mº¢(ÿA;ÍÙ9€ xT‘aÐ>RQ”ÝÀEQü…IòcR3åac“G`çÎØ;ºàß^OÍ»Eûz×ßa#î.y]_ûiØG‰äV“™™Îõ´óØÛYËfn!>>ÞŒ?€ß~û³Æð„ €›××111(ŠrÓôäšäÀØÙÙÕX`/FæM›:thµ"6gffrùòelRû„¤$&.XÀ…_,]Ñ¡Ÿ÷îk×òÌĉòá¿g”Cp[1`ÀöìÙƒJ¥¢C‡r@t¸ººrï½Úä=qqqRW‘mhÝ|aÀ*nx| ½¼‘h=¯ýX´A±rè ìvÕ=K»£Ì¶?ÚÀ»Ê6BìU%¯Œ`î dSŒx-rôÿ>)‚kÜm@‘¢¢<Ý%£Ègù,ŸÅ3Ø;¸áë×…„SquuÂÊR á†ÎªU«J `=z!ìääDppp³¸¸˜mÛ¶@ëÖ­kí½íÙ³www:vìXc6·oßNïÞ½qpp¨–ŽŽŽ6Yü/ÿî;Þ%yùr°+³TÆÖ–ä… ™õë¯lš>oß|³Z©£$’†JŸ>}Ø¿?qqquÍ^ÒÈE°"UQ”£:ŒNgÿ4Û }Eù ìBU`Z?‡çª‘º+e¶*h Ú Wz¼Ñ®S¶§t0¯²øÉË©v(.Ê“ƒ ‘4Âϵ«{ <š´$ãúyÜÝÝå 4p&Mšd4J²^Õèñ222صkƒ 2ypUصkÍ›7§mÛ¶5fs÷îÝøûûãååUmqÞ³gO“fSüÉ’+ðéߟ¹s8Õ¹3×~%9™îK—b£[¿}úêU{ñE~øüsyaKnKBCC9tèG¥[·nr@¤®1¶/éÖà¶ !Š „rž¢(»€Þ@GÀ“ ¦BëHÔ=71RW6ÊÃ%ÝóM!àtžßf€>¯Á5´Á° !Bå%#‘H$5C~^:M<}8{&Œ§¡Sž6µÞΜ9ÃéÓ§kuý/ÀŽ;hÓ¦Mz™;†““mÚ´©–sçÎaggGÓ¦MMj?fäHƤ‹Š‹‹ã±åˉyøaZ¬[ÇÆ×^3Ù–Dr;н{wbcctß}Ì{é% †§ç÷-[øþï¿ùnÉ9pRW*‚§£]—ëñ€W‘À»ÀhƒíŠ8ˆv®]O#uAe¶OyÀŠ¢(¢t4¨þ€ /V% TÅU‘YF4?Œ6"ôwºÜÅB^V‰DR9BhÓÙ"„Fˆ¤„‹/–›ªdïÞ½XZZÖêú_![·n¥S§N5š2EŸ ¸oß¾Õ²SPP@|||•Ç ¨¨ˆ .pOHûŽ§ÍµkRK<………lܸ‘]»w‘’•‚…¥ÎÖÎô é˘1cªù¼K—.¸4kÆè]»Y³†ù“&1Ð@ ÿ±e ~ý•c­[óL«Vò$H\)»€"´ë~A˘~x8'„8YÉ?¬LEQvw+Š2D±M'R]ж2l›£(Ê'ÀËÀ\EQæ !„¢(>À‡FÌ‚v*ôREQžB¨u¶ƒ€•h§n/×µ½h¯Æp\»žÎÑ£qdeçèD¿öÆU>Ëgù,ŸMypvr&((w7ù_WbË—/çwÞ)UV\\\"Lksý¯F£aË–-tëÖ Ÿ³[6puЧª*{öì¡OŸ>„„„°xÖ,î¨áµÚI]³öçµü¼ëg<ƒ=ñºß‹P?íäͬ”,vÙÍŸïþIw3_˜ivV‚/^}•à?æŸ%K·v-AëÖq_¬Þ·Ø!CÈZ¾œ³fñæoÈ!Ep¥¿°æ*вmÔçcQ•…ÇE¹„v­­©‹RîC›ów£¢(ëЮ¾['¸Ëò6ÐK'§(Šrè†6h£A—ü?ÖÙyVe3ÚàZ#ucø¸ÞC,„Pél Våð?!Äg ñâHOÏäà¡£x7m‚ŸŸüuK"‘Tì¬l¡(Ê †v òÇBˆµµÝEQ&rsäêgÆŒ—)È¿ÔÚÆÆ””Kx6õBUT@zF&¿ÿ¹UQjÍ¥Ðׯ§ãææŠµMå_³gMÇÎÖV~¢$‰Db .$##£TÙ¦M›P©Tìܹ³Tù´iÓÌVµyóf†jT<>|˜0iÒ¤jýèûÝâïh¿O7‘ì$þê0¿÷þ®]»V;0ÀÉ“'iÞ¼yµÄ´>–©h4ºtéO\\4RKê7Š¢ðàýòàýÚHÑjµ8€““S•0À²Õ«‰Ÿ<ÙØ‰1‚Ÿ7näžXó/‘"¸ÖB¬T%môé¾haíPÉ4í$W'¾k77Wò­o¤±±s"// WWWŠ‹l(V©pss£¸Ð µº$]3EEE¸º8cc[úÆàì¹+7coLlµ§zI$’ÆAxx8Y—ä@HLbÖ¬YFohËF‡®*Mš4aæÌ™,Z´¨”>xð sæÌáû￯–NLL¤ødq©²æ—›³ýíŒ;¶ÚýÏÎÎæÒ¥K <¸Ê6®_¿Ž……nnGh?pà+W®ç­·žgÏžCdd<Á'ŸüÀK/=Ì»ï~I§Nm¸ÿþÑò¢­233ùâÃ/8s”ôôt»2iê$ºtí"§°5pÌ„„„ðÏ?ÿàääT¥”g*•Н¢xʸv+VàŸ’ÂéV­¸òÌ3äŽâ矗"XŠà%„~»…Çÿø½¶£R¾“ž–\²íäâÅáC{éD~n:§’èÝwyÙW).Î/iwòdmÛúáèT:@È¿{ŽÒ¯ –%‘H$’úO=J„0À¾}û˜7o?þøcµ—ýøûûcág¡ ©©ã¼ëyÞœn~ÄåÌÌÌRk’õkŠG®žðŒ‰‰aذa•¶ëܹÿ}šÍ›—"ÄA ~cÕªGùî»Ó¤¥e²oßò‚ªþúõ/V¾º¿~xàAiˆhÁüŸæÓybg^{ï5,--Kí¹5’£Ç2ã…¥Êþýg®^»Ê3>#¶Hdd$öööfÏlX¶z5'Fޤży„²ä¹çhÞ¼9 IILŸ;—//âúõ“Þ`)‚%‰D"‘Hê§Ž'66–ü±ZÓ#õ(ŠBÄãüžñ;ž‰ž¤¸¦à—?:šl#zo4_ÿþ59¶9890vÀXzwïÍŒ·gPìZÌ/û¡k³®üßóÿgvÿâããiÛ¶­IÁ°ìíí?>˜eËîCíZìÄÄÕ@>wÝõ&~~­åÅTM8Ì×Ï}MðemnæL´ËÕì°£ó•Τ,Oaå^ïõ’}¶ìØÂ—Û¿Ä3Гù‹çóúËÚºŸ~û‰õqëqðrࣕñÂ/È®€aƱaÆ bV «å̸!CXú ´lÙ²¤¼¿?¿-^Ì©„^üè#ÞŒ•"XŠ`Éí„«{ 2Ó/š\^Õv‰D"‘˜Jrr2+V¬(U¶wï^¢££ùÏþÃܹsKÊ}||˜6mZ•62Œ¨­QÄfÆâÑ΃‘÷™Áù‹?¾ èå!B~üàGþÜü'ÝfuÃÚN»Ô(qK"»÷î¦oï¾&ÛU©Tœ9s†Q£FUÚ6))‰>øèè+X[PTdX›KBB<2‡iÓî¡{w™ê¬ª,x~—Ë­÷.öæØÚcœ~ò4í;h×™çæåbí`M‡ðœÚxŠù‹çÓ©}'ÖÇ­§ç3=9µñ¹©¹rp+ÁÒÒ’ádziÓ&ÂÃÃM^Æwlûö ƒæuh׎¿–-#''G²Á’Û]»º›¾æB `‰D"‘R˜üüüX¸paÉv||<¹¹¹8;;ceeÅ‚ ª··DØ<·ï Þ a¤ÂŠÿ® ßà~&Mµ>tøÖíKߌ7Öœ˜•1ÚÝKþCüY½dµY"xÏž=ôíkZûåË䯿â¸|yEEþej›pêÔ=äæ®%#ãS~ûm…¼H«À… °:[ù­·ïE_~þögfÍ×®—¿+â.Š‹‹Yÿ©VôžŽ}J¦GKLÃÅÅ…ÐÐP¢¢¢ä`ÜÆHO°¤FÈL¿Xâ 6|6·e=Ååy€ ëäTi‰D"‘Ô”Ö¯‹-,«:a„³(U–g—GûNíMÚßÉɉö.í9ñÛ Ú‡·'9*‡+Œ»ë>]Gçû;““’Cü·ñ|»ø[“ûµgÏ“‚abiiIttju3\\Þ#+kMš¬"++sçË¥O/q‹ûùp1†¦~@ѯEt<בï?üGGG“ì>}???“‚a²wï’“÷3uêZ&NÌŽ0dH<‹Ÿ$/ï4«Vý ?4Õ ¸{09>•¯M±H¡ÏÐÒ9õA°º?ÞÓ‘§‰ù<†¶CÛr¥Åæ/žorð›0öÏ?χß}×`ÇR­V3ãñLê3‰ÏV%mÚhŒ8yò¤¼8+!55•E‹±hÑ"¢££Å{²Ò‰Á–ÀFÀ¸ (Bu¿-Ï€¯ Ê'¯†s‰Ïßä%^»Èé͉DR¿hÕª%?>€5Ê÷8nܸrëôáêòö'o³mÜ6~úì'‚Âyç™wPÅ,ݺtãý.ï—*óôôdîËs¶ÿòçŸéܦ }ʼ•JÅ»Ÿά§Ÿ6û}„„‘œü[·n%44”uë†1cÆ—ôìÙƒ^˜Laa¡üÐTÀ¹sçØMB|<ÁÁôéן’zÚ oGÎÊœ4å¯Mé–Â=ï)ÙþíïßJÁòMó¥•u+b>¡×3½8µñ ?\ȬÿT¾.8??ŸÔñãÉ #õ¯¿ìX¿9íMо.¢£º#Ñ‰Ñøvð%|lx®ùêå¾]ððð`êÔ©€6ïxc@ï î¯ÀË…Í…ýð{ûÌš–)û ˜ H×càÌô‹%‰D"‘Üz¬­­qssÃÍÍÍäH© 9sæÔÎÍLj!|òÓ'L6[[ÛZ•Šˆ xõÃB;;==°Ge}L M==ͶieeÅ… ðóóÃÓÓ“·ÞšFÏžZ‘­(J¤”jŒäççóÎsùùƒé§á?}‡â“’ÉWo¿Ë',C­¾áWš»l.É}’ɶȾɎ5ýòÊ'¯”º†)Î+æÔÆS´LiÉÌfòÐýÑ׳/û>ÝGqn1ŽŽ·ÍxstûQ<ÔÚzþiþ¬þtu•í 8ØØØF#îjKKKÜÝÝqww¯vnõú&‚½uÏ»n§*„Ø$„X(„H‘—wõ1\\™ –H$‰äVÐXnàîˆÀ­sg–„„0ø™gøþ·ßèóÊ+ì]²„ =pqq©’Ý“'O 4÷üóSäSù½$OL~§Œ\^¼ë^º·ïˆ³ƒ}:waö„I$Çä•—¦—´·³³cõ¶ÕØ[ïÇ<>.××Reùœ={–Ž;VÉfUsK°ü}B>/´ÞaÝl°¸ìç€ÇE™(„ب¿ö€G{à^@A;ÅúC »®N­{Þ ´EùXœr€EQÚ !®ëúÑøí”íƒÀ´ëy#€'Eé$„¸Œvº³~þ' ìtïÑF÷Þü€+º6}t}ú8 8ë¶uö­€Ýë±@?]ÿô_@À^]»-€5ð>ð€îýŸ½6ôü–÷ÚÔví#‘H$‰äÃ;t`kl,téBîý÷Ðò·ß˜øŸÿTÉ^ll,#FŒk‰Çb™2áAºû·gù¯?”áå¿þ€G“Q<ë§oKD°¡ð "((ˆ­[·š}lOOOÒÒÒð¬`êû¡S§ˆš6 |}oª;ñõל0Øv™=Ûì> 4‘?|…  ®u8æ‰xjJ¿gÇTG._¾\e¬ÿqbРAlÛ¶‘#GÊ‹»‘c!„ ,×m¿&„¸C¥(JO`>Zng!Ä=BˆpøJQ”²smîþpz ! êî‚„Ðzr·é„ð2`”"@ | Õ†?m½¥¸ƒ„½…÷èû7€«^˜ !æ£õÀ,Ò½—#Bˆ;€$ KWVYä€ñÀ{€·bÐV'’')Šâ§ÀÀ¯€è+„+„¥{OÝå¥%‘H$‰¤¶øö?øìüyhÛ¶TùÅI“õÚk$ž9c–½+W®àíí-Ó ™Aqq1–깦Ÿ¿û>®\¿Æï½UJ(ÅÅ5~ü¶mÛVÕ½™»;ï¾Kð¬YÏšE·3°>z²³éðòË%åÁ³fáif4ñÿÝÏÑ£aÌœùEŽ{‹¶-H'½TYGMš4©¶mºwï.sß"¸‚ºÿ¢õp>/„¸ª/ÔM“^ø õšr\±X‘-„Ø_¦n¹"Ng£ؤÿBl2h§÷.·Õ‰MKØ|Vñ¯A?TÀݦO K°P‘¯;V*ð;Z·¿®M´Ó·—!Žô+­gY"‘H$‰¤F)((`Ò«¯òŸ‚’Þz ʬqVwìȾ¥Kúùç|úý÷&Û=räݺu“|‹°´´D£Ñ˜µ³³399§\zöˆ]¾œC rháBV?ü0Ö±±žÎØ-JÊ-\Èóç›uüW^ùšŒŒ©>Ü#GŽÕÙXµïÜžŒ¦¥ó(§Ú¥âkÄÛ]¼¼¼hÑ¢‡’æm*‚;ùBˆÝFêôs6Ë”Ÿ¨À^|™ísºç²?a¥êžmu¢R-„øDñ©¢(öŠ¢tWåQEQ>Ö‰qÐNîIN”]ûkÐ_}ø=}N‚Föß./-‰D"©EEEr$’2L5‹}IIø8@ð¬YN›†¢óú6ýþ{­Woî\Ü5ÞY¾œŸMHƒ“––†‡‡–––r€ÍÀÚÚµå[iýè•3^ãÊõkü´óÆgaUñÓ¦M›rõêÕ*‰g•JU¥þ_MM­ò{ÿ÷ßý9 X“’òXzƒ D–{V©2­''§;F›6mBÈÂ˜Šæ=ø£ >e }y«2å-à<_NyÙPneÅ'Š¢´A»Öö.áÖc\ ã’f¤Lß^Ÿø¯cÑnÈ•Æ|Ñ„‡‡ËOŽD"©uœ|ö<;"4r@$ß|ðA©íÔÔTz|ñfÍ¢SL ;ßßl›‡bðàÁrp«€—ÎNJ êÈÁRS Ÿ¿û¾’5–66 ‰¨øÞÉËˋ˗/—Ê+l ­Zµâüùó´iÓÆì¾Wã‡F­x‰nË®Ä\ÕµÁ899ae”lgggZumEþ©|ì±çŠÃÆMÇ®]»¸ãŽ;jìÜöèÑCæ¾MEðe ¼3®O©”^¦¼¢yêªtPQG´A§Ú   µ ˆB¤ê¢?1¦u‰þ‡€æÀñ2uú“’•qI~r$I­bccCÇ€.ØÚØP/E°DRM›6Å?- §O3¢C‡Z†|÷õ7\¾p—_}å¶ÿ)SŸdòø hÓ¡Ô`½žýùǨÝ]xwÖ‹ÚiÒ¤ ÇŽ™?¥¸uëÖDGG›,‚---±Û¶ »sçðôð 99???³ŽièÖ£õÏbãÆ̲uåÊ–/^‚’KF~!ÃxpÊ#•^ï~õ.O¦>IÁÙüïðgÚìiy7ÐOeö*c' TÅVQXýúM÷CÁ Š¢lBü£ë×À³òÒ’H$’ª#§CK$¦óÄÝwóýºufO¥ÍÎÎÆÖÖÖ$»iÃFRR®ÐÂ×—óçÎ1¨}g¼]ÝX¾l¡½{sîÌœ¹küøÛnüxuþ<Î;Çî¨h¾ùóºöèÁäÿ{ѬsR àääDnn®ÉÂÏÐkliiiÖ ÄÆ¶ÁÐ ¬'%å1Þxc6ë×/5[üêéHŸ€@“Ű^ë8®(Êv!ÄÕé”B­(Ê´Q£·+Šr ízå@`?Ћ*N—H$’Û9Z"1oooŽîØaö~û÷ï§OŸ>&µÍÊÌ$)j/Mr°ÎÍáÎAC±P.Dí {Ïa²¯^!³¥×m}|}}ñäK¯¾}J<­æ öln€'«Ý;00'Nd²€nÞ<‰k׌ Ò(Æë_î¾ æ¾IöùKL¿ëÞ›ÄoY ÅðƒãÆ3kÞ\‚{ô¨´vvvtëÖ½{÷Ò»wïjO•JÅ”éSÊp·á¥êüBýJ^¯ûiYÙYÜq—ü2j@èƒL]@å8­ŒÐ{x@Wÿ0­÷÷!Ä6ƒ¦9ºý‰âd]]F™ò«ºò eÊKÙB¤£^üÌZÿ'„| (Š¢è£IŸžNhó ÌÖùÜXÓ|Zw¬lÝvnÛØ4ï›ÆH±æ¢ÍC¼¸­ S^b‰DR5 ó3)È—_£Im——‡……öö¦%ؘðÀý:;ÐÉ·5“¢]a6!ìúvîBÜõ«<ûßÿÊÅ´ÔEÆðòò"%%åÿÛ»óø¨ªƒÿãß™L†Lö –€”Mv×¢ËóTZkëÖZܵUQ´""àRDŠU»ØZ•¢O¬ìð(ZHX4İ%Ù·Yîïìûl@–ÏÛ&s—3÷ž{g2ß9çžëÕzÞŒ,]ýJVV–ÛËhÖ¬q²ZßG7)i…n»íúf×½öÆT`Ò_6¯Siyë2¿;vD½U£ÇW¢×»÷îÝ[‡Ã£ýjÊ7ß|£î“»+82¸Åå†Þ0T«6®âÄog,UaîcI7µ€aHjñFsU-ÂS›™÷ޤwš˜¾Nµ·Zª;ýÛ†e†±_Ò5SþÝML["iIƒi'%ÝÜ`ÚbI‹ë<Îja?>”ôaõc“É«Êé݆aÔ»±šÉdz¤ê×,N1ðÝ¡³kÇŽ5j”Gëüüчõú¯çè7?žUoú{k×è'÷Þ-“ÉÔnë#''G&Ü%‹ep½é§OÿK]»Ž©7Íl> ÔÔ¿7[Vpp°Š‹‹=Þ†ØØXíڵ˯;¹#**JyyyŠŒŒtkù{ï½I¿ûÝÚ³gbÍ´  êþû§·x MNÖ¢eK•–šª'_}]ýãt猫eëÒ¥Qø}kígê1 ¿žym‘W·>?~¼V­Z¥Ë.»ÌëkÖ/¼ðBÍg¾úNî« ° f—;ð¿4câ ÞTÚc†W"$­¯úwqÜM•­Õå’6SMí‡ar8œ 0(‹% ]ÿáÚ"ºCgOYY™œN§ÇáâŧžÑ3×þO£é?»r¦f/zU#Þ{ÇãQ¦ÛŠÒÒR•”ÌÐñã÷4˜ó„Nž|±Þ”žh±¬àà`•””x¼ 6›Í«õªtvv¶Ç]°%iذaÚ¹s§&NœèÖòÕ­Á?¼U•ëÄÇ¿§[oýƒ[ë7†åžÔ¢O>R¿á)^‡ßÚ/*Ìš2eŠ6lØ Ë.»Ìë¿CoÏ{[sÎÑ¡¬C KS×”®²uµétÚiåîÍU˜%L·Ì¼E—M¿Œ7Bp§qPÒ?$]m2™¾“ô…*ʺ®3 #jjìv‡JKËÙM]c"Ü^¯°0Oy¹²Ùº(0—àO†³Te¥¥Tàg;wîô¸øÍ×^—µ¤\ïo^§Ü¼<ýêG·*0À¢ßüíO µ«ox”žýÕ¯5÷¥yT°äõ—ãÞŽ5`ÀíܹӫìMh¯Û¼V÷Ü3Eiii6Ìýû× ÃOýv‰b{Åëî_?®ÀÀ@Ÿpµððp%$$hïÞ½JIIñªŒÈÈH-~n±JKKµk×.9vD‡ ”Ð?AC®¢>}úp²‚;Ù‡3Ã0L&ÓTyýñKºQÒaI”ô·êÑ¢Ñö9Nååª_¿!r.•—»?xxpp¨BBÂô}ÆEE†Ëb  BmΑ#GÕ»w¼ìv»JJJîÑúS/¹X£ÆU||¼6oÚ¤¿l\§Ø¨(ºr†FŒ¥cÇŽÉÀß@_ìÅb‘Ãáð¸EÝf³©Ô‡/ ”™™©„„·–¯Ûœ”´B³gÿV«W¯VJJŠÇ_ MNÖ‚%¿­yüùçŸ+11Ñ/½ † ¢µk×*>>ÞíîÞÍÕï„ 8±;3UàÓ\±aó ÃmFŒa# øŸÜ¾*"²›\†Keeår8nÿ+++—aHá1*(,¤2mŽËåÒèÑ—*++K»víÒH7FÙm(ièP3F={öTL·núwö}¾w·.»êJÅÆÆjÔ¨Qº`Ä*»Š·-Á111ÊÍÍõj]«ÕªŠŠ ¯Ö8p ¾ýö[Ö¹÷Þ›Ô«×Üšk‡ ¢ýû÷û\wãÆÓ¶mÛüv,¦NªÍ›7Ëårqb¢-ÁðIDT/åŸ9Öâôæ–imÞ¹àt:•Ÿ—¯> Ckn`,o5ðÞSOÝ­§ž’^zéU=óÌ6•—O¯3÷Œ.¹d…V®\æÑ@Ž ôøZWIÚôÏM’¤)3Úî D•u±U11õ뤰ð……-«7Íáh½»oXX˜[]‹7¯Û¬òœre)«fZ®ruFgêMs¸Ú¸j£n¸é†Fe L¨….ÔƒO=¨is§éæ+¯÷Þþæv]›|­n¸æBb©÷g7™LŠ‹‹ÓñãÇÕ³gOŸÊŠŒŒ”aÊËËóéGu%$$èðáÃÊÎÎöê¾Ê ç$PŸ+Þvm¦;4 -úÇ?2ärMVÏžOëøñçû–ŠŠúè»ïº«´´T¡¡¡n—•ššª¡C‡z¼ .—K¯<þŠ$iÒ%“¼ ÑçB¯^½”–¶¤Ñmt6mêÞhDa‹å¦VË v«%ØépªÜ(W©jƒg…*d—½Þ4§œ²8šÿØ>0q cuòàIÅ¥ÄÉQîPήÍ|x¦W!Ñ×®ïÇ×_|ás–*[ƒ×®]«Ë/¿ÜoÇû¢‹.ÒªU«tùå—+007 B0pî´Öê{¾oŸ@{õïïÑáÃÛõâ‹ÈlNÐÃoÖÅgé¿þ«ž}6]Ë—ÿE¿üå,·ËËÉÉÑ…^èñv¼µè-õLíYóû=ÝsÖöùÃ5kôægŸÉÜrð¬¨Ð”ÁƒõëŸý¬þ玈ˆFˆ……yu›žàà`·ZT']}ëS«'Iúô­OuÝ-שGgeŸÿ“‘¡ ¹¹Ò“OJ))M/tú´ô«_ɲo_›8NñññÒɵÕ%s CõäÍÕ×\Ñä¼—^IGºÑ Ëiû›Û52l¤6<µAÓæNSßÛûjÖC³´|ár·[<Íf³õ]F†¶îØ!³É¤ÍœéQ–¤#Fhýúõš1c†Ïõ4xð`­Y³Fýúõ“ÅâŸø¡^½z)55UÉÉÉ•_@œ<©œœœšÇè¸ >‡ÚÖ¦7\¦zôç–æìíÈÐt‡´5&“©¦ëqXX˜æÏLcÆŒª™ïÉõÀ{öìñ(”––êéÙOkÖØYºàÛÚ{Î^ðíú队êéÙOû|Ýi³^xAzûm©©™NŸ® ÈóæÉÚ†nóô»/hwŸÝ2Ôôç‰ô˜tÝúô­M¶TWTThíÖµê;¥oÍ5ÀþüA-|t¡6<µA‘ñ‘ÊÌ×Áo=»ÕPRR’æ¼ñ†nïÞ]¿8pÀ«ÛY,…‡‡ëôéÓ~©§1cÆøu,I:t¨²²²”——§Õ+Wë®Ü¥ßLù½ãQÞDÁhÏfÍú^­çé½wlÛ¡ÿ¡ ^+ШÌQ²©v=›lsxŒ ^+ÐÕî֎m;ü¿£Ò‚Òòåõƒpu~é%©‰0éOÅÅÅúäïׇü“æ<ö„V|ø‘òóó›]¾ÿ€þzá_PÚ¸4eÚ2U¬b•«\G,GôŸ”ÿè†7oÐÌÿnúÚ^«Õªw^yGŸÿüózƒ`U–õé]Ÿêž«îQòPÏZ6»wï®â’iÄYúõóº.FŽ©;wú¥^»víZ3H–?M™2E6lÐò§—ëÂÃjЩAÊù(Gk׬壣;4:½s€233Ô§O‚W÷¢ ´(33Cæ*Сddd¨Ÿ!(ù‚dM¸j‚ö|²G¶ïm RP½ù%*QFï ]tõEJ¾à,u9­Â<"Ýq‡'=ñ„4þYÀ›6lК¿~¨FOÐâÿw§L2)-3C¯<þk¹ì]}í5M®—2¬V«‚‚‚TPP ððpŸË;v¬Ö­[ç×A²UVV&Û÷µ_Øô)裷—¼­K.¿„7B0Ðñ˜Í&…„ëà4EGÇÈf³Éápÿ:)‹%@EEÅ:x Mú'Èl6Q©€#==]_|±ÛËÛl6=·ø9e=–¥gîF޵%T°”ž)Ë%-}c©âââÜ*ï¦ÛnÒ’ÅK<œ* @zùeiöl©¸XZ¸ð¬àmÛ¦'ù þüäsê[» ¨Q]uË Ï(ÀbÑW_ÕÌg ‹&Mž¤I“'—cŸŸ¯S§NÕ†Ã:#y=zT!!!5{õê¥.]º¸UîèÑ£õÕW_iÚ´i~ ¬ úî»ï”˜˜è·}ß¶y›ºv«ý|(³²¾Ïâ  #gªY@@€"##®uk×èàÁƒr¹\nÿ;pà€Ö¯[£®QŠŒŒðè+Ú²òòrY­V¯nk§e/SñÈb9«þ+U¢e/s;KÒ¾ô}Úµ{—·©N*+“¤cgw¬‘ŠŠ ýqÉR­}ù ½ôþ{ú>»6@eŸ>¥§Þ^ª•s_ÖæOW©°°°Mï[çÍÓØµkkþý#9Y UþäÉúñ‘#5ÓG¿÷žÞ^±Âírƒ‚‚d6›U\\ì—íLJJÒÁƒ½ê½×œ3GÎ(RõïCl+·5ºe:Z‚ÑéÛß»—Lf“¥ï×î]ÛÝ^7<}ZzüñÊ.ÐaaÒ£JwÞ)%%•ºÚ½{·¦ &[—.ZtÿƒšýÆB=vóm ²ZõÔÛKµèþj³éÊá#õÕ—_jÆe—µ¹ãmŽˆPî]w5šîˆ¯?}Ï9÷ìñ¨ìÑ£GkÇŽî¹ì­1cÆhûöí?~¼_Ê p6nÄtª¢¢Â/ÝÂAÚÞ›¾Ù¬Èˆpu±ZÕ-&Z¥¥en¯k³),,L6[L&ºB:Žœœ9Ò§2ºØº(vz¬L&“†&õ8XÆ_¯¬4»¥Và—^’"«Z÷æÏ?«Aø›m_몃%IAV«Ýÿ x}N§^ýùà ­XlÄ€Azã« m2_3t¨‚¼æñ¿wìÐþ•+ñÅÿ(¢[mwá)·ßîQÙ!!!r¹\*++SPPþùÏMzï½5úóŸ_ôj[cbb”––¦üüü&GÍö”)ܤ UȪÚÛ@•ØJÀ„` c3™L ¶Éf òè–G&“‰ð èpN:¥èèhŸËq:zåW¼Zw͆5ŠûaœR·§Ö„'·ðܹ•¡·îuÄ•Ó{¬r°,?³«´¼¼æq^Q¡\.CVK róójBpiE¹lÁ!mò˜ß>s¦nŸY; õ/çÍÓ~§S–ÒRÍÿå/5lØ0ŸÊ5j”vìØ¡‰'jîÜ¿)==N™™‡•ÐÇ«òƧõë×ûe¬ác‡+ueªâ+âë$wÞ:2® „Z³Ùìö?0 #Ú»w¯RRR|*#??_‘‘‘²Ùlnßb©ÚÂ% •Ý-[Q½£”<+Y7ßw³rss[_ñÉ'+G‚–¤3gêÿ+(¨l!^´H%-ܲÈ£ÇÕ×÷Iª½xÑýjÑýÖ»FxWúAž0®SžSááá*//תUkµwï$eeݯ‡zÓëò¬V«úôé£ôôtŸ·ínúö¬½V»@šqå Þ:0Z‚P£úÞÀ¾vMOO×€p—Þ_ñ‘Oaí\5t¨&?ÿ¼\ê6}º_Êü׿þ­ôôKUÝ5+ë^=ôÐKú製.sìØ±~$ëÅß½¨¢^Pα½ôâK~¹¯1Á 9pà þø§÷%Iû÷ï§B:¹M6hÊ´iJOO×%—\âSY.—Ë«[+ý~éïY5˜Uõ Gf³YÓ§N×ô©ÓeŸo—Ýno“õ¯MÛ¾Ô‚çžWßÐHî?P‹vúVûs³õÉç«Õ×Ï-ÐgÓ­3gêÖ:×ûÃóϨ¢¢×ëL‰ô¹58&&F{÷îÕ§+Wê³V¨K°M?ûå:ԳجV«ž]ü,oMÈÎÎÖâÅ‹%I[·nÕ7^GíÓàÁƒôüÜ9’¤#‡R!Xnn®}`¶^[¶Ôë{×uüøqõêÕËãõªpõïØØØzóØfë±Gzé·¯ëĉÚþõ×r:œºò¾Yº³gÏNŽ}þùFíÝ;I ‡$òµ5xí?¿ÐßÞ}O““´äΟ«´¼LË–¿§·Šò¼ Ãh,66VóæÍ“$Í™3§Cì! “[þÛ%úàWs5oÉR=¿pÏåeddh̘1>•‘““ãS·l§Ó©€€€ó†¯ös+j{÷òË+”—·¸‰9‘úúk“Ç·;ZûÏ/ôá{дÁ)Zz×2W VdÓìknTqY)a„`T*,,Ô ?œ© ¿@ÃPE~¡úN½R½CÂõÊÜd6 íÙ—¦/Rrr²Çå———«K—.>mcEE…O­¾ÅÅÅ á>7mÅ„ µqã~9 è¡ØØ3nàÿÛ²EXºLÓ§èÍY¿¨ ¿ 5†g?ù„úöíËÁ! ³ Ó¥—ÎÐÅq}uá€5Óõ£K’ŽŸÊѻݫìö=}ϲ¢¢"…††r°Ûˆ'ž¸Sü¤ÒÒÕ«!Ÿêñǯq»œÜ'erêÛ½G³¸.[— õ‰é®ÔãGUèç[c¡ýâ>ÁÐìÇÕÒõ«UTZÚhÞ +þª¹ æ{UnFF†Ï£KÛívŸ¯ý-..&·!6›M×_ß_iu¦JJZ£ë¯¿Üír®¹áz½þ‡w´WeºwÙ«Ú¶oo“˹ Cÿß&ݽì5…Œªåý³†]p’h €?~\C† ñ©Œ“'Oª{÷î>‡àèèh·–u:úë_?‘Ýî¬7=--MGŽœ®7-22\×^{Ú [ƒ=m®¨;ï¾[ö;îÐ{o¿­?,{U?žt±Æ%¥Èeúû—›õùÞݺµìɇ¨x‚ -š7_wO¿¢Þ=m«ýêº驇ÕÂ7ëq¹†aÈäF7ÕÖBp?o'äIwècÇŽiöìíÊɹ¡ÁœÏ$]]oJïÞo‚½TÝ|à@šœÎ¤ªVà7½.¯a~ï­W唿  ¾‚‚}öÙgÊ5Jýg»Ê*ôÜM?Ñ«ÿX¡S.»œv»öï߯={öhذan—{êÔ)ÅÄÄø¼}yyyõn—äòòrY­VUI£Lm4ÍjíÆ äƒêÖàÌÌ©^µ·†ï¼ûn*„`4®õÿ·¥æñü¹Ï+óD–²ÊK4ïµÅ^—›žž®¤¤$Ÿ·Ï0 ŸËðõ^Ç8;ª[ƒ?ùä]ýûTÁ8÷~zÏݺjú%Zúîï}*§  @aaa>o¯Ý©ý¤qv<ùä,ýä'?¤"@Àù£ù¯.Ò…#Gz]†ÃáÅâûGËüü|…‡‡Ÿó:0ŒIgL-k4Íå*ç„ñQPPú÷ïKE€ üÃ\{-eiI·L™6ͧõ=ªÞ½{û¼'NœP=Îùþo•äh0u·¤eõ¦¥q²„`ЖpÝàÛð1p¶dffꢋ.ò¹œ“'OjìØ±>•ár¹<¾&84t† îi05OÒcõ¦„‡ózÚ;F  !ðâ|±Ûí~é]QQ¡ÀÀ@ŸÊ())QHH!þW\\ܦBgaa¡Û÷@]¡q®:tH ð¹‡Ãás+°TÙLЮ €ONœ8¡aÆù\ÎÉ“'Õ­[7ŸË)**òh®ˆˆ%&îÒ A×›žŸLõ§sÀB08ZšV`œKþ¸¯ou˜îׯŸÏå{Ô¡M›–q B0hËš ¹-à‚ÂBÊ=U¹~Y™NåæJ’rssµsçN•—sT¸Ç0 Ý÷ÓûôÓ{~ªØž±Ú´i“Ïe¦¥¥©  À/åØívॲ²29Syl»Ý^óû¶mÛtÅ3Á íhíIǎח_~%I:súŒÒöí“$ÊÈÐ'+WjëÖÍ~ÙŽ½{S•’’ì·ýògy%%%ÊÎ>¡þýûù¥¼#GŽ(<}šßêîÀƒøàC¿ÕÝê5Ÿ+"–×­[7]uå~+oêÔÉ ñßíx†¥¤øµîn¼ñúNSwþ>ïüy,B‚ƒýZwc~0Ú¯-rmù5lÓ°a)Ô—ç?Ë4p "£" Á ý‹ŒŒP—.V¿•7`@¿nŸ?Ë ê¢^½zú­¼=zø÷XøùfBBŸNSwþ>ïüy,,‹_ë.::ºM×?Ë °XIÝyyÞù³¼°ð0…†´ÿ{p›ù³è,h š4i¢¢¢¢¨ˆ6àš™WS  Ôa ÒŒ—Pm@Jr²¬Ö@B0hÿš1çÇèÑ£¨Žê°Z­>|Ñôîß!öƒîЀNƒ @€ !€6[$€Ù‚#k~/-É£BÎqÝSçœ÷àµÀk ÎÓO>ˆžŸžà¼çµ^ þCwhÐ,>øS÷¯ê¾£!: B0p ]B€¿„`ÚÆ’Z”VàóSïÐVÞ§:Òû!´ÀÀç§Þ€|vиýá‡[•Ú;Z‚@«‚pþê–øs¯´$nê¼Ðÿ‚@‹aÔ=õŽuß‘ÐÐi‚„`Á‚ @€ !‰-8ÒïËzRf[Úß¶ºÝm¹>¦X¨€Î$KKò:TvwŸÚknËǬ=œK@]´tð\Z’×èßÙha=×áÊ›íjk­î>&B0ü€› ƒtcÐÑ€°\Œ>®þ½¹.Õ [2›jÙl®µ³:¤·ôü­msÃmmX~SÏÛT9îl¯;ë¶´_îìCsǤ©/5ZÛNwŽ7õÑÔcOë8Ÿh è䕺«¯Û•ºáô–~6ìŽÝ0øÕßðqK-Õ ·«á²Í=ose5õü­}qàî~5÷Ü ÷¡©²›ûÂílíØ¸»O-•ãÍv„`tš€Ý\@ód]o–m®¥Ô_ÏïÉ~y³/Þngà íïrü]À¹@whxÒêvùmêgðÙÖ´Ô:ëNwì¶r Üý µìαr§œöRèÜh ÀY › »R·•Ù\WeoFÐnkûÐÔ1pw¹æŽ•»å´—ú!LKA¤¥½–®Cm© w[= ÐM=osËy³LÃ::_ÁÍÓçuwù†]—½Ý?wË!ø¢= ;4@' ÂM †ÕÚHË ÌjiÔàÖ¦7wjkÛÑ\8km@ª†#7·^ÝeÜíöëiùîîCS_x²žn‹»ÇÊ×íÁ8ëAØÓåšû½µ íîó»; ’/˹3ÍÝ}ðv¿<°Ë“uÝÙNoöÍ×}"ø¢= ;4€ !B0„`ÚF‡À7}E%ÐŽÐ @€ !B0„`Á‚ @‚ @€ !B0„`Á‚ Á‚ @Fa³ÉápR|æp8DE€óÎB 911Ñ*)-‘ÕZ ³ÙD…ðŠËe¨¤´DݺES€Œ¶+8Ø&³Ù¤ÂÂBY9UxÇaw($8X]ºt¡2!m[PP]t\ £ó0›•™ù­¬A¡T€s¦KP¨23*ÀÌŸ"pîк“3™Ì  Ö¾½ÿQ·èh‡v“½¢ˆŠpVY»„ª¼¼Ti©ÿQß„Þ2™€Œs À¬ˆˆ……çkÍêÕJIIVŸ¾ýª#²$ƒŸüä'?ýüSúîà~¥íKUÝ¡€B0 ã ¶)¾WœÌf“Òö¥jÛ¿¾¦RœUáa¡Š‹ë¡žq± ¶1ø ã,r: WÎÉŠî)Ã(WDD¸¬V«b¢»ª´´ŒJpVÙlA •Í$“ɤK rs²&—ÓÑø•É©¢RÜRTêYB0*Ù+JÔ+¾¯Ò¿Ý'ÉPXX˜Ì•÷¶QAÎ!“ (/¯@yy§ÔÀÙí¥–êÖÕ¦gìŠé£Ð`+ÕšÀ%*¯p*.&¤éO†aPKœÃQnœÉý¾Þ´ [¸œNCÙÙGURZ,—ÓÉd–Él’ÙdªüÝ$™L&™L&™Í&I&™L’Ùd®üÝ\yuŸÉd’QõaÖdªù\[;ô¸©êJ@C2 Éd2d†êžzµ 2TU Œªy¦êejWaT.­ºËTΪz\5×0d&ÉTõ†Q³^åï¦Úç¬zŽÊÇFÕt—d˜TûZ1ª~7Õ.oÔ-YŸ¾¶@IDAT#ºIFÕ9o2™j.w­|ÍH2L5e2d2*_K•§bÕ2õæU=®ºfÖdªù­¶™*§UÏ3™j~Ö®Sùz4UÍ«~}VÏ«^°zkŸ²zùªÿÕ›W»Fõ Z’K.™ ÉUõb5\FõËB†á’ — ™ä2 ÉeÈ0I†Ó%—áªyÍ» §Ìæ…ØlêÞ#Vfs€ÊË ›¬óÂâRÊ/W…ó4¯K Y1QV…4qÉUTt!¸³†`ɤ@«MA5£²Ö‹oUçEÝ@Zý‹Ñ`™:S,j4j‚r͇}S½Çjða½öãyÝÇõ’@“å˜êÌ«}Þ&ž£N9õË­šR÷9êÌ« $¦f£C«ù£ö›Ú×Ñäãú¯)£™Çõ_[FÃ×R£çma;꾎ë=®»Mµ_$Õ{×{^£Îk©áW~ÔMÎMLkúõí2œrØËd¯(kæ½ÀwQÑ t‡îÄŸàe¯(‘½¢„ªÐipO !B0„`ÁhÃºÇ ñhúùܾæ~B0|ÂmÁɬýMB0Z š­áîqCjþµ¦›[®¥2š›W·Ì¦~z²>­Ç@çÒ-vp£iÜ'­ê7¤^ËkÃÇÍ-ÛÜïžÌkÔ›zÞÖÖoé¹tܼfÍѯÞtZ‚Q/dú£œ¶PÆÙ,@ûÀM!ÃïAØÛç¥Ë2à†­Àݡцx5º,ðGî;X9Ùê-CK0…Q_Zc½Y—Ö_g#7Õ%ÚdµÕÁ9寙Üï›   []_^ð†^~åšé//x£Ñz<|“e=òÐýM.×R-Ík8¨Õ#ݯG¾¿Þtw×on´oÁ¡ÑÊÊÎÓwß}§¸ØH%&&Ö<®Û%:*:AsæÌ¡Æ:8—Ë9§¬$¯ÙùMÓäãº?¿üò_-`Izù•7´rÅj–­»\Ke´V¾¯Û×Üú:vŽ‹TX¨­fY[p$-ÁAK-ÁþD++€¶€URtªfù¨è® †ÿ4ì ç3ge7îKKp'p®Z‚ -àº×ÛívuM¤%Ð1p\ldM>|ø˜ìv;!¸30™¨.'&&ÖÉE’Ùl!w.—“JÐ!å*.6R=c£Üz\­wïx™Ìf® î òóO¥y’T€NÁn·+00°¦+tŸ¾}MKpgàtª°»dwð…€Î€>&‡ÃQ•‰$»Ý%§Ó! ÕÓñZƒT({…C¥¥%2\†""B©–É$åç)Шظ^êbµ)P†þ?ƒ¼èT‘¸ÌIEND®B`‚veusz-1.15/helpers/0000755002344000001440000000000011734662466014253 5ustar jssusers00000000000000veusz-1.15/helpers/__init__.py0000644002344000001440000000166411734662204016361 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Helper compiled routines.""" veusz-1.15/helpers/src/0000755002344000001440000000000011734662466015042 5ustar jssusers00000000000000veusz-1.15/helpers/src/beziers_qtwrap.cpp0000644002344000001440000000322011734662204020572 0ustar jssusers00000000000000// Copyright (C) 2010 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "beziers.h" #include "beziers_qtwrap.h" QPolygonF bezier_fit_cubic_single( const QPolygonF& data, double error ) { QPolygonF out(4); const int retn = sp_bezier_fit_cubic(out.data(), data.data(), data.count(), error); if( retn >= 0 ) return out; else return QPolygonF(); } QPolygonF bezier_fit_cubic_multi( const QPolygonF& data, double error, unsigned max_beziers ) { QPolygonF out(4*max_beziers); const int retn = sp_bezier_fit_cubic_r(out.data(), data.data(), data.count(), error, max_beziers); if( retn >= 0 ) { // get rid of unused points if( retn*4 < out.count() ) out.remove( retn*4, out.count()-retn*4 ); return out; } else return QPolygonF(); } veusz-1.15/helpers/src/qtloops.h0000644002344000001440000000456711734662204016716 0ustar jssusers00000000000000// -*- mode: C++; -*- #ifndef QTLOOPS_H #define QTLOOPS_H // Copyright (C) 2009 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "qtloops_helpers.h" #include #include #include #include #include class QtLoops { public: QtLoops() {}; }; // add sets of points to a QPolygonF void addNumpyToPolygonF(QPolygonF& poly, const Tuple2Ptrs& v); // add sets of polygon points to a path void addNumpyPolygonToPath(QPainterPath &path, const Tuple2Ptrs& d, const QRectF* clip = 0); // plot paths to painter // x and y locations are given in x and y // if scaling is not 0, is an array to scale the data points by // if colorimg is not 0, is a Nx1 image containing color points for path fills // clip is a clipping rectangle if set void plotPathsToPainter(QPainter& painter, QPainterPath& path, const Numpy1DObj& x, const Numpy1DObj& y, const Numpy1DObj* scaling = 0, const QRectF* clip = 0, const QImage* colorimg = 0); void plotLinesToPainter(QPainter& painter, const Numpy1DObj& x1, const Numpy1DObj& y1, const Numpy1DObj& x2, const Numpy1DObj& y2, const QRectF* clip = 0, bool autoexpand = true); void plotBoxesToPainter(QPainter& painter, const Numpy1DObj& x1, const Numpy1DObj& y1, const Numpy1DObj& x2, const Numpy1DObj& y2, const QRectF* clip = 0, bool autoexpand = true); QImage numpyToQImage(const Numpy2DObj& data, const Numpy2DIntObj &colors, bool forcetrans = false); void applyImageTransparancy(QImage& img, const Numpy2DObj& data); #endif veusz-1.15/helpers/src/polygonclip.h0000644002344000001440000000241511734662204017542 0ustar jssusers00000000000000// -*- mode: C++; -*- // Copyright (C) 2010 Jeremy Sanders // 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 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301, USA. //////////////////////////////////////////////////////////////////// #ifndef POLYGONCLIP_HH #define POLYGONCLIP_HH #include #include #include #include // clip input polygon to clipping rectangle, filling outpoly void polygonClip(const QPolygonF& inpoly, const QRectF& cliprect, QPolygonF& outpoly); // plot a clipped polygon to painter void plotClippedPolygon(QPainter& painter, QRectF rect, const QPolygonF& inpoly, bool autoexpand = true); #endif veusz-1.15/helpers/src/recordpaintengine.cpp0000644002344000001440000003343111734662204021240 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include "paintelement.h" #include "recordpaintengine.h" #include "recordpaintdevice.h" namespace { ////////////////////////////////////////////////////////////// // Drawing Elements // these are defined for each type of painting // the QPaintEngine does // draw an ellipse (QRect and QRectF) template class ellipseElement : public PaintElement { public: ellipseElement(const T &rect) : _ellipse(rect) {} void paint(QPainter& painter) { painter.drawEllipse(_ellipse); } private: T _ellipse; }; typedef ellipseElement EllipseElement; typedef ellipseElement EllipseFElement; // draw QImage class ImageElement : public PaintElement { public: ImageElement(const QRectF& rect, const QImage& image, const QRectF& sr, Qt::ImageConversionFlags flags) : _image(image), _rect(rect), _sr(sr), _flags(flags) {} void paint(QPainter& painter) { painter.drawImage(_rect, _image, _sr, _flags); } private: QImage _image; QRectF _rect, _sr; Qt::ImageConversionFlags _flags; }; // draw lines // this is for painting QLine and QLineF template class lineElement : public PaintElement { public: lineElement(const T *lines, int linecount) { for(int i = 0; i < linecount; i++) _lines << lines[i]; } void paint(QPainter& painter) { painter.drawLines(_lines); } private: QVector _lines; }; // specific Line and LineF variants typedef lineElement LineElement; typedef lineElement LineFElement; // draw QPainterPath class PathElement : public PaintElement { public: PathElement(const QPainterPath& path) : _path(path) {} void paint(QPainter& painter) { painter.drawPath(_path); } private: QPainterPath _path; }; // draw Pixmap class PixmapElement : public PaintElement { public: PixmapElement(const QRectF& r, const QPixmap& pm, const QRectF& sr) : _r(r), _pm(pm), _sr(sr) {} void paint(QPainter& painter) { painter.drawPixmap(_r, _pm, _sr); } private: QRectF _r; QPixmap _pm; QRectF _sr; }; // draw points (QPoint and QPointF) template class pointElement : public PaintElement { public: pointElement(const T* points, int pointcount) { for(int i=0; i PointElement; typedef pointElement PointFElement; // for QPolygon and QPolygonF template class polyElement: public PaintElement { public: polyElement(const T* points, int pointcount, QPaintEngine::PolygonDrawMode mode) : _mode(mode) { for(int i=0; i PolygonElement; typedef polyElement PolygonFElement; // for QRect and QRectF template class rectElement : public PaintElement { public: rectElement(const T* rects, int rectcount) { for(int i=0; i _rects; }; typedef rectElement RectElement; typedef rectElement RectFElement; // draw Text class TextElement : public PaintElement { public: TextElement(const QPointF& pt, const QTextItem& txt) : _pt(pt), _text(txt.text()) {} void paint(QPainter& painter) { painter.drawText(_pt, _text); } private: QPointF _pt; QString _text; }; class TiledPixmapElement : public PaintElement { public: TiledPixmapElement(const QRectF& rect, const QPixmap& pixmap, const QPointF& pt) : _rect(rect), _pixmap(pixmap), _pt(pt) {} void paint(QPainter& painter) { painter.drawTiledPixmap(_rect, _pixmap, _pt); } private: QRectF _rect; QPixmap _pixmap; QPointF _pt; }; /////////////////////////////////////////////////////////////////// // State paint elements // these define and change the state of the painter class BackgroundBrushElement : public PaintElement { public: BackgroundBrushElement(const QBrush& brush) : _brush(brush) {} void paint(QPainter& painter) { painter.setBackground(_brush); } private: QBrush _brush; }; class BackgroundModeElement : public PaintElement { public: BackgroundModeElement(Qt::BGMode mode) : _mode(mode) {} void paint(QPainter& painter) { painter.setBackgroundMode(_mode); } private: Qt::BGMode _mode; }; class BrushElement : public PaintElement { public: BrushElement(const QBrush& brush) : _brush(brush) {} void paint(QPainter& painter) { painter.setBrush(_brush); } private: QBrush _brush; }; class BrushOriginElement : public PaintElement { public: BrushOriginElement(const QPointF& origin) : _origin(origin) {} void paint(QPainter& painter) { painter.setBrushOrigin(_origin); } private: QPointF _origin; }; class ClipRegionElement : public PaintElement { public: ClipRegionElement(Qt::ClipOperation op, const QRegion& region) : _op(op), _region(region) {} void paint(QPainter& painter) { painter.setClipRegion(_region, _op); } private: Qt::ClipOperation _op; QRegion _region; }; class ClipPathElement : public PaintElement { public: ClipPathElement(Qt::ClipOperation op, const QPainterPath& region) : _op(op), _region(region) {} void paint(QPainter& painter) { painter.setClipPath(_region, _op); } private: Qt::ClipOperation _op; QPainterPath _region; }; class CompositionElement : public PaintElement { public: CompositionElement(QPainter::CompositionMode mode) : _mode(mode) {} void paint(QPainter& painter) { painter.setCompositionMode(_mode); } private: QPainter::CompositionMode _mode; }; class FontElement : public PaintElement { public: FontElement(const QFont& font) : _font(font) {} void paint(QPainter& painter) { painter.setFont(_font); } private: QFont _font; }; class TransformElement : public PaintElement { public: TransformElement(const QTransform& t) : _t(t) {} void paint(QPainter& painter) { painter.setWorldTransform(_t); } private: QTransform _t; }; class ClipEnabledElement : public PaintElement { public: ClipEnabledElement(bool enabled) : _enabled(enabled) {} void paint(QPainter& painter) { painter.setClipping(_enabled); } private: bool _enabled; }; class PenElement : public PaintElement { public: PenElement(const QPen& pen) : _pen(pen) {} void paint(QPainter& painter) { painter.setPen(_pen); } private: QPen _pen; }; class HintsElement : public PaintElement { public: HintsElement(QPainter::RenderHints hints) : _hints(hints) {} void paint(QPainter& painter) { painter.setRenderHints(_hints); } private: QPainter::RenderHints _hints; }; // end anonymous block } /////////////////////////////////////////////////////////////////// // Paint engine follows RecordPaintEngine::RecordPaintEngine() : QPaintEngine(QPaintEngine::AllFeatures), _drawitemcount(0), _pdev(0) { } bool RecordPaintEngine::begin(QPaintDevice* pdev) { // old style C cast - probably should use dynamic_cast _pdev = (RecordPaintDevice*)(pdev); // signal started ok return 1; } // for each type of drawing command we add a new element // to the list maintained by the device void RecordPaintEngine::drawEllipse(const QRectF& rect) { _pdev->addElement( new EllipseFElement(rect) ); _drawitemcount++; } void RecordPaintEngine::drawEllipse(const QRect& rect) { _pdev->addElement( new EllipseElement(rect) ); _drawitemcount++; } void RecordPaintEngine::drawImage(const QRectF& rectangle, const QImage& image, const QRectF& sr, Qt::ImageConversionFlags flags) { _pdev->addElement( new ImageElement(rectangle, image, sr, flags) ); _drawitemcount++; } void RecordPaintEngine::drawLines(const QLineF* lines, int lineCount) { _pdev->addElement( new LineFElement(lines, lineCount) ); _drawitemcount += lineCount; } void RecordPaintEngine::drawLines(const QLine* lines, int lineCount) { _pdev->addElement( new LineElement(lines, lineCount) ); _drawitemcount += lineCount; } void RecordPaintEngine::drawPath(const QPainterPath& path) { _pdev->addElement( new PathElement(path) ); _drawitemcount++; } void RecordPaintEngine::drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr) { _pdev->addElement( new PixmapElement(r, pm, sr) ); _drawitemcount++; } void RecordPaintEngine::drawPoints(const QPointF* points, int pointCount) { _pdev->addElement( new PointFElement(points, pointCount) ); _drawitemcount += pointCount; } void RecordPaintEngine::drawPoints(const QPoint* points, int pointCount) { _pdev->addElement( new PointElement(points, pointCount) ); _drawitemcount += pointCount; } void RecordPaintEngine::drawPolygon(const QPointF* points, int pointCount, QPaintEngine::PolygonDrawMode mode) { _pdev->addElement( new PolygonFElement(points, pointCount, mode) ); _drawitemcount += pointCount; } void RecordPaintEngine::drawPolygon(const QPoint* points, int pointCount, QPaintEngine::PolygonDrawMode mode) { _pdev->addElement( new PolygonElement(points, pointCount, mode) ); _drawitemcount += pointCount; } void RecordPaintEngine::drawRects(const QRectF* rects, int rectCount) { _pdev->addElement( new RectFElement( rects, rectCount ) ); _drawitemcount += rectCount; } void RecordPaintEngine::drawRects(const QRect* rects, int rectCount) { _pdev->addElement( new RectElement( rects, rectCount ) ); _drawitemcount += rectCount; } void RecordPaintEngine::drawTextItem(const QPointF& p, const QTextItem& textItem) { _pdev->addElement( new TextElement(p, textItem) ); _drawitemcount += textItem.text().length(); } void RecordPaintEngine::drawTiledPixmap(const QRectF& rect, const QPixmap& pixmap, const QPointF& p) { _pdev->addElement( new TiledPixmapElement(rect, pixmap, p) ); _drawitemcount += 1; } bool RecordPaintEngine::end() { // signal finished ok return 1; } QPaintEngine::Type RecordPaintEngine::type () const { // some sort of random number for the ID of the engine type return QPaintEngine::Type(int(QPaintEngine::User)+34); } void RecordPaintEngine::updateState(const QPaintEngineState& state) { // we add a new element for each change of state // these are replayed later const int flags = state.state(); if( flags & QPaintEngine::DirtyBackground ) _pdev->addElement( new BackgroundBrushElement( state.backgroundBrush() ) ); if( flags & QPaintEngine::DirtyBackgroundMode ) _pdev->addElement( new BackgroundModeElement( state.backgroundMode() ) ); if( flags & QPaintEngine::DirtyBrush ) _pdev->addElement( new BrushElement( state.brush() ) ); if( flags & QPaintEngine::DirtyBrushOrigin ) _pdev->addElement( new BrushOriginElement( state.brushOrigin() ) ); if( flags & QPaintEngine::DirtyClipRegion ) _pdev->addElement( new ClipRegionElement( state.clipOperation(), state.clipRegion() ) ); if( flags & QPaintEngine::DirtyClipPath ) _pdev->addElement( new ClipPathElement( state.clipOperation(), state.clipPath() ) ); if( flags & QPaintEngine::DirtyCompositionMode ) _pdev->addElement( new CompositionElement( state.compositionMode() ) ); if( flags & QPaintEngine::DirtyFont ) _pdev->addElement( new FontElement( state.font() ) ); if( flags & QPaintEngine::DirtyTransform ) _pdev->addElement( new TransformElement( state.transform() ) ); if( flags & QPaintEngine::DirtyClipEnabled ) _pdev->addElement( new ClipEnabledElement( state.isClipEnabled() ) ); if( flags & QPaintEngine::DirtyPen ) _pdev->addElement( new PenElement( state.pen() ) ); if( flags & QPaintEngine::DirtyHints ) _pdev->addElement( new HintsElement( state.renderHints() ) ); } veusz-1.15/helpers/src/numpyfuncs.h0000644002344000001440000000263511734662204017416 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #ifndef NUMPYFUNCS_HH #define NUMPYFUNCS_HH #include "qtloops_helpers.h" // bin up data given by factor. If average is True, then divide by number // of elements in bins void binData(const Numpy1DObj& indata, int binning, bool average, int* numoutbins, double** outdata); // rolling average calculation // weights is an optional weighting array void rollingAverage(const Numpy1DObj& indata, const Numpy1DObj* weights, int width, int* numoutbins, double** outdata); #endif veusz-1.15/helpers/src/beziers_qtwrap.h0000644002344000001440000000226411734662204020246 0ustar jssusers00000000000000// -*- mode: C++; -*- // Copyright (C) 2010 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #ifndef BEZIERS_QTWRAP_H #define BEZIERS_QTWRAP_H #include QPolygonF bezier_fit_cubic_single(const QPolygonF& data, double error); QPolygonF bezier_fit_cubic_multi(const QPolygonF& data, double error, unsigned max_beziers); #endif veusz-1.15/helpers/src/beziers.h0000644002344000001440000000250111734662204016642 0ustar jssusers00000000000000// -*- mode: C++; -*- #ifndef SP_BEZIERS_H #define SP_BEZIERS_H /* * An Algorithm for Automatically Fitting Digitized Curves * by Philip J. Schneider * from "Graphics Gems", Academic Press, 1990 * * Authors: * Philip J. Schneider * Lauris Kaplinski * * Copyright (C) 1990 Philip J. Schneider * Copyright (C) 2001 Lauris Kaplinski and Ximian, Inc. * * Released under GNU GPL */ /* Bezier approximation utils */ // Modified to be based around QPointF by Jeremy Sanders (2007) #include QPointF bezier_pt(unsigned const degree, QPointF const V[], double const t); int sp_bezier_fit_cubic(QPointF bezier[], QPointF const *data, int len, double error); int sp_bezier_fit_cubic_r(QPointF bezier[], QPointF const data[], int len, double error, unsigned max_beziers); int sp_bezier_fit_cubic_full(QPointF bezier[], int split_points[], QPointF const data[], int len, QPointF const &tHat1, QPointF const &tHat2, double error, unsigned max_beziers); QPointF sp_darray_left_tangent(QPointF const d[], unsigned const len); QPointF sp_darray_left_tangent(QPointF const d[], unsigned const len, double const tolerance_sq); QPointF sp_darray_right_tangent(QPointF const d[], unsigned const length, double const tolerance_sq); #endif /* SP_BEZIERS_H */ veusz-1.15/helpers/src/README0000644002344000001440000000035711734662204015715 0ustar jssusers00000000000000_na_cntr.c is taken from matplotlib (currently version 0.84). The licence of this file is in the file LICENSE_MATPLOTLIB or can be read at http://matplotlib.sourceforge.net/license.html Thanks for the matplotlib guys for this nice code. veusz-1.15/helpers/src/beziers.cpp0000644002344000001440000007425511734662204017214 0ustar jssusers00000000000000#define SP_BEZIERS_C /** \file * Bezier interpolation for inkscape drawing code. */ /* * Original code published in: * An Algorithm for Automatically Fitting Digitized Curves * by Philip J. Schneider * "Graphics Gems", Academic Press, 1990 * * Authors: * Philip J. Schneider * Lauris Kaplinski * Peter Moulder * * Copyright (C) 1990 Philip J. Schneider * Copyright (C) 2001 Lauris Kaplinski * Copyright (C) 2001 Ximian, Inc. * Copyright (C) 2003,2004 Monash University * * Released under GNU GPL, read the file 'COPYING' for more information */ // Modified to be based around QPointF by Jeremy Sanders (2007) #define SP_HUGE 1e5 //#define BEZIER_DEBUG #include #include #include #include #include "beziers.h" #include "isnan.h" #define g_return_val_if_fail(check, val) \ if(!(check)) { \ fprintf(stderr, "Error in check g_return_val_if_fail in " \ __FILE__ "\n"); \ return(val); \ } #define g_return_if_fail(check) \ if(!(check)) { \ fprintf(stderr, "Error in check g_return_if_fail in " \ __FILE__ "\n"); \ return; \ } #define g_assert(check) \ if(!(check)) { \ fprintf(stderr, "Assertion failed in g_assert in " \ __FILE__ "\n"); \ abort(); \ } #define G_N_ELEMENTS(arr) (sizeof (arr) / sizeof ((arr)[0])) static inline bool is_zero(const QPointF& pt) { return pt.isNull(); } static inline QPointF unit_vector(const QPointF& pt) { const double mag = sqrt(pt.x()*pt.x()+pt.y()*pt.y()); return pt / mag; } static inline double dot(const QPointF& a, const QPointF& b) { return a.x()*b.x() + a.y()*b.y(); } /** Compute the L2, or euclidean, norm of \a p. */ static inline double L2(const QPointF &p) { return hypot(p.x(), p.y()); } static inline QPointF rot90(const QPointF& p) { return QPointF(-p.y(), p.x()); } typedef QPointF BezierCurve[]; /* Forward declarations */ static void generate_bezier(QPointF b[], QPointF const d[], double const u[], unsigned len, QPointF const &tHat1, QPointF const &tHat2, double tolerance_sq); static void estimate_lengths(QPointF bezier[], QPointF const data[], double const u[], unsigned len, QPointF const &tHat1, QPointF const &tHat2); static void estimate_bi(QPointF b[4], unsigned ei, QPointF const data[], double const u[], unsigned len); static void reparameterize(QPointF const d[], unsigned len, double u[], BezierCurve const bezCurve); static double NewtonRaphsonRootFind(BezierCurve const Q, QPointF const &P, double u); static QPointF sp_darray_center_tangent(QPointF const d[], unsigned center, unsigned length); static QPointF sp_darray_right_tangent(QPointF const d[], unsigned const len); static unsigned copy_without_nans_or_adjacent_duplicates(QPointF const src[], unsigned src_len, QPointF dest[]); static void chord_length_parameterize(QPointF const d[], double u[], unsigned len); static double compute_max_error_ratio(QPointF const d[], double const u[], unsigned len, BezierCurve const bezCurve, double tolerance, unsigned *splitPoint); static double compute_hook(QPointF const &a, QPointF const &b, double const u, BezierCurve const bezCurve, double const tolerance); static QPointF const unconstrained_tangent(0, 0); /* * B0, B1, B2, B3 : Bezier multipliers */ static inline double B0(double u) { return ( ( 1.0 - u ) * ( 1.0 - u ) * ( 1.0 - u ) ); } static inline double B1(double u) { return ( 3 * u * ( 1.0 - u ) * ( 1.0 - u ) ); } static inline double B2(double u) { return ( 3 * u * u * ( 1.0 - u ) ); } static inline double B3(double u) { return ( u * u * u ); } #ifdef BEZIER_DEBUG # define DOUBLE_ASSERT(x) g_assert( ( (x) > -SP_HUGE ) && ( (x) < SP_HUGE ) ) # define BEZIER_ASSERT(b) do { \ DOUBLE_ASSERT((b)[0].x()); DOUBLE_ASSERT((b)[0].y()); \ DOUBLE_ASSERT((b)[1].x()); DOUBLE_ASSERT((b)[1].y()); \ DOUBLE_ASSERT((b)[2].x()); DOUBLE_ASSERT((b)[2].y()); \ DOUBLE_ASSERT((b)[3].x()); DOUBLE_ASSERT((b)[3].y()); \ } while(0) #else # define DOUBLE_ASSERT(x) do { } while(0) # define BEZIER_ASSERT(b) do { } while(0) #endif /** * Fit a single-segment Bezier curve to a set of digitized points. * * \return Number of segments generated, or -1 on error. */ int sp_bezier_fit_cubic(QPointF *bezier, QPointF const *data, int len, double error) { return sp_bezier_fit_cubic_r(bezier, data, len, error, 1); } /** * Fit a multi-segment Bezier curve to a set of digitized points, with * possible weedout of identical points and NaNs. * * \param max_beziers Maximum number of generated segments * \param Result array, must be large enough for n. segments * 4 elements. * * \return Number of segments generated, or -1 on error. */ int sp_bezier_fit_cubic_r(QPointF bezier[], QPointF const data[], int const len, double const error, unsigned const max_beziers) { g_return_val_if_fail(bezier != NULL, -1); g_return_val_if_fail(data != NULL, -1); g_return_val_if_fail(len > 0, -1); g_return_val_if_fail(max_beziers < (1ul << (31 - 2 - 1 - 3)), -1); QPolygonF uniqued_data(len); unsigned uniqued_len = copy_without_nans_or_adjacent_duplicates(data, len, uniqued_data.data() ); if ( uniqued_len < 2 ) { return 0; } /* Call fit-cubic function with recursion. */ return sp_bezier_fit_cubic_full(bezier, NULL, uniqued_data.data(), uniqued_len, unconstrained_tangent, unconstrained_tangent, error, max_beziers); } /** * Copy points from src to dest, filter out points containing NaN and * adjacent points with equal x and y. * \return length of dest */ static unsigned copy_without_nans_or_adjacent_duplicates(QPointF const src[], unsigned src_len, QPointF dest[]) { unsigned si = 0; for (;;) { if ( si == src_len ) { return 0; } if (!isNaN(src[si].x()) && !isNaN(src[si].y())) { dest[0] = QPointF(src[si]); ++si; break; } } unsigned di = 0; for (; si < src_len; ++si) { QPointF const src_pt = QPointF(src[si]); if ( src_pt != dest[di] && !isNaN(src_pt.x()) && !isNaN(src_pt.y())) { dest[++di] = src_pt; } } unsigned dest_len = di + 1; g_assert( dest_len <= src_len ); return dest_len; } /** * Fit a multi-segment Bezier curve to a set of digitized points, without * possible weedout of identical points and NaNs. * * \pre data is uniqued, i.e. not exist i: data[i] == data[i + 1]. * \param max_beziers Maximum number of generated segments * \param Result array, must be large enough for n. segments * 4 elements. */ int sp_bezier_fit_cubic_full(QPointF bezier[], int split_points[], QPointF const data[], int const len, QPointF const &tHat1, QPointF const &tHat2, double const error, unsigned const max_beziers) { int const maxIterations = 4; /* Max times to try iterating */ g_return_val_if_fail(bezier != NULL, -1); g_return_val_if_fail(data != NULL, -1); g_return_val_if_fail(len > 0, -1); g_return_val_if_fail(max_beziers >= 1, -1); g_return_val_if_fail(error >= 0.0, -1); if ( len < 2 ) return 0; if ( len == 2 ) { /* We have 2 points, which can be fitted trivially. */ bezier[0] = data[0]; bezier[3] = data[len - 1]; double const dist = ( L2( data[len - 1] - data[0] ) *(1./3.) ); if (isNaN(dist)) { /* Numerical problem, fall back to straight line segment. */ bezier[1] = bezier[0]; bezier[2] = bezier[3]; } else { bezier[1] = ( is_zero(tHat1) ? ( 2 * bezier[0] + bezier[3] ) * (1./3.) : bezier[0] + dist * tHat1 ); bezier[2] = ( is_zero(tHat2) ? ( bezier[0] + 2 * bezier[3] ) * (1./3.) : bezier[3] + dist * tHat2 ); } BEZIER_ASSERT(bezier); return 1; } /* Parameterize points, and attempt to fit curve */ unsigned splitPoint; /* Point to split point set at. */ bool is_corner; { QVector u(len); chord_length_parameterize(data, u.data(), len); if ( u[len - 1] == 0.0 ) { /* Zero-length path: every point in data[] is the same. * * (Clients aren't allowed to pass such data; handling the case is defensive * programming.) */ return 0; } generate_bezier(bezier, data, u.data(), len, tHat1, tHat2, error); reparameterize(data, len, u.data(), bezier); /* Find max deviation of points to fitted curve. */ double const tolerance = sqrt(error + 1e-9); double maxErrorRatio = compute_max_error_ratio(data, u.data(), len, bezier, tolerance, &splitPoint); if ( fabs(maxErrorRatio) <= 1.0 ) { BEZIER_ASSERT(bezier); return 1; } /* If error not too large, then try some reparameterization and iteration. */ if ( 0.0 <= maxErrorRatio && maxErrorRatio <= 3.0 ) { for (int i = 0; i < maxIterations; i++) { generate_bezier(bezier, data, u.data(), len, tHat1, tHat2, error); reparameterize(data, len, u.data(), bezier); maxErrorRatio = compute_max_error_ratio(data, u.data(), len, bezier, tolerance, &splitPoint); if ( fabs(maxErrorRatio) <= 1.0 ) { BEZIER_ASSERT(bezier); return 1; } } } is_corner = (maxErrorRatio < 0); } if (is_corner) { g_assert(splitPoint < unsigned(len)); if (splitPoint == 0) { if (is_zero(tHat1)) { /* Got spike even with unconstrained initial tangent. */ ++splitPoint; } else { return sp_bezier_fit_cubic_full(bezier, split_points, data, len, unconstrained_tangent, tHat2, error, max_beziers); } } else if (splitPoint == unsigned(len - 1)) { if (is_zero(tHat2)) { /* Got spike even with unconstrained final tangent. */ --splitPoint; } else { return sp_bezier_fit_cubic_full(bezier, split_points, data, len, tHat1, unconstrained_tangent, error, max_beziers); } } } if ( 1 < max_beziers ) { /* * Fitting failed -- split at max error point and fit recursively */ unsigned const rec_max_beziers1 = max_beziers - 1; QPointF recTHat2, recTHat1; if (is_corner) { g_return_val_if_fail(0 < splitPoint && splitPoint < unsigned(len - 1), -1); recTHat1 = recTHat2 = unconstrained_tangent; } else { /* Unit tangent vector at splitPoint. */ recTHat2 = sp_darray_center_tangent(data, splitPoint, len); recTHat1 = -recTHat2; } int const nsegs1 = sp_bezier_fit_cubic_full(bezier, split_points, data, splitPoint + 1, tHat1, recTHat2, error, rec_max_beziers1); if ( nsegs1 < 0 ) { #ifdef BEZIER_DEBUG fprintf(stderr, "fit_cubic[1]: recursive call failed\n"); #endif return -1; } g_assert( nsegs1 != 0 ); if (split_points != NULL) { split_points[nsegs1 - 1] = splitPoint; } unsigned const rec_max_beziers2 = max_beziers - nsegs1; int const nsegs2 = sp_bezier_fit_cubic_full(bezier + nsegs1*4, ( split_points == NULL ? NULL : split_points + nsegs1 ), data + splitPoint, len - splitPoint, recTHat1, tHat2, error, rec_max_beziers2); if ( nsegs2 < 0 ) { #ifdef BEZIER_DEBUG fprintf(stderr, "fit_cubic[2]: recursive call failed\n"); #endif return -1; } #ifdef BEZIER_DEBUG fprintf(stderr, "fit_cubic: success[nsegs: %d+%d=%d] on max_beziers:%u\n", nsegs1, nsegs2, nsegs1 + nsegs2, max_beziers); #endif return nsegs1 + nsegs2; } else { return -1; } } /** * Fill in \a bezier[] based on the given data and tangent requirements, using * a least-squares fit. * * Each of tHat1 and tHat2 should be either a zero vector or a unit vector. * If it is zero, then bezier[1 or 2] is estimated without constraint; otherwise, * it bezier[1 or 2] is placed in the specified direction from bezier[0 or 3]. * * \param tolerance_sq Used only for an initial guess as to tangent directions * when \a tHat1 or \a tHat2 is zero. */ static void generate_bezier(QPointF bezier[], QPointF const data[], double const u[], unsigned const len, QPointF const &tHat1, QPointF const &tHat2, double const tolerance_sq) { bool const est1 = is_zero(tHat1); bool const est2 = is_zero(tHat2); QPointF est_tHat1( est1 ? sp_darray_left_tangent(data, len, tolerance_sq) : tHat1 ); QPointF est_tHat2( est2 ? sp_darray_right_tangent(data, len, tolerance_sq) : tHat2 ); estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); /* We find that sp_darray_right_tangent tends to produce better results for our current freehand tool than full estimation. */ if (est1) { estimate_bi(bezier, 1, data, u, len); if (bezier[1] != bezier[0]) { est_tHat1 = unit_vector(bezier[1] - bezier[0]); } estimate_lengths(bezier, data, u, len, est_tHat1, est_tHat2); } } static void estimate_lengths(QPointF bezier[], QPointF const data[], double const uPrime[], unsigned const len, QPointF const &tHat1, QPointF const &tHat2) { double C[2][2]; /* Matrix C. */ double X[2]; /* Matrix X. */ /* Create the C and X matrices. */ C[0][0] = 0.0; C[0][1] = 0.0; C[1][0] = 0.0; C[1][1] = 0.0; X[0] = 0.0; X[1] = 0.0; /* First and last control points of the Bezier curve are positioned exactly at the first and last data points. */ bezier[0] = data[0]; bezier[3] = data[len - 1]; for (unsigned i = 0; i < len; i++) { /* Bezier control point coefficients. */ double const b0 = B0(uPrime[i]); double const b1 = B1(uPrime[i]); double const b2 = B2(uPrime[i]); double const b3 = B3(uPrime[i]); /* rhs for eqn */ QPointF const a1 = b1 * tHat1; QPointF const a2 = b2 * tHat2; C[0][0] += dot(a1, a1); C[0][1] += dot(a1, a2); C[1][0] = C[0][1]; C[1][1] += dot(a2, a2); /* Additional offset to the data point from the predicted point if we were to set bezier[1] to bezier[0] and bezier[2] to bezier[3]. */ QPointF const shortfall = ( data[i] - ( ( b0 + b1 ) * bezier[0] ) - ( ( b2 + b3 ) * bezier[3] ) ); X[0] += dot(a1, shortfall); X[1] += dot(a2, shortfall); } /* We've constructed a pair of equations in the form of a matrix product C * alpha = X. Now solve for alpha. */ double alpha_l, alpha_r; /* Compute the determinants of C and X. */ double const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; if ( det_C0_C1 != 0 ) { /* Apparently Kramer's rule. */ double const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; double const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; alpha_l = det_X_C1 / det_C0_C1; alpha_r = det_C0_X / det_C0_C1; } else { /* The matrix is under-determined. Try requiring alpha_l == alpha_r. * * One way of implementing the constraint alpha_l == alpha_r is to treat them as the same * variable in the equations. We can do this by adding the columns of C to form a single * column, to be multiplied by alpha to give the column vector X. * * We try each row in turn. */ double const c0 = C[0][0] + C[0][1]; if (c0 != 0) { alpha_l = alpha_r = X[0] / c0; } else { double const c1 = C[1][0] + C[1][1]; if (c1 != 0) { alpha_l = alpha_r = X[1] / c1; } else { /* Let the below code handle this. */ alpha_l = alpha_r = 0.; } } } /* If alpha negative, use the Wu/Barsky heuristic (see text). (If alpha is 0, you get coincident control points that lead to divide by zero in any subsequent NewtonRaphsonRootFind() call.) */ /// \todo Check whether this special-casing is necessary now that /// NewtonRaphsonRootFind handles non-positive denominator. if ( alpha_l < 1.0e-6 || alpha_r < 1.0e-6 ) { alpha_l = alpha_r = ( L2( data[len - 1] - data[0] ) * (1./3.) ); } /* Control points 1 and 2 are positioned an alpha distance out on the tangent vectors, left and right, respectively. */ bezier[1] = alpha_l * tHat1 + bezier[0]; bezier[2] = alpha_r * tHat2 + bezier[3]; return; } static double lensq(QPointF const p) { return dot(p, p); } static void estimate_bi(QPointF bezier[4], unsigned const ei, QPointF const data[], double const u[], unsigned const len) { g_return_if_fail(1 <= ei && ei <= 2); unsigned const oi = 3 - ei; QPointF num(0., 0.); double den = 0.; for (unsigned i = 0; i < len; ++i) { double const ui = u[i]; double const b[4] = { B0(ui), B1(ui), B2(ui), B3(ui) }; num.rx() += b[ei] * (b[0] * bezier[0].x() + b[oi] * bezier[0].x() + b[3] * bezier[3].x() + - data[i].x()); num.ry() += b[ei] * (b[0] * bezier[0].y() + b[oi] * bezier[0].y() + b[3] * bezier[3].y() + - data[i].y()); den -= b[ei] * b[ei]; } if (den != 0.) { bezier[ei] = num / den; } else { bezier[ei] = ( oi * bezier[0] + ei * bezier[3] ) * (1./3.); } } /** * Given set of points and their parameterization, try to find a better assignment of parameter * values for the points. * * \param d Array of digitized points. * \param u Current parameter values. * \param bezCurve Current fitted curve. * \param len Number of values in both d and u arrays. * Also the size of the array that is allocated for return. */ static void reparameterize(QPointF const d[], unsigned const len, double u[], BezierCurve const bezCurve) { g_assert( 2 <= len ); unsigned const last = len - 1; g_assert( bezCurve[0] == d[0] ); g_assert( bezCurve[3] == d[last] ); g_assert( u[0] == 0.0 ); g_assert( u[last] == 1.0 ); /* Otherwise, consider including 0 and last in the below loop. */ for (unsigned i = 1; i < last; i++) { u[i] = NewtonRaphsonRootFind(bezCurve, d[i], u[i]); } } /** * Use Newton-Raphson iteration to find better root. * * \param Q Current fitted curve * \param P Digitized point * \param u Parameter value for "P" * * \return Improved u */ static double NewtonRaphsonRootFind(BezierCurve const Q, QPointF const &P, double const u) { g_assert( 0.0 <= u ); g_assert( u <= 1.0 ); /* Generate control vertices for Q'. */ QPointF Q1[3]; for (unsigned i = 0; i < 3; i++) { Q1[i] = 3.0 * ( Q[i+1] - Q[i] ); } /* Generate control vertices for Q''. */ QPointF Q2[2]; for (unsigned i = 0; i < 2; i++) { Q2[i] = 2.0 * ( Q1[i+1] - Q1[i] ); } /* Compute Q(u), Q'(u) and Q''(u). */ QPointF const Q_u = bezier_pt(3, Q, u); QPointF const Q1_u = bezier_pt(2, Q1, u); QPointF const Q2_u = bezier_pt(1, Q2, u); /* Compute f(u)/f'(u), where f is the derivative wrt u of distsq(u) = 0.5 * the square of the distance from P to Q(u). Here we're using Newton-Raphson to find a stationary point in the distsq(u), hopefully corresponding to a local minimum in distsq (and hence a local minimum distance from P to Q(u)). */ QPointF const diff = Q_u - P; double numerator = dot(diff, Q1_u); double denominator = dot(Q1_u, Q1_u) + dot(diff, Q2_u); double improved_u; if ( denominator > 0. ) { /* One iteration of Newton-Raphson: improved_u = u - f(u)/f'(u) */ improved_u = u - ( numerator / denominator ); } else { /* Using Newton-Raphson would move in the wrong direction (towards a local maximum rather than local minimum), so we move an arbitrary amount in the right direction. */ if ( numerator > 0. ) { improved_u = u * .98 - .01; } else if ( numerator < 0. ) { /* Deliberately asymmetrical, to reduce the chance of cycling. */ improved_u = .031 + u * .98; } else { improved_u = u; } } if (!isFinite(improved_u)) { improved_u = u; } else if ( improved_u < 0.0 ) { improved_u = 0.0; } else if ( improved_u > 1.0 ) { improved_u = 1.0; } /* Ensure that improved_u isn't actually worse. */ { double const diff_lensq = lensq(diff); for (double proportion = .125; ; proportion += .125) { if ( lensq( bezier_pt(3, Q, improved_u) - P ) > diff_lensq ) { if ( proportion > 1.0 ) { //g_warning("found proportion %g", proportion); improved_u = u; break; } improved_u = ( ( 1 - proportion ) * improved_u + proportion * u ); } else { break; } } } DOUBLE_ASSERT(improved_u); return improved_u; } /** * Evaluate a Bezier curve at parameter value \a t. * * \param degree The degree of the Bezier curve: 3 for cubic, 2 for quadratic etc. * \param V The control points for the Bezier curve. Must have (\a degree+1) * elements. * \param t The "parameter" value, specifying whereabouts along the curve to * evaluate. Typically in the range [0.0, 1.0]. * * Let s = 1 - t. * BezierII(1, V) gives (s, t) * V, i.e. t of the way * from V[0] to V[1]. * BezierII(2, V) gives (s**2, 2*s*t, t**2) * V. * BezierII(3, V) gives (s**3, 3 s**2 t, 3s t**2, t**3) * V. * * The derivative of BezierII(i, V) with respect to t * is i * BezierII(i-1, V'), where for all j, V'[j] = * V[j + 1] - V[j]. */ QPointF bezier_pt(unsigned const degree, QPointF const V[], double const t) { /** Pascal's triangle. */ static int const pascal[4][4] = {{1}, {1, 1}, {1, 2, 1}, {1, 3, 3, 1}}; g_assert( degree < G_N_ELEMENTS(pascal) ); double const s = 1.0 - t; /* Calculate powers of t and s. */ double spow[4]; double tpow[4]; spow[0] = 1.0; spow[1] = s; tpow[0] = 1.0; tpow[1] = t; for (unsigned i = 1; i < degree; ++i) { spow[i + 1] = spow[i] * s; tpow[i + 1] = tpow[i] * t; } QPointF ret = spow[degree] * V[0]; for (unsigned i = 1; i <= degree; ++i) { ret += pascal[degree][i] * spow[degree - i] * tpow[i] * V[i]; } return ret; } /* * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent : * Approximate unit tangents at endpoints and "center" of digitized curve */ /** * Estimate the (forward) tangent at point d[first + 0.5]. * * Unlike the center and right versions, this calculates the tangent in * the way one might expect, i.e., wrt increasing index into d. * \pre (2 \<= len) and (d[0] != d[1]). **/ QPointF sp_darray_left_tangent(QPointF const d[], unsigned const len) { g_assert( len >= 2 ); g_assert( d[0] != d[1] ); return unit_vector( d[1] - d[0] ); } /** * Estimates the (backward) tangent at d[last - 0.5]. * * \note The tangent is "backwards", i.e. it is with respect to * decreasing index rather than increasing index. * * \pre 2 \<= len. * \pre d[len - 1] != d[len - 2]. * \pre all[p in d] in_svg_plane(p). */ static QPointF sp_darray_right_tangent(QPointF const d[], unsigned const len) { g_assert( 2 <= len ); unsigned const last = len - 1; unsigned const prev = last - 1; g_assert( d[last] != d[prev] ); return unit_vector( d[prev] - d[last] ); } /** * Estimate the (forward) tangent at point d[0]. * * Unlike the center and right versions, this calculates the tangent in * the way one might expect, i.e., wrt increasing index into d. * * \pre 2 \<= len. * \pre d[0] != d[1]. * \pre all[p in d] in_svg_plane(p). * \post is_unit_vector(ret). **/ QPointF sp_darray_left_tangent(QPointF const d[], unsigned const len, double const tolerance_sq) { g_assert( 2 <= len ); g_assert( 0 <= tolerance_sq ); for (unsigned i = 1;;) { QPointF const pi(d[i]); QPointF const t(pi - d[0]); double const distsq = dot(t, t); if ( tolerance_sq < distsq ) { return unit_vector(t); } ++i; if (i == len) { return ( distsq == 0 ? sp_darray_left_tangent(d, len) : unit_vector(t) ); } } } /** * Estimates the (backward) tangent at d[last]. * * \note The tangent is "backwards", i.e. it is with respect to * decreasing index rather than increasing index. * * \pre 2 \<= len. * \pre d[len - 1] != d[len - 2]. * \pre all[p in d] in_svg_plane(p). */ QPointF sp_darray_right_tangent(QPointF const d[], unsigned const len, double const tolerance_sq) { g_assert( 2 <= len ); g_assert( 0 <= tolerance_sq ); unsigned const last = len - 1; for (unsigned i = last - 1;; i--) { QPointF const pi(d[i]); QPointF const t(pi - d[last]); double const distsq = dot(t, t); if ( tolerance_sq < distsq ) { return unit_vector(t); } if (i == 0) { return ( distsq == 0 ? sp_darray_right_tangent(d, len) : unit_vector(t) ); } } } /** * Estimates the (backward) tangent at d[center], by averaging the two * segments connected to d[center] (and then normalizing the result). * * \note The tangent is "backwards", i.e. it is with respect to * decreasing index rather than increasing index. * * \pre (0 \< center \< len - 1) and d is uniqued (at least in * the immediate vicinity of \a center). */ static QPointF sp_darray_center_tangent(QPointF const d[], unsigned const center, unsigned const len) { g_assert( center != 0 ); g_assert( center < len - 1 ); QPointF ret; if ( d[center + 1] == d[center - 1] ) { /* Rotate 90 degrees in an arbitrary direction. */ QPointF const diff = d[center] - d[center - 1]; ret = rot90(diff); } else { ret = d[center - 1] - d[center + 1]; } return unit_vector(ret); } /** * Assign parameter values to digitized points using relative distances between points. * * \pre Parameter array u must have space for \a len items. */ static void chord_length_parameterize(QPointF const d[], double u[], unsigned const len) { g_return_if_fail( 2 <= len ); /* First let u[i] equal the distance travelled along the path from d[0] to d[i]. */ u[0] = 0.0; for (unsigned i = 1; i < len; i++) { double const dist = L2( d[i] - d[i-1] ); u[i] = u[i-1] + dist; } /* Then scale to [0.0 .. 1.0]. */ double tot_len = u[len - 1]; g_return_if_fail( tot_len != 0 ); if (isFinite(tot_len)) { for (unsigned i = 1; i < len; ++i) { u[i] /= tot_len; } } else { /* We could do better, but this probably never happens anyway. */ for (unsigned i = 1; i < len; ++i) { u[i] = i / (double) ( len - 1 ); } } /** \todo * It's been reported that u[len - 1] can differ from 1.0 on some * systems (amd64), despite it having been calculated as x / x where x * is isFinite and non-zero. */ if (u[len - 1] != 1) { double const diff = u[len - 1] - 1; if (fabs(diff) > 1e-13) { fprintf(stderr, "u[len - 1] = %19g (= 1 + %19g), expecting exactly 1", u[len - 1], diff); } u[len - 1] = 1; } #ifdef BEZIER_DEBUG g_assert( u[0] == 0.0 && u[len - 1] == 1.0 ); for (unsigned i = 1; i < len; i++) { g_assert( u[i] >= u[i-1] ); } #endif } /** * Find the maximum squared distance of digitized points to fitted curve, and (if this maximum * error is non-zero) set \a *splitPoint to the corresponding index. * * \pre 2 \<= len. * \pre u[0] == 0. * \pre u[len - 1] == 1.0. * \post ((ret == 0.0) * || ((*splitPoint \< len - 1) * \&\& (*splitPoint != 0 || ret \< 0.0))). */ static double compute_max_error_ratio(QPointF const d[], double const u[], unsigned const len, BezierCurve const bezCurve, double const tolerance, unsigned *const splitPoint) { g_assert( 2 <= len ); unsigned const last = len - 1; g_assert( bezCurve[0] == d[0] ); g_assert( bezCurve[3] == d[last] ); g_assert( u[0] == 0.0 ); g_assert( u[last] == 1.0 ); /* I.e. assert that the error for the first & last points is zero. * Otherwise we should include those points in the below loop. * The assertion is also necessary to ensure 0 < splitPoint < last. */ double maxDistsq = 0.0; /* Maximum error */ double max_hook_ratio = 0.0; unsigned snap_end = 0; QPointF prev = bezCurve[0]; for (unsigned i = 1; i <= last; i++) { QPointF const curr = bezier_pt(3, bezCurve, u[i]); double const distsq = lensq( curr - d[i] ); if ( distsq > maxDistsq ) { maxDistsq = distsq; *splitPoint = i; } double const hook_ratio = compute_hook(prev, curr, .5 * (u[i - 1] + u[i]), bezCurve, tolerance); if (max_hook_ratio < hook_ratio) { max_hook_ratio = hook_ratio; snap_end = i; } prev = curr; } double const dist_ratio = sqrt(maxDistsq) / tolerance; double ret; if (max_hook_ratio <= dist_ratio) { ret = dist_ratio; } else { g_assert(0 < snap_end); ret = -max_hook_ratio; *splitPoint = snap_end - 1; } g_assert( ret == 0.0 || ( ( *splitPoint < last ) && ( *splitPoint != 0 || ret < 0. ) ) ); return ret; } /** * Whereas compute_max_error_ratio() checks for itself that each data point * is near some point on the curve, this function checks that each point on * the curve is near some data point (or near some point on the polyline * defined by the data points, or something like that: we allow for a * "reasonable curviness" from such a polyline). "Reasonable curviness" * means we draw a circle centred at the midpoint of a..b, of radius * proportional to the length |a - b|, and require that each point on the * segment of bezCurve between the parameters of a and b be within that circle. * If any point P on the bezCurve segment is outside of that allowable * region (circle), then we return some metric that increases with the * distance from P to the circle. * * Given that this is a fairly arbitrary criterion for finding appropriate * places for sharp corners, we test only one point on bezCurve, namely * the point on bezCurve with parameter halfway between our estimated * parameters for a and b. (Alternatives are taking the farthest of a * few parameters between those of a and b, or even using a variant of * NewtonRaphsonFindRoot() for finding the maximum rather than minimum * distance.) */ static double compute_hook(QPointF const &a, QPointF const &b, double const u, BezierCurve const bezCurve, double const tolerance) { QPointF const P = bezier_pt(3, bezCurve, u); QPointF const diff = .5 * (a + b) - P; double const dist = L2(diff); if (dist < tolerance) { return 0; } // factor of 0.1 introduced by JSS to stop more hooks double const allowed = L2(b - a)*0.1 + tolerance; return dist / allowed; /** \todo * effic: Hooks are very rare. We could start by comparing * distsq, only resorting to the more expensive L2 in cases of * uncertainty. */ } veusz-1.15/helpers/src/_nc_cntr.c0000644002344000001440000016743611734662204017002 0ustar jssusers00000000000000/* cntr.c General purpose contour tracer for quadrilateral meshes. Handles single level contours, or region between a pair of levels. The routines that do all the work, as well as the explanatory comments, came from gcntr.c, part of the GIST package. The original mpl interface was also based on GIST. The present interface uses parts of the original, but places them in the entirely different framework of a Python type. It was written by following the Python "Extending and Embedding" tutorial. */ /* LICENSE AGREEMENT FOR MATPLOTLIB 0.84 -------------------------------------- 1. This LICENSE AGREEMENT is between John D. Hunter ("JDH"), and the Individual or Organization ("Licensee") accessing and otherwise using matplotlib software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, JDH hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use matplotlib 0.84 alone or in any derivative version, provided, however, that JDH's License Agreement and JDH's notice of copyright, i.e., "Copyright (c) 2002-2005 John D. Hunter; All Rights Reserved" are retained in matplotlib 0.84 alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates matplotlib 0.84 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to matplotlib 0.84. 4. JDH is making matplotlib 0.84 available to Licensee on an "AS IS" basis. JDH MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, JDH MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB 0.84 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. JDH SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB 0.84 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING MATPLOTLIB 0.84, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between JDH and Licensee. This License Agreement does not grant permission to use JDH trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using matplotlib 0.84, Licensee agrees to be bound by the terms and conditions of this License Agreement. */ #include #include "structmember.h" #include #include /* modified by jss */ #define NUMERIC #include "numpy/arrayobject.h" #ifndef PyArray_SBYTE #include "numpy/oldnumeric.h" #endif /* Note that all arrays in these routines are Fortran-style, in the sense that the "i" index varies fastest; the dimensions of the corresponding C array are z[jmax][imax] in the notation used here. We can identify i and j with the x and y dimensions, respectively. */ /* What is a contour? * * Given a quadrilateral mesh (x,y), and values of a z at the points * of that mesh, we seek a set of polylines connecting points at a * particular value of z. Each point on such a contour curve lies * on an edge of the mesh, at a point linearly interpolated to the * contour level z0 between the given values of z at the endpoints * of the edge. * * Identifying these points is easy. Figuring out how to connect them * into a curve -- or possibly a set of disjoint curves -- is difficult. * Each disjoint curve may be either a closed circuit, or it may begin * and end on a mesh boundary. * * One of the problems with a quadrilateral mesh is that when the z * values at one pair of diagonally opposite points lie below z0, and * the values at the other diagonal pair of the same zone lie above z0, * all four edges of the zone are cut, and there is an ambiguity in * how we should connect the points. I call this a saddle zone. * The problem is that two disjoint curves cut through a saddle zone * (I reject the alternative of connecting the opposite points to make * a single self-intersecting curve, since those make ugly contour plots * -- I've tried it). The real problem with saddle zones is that you * need to communicate the connectivity decision you make back to the * calling routine, since for the next contour level, we need to tell * the contour tracer to make the same decision as on the previous * level. The input/output triangulation array is the solution to this * nasty problem. * * Another complicating factor is that there may be logical holes in * the mesh -- zones which do not exist. We want our contours to stop * if they hit the edge of such a zone, just as if they'd hit the edge * of the whole mesh. The input region array addresses this issue. * * Yet another complication: We may want a list of closed polygons which * outline the region between two contour levels z0 and z1. These may * include sections of the mesh boundary (including edges of logical * holes defined by the region array), in addition to sections of the * contour curves at one or both levels. This introduces a huge * topological problem -- if one of the closed contours (possibly * including an interior logical hole in the mesh, but not any part of * the boundary of the whole mesh) encloses a region which is not * between z0 and z1, that curve must be connected by a slit (or "branch * cut") to the enclosing curve, so that the list of disjoint polygons * we return is each simply connected. * * Okay, one final stunning difficulty: For the two level case, no * individual polygon should have more than a few thousand sides, since * huge filled polygons place an inordinate load on rendering software, * which needs an amount of scratch space proportional to the number * of sides it needs to fill. So in the two level case, we want to * chunk the mesh into rectangular pieces of no more than, say, 30x30 * zones, which keeps each returned polygon to less than a few thousand * sides (the worst case is very very bad -- you can easily write down * a function and two level values which produce a polygon that cuts * every edge of the mesh twice). */ /* * Here is the numbering scheme for points, edges, and zones in * the mesh -- note that each ij corresponds to one point, one zone, * one i-edge (i=constant edge) and one j-edge (j=constant edge): * * (ij-1)-------(ij)-------(ij) * | | * | | * | | * (ij-1) (ij) (ij) * | | * | | * | | * (ij-iX-1)----(ij-iX)----(ij-iX) * * At each point, the function value is either 0, 1, or 2, depending * on whether it is below z0, between z0 and z1, or above z1. * Each zone either exists (1) or not (0). * From these three bits of data, all of the curve connectivity follows. * * The tracing algorithm is naturally edge-based: Either you are at a * point where a level cuts an edge, ready to step across a zone to * another edge, or you are drawing the edge itself, if it happens to * be a boundary with at least one section between z0 and z1. * * In either case, the edge is a directed edge -- either the zone * you are advancing into is to its left or right, or you are actually * drawing it. I always trace curves keeping the region between z0 and * z1 to the left of the curve. If I'm tracing a boundary, I'm always * moving CCW (counter clockwise) around the zone that exists. And if * I'm about to cross a zone, I'll make the direction of the edge I'm * sitting on be such that the zone I'm crossing is to its left. * * I start tracing each curve near its lower left corner (mesh oriented * as above), which is the first point I encounter scanning through the * mesh in order. When I figure the 012 z values and zonal existence, * I also mark the potential starting points: Each edge may harbor a * potential starting point corresponding to either direction, so there * are four start possibilities at each ij point. Only the following * possibilities need to be marked as potential starting edges: * * +-+-+-+ * | | | | * A-0-C-+ One or both levels cut E and have z=1 above them, and * | EZ| | 0A is cut and either 0C is cut or CD is cut. * +-B-D-+ Or, one or both levels cut E and E is a boundary edge. * | | | | (and Z exists) * +-+-+-+ * * +-+-+-+ * | | | | * +-A-0-C One or both levels cut E and have z=1 below them, and * | |ZE | 0A is cut and either 0C is cut or CD is cut. * +-+-B-D Or, one or both levels cut E and E is a boundary edge. * | | | | (and Z exists) * +-+-+-+ * * +-+-+-+ * | | | | * +-+-+-+ E is a boundary edge, Z exists, at some point on E * | |Z| | lies between the levels. * +-+E+-+ * | | | | * +-+-+-+ * * +-+-+-+ * | | | | * +-+E+-+ E is a boundary edge, Z exists, at some point on E * | |Z| | lies between the levels. * +-+-+-+ * | | | | * +-+-+-+ * * During the first tracing pass, the start mark is erased whenever * any non-starting edge is encountered, reducing the number of points * that need to be considered for the second pass. The first pass * makes the basic connectivity decisions. It figures out how many * disjoint curves there will be, and identifies slits for the two level * case or open contours for the single level case, and removes all but * the actual start markers. A second tracing pass can perform the * actual final trace. */ /* ------------------------------------------------------------------------ */ /* the data about edges, zones, and points -- boundary or not, exists * or not, z value 0, 1, or 2 -- is kept in a mesh sized data array */ typedef short Cdata; /* here is the minimum structure required to tell where we are in the * mesh sized data array */ typedef struct Csite Csite; struct Csite { long edge; /* ij of current edge */ long left; /* +-1 or +-imax as the zone is to right, left, below, * or above the edge */ long imax; /* imax for the mesh */ long jmax; /* jmax for the mesh */ long n; /* number of points marked on this curve so far */ long count; /* count of start markers visited */ double zlevel[2]; /* contour levels, zlevel[1]<=zlevel[0] * signals single level case */ short *triangle; /* triangulation array for the mesh */ char *reg; /* region array for the mesh (was int) */ Cdata *data; /* added by EF */ long edge0, left0; /* starting site on this curve for closure */ int level0; /* starting level for closure */ long edge00; /* site needing START_ROW mark */ /* making the actual marks requires a bunch of other stuff */ const double *x, *y, *z; /* mesh coordinates and function values */ double *xcp, *ycp; /* output contour points */ }; #if 0 static void print_Csite(Csite *Csite) { Cdata *data = Csite->data; int i, j, ij; int nd = Csite->imax * (Csite->jmax + 1) + 1; printf("zlevels: %8.2lg %8.2lg\n", Csite->zlevel[0], Csite->zlevel[1]); printf("edge %ld, left %ld, n %ld, count %ld, edge0 %ld, left0 %ld\n", Csite->edge, Csite->left, Csite->n, Csite->count, Csite->edge0, Csite->left0); printf(" level0 %d, edge00 %ld\n", Csite->level0, Csite->edge00); printf("%04x\n", data[nd-1]); for (j = Csite->jmax; j >= 0; j--) { for (i=0; i < Csite->imax; i++) { ij = i + j * Csite->imax; printf("%04x ", data[ij]); } printf("\n"); } printf("\n"); } #endif /* triangle only takes values of -1, 0, 1, so it could be a signed char. */ /* most or all of the longs probably could be converted to ints with no loss */ /* the Cdata array consists of the following bits: * Z_VALUE (2 bits) 0, 1, or 2 function value at point * ZONE_EX 1 zone exists, 0 zone doesn't exist * I_BNDY this i-edge (i=constant edge) is a mesh boundary * J_BNDY this j-edge (i=constant edge) is a mesh boundary * I0_START this i-edge is a start point into zone to left * I1_START this i-edge is a start point into zone to right * J0_START this j-edge is a start point into zone below * J1_START this j-edge is a start point into zone above * START_ROW next start point is in current row (accelerates 2nd pass) * SLIT_UP marks this i-edge as the beginning of a slit upstroke * SLIT_DN marks this i-edge as the beginning of a slit downstroke * OPEN_END marks an i-edge start point whose other endpoint is * on a boundary for the single level case * ALL_DONE marks final start point */ #define Z_VALUE 0x0003 #define ZONE_EX 0x0004 #define I_BNDY 0x0008 #define J_BNDY 0x0010 #define I0_START 0x0020 #define I1_START 0x0040 #define J0_START 0x0080 #define J1_START 0x0100 #define START_ROW 0x0200 #define SLIT_UP 0x0400 #define SLIT_DN 0x0800 #define OPEN_END 0x1000 #define ALL_DONE 0x2000 /* some helpful macros to find points relative to a given directed * edge -- points are designated 0, 1, 2, 3 CCW around zone with 0 and * 1 the endpoints of the current edge */ #define FORWARD(left,ix) ((left)>0?((left)>1?1:-(ix)):((left)<-1?-1:(ix))) #define POINT0(edge,fwd) ((edge)-((fwd)>0?fwd:0)) #define POINT1(edge,fwd) ((edge)+((fwd)<0?fwd:0)) #define IS_JEDGE(edge,left) ((left)>0?((left)>1?1:0):((left)<-1?1:0)) #define ANY_START (I0_START|I1_START|J0_START|J1_START) #define START_MARK(left) \ ((left)>0?((left)>1?J1_START:I1_START):((left)<-1?J0_START:I0_START)) /* ------------------------------------------------------------------------ */ /* these actually mark points */ static int zone_crosser (Csite * site, int level, int pass2); static int edge_walker (Csite * site, int pass2); static int slit_cutter (Csite * site, int up, int pass2); /* this calls the first three to trace the next disjoint curve * -- return value is number of points on this curve, or * 0 if there are no more curves this pass * -(number of points) on first pass if: * this is two level case, and the curve closed on a hole * this is single level case, curve is open, and will start from * a different point on the second pass * -- in both cases, this curve will be combined with another * on the second pass */ static long curve_tracer (Csite * site, int pass2); /* this initializes the data array for curve_tracer */ static void data_init (Csite * site, int region, long nchunk); /* ------------------------------------------------------------------------ */ /* zone_crosser assumes you are sitting at a cut edge about to cross * the current zone. It always marks the initial point, crosses at * least one zone, and marks the final point. On non-boundary i-edges, * it is responsible for removing start markers on the first pass. */ static int zone_crosser (Csite * site, int level, int pass2) { Cdata * data = site->data; long edge = site->edge; long left = site->left; long n = site->n; long fwd = FORWARD (left, site->imax); long p0, p1; int jedge = IS_JEDGE (edge, left); long edge0 = site->edge0; long left0 = site->left0; int level0 = site->level0 == level; int two_levels = site->zlevel[1] > site->zlevel[0]; short *triangle = site->triangle; const double *x = pass2 ? site->x : 0; const double *y = pass2 ? site->y : 0; const double *z = pass2 ? site->z : 0; double zlevel = pass2 ? site->zlevel[level] : 0.0; double *xcp = pass2 ? site->xcp : 0; double *ycp = pass2 ? site->ycp : 0; int z0, z1, z2, z3; int keep_left = 0; /* flag to try to minimize curvature in saddles */ int done = 0; if (level) level = 2; for (;;) { /* set edge endpoints */ p0 = POINT0 (edge, fwd); p1 = POINT1 (edge, fwd); /* always mark cut on current edge */ if (pass2) { /* second pass actually computes and stores the point */ double zcp = (zlevel - z[p0]) / (z[p1] - z[p0]); xcp[n] = zcp * (x[p1] - x[p0]) + x[p0]; ycp[n] = zcp * (y[p1] - y[p0]) + y[p0]; } if (!done && !jedge) { if (n) { /* if this is not the first point on the curve, and we're * not done, and this is an i-edge, check several things */ if (!two_levels && !pass2 && (data[edge] & OPEN_END)) { /* reached an OPEN_END mark, skip the n++ */ done = 4; /* same return value 4 used below */ break; } /* check for curve closure -- if not, erase any start mark */ if (edge == edge0 && left == left0) { /* may signal closure on a downstroke */ if (level0) done = (!pass2 && two_levels && left < 0) ? 5 : 3; } else if (!pass2) { Cdata start = data[edge] & (fwd > 0 ? I0_START : I1_START); if (start) { data[edge] &= ~start; site->count--; } if (!two_levels) { start = data[edge] & (fwd > 0 ? I1_START : I0_START); if (start) { data[edge] &= ~start; site->count--; } } } } } n++; if (done) break; /* cross current zone to another cut edge */ z0 = (data[p0] & Z_VALUE) != level; /* 1 if fill toward p0 */ z1 = !z0; /* know level cuts edge */ z2 = (data[p1 + left] & Z_VALUE) != level; z3 = (data[p0 + left] & Z_VALUE) != level; if (z0 == z2) { if (z1 == z3) { /* this is a saddle zone, need triangle to decide * -- set triangle if not already decided for this zone */ long zone = edge + (left > 0 ? left : 0); if (triangle) { if (!triangle[zone]) { if (keep_left) triangle[zone] = jedge ? -1 : 1; else triangle[zone] = jedge ? 1 : -1; } if (triangle[zone] > 0 ? !jedge : jedge) goto bkwd; } else { if (keep_left) goto bkwd; } } /* bend forward (right along curve) */ keep_left = 1; jedge = !jedge; edge = p1 + (left > 0 ? left : 0); { long tmp = fwd; fwd = -left; left = tmp; } } else if (z1 == z3) { bkwd: /* bend backward (left along curve) */ keep_left = 0; jedge = !jedge; edge = p0 + (left > 0 ? left : 0); { long tmp = fwd; fwd = left; left = -tmp; } } else { /* straight across to opposite edge */ edge += left; } /* after crossing zone, edge/left/fwd is oriented CCW relative to * the next zone, assuming we will step there */ /* now that we've taken a step, check for the downstroke * of a slit on the second pass (upstroke checked above) * -- taking step first avoids a race condition */ if (pass2 && two_levels && !jedge) { if (left > 0) { if (data[edge] & SLIT_UP) done = 6; } else { if (data[edge] & SLIT_DN) done = 5; } } if (!done) { /* finally, check if we are on a boundary */ if (data[edge] & (jedge ? J_BNDY : I_BNDY)) { done = two_levels ? 2 : 4; /* flip back into the zone that exists */ left = -left; fwd = -fwd; if (!pass2 && (edge != edge0 || left != left0)) { Cdata start = data[edge] & START_MARK (left); if (start) { data[edge] &= ~start; site->count--; } } } } } site->edge = edge; site->n = n; site->left = left; return done > 4 ? slit_cutter (site, done - 5, pass2) : done; } /* edge_walker assumes that the current edge is being drawn CCW * around the current zone. Since only boundary edges are drawn * and we always walk around with the filled region to the left, * no edge is ever drawn CW. We attempt to advance to the next * edge on this boundary, but if current second endpoint is not * between the two contour levels, we exit back to zone_crosser. * Note that we may wind up marking no points. * -- edge_walker is never called for single level case */ static int edge_walker (Csite * site, int pass2) { Cdata * data = site->data; long edge = site->edge; long left = site->left; long n = site->n; long fwd = FORWARD (left, site->imax); long p0 = POINT0 (edge, fwd); long p1 = POINT1 (edge, fwd); int jedge = IS_JEDGE (edge, left); long edge0 = site->edge0; long left0 = site->left0; int level0 = site->level0 == 2; int marked; const double *x = pass2 ? site->x : 0; const double *y = pass2 ? site->y : 0; double *xcp = pass2 ? site->xcp : 0; double *ycp = pass2 ? site->ycp : 0; int z0, z1, heads_up = 0; for (;;) { /* mark endpoint 0 only if value is 1 there, and this is a * two level task */ z0 = data[p0] & Z_VALUE; z1 = data[p1] & Z_VALUE; marked = 0; if (z0 == 1) { /* mark current boundary point */ if (pass2) { xcp[n] = x[p0]; ycp[n] = y[p0]; } marked = 1; } else if (!n) { /* if this is the first point is not between the levels * must do the job of the zone_crosser and mark the first cut here, * so that it will be marked again by zone_crosser as it closes */ if (pass2) { double zcp = site->zlevel[(z0 != 0)]; zcp = (zcp - site->z[p0]) / (site->z[p1] - site->z[p0]); xcp[n] = zcp * (x[p1] - x[p0]) + x[p0]; ycp[n] = zcp * (y[p1] - y[p0]) + y[p0]; } marked = 1; } if (n) { /* check for closure */ if (level0 && edge == edge0 && left == left0) { site->edge = edge; site->left = left; site->n = n + marked; /* if the curve is closing on a hole, need to make a downslit */ if (fwd < 0 && !(data[edge] & (jedge ? J_BNDY : I_BNDY))) return slit_cutter (site, 0, pass2); return 3; } else if (pass2) { if (heads_up || (fwd < 0 && (data[edge] & SLIT_DN))) { site->edge = edge; site->left = left; site->n = n + marked; return slit_cutter (site, heads_up, pass2); } } else { /* if this is not first point, clear start mark for this edge */ Cdata start = data[edge] & START_MARK (left); if (start) { data[edge] &= ~start; site->count--; } } } if (marked) n++; /* if next endpoint not between levels, need to exit to zone_crosser */ if (z1 != 1) { site->edge = edge; site->left = left; site->n = n; return (z1 != 0); /* return level closest to p1 */ } /* step to p1 and find next edge * -- turn left if possible, else straight, else right * -- check for upward slit beginning at same time */ edge = p1 + (left > 0 ? left : 0); if (pass2 && jedge && fwd > 0 && (data[edge] & SLIT_UP)) { jedge = !jedge; heads_up = 1; } else if (data[edge] & (jedge ? I_BNDY : J_BNDY)) { long tmp = fwd; fwd = left; left = -tmp; jedge = !jedge; } else { edge = p1 + (fwd > 0 ? fwd : 0); if (pass2 && !jedge && fwd > 0 && (data[edge] & SLIT_UP)) { heads_up = 1; } else if (!(data[edge] & (jedge ? J_BNDY : I_BNDY))) { edge = p1 - (left < 0 ? left : 0); jedge = !jedge; { long tmp = fwd; fwd = -left; left = tmp; } } } p0 = p1; p1 = POINT1 (edge, fwd); } } /* -- slit_cutter is never called for single level case */ static int slit_cutter (Csite * site, int up, int pass2) { Cdata * data = site->data; long imax = site->imax; long n = site->n; const double *x = pass2 ? site->x : 0; const double *y = pass2 ? site->y : 0; double *xcp = pass2 ? site->xcp : 0; double *ycp = pass2 ? site->ycp : 0; if (up) { /* upward stroke of slit proceeds up left side of slit until * it hits a boundary or a point not between the contour levels * -- this never happens on the first pass */ long p1 = site->edge; int z1; for (;;) { z1 = data[p1] & Z_VALUE; if (z1 != 1) { site->edge = p1; site->left = -1; site->n = n; return (z1 != 0); } else if (data[p1] & J_BNDY) { /* this is very unusual case of closing on a mesh hole */ site->edge = p1; site->left = -imax; site->n = n; return 2; } xcp[n] = x[p1]; ycp[n] = y[p1]; n++; p1 += imax; } } else { /* downward stroke proceeds down right side of slit until it * hits a boundary or point not between the contour levels */ long p0 = site->edge; int z0; /* at beginning of first pass, mark first i-edge with SLIT_DN */ data[p0] |= SLIT_DN; p0 -= imax; for (;;) { z0 = data[p0] & Z_VALUE; if (!pass2) { if (z0 != 1 || (data[p0] & I_BNDY) || (data[p0 + 1] & J_BNDY)) { /* at end of first pass, mark final i-edge with SLIT_UP */ data[p0 + imax] |= SLIT_UP; /* one extra count for splicing at outer curve */ site->n = n + 1; return 4; /* return same special value as for OPEN_END */ } } else { if (z0 != 1) { site->edge = p0 + imax; site->left = 1; site->n = n; return (z0 != 0); } else if (data[p0 + 1] & J_BNDY) { site->edge = p0 + 1; site->left = imax; site->n = n; return 2; } else if (data[p0] & I_BNDY) { site->edge = p0; site->left = 1; site->n = n; return 2; } } if (pass2) { xcp[n] = x[p0]; ycp[n] = y[p0]; n++; } else { /* on first pass need to count for upstroke as well */ n += 2; } p0 -= imax; } } } /* ------------------------------------------------------------------------ */ /* curve_tracer finds the next starting point, then traces the curve, * returning the number of points on this curve * -- in a two level trace, the return value is negative on the * first pass if the curve closed on a hole * -- in a single level trace, the return value is negative on the * first pass if the curve is an incomplete open curve * -- a return value of 0 indicates no more curves */ static long curve_tracer (Csite * site, int pass2) { Cdata * data = site->data; long imax = site->imax; long edge0 = site->edge0; long left0 = site->left0; long edge00 = site->edge00; int two_levels = site->zlevel[1] > site->zlevel[0]; int level, level0, mark_row; long n; /* it is possible for a single i-edge to serve as two actual start * points, one to the right and one to the left * -- for the two level case, this happens on the first pass for * a doubly cut edge, or on a chunking boundary * -- for single level case, this is impossible, but a similar * situation involving open curves is handled below * a second two start possibility is when the edge0 zone does not * exist and both the i-edge and j-edge boundaries are cut * yet another possibility is three start points at a junction * of chunk cuts * -- sigh, several other rare possibilities, * allow for general case, just go in order i1, i0, j1, j0 */ int two_starts; /* printf("curve_tracer pass %d\n", pass2); */ /* print_Csite(site); */ if (left0 == 1) two_starts = data[edge0] & (I0_START | J1_START | J0_START); else if (left0 == -1) two_starts = data[edge0] & (J1_START | J0_START); else if (left0 == imax) two_starts = data[edge0] & J0_START; else two_starts = 0; if (pass2 || edge0 == 0) { /* zip up to row marked on first pass (or by data_init if edge0==0) * -- but not for double start case */ if (!two_starts) { /* final start point marked by ALL_DONE marker */ int first = (edge0 == 0 && !pass2); long e0 = edge0; if (data[edge0] & ALL_DONE) return 0; while (!(data[edge0] & START_ROW)) edge0 += imax; if (e0 == edge0) edge0++; /* two starts handled specially */ if (first) /* if this is the very first start point, we want to remove * the START_ROW marker placed by data_init */ data[edge0 - edge0 % imax] &= ~START_ROW; } } else { /* first pass ends when all potential start points visited */ if (site->count <= 0) { /* place ALL_DONE marker for second pass */ data[edge00] |= ALL_DONE; /* reset initial site for second pass */ site->edge0 = site->edge00 = site->left0 = 0; return 0; } if (!two_starts) edge0++; } if (two_starts) { /* trace second curve with this start immediately */ if (left0 == 1 && (data[edge0] & I0_START)) { left0 = -1; level = (data[edge0] & I_BNDY) ? 2 : 0; } else if ((left0 == 1 || left0 == -1) && (data[edge0] & J1_START)) { left0 = imax; level = 2; } else { left0 = -imax; level = 2; } } else { /* usual case is to scan for next start marker * -- on second pass, this is at most one row of mesh, but first * pass hits nearly every point of the mesh, since it can't * know in advance which potential start marks removed */ while (!(data[edge0] & ANY_START)) edge0++; if (data[edge0] & I1_START) left0 = 1; else if (data[edge0] & I0_START) left0 = -1; else if (data[edge0] & J1_START) left0 = imax; else /*data[edge0]&J0_START */ left0 = -imax; if (data[edge0] & (I1_START | I0_START)) level = (data[edge0] & I_BNDY) ? 2 : 0; else level = 2; } /* this start marker will not be unmarked, but it has been visited */ if (!pass2) site->count--; /* if this curve starts on a non-boundary i-edge, we need to * determine the level */ if (!level && two_levels) level = left0 > 0 ? ((data[edge0 - imax] & Z_VALUE) != 0) : ((data[edge0] & Z_VALUE) != 0); /* initialize site for this curve */ site->edge = site->edge0 = edge0; site->left = site->left0 = left0; site->level0 = level0 = level; /* for open curve detection only */ /* single level case just uses zone_crosser */ if (!two_levels) level = 0; /* to generate the curve, alternate between zone_crosser and * edge_walker until closure or first call to edge_walker in * single level case */ site->n = 0; for (;;) { if (level < 2) level = zone_crosser (site, level, pass2); else if (level < 3) level = edge_walker (site, pass2); else break; } n = site->n; /* single level case may have ended at a boundary rather than closing * -- need to recognize this case here in order to place the * OPEN_END mark for zone_crosser, remove this start marker, * and be sure not to make a START_ROW mark for this case * two level case may close with slit_cutter, in which case start * must also be removed and no START_ROW mark made * -- change sign of return n to inform caller */ if (!pass2 && level > 3 && (two_levels || level0 == 0)) { if (!two_levels) data[edge0] |= OPEN_END; data[edge0] &= ~(left0 > 0 ? I1_START : I0_START); mark_row = 0; /* do not mark START_ROW */ n = -n; } else { if (two_levels) mark_row = !two_starts; else mark_row = 1; } /* on first pass, must apply START_ROW mark in column above previous * start marker * -- but skip if we just did second of two start case */ if (!pass2 && mark_row) { data[edge0 - (edge0 - edge00) % imax] |= START_ROW; site->edge00 = edge0; } return n; } /* ------------------------------------------------------------------------ */ /* The sole function of the "region" argument is to specify the value in Csite.reg that denotes a missing zone. We always use zero. */ static void data_init (Csite * site, int region, long nchunk) { Cdata * data = site->data; long imax = site->imax; long jmax = site->jmax; long ijmax = imax * jmax; const double *z = site->z; double zlev0 = site->zlevel[0]; double zlev1 = site->zlevel[1]; int two_levels = zlev1 > zlev0; char *reg = site->reg; long count = 0; int started = 0; int ibndy, jbndy, i_was_chunk; long icsize = imax - 1; long jcsize = jmax - 1; long ichunk, jchunk, irem, jrem, i, j, ij; if (nchunk && two_levels) { /* figure out chunk sizes * -- input nchunk is square root of maximum allowed zones per chunk * -- start points for single level case are wrong, so don't try it */ long inum = (nchunk * nchunk) / (jmax - 1); long jnum = (nchunk * nchunk) / (imax - 1); if (inum < nchunk) inum = nchunk; if (jnum < nchunk) jnum = nchunk; /* ijnum= actual number of chunks, * ijrem= number of those chunks needing one more zone (ijcsize+1) */ inum = (imax - 2) / inum + 1; icsize = (imax - 1) / inum; irem = (imax - 1) % inum; jnum = (jmax - 2) / jnum + 1; jcsize = (jmax - 1) / jnum; jrem = (jmax - 1) % jnum; /* convert ijrem into value of i or j at which to begin adding an * extra zone */ irem = (inum - irem) * icsize; jrem = (jnum - jrem) * jcsize; } else { irem = imax; jrem = jmax; } /* do everything in a single pass through the data array to * minimize cache faulting (z, reg, and data are potentially * very large arrays) * access to the z and reg arrays is strictly sequential, * but we need two rows (+-imax) of the data array at a time */ if (z[0] > zlev0) data[0] = (two_levels && z[0] > zlev1) ? 2 : 1; else data[0] = 0; jchunk = 0; for (j = ij = 0; j < jmax; j++) { ichunk = i_was_chunk = 0; for (i = 0; i < imax; i++, ij++) { /* transfer zonal existence from reg to data array * -- get these for next row so we can figure existence of * points and j-edges for this row */ data[ij + imax + 1] = 0; if (reg) { if (region ? (reg[ij + imax + 1] == region) : (reg[ij + imax + 1] != 0)) data[ij + imax + 1] = ZONE_EX; } else { if (i < imax - 1 && j < jmax - 1) data[ij + imax + 1] = ZONE_EX; } /* translate z values to 0, 1, 2 flags */ if (ij < imax) data[ij + 1] = 0; if (ij < ijmax - 1 && z[ij + 1] > zlev0) data[ij + 1] |= (two_levels && z[ij + 1] > zlev1) ? 2 : 1; /* apply edge boundary marks */ ibndy = i == ichunk || (data[ij] & ZONE_EX) != (data[ij + 1] & ZONE_EX); jbndy = j == jchunk || (data[ij] & ZONE_EX) != (data[ij + imax] & ZONE_EX); if (ibndy) data[ij] |= I_BNDY; if (jbndy) data[ij] |= J_BNDY; /* apply i-edge start marks * -- i-edges are only marked when actually cut * -- no mark is necessary if one of the j-edges which share * the lower endpoint is also cut * -- no I0 mark necessary unless filled region below some cut, * no I1 mark necessary unless filled region above some cut */ if (j) { int v0 = (data[ij] & Z_VALUE); int vb = (data[ij - imax] & Z_VALUE); if (v0 != vb) { /* i-edge is cut */ if (ibndy) { if (data[ij] & ZONE_EX) { data[ij] |= I0_START; count++; } if (data[ij + 1] & ZONE_EX) { data[ij] |= I1_START; count++; } } else { int va = (data[ij - 1] & Z_VALUE); int vc = (data[ij + 1] & Z_VALUE); int vd = (data[ij - imax + 1] & Z_VALUE); if (v0 != 1 && va != v0 && (vc != v0 || vd != v0) && (data[ij] & ZONE_EX)) { data[ij] |= I0_START; count++; } if (vb != 1 && va == vb && (vc == vb || vd == vb) && (data[ij + 1] & ZONE_EX)) { data[ij] |= I1_START; count++; } } } } /* apply j-edge start marks * -- j-edges are only marked when they are boundaries * -- all cut boundary edges marked * -- for two level case, a few uncut edges must be marked */ if (i && jbndy) { int v0 = (data[ij] & Z_VALUE); int vb = (data[ij - 1] & Z_VALUE); if (v0 != vb) { if (data[ij] & ZONE_EX) { data[ij] |= J0_START; count++; } if (data[ij + imax] & ZONE_EX) { data[ij] |= J1_START; count++; } } else if (two_levels && v0 == 1) { if (data[ij + imax] & ZONE_EX) { if (i_was_chunk || !(data[ij + imax - 1] & ZONE_EX)) { /* lower left is a drawn part of boundary */ data[ij] |= J1_START; count++; } } else if (data[ij] & ZONE_EX) { if (data[ij + imax - 1] & ZONE_EX) { /* weird case of open hole at lower left */ data[ij] |= J0_START; count++; } } } } i_was_chunk = (i == ichunk); if (i_was_chunk) ichunk += icsize + (ichunk >= irem); } if (j == jchunk) jchunk += jcsize + (jchunk >= jrem); /* place first START_ROW marker */ if (count && !started) { data[ij - imax] |= START_ROW; started = 1; } } /* place immediate stop mark if nothing found */ if (!count) data[0] |= ALL_DONE; /* initialize site */ site->edge0 = site->edge00 = site->edge = 0; site->left0 = site->left = 0; site->n = 0; site->count = count; } /* ------------------------------------------------------------------------ Original (slightly modified) core contour generation routines are above; below are new routines for interfacing to mpl. ------------------------------------------------------------------------ */ /* Note: index order gets switched in the Python interface; python Z[i,j] -> C z[j,i] so if the array has shape Mi, Nj in python, we have iMax = Nj, jMax = Mi in gcntr.c. On the Python side: Ny, Nx = shape(z), so in C, the x-dimension is the first index, the y-dimension the second. */ /* reg should have the same dimensions as data, which has an extra iMax + 1 points relative to Z. It differs from mask in being the opposite (True where a region exists, versus the mask, which is True where a data point is bad), and in that it marks zones, not points. All four zones sharing a bad point must be marked as not existing. */ static void mask_zones (long iMax, long jMax, char *mask, char *reg) { long i, j, ij; long nreg = iMax * jMax + iMax + 1; for (ij = iMax+1; ij < iMax*jMax; ij++) { reg[ij] = 1; } ij = 0; for (j = 0; j < jMax; j++) { for (i = 0; i < iMax; i++, ij++) { if (i == 0 || j == 0) reg[ij] = 0; if (mask[ij] != 0) { reg[ij] = 0; reg[ij + 1] = 0; reg[ij + iMax] = 0; reg[ij + iMax + 1] = 0; } } } for (; ij < nreg; ij++) { reg[ij] = 0; } } static Csite * cntr_new(void) { Csite *site; site = (Csite *) PyMem_Malloc(sizeof(Csite)); if (site == NULL) return NULL; site->data = NULL; site->reg = NULL; site->triangle = NULL; site->xcp = NULL; site->ycp = NULL; site->x = NULL; site->y = NULL; site->z = NULL; return site; } static int cntr_init(Csite *site, long iMax, long jMax, double *x, double *y, double *z, char *mask) { long ijmax = iMax * jMax; long nreg = iMax * jMax + iMax + 1; long i; site->imax = iMax; site->jmax = jMax; site->data = (Cdata *) PyMem_Malloc(sizeof(Cdata) * nreg); if (site->data == NULL) { PyMem_Free(site); return -1; } site->triangle = (short *) PyMem_Malloc(sizeof(short) * ijmax); if (site->triangle == NULL) { PyMem_Free(site->data); PyMem_Free(site); return -1; } for (i = 0; i < ijmax; i++) site->triangle[i] = 0; site->reg = NULL; if (mask != NULL) { site->reg = (char *) PyMem_Malloc(sizeof(char) * nreg); if (site->reg == NULL) { PyMem_Free(site->triangle); PyMem_Free(site->data); PyMem_Free(site); return -1; } mask_zones(iMax, jMax, mask, site->reg); } /* I don't think we need to initialize site->data. */ site->x = x; site->y = y; site->z = z; site->xcp = NULL; site->ycp = NULL; return 0; } static void cntr_del(Csite *site) { PyMem_Free(site->triangle); PyMem_Free(site->reg); PyMem_Free(site->data); PyMem_Free(site); site = NULL; } /* Build a list of lists of points, where each point is an (x,y) tuple. */ static PyObject * build_cntr_list_p(long *np, double *xp, double *yp, int nparts, long ntotal) { PyObject *point, *contourList, *all_contours; int start = 0, end = 0; int i, j, k; all_contours = PyList_New(nparts); if (all_contours == NULL) return NULL; for (i = 0; i < nparts; i++) { start = end; end += np[i]; contourList = PyList_New(np[i]); if (contourList == NULL) goto error; for (k = 0, j = start; j < end; j++, k++) { point = Py_BuildValue("(dd)", xp[j], yp[j]); if (PyList_SetItem(contourList, k, point)) goto error; } if (PyList_SetItem(all_contours, i, contourList)) goto error; } return all_contours; error: Py_XDECREF(all_contours); return NULL; } /* Build a list of tuples (X, Y), where X and Y are 1-D arrays. */ #if 0 static PyObject * build_cntr_list_v(long *np, double *xp, double *yp, int nparts, long ntotal) { PyObject *point, *all_contours; PyArrayObject *xv, *yv; npy_intp dims[1]; int i; long j, k; all_contours = PyList_New(nparts); k = 0; for (i = 0; i < nparts; i++) { dims[0] = np[i]; xv = (PyArrayObject *) PyArray_SimpleNew(1, dims, PyArray_DOUBLE); yv = (PyArrayObject *) PyArray_SimpleNew(1, dims, PyArray_DOUBLE); if (xv == NULL || yv == NULL) goto error; for (j = 0; j < dims[0]; j++) { ((double *)xv->data)[j] = xp[k]; ((double *)yv->data)[j] = yp[k]; k++; } point = Py_BuildValue("(NN)", xv, yv); /* "O" increments ref count; "N" does not. */ if (PyList_SetItem(all_contours, i, point)) goto error; } return all_contours; error: Py_XDECREF(all_contours); return NULL; } #endif /* Build a list of XY 2-D arrays, shape (N,2) */ static PyObject * build_cntr_list_v2(long *np, double *xp, double *yp, int nparts, long ntotal) { PyObject *all_contours; PyArrayObject *xyv; npy_intp dims[2]; int i; long j, k; all_contours = PyList_New(nparts); if (all_contours == NULL) return NULL; k = 0; for (i = 0; i < nparts; i++) { dims[0] = np[i]; dims[1] = 2; xyv = (PyArrayObject *) PyArray_SimpleNew(2, dims, PyArray_DOUBLE); if (xyv == NULL) goto error; for (j = 0; j < dims[0]; j++) { ((double *)xyv->data)[2*j] = xp[k]; ((double *)xyv->data)[2*j+1] = yp[k]; k++; } if (PyList_SetItem(all_contours, i, (PyObject *)xyv)) goto error; } return all_contours; error: Py_XDECREF(all_contours); return NULL; } /* cntr_trace is called once per contour level or level pair. If nlevels is 1, a set of contour lines will be returned; if nlevels is 2, the set of polygons bounded by the levels will be returned. If points is True, the lines will be returned as a list of list of points; otherwise, as a list of tuples of vectors. */ static PyObject * cntr_trace(Csite *site, double levels[], int nlevels, int points, long nchunk) { PyObject *c_list; double *xp0; double *yp0; long *nseg0; int iseg; /* long nchunk = 30; was hardwired */ long n; long nparts = 0; long ntotal = 0; long nparts2 = 0; long ntotal2 = 0; site->zlevel[0] = levels[0]; site->zlevel[1] = levels[0]; if (nlevels == 2) { site->zlevel[1] = levels[1]; } site->n = site->count = 0; data_init (site, 0, nchunk); /* make first pass to compute required sizes for second pass */ for (;;) { n = curve_tracer (site, 0); if (!n) break; if (n > 0) { nparts++; ntotal += n; } else { ntotal -= n; } } xp0 = (double *) PyMem_Malloc(ntotal * sizeof(double)); yp0 = (double *) PyMem_Malloc(ntotal * sizeof(double)); nseg0 = (long *) PyMem_Malloc(nparts * sizeof(long)); if (xp0 == NULL || yp0 == NULL || nseg0 == NULL) { PyErr_NoMemory(); goto error; } /* second pass */ site->xcp = xp0; site->ycp = yp0; iseg = 0; for (;;iseg++) { n = curve_tracer (site, 1); if (ntotal2 + n > ntotal) { PyErr_SetString(PyExc_RuntimeError, "curve_tracer: ntotal2, pass 2 exceeds ntotal, pass 1"); goto error; } if (n == 0) break; if (n > 0) { /* could add array bounds checking */ nseg0[iseg] = n; site->xcp += n; site->ycp += n; ntotal2 += n; nparts2++; } else { PyErr_SetString(PyExc_RuntimeError, "Negative n from curve_tracer in pass 2"); goto error; } } if (points) { c_list = build_cntr_list_p(nseg0, xp0, yp0, nparts, ntotal); } else { c_list = build_cntr_list_v2(nseg0, xp0, yp0, nparts, ntotal); } PyMem_Free(xp0); PyMem_Free(yp0); PyMem_Free(nseg0); site->xcp = NULL; site->ycp = NULL; return c_list; error: PyMem_Free(xp0); PyMem_Free(yp0); PyMem_Free(nseg0); site->xcp = NULL; site->ycp = NULL; return NULL; } /******* Make an extension type. Based on the tutorial.************/ /* site points to the data arrays in the arrays pointed to by xpa, ypa, zpa, and mpa, so we include them in the structure so we can ensure they are not deleted until we have finished using them. */ typedef struct { PyObject_HEAD PyArrayObject *xpa, *ypa, *zpa, *mpa; Csite *site; } Cntr; static int Cntr_clear(Cntr* self) { PyArrayObject *tmp; cntr_del(self->site); tmp = self->xpa; self->xpa = NULL; Py_XDECREF(tmp); tmp = self->ypa; self->ypa = NULL; Py_XDECREF(tmp); tmp = self->zpa; self->zpa = NULL; Py_XDECREF(tmp); tmp = self->mpa; self->mpa = NULL; Py_XDECREF(tmp); return 0; } static void Cntr_dealloc(Cntr* self) { Cntr_clear(self); self->ob_type->tp_free((PyObject*)self); } static PyObject * Cntr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { Cntr *self; self = (Cntr *)type->tp_alloc(type, 0); if (self != NULL) { self->site = cntr_new(); if (self->site == NULL) { PyErr_SetString(PyExc_MemoryError, "Memory allocation failed in cntr_new."); Py_XDECREF(self); return NULL; } self->xpa = NULL; self->ypa = NULL; self->zpa = NULL; self->mpa = NULL; } return (PyObject *)self; } static int Cntr_init(Cntr *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"x", "y", "z", "mask", NULL}; PyObject *xarg, *yarg, *zarg, *marg; PyArrayObject *xpa, *ypa, *zpa, *mpa; long iMax, jMax; char *mask; marg = NULL; if (! PyArg_ParseTupleAndKeywords(args, kwds, "OOO|O", kwlist, &xarg, &yarg, &zarg, &marg)) return -1; if (marg == Py_None) marg = NULL; if (!PyArray_Check(xarg) || !PyArray_Check(yarg) || !PyArray_Check(zarg) || (marg && !PyArray_Check(marg))) { PyErr_SetString(PyExc_TypeError, "Arguments x, y, z, (optional) mask must be arrays."); return -1; } xpa = (PyArrayObject *) PyArray_ContiguousFromObject(xarg, PyArray_DOUBLE, 2, 2); ypa = (PyArrayObject *) PyArray_ContiguousFromObject(yarg, PyArray_DOUBLE, 2, 2); zpa = (PyArrayObject *) PyArray_ContiguousFromObject(zarg, PyArray_DOUBLE, 2, 2); if (marg) mpa = (PyArrayObject *) PyArray_ContiguousFromObject(marg, PyArray_SBYTE, 2, 2); else mpa = NULL; if (xpa == NULL || ypa == NULL || zpa == NULL || (marg && mpa == NULL)) { PyErr_SetString(PyExc_ValueError, "Arguments x, y, z, mask (if present) must be 2D arrays."); goto error; } iMax = zpa->dimensions[1]; jMax = zpa->dimensions[0]; if (xpa->dimensions[0] != jMax || xpa->dimensions[1] != iMax || ypa->dimensions[0] != jMax || ypa->dimensions[1] != iMax || (mpa && (mpa->dimensions[0] != jMax || mpa->dimensions[1] != iMax))) { PyErr_SetString(PyExc_ValueError, "Arguments x, y, z, mask (if present)" " must have the same dimensions."); goto error; } if (mpa) mask = mpa->data; else mask = NULL; if ( cntr_init(self->site, iMax, jMax, (double *)xpa->data, (double *)ypa->data, (double *)zpa->data, mask)) { PyErr_SetString(PyExc_MemoryError, "Memory allocation failure in cntr_init"); goto error; } self->xpa = xpa; self->ypa = ypa; self->zpa = zpa; self->mpa = mpa; return 0; error: Py_XDECREF(xpa); Py_XDECREF(ypa); Py_XDECREF(zpa); Py_XDECREF(mpa); return -1; } static PyObject * Cntr_trace(Cntr *self, PyObject *args, PyObject *kwds) { double levels[2] = {0.0, -1e100}; int nlevels = 2; int points = 0; long nchunk = 0L; static char *kwlist[] = {"level0", "level1", "points", "nchunk", NULL}; if (! PyArg_ParseTupleAndKeywords(args, kwds, "d|dil", kwlist, levels, levels+1, &points, &nchunk)) { return NULL; } if (levels[1] == -1e100 || levels[1] <= levels[0]) nlevels = 1; return cntr_trace(self->site, levels, nlevels, points, nchunk); } static PyMethodDef Cntr_methods[] = { {"trace", (PyCFunction)Cntr_trace, METH_VARARGS | METH_KEYWORDS, "Return a list of contour line segments or polygons.\n\n" " Required argument: level0, a contour level\n" " Optional argument: level1; if given, and if level1 > level0,\n" " then the contours will be polygons surrounding areas between\n" " the levels.\n" " Optional argument: points; if 0 (default), return a list of\n" " vector pairs; otherwise, return a list of lists of points.\n" " Optional argument: nchunk; approximate number of grid points\n" " per chunk. 0 (default) for no chunking.\n" }, {NULL} /* Sentinel */ }; static PyTypeObject CntrType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "cntr.Cntr", /*tp_name*/ sizeof(Cntr), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Cntr_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "Contour engine", /* tp_doc */ 0, /* tp_traverse */ (inquiry)Cntr_clear, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Cntr_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)Cntr_init, /* tp_init */ 0, /* tp_alloc */ Cntr_new, /* tp_new */ }; static PyMethodDef module_methods[] = { {NULL} /* Sentinel */ }; #ifdef NUMARRAY #if PY_MINOR_VERSION > 2 PyMODINIT_FUNC #else DL_EXPORT(void) #endif init_na_cntr(void) { PyObject* m; if (PyType_Ready(&CntrType) < 0) return; m = Py_InitModule3("_na_cntr", module_methods, "Contouring engine as an extension type (numarray)."); if (m == NULL) return; import_array(); Py_INCREF(&CntrType); PyModule_AddObject(m, "Cntr", (PyObject *)&CntrType); } #endif #ifdef NUMERIC #if PY_MINOR_VERSION > 2 PyMODINIT_FUNC #else DL_EXPORT(void) #endif init_nc_cntr(void) { PyObject* m; if (PyType_Ready(&CntrType) < 0) return; m = Py_InitModule3("_nc_cntr", module_methods, "Contouring engine as an extension type (Numeric)."); if (m == NULL) return; import_array(); Py_INCREF(&CntrType); PyModule_AddObject(m, "Cntr", (PyObject *)&CntrType); } #endif #ifdef SCIPY #if PY_MINOR_VERSION > 2 PyMODINIT_FUNC #else DL_EXPORT(void) #endif init_ns_cntr(void) { PyObject* m; if (PyType_Ready(&CntrType) < 0) return; m = Py_InitModule3("_ns_cntr", module_methods, "Contouring engine as an extension type (Scipy)."); if (m == NULL) return; import_array(); Py_INCREF(&CntrType); PyModule_AddObject(m, "Cntr", (PyObject *)&CntrType); } #endif veusz-1.15/helpers/src/recordpaintengine.h0000644002344000001440000000516211734662204020705 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #ifndef RECORD_PAINT_ENGINE__H #define RECORD_PAINT_ENGINE__H #include #include #include #include #include #include #include #include #include #include class RecordPaintDevice; class RecordPaintEngine : public QPaintEngine { public: RecordPaintEngine(); // standard methods to be overridden in engine bool begin(QPaintDevice* pdev); void drawEllipse(const QRectF& rect); void drawEllipse(const QRect& rect); void drawImage(const QRectF& rectangle, const QImage& image, const QRectF& sr, Qt::ImageConversionFlags flags = Qt::AutoColor); void drawLines(const QLineF* lines, int lineCount); void drawLines(const QLine* lines, int lineCount); void drawPath(const QPainterPath& path); void drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr); void drawPoints(const QPointF* points, int pointCount); void drawPoints(const QPoint* points, int pointCount); void drawPolygon(const QPointF* points, int pointCount, QPaintEngine::PolygonDrawMode mode); void drawPolygon(const QPoint* points, int pointCount, QPaintEngine::PolygonDrawMode mode); void drawRects(const QRectF* rects, int rectCount); void drawRects(const QRect* rects, int rectCount); void drawTextItem(const QPointF& p, const QTextItem& textItem); void drawTiledPixmap(const QRectF& rect, const QPixmap& pixmap, const QPointF& p); bool end (); QPaintEngine::Type type () const; void updateState(const QPaintEngineState& state); // return an estimate of number of items drawn int drawItemCount() const { return _drawitemcount; } private: int _drawitemcount; RecordPaintDevice* _pdev; }; #endif veusz-1.15/helpers/src/qtloops_helpers.cpp0000644002344000001440000000700311734662204020757 0ustar jssusers00000000000000// Copyright (C) 2009 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "Python.h" #include "numpy/arrayobject.h" #include "qtloops_helpers.h" void do_numpy_init_package() { import_array(); } Tuple2Ptrs::Tuple2Ptrs(PyObject* tuple) { const size_t numitems = PyTuple_Size(tuple); for(size_t i=0; i != numitems; ++i) { // access python tuple item PyObject* obj = PyTuple_GetItem(tuple, i); // convert to C array (stored in objdata) PyArrayObject *array = (PyArrayObject*) PyArray_ContiguousFromObject(obj, PyArray_DOUBLE, 1, 1); if( array == NULL ) { throw "Cannot covert parameter to 1D numpy array"; } data.push_back( (double*)(array->data) ); dims.push_back( array->dimensions[0] ); _arrays.push_back( (PyObject*)array ); } } Tuple2Ptrs::~Tuple2Ptrs() { // delete array objects for(int i=0; i < _arrays.size(); ++i) { Py_DECREF(_arrays[i]); _arrays[i] = 0; data[i] = 0; } } Numpy1DObj::Numpy1DObj(PyObject* array) : data(0), _array(0) { PyArrayObject *arrayobj = (PyArrayObject*) PyArray_ContiguousFromObject(array, PyArray_DOUBLE, 1, 1); if( arrayobj == NULL ) { throw "Cannot covert item to 1D numpy array"; } data = (double*)(arrayobj->data); dim = arrayobj->dimensions[0]; _array = (PyObject*)arrayobj; } Numpy1DObj::~Numpy1DObj() { Py_XDECREF(_array); _array = 0; data = 0; } Numpy2DObj::Numpy2DObj(PyObject* array) : data(0), _array(0) { PyArrayObject *arrayobj = (PyArrayObject*) PyArray_ContiguousFromObject(array, PyArray_DOUBLE, 2, 2); if( arrayobj == NULL ) { throw "Cannot convert to 2D numpy array"; } data = (double*)(arrayobj->data); dims[0] = arrayobj->dimensions[0]; dims[1] = arrayobj->dimensions[1]; _array = (PyObject*)arrayobj; } Numpy2DObj::~Numpy2DObj() { Py_XDECREF(_array); _array = 0; data = 0; } Numpy2DIntObj::Numpy2DIntObj(PyObject* array) : data(0), _array(0) { PyArrayObject *arrayobj = (PyArrayObject*) PyArray_ContiguousFromObject(array, PyArray_INT, 2, 2); if( arrayobj == NULL ) { throw "Cannot convert to 2D numpy integer array. " "Requires numpy.intc argument."; } data = (int*)(arrayobj->data); dims[0] = arrayobj->dimensions[0]; dims[1] = arrayobj->dimensions[1]; _array = (PyObject*)arrayobj; } Numpy2DIntObj::~Numpy2DIntObj() { Py_XDECREF(_array); _array = 0; data = 0; } PyObject* doubleArrayToNumpy(const double* d, int len) { npy_intp dims[1]; dims[0] = len; PyObject* n = PyArray_SimpleNew(1, dims, NPY_DOUBLE); double* pydata = (double*) ((PyArrayObject*)(n))->data; for(int i = 0; i < len; ++i) pydata[i] = d[i]; return n; } veusz-1.15/helpers/src/LICENSE_MATPLOTLIB0000644002344000001440000000454411734662204017573 0ustar jssusers00000000000000LICENSE AGREEMENT FOR MATPLOTLIB 0.84 -------------------------------------- 1. This LICENSE AGREEMENT is between John D. Hunter ("JDH"), and the Individual or Organization ("Licensee") accessing and otherwise using matplotlib software in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, JDH hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use matplotlib 0.84 alone or in any derivative version, provided, however, that JDH's License Agreement and JDH's notice of copyright, i.e., "Copyright (c) 2002-2005 John D. Hunter; All Rights Reserved" are retained in matplotlib 0.84 alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates matplotlib 0.84 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to matplotlib 0.84. 4. JDH is making matplotlib 0.84 available to Licensee on an "AS IS" basis. JDH MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, JDH MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB 0.84 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. JDH SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB 0.84 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING MATPLOTLIB 0.84, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between JDH and Licensee. This License Agreement does not grant permission to use JDH trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using matplotlib 0.84, Licensee agrees to be bound by the terms and conditions of this License Agreement. veusz-1.15/helpers/src/numpyfuncs.cpp0000644002344000001440000000557111734662204017753 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "numpyfuncs.h" #include #include "isnan.h" namespace { template inline T min(T a, T b) { return (a::quiet_NaN(); } else { if( average ) out[i / binning] = sum / ct; else out[i / binning] = sum; } sum = 0; ct = 0; } } } void rollingAverage(const Numpy1DObj& indata, const Numpy1DObj* weights, int width, int* numoutbins, double** outdata) { // round up output size int size = indata.dim; if( weights != 0 ) size = min( weights->dim, size ); // create output array *numoutbins = size; double *out = new double[size]; *outdata = out; for(int i = 0 ; i < size; ++i) { double ct = 0.; double sum = 0.; // iterate over rolling width for(int di = -width; di <= width; ++di) { const int ri = di+i; if ( ri >= 0 && ri < size && isFinite(indata(ri)) ) { if( weights != 0 ) { // weighted average const double w = (*weights)(ri); if( isFinite(w) ) { ct += w; sum += w*indata(ri); } } else { // standard average ct += 1; sum += indata(ri); } } } if( ct != 0. ) out[i] = sum / ct; else out[i] = std::numeric_limits::quiet_NaN(); } } veusz-1.15/helpers/src/qtloops.sip0000644002344000001440000001325611734662204017255 0ustar jssusers00000000000000// Copyright (C) 2009 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// %Module qtloops 0 %Import QtCore/QtCoremod.sip %Import QtGui/QtGuimod.sip %ModuleHeaderCode #include %End %PostInitialisationCode do_numpy_init_package(); %End class QtLoops { %TypeHeaderCode #include #include #include #include #include %End public: QtLoops(); }; void addNumpyToPolygonF(QPolygonF&, ...); %MethodCode { try { Tuple2Ptrs t(a1); addNumpyToPolygonF(*a0, t); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End void addNumpyPolygonToPath(QPainterPath&, const QRectF*, ...); %MethodCode { try { Tuple2Ptrs t(a2); addNumpyPolygonToPath(*a0, t, a1); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End void plotPathsToPainter(QPainter&, QPainterPath&, SIP_PYOBJECT, SIP_PYOBJECT, SIP_PYOBJECT, const QRectF* clip=0, const QImage* colorimg=0); %MethodCode { Numpy1DObj* scaling = 0; try { // x and y coordinates Numpy1DObj x(a2); Numpy1DObj y(a3); // a4 is scaling or None if (a4 != Py_None) { scaling = new Numpy1DObj(a4); } plotPathsToPainter(*a0, *a1, x, y, scaling, a5, a6); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } delete scaling; } %End void plotLinesToPainter(QPainter& painter, SIP_PYOBJECT, SIP_PYOBJECT, SIP_PYOBJECT, SIP_PYOBJECT, const QRectF* clip = 0, bool autoexpand = true); %MethodCode { try { Numpy1DObj x1(a1); Numpy1DObj y1(a2); Numpy1DObj x2(a3); Numpy1DObj y2(a4); plotLinesToPainter(*a0, x1, y1, x2, y2, a5, a6); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End void plotBoxesToPainter(QPainter& painter, SIP_PYOBJECT, SIP_PYOBJECT, SIP_PYOBJECT, SIP_PYOBJECT, const QRectF* clip = 0, bool autoexpand = true); %MethodCode { try { Numpy1DObj x1(a1); Numpy1DObj y1(a2); Numpy1DObj x2(a3); Numpy1DObj y2(a4); plotBoxesToPainter(*a0, x1, y1, x2, y2, a5, a6); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End QImage numpyToQImage(SIP_PYOBJECT, SIP_PYOBJECT, bool forcetrans = false); %MethodCode { try { Numpy2DObj data(a0); Numpy2DIntObj colors(a1); QImage *img = new QImage( numpyToQImage(data, colors, a2) ); sipRes = img; } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End void applyImageTransparancy(QImage& img, SIP_PYOBJECT); %MethodCode { try { Numpy2DObj data(a1); applyImageTransparancy(*a0, data); } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } } %End void polygonClip(const QPolygonF& inpoly, const QRectF& cliprect, QPolygonF& outpoly); void plotClippedPolygon(QPainter& painter, QRectF rect, const QPolygonF& inpoly, bool autoexpand = true); void plotClippedPolyline(QPainter& painter, QRectF clip, const QPolygonF& poly, bool autoexpand = true); QPolygonF bezier_fit_cubic_single(const QPolygonF& data, double error); QPolygonF bezier_fit_cubic_multi(const QPolygonF& data, double error, unsigned max_beziers); class RecordPaintDevice : QPaintDevice { %TypeHeaderCode #include %End public: RecordPaintDevice(int width, int height, int dpix, int dpiy); ~RecordPaintDevice(); void play(QPainter& painter); QPaintEngine* paintEngine() const; int metric(QPaintDevice::PaintDeviceMetric metric) const; int drawItemCount() const; }; SIP_PYOBJECT binData(SIP_PYOBJECT data, int binning, bool average); %MethodCode try { Numpy1DObj d(a0); double* data; int numelem; binData(d, a1, a2, &numelem, &data); sipRes = doubleArrayToNumpy(data, numelem); delete[] data; } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } %End SIP_PYOBJECT rollingAverage(SIP_PYOBJECT data, SIP_PYOBJECT weights, int width); %MethodCode { Numpy1DObj* weightarray = 0; try { Numpy1DObj d(a0); if( a1 != Py_None ) { weightarray = new Numpy1DObj(a1); } double* data; int numelem; rollingAverage(d, weightarray, a2, &numelem, &data); sipRes = doubleArrayToNumpy(data, numelem); delete[] data; } catch( const char *msg ) { sipIsErr = 1; PyErr_SetString(PyExc_TypeError, msg); } delete weightarray; } %End veusz-1.15/helpers/src/qtloops.cpp0000644002344000001440000002166111734662204017243 0ustar jssusers00000000000000// Copyright (C) 2009 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "qtloops.h" #include "isnan.h" #include "polylineclip.h" #include "polygonclip.h" #include #include #include #include #include #include #include namespace { // is difference between points very small? inline bool smallDelta(const QPointF& pt1, const QPointF& pt2) { return fabs(pt1.x() - pt2.x()) < 0.01 && fabs(pt1.y()- pt2.y()) < 0.01; } template inline T min(T a, T b) { return (a inline T min(T a, T b, T c, T d) { return min( min(a, b), min(c, d) ); } template inline T clipval(T val, T minv, T maxv) { if( val < minv ) return minv; if( val > maxv ) return maxv; return val; } } void addNumpyToPolygonF(QPolygonF& poly, const Tuple2Ptrs& d) { // iterate over rows until none left const int numcols = d.data.size(); QPointF lastpt(-1e6, -1e6); for(int row=0 ; ; ++row) { bool ifany = false; // the numcols-1 makes sure we don't get odd numbers of columns for(int col=0; col < (numcols-1); col += 2) { // add point if point in two columns if( row < d.dims[col] && row < d.dims[col+1] ) { const QPointF pt(d.data[col][row], d.data[col+1][row]); if( ! smallDelta(pt, lastpt) ) { poly << pt; lastpt = pt; } ifany = true; } } // exit loop if no more columns if(! ifany ) break; } } void addNumpyPolygonToPath(QPainterPath &path, const Tuple2Ptrs& d, const QRectF* clip) { const int numcols = d.data.size(); for(int row=0 ; ; ++row) { bool ifany = false; // output polygon QPolygonF poly; // the numcols-1 makes sure we don't get odd numbers of columns for(int col=0; col < (numcols-1); col += 2) { // add point if point in two columns if( row < d.dims[col] && row < d.dims[col+1] ) { const QPointF pt(d.data[col][row], d.data[col+1][row]); poly << pt; ifany = true; } } if( ifany ) { if( clip != 0 ) { QPolygonF clippedpoly; polygonClip(poly, *clip, clippedpoly); path.addPolygon(clippedpoly); } else { path.addPolygon(poly); } path.closeSubpath(); } else { // exit loop if no more columns break; } } } void plotPathsToPainter(QPainter& painter, QPainterPath& path, const Numpy1DObj& x, const Numpy1DObj& y, const Numpy1DObj* scaling, const QRectF* clip, const QImage* colorimg) { QRectF cliprect( QPointF(-32767,-32767), QPointF(32767,32767) ); if( clip != 0 ) { qreal x1, y1, x2, y2; clip->getCoords(&x1, &y1, &x2, &y2); cliprect.setCoords(x1, y1, x2, y2); } QRectF pathbox = path.boundingRect(); cliprect.adjust(pathbox.left(), pathbox.top(), pathbox.bottom(), pathbox.right()); // keep track of duplicate points QPointF lastpt(-1e6, -1e6); // keep original transformation for restoration after each iteration QTransform origtrans(painter.worldTransform()); // number of iterations int size = min(x.dim, y.dim); // if few color points, trim down number of paths if( colorimg != 0 ) size = min(size, colorimg->width()); // too few scaling points if( scaling != 0 ) size = min(size, scaling->dim); // draw each path for(int i = 0; i < size; ++i) { const QPointF pt(x(i), y(i)); if( cliprect.contains(pt) && ! smallDelta(lastpt, pt) ) { painter.translate(pt); if( scaling != 0 ) { // scale point if requested const qreal s = (*scaling)(i); painter.scale(s, s); } if( colorimg != 0 ) { // get color from pixel and create a new brush QBrush b( QColor::fromRgba(colorimg->pixel(i, 0)) ); painter.setBrush(b); } painter.drawPath(path); painter.setWorldTransform(origtrans); lastpt = pt; } } } void plotLinesToPainter(QPainter& painter, const Numpy1DObj& x1, const Numpy1DObj& y1, const Numpy1DObj& x2, const Numpy1DObj& y2, const QRectF* clip, bool autoexpand) { const int maxsize = min(x1.dim, x2.dim, y1.dim, y2.dim); // if autoexpand, expand rectangle by line width QRectF clipcopy; if ( clip != 0 && autoexpand ) { const qreal lw = painter.pen().widthF(); qreal x1, y1, x2, y2; clip->getCoords(&x1, &y1, &x2, &y2); clipcopy.setCoords(x1, y1, x2, y2); clipcopy.adjust(-lw, -lw, lw, lw); } if( maxsize != 0 ) { QVector lines; for(int i = 0; i < maxsize; ++i) { QPointF pt1(x1(i), y1(i)); QPointF pt2(x2(i), y2(i)); if( clip != 0 ) { if( clipLine(clipcopy, pt1, pt2) ) lines << QLineF(pt1, pt2); } else lines << QLineF(pt1, pt2); } painter.drawLines(lines); } } void plotBoxesToPainter(QPainter& painter, const Numpy1DObj& x1, const Numpy1DObj& y1, const Numpy1DObj& x2, const Numpy1DObj& y2, const QRectF* clip, bool autoexpand) { // if autoexpand, expand rectangle by line width QRectF clipcopy(QPointF(-32767,-32767), QPointF(32767,32767)); if ( clip != 0 && autoexpand ) { const qreal lw = painter.pen().widthF(); qreal x1, y1, x2, y2; clip->getCoords(&x1, &y1, &x2, &y2); clipcopy.setCoords(x1, y1, x2, y2); clipcopy.adjust(-lw, -lw, lw, lw); } const int maxsize = min(x1.dim, x2.dim, y1.dim, y2.dim); QVector rects; for(int i = 0; i < maxsize; ++i) { QPointF pt1(x1(i), y1(i)); QPointF pt2(x2(i), y2(i)); const QRectF rect(pt1, pt2); if( clipcopy.intersects(rect) ) { rects << clipcopy.intersected(rect); } } if( ! rects.isEmpty() ) painter.drawRects(rects); } QImage numpyToQImage(const Numpy2DObj& imgdata, const Numpy2DIntObj &colors, bool forcetrans) { // make format use alpha transparency if required const int numcolors = colors.dims[0]; if ( colors.dims[1] != 4 ) throw "4 columns required in colors array"; const int numbands = numcolors-1; const int xw = imgdata.dims[1]; const int yw = imgdata.dims[0]; QImage::Format format = QImage::Format_RGB32; if( forcetrans ) format = QImage::Format_ARGB32; else { for(int i = 0; i < numcolors; ++i) { if( colors(i, 3) != 255 ) format = QImage::Format_ARGB32; } } // make image QImage img(xw, yw, format); // iterate over input pixels for(int y=0; y(img.scanLine(yw-y-1)); for(int x=0; x(img.scanLine(yw-y-1)); for(int x=0; x #include "polygonclip.h" using std::abs; // macro to clip point against edge // - edge: name of edge for clipping // - isinside: test whether point is inside edge // - intercept: function to calculate coordinate where line // crosses edge // - next: function to call next to clip point #define CLIPEDGE(edge, isinside, intercept, next) \ void edge##ClipPoint(const QPointF& pt) \ { \ QPointF& lastpt = edge##last; \ if( edge##is1st ) \ { \ /* do nothing */ \ edge##1st = pt; \ edge##is1st = false; \ } \ else \ { \ if( isinside(pt) ) \ { \ if( ! isinside(lastpt) ) \ /* this point inside and last point outside */ \ next( intercept(clip.edge(), pt, lastpt) ); \ next(pt); \ } \ else \ { \ if( isinside(lastpt) ) \ /* this point outside and last point inside */ \ next( intercept(clip.edge(), pt, lastpt) ); \ /* else do nothing if both outside */ \ } \ } \ \ lastpt = pt; \ } // tolerance for points being the same #define TOL 1e-5 namespace { inline QPointF interceptVert(qreal horzval, const QPointF& pt1, const QPointF& pt2) { const qreal gradient = (pt2.y()-pt1.y()) / (pt2.x()-pt1.x()); return QPointF(horzval, (horzval - pt1.x())*gradient + pt1.y()); } inline QPointF interceptHorz(qreal vertval, const QPointF& pt1, const QPointF& pt2) { const qreal gradient = (pt2.x()-pt1.x()) / (pt2.y()-pt1.y()); return QPointF((vertval - pt1.y())*gradient + pt1.x(), vertval); } // greater than or close inline int gtclose(qreal v1, qreal v2) { return v1 > v2 || abs(v1-v2) < TOL; } // less than or close inline int ltclose(qreal v1, qreal v2) { return v1 < v2 || abs(v1-v2) < TOL; } struct State { State(const QRectF& rect, QPolygonF& out) : clip(rect), output(out), leftis1st(true), rightis1st(true), topis1st(true), bottomis1st(true) { } // tests for whether point is inside of outside of each side inline bool insideBottom(const QPointF& pt) const { return ltclose(pt.y(), clip.bottom()); } inline bool insideTop(const QPointF& pt) const { return gtclose(pt.y(), clip.top()); } inline bool insideRight(const QPointF& pt) const { return ltclose(pt.x(), clip.right()); } inline bool insideLeft(const QPointF& pt) const { return gtclose(pt.x(), clip.left()); } // add functions for clipping to each edge CLIPEDGE(bottom, insideBottom, interceptHorz, writeClipPoint) CLIPEDGE(top, insideTop, interceptHorz, bottomClipPoint) CLIPEDGE(right, insideRight, interceptVert, topClipPoint) CLIPEDGE(left, insideLeft, interceptVert, rightClipPoint) // finally writes to output void writeClipPoint(const QPointF& pt) { // don't add the same point if( output.empty() || abs(pt.x() - output.last().x()) > TOL || abs(pt.y() - output.last().y()) > TOL ) output << pt; } /* location of corners of clip rectangle */ QRectF clip; /* output points are added here */ QPolygonF &output; /* last points added */ QPointF leftlast, rightlast, toplast, bottomlast; /* first point for each stage */ QPointF left1st, right1st, top1st, bottom1st; /* whether this is the 1st point through */ bool leftis1st, rightis1st, topis1st, bottomis1st; }; } void polygonClip(const QPolygonF& inpoly, const QRectF& cliprect, QPolygonF& out) { // construct initial state State state(cliprect, out); // do the clipping for(QPolygonF::const_iterator pt = inpoly.begin(); pt != inpoly.end(); ++pt) { state.leftClipPoint(*pt); } // complete state.leftClipPoint(state.left1st); state.rightClipPoint(state.right1st); state.topClipPoint(state.top1st); state.bottomClipPoint(state.bottom1st); } void plotClippedPolygon(QPainter& painter, QRectF rect, const QPolygonF& inpoly, bool autoexpand) { if ( autoexpand ) { const qreal lw = painter.pen().widthF(); if( painter.pen().style() != Qt::NoPen ) rect.adjust(-lw, -lw, lw, lw); } QPolygonF plt; polygonClip(inpoly, rect, plt); painter.drawPolygon(plt); } veusz-1.15/helpers/src/recordpaintdevice.cpp0000644002344000001440000000425411734662204021233 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include #include #include "recordpaintdevice.h" #include "recordpaintengine.h" #define INCH_MM 25.4 RecordPaintDevice::RecordPaintDevice(int width, int height, int dpix, int dpiy) :_width(width), _height(height), _dpix(dpix), _dpiy(dpiy), _engine(new RecordPaintEngine) { } RecordPaintDevice::~RecordPaintDevice() { delete _engine; qDeleteAll(_elements); } QPaintEngine* RecordPaintDevice::paintEngine() const { return _engine; } int RecordPaintDevice::metric(QPaintDevice::PaintDeviceMetric metric) const { switch(metric) { case QPaintDevice::PdmWidth: return _width; case QPaintDevice::PdmHeight: return _height; case QPaintDevice::PdmWidthMM: return int(_width * INCH_MM / _dpix); case QPaintDevice::PdmHeightMM: return int(_height * INCH_MM / _dpiy); case QPaintDevice::PdmNumColors: return std::numeric_limits::max(); case QPaintDevice::PdmDepth: return 24; case QPaintDevice::PdmDpiX: case QPaintDevice::PdmPhysicalDpiX: return _dpix; case QPaintDevice::PdmDpiY: case QPaintDevice::PdmPhysicalDpiY: return _dpiy; default: return -1; } } void RecordPaintDevice::play(QPainter& painter) { foreach(PaintElement* el, _elements) { el->paint(painter); } } veusz-1.15/helpers/src/paintelement.h0000644002344000001440000000212511734662204017666 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #ifndef PAINTELEMENT_H #define PAINTELEMENT_H class QPainter; class PaintElement { public: virtual ~PaintElement() {}; virtual void paint(QPainter& painter) = 0; }; #endif veusz-1.15/helpers/src/qtloops_helpers.h0000644002344000001440000000521711734662204020431 0ustar jssusers00000000000000// -*- mode: C++; -*- #ifndef QTLOOPS_HELPERS_H #define QTLOOPS_HELPERS_H // Copyright (C) 2009 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #include "Python.h" #include #define DEBUG false void do_numpy_init_package(); // class for converting tuples to objects which clean themselves up // throws const char* if conversion failed class Tuple2Ptrs { public: Tuple2Ptrs(PyObject* tuple); ~Tuple2Ptrs(); // data in tuple are stored here QVector data; QVector dims; private: // these are the python objects made by PyArray_AsCArray QVector _arrays; }; // class for converting numpy array to an array class Numpy1DObj { public: Numpy1DObj(PyObject* array); ~Numpy1DObj(); const double* data; int dim; inline double operator()(const int x) const { if( DEBUG && (x < 0 || x >= dim) ) throw "Invalid index in array"; return data[x]; } private: PyObject* _array; }; // class for converting a 2D numpy array to an array class Numpy2DObj { public: Numpy2DObj(PyObject* array); ~Numpy2DObj(); const double* data; int dims[2]; inline double operator()(const int x, const int y) const { if( DEBUG && (x < 0 || x >= dims[0] || y < 0 || y >= dims[1]) ) throw "Invalid index in array"; return data[x+y*dims[1]]; } private: PyObject* _array; }; // class for converting a 2D numpy array to an integer array class Numpy2DIntObj { public: Numpy2DIntObj(PyObject* array); ~Numpy2DIntObj(); const int* data; int dims[2]; inline int operator()(const int x, const int y) const { if( DEBUG && (x < 0 || x >= dims[0] || y < 0 || y >= dims[1]) ) throw "Invalid index in array"; return data[x+y*dims[1]]; } private: PyObject* _array; }; PyObject* doubleArrayToNumpy(const double* d, int len); #endif veusz-1.15/helpers/src/polylineclip.h0000644002344000001440000000255711734662204017715 0ustar jssusers00000000000000// -*- mode: C++; -*- // Copyright (C) 2010 Jeremy Sanders // 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 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301, USA. //////////////////////////////////////////////////////////////////// #ifndef POLYLINECLIP_HH #define POLYLINECLIP_HH #include #include #include // plot a polyline poly on the painter, clipping by the rectangle given // if autoexpand is true, then the rectangle is expanded by the line width void plotClippedPolyline(QPainter& painter, QRectF clip, const QPolygonF& poly, bool autoexpand = true); // clip a line made up of the points given, returning true // if is in region or false if not bool clipLine(const QRectF& clip, QPointF& pt1, QPointF& pt2); #endif veusz-1.15/helpers/src/recordpaintdevice.h0000644002344000001440000000334011734662204020673 0ustar jssusers00000000000000// Copyright (C) 2011 Jeremy S. Sanders // Email: Jeremy Sanders // // 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 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ///////////////////////////////////////////////////////////////////////////// #ifndef RECORD_PAINT_DEVICE__H #define RECORD_PAINT_DEVICE__H #include #include #include "paintelement.h" #include "recordpaintengine.h" class RecordPaintDevice : public QPaintDevice { public: RecordPaintDevice(int width, int height, int dpix, int dpiy); ~RecordPaintDevice(); QPaintEngine* paintEngine() const; // play back all void play(QPainter& painter); int metric(QPaintDevice::PaintDeviceMetric metric) const; int drawItemCount() const { return _engine->drawItemCount(); } public: friend class RecordPaintEngine; private: // add an element to the list of maintained elements void addElement(PaintElement* el) { _elements.push_back(el); } private: int _width, _height, _dpix, _dpiy; RecordPaintEngine* _engine; QVector _elements; }; #endif veusz-1.15/helpers/src/polylineclip.cpp0000644002344000001440000001231411734662204020240 0ustar jssusers00000000000000// Copyright (C) 2010 Jeremy Sanders // 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 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301, USA. //////////////////////////////////////////////////////////////////// #include #include #include #include using std::fabs; // Cohen-Sutherland line clipping algorithm // codes used classify endpoints are combinations of these #define LEFT 1 #define RIGHT 2 #define TOP 4 #define BOTTOM 8 namespace { // compute intersection with horizontal line inline QPointF horzIntersection(qreal yval, const QPointF& pt1, const QPointF& pt2) { return QPointF( pt1.x() + (yval-pt1.y()) * (pt2.x()-pt1.x()) / (pt2.y()-pt1.y()), yval ); } // compute intersection with vertical line inline QPointF vertIntersection(qreal xval, const QPointF& pt1, const QPointF& pt2) { return QPointF( xval, pt1.y() + (xval-pt1.x()) * (pt2.y()-pt1.y()) / (pt2.x()-pt1.x()) ); } class Clipper { public: Clipper(const QRectF& cliprect) : clip(cliprect) { } // compute the Cohen-Sutherland code unsigned computeCode(const QPointF& pt) const { unsigned code = 0; if (pt.x() < clip.left()) code |= LEFT; else if (pt.x() > clip.right()) code |= RIGHT; if (pt.y() < clip.top()) code |= TOP; else if (pt.y() > clip.bottom()) code |= BOTTOM; return code; } // modifies points, returning true if okay to accept bool clipLine(QPointF& pt1, QPointF& pt2) const { unsigned code1 = computeCode(pt1); unsigned code2 = computeCode(pt2); // repeat until points are at edge of box // bail out if we repeat too many times (avoid numerical issues) for(unsigned count = 0 ; count < 16 ; count++ ) { if( (code1 | code2) == 0 ) { // no more clipping required - inside return true; } else if( (code1 & code2) != 0 ) { // line should not be drawn - outside return false; } else { // compute intersection // which point to compute for? const unsigned code = (code1 != 0) ? code1 : code2; // get intersection new point and new code for line QPointF pt; if( code & LEFT ) pt = vertIntersection(clip.left(), pt1, pt2); else if( code & RIGHT ) pt = vertIntersection(clip.right(), pt1, pt2); else if( code & TOP ) pt = horzIntersection(clip.top(), pt1, pt2); else if ( code & BOTTOM ) pt = horzIntersection(clip.bottom(), pt1, pt2); // update point as intersection if( code == code1 ) { // changed first point pt1 = pt; code1 = computeCode(pt1); } else { // changed second point pt2 = pt; code2 = computeCode(pt2); } } } // loop return false; } private: QRectF clip; }; // is difference between points very small? inline bool smallDelta(const QPointF& pt1, const QPointF& pt2) { return fabs(pt1.x() - pt2.x()) < 0.01 && fabs(pt1.y()- pt2.y()) < 0.01; } } bool clipLine(const QRectF& clip, QPointF& pt1, QPointF& pt2) { Clipper clipper(clip); return clipper.clipLine(pt1, pt2); } void plotClippedPolyline(QPainter& painter, QRectF clip, const QPolygonF& poly, bool autoexpand) { // exit if fewer than 2 points in polygon if ( poly.size() < 2 ) return; // if autoexpand, expand rectangle by line width if ( autoexpand ) { const qreal lw = painter.pen().widthF(); clip.adjust(-lw, -lw, lw, lw); } // work is done by the clipping object Clipper clipper(clip); // output goes here QPolygonF pout; QPolygonF::const_iterator i = poly.begin(); QPointF lastpt = *i; i++; for( ; i != poly.end(); ++i ) { QPointF p1 = lastpt; QPointF p2 = *i; bool plotline = clipper.clipLine(p1, p2); if( plotline ) { if ( pout.isEmpty() ) { // add first line pout << p1; if( ! smallDelta(p1, p2) ) pout << p2; } else { if( p1 == pout.last() ) { if( ! smallDelta(p1, p2) ) // extend polyline pout << p2; } else { // paint existing line if( pout.size() >= 2 ) painter.drawPolyline(pout); // start new line pout.clear(); pout << p1; if( ! smallDelta(p1, p2) ) pout << p2; } } } else { // line isn't in region, so ignore results from clip function // paint existing line if( pout.size() >= 2 ) painter.drawPolyline(pout); // cleanup pout.clear(); } lastpt = *i; } if( pout.size() >= 2 ) painter.drawPolyline(pout); } veusz-1.15/helpers/src/isnan.h0000644002344000001440000000425111734662204016313 0ustar jssusers00000000000000// -*- mode: C++; -*- #ifndef __ISNAN_H__ #define __ISNAN_H__ /* * Temporary fix for various misdefinitions of isnan(). * isnan() is becoming undef'd in some .h files. * #include this last in your .cpp file to get it right. * * The problem is that isnan and isfinite are part of C99 but aren't part of * the C++ standard (which predates C99). * * Authors: * Inkscape groupies and obsessive-compulsives * * Copyright (C) 2004 authors * * Released under GNU GPL, read the file 'COPYING' for more information * * 2005 modification hereby placed in public domain. Probably supercedes the 2004 copyright * for the code itself. */ #include #include /* You might try changing the above to if you have problems. * Whether you use math.h or cmath, you may need to edit the .cpp file * and/or other .h files to use the same header file. */ #if defined(__isnan) # define isNaN(_a) (__isnan(_a)) /* MacOSX/Darwin definition < 10.4 */ #elif defined(WIN32) || defined(_isnan) || defined(_MSC_VER) # define isNaN(_a) (_isnan(_a)) /* Win32 definition */ #elif defined(isnan) || defined(__FreeBSD__) || defined(__osf__) # define isNaN(_a) (isnan(_a)) /* GNU definition */ #else # define isNaN(_a) (std::isnan(_a)) #endif /* If the above doesn't work, then try (a != a). * Also, please report a bug as per http://www.inkscape.org/report_bugs.php, * giving information about what platform and compiler version you're using. */ #if defined(__isfinite) # define isFinite(_a) (__isfinite(_a)) /* MacOSX/Darwin definition < 10.4 */ #elif defined(WIN32) || defined(_finite) || defined(_MSC_VER) # define isFinite(_a) (_finite(_a)) /* Win32 definition */ #elif defined(__sgi) # define isFinite(_a) (_isfinite(_a)) #elif defined(isfinite) # define isFinite(_a) (isfinite(_a)) #elif defined(__osf__) # define isFinite(_a) (finite(_a) && !isNaN(_a)) #else # define isFinite(_a) (std::isfinite(_a)) #endif /* If the above doesn't work, then try (finite(_a) && !isNaN(_a)) or (!isNaN((_a) - (_a))). * Also, please report a bug as per http://www.inkscape.org/report_bugs.php, * giving information about what platform and compiler version you're using. */ #endif /* __ISNAN_H__ */ veusz-1.15/__init__.py0000644002344000001440000000165411734662204014716 0ustar jssusers00000000000000# Copyright (C) 2008 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Main veusz module.""" veusz-1.15/PKG-INFO0000644002344000001440000000170511734662466013711 0ustar jssusers00000000000000Metadata-Version: 1.0 Name: veusz Version: 1.15 Summary: A scientific plotting package Home-page: http://home.gna.org/veusz/ Author: Jeremy Sanders Author-email: jeremy@jeremysanders.net License: GPL Description: Veusz is a scientific plotting package, designed to create publication-ready Postscript, PDF and SVG output. It features GUI, command-line, and scripting interfaces. Graphs are constructed from "widgets", allowing complex layouts to be designed. Veusz supports plotting functions, data with errors, keys, labels, stacked plots, multiple plots, and fitting data. Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: Qt Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Topic :: Scientific/Engineering :: Visualization veusz-1.15/ChangeLog0000644002344000001440000002211511734662204014352 0ustar jssusers00000000000000Changes in 1.15: * Improved hatching: - More hatch styles - Adjust spacing of hatching - Change hatching line style - Allow hatching background color * Axes will not extend beyond specified min and max values * Add options to extend axes by 2, 5, 10 and 15% of data range * Ctrl+MouseWheel zooms in and out of plot * Full screen graph view mode * New dataset plugins - Linear interpolation - Cumulative value - Rolling average - Subtract mean / minimum * Allow grid widgets to be placed in grid widgets * Catch EnvironmentError exceptions on Windows * Allow multiple datasets to be selected in dataset browser * Allow tagging of datasets and allow datasets be grouped by tags in dataset browser * Allow text to be written as text in SVG, rather than curves * Add DBus interface to program, if DBus is installed * 2D QDP support * Add setup.py options for packagers --veusz-resource-dir : location of data files --disable-install-docs * Add title option for keys Minor changes: * Use / rather than \ for path separator in saved file names for Windows/Unix compatibility * Add diamond fill error bar type * Add \color and \marker commands to text renderer * Support labels on xy datasets if one of x or y datasets missing * Reorganise dataset plugin menu * Fix links in INSTALL/README * Floating point intervals in capture dialog Bug fixes: * Trap case where nan values could be plotted * Fix error if website not accessible in exception dialog * Crash when min and max of axes are too similar * Fix clipping of paths after transform in SVG files * Fix crash in picker * Fix crash if duplication of characters in CSV date format * Fix crash in tool tip in dataset browser * Fix GlobalColor error (on certain dark color sets) * Fix blocked data import if no descriptor * Fix crash if log contours and minimum is zero * Bug fix https://bugzilla.redhat.com/show_bug.cgi?id=800196 Changes in 1.14: * Added interactive tutorial * Points in graphs can be colored depending on another dataset and the scale shown in a colorbar widget * Improved CSV import - better data type detection - locale-specific numeric and date formats - single/multiple/none header modes - option to skip lines at top of file - better handling of missing values * Data can be imported from clipboard * Substantially reduced size of output SVG files * In standard data import, descriptor can be left blank to generate dataset names colX * Axis plotting range can be interactively manipulated * If axis is in date-time format, show and allow the min and max values to be in date-time format * ImageFile widget can have image data embedded in document file * Fit widget can update the fit parameters and fit quality to a label widget * Allow editing of 2D datasets in data edit dialog * Add copy and paste dataset command to dataset browser context menu Minor and API changes: * Examples added to help menu * Picker shows date values as dates * Allow descriptor statement in standard data files after a comment character, e.g. "#descriptor x y" * Added some further color maps * Draw key symbols for vector field widget * Import plugin changes - Register classes rather than instances (backward compatibility is retained) - Plugins can return constants and functions (see Constant and Function types) - Add DatasetDateTime for returning date-time datasets * Custom definitions - Add RemoveCustom API to remove custom definitions - AddCustom API can specify order where custom definition is added * C++ code to speed up plotting points of different sizes / colors * Expand files by default in data navigator window * Select created datasets in data edit dialog * Tooltip wrapping used in data navigator window * Grid lines are dropped if they overlap with edge of graph Bug fixes * Fix initial extension in export dialog * Fix crash on hiding pages * Fixed validation for numeric values * Position of grid lines in perpendicular direction for non default positions * Catch errors in example import plugin * Fix crash for non existent key symbols * Fix crash when mismatch of dataset sizes when combining 1D datasets to make 2D dataset Changes in 1.13: * Graphs are rendered in separate threads for speed and a responsive user interface * A changed Graph is rendered immediately on document modification, improving latency * A new ternary plot widget is included * Size of pages can be modified individually in a document * Binary data import added * NPY/NPZ numpy data import added * Axis and tick labels on axes can be rotated at 45 deg intervals * Labels can be plotted next to points on non-orthogonal plots * Add an option for DPI of output EPS and PDF files Minor improvements: * Import dialog detects filename extension to show correct tab * Polygon fill mode for non orthogonal plotting * --plugin command line option added, for loading and testing plugins * Plugin for swapping two colors in a plot * Dataset navigator is moved to right of window by default * Mac OS X binary release updated to Python 2.7.2 * Import plugins can say which file extensions they support * Import plugins can be "promoted" to their own tab on the import dialog * ForceUpdate command added to embedding API, to force an update of the displayed plot (useful if SetUpdateInterval is set to 0) * X or Y dataset can be left blank in plotter to plot by row number Bugs fixed: * Images plotted when axes are inverted are inverted too * Fixed crash when selecting datasets for plotting in the popup menu * Picker crashes with a constant function * 2D dataset creation using expressions fixed * CSV reader treated dataset names ending in + or - incorrectly * unique1d function no longer available in numpy Changes in 1.12: * Multiple widgets can now be selected for editing properties * Add Edit->Select menu and context menu for above * Added context menu on dataset browser for filenames to reload, delete or unlink all associated datasets * New tree-like dataset browsing widget is shown in data edit dialog * Importing 1D fits images is now supported * Date / time data has its own dataset type * The data edit dialog box can create or edit date/time data in human-readable form Minor improvements: * Add LaTeX commands \cdot, \nabla, \overline plus some arrows * Inform user in exception dialog if a new version is available * Add linevertbar and linehorzbar error bar styles Bug fixes: * Fix crash on filling filled error regions if no error bars * Remove grouping separator to numbers in locale as it creates ambiguous lists of numbers * Undo works properly for boolean and integer settings * Prevent widgets getting the same names when dragging and dropping * Hidden plot widgets are ignored when calculating axis ranges * Combo boxes are now case sensitive when displaying matches with previous text * Fix errors if plotting DatasetRange or Dataset1DPlugin datasets against data with nan values * Fix division by zero in dataset preview * Do not leave settings pointing to deleted widgets after an undo * Fix errors when using super/subscripts of super/subscripts * Fix crash when giving positions of bar plot and labels * Do not allow dataset names to be invalid after remaining * Several EMF format bug fixes, including not showing hidden lines and not connecting points making curves * Stop crash when contouring zero-sized datasets Changes in 1.11: * New data point picker for finding coordinates of points on plot (contributed by B.K. Stuhl) * New data navigator window for filtering, sorting and examining dataset statistics * ".." button next to dataset settings pops up data navigator for choosing datasets * Data fitting can now use PyMinuit, giving error estimates (B.K. Stuhl) * Console history now uses currently entered characters to select lines from history (B.K. Stuhl) * New self test script, comparing graph output with expected output * Put superscripts and subscripts above each other when formatting (B.K. Stuhl) * Key entries can have multiple lines (using \\) (B.K. Stuhl) * Option to treat blanks as data items in CSV files * Locale support added for number formatting - Can use current locale or US/English in documents - Can use US/English or current local in user interface * Contours avoid missing (nan) values * Linux binaries are now created on a more modern system * Windows binaries now use MSVC for compilation Bug fixes: * CSV import with blank columns fixed * Embedding module now working when using binary * Remember current directory with unicode characters * Extension module now compiles under MSVC in Windows * Output is always appended to console (B.K. Stuhl) * \r characters sometimes break data import in Windows * If using --export option, add directory of script to import path Minor bug fixes: * Zero sized dataset contour plot fix * Fix problem on context menu for axis match setting * Small values on log axis fix * Disable data edit dialog context menu when no datasets * Loading files with unicode filenames on command line * Do not allow non finite float settings veusz-1.15/INSTALL0000644002344000001440000001015011734662204013625 0ustar jssusers00000000000000Veusz Installation ================== 1. INSTALLING FROM SOURCE ************************* Veusz uses distutils for its installation. See below for how to use it. Requirements: python >= 2.4 http://www.python.org/ PyQt >= 4.3 http://www.riverbankcomputing.co.uk/software/pyqt/ numpy >= 1.0 http://numpy.scipy.org/ PyQt requires Qt4 http://www.trolltech.com/products/qt/ (free version) version >= 4.4 recommended SIP http://www.riverbankcomputing.co.uk/software/sip/ Optional requirements: PyFITS>=1.1 http://www.stsci.edu/resources/software_hardware/pyfits pyemf >= 2.0.0 http://pyemf.sourceforge.net/ PyMinuit http://code.google.com/p/pyminuit/ dbus-python http://dbus.freedesktop.org/doc/dbus-python/ 1.1 Full installation with distutils ==================================== There are a number of ways to install programs using distutils. I will list a few of the possible method here: To install on linux to the standard location on the hard disk # cd veusz-1.15 # python setup.py build # su [enter root password] # python setup.py install # exit If you do not have a root account (as is default on Ubuntu), do # sudo python setup.py install instead of the final three lines On Windows, it should just be a matter of running the python setup.py build and install steps with the requirements installed. 1.2 Testing =========== After veusz has been installed into the Python path (in the standard location or in PYTHONPATH), you can run the runselftest.py executable in the tests directory. This will compare the generated output of example documents with the expected output. The return code of the runselftest.py script is the number of tests that have failed (0 for success). On Unix/Linux, Qt requires the DISPLAY environment to be set to an X11 server for the self test to run. In a non graphical environment Xvfb can be used to create a hidden X11 server: # xvfb-run -a --server-args "-screen 0 640x480x24" \ python tests/runselftest.py 1.3 Simple source use (if requirements installed) ================================================= If you don't want to bother installing veusz fully, it can be run from its own directory (at the moment). Simply do: # tar xzf veusz-1.15.tar.gz [change version here] # cd veusz-1.15 # ./veusz_main.py Certain features will be disabled if you do this. You will not be able to do contour plotting. Plotting will be much slower and export file sizes can be larger. You can build support without installing by doing: # python setup.py build # cp build/*/veusz/helpers/*.so helpers/ 2. BINARY INSTALL ***************** 2.1 Linux binary ================ If your distribution does not include an up to date package, you can use the Linux binary instead (for i386/x86_64). Note that this may not work on all distributions due to glibc/library incompatibilities. Simply unpack the tar file and run the main executable: # tar xzf veusz-linux-i386-1.15.tar.gz [change version here] # cd veusz-linux-i386-1.15 # ./veusz 2.2 Installing in Windows ========================= Simply run the setup.exe binary installer. Add the location of the embed.py file to your PYTHONPATH if you want to use the embedding module. 2.3 Installing on Mac OS X ========================== A binary is available for Mac OS X. Simply drag the Veusz application into your Applications directory. 3. NOTES FOR PACKAGERS ********************** - It is recommended to run the self test above (if possible) - The --veusz-resource-dir allows the packager to move veusz's resource data files outside of the python path, e.g. into LFS /usr/share/veusz. This option installs the resource files into this direction. The packager should use a symlink "resources" in the veusz directory to point to this location. - The --disable-install-examples option prevents installing the examples, so that they can be installed by the packager into e.g. /usr/share/doc/veusz/examples. Veusz looks for the examples directory in the resource directory for the examples, to make its examples menu. It is recommended to use a symlink to point this to the real examples location. veusz-1.15/MANIFEST.in0000644002344000001440000000076411734662204014344 0ustar jssusers00000000000000include VERSION AUTHORS ChangeLog COPYING INSTALL README include MANIFEST.in setup.py setup.cfg include scripts/veusz scripts/veusz_listen recursive-include tests *.py *.sh *.vsz *.selftest *.csv *.dat *.npy *.npz *.qdp *.pco recursive-include Documents *.xml *.sh *.png *.txt *.pdf *.html *.xsl *.py *.pod *.1 recursive-include windows *.png *.ico *.svg README recursive-include dialogs *.ui recursive-include examples *.vsz *.py *.csv *.dat recursive-include helpers *.c *.cpp *.h README LICENSE_* veusz-1.15/veusz_listen.py0000755002344000001440000001226311734662204015712 0ustar jssusers00000000000000#!/usr/bin/env python # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """ Veusz interface which listens to stdin, and receives commands. Results are written to stdout All commands in CommandInterface are supported, plus further commands: Quit: exit the listening program Zoom x: Change the zoom factor of the plot to x """ import sys import os.path # Allow veusz to be run even if not installed into PYTHONPATH try: import veusz except ImportError: # load in the veusz module, but change its path to # the veusz directory, and insert it into sys.modules import __init__ as veusz thisdir = os.path.dirname( os.path.abspath(__file__) ) veusz.__path__ = [thisdir] veusz.__name__ = 'veusz' sys.modules['veusz'] = veusz import veusz.qtall as qt4 from veusz.windows.simplewindow import SimpleWindow import veusz.document as document class ReadingThread(qt4.QThread): """Stdin reading thread. Emits newline signals with new data. We could use a QSocketNotifier on Unix, but this doesn't work on Windows as its stdin is a weird object """ def run(self): """Emit lines read from stdin.""" while True: line = sys.stdin.readline() if line == '': break self.emit(qt4.SIGNAL("newline"), line) class InputListener(qt4.QObject): """Class reads text from stdin, in order to send commands to a document.""" def __init__(self, window): """Initialse the listening object to send commands to the document given by window.""" qt4.QObject.__init__(self) self.window = window self.document = window.document self.plot = window.plot self.pickle = False self.ci = document.CommandInterpreter(self.document) self.ci.addCommand('Quit', self.quitProgram) self.ci.addCommand('Zoom', self.plotZoom) self.ci.addCommand('EnableToolbar', self.enableToolbar) self.ci.addCommand('Pickle', self.enablePickle) self.ci.addCommand('ResizeWindow', self.resizeWindow) self.ci.addCommand('SetUpdateInterval', self.setUpdateInterval) self.ci.addCommand('MoveToPage', self.moveToPage) # reading is done in a separate thread so as not to block self.readthread = ReadingThread(self) self.connect(self.readthread, qt4.SIGNAL("newline"), self.processLine) self.readthread.start() def resizeWindow(self, width, height): """ResizeWindow(width, height) Resize the window to be width x height pixels.""" self.window.resize(width, height) def setUpdateInterval(self, interval): """SetUpdateInterval(interval) Set graph update interval. interval is in milliseconds (ms) set to zero to disable updates """ self.plot.setTimeout(interval) def moveToPage(self, pagenum): """MoveToPage(pagenum) Tell window to show specified pagenumber (starting from 1). """ self.plot.setPageNumber(pagenum-1) def quitProgram(self): """Exit the program.""" self.window.close() def plotZoom(self, zoomfactor): """Set the plot zoom factor.""" self.window.setZoom(zoomfactor) def enableToolbar(self, enable=True): """Enable plot toolbar.""" self.window.enableToolbar(enable) def enablePickle(self, on=True): """Enable/disable pickling of commands to/data from veusz""" self.pickle = on def processLine(self, line): """Process inputted line.""" if self.pickle: # line is repr form of pickled string get get rid of \n retn = self.ci.runPickle( eval(line.strip()) ) sys.stdout.write('%s\n' % repr(retn)) sys.stdout.flush() else: self.ci.run(line) def openWindow(args, quiet=False): '''Opening listening window. args is a list of arguments to the program ''' global _win global _listen if len(args) > 1: name = args[1] else: name = 'Veusz output' _win = SimpleWindow(name) if not quiet: _win.show() _listen = InputListener(_win) def run(): '''Actually run the program.''' app = qt4.QApplication(sys.argv) openWindow(sys.argv) app.exec_() # if ran as a program if __name__ == '__main__': run() veusz-1.15/VERSION0000644002344000001440000000000511734662204013642 0ustar jssusers000000000000001.15 veusz-1.15/setting/0000755002344000001440000000000011734662466014266 5ustar jssusers00000000000000veusz-1.15/setting/settingdb.py0000644002344000001440000001520311734662204016612 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """A database for default values of settings.""" import sys import veusz.qtall as qt4 # default values to some settings in case the user does not have these defaultValues = { # export options 'export_DPI': 100, 'export_DPI_PDF': 150, 'export_color': True, 'export_antialias': True, 'export_quality': 85, 'export_background': '#ffffff00', 'export_SVG_text_as_text': False, # plot options 'plot_updatepolicy': -1, # update on document changed 'plot_antialias': True, 'plot_numthreads': 2, # recent files list 'main_recentfiles': [], # default stylesheet 'stylesheet_default': '', # default custom definitons 'custom_default': '', # colors (isdefault, 'notdefaultcolor') 'color_page': (True, 'white'), 'color_error': (True, 'red'), 'color_command': (True, 'blue'), 'color_cntrlline': (True, 'blue'), 'color_cntrlcorner': (True, 'black'), # further ui options 'toolbar_size': 24, # if set to true, do UI formatting in US/English 'ui_english': False, # use cwd as starting directory 'dirname_usecwd': False, # ask tutorial before? 'ask_tutorial': False, } class _SettingDB(object): """A class which provides access to a persistant settings database. Items are accesses as a dict, with items as key=value """ # list of colors colors = ('page', 'error', 'command', 'cntrlline', 'cntrlcorner') # names for display of colors and a longer description color_names = { 'page': ('Page', 'Page background color'), 'error': ('Error', 'Color for errors'), 'command': ('Console command', 'Commands in the console window color'), 'cntrlline': ('Control line', 'Color of lines controlling widgets'), 'cntrlcorner': ('Control corner', 'Color of corners controlling widgets'), } # default colors if isdefault is set in the setting color_defaults = { 'page': 'LightBase', 'error': 'red', 'command': 'blue', 'cntrlline': 'blue', 'cntrlcorner': 'black', } def __init__(self): """Initialise the object, reading the settings.""" # This domain name is fictional! self.domain = 'veusz.org' self.product = 'veusz' self.database = {} self.sepchars = "%%%" # read settings using QSettings self.readSettings() def color(self, name): """Get a color setting as a QColor.""" val = self.database['color_' + name] if val[0]: default = self.color_defaults[name] if default == 'LightBase': base = qt4.qApp.palette().color(qt4.QPalette.Base) if base.value() < 127: base = qt4.QColor(qt4.Qt.white) return base return qt4.QColor(default) else: return qt4.QColor(val[1]) def readSettings(self): """Read the settings using QSettings. Entries have / replaced with set of characters self.sepchars This is because it greatly simplifies the logic as QSettings has special meaning for / The only issues are that the key may be larger than 255 characters We should probably check for this """ s = qt4.QSettings(self.domain, self.product) for key in s.childKeys(): val = s.value(key).toString() realkey = unicode(key).replace(self.sepchars, '/') try: self.database[realkey] = eval( unicode(val) ) except: print >>sys.stderr, ('Error interpreting item "%s" in ' 'settings file' % realkey) # set any defaults which haven't been set for key, value in defaultValues.iteritems(): if key not in self.database: self.database[key] = value def writeSettings(self): """Write the settings using QSettings. This is called by the mainwindow on close """ s = qt4.QSettings(self.domain, self.product) # write each entry, keeping track of which ones haven't been written cleankeys = [] for key, value in self.database.iteritems(): cleankey = key.replace('/', self.sepchars) cleankeys.append(cleankey) # repr doesn't work on QStrings if isinstance(value, qt4.QString): value = unicode(value) s.setValue(cleankey, qt4.QVariant(repr(value))) # now remove all the values which have been removed remove = [] for key in list(s.childKeys()): if unicode(key) not in cleankeys: s.remove(key) def get(self, key, defaultval=None): """Return key if it is in database, else defaultval.""" return self.database.get(key, defaultval) def __getitem__(self, key): """Get the item from the database.""" return self.database[key] def __setitem__(self, key, value): """Set the value in the database.""" self.database[key] = value def __delitem__(self, key): """Remove the key from the database.""" del self.database[key] def __contains__(self, key): """Is the key in the database.""" return key in self.database # create the SettingDB singleton settingdb = _SettingDB() # a normal dict for non-persistent settings # (e.g. disable safe mode) transient_settings = {} def updateUILocale(): """Update locale to one given in preferences.""" global uilocale if settingdb['ui_english']: uilocale = qt4.QLocale.c() else: uilocale = qt4.QLocale.system() uilocale.setNumberOptions(qt4.QLocale.OmitGroupSeparator) qt4.QLocale.setDefault(uilocale) updateUILocale() veusz-1.15/setting/settings.py0000644002344000001440000002051511734662204016471 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Module for holding collections of settings.""" from reference import Reference class Settings(object): """A class for holding collections of settings.""" # differentiate widgets, settings and setting nodetype = 'settings' def __init__(self, name, descr = '', usertext='', pixmap='', setnsmode='formatting'): """A new Settings with a name. name: name in hierarchy descr: description (for user) usertext: name for user of class pixmap: pixmap to show in tab (if appropriate) setnsmode: type of Settings class, one of ('formatting', 'groupedsetting', 'widgetsettings', 'stylesheet') """ self.__dict__['setdict'] = {} self.name = name self.descr = descr self.usertext = usertext self.pixmap = pixmap self.setnsmode = setnsmode self.setnames = [] # a list of names self.parent = None def copy(self): """Make a copy of the settings and its subsettings.""" s = Settings( self.name, descr=self.descr, usertext=self.usertext, pixmap=self.pixmap, setnsmode=self.setnsmode ) for name in self.setnames: s.add( self.setdict[name].copy() ) return s def isWidget(self): """Is this object a widget?""" return False def getSettingsNames(self): """Get a list of names of settings.""" return self.setnames def getList(self): """Get a list of setting or settings types.""" return [self.setdict[n] for n in self.setnames] def getSettingList(self): """Get a list of setting types.""" return [self.setdict[n] for n in self.setnames if not isinstance(self.setdict[n], Settings)] def getSettingsList(self): """Get a list of settings types.""" return [self.setdict[n] for n in self.setnames if isinstance(self.setdict[n], Settings)] def getNames(self): """Return list of names.""" return self.setnames def getSettingNames(self): """Get list of setting names.""" return [n for n in self.setnames if not isinstance(self.setdict[n], Settings)] def getSettingsNames(self): """Get list of settings names.""" return [n for n in self.setnames if isinstance(self.setdict[n], Settings)] def isSetting(self, name): """Is the name a supported setting?""" return name in self.setdict def add(self, setting, posn = -1, readonly = False, pixmap=None): """Add a new setting with the name, or a set of subsettings.""" name = setting.name if name in self.setdict: raise RuntimeError, "Name already in settings dictionary" self.setdict[name] = setting if posn < 0: self.setnames.append(name) else: self.setnames.insert(posn, name) setting.parent = self if pixmap: setting.pixmap = pixmap if readonly: setting.readonly = True def remove(self, name): """Remove name from the list of settings.""" del self.setnames[ self.setnames.index( name ) ] del self.setdict[ name ] def __setattr__(self, name, val): """Allow us to do foo.setname = 42 """ d = self.__dict__['setdict'] if name in d: d[name].val = val else: self.__dict__[name] = val def __getattr__(self, name): """Allow us to do print foo.setname """ try: s = self.__dict__['setdict'][name] if isinstance(s, Settings): return s return s.val except KeyError: pass try: return self.__dict__[name] except KeyError: raise AttributeError, "'%s' is not a setting" % name def __getitem__(self, name): """Also allows us to do print foo['setname'] """ d = self.__dict__['setdict'] try: s = d[name] if isinstance(s, Settings): return s else: return s.val except KeyError: raise KeyError, "'%s' is not a setting" % name def __contains__(self, name): """Whether settings contains name.""" return name in self.__dict__['setdict'] def get(self, name = None): """Get the setting variable.""" if name is None: return self else: return self.setdict[name] def getFromPath(self, path): """Get setting according to the path given as a list.""" name = path[0] if name in self.setdict: val = self.setdict[name] if len(path) == 1: if isinstance(val, Settings): raise ValueError, ( '"%s" is a list of settings, not a setting' % name) else: return val else: if isinstance(val, Settings): return val.getFromPath(path[1:]) else: raise ValueError, '"%s" not a valid subsetting' % name else: raise ValueError, '"%s" is not a setting' % name def saveText(self, saveall, rootname = None): """Return the text which would reload the settings. if saveall is true, save those which haven't been modified. rootname is the part to stick on the front of the settings """ # we want to build the root up if we're not the first item # (first item is implicit) if rootname is None: rootname = '' else: rootname += self.name + '/' text = ''.join( [self.setdict[name].saveText(saveall, rootname) for name in self.setnames] ) return text def readDefaults(self, root, widgetname): """Return default values from saved text. root is the path of the setting in the db, built up by settings above this one widgetname is the name of the widget this setting belongs to """ root = '%s/%s' % (root, self.name) for s in self.setdict.values(): s.readDefaults(root, widgetname) def linkToStylesheet(self, _root=None): """Link the settings within this Settings to a stylesheet. _root is an internal parameter as this function is recursive.""" # build up root part of pathname to reference if _root is None: path = [] obj = self while not obj.parent.isWidget(): path.insert(0, obj.name) obj = obj.parent path = ['', 'StyleSheet', obj.parent.typename] + path + [''] _root = '/'.join(path) # iterate over subsettings for name, setn in self.setdict.iteritems(): thispath = _root + name if isinstance(setn, Settings): # call recursively if this is a Settings setn.linkToStylesheet(_root=thispath+'/') else: ref = Reference(thispath) try: # if the reference resolves, then set it ref.resolve(setn) setn.set(ref) setn.default = ref except Reference.ResolveException: pass veusz-1.15/setting/reference.py0000644002344000001440000000557111734662204016574 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### class Reference(object): """A value a setting can have to point to another setting. Formats of a reference are like /foo/bar/setting or ../Line/width alternatively style sheets can be used with the format, e.g. /StyleSheet/linewidth """ class ResolveException(Exception): pass def __init__(self, value): """Initialise reference with value, which is a string as above.""" self.value = value self.split = value.split('/') self.resolved = None def resolve(self, thissetting): """Return the setting object associated with the reference.""" # this is for stylesheet references which don't move if self.resolved: return self.resolved item = thissetting.parent parts = list(self.split) if parts[0] == '': # need root widget if begins with slash while item.parent is not None: item = item.parent parts = parts[1:] # do an iterative lookup of the setting for p in parts: if p == '..': if item.parent is not None: item = item.parent elif p == '': pass else: if item.isWidget(): child = item.getChild(p) if not child: try: item = item.settings.get(p) except KeyError: raise self.ResolveException() else: item = child else: try: item = item.get(p) except KeyError: raise self.ResolveException() # shortcut to resolve stylesheets # hopefully this won't ever change if len(self.split) > 2 and self.split[1] == 'StyleSheet': self.resolved = item return item veusz-1.15/setting/setting.py0000644002344000001440000016342011734662204016311 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Module for holding setting values. e.g. s = Int('foo', 5) s.get() s.set(42) s.fromText('42') """ import re import sys import numpy as N import veusz.qtall as qt4 import controls from settingdb import settingdb, uilocale from reference import Reference import veusz.utils as utils # if invalid type passed to set class InvalidType(Exception): pass class Setting(object): """A class to store a value with a particular type.""" # differentiate widgets, settings and setting nodetype = 'setting' typename = 'setting' def __init__(self, name, value, descr='', usertext='', formatting=False, hidden=False): """Initialise the values. name: setting name value: default value and initial value descr: description of the setting usertext: name of setting for user formatting: whether setting applies to formatting hidden: hide widget from user """ self.readonly = False self.parent = None self.name = name self.descr = descr self.usertext = usertext self.formatting = formatting self.hidden = hidden self.default = value self.onmodified = qt4.QObject() self._val = None # calls the set function for the val property self.val = value def isWidget(self): """Is this object a widget?""" return False def _copyHelper(self, before, after, optional): """Help copy an object. before are arguments before val after are arguments after val optinal as optional arguments """ if isinstance(self._val, Reference): val = self._val else: val = self.val args = (self.name,) + before + (val,) + after opt = optional.copy() opt['descr'] = self.descr opt['usertext'] = self.usertext opt['formatting'] = self.formatting opt['hidden'] = self.hidden obj = self.__class__(*args, **opt) obj.readonly = self.readonly obj.default = self.default return obj def copy(self): """Make a setting which has its values copied from this one. This needs to be overridden if the constructor changes """ return self._copyHelper((), (), {}) def get(self): """Get the value.""" if isinstance(self._val, Reference): return self._val.resolve(self).get() else: return self.convertFrom(self._val) def set(self, v): """Set the value.""" if isinstance(v, Reference): self._val = v else: # this also removes the linked value if there is one set self._val = self.convertTo(v) self.onmodified.emit(qt4.SIGNAL("onModified"), True) val = property(get, set, None, 'Get or modify the value of the setting') def isReference(self): """Is this a setting a reference to another object.""" return isinstance(self._val, Reference) def getReference(self): """Return the reference object. Raise ValueError if not a reference""" if isinstance(self._val, Reference): return self._val else: raise ValueError, "Setting is not a reference" def getStylesheetLink(self): """Get text that this setting should default to linked to the stylesheet.""" path = [] obj = self while not obj.parent.isWidget(): path.insert(0, obj.name) obj = obj.parent path = ['', 'StyleSheet', obj.parent.typename] + path return '/'.join(path) def linkToStylesheet(self): """Make this setting link to stylesheet setting, if possible.""" self.set( Reference(self.getStylesheetLink()) ) def _path(self): """Return full path of setting.""" path = [] obj = self while obj is not None: # logic easier to understand here # do not add settings name for settings of widget if not obj.isWidget() and obj.parent.isWidget(): pass else: if obj.name == '/': path.insert(0, '') else: path.insert(0, obj.name) obj = obj.parent return '/'.join(path) path = property(_path, None, None, 'Return the full path of the setting') def toText(self): """Convert the type to text for saving.""" return "" def fromText(self, text): """Convert text to type suitable for setting. Raises InvalidType if cannot convert.""" return None def readDefaults(self, root, widgetname): """Check whether the user has a default for this setting.""" deftext = None unnamedpath = '%s/%s' % (root, self.name) try: deftext = settingdb[unnamedpath] except KeyError: pass # named defaults supersedes normal defaults namedpath = '%s_NAME:%s' % (widgetname, unnamedpath) try: deftext = settingdb[namedpath] except KeyError: pass if deftext is not None: self.val = self.fromText(deftext) self.default = self.val def removeDefault(self): """Remove the default setting for this setting.""" # build up setting path path = '' item = self while not item.isWidget(): path = '/%s%s' % (item.name, path) item = item.parent # remove the settings (ignore if they are not set) if path in settingdb: del settingdb[path] # specific setting to this widgetname namedpath = '%s_NAME:%s' % (item.name, path) if namedpath in settingdb: del settingdb[namedpath] def setAsDefault(self, withwidgetname = False): """Set the current value of this setting as the default value If withwidthname is True, then it is only the default for widgets of the particular name this setting is contained within.""" # build up setting path path = '' item = self while not item.isWidget(): path = '/%s%s' % (item.name, path) item = item.parent # if the setting is only for widgets with a certain name if withwidgetname: path = '%s_NAME:%s' % (item.name, path) # set the default settingdb[path] = self.toText() def saveText(self, saveall, rootname = ''): """Return text to restore the value of this setting.""" if (saveall or not self.isDefault()) and not self.readonly: if isinstance(self._val, Reference): return "SetToReference('%s%s', %s)\n" % (rootname, self.name, repr(self._val.value)) else: return "Set('%s%s', %s)\n" % ( rootname, self.name, repr(self.val) ) else: return '' def setOnModified(self, fn): """Set the function to be called on modification (passing True).""" self.onmodified.connect(self.onmodified, qt4.SIGNAL("onModified"), fn) if isinstance(self._val, Reference): # make reference pointed to also call this onModified r = self._val.resolve(self) r.setOnModified(fn) def removeOnModified(self, fn): """Remove the function from the list of function to be called.""" self.onmodified.disconnect(self.onmodified, 0, fn, 0) def newDefault(self, value): """Update the default and the value.""" self.default = value self.val = value def isDefault(self): """Is the current value a default? This also returns true if it is linked to the appropriate stylesheet """ if ( isinstance(self._val, Reference) and isinstance(self.default, Reference) ): return self._val.value == self.default.value else: return self.val == self.default def isDefaultLink(self): """Is this a link to the default stylesheet value.""" return ( isinstance(self._val, Reference) and self._val.value == self.getStylesheetLink() ) def setSilent(self, val): """Set the setting, without propagating modified flags. This shouldn't often be used as it defeats the automatic updation. Used for temporary modifications.""" self._val = self.convertTo(val) def convertTo(self, val): """Convert for storage.""" return val def convertFrom(self, val): """Convert to storage.""" return val def makeControl(self, *args): """Make a qt control for editing the setting. The control emits settingValueChanged() when the setting has changed value.""" return None def getDocument(self): """Return document.""" p = self.parent while p: try: return p.getDocument() except AttributeError: pass p = p.parent return None def getWidget(self): """Return associated widget.""" w = self.parent while not w.isWidget(): w = w.parent return w def safeEvalHelper(self, text): """Evaluate an expression, catching naughtiness.""" try: if utils.checkCode(text) is not None: raise InvalidType return float( eval(text, self.getDocument().eval_context) ) except: raise InvalidType # forward setting to another setting class SettingBackwardCompat(Setting): """Forward setting requests to another setting. This is used for backward-compatibility. """ typename = 'backward-compat' def __init__(self, name, newrelpath, val, translatefn = None, **args): """Point this setting to another. newrelpath is a path relative to this setting's parent """ self.translatefn = translatefn Setting.__init__(self, name, val, **args) self.relpath = newrelpath.split('/') def getForward(self): """Get setting this setting forwards to.""" return self.parent.getFromPath(self.relpath) def convertTo(self, val): if self.parent is not None: return self.getForward().convertTo(val) def toText(self): return self.getForward().toText() def fromText(self, val): return self.getForward().fromText(val) def set(self, val): if self.parent is not None and not isinstance(val, Reference): if self.translatefn: val = self.translatefn(val) self.getForward().set(val) def isDefault(self): return self.getForward().isDefault() def get(self): return self.getForward().get() def copy(self): return self._copyHelper(('/'.join(self.relpath),), (), {'translatefn': self.translatefn}) def makeControl(self, *args): return None def saveText(self, saveall, rootname = ''): return '' def linkToStylesheet(self): """Do nothing for backward compatibility settings.""" pass # Store strings class Str(Setting): """String setting.""" typename = 'str' def convertTo(self, val): if isinstance(val, basestring): return val raise InvalidType def toText(self): return self.val def fromText(self, text): return text def makeControl(self, *args): return controls.String(self, *args) # Store bools class Bool(Setting): """Bool setting.""" typename = 'bool' def convertTo(self, val): if type(val) in (bool, int): return bool(val) raise InvalidType def toText(self): if self.val: return 'True' else: return 'False' def fromText(self, text): t = text.strip().lower() if t in ('true', '1', 't', 'y', 'yes'): return True elif t in ('false', '0', 'f', 'n', 'no'): return False else: raise InvalidType def makeControl(self, *args): return controls.Bool(self, *args) # Storing integers class Int(Setting): """Integer settings.""" typename = 'int' def __init__(self, name, value, minval=-1000000, maxval=1000000, **args): """Initialise the values. minval is minimum possible value of setting maxval is maximum possible value of setting """ self.minval = minval self.maxval = maxval Setting.__init__(self, name, value, **args) def copy(self): """Make a setting which has its values copied from this one. This needs to be overridden if the constructor changes """ return self._copyHelper((), (), {'minval': self.minval, 'maxval': self.maxval}) def convertTo(self, val): if isinstance(val, int): if val >= self.minval and val <= self.maxval: return val else: raise InvalidType, 'Out of range allowed' raise InvalidType def toText(self): return unicode( uilocale.toString(self.val) ) def fromText(self, text): i, ok = uilocale.toLongLong(text) if not ok: raise ValueError if i >= self.minval and i <= self.maxval: return i else: raise InvalidType, 'Out of range allowed' def makeControl(self, *args): return controls.Int(self, *args) def _finiteRangeFloat(f, minval=-1e300, maxval=1e300): """Return a finite float in range or raise exception otherwise.""" f = float(f) if not N.isfinite(f): raise InvalidType, 'Finite values only allowed' if f < minval or f > maxval: raise InvalidType, 'Out of range allowed' return f # for storing floats class Float(Setting): """Float settings.""" typename = 'float' def __init__(self, name, value, minval=-1e200, maxval=1e200, **args): """Initialise the values. minval is minimum possible value of setting maxval is maximum possible value of setting """ self.minval = minval self.maxval = maxval Setting.__init__(self, name, value, **args) def copy(self): """Make a setting which has its values copied from this one. This needs to be overridden if the constructor changes """ return self._copyHelper((), (), {'minval': self.minval, 'maxval': self.maxval}) def convertTo(self, val): if isinstance(val, int) or isinstance(val, float): return _finiteRangeFloat(val, minval=self.minval, maxval=self.maxval) raise InvalidType def toText(self): return unicode(uilocale.toString(self.val)) def fromText(self, text): f, ok = uilocale.toDouble(text) if not ok: # try to evaluate f = self.safeEvalHelper(text) return self.convertTo(f) def makeControl(self, *args): return controls.Edit(self, *args) class FloatOrAuto(Setting): """Save a float or text auto.""" typename = 'float-or-auto' def convertTo(self, val): if type(val) in (int, float): return _finiteRangeFloat(val) elif isinstance(val, basestring) and val.strip().lower() == 'auto': return None else: raise InvalidType def convertFrom(self, val): if val is None: return 'Auto' else: return val def toText(self): if self.val is None or (isinstance(self.val, basestring) and self.val.lower() == 'auto'): return 'Auto' else: return unicode(uilocale.toString(self.val)) def fromText(self, text): if text.strip().lower() == 'auto': return 'Auto' else: f, ok = uilocale.toDouble(text) if ok: return self.convertTo(f) else: # try to evaluate return self.safeEvalHelper(text) def makeControl(self, *args): return controls.Choice(self, True, ['Auto'], *args) class IntOrAuto(Setting): """Save an int or text auto.""" typename = 'int-or-auto' def convertTo(self, val): if isinstance(val, int): return val elif isinstance(val, basestring) and val.strip().lower() == 'auto': return None else: raise InvalidType def convertFrom(self, val): if val is None: return 'Auto' else: return val def toText(self): if self.val is None or (isinstance(self.val, basestring) and self.val.lower() == 'auto'): return 'Auto' else: return unicode( uilocale.toString(self.val) ) def fromText(self, text): if text.strip().lower() == 'auto': return 'Auto' else: i, ok = uilocale.toLongLong(text) if not ok: raise InvalidType return i def makeControl(self, *args): return controls.Choice(self, True, ['Auto'], *args) # these are functions used by the distance setting below. # they don't work as class methods def _distPhys(match, painter, mult): """Convert a physical unit measure in multiples of points.""" return (painter.pixperpt * mult * float(match.group(1)) * painter.scaling) def _distInvPhys(pixdist, painter, mult, unit): """Convert number of pixels into physical distance.""" dist = pixdist / (mult * painter.pixperpt * painter.scaling) return "%.3g%s" % (dist, unit) def _distPerc(match, painter): """Convert from a percentage of maxsize.""" return painter.maxsize * 0.01 * float(match.group(1)) def _distInvPerc(pixdist, painter): """Convert pixel distance into percentage.""" perc = pixdist * 100. / painter.maxsize return "%.3g%%" % perc def _distFrac(match, painter): """Convert from a fraction a/b of maxsize.""" try: return painter.maxsize * float(match.group(1))/float(match.group(4)) except ZeroDivisionError: return 0. def _distRatio(match, painter): """Convert from a simple 0.xx ratio of maxsize.""" # if it's greater than 1 then assume it's a point measurement if float(match.group(1)) > 1.: return _distPhys(match, painter, 1) return painter.maxsize * float(match.group(1)) # regular expression to match distances distre_expr = r'''^ [ ]* # optional whitespace (\.?[0-9]+|[0-9]+\.[0-9]*) # a floating point number [ ]* # whitespace (cm|pt|mm|inch|in|"|%|| # ( unit, no unit, (?P/) ) # or / ) (?(slash)[ ]* # if it was a slash, match any whitespace (\.?[0-9]+|[0-9]+\.[0-9]*)) # and match following fp number [ ]* # optional whitespace $''' class Distance(Setting): """A veusz distance measure, e.g. 1pt or 3%.""" typename = 'distance' # match a distance distre = re.compile(distre_expr, re.VERBOSE) # functions to convert from unit values to pixels unit_func = { 'cm': lambda match, painter: _distPhys(match, painter, 28.452756), 'pt': lambda match, painter: _distPhys(match, painter, 1.), 'mm': lambda match, painter: _distPhys(match, painter, 2.8452756), 'in': lambda match, painter: _distPhys(match, painter, 72.27), 'inch': lambda match, painter: _distPhys(match, painter, 72.27), '"': lambda match, painter: _distPhys(match, painter, 72.27), '%': _distPerc, '/': _distFrac, '': _distRatio } # inverse functions for converting pixels to units inv_unit_func = { 'cm': lambda match, painter: _distInvPhys(match, painter, 28.452756, 'cm'), 'pt': lambda match, painter: _distInvPhys(match, painter, 1., 'pt'), 'mm': lambda match, painter: _distInvPhys(match, painter, 2.8452756, 'mm'), 'in': lambda match, painter: _distInvPhys(match, painter, 72.27, 'in'), 'inch': lambda match, painter: _distInvPhys(match, painter, 72.27, 'in'), '"': lambda match, painter: _distInvPhys(match, painter, 72.27, 'in'), '%': _distInvPerc, '/': _distInvPerc, '': _distInvPerc } @classmethod def isDist(kls, dist): """Is the text a valid distance measure?""" return kls.distre.match(dist) is not None def convertTo(self, val): if self.distre.match(val) is not None: return val else: raise InvalidType def toText(self): # convert decimal point to display locale return self.val.replace('.', qt4.QString(uilocale.decimalPoint())) def fromText(self, text): # convert decimal point from display locale text = text.replace(qt4.QString(uilocale.decimalPoint()), '.') if self.isDist(text): return text else: raise InvalidType def makeControl(self, *args): return controls.Distance(self, *args) @classmethod def convertDistance(kls, painter, dist): '''Convert a distance to plotter units. dist: eg 0.1 (fraction), 10% (percentage), 1/10 (fraction), 10pt, 1cm, 20mm, 1inch, 1in, 1" (size) maxsize: size fractions are relative to painter: painter to get metrics to convert physical sizes ''' # match distance against expression m = kls.distre.match(dist) if m is not None: # lookup function to call to do conversion func = kls.unit_func[m.group(2)] return func(m, painter) # none of the regexps match raise ValueError( "Cannot convert distance in form '%s'" % dist ) def convert(self, painter): """Convert this setting's distance as above""" return self.convertDistance(painter, self.val) def convertPts(self, painter): """Get the distance in points.""" return self.convert(painter) / painter.pixperpt def convertInverse(self, distpix, painter): """Convert distance in pixels into units of this distance. """ m = self.distre.match(self.val) if m is not None: # if it matches convert back inversefn = self.inv_unit_func[m.group(2)] else: # otherwise force unit inversefn = self.inv_unit_func['cm'] # do inverse mapping return inversefn(distpix, painter) class DistancePt(Distance): """For a distance in points.""" def makeControl(self, *args): return controls.DistancePt(self, *args) class DistanceOrAuto(Distance): """A distance or the value Auto""" typename = 'distance-or-auto' distre = re.compile( distre_expr + r'|^Auto$', re.VERBOSE ) def isAuto(self): return self.val == 'Auto' def makeControl(self, *args): return controls.Distance(self, allowauto=True, *args) class Choice(Setting): """One out of a list of strings.""" # maybe should be implemented as a dict to speed up checks typename = 'choice' def __init__(self, name, vallist, val, **args): """Setting val must be in vallist. descriptions is an optional addon to put a tooltip on each item in the control. """ assert type(vallist) in (list, tuple) self.vallist = vallist self.descriptions = args.get('descriptions', None) if self.descriptions: del args['descriptions'] Setting.__init__(self, name, val, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((self.vallist,), (), {}) def convertTo(self, val): if val in self.vallist: return val else: raise InvalidType def toText(self): return self.val def fromText(self, text): if text in self.vallist: return text else: raise InvalidType def makeControl(self, *args): argsv = {'descriptions': self.descriptions} return controls.Choice(self, False, self.vallist, *args, **argsv) class ChoiceOrMore(Setting): """One out of a list of strings, or anything else.""" # maybe should be implemented as a dict to speed up checks typename = 'choice-or-more' def __init__(self, name, vallist, val, **args): """Setting has val must be in vallist. descriptions is an optional addon to put a tooltip on each item in the control """ self.vallist = vallist self.descriptions = args.get('descriptions', None) if self.descriptions: del args['descriptions'] Setting.__init__(self, name, val, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((self.vallist,), (), {}) def convertTo(self, val): return val def toText(self): return self.val def fromText(self, text): return text def makeControl(self, *args): argsv = {'descriptions': self.descriptions} return controls.Choice(self, True, self.vallist, *args, **argsv) class FloatDict(Setting): """A dictionary, taking floats as values.""" typename = 'float-dict' def convertTo(self, val): if type(val) != dict: raise InvalidType out = {} for key, val in val.iteritems(): if type(val) not in (float, int): raise InvalidType else: out[key] = val return out def toText(self): keys = self.val.keys() keys.sort() text = ['%s = %s' % (k, uilocale.toString(self.val[k])) for k in keys] return '\n'.join(text) def fromText(self, text): """Do conversion from list of a=X\n values.""" out = {} # break up into lines for l in text.split('\n'): l = l.strip() if len(l) == 0: continue # break up using = p = l.strip().split('=') if len(p) != 2: raise InvalidType v, ok = uilocale.toDouble(p[1]) if not ok: raise InvalidType out[ p[0].strip() ] = v return out def makeControl(self, *args): return controls.MultiLine(self, *args) class FloatList(Setting): """A list of float values.""" typename = 'float-list' def convertTo(self, val): if type(val) not in (list, tuple): raise InvalidType # horribly slow test for invalid entries out = [] for i in val: if type(i) not in (float, int): raise InvalidType else: out.append( float(i) ) return out def toText(self): """Make a string a, b, c.""" # can't use the comma for splitting if used as a decimal point join = ', ' if uilocale.decimalPoint() == qt4.QChar(','): join = '; ' return join.join( [unicode(uilocale.toString(x)) for x in self.val] ) def fromText(self, text): """Convert from a, b, c or a b c.""" # don't use commas if it is the decimal separator splitre = r'[\t\n, ]+' if uilocale.decimalPoint() == qt4.QChar(','): splitre = r'[\t\n; ]+' out = [] for x in re.split(splitre, text.strip()): if x: f, ok = uilocale.toDouble(x) if ok: out.append(f) else: out.append( self.safeEvalHelper(x) ) return out def makeControl(self, *args): return controls.String(self, *args) class WidgetPath(Str): """A setting holding a path to a widget. This is checked for validity.""" typename = 'widget-path' def __init__(self, name, val, relativetoparent=True, allowedwidgets = None, **args): """Initialise the setting. The widget is located relative to parent if relativetoparent is True, otherwise this widget. If allowedwidgets is not None, only those widgets types in the list are allowed by this setting. """ Str.__init__(self, name, val, **args) self.relativetoparent = relativetoparent self.allowedwidgets = allowedwidgets def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {'relativetoparent': self.relativetoparent, 'allowedwidgets': self.allowedwidgets}) def getReferredWidget(self, val = None): """Get the widget referred to. We double-check here to make sure it's the one. Returns None if setting is blank InvalidType is raised if there's a problem """ # this is a bit of a hack, so we don't have to pass a value # for the setting (which we need to from convertTo) if val is None: val = self.val if val == '': return None # find the widget associated with this setting widget = self while not widget.isWidget(): widget = widget.parent # usually makes sense to give paths relative to a parent of a widget if self.relativetoparent: widget = widget.parent # resolve the text to a widget try: widget = widget.document.resolve(widget, val) except ValueError: raise InvalidType # check the widget against the list of allowed types if given if self.allowedwidgets is not None: allowed = False for c in self.allowedwidgets: if isinstance(widget, c): allowed = True if not allowed: raise InvalidType return widget class Dataset(Str): """A setting to choose from the possible datasets.""" typename = 'dataset' def __init__(self, name, val, dimensions=1, datatype='numeric', **args): """ dimensions is the number of dimensions the dataset needs """ Setting.__init__(self, name, val, **args) self.dimensions = dimensions self.datatype = datatype def copy(self): """Make a setting which has its values copied from this one.""" return self._copyHelper((), (), {'dimensions': self.dimensions, 'datatype': self.datatype}) def makeControl(self, *args): """Allow user to choose between the datasets.""" return controls.Dataset(self, self.getDocument(), self.dimensions, self.datatype, *args) def getData(self, doc): """Return a list of datasets entered.""" d = doc.data.get(self.val) if ( d is not None and d.datatype == self.datatype and d.dimensions == self.dimensions ): return d class Strings(Setting): """A multiple set of strings.""" typename = 'str-multi' def convertTo(self, val): """Takes a tuple/list of strings: ('ds1','ds2'...) """ if isinstance(val, basestring): return (val, ) if type(val) not in (list, tuple): raise InvalidType # check each entry in the list is appropriate for ds in val: if not isinstance(ds, basestring): raise InvalidType return tuple(val) def makeControl(self, *args): """Allow user to choose between the datasets.""" return controls.Strings(self, self.getDocument(), *args) class Datasets(Setting): """A setting to choose one or more of the possible datasets.""" typename = 'dataset-multi' def __init__(self, name, val, dimensions=1, datatype='numeric', **args): """ dimensions is the number of dimensions the dataset needs """ Setting.__init__(self, name, val, **args) self.dimensions = dimensions self.datatype = datatype def convertTo(self, val): """Takes a tuple/list of strings: ('ds1','ds2'...) """ if isinstance(val, basestring): return (val, ) if type(val) not in (list, tuple): raise InvalidType # check each entry in the list is appropriate for ds in val: if not isinstance(ds, basestring): raise InvalidType return tuple(val) def copy(self): """Make a setting which has its values copied from this one.""" return self._copyHelper((), (), {'dimensions': self.dimensions, 'datatype': self.datatype}) def makeControl(self, *args): """Allow user to choose between the datasets.""" return controls.Datasets(self, self.getDocument(), self.dimensions, self.datatype, *args) def getData(self, doc): """Return a list of datasets entered.""" out = [] for name in self.val: d = doc.data.get(name) if ( d is not None and d.datatype == self.datatype and d.dimensions == self.dimensions ): out.append(d) return out class DatasetOrFloatList(Dataset): """Choose a dataset or specify a list of float values.""" typename = 'dataset-or-floatlist' def convertTo(self, val): """Check is a string (dataset name) or a list of floats (numbers). """ if isinstance(val, basestring): return val elif isinstance(val, float) or isinstance(val, int): return [val] else: try: return [float(x) for x in val] except (TypeError, ValueError): raise InvalidType def toText(self): if isinstance(self.val, basestring): return self.val else: # join based on , or ; depending on decimal point join = ', ' if uilocale.decimalPoint() == qt4.QChar(','): join = '; ' return join.join( [ unicode(uilocale.toString(x)) for x in self.val ] ) def fromText(self, text): """Convert from text.""" # split based on , or ; depending on decimal point splitre = r'[\t\n, ]+' if uilocale.decimalPoint() == qt4.QChar(','): splitre = r'[\t\n; ]+' out = [] for x in re.split(splitre, text.strip()): if x: f, ok = uilocale.toDouble(x) if ok: out.append(f) else: # fail conversion, so exit with text return text return out def getFloatArray(self, doc): """Get a numpy of values or None.""" if isinstance(self.val, basestring): ds = doc.data.get(self.val) if ds: # get numpy of values return ds.data else: return None else: # list of values return N.array(self.val) def isDataset(self, doc): """Is this setting a dataset?""" return (isinstance(self.val, basestring) and doc.data.get(self.val)) def isEmpty(self): """Is this unset?""" return self.val == [] def getData(self, doc): """Return veusz dataset""" if isinstance(self.val, basestring): d = doc.data.get(self.val) if ( d is not None and d.datatype == self.datatype and d.dimensions == self.dimensions ): return d return None else: # blah - need to import here due to dependencies import veusz.document as document return document.Dataset(data=self.val) class DatasetOrStr(Dataset): """Choose a dataset or enter a string.""" typename = 'dataset-or-str' def getData(self, doc, checknull=False): """Return either a list of strings, a single item list. If checknull then None is returned if blank """ if doc: ds = doc.data.get(self.val) if ds and ds.datatype == self.datatype: return ds.data if checknull and not self.val: return None else: return [unicode(self.val)] def makeControl(self, *args): # use string editor rather than drop down list # need to write a custom control return controls.DatasetOrString(self, self.getDocument(), self.dimensions, self.datatype, *args) #return controls.String(self, *args) class Color(ChoiceOrMore): """A color setting.""" typename = 'color' _colors = [ 'white', 'black', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'grey', 'darkred', 'darkgreen', 'darkblue', 'darkcyan', 'darkmagenta' ] controls.Color._colors = _colors def __init__(self, name, value, **args): """Initialise the color setting with the given name, default and description.""" ChoiceOrMore.__init__(self, name, self._colors, value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def color(self): """Return QColor for color.""" return qt4.QColor(self.val) def makeControl(self, *args): return controls.Color(self, *args) class FillStyle(Choice): """A setting for the different fill styles provided by Qt.""" typename = 'fill-style' _fillstyles = [ 'solid', 'horizontal', 'vertical', 'cross', 'forward diagonals', 'backward diagonals', 'diagonal cross', '94% dense', '88% dense', '63% dense', '50% dense', '37% dense', '12% dense', '6% dense' ] _fillcnvt = { 'solid': qt4.Qt.SolidPattern, 'horizontal': qt4.Qt.HorPattern, 'vertical': qt4.Qt.VerPattern, 'cross': qt4.Qt.CrossPattern, 'forward diagonals': qt4.Qt.FDiagPattern, 'backward diagonals': qt4.Qt.BDiagPattern, 'diagonal cross': qt4.Qt.DiagCrossPattern, '94% dense': qt4.Qt.Dense1Pattern, '88% dense': qt4.Qt.Dense2Pattern, '63% dense': qt4.Qt.Dense3Pattern, '50% dense': qt4.Qt.Dense4Pattern, '37% dense': qt4.Qt.Dense5Pattern, '12% dense': qt4.Qt.Dense6Pattern, '6% dense': qt4.Qt.Dense7Pattern } controls.FillStyle._fills = _fillstyles controls.FillStyle._fillcnvt = _fillcnvt def __init__(self, name, value, **args): Choice.__init__(self, name, self._fillstyles, value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def qtStyle(self): """Return Qt ID of fill.""" return self._fillcnvt[self.val] def makeControl(self, *args): return controls.FillStyle(self, *args) class LineStyle(Choice): """A setting choosing a particular line style.""" typename = 'line-style' # list of allowed line styles _linestyles = ['solid', 'dashed', 'dotted', 'dash-dot', 'dash-dot-dot', 'dotted-fine', 'dashed-fine', 'dash-dot-fine', 'dot1', 'dot2', 'dot3', 'dot4', 'dash1', 'dash2', 'dash3', 'dash4', 'dash5', 'dashdot1', 'dashdot2', 'dashdot3'] # convert from line styles to Qt constants and a custom pattern (if any) _linecnvt = { 'solid': (qt4.Qt.SolidLine, None), 'dashed': (qt4.Qt.DashLine, None), 'dotted': (qt4.Qt.DotLine, None), 'dash-dot': (qt4.Qt.DashDotLine, None), 'dash-dot-dot': (qt4.Qt.DashDotDotLine, None), 'dotted-fine': (qt4.Qt.CustomDashLine, [2, 4]), 'dashed-fine': (qt4.Qt.CustomDashLine, [8, 4]), 'dash-dot-fine': (qt4.Qt.CustomDashLine, [8, 4, 2, 4]), 'dot1': (qt4.Qt.CustomDashLine, [0.1, 2]), 'dot2': (qt4.Qt.CustomDashLine, [0.1, 4]), 'dot3': (qt4.Qt.CustomDashLine, [0.1, 6]), 'dot4': (qt4.Qt.CustomDashLine, [0.1, 8]), 'dash1': (qt4.Qt.CustomDashLine, [4, 4]), 'dash2': (qt4.Qt.CustomDashLine, [4, 8]), 'dash3': (qt4.Qt.CustomDashLine, [8, 8]), 'dash4': (qt4.Qt.CustomDashLine, [16, 8]), 'dash5': (qt4.Qt.CustomDashLine, [16, 16]), 'dashdot1': (qt4.Qt.CustomDashLine, [0.1, 4, 4, 4]), 'dashdot2': (qt4.Qt.CustomDashLine, [0.1, 4, 8, 4]), 'dashdot3': (qt4.Qt.CustomDashLine, [0.1, 2, 4, 2]), } controls.LineStyle._lines = _linestyles def __init__(self, name, default, **args): Choice.__init__(self, name, self._linestyles, default, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def qtStyle(self): """Get Qt ID of chosen line style.""" return self._linecnvt[self.val] def makeControl(self, *args): return controls.LineStyle(self, *args) class Axis(Str): """A setting to hold the name of an axis.""" typename = 'axis' def __init__(self, name, val, direction, **args): """Initialise using the document, so we can get the axes later. direction is horizontal or vertical to specify the type of axis to show """ Setting.__init__(self, name, val, **args) self.direction = direction def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (self.direction,), {}) def makeControl(self, *args): """Allows user to choose an axis or enter a name.""" return controls.Axis(self, self.getDocument(), self.direction, *args) class WidgetChoice(Str): """Hold the name of a child widget.""" typename = 'widget-choice' def __init__(self, name, val, widgettypes={}, **args): """Choose widgets from (named) type given.""" Setting.__init__(self, name, val, **args) self.widgettypes = widgettypes def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {'widgettypes': self.widgettypes}) def buildWidgetList(self, level, widget, outdict): """A recursive helper to build up a list of possible widgets. This iterates over widget's children, and adds widgets as tuples to outdict using outdict[name] = (widget, level) Lower level images of the same name outweigh other images further down the tree """ for child in widget.children: if child.typename in self.widgettypes: if (child.name not in outdict) or (outdict[child.name][1]>level): outdict[child.name] = (child, level) else: self.buildWidgetList(level+1, child, outdict) def getWidgetList(self): """Return a dict of valid widget names and the corresponding objects.""" # find widget which contains setting widget = self.parent while not widget.isWidget() and widget is not None: widget = widget.parent # get widget's parent if widget is not None: widget = widget.parent # get list of widgets from recursive find images = {} if widget is not None: self.buildWidgetList(0, widget, images) # turn (object, level) pairs into object outdict = {} for name, val in images.iteritems(): outdict[name] = val[0] return outdict def findWidget(self): """Find the image corresponding to this setting. Returns Image object if succeeds or None if fails """ widgets = self.getWidgetList() try: return widgets[self.get()] except KeyError: return None def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def makeControl(self, *args): """Allows user to choose an image widget or enter a name.""" return controls.WidgetChoice(self, self.getDocument(), *args) class Marker(Choice): """Choose a marker type from one allowable.""" typename = 'marker' def __init__(self, name, value, **args): Choice.__init__(self, name, utils.MarkerCodes, value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def makeControl(self, *args): return controls.Marker(self, *args) class Arrow(Choice): """Choose an arrow type from one allowable.""" typename = 'arrow' def __init__(self, name, value, **args): Choice.__init__(self, name, utils.ArrowCodes, value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def makeControl(self, *args): return controls.Arrow(self, *args) class LineSet(Setting): """A setting which corresponds to a set of lines. """ typename='line-multi' def convertTo(self, val): """Takes a tuple/list of tuples: [('dotted', '1pt', 'color', , False), ...] These are style, width, color, and hide or style, widget, color, transparency, hide """ if type(val) not in (list, tuple): raise InvalidType # check each entry in the list is appropriate for line in val: try: style, width, color, hide = line except ValueError: raise InvalidType if ( not isinstance(color, basestring) or not Distance.isDist(width) or style not in LineStyle._linestyles or type(hide) not in (int, bool) ): raise InvalidType return val def makeControl(self, *args): """Make specialised lineset control.""" return controls.LineSet(self, *args) def makePen(self, painter, row): """Make a pen for the painter using row. If row is outside of range, then cycle """ if len(self.val) == 0: return qt4.QPen(qt4.Qt.NoPen) else: row = row % len(self.val) v = self.val[row] style, width, color, hide = v width = Distance.convertDistance(painter, width) style, dashpattern = LineStyle._linecnvt[style] col = utils.extendedColorToQColor(color) pen = qt4.QPen(col, width, style) if dashpattern: pen.setDashPattern(dashpattern) if hide: pen.setStyle(qt4.Qt.NoPen) return pen class FillSet(Setting): """A setting which corresponds to a set of fills. This setting keeps an internal array of LineSettings. """ typename = 'fill-multi' def convertTo(self, val): """Takes a tuple/list of tuples: [('solid', 'color', False), ...] These are color, fill style, and hide or color, fill style, and hide (style, color, hide, [optional transparency, linewidth, linestyle, spacing, backcolor, backtrans, backhide]]) """ if type(val) not in (list, tuple): raise InvalidType # check each entry in the list is appropriate for fill in val: try: style, color, hide = fill[:3] except ValueError: raise InvalidType if ( not isinstance(color, basestring) or style not in utils.extfillstyles or type(hide) not in (int, bool) or len(fill) not in (3, 10) ): raise InvalidType return val def makeControl(self, *args): """Make specialised lineset control.""" return controls.FillSet(self, *args) def returnBrushExtended(self, row): """Return BrushExtended for the row.""" import collections s = collections.BrushExtended('tempbrush') if len(self.val) == 0: s.hide = True else: v = self.val[row % len(self.val)] s.style = v[0] col = utils.extendedColorToQColor(v[1]) if col.alpha() != 255: s.transparency = int(100 - col.alphaF()*100) col.setAlpha(255) s.color = col.name() else: s.color = v[1] s.hide = v[2] if len(v) == 10: (s.transparency, s.linewidth, s.linestyle, s.patternspacing, s.backcolor, s.backtransparency, s.backhide) = v[3:] return s class Filename(Str): """Represents a filename setting.""" typename = 'filename' def makeControl(self, *args): return controls.Filename(self, 'file', *args) def convertTo(self, val): if sys.platform == 'win32': val = val.replace('\\', '/') return val class ImageFilename(Filename): """Represents an image filename setting.""" typename = 'filename-image' def makeControl(self, *args): return controls.Filename(self, 'image', *args) class FontFamily(Str): """Represents a font family.""" typename = 'font-family' def makeControl(self, *args): """Make a special font combobox.""" return controls.FontFamily(self, *args) class ErrorStyle(Choice): """Error bar style. The allowed values are below in _errorstyles. """ typename = 'errorbar-style' _errorstyles = ( 'none', 'bar', 'barends', 'box', 'diamond', 'curve', 'barbox', 'bardiamond', 'barcurve', 'boxfill', 'diamondfill', 'fillvert', 'fillhorz', 'linevert', 'linehorz', 'linevertbar', 'linehorzbar' ) controls.ErrorStyle._errorstyles = _errorstyles def __init__(self, name, value, **args): Choice.__init__(self, name, self._errorstyles, value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def makeControl(self, *args): return controls.ErrorStyle(self, *args) class AlignHorz(Choice): """Alignment horizontally.""" typename = 'align-horz' def __init__(self, name, value, **args): Choice.__init__(self, name, ['left', 'centre', 'right'], value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) class AlignVert(Choice): """Alignment vertically.""" typename = 'align-vert' def __init__(self, name, value, **args): Choice.__init__(self, name, ['top', 'centre', 'bottom'], value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) class AlignHorzWManual(Choice): """Alignment horizontally.""" typename = 'align-horz-+manual' def __init__(self, name, value, **args): Choice.__init__(self, name, ['left', 'centre', 'right', 'manual'], value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) class AlignVertWManual(Choice): """Alignment vertically.""" typename = 'align-vert-+manual' def __init__(self, name, value, **args): Choice.__init__(self, name, ['top', 'centre', 'bottom', 'manual'], value, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) # Bool which shows/hides other settings class BoolSwitch(Bool): """Bool switching setting.""" def __init__(self, name, value, settingsfalse=[], settingstrue=[], **args): """Enables/disables a set of settings if True or False settingsfalse and settingstrue are lists of names of settings which are hidden/shown to user """ self.sfalse = settingsfalse self.strue = settingstrue Bool.__init__(self, name, value, **args) def makeControl(self, *args): return controls.BoolSwitch(self, *args) def copy(self): return self._copyHelper((), (), {'settingsfalse': self.sfalse, 'settingstrue': self.strue}) class ChoiceSwitch(Choice): """Show or hide other settings based on the choice given here.""" def __init__(self, name, vallist, value, settingstrue=[], settingsfalse=[], showfn=lambda val: True, **args): """Enables/disables a set of settings if True or False settingsfalse and settingstrue are lists of names of settings which are hidden/shown to user depending on showfn(val).""" self.sfalse = settingsfalse self.strue = settingstrue self.showfn = showfn Choice.__init__(self, name, vallist, value, **args) def makeControl(self, *args): return controls.ChoiceSwitch(self, *args) def copy(self): return self._copyHelper((self.vallist), (), {'settingsfalse': self.sfalse, 'settingstrue': self.strue, 'showfn': self.showfn}) class FillStyleExtended(ChoiceSwitch): """A setting for the different fill styles provided by Qt.""" typename = 'fill-style-ext' _strue = ( 'linewidth', 'linestyle', 'patternspacing', 'backcolor', 'backtransparency', 'backhide' ) @staticmethod def _ishatch(val): """Is this a hatching fill?""" return not ( val == 'solid' or val.find('dense') >= 0 ) def __init__(self, name, value, **args): ChoiceSwitch.__init__(self, name, utils.extfillstyles, value, settingstrue=self._strue, settingsfalse=(), showfn=self._ishatch, **args) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) def makeControl(self, *args): return controls.FillStyleExtended(self, *args) class RotateInterval(Choice): '''Rotate a label with intervals given.''' def __init__(self, name, val, **args): Choice.__init__(self, name, ('-180', '-135', '-90', '-45', '0', '45', '90', '135', '180'), val, **args) def convertTo(self, val): """Store rotate angle.""" # backward compatibility with rotate option # False: angle 0 # True: angle 90 if val == False: val = '0' elif val == True: val = '90' return Choice.convertTo(self, val) def copy(self): """Make a copy of the setting.""" return self._copyHelper((), (), {}) class Colormap(Str): """A setting to set the color map used in an image. This is based on a Str rather than Choice as the list might change later. """ def makeControl(self, *args): return controls.Colormap(self, self.getDocument(), *args) class AxisBound(FloatOrAuto): """Axis bound - either numeric, Auto or date.""" typename = 'axis-bound' def makeControl(self, *args): return controls.AxisBound(self, *args) def toText(self): """Convert to text, taking into account mode of Axis. Displays datetimes in date format if used """ try: mode = self.parent.mode except AttributeError: mode = None v = self.val if ( not isinstance(v, basestring) and v is not None and mode == 'datetime' ): return utils.dateFloatToString(v) return FloatOrAuto.toText(self) def fromText(self, txt): """Convert from text, allowing datetimes.""" v = utils.dateStringToDate(txt) if N.isfinite(v): return v else: return FloatOrAuto.fromText(self, txt) veusz-1.15/setting/controls.py0000644002344000001440000016435411734662204016506 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """Module for creating QWidgets for the settings, to enable their values to be changed. These widgets emit settingChanged(control, setting, val) when the setting is changed. The creator should use this to change the setting. """ from itertools import izip import re import numpy as N import veusz.qtall as qt4 import setting import veusz.utils as utils def styleClear(widget): """Return widget to default""" widget.setStyleSheet("") def styleError(widget): """Show error state on widget.""" widget.setStyleSheet("background-color: " + setting.settingdb.color('error').name() ) class DotDotButton(qt4.QPushButton): """A button for opening up more complex editor.""" def __init__(self, tooltip=None, checkable=True): qt4.QPushButton.__init__(self, "..", flat=True, checkable=checkable, maximumWidth=16) if tooltip: self.setToolTip(tooltip) self.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum) class Edit(qt4.QLineEdit): """Main control for editing settings which are text.""" def __init__(self, setting, parent): """Initialise the setting widget.""" qt4.QLineEdit.__init__(self, parent) self.setting = setting # set the text of the widget to the self.setText( setting.toText() ) self.connect(self, qt4.SIGNAL('editingFinished()'), self.validateAndSet) self.setting.setOnModified(self.onModified) if setting.readonly: self.setReadOnly(True) def validateAndSet(self): """Check the text is a valid setting and update it.""" text = unicode(self.text()) try: val = self.setting.fromText(text) styleClear(self) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) except setting.InvalidType: styleError(self) def onModified(self, mod): """called when the setting is changed remotely""" self.setText( self.setting.toText() ) class _EditBox(qt4.QTextEdit): """A popup edit box to support editing long text sections. Emits closing(text) when the box closes """ def __init__(self, origtext, readonly, parent): """Make a popup, framed widget containing a text editor.""" qt4.QTextEdit.__init__(self, parent) self.setWindowFlags(qt4.Qt.Popup) self.setAttribute(qt4.Qt.WA_DeleteOnClose) self.spacing = self.fontMetrics().height() self.origtext = origtext self.setPlainText(origtext) cursor = self.textCursor() cursor.movePosition(qt4.QTextCursor.End) self.setTextCursor(cursor) if readonly: self.setReadOnly(True) utils.positionFloatingPopup(self, parent) self.installEventFilter(self) def eventFilter(self, obj, event): """Grab clicks outside this window to close it.""" if ( isinstance(event, qt4.QMouseEvent) and event.buttons() != qt4.Qt.NoButton ): frame = qt4.QRect(0, 0, self.width(), self.height()) if not frame.contains(event.pos()): self.close() return True return qt4.QTextEdit.eventFilter(self, obj, event) def keyPressEvent(self, event): """Close if escape or return is pressed.""" qt4.QTextEdit.keyPressEvent(self, event) key = event.key() if key == qt4.Qt.Key_Escape: # restore original content self.setPlainText(self.origtext) self.close() elif key == qt4.Qt.Key_Return: # keep changes self.close() def sizeHint(self): """A reasonable size for the text editor.""" return qt4.QSize(self.spacing*40, self.spacing*3) def closeEvent(self, event): """Tell the calling widget that we are closing, and provide the new text.""" text = unicode(self.toPlainText()) text = text.replace('\n', '') self.emit(qt4.SIGNAL('closing'), text) event.accept() class String(qt4.QWidget): """A line editor which allows editting in a larger popup window.""" def __init__(self, setting, parent): qt4.QWidget.__init__(self, parent) self.setting = setting layout = qt4.QHBoxLayout() layout.setSpacing(0) layout.setMargin(0) self.setLayout(layout) self.edit = qt4.QLineEdit() layout.addWidget(self.edit) b = self.button = DotDotButton(tooltip="Edit text") layout.addWidget(b) # set the text of the widget to the self.edit.setText( setting.toText() ) self.connect(self.edit, qt4.SIGNAL('editingFinished()'), self.validateAndSet) self.connect(b, qt4.SIGNAL('toggled(bool)'), self.buttonToggled) self.setting.setOnModified(self.onModified) if setting.readonly: self.edit.setReadOnly(True) def buttonToggled(self, on): """Button is pressed to bring popup up / down.""" # if button is down and there's no existing popup, bring up a new one if on: e = _EditBox( unicode(self.edit.text()), self.setting.readonly, self.button) # we get notified with text when the popup closes self.connect(e, qt4.SIGNAL('closing'), self.boxClosing) e.show() def boxClosing(self, text): """Called when the popup edit box closes.""" # update the text if we can if not self.setting.readonly: self.edit.setText(text) self.edit.setFocus() self.parentWidget().setFocus() self.edit.setFocus() self.button.setChecked(False) def validateAndSet(self): """Check the text is a valid setting and update it.""" text = unicode(self.edit.text()) try: val = self.setting.fromText(text) styleClear(self.edit) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) except setting.InvalidType: styleError(self.edit) def onModified(self, mod): """called when the setting is changed remotely""" self.edit.setText( self.setting.toText() ) class Int(qt4.QSpinBox): """A control for changing an integer.""" def __init__(self, setting, parent): qt4.QSpinBox.__init__(self, parent) self.ignorechange = False self.setting = setting self.setMinimum(setting.minval) self.setMaximum(setting.maxval) self.setValue(setting.val) self.connect(self, qt4.SIGNAL('valueChanged(int)'), self.slotChanged) self.setting.setOnModified(self.onModified) if setting.readonly: self.setEnabled(False) def slotChanged(self, value): """If check box changes.""" # this is emitted by setValue, so ignore onModified doing this if not self.ignorechange: self.emit(qt4.SIGNAL('settingChanged'), self, self.setting, value) def onModified(self, mod): """called when the setting is changed remotely""" self.ignorechange = True self.setValue( self.setting.val ) self.ignorechange = False class Bool(qt4.QCheckBox): """A check box for changing a bool setting.""" def __init__(self, setting, parent): qt4.QCheckBox.__init__(self, parent) self.ignorechange = False self.setting = setting self.setChecked(setting.val) # we get a signal when the button is toggled self.connect( self, qt4.SIGNAL('toggled(bool)'), self.slotToggled ) self.setting.setOnModified(self.onModified) if setting.readonly: self.setEnabled(False) def slotToggled(self, state): """Emitted when checkbox toggled.""" # this is emitted by setChecked, so ignore onModified doing this if not self.ignorechange: self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, state ) def onModified(self, mod): """called when the setting is changed remotely""" self.ignorechange = True self.setChecked( self.setting.val ) self.ignorechange = False class BoolSwitch(Bool): """Bool for switching off/on other settings.""" def showEvent(self, event): Bool.showEvent(self, event) self.updateState() def slotToggled(self, state): Bool.slotToggled(self, state) self.updateState() def updateState(self): """Set hidden state of settings.""" s1, s2 = self.setting.strue, self.setting.sfalse if self.setting.val: show, hide = s1, s2 else: show, hide = s2, s1 if hasattr(self.parent(), 'showHideSettings'): self.parent().showHideSettings(show, hide) class Choice(qt4.QComboBox): """For choosing between a set of values.""" def __init__(self, setting, iseditable, vallist, parent, icons=None, descriptions=None): qt4.QComboBox.__init__(self, parent) self.setting = setting self.setEditable(iseditable) # stops combobox readjusting in size to fit contents self.setSizeAdjustPolicy( qt4.QComboBox.AdjustToMinimumContentsLengthWithIcon) if icons is None: # add items to list (text only) self.addItems( list(vallist) ) else: # add pixmaps and text to list for icon, text in izip(icons, vallist): self.addItem(icon, text) # use tooltip descriptions if requested if descriptions is not None: for i, descr in enumerate(descriptions): self.setItemData(i, qt4.QVariant(descr), qt4.Qt.ToolTipRole) # choose the correct setting try: index = list(vallist).index(setting.toText()) self.setCurrentIndex(index) except ValueError: # for cases when this is editable # set the text of the widget to the setting assert iseditable self.setEditText( setting.toText() ) # if a different item is selected self.connect( self, qt4.SIGNAL('activated(const QString&)'), self.slotActivated ) self.setting.setOnModified(self.onModified) if setting.readonly: self.setEnabled(False) # make completion case sensitive (to help fix case typos) if self.completer(): self.completer().setCaseSensitivity(qt4.Qt.CaseSensitive) def focusOutEvent(self, *args): """Allows us to check the contents of the widget.""" qt4.QComboBox.focusOutEvent(self, *args) self.slotActivated('') def slotActivated(self, val): """If a different item is chosen.""" text = unicode(self.currentText()) try: val = self.setting.fromText(text) styleClear(self) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) except setting.InvalidType: styleError(self) def onModified(self, mod): """called when the setting is changed remotely""" text = self.setting.toText() index = self.findText(text) if index >= 0: self.setCurrentIndex(index) if self.isEditable(): self.setEditText(text) class ChoiceSwitch(Choice): """Show or hide other settings based on value.""" def showEvent(self, event): Choice.showEvent(self, event) self.updateState() def onModified(self, mod): """called when the setting is changed remotely""" Choice.onModified(self, mod) self.updateState() def updateState(self): """Set hidden state of settings.""" s1, s2 = self.setting.strue, self.setting.sfalse if self.setting.showfn(self.setting.val): show, hide = s1, s2 else: show, hide = s2, s1 if hasattr(self.parent(), 'showHideSettings'): self.parent().showHideSettings(show, hide) class FillStyleExtended(ChoiceSwitch): """Extended fill style list.""" _icons = None def __init__(self, setting, parent): if self._icons is None: self._generateIcons() ChoiceSwitch.__init__(self, setting, False, utils.extfillstyles, parent, icons=self._icons) @classmethod def _generateIcons(cls): """Generate a list of pixmaps for drop down menu.""" import collections brush = collections.BrushExtended("") brush.color = 'black' brush.patternspacing = '5pt' brush.linewidth = '0.5pt' size = 12 cls._icons = icons = [] path = qt4.QPainterPath() path.addRect(0, 0, size, size) for f in utils.extfillstyles: pix = qt4.QPixmap(size, size) pix.fill() painter = qt4.QPainter(pix) painter.pixperpt = 1. painter.scaling = 1. painter.setRenderHint(qt4.QPainter.Antialiasing) brush.style = f utils.brushExtFillPath(painter, brush, path) painter.end() icons.append( qt4.QIcon(pix) ) class MultiLine(qt4.QTextEdit): """For editting multi-line settings.""" def __init__(self, setting, parent): """Initialise the widget.""" qt4.QTextEdit.__init__(self, parent) self.setting = setting self.setWordWrapMode(qt4.QTextOption.NoWrap) self.setTabChangesFocus(True) # set the text of the widget to the self.setPlainText( setting.toText() ) self.setting.setOnModified(self.onModified) if setting.readonly: self.setReadOnly(True) def focusOutEvent(self, *args): """Allows us to check the contents of the widget.""" qt4.QTextEdit.focusOutEvent(self, *args) text = unicode(self.toPlainText()) try: val = self.setting.fromText(text) styleClear(self) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) except setting.InvalidType: styleError(self) def onModified(self, mod): """called when the setting is changed remotely""" self.setPlainText( self.setting.toText() ) class Distance(Choice): """For editing distance settings.""" # used to remove non-numerics from the string # we also remove X/ from X/num stripnumre = re.compile(r"[0-9]*/|[^0-9.,]") # remove spaces stripspcre = re.compile(r"\s") def __init__(self, setting, parent, allowauto=False): '''Initialise with blank list, then populate with sensible units.''' Choice.__init__(self, setting, True, [], parent) self.allowauto = allowauto self.updateComboList() def updateComboList(self): '''Populates combo list with sensible list of other possible units.''' # turn off signals, so our modifications don't create more signals self.blockSignals(True) # get current text text = unicode(self.currentText()) # get rid of non-numeric things from the string num = self.stripnumre.sub('', text) # here are a list of possible different units the user can choose # between. should this be in utils? newitems = [ num+'pt', num+'cm', num+'mm', num+'in', num+'%', '1/'+num ] if self.allowauto: newitems.insert(0, 'Auto') # if we're already in this list, we position the current selection # to the correct item (up and down keys work properly then) # spaces are removed to make sure we get sensible matches spcfree = self.stripspcre.sub('', text) try: index = newitems.index(spcfree) except ValueError: index = 0 newitems.insert(0, text) # get rid of existing items in list (clear doesn't work here) for i in range(self.count()): self.removeItem(0) # put new items in and select the correct option self.addItems(newitems) self.setCurrentIndex(index) # must remember to do this! self.blockSignals(False) def slotActivated(self, val): '''Populate the drop down list before activation.''' self.updateComboList() Choice.slotActivated(self, val) class DistancePt(Choice): """For editing distances with defaults in points.""" points = ( '0pt', '0.25pt', '0.5pt', '1pt', '1.5pt', '2pt', '3pt', '4pt', '5pt', '6pt', '8pt', '10pt', '12pt', '14pt', '16pt', '18pt', '20pt', '22pt', '24pt', '26pt', '28pt', '30pt', '34pt', '40pt', '44pt', '50pt', '60pt', '70pt' ) def __init__(self, setting, parent, allowauto=False): '''Initialise with blank list, then populate with sensible units.''' Choice.__init__(self, setting, True, DistancePt.points, parent) class Dataset(qt4.QWidget): """Allow the user to choose between the possible datasets.""" def __init__(self, setting, document, dimensions, datatype, parent): """Initialise the combobox. The list is populated with datasets. dimensions specifies the dimension of the dataset to list Changes on the document refresh the list of datasets.""" qt4.QWidget.__init__(self, parent) self.choice = Choice(setting, True, [], None) self.connect( self.choice, qt4.SIGNAL("settingChanged"), self.slotSettingChanged ) b = self.button = DotDotButton(tooltip="Select using dataset browser") self.connect(b, qt4.SIGNAL("toggled(bool)"), self.slotButtonToggled) self.document = document self.dimensions = dimensions self.datatype = datatype self.lastdatasets = None self._populateEntries() self.connect(document, qt4.SIGNAL("sigModified"), self.slotModified) layout = qt4.QHBoxLayout() layout.setSpacing(0) layout.setMargin(0) layout.addWidget(self.choice) layout.addWidget(b) self.setLayout(layout) def slotSettingChanged(self, *args): """Reemit setting changed signal if combo box changes.""" self.emit( qt4.SIGNAL("settingChanged"), *args ) def _populateEntries(self): """Put the list of datasets into the combobox.""" # get datasets of the correct dimension datasets = [] for name, ds in self.document.data.iteritems(): if ds.dimensions == self.dimensions and ds.datatype == self.datatype: datasets.append(name) datasets.sort() if datasets != self.lastdatasets: utils.populateCombo(self.choice, datasets) self.lastdatasets = datasets def slotModified(self, modified): """Update the list of datasets if the document is modified.""" self._populateEntries() def slotButtonToggled(self, on): """Bring up list of datasets.""" if on: from veusz.qtwidgets.datasetbrowser import DatasetBrowserPopup d = DatasetBrowserPopup(self.document, unicode(self.choice.currentText()), self.button, filterdims=set((self.dimensions,)), filterdtype=set((self.datatype,)) ) self.connect(d, qt4.SIGNAL("closing"), self.boxClosing) self.connect(d, qt4.SIGNAL("newdataset"), self.newDataset) d.show() def boxClosing(self): """Called when the popup edit box closes.""" self.button.setChecked(False) def newDataset(self, dsname): """New dataset selected.""" self.emit( qt4.SIGNAL("settingChanged"), self, self.choice.setting, dsname ) class DatasetOrString(Dataset): """Allow use to choose a dataset or enter some text.""" def __init__(self, setting, document, dimensions, datatype, parent): Dataset.__init__(self, setting, document, dimensions, datatype, parent) b = self.textbutton = DotDotButton() self.layout().addWidget(b) self.connect(b, qt4.SIGNAL('toggled(bool)'), self.textButtonToggled) def textButtonToggled(self, on): """Button is pressed to bring popup up / down.""" # if button is down and there's no existing popup, bring up a new one if on: e = _EditBox( unicode(self.choice.currentText()), self.choice.setting.readonly, self.textbutton) # we get notified with text when the popup closes self.connect(e, qt4.SIGNAL("closing"), self.textBoxClosing) e.show() def textBoxClosing(self, text): """Called when the popup edit box closes.""" self.textbutton.setChecked(False) # update the text if we can if not self.choice.setting.readonly: self.choice.setEditText(text) self.choice.setFocus() self.parentWidget().setFocus() self.choice.setFocus() class FillStyle(Choice): """For choosing between fill styles.""" _icons = None _fills = None _fillcnvt = None def __init__(self, setting, parent): if self._icons is None: self._generateIcons() Choice.__init__(self, setting, False, self._fills, parent, icons=self._icons) @classmethod def _generateIcons(cls): """Generate a list of pixmaps for drop down menu.""" size = 12 icons = [] c = qt4.QColor('grey') for f in cls._fills: pix = qt4.QPixmap(size, size) pix.fill() painter = qt4.QPainter(pix) painter.setRenderHint(qt4.QPainter.Antialiasing) brush = qt4.QBrush(c, cls._fillcnvt[f]) painter.fillRect(0, 0, size, size, brush) painter.end() icons.append( qt4.QIcon(pix) ) cls._icons = icons class Marker(Choice): """A control to let the user choose a marker.""" _icons = None def __init__(self, setting, parent): if self._icons is None: self._generateIcons() Choice.__init__(self, setting, False, utils.MarkerCodes, parent, icons=self._icons) @classmethod def _generateIcons(cls): size = 16 icons = [] brush = qt4.QBrush( qt4.QColor('darkgrey') ) pen = qt4.QPen( qt4.QBrush(qt4.Qt.black), 1. ) for marker in utils.MarkerCodes: pix = qt4.QPixmap(size, size) pix.fill() painter = qt4.QPainter(pix) painter.setRenderHint(qt4.QPainter.Antialiasing) painter.setBrush(brush) painter.setPen(pen) utils.plotMarker(painter, size*0.5, size*0.5, marker, size*0.33) painter.end() icons.append( qt4.QIcon(pix) ) cls._icons = icons class Arrow(Choice): """A control to let the user choose an arrowhead.""" _icons = None def __init__(self, setting, parent): if self._icons is None: self._generateIcons() Choice.__init__(self, setting, False, utils.ArrowCodes, parent, icons=self._icons) @classmethod def _generateIcons(cls): size = 16 icons = [] brush = qt4.QBrush(qt4.Qt.black) pen = qt4.QPen( qt4.QBrush(qt4.Qt.black), 1. ) for arrow in utils.ArrowCodes: pix = qt4.QPixmap(size, size) pix.fill() painter = qt4.QPainter(pix) painter.setRenderHint(qt4.QPainter.Antialiasing) painter.setBrush(brush) painter.setPen(pen) utils.plotLineArrow(painter, size*0.4, size*0.5, size*2, 0., arrowsize=size*0.2, arrowleft=arrow, arrowright=arrow) painter.end() icons.append( qt4.QIcon(pix) ) cls._icons = icons class LineStyle(Choice): """For choosing between line styles.""" _icons = None _lines = None _linecnvt = None size = (24, 8) def __init__(self, setting, parent): if self._icons is None: self._generateIcons() Choice.__init__(self, setting, False, self._lines, parent, icons=self._icons) self.setIconSize( qt4.QSize(*self.size) ) @classmethod def _generateIcons(cls): """Generate a list of icons for drop down menu.""" # import later for dependency issues import veusz.setting.collections import veusz.document icons = [] size = cls.size setn = veusz.setting.collections.Line('temp') setn.get('color').set('black') setn.get('width').set('1pt') for lstyle in cls._lines: pix = qt4.QPixmap(*size) pix.fill() ph = veusz.document.PaintHelper( (1, 1) ) painter = qt4.QPainter(pix) painter.setRenderHint(qt4.QPainter.Antialiasing) setn.get('style').set(lstyle) painter.setPen( setn.makeQPen(ph) ) painter.drawLine( int(size[0]*0.1), size[1]/2, int(size[0]*0.9), size[1]/2 ) painter.end() icons.append( qt4.QIcon(pix) ) cls._icons = icons class Color(qt4.QWidget): """A control which lets the user choose a color. A drop down list and a button to bring up a dialog are used """ _icons = None _colors = None _qobj = None def __init__(self, setting, parent): qt4.QWidget.__init__(self, parent) if self._icons is None: self._generateIcons() self.setting = setting # combo box c = self.combo = qt4.QComboBox() c.setEditable(True) for color in self._colors: c.addItem(self._icons[color], color) self.connect(c, qt4.SIGNAL('activated(const QString&)'), self.slotActivated ) # add color if a color is added by a different combo box self.connect(Color._qobj, qt4.SIGNAL('addcolor'), self.addcolorSlot) # button for selecting colors b = self.button = qt4.QPushButton() b.setFlat(True) b.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum) b.setMaximumHeight(24) b.setMaximumWidth(24) self.connect(b, qt4.SIGNAL('clicked()'), self.slotButtonClicked) if setting.readonly: c.setEnabled(False) b.setEnabled(False) layout = qt4.QHBoxLayout() layout.setSpacing(0) layout.setMargin(0) layout.addWidget(c) layout.addWidget(b) self.setColor( setting.toText() ) self.setLayout(layout) self.setting.setOnModified(self.onModified) def addcolorSlot(self, color): """When another Color combo adds a color, add one to this one""" self.combo.addItem(self._icons[color], color) @classmethod def _generateIcons(cls): """Generate a list of icons for drop down menu. Does not generate existing icons """ size = 12 if cls._icons is None: cls._icons = {} icons = cls._icons for c in cls._colors: if c not in icons: pix = qt4.QPixmap(size, size) pix.fill( qt4.QColor(c) ) icons[c] = qt4.QIcon(pix) if cls._qobj is not None: # tell other combo boxes a color has been added cls._qobj.emit(qt4.SIGNAL('addcolor'), c) if cls._qobj is None: cls._qobj = qt4.QObject() def slotButtonClicked(self): """Open dialog to edit color.""" col = qt4.QColorDialog.getColor(self.setting.color(), self) if col.isValid(): # change setting val = unicode( col.name() ) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) def slotActivated(self, val): """A different value is selected.""" text = unicode(self.combo.currentText()) val = self.setting.fromText(text) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) def setColor(self, color): """Update control with color given.""" # construct color icon if not there if color not in Color._icons: Color._colors.append(color) Color._generateIcons() # add text to combo if not there index = self.combo.findText(color) # set correct index in combobox self.combo.setCurrentIndex(index) self.button.setIcon( self.combo.itemIcon(index) ) def onModified(self, mod): """called when the setting is changed remotely""" self.setColor( self.setting.toText() ) class WidgetSelector(Choice): """For choosing from a list of widgets.""" def __init__(self, setting, document, parent): """Initialise and populate combobox.""" Choice.__init__(self, setting, True, [], parent) self.document = document self.connect(document, qt4.SIGNAL('sigModified'), self.slotModified) def _populateEntries(self): pass def slotModified(self, modified): """Update list of axes.""" self._populateEntries() class WidgetChoice(WidgetSelector): """Choose a widget.""" def __init__(self, setting, document, parent): """Initialise and populate combobox.""" WidgetSelector.__init__(self, setting, document, parent) self._populateEntries() def _populateEntries(self): """Build up a list of widgets for combobox.""" widgets = self.setting.getWidgetList() # we only need the list of names names = widgets.keys() names.sort() utils.populateCombo(self, names) class Axis(WidgetSelector): """Choose an axis to plot against.""" def __init__(self, setting, document, direction, parent): """Initialise and populate combobox.""" WidgetSelector.__init__(self, setting, document, parent) self.direction = direction self._populateEntries() def _populateEntries(self): """Build up a list of possible axes.""" # get parent widget widget = self.setting.parent while not widget.isWidget() and widget is not None: widget = widget.parent # get list of axis widgets up the tree axes = {} while widget is not None: for w in widget.children: try: # succeeds if axis if w.settings.direction == self.direction: axes[w.name] = True except AttributeError: pass widget = widget.parent names = axes.keys() names.sort() utils.populateCombo(self, names) class ListSet(qt4.QFrame): """A widget for constructing settings which are lists of other properties. This code is pretty nasty and horrible, so we abstract it in this base widget """ pixsize = 12 def __init__(self, defaultval, setting, parent): """Initialise this base widget. defaultval is the default entry to add if add is clicked with no current entries setting is the setting this widget corresponds to parent is the parent widget. """ qt4.QFrame.__init__(self, parent) self.setFrameStyle(qt4.QFrame.Box) self.defaultval = defaultval self.setting = setting self.controls = [] self.layout = qt4.QGridLayout(self) self.layout.setMargin( self.layout.margin()/2 ) self.layout.setSpacing( self.layout.spacing()/4 ) # ignore changes if this set self.ignorechange = False self.populate() self.setting.setOnModified(self.onModified) def populateRow(self, row, val): """Populate the row in the control. Returns a list of the widgets created. """ return None def populate(self): """Construct the list of controls.""" # delete all children in case of refresh self.controls = [] for c in self.children(): if isinstance(c, qt4.QWidget): self.layout.removeWidget(c) c.deleteLater() c = None # iterate over each row row = -1 for row, val in enumerate(self.setting.val): cntrls = self.populateRow(row, val) for col in xrange(len(cntrls)): self.layout.addWidget(cntrls[col], row, col) for c in cntrls: c.show() self.controls.append(cntrls) # buttons at end bbox = qt4.QWidget() h = qt4.QHBoxLayout(bbox) h.setMargin(0) bbox.setLayout(h) self.layout.addWidget(bbox, row+1, 0, 1, -1) # a button to add a new entry b = qt4.QPushButton('Add') h.addWidget(b) self.connect(b, qt4.SIGNAL('clicked()'), self.onAddClicked) b.show() # a button to delete the last entry b = qt4.QPushButton('Delete') h.addWidget(b) self.connect(b, qt4.SIGNAL('clicked()'), self.onDeleteClicked) b.setEnabled( len(self.setting.val) > 0 ) b.show() def polish(self): """Remove tooltip from widget - avoid Qt bugs.""" qt4.QVBox.polish(self) qt4.QToolTip.remove(self) def onAddClicked(self): """Add a line style to the list given.""" rows = list(self.setting.val) if len(rows) != 0: rows.append(rows[-1]) else: rows.append(self.defaultval) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, rows ) def onDeleteClicked(self): """Remove final entry in settings list.""" rows = list(self.setting.val)[:-1] self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, rows ) def onModified(self, mod): """called when the setting is changed remotely""" if not self.ignorechange: self.populate() else: self.ignorechange = False def identifyPosn(self, widget): """Identify the position this widget is in. Returns (row, col) or (None, None) if not found. """ for row, cntrls in enumerate(self.controls): for col, cntrl in enumerate(cntrls): if cntrl == widget: return (row, col) return (None, None) def addColorButton(self, row, col, tooltip): """Add a color button to the list at the position specified.""" color = self.setting.val[row][col] wcolor = qt4.QPushButton() wcolor.setFlat(True) wcolor.setSizePolicy(qt4.QSizePolicy.Maximum, qt4.QSizePolicy.Maximum) wcolor.setMaximumHeight(24) wcolor.setMaximumWidth(24) pix = qt4.QPixmap(self.pixsize, self.pixsize) pix.fill( utils.extendedColorToQColor(color) ) wcolor.setIcon( qt4.QIcon(pix) ) wcolor.setToolTip(tooltip) self.connect(wcolor, qt4.SIGNAL('clicked()'), self.onColorClicked) return wcolor def addToggleButton(self, row, col, tooltip): """Make a toggle button.""" toggle = self.setting.val[row][col] wtoggle = qt4.QCheckBox() wtoggle.setChecked(toggle) wtoggle.setToolTip(tooltip) self.connect(wtoggle, qt4.SIGNAL('toggled(bool)'), self.onToggled) return wtoggle def addCombo(self, row, col, tooltip, values, icons, texts): """Make an enumeration combo - choose from a set of icons.""" val = self.setting.val[row][col] wcombo = qt4.QComboBox() if texts is None: for icon in icons: wcombo.addItem(icon, "") else: for text, icon in izip(texts, icons): wcombo.addItem(icon, text) wcombo.setCurrentIndex(values.index(val)) wcombo.setToolTip(tooltip) self.connect(wcombo, qt4.SIGNAL('activated(int)'), self.onComboChanged) wcombo._vz_values = values return wcombo def _updateRowCol(self, row, col, val): """Update value on row and column.""" rows = list(self.setting.val) items = list(rows[row]) items[col] = val rows[row] = tuple(items) self.ignorechange = True self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, rows ) def onToggled(self, on): """Checkbox toggled.""" row, col = self.identifyPosn(self.sender()) self._updateRowCol(row, col, on) def onComboChanged(self, val): """Update the setting if the combo changes.""" sender = self.sender() row, col = self.identifyPosn(sender) self._updateRowCol(row, col, sender._vz_values[val]) def onColorClicked(self): """Color button clicked for line.""" sender = self.sender() row, col = self.identifyPosn(sender) rows = self.setting.val color = qt4.QColorDialog.getColor( utils.extendedColorToQColor(rows[row][col]), self, "Choose color", qt4.QColorDialog.ShowAlphaChannel ) if color.isValid(): # change setting # this is a bit irritating, as have to do lots of # tedious conversions color = utils.extendedColorFromQColor(color) self._updateRowCol(row, col, color) # change the color pix = qt4.QPixmap(self.pixsize, self.pixsize) pix.fill( utils.extendedColorToQColor(color) ) sender.setIcon( qt4.QIcon(pix) ) class LineSet(ListSet): """A list of line styles. """ def __init__(self, setting, parent): ListSet.__init__(self, ('solid', '1pt', 'black', False), setting, parent) def populateRow(self, row, val): """Add the widgets for the row given.""" # create line icons if not already created if LineStyle._icons is None: LineStyle._generateIcons() # make line style selector wlinestyle = self.addCombo(row, 0, 'Line style', LineStyle._lines, LineStyle._icons, None) # make line width edit box wwidth = qt4.QLineEdit() wwidth.setText(self.setting.val[row][1]) wwidth.setToolTip('Line width') self.connect(wwidth, qt4.SIGNAL('editingFinished()'), self.onWidthChanged) # make color selector button wcolor = self.addColorButton(row, 2, 'Line color') # make hide checkbox whide = self.addToggleButton(row, 3, 'Hide line') # return created controls return [wlinestyle, wwidth, wcolor, whide] def onWidthChanged(self): """Width has changed - validate.""" sender = self.sender() row, col = self.identifyPosn(sender) text = unicode(sender.text()) if setting.Distance.isDist(text): # valid distance styleClear(sender) self._updateRowCol(row, col, text) else: # invalid distance styleError(sender) class _FillBox(qt4.QScrollArea): """Pop up box for extended fill settings.""" def __init__(self, doc, thesetting, row, button, parent): """Initialse widget. This is based on a PropertyList widget. FIXME: we have to import at runtime, so we should improve the inheritance here. Is using PropertyList window a hack? """ qt4.QScrollArea.__init__(self, parent) self.setWindowFlags(qt4.Qt.Popup) self.setAttribute(qt4.Qt.WA_DeleteOnClose) self.parent = parent self.row = row self.setting = thesetting self.extbrush = thesetting.returnBrushExtended(row) from veusz.windows.treeeditwindow import SettingsProxySingle, \ PropertyList fbox = self class DirectSetProxy(SettingsProxySingle): """Class to intercept changes of settings from UI.""" def onSettingChanged(self, control, setting, val): # set value in setting setting.val = val # tell box to update setting fbox.onSettingChanged() # actual widget for changing the fill plist = PropertyList(doc) plist.updateProperties( DirectSetProxy(doc, self.extbrush) ) self.setWidget(plist) utils.positionFloatingPopup(self, button) self.installEventFilter(self) def onSettingChanged(self): """Called when user changes a fill property.""" # get value of brush and get data for row e = self.extbrush rowdata = [e.style, e.color, e.hide] if e.style != 'solid': rowdata += [ e.transparency, e.linewidth, e.linestyle, e.patternspacing, e.backcolor, e.backtransparency, e.backhide ] rowdata = tuple(rowdata) if self.setting.val[self.row] != rowdata: # if row different, send update signal val = list(self.setting.val) val[self.row] = rowdata self.emit( qt4.SIGNAL("settingChanged"), self, self.setting, val ) def eventFilter(self, obj, event): """Grab clicks outside this window to close it.""" if ( isinstance(event, qt4.QMouseEvent) and event.buttons() != qt4.Qt.NoButton ): frame = qt4.QRect(0, 0, self.width(), self.height()) if not frame.contains(event.pos()): self.close() return True return qt4.QScrollArea.eventFilter(self, obj, event) def keyPressEvent(self, event): """Close if escape or return is pressed.""" qt4.QScrollArea.keyPressEvent(self, event) key = event.key() if key == qt4.Qt.Key_Escape: self.close() def closeEvent(self, event): """Tell the calling widget that we are closing, and provide the new text.""" self.emit(qt4.SIGNAL("closing"), self.row) qt4.QScrollArea.closeEvent(self, event) class FillSet(ListSet): """A list of fill settings.""" def __init__(self, setting, parent): ListSet.__init__(self, ('solid', 'black', False), setting, parent) def populateRow(self, row, val): """Add the widgets for the row given.""" # construct fill icons if not already done if FillStyle._icons is None: FillStyle._generateIcons() # make fill style selector wfillstyle = self.addCombo(row, 0, "Fill style", FillStyle._fills, FillStyle._icons, FillStyle._fills) wfillstyle.setMinimumWidth(self.pixsize) # make color selector button wcolor = self.addColorButton(row, 1, "Fill color") # make hide checkbox whide = self.addToggleButton(row, 2, "Hide fill") # extended options wmore = DotDotButton(tooltip="More options") self.connect(wmore, qt4.SIGNAL("toggled(bool)"), lambda on, row=row: self.editMore(on, row)) # return widgets return [wfillstyle, wcolor, whide, wmore] def buttonAtRow(self, row): """Get .. button on row.""" return self.layout.itemAtPosition(row, 3).widget() def editMore(self, on, row): if on: fb = _FillBox(self.setting.getDocument(), self.setting, row, self.buttonAtRow(row), self.parent()) self.connect(fb, qt4.SIGNAL("closing"), self.boxClosing) self.connect(fb, qt4.SIGNAL("settingChanged"), self, qt4.SIGNAL("settingChanged")) fb.show() def boxClosing(self, row): """Called when the popup edit box closes.""" # uncheck the .. button self.buttonAtRow(row).setChecked(False) class MultiSettingWidget(qt4.QWidget): """A widget for storing multiple values in a tuple, with + and - signs by each entry.""" def __init__(self, setting, doc, *args): """Construct widget as combination of LineEdit and PushButton for browsing.""" qt4.QWidget.__init__(self, *args) self.setting = setting self.document = doc self.grid = layout = qt4.QGridLayout() layout.setHorizontalSpacing(0) layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.last = () self.controls = [] self.setting.setOnModified(self.onModified) def makeRow(self): """Make new row at end""" row = len(self.controls) cntrl = self.makeControl(row) cntrl.installEventFilter(self) addbutton = qt4.QPushButton('+') addbutton.setFixedWidth(24) addbutton.setFlat(True) addbutton.setToolTip('Add another item') subbutton = qt4.QPushButton('-') subbutton.setToolTip('Remove item') subbutton.setFixedWidth(24) subbutton.setFlat(True) self.controls.append((cntrl, addbutton, subbutton)) self.grid.addWidget(cntrl, row, 0) self.grid.addWidget(addbutton, row, 1) self.grid.addWidget(subbutton, row, 2) self.connect(addbutton, qt4.SIGNAL('clicked()'), lambda: self.addPressed(row)) self.connect(subbutton, qt4.SIGNAL('clicked()'), lambda: self.subPressed(row)) if len(self.controls) == 2: # enable first subtraction button self.controls[0][2].setEnabled(True) elif len(self.controls) == 1: # or disable self.controls[0][2].setEnabled(False) def eventFilter(self, obj, event): """Capture loss of focus by controls.""" if event.type() == qt4.QEvent.FocusOut: for row, c in enumerate(self.controls): if c[0] is obj: self.dataChanged(row) break return qt4.QWidget.eventFilter(self, obj, event) def deleteRow(self): """Remove last row""" for w in self.controls[-1]: self.grid.removeWidget(w) w.deleteLater() self.controls.pop(-1) # disable first subtraction button if len(self.controls) == 1: self.controls[0][2].setEnabled(False) def addPressed(self, row): """User adds a new row.""" val = list(self.setting.val) val.insert(row+1, '') self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, tuple(val) ) def subPressed(self, row): """User deletes a row.""" val = list(self.setting.val) val.pop(row) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, tuple(val) ) def onModified(self, mod): """Called when the setting is changed remotely, or when control is opened""" s = self.setting if self.last == s.val: return self.last = s.val # update number of rows while len(self.setting.val) > len(self.controls): self.makeRow() while len(self.setting.val) < len(self.controls): self.deleteRow() # update values self.updateControls() def makeControl(self, row): """Override this to make an editing widget.""" return None def updateControls(): """Override this to update values in controls.""" pass def readControl(self, cntrl): """Read value from control.""" return None def dataChanged(self, row): """Update row of setitng with new data""" val = list(self.setting.val) val[row] = self.readControl( self.controls[row][0] ) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, tuple(val) ) class Datasets(MultiSettingWidget): """A control for editing a list of datasets.""" def __init__(self, setting, doc, dimensions, datatype, *args): """Contruct set of comboboxes""" MultiSettingWidget.__init__(self, setting, doc, *args) self.dimensions = dimensions self.datatype = datatype self.lastdatasets = [] # force updating to initialise self.onModified(True) def makeControl(self, row): """Make QComboBox edit widget.""" combo = qt4.QComboBox() combo.setEditable(True) self.connect(combo.lineEdit(), qt4.SIGNAL('editingFinished()'), lambda: self.dataChanged(row)) # if a different item is selected self.connect(combo, qt4.SIGNAL('activated(const QString&)'), lambda x: self.dataChanged(row)) utils.populateCombo(combo, self.getDatasets()) return combo def readControl(self, control): """Get text for control.""" return unicode( control.lineEdit().text() ) def getDatasets(self): """Get applicable datasets (sorted).""" datasets = [] for name, ds in self.document.data.iteritems(): if (ds.dimensions == self.dimensions and ds.datatype == self.datatype): datasets.append(name) datasets.sort() return datasets def updateControls(self): """Set values of controls.""" for cntrls, val in izip(self.controls, self.setting.val): cntrls[0].lineEdit().setText(val) def onModified(self, mod): """Called when the setting is changed remotely, or when control is opened""" MultiSettingWidget.onModified(self, mod) s = self.setting datasets = self.getDatasets() if self.lastdatasets == datasets: return self.lastdatasets = datasets # update list of datasets for cntrls in self.controls: utils.populateCombo(cntrls[0], datasets) class Strings(MultiSettingWidget): """A list of strings.""" def __init__(self, setting, doc, *args): """Construct widget as combination of LineEdit and PushButton for browsing.""" MultiSettingWidget.__init__(self, setting, doc, *args) self.onModified(True) def makeControl(self, row): """Make edit widget.""" lineedit = qt4.QLineEdit() self.connect(lineedit, qt4.SIGNAL('editingFinished()'), lambda: self.dataChanged(row)) return lineedit def readControl(self, control): """Get text for control.""" return unicode( control.text() ) def updateControls(self): """Set values of controls.""" for cntrls, val in izip(self.controls, self.setting.val): cntrls[0].setText(val) class Filename(qt4.QWidget): """A widget for selecting a filename with a browse button.""" def __init__(self, setting, mode, parent): """Construct widget as combination of LineEdit and PushButton for browsing. mode is 'image' or 'file' """ qt4.QWidget.__init__(self, parent) self.mode = mode self.setting = setting layout = qt4.QHBoxLayout() layout.setSpacing(0) layout.setMargin(0) self.setLayout(layout) # the actual edit control self.edit = qt4.QLineEdit() self.edit.setText( setting.toText() ) layout.addWidget(self.edit) b = self.button = DotDotButton(checkable=False, tooltip="Browse for file") layout.addWidget(b) # connect up signals self.connect(self.edit, qt4.SIGNAL('editingFinished()'), self.validateAndSet) self.connect(b, qt4.SIGNAL('clicked()'), self.buttonClicked) # add completion if we have support (qt >= 4.3) if hasattr(qt4, 'QDirModel'): c = self.filenamecompleter = qt4.QCompleter(self) model = qt4.QDirModel(c) c.setModel(model) self.edit.setCompleter(c) # for read only filenames if setting.readonly: self.edit.setReadOnly(True) self.setting.setOnModified(self.onModified) def buttonClicked(self): """Button clicked - show file open dialog.""" title = 'Choose file' filefilter = "All files (*)" if self.mode == 'image': title = 'Choose image' filefilter = ("Images (*.png *.jpg *.jpeg *.bmp *.svg *.tiff *.tif " "*.gif *.xbm *.xpm);;" + filefilter) filename = qt4.QFileDialog.getOpenFileName( self, title, self.edit.text(), filefilter) if filename: val = unicode(filename) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) def validateAndSet(self): """Check the text is a valid setting and update it.""" text = unicode(self.edit.text()) try: val = self.setting.fromText(text) styleClear(self.edit) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, val ) except setting.InvalidType: styleError(self.edit) def onModified(self, mod): """called when the setting is changed remotely""" self.edit.setText( self.setting.toText() ) class FontFamily(qt4.QFontComboBox): """List the font families, showing each font.""" def __init__(self, setting, parent): """Create the combobox.""" qt4.QFontComboBox.__init__(self, parent) self.setting = setting self.setFontFilters( qt4.QFontComboBox.ScalableFonts ) # set initial value self.onModified(True) # stops combobox readjusting in size to fit contents self.setSizeAdjustPolicy( qt4.QComboBox.AdjustToMinimumContentsLengthWithIcon) self.setting.setOnModified(self.onModified) # if a different item is selected self.connect( self, qt4.SIGNAL('activated(const QString&)'), self.slotActivated ) def focusOutEvent(self, *args): """Allows us to check the contents of the widget.""" qt4.QFontComboBox.focusOutEvent(self, *args) self.slotActivated('') def slotActivated(self, val): """Update setting if a different item is chosen.""" newval = unicode(self.currentText()) self.emit( qt4.SIGNAL('settingChanged'), self, self.setting, newval ) def onModified(self, mod): """Make control reflect chosen setting.""" self.setCurrentFont( qt4.QFont(self.setting.toText()) ) class ErrorStyle(Choice): """Choose different error bar styles.""" _icons = None # generated icons _errorstyles = None # copied in by setting.py def __init__(self, setting, parent): if self._icons is None: self._generateIcons() Choice.__init__(self, setting, False, self._errorstyles, parent, icons=self._icons) def _generateIcons(cls): """Generate a list of pixmaps for drop down menu.""" cls._icons = [] for errstyle in cls._errorstyles: cls._icons.append( utils.getIcon('error_%s' % errstyle) ) class Colormap(Choice): """Give the user a preview of colormaps. Based on Choice to make life easier """ _icons = {} size = (32, 12) def __init__(self, setn, document, parent): names = sorted(document.colormaps.keys()) icons = Colormap._generateIcons(document, names) setting.controls.Choice.__init__(self, setn, True, names, parent, icons=icons) self.setIconSize( qt4.QSize(*self.size) ) @classmethod def _generateIcons(kls, document, names): """Generate a list of icons for drop down menu.""" # create a fake dataset smoothly varying from 0 to size[0]-1 size = kls.size fakedataset = N.fromfunction(lambda x, y: y, (size[1], size[0])) # keep track of icons to return retn = [] # iterate over colour maps for name in names: val = document.colormaps.get(name, None) if val in kls._icons: icon = kls._icons[val] else: if val is None: # empty icon pixmap = qt4.QPixmap(*size) pixmap.fill(qt4.Qt.transparent) else: # generate icon image = utils.applyColorMap(val, 'linear', fakedataset, 0., size[0]-1., 0) pixmap = qt4.QPixmap.fromImage(image) icon = qt4.QIcon(pixmap) kls._icons[val] = icon retn.append(icon) return retn class AxisBound(Choice): """Control for setting bounds of axis. This is to allow dates etc """ def __init__(self, setting, *args): Choice.__init__(self, setting, True, ['Auto'], *args) modesetn = setting.parent.get('mode') modesetn.setOnModified(self.modeChange) def modeChange(self, changed): """Called if the mode of the axis changes. Re-set text as float or date.""" if unicode(self.currentText()).lower() != 'auto': self.setEditText( self.setting.toText() ) veusz-1.15/setting/__init__.py0000644002344000001440000000205411734662204016366 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### from settingdb import * from reference import Reference from setting import * from settings import * from collections import * from stylesheet import * veusz-1.15/setting/collections.py0000644002344000001440000003101111734662204017140 0ustar jssusers00000000000000# Copyright (C) 2005 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Collections of predefined settings for common settings.""" import veusz.qtall as qt4 import setting from settings import Settings class Line(Settings): '''For holding properities of a line.''' def __init__(self, name, **args): Settings.__init__(self, name, **args) self.add( setting.Color('color', setting.Reference('/StyleSheet/Line/color'), descr = 'Color of line', usertext='Color') ) self.add( setting.DistancePt('width', setting.Reference('/StyleSheet/Line/width'), descr = 'Width of line', usertext='Width') ) self.add( setting.LineStyle('style', 'solid', descr = 'Line style', usertext='Style') ) self.add( setting.Int( 'transparency', 0, descr = 'Transparency percentage', usertext = 'Transparency', minval = 0, maxval = 100 ) ) self.add( setting.Bool('hide', False, descr = 'Hide the line', usertext='Hide') ) def makeQPen(self, painthelper): '''Make a QPen from the description. This currently ignores the hide attribute ''' color = qt4.QColor(self.color) color.setAlphaF( (100-self.transparency) / 100.) width = self.get('width').convert(painthelper) style, dashpattern = setting.LineStyle._linecnvt[self.style] pen = qt4.QPen( color, width, style ) if dashpattern: pen.setDashPattern(dashpattern) return pen def makeQPenWHide(self, painthelper): """Make a pen, taking account of hide attribute.""" if self.hide: return qt4.QPen(qt4.Qt.NoPen) else: return self.makeQPen(painthelper) class XYPlotLine(Line): '''A plot line for plotting data, allowing histogram-steps to be plotted.''' def __init__(self, name, **args): Line.__init__(self, name, **args) self.add( setting.Choice('steps', ['off', 'left', 'centre', 'right'], 'off', descr='Plot horizontal steps ' 'instead of a line', usertext='Steps'), 0 ) self.add( setting.Bool('bezierJoin', False, descr='Connect points with a cubic Bezier curve', usertext='Bezier join'), 1 ) class ErrorBarLine(Line): '''A line style for error bar plotting.''' def __init__(self, name, **args): Line.__init__(self, name, **args) self.add( setting.Float('endsize', 1.0, minval = 0., descr='Scale ends of error bars by this factor', usertext = 'End size') ) self.add( setting.Bool('hideHorz', False, descr = 'Hide horizontal errors', usertext='Hide horz.') ) self.add( setting.Bool('hideVert', False, descr = 'Hide vertical errors', usertext='Hide vert.') ) class Brush(Settings): '''Settings of a fill.''' def __init__(self, name, **args): Settings.__init__(self, name, **args) self.add( setting.Color( 'color', 'black', descr = 'Fill colour', usertext='Color') ) self.add( setting.FillStyle( 'style', 'solid', descr = 'Fill style', usertext='Style') ) self.add( setting.Int( 'transparency', 0, descr = 'Transparency percentage', usertext = 'Transparency', minval = 0, maxval = 100 ) ) self.add( setting.Bool( 'hide', False, descr = 'Hide the fill', usertext='Hide') ) def makeQBrush(self): '''Make a qbrush from the settings.''' color = qt4.QColor(self.color) color.setAlphaF( (100-self.transparency) / 100.) return qt4.QBrush( color, self.get('style').qtStyle() ) def makeQBrushWHide(self): """Make a brush, taking account of hide attribute.""" if self.hide: return qt4.QBrush() else: return self.makeQBrush() class BrushExtended(Settings): '''Extended brush style.''' def __init__(self, name, **args): Settings.__init__(self, name, **args) self.add( setting.Color( 'color', 'black', descr = 'Fill colour', usertext='Color') ) self.add( setting.FillStyleExtended( 'style', 'solid', descr = 'Fill style', usertext='Style') ) self.add( setting.Bool( 'hide', False, descr = 'Hide the fill', usertext='Hide') ) self.add( setting.Int( 'transparency', 0, descr = 'Transparency percentage', usertext = 'Transparency', minval = 0, maxval = 100 ) ) self.add( setting.DistancePt( 'linewidth', '0.5pt', descr = 'Width of hatch or pattern line', usertext='Line width') ) self.add( setting.LineStyle( 'linestyle', 'solid', descr = 'Hatch or pattern line style', usertext='Line style') ) self.add( setting.DistancePt( 'patternspacing', '5pt', descr = 'Hatch or pattern spacing', usertext = 'Spacing') ) self.add( setting.Color( 'backcolor', 'white', descr = 'Hatch or pattern background color', usertext = 'Back color' ) ) self.add( setting.Int( 'backtransparency', 0, descr = 'Hatch or pattern background transparency percentage', usertext = 'Back trans.', minval = 0, maxval = 100 ) ) self.add( setting.Bool( 'backhide', True, descr = 'Hide hatch or pattern background', usertext='Back hide') ) class KeyBrush(BrushExtended): '''Fill used for back of key.''' def __init__(self, name, **args): BrushExtended.__init__(self, name, **args) self.get('color').newDefault('white') class BoxPlotMarkerFillBrush(Brush): '''Fill used for points on box plots.''' def __init__(self, name, **args): Brush.__init__(self, name, **args) self.get('color').newDefault('white') class GraphBrush(BrushExtended): '''Fill used for back of graph.''' def __init__(self, name, **args): BrushExtended.__init__(self, name, **args) self.get('color').newDefault('white') class PlotterFill(BrushExtended): '''Filling used for filling on plotters.''' def __init__(self, name, **args): BrushExtended.__init__(self, name, **args) self.get('hide').newDefault(True) class PointFill(BrushExtended): '''Filling used for filling above/below line or inside error region for xy-point plotters. ''' def __init__(self, name, **args): BrushExtended.__init__(self, name, **args) hide = self.get('hide') hide.newDefault(True) hide.usertext = 'Hide edge fill' hide.descr = 'Hide the filled region to the edge of the plot' self.get('color').newDefault('grey') self.add( setting.Bool( 'hideerror', False, descr = 'Hide the filled region inside the error bars', usertext='Hide error fill') ) class ShapeFill(BrushExtended): '''Filling used for filling shapes.''' def __init__(self, name, **args): BrushExtended.__init__(self, name, **args) self.get('hide').newDefault(True) self.get('color').newDefault('white') class ArrowFill(Brush): """Brush for filling arrow heads""" def __init__(self, name, **args): Brush.__init__(self, name, **args) self.get('color').newDefault( setting.Reference( '../Line/color') ) class Text(Settings): '''Text settings.''' # need to examine font table to see what's available # this is set on app startup defaultfamily = None families = None def __init__(self, name, **args): Settings.__init__(self, name, **args) self.add( setting.FontFamily('font', setting.Reference('/StyleSheet/Font/font'), descr = 'Font name', usertext='Font') ) self.add( setting.DistancePt('size', setting.Reference('/StyleSheet/Font/size'), descr = 'Font size', usertext='Size' ) ) self.add( setting.Color( 'color', setting.Reference('/StyleSheet/Font/color'), descr = 'Font color', usertext='Color' ) ) self.add( setting.Bool( 'italic', False, descr = 'Italic font', usertext='Italic' ) ) self.add( setting.Bool( 'bold', False, descr = 'Bold font', usertext='Bold' ) ) self.add( setting.Bool( 'underline', False, descr = 'Underline font', usertext='Underline' ) ) self.add( setting.Bool( 'hide', False, descr = 'Hide the text', usertext='Hide') ) def copy(self): """Make copy of settings.""" c = Settings.copy(self) c.defaultfamily = self.defaultfamily c.families = self.families return c def makeQFont(self, painthelper): '''Return a qt4.QFont object corresponding to the settings.''' size = self.get('size').convertPts(painthelper) weight = qt4.QFont.Normal if self.bold: weight = qt4.QFont.Bold f = qt4.QFont(self.font, size, weight, self.italic) if self.underline: f.setUnderline(True) f.setStyleHint(qt4.QFont.Times) return f def makeQPen(self): """ Return a qt4.QPen object for the font pen """ return qt4.QPen(qt4.QColor(self.color)) class PointLabel(Text): """For labelling points on plots.""" def __init__(self, name, **args): Text.__init__(self, name, **args) self.add( setting.Float('angle', 0., descr='Angle of the labels in degrees', usertext='Angle', formatting=True), 0 ) self.add( setting.AlignVert('posnVert', 'centre', descr='Vertical position of label', usertext='Vert position', formatting=True), 0 ) self.add( setting.AlignHorz('posnHorz', 'right', descr="Horizontal position of label", usertext='Horz position', formatting=True), 0 ) veusz-1.15/setting/stylesheet.py0000644002344000001440000001066611734662204017030 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import sys from settings import Settings import setting import collections import veusz.qtall as qt4 class StyleSheet(Settings): """A class for handling default values of settings. Settings are registered to be added to the stylesheet.""" registeredsettings = [] @classmethod def register(kls, settingskls, posn=None): """Register a settings object with the stylesheet. This settings object is copied for each new document. """ if posn is None: kls.registeredsettings.append(settingskls) else: kls.registeredsettings.insert(posn, settingskls) def __init__(self, **args): """Create the default settings.""" Settings.__init__(self, 'StyleSheet', setnsmode='stylesheet', **args) self.pixmap = 'settings_stylesheet' for subset in self.registeredsettings: self.add( subset() ) class StylesheetLine(Settings): """Hold the properties of the default line.""" def __init__(self): Settings.__init__(self, 'Line', pixmap='settings_plotline', descr='Default line style for document', usertext='Line') self.add( setting.DistancePt('width', '0.5pt', descr='Default line width', usertext='Width', formatting=True) ) self.add( setting.Color('color', 'black', descr='Default line color', usertext='Color', formatting=True) ) # register these properties with the stylesheet StyleSheet.register(StylesheetLine) def _registerFontStyleSheet(): """Get fonts, and register default with StyleSheet and Text class.""" families = [ unicode(name) for name in qt4.QFontDatabase().families() ] deffont = None for f in ('Times New Roman', 'Bitstream Vera Serif', 'Times', 'Utopia', 'Serif'): if f in families: deffont = unicode(f) break if deffont is None: print >>sys.stderr, "Warning: did not find a sensible default font. Choosing first font." deffont = unicode(families[0]) collections.Text.defaultfamily = deffont collections.Text.families = families StylesheetText.defaultfamily = deffont StylesheetText.families = families class StylesheetText(Settings): """Hold properties of default text font.""" defaultfamily = None families = None def __init__(self): """Initialise with default font family and list of families.""" Settings.__init__(self, 'Font', pixmap='settings_axislabel', descr='Default font for document', usertext='Font') if StylesheetText.defaultfamily is None: _registerFontStyleSheet() self.add( setting.FontFamily('font', StylesheetText.defaultfamily, descr='Font name', usertext='Font', formatting=True)) self.add( setting.DistancePt('size', '14pt', descr='Default font size', usertext='Size', formatting=True)) self.add( setting.Color('color', 'black', descr='Default font color', usertext='Color', formatting=True)) StyleSheet.register(StylesheetText) veusz-1.15/qtwidgets/0000755002344000001440000000000011734662466014624 5ustar jssusers00000000000000veusz-1.15/qtwidgets/historyvaluecombo.py0000644002344000001440000000613411734662204020746 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A combobox which remembers previous setting """ import veusz.qtall as qt4 import veusz.setting as setting class HistoryValueCombo(qt4.QComboBox): """This combobox records what value was previously saved """ def __init__(self, *args): qt4.QComboBox.__init__(self, *args) self.defaultlist = [] self.defaultval = None self.hasshown = False def getSettingName(self): """Get name for saving in settings.""" # get dialog for widget dialog = self.parent() while not isinstance(dialog, qt4.QDialog): dialog = dialog.parent() # combine dialog and object names to make setting return '%s_%s_HistoryValueCombo' % ( dialog.objectName(), self.objectName() ) def saveHistory(self): """Save contents of history combo to settings.""" # only save history if it has been loaded if not self.hasshown: return # collect current items history = [ unicode(self.itemText(i)) for i in xrange(self.count()) ] history.insert(0, unicode(self.currentText())) # remove dups histout = [] histset = set() for item in history: if item not in histset: histout.append(item) histset.add(item) # save the history setting.settingdb[self.getSettingName()] = histout def showEvent(self, event): """Show HistoryCombo and load history.""" qt4.QComboBox.showEvent(self, event) self.clear() self.addItems(self.defaultlist) text = setting.settingdb.get(self.getSettingName(), self.defaultval) if text is not None: indx = self.findText(text) if indx < 0: if self.isEditable(): self.insertItem(0, text) indx = 0 self.setCurrentIndex(indx) self.hasshown = True def hideEvent(self, event): """Save history as widget is hidden.""" qt4.QComboBox.hideEvent(self, event) if self.hasshown: text = unicode(self.currentText()) setting.settingdb[self.getSettingName()] = text veusz-1.15/qtwidgets/datasetbrowser.py0000644002344000001440000006772411734662204020235 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### """A widget for navigating datasets.""" import os.path import numpy as N import textwrap import veusz.qtall as qt4 import veusz.setting as setting import veusz.document as document import veusz.utils as utils from lineeditwithclear import LineEditWithClear from veusz.utils.treemodel import TMNode, TreeModel def datasetLinkFile(ds): """Get a linked filename from a dataset.""" if ds.linked is None: return "/" else: return ds.linked.filename class DatasetNode(TMNode): """Node for a dataset.""" def __init__(self, doc, dsname, cols, parent): ds = doc.data[dsname] data = [] assert cols[0] == "name" for c in cols: if c == "name": data.append( dsname ) elif c == "size": data.append( ds.userSize() ) elif c == "type": data.append( ds.dstype ) elif c == "linkfile": data.append( os.path.basename(datasetLinkFile(ds)) ) TMNode.__init__(self, tuple(data), parent) self.doc = doc self.cols = cols def getPreviewPixmap(self, ds): """Get a preview pixmap for a dataset.""" size = (140, 70) if ds.dimensions != 1 or ds.datatype != "numeric": return None pixmap = qt4.QPixmap(*size) pixmap.fill(qt4.Qt.transparent) p = qt4.QPainter(pixmap) p.setRenderHint(qt4.QPainter.Antialiasing) # calculate data points try: if len(ds.data) < size[1]: y = ds.data else: intvl = len(ds.data)/size[1]+1 y = ds.data[::intvl] x = N.arange(len(y)) # plot data points on image minval, maxval = N.nanmin(y), N.nanmax(y) y = (y-minval) / (maxval-minval) * size[1] finite = N.isfinite(y) x, y = x[finite], y[finite] x = x * (1./len(x)) * size[0] poly = qt4.QPolygonF() utils.addNumpyToPolygonF(poly, x, size[1]-y) p.setPen( qt4.QPen(qt4.Qt.blue) ) p.drawPolyline(poly) # draw x axis if span 0 p.setPen( qt4.QPen(qt4.Qt.black) ) if minval <= 0 and maxval > 0: y0 = size[1] - (0-minval)/(maxval-minval)*size[1] p.drawLine(x[0], y0, x[-1], y0) else: p.drawLine(x[0], size[1], x[-1], size[1]) p.drawLine(x[0], 0, x[0], size[1]) except (ValueError, ZeroDivisionError): # zero sized array after filtering or min == max, so return None p.end() return None p.end() return pixmap def toolTip(self, column): """Return tooltip for column.""" try: ds = self.doc.data[self.data[0]] except KeyError: return qt4.QVariant() c = self.cols[column] if c == "name": text = ds.description() if text is None: text = '' if ds.tags: text += '\n\nTags: %s' % (' '.join(list(sorted(ds.tags)))) return qt4.QVariant(textwrap.fill(text, 40)) elif c == "size" or (c == 'type' and 'size' not in self.cols): text = ds.userPreview() # add preview of dataset if possible pix = self.getPreviewPixmap(ds) if pix: text = text.replace("\n", "
    ") text = "%s
    %s" % (text, utils.pixmapAsHtml(pix)) return qt4.QVariant(text) elif c == "linkfile" or c == "type": return qt4.QVariant(textwrap.fill(ds.linkedInformation(), 40)) return qt4.QVariant() def dataset(self): """Get associated dataset.""" try: return self.doc.data[self.data[0]] except KeyError: return None def datasetName(self): """Get dataset name.""" return self.data[0] def cloneTo(self, newroot): """Make a clone of self at the root given.""" return self.__class__(self.doc, self.data[0], self.cols, newroot) class FilenameNode(TMNode): """A special node for holding filenames of files.""" def nodeData(self, column): """basename of filename for data.""" if column == 0: if self.data[0] == "/": return qt4.QVariant("/") else: return qt4.QVariant(os.path.basename(self.data[0])) return qt4.QVariant() def filename(self): """Return filename.""" return self.data[0] def toolTip(self, column): """Full filename for tooltip.""" if column == 0: return qt4.QVariant(self.data[0]) return qt4.QVariant() def treeFromList(nodelist, rootdata): """Construct a tree from a list of nodes.""" tree = TMNode( rootdata, None ) for node in nodelist: tree.insertChildSorted(node) return tree class DatasetRelationModel(TreeModel): """A model to show how the datasets are related to each file.""" def __init__(self, doc, grouping="filename", readonly=False, filterdims=None, filterdtype=None): """Model parameters: doc: document group: how to group datasets readonly: no modification of data filterdims/filterdtype: filter dimensions and datatypes. """ TreeModel.__init__(self, ("Dataset", "Size", "Type")) self.doc = doc self.linkednodes = {} self.grouping = grouping self.filter = "" self.readonly = readonly self.filterdims = filterdims self.filterdtype = filterdtype self.refresh() self.connect(doc, qt4.SIGNAL("sigModified"), self.refresh) def datasetFilterOut(self, ds, node): """Should dataset be filtered out by filter options.""" filterout = False # is filter text not in node text or text keep = True if self.filter != "": keep = False if any([t.find(self.filter) >= 0 for t in ds.tags]): keep = True if any([t.find(self.filter) >= 0 for t in node.data]): keep = True # check dimensions haven't been filtered if ( self.filterdims is not None and ds.dimensions not in self.filterdims ): filterout = True # check type hasn't been filtered if ( self.filterdtype is not None and ds.datatype not in self.filterdtype ): filterout = True if filterout: return True return not keep def makeGrpTreeNone(self): """Make tree with no grouping.""" tree = TMNode( ("Dataset", "Size", "Type", "File"), None ) for name, ds in self.doc.data.iteritems(): child = DatasetNode( self.doc, name, ("name", "size", "type", "linkfile"), None ) # add if not filtered for filtering if not self.datasetFilterOut(ds, child): tree.insertChildSorted(child) return tree def makeGrpTree(self, coltitles, colitems, grouper, GrpNodeClass): """Make a tree grouping with function: coltitles: tuple of titles of columns for user colitems: tuple of items to lookup in DatasetNode grouper: function of dataset to return text for grouping GrpNodeClass: class for creating grouping nodes """ grpnodes = {} for name, ds in self.doc.data.iteritems(): child = DatasetNode(self.doc, name, colitems, None) # check whether filtered out if not self.datasetFilterOut(ds, child): # get group grps = grouper(ds) for grp in grps: if grp not in grpnodes: grpnodes[grp] = GrpNodeClass( (grp,), None ) # add to group grpnodes[grp].insertChildSorted(child) return treeFromList(grpnodes.values(), coltitles) def makeGrpTreeFilename(self): """Make a tree of datasets grouped by linked file.""" return self.makeGrpTree( ("Dataset", "Size", "Type"), ("name", "size", "type"), lambda ds: (datasetLinkFile(ds),), FilenameNode ) def makeGrpTreeSize(self): """Make a tree of datasets grouped by dataset size.""" return self.makeGrpTree( ("Dataset", "Type", "Filename"), ("name", "type", "linkfile"), lambda ds: (ds.userSize(),), TMNode ) def makeGrpTreeType(self): """Make a tree of datasets grouped by dataset type.""" return self.makeGrpTree( ("Dataset", "Size", "Filename"), ("name", "size", "linkfile"), lambda ds: (ds.dstype,), TMNode ) def makeGrpTreeTags(self): """Make a tree of datasets grouped by tags.""" def getgrp(ds): if ds.tags: return list(sorted(ds.tags)) else: return [u"None"] return self.makeGrpTree( ("Dataset", "Size", "Type", "Filename"), ("name", "size", "type", "linkfile"), getgrp, TMNode ) def flags(self, idx): """Return model flags for index.""" f = TreeModel.flags(self, idx) # allow dataset names to be edited if ( idx.isValid() and isinstance(self.objFromIndex(idx), DatasetNode) and not self.readonly and idx.column() == 0 ): f |= qt4.Qt.ItemIsEditable return f def setData(self, idx, data, role): """Rename dataset.""" dsnode = self.objFromIndex(idx) newname = unicode(data.toString()) if not utils.validateDatasetName(newname) or newname in self.doc.data: return False self.doc.applyOperation( document.OperationDatasetRename(dsnode.data[0], newname)) self.emit( qt4.SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), idx, idx) return True def refresh(self): """Update tree of datasets when document changes.""" header = self.root.data tree = { "none": self.makeGrpTreeNone, "filename": self.makeGrpTreeFilename, "size": self.makeGrpTreeSize, "type": self.makeGrpTreeType, "tags": self.makeGrpTreeTags, }[self.grouping]() self.syncTree(tree) class DatasetsNavigatorTree(qt4.QTreeView): """Tree view for dataset names.""" def __init__(self, doc, mainwin, grouping, parent, readonly=False, filterdims=None, filterdtype=None): """Initialise the dataset tree view. doc: veusz document mainwin: veusz main window (or None if readonly) grouping: grouping mode of datasets parent: parent window or None filterdims: if set, only show datasets with dimensions given filterdtype: if set, only show datasets with type given """ qt4.QTreeView.__init__(self, parent) self.doc = doc self.mainwindow = mainwin self.model = DatasetRelationModel(doc, grouping, readonly=readonly, filterdims=filterdims, filterdtype=filterdtype) self.setModel(self.model) self.setSelectionMode(qt4.QTreeView.ExtendedSelection) self.setSelectionBehavior(qt4.QTreeView.SelectRows) self.setUniformRowHeights(True) self.setContextMenuPolicy(qt4.Qt.CustomContextMenu) if not readonly: self.connect(self, qt4.SIGNAL("customContextMenuRequested(QPoint)"), self.showContextMenu) self.model.refresh() self.expandAll() # stretch of columns hdr = self.header() hdr.setStretchLastSection(False) hdr.setResizeMode(0, qt4.QHeaderView.Stretch) for col in xrange(1, 3): hdr.setResizeMode(col, qt4.QHeaderView.ResizeToContents) # when documents have finished opening, expand all nodes if mainwin is not None: self.connect(mainwin, qt4.SIGNAL("documentopened"), self.expandAll) # keep track of selection self.connect( self.selectionModel(), qt4.SIGNAL("selectionChanged(const QItemSelection&, " "const QItemSelection&)"), self.slotNewSelection ) # expand nodes by default self.connect( self.model, qt4.SIGNAL("rowsInserted(const QModelIndex&, int, int)"), self.slotNewRow ) def changeGrouping(self, grouping): """Change the tree grouping behaviour.""" self.model.grouping = grouping self.model.refresh() self.expandAll() def changeFilter(self, filtertext): """Change filtering text.""" self.model.filter = filtertext self.model.refresh() self.expandAll() def selectDataset(self, dsname): """Find, and if possible select dataset name.""" matches = self.model.match( self.model.index(0, 0, qt4.QModelIndex()), qt4.Qt.DisplayRole, qt4.QVariant(dsname), -1, qt4.Qt.MatchFixedString | qt4.Qt.MatchCaseSensitive | qt4.Qt.MatchRecursive ) for idx in matches: if isinstance(self.model.objFromIndex(idx), DatasetNode): self.selectionModel().setCurrentIndex( idx, qt4.QItemSelectionModel.SelectCurrent | qt4.QItemSelectionModel.Clear | qt4.QItemSelectionModel.Rows ) def showContextMenu(self, pt): """Context menu for nodes.""" # get selected nodes idxs = self.selectionModel().selection().indexes() nodes = [ self.model.objFromIndex(i) for i in idxs if i.column() == 0 ] # unique list of types of nodes types = utils.unique([ type(n) for n in nodes ]) menu = qt4.QMenu() # put contexts onto submenus if multiple types selected if DatasetNode in types: thismenu = menu if len(types) > 1: thismenu = menu.addMenu("Datasets") self.datasetContextMenu( [n for n in nodes if isinstance(n, DatasetNode)], thismenu) elif FilenameNode in types: thismenu = menu if len(types) > 1: thismenu = menu.addMenu("Files") self.filenameContextMenu( [n for n in nodes if isinstance(n, FilenameNode)], thismenu) def _paste(): """Paste dataset(s).""" if document.isDataMime(): mime = qt4.QApplication.clipboard().mimeData() self.doc.applyOperation(document.OperationDataPaste(mime)) # if there is data to paste, add menu item if document.isDataMime(): menu.addAction("Paste", _paste) if len( menu.actions() ) != 0: menu.exec_(self.mapToGlobal(pt)) def datasetContextMenu(self, dsnodes, menu): """Return context menu for datasets.""" import veusz.dialogs.dataeditdialog as dataeditdialog datasets = [d.dataset() for d in dsnodes] dsnames = [d.datasetName() for d in dsnodes] def _edit(): """Open up dialog box to recreate dataset.""" for dataset, dsname in zip(datasets, dsnames): if type(dataset) in dataeditdialog.recreate_register: dataeditdialog.recreate_register[type(dataset)]( self.mainwindow, self.doc, dataset, dsname) def _edit_data(): """Open up data edit dialog.""" for dataset, dsname in zip(datasets, dsnames): if type(dataset) not in dataeditdialog.recreate_register: dialog = self.mainwindow.slotDataEdit(editdataset=dsname) def _delete(): """Simply delete dataset.""" self.doc.applyOperation( document.OperationMultiple( [document.OperationDatasetDelete(n) for n in dsnames], descr='delete dataset(s)')) def _unlink_file(): """Unlink dataset from file.""" self.doc.applyOperation( document.OperationMultiple( [document.OperationDatasetUnlinkFile(n) for d,n in zip(datasets,dsnames) if d.canUnlink() and d.linked], descr='unlink dataset(s)')) def _unlink_relation(): """Unlink dataset from relation.""" self.doc.applyOperation( document.OperationMultiple( [document.OperationDatasetUnlinkRelation(n) for d,n in zip(datasets,dsnames) if d.canUnlink() and not d.linked], descr='unlink dataset(s)')) def _copy(): """Copy data to clipboard.""" mime = document.generateDatasetsMime(dsnames, self.doc) qt4.QApplication.clipboard().setMimeData(mime) # editing recreate = [type(d) in dataeditdialog.recreate_register for d in datasets] if any(recreate): menu.addAction("Edit", _edit) if not all(recreate): menu.addAction("Edit data", _edit_data) # deletion menu.addAction("Delete", _delete) # linking unlink_file = [d.canUnlink() and d.linked for d in datasets] if any(unlink_file): menu.addAction("Unlink file", _unlink_file) unlink_relation = [d.canUnlink() and not d.linked for d in datasets] if any(unlink_relation): menu.addAction("Unlink relation", _unlink_relation) # tagging submenu tagmenu = menu.addMenu("Tags") for tag in self.doc.datasetTags(): def toggle(tag=tag): state = [tag in d.tags for d in datasets] if all(state): op = document.OperationDataUntag else: op = document.OperationDataTag self.doc.applyOperation(op(tag, dsnames)) a = tagmenu.addAction(tag, toggle) a.setCheckable(True) state = [tag in d.tags for d in datasets] a.setChecked(all([tag in d.tags for d in datasets])) def addtag(): tag, ok = qt4.QInputDialog.getText( self, "New tag", "Enter new tag") if ok: tag = unicode(tag).strip().replace(' ', '') if tag: self.doc.applyOperation( document.OperationDataTag( tag, dsnames) ) tagmenu.addAction("Add...", addtag) # copy menu.addAction("Copy", _copy) if len(datasets) == 1: useasmenu = menu.addMenu("Use as") self.getMenuUseAs(useasmenu, datasets[0]) def filenameContextMenu(self, nodes, menu): """Return context menu for filenames.""" from veusz.dialogs.reloaddata import ReloadData filenames = [n.filename() for n in nodes if n.filename() != '/'] if not filenames: return def _reload(): """Reload data in this file.""" d = ReloadData(self.doc, self.mainwindow, filenames=set(filenames)) self.mainwindow.showDialog(d) def _unlink_all(): """Unlink all datasets associated with file.""" self.doc.applyOperation( document.OperationMultiple( [document.OperationDatasetUnlinkByFile(f) for f in filenames], descr='unlink by file')) def _delete_all(): """Delete all datasets associated with file.""" self.doc.applyOperation( document.OperationMultiple( [document.OperationDatasetDeleteByFile(f) for f in filenames], descr='delete by file')) menu.addAction("Reload", _reload) menu.addAction("Unlink all", _unlink_all) menu.addAction("Delete all", _delete_all) def getMenuUseAs(self, menu, dataset): """Build up menu of widget settings to use dataset in.""" def addifdatasetsetting(path, setn): def _setdataset(): self.doc.applyOperation( document.OperationSettingSet( path, self.doc.datasetName(dataset)) ) if ( isinstance(setn, setting.Dataset) and setn.dimensions == dataset.dimensions and setn.datatype == dataset.datatype and path[:12] != "/StyleSheet/" ): menu.addAction(path, _setdataset) self.doc.walkNodes(addifdatasetsetting, nodetypes=("setting",)) def keyPressEvent(self, event): """Enter key selects widget.""" if event.key() in (qt4.Qt.Key_Return, qt4.Qt.Key_Enter): self.emit(qt4.SIGNAL("updateitem")) return qt4.QTreeView.keyPressEvent(self, event) def mouseDoubleClickEvent(self, event): """Emit updateitem signal if double clicked.""" retn = qt4.QTreeView.mouseDoubleClickEvent(self, event) self.emit(qt4.SIGNAL("updateitem")) return retn def slotNewSelection(self, selected, deselected): """Emit selecteditem signal on new selection.""" self.emit(qt4.SIGNAL("selecteditem"), self.getSelectedDataset()) def slotNewRow(self, parent, start, end): """Expand parent if added.""" self.expand(parent) def getSelectedDataset(self): """Return selected dataset.""" name = None sel = self.selectionModel().selection() try: modelidx = sel.indexes()[0] node = self.model.objFromIndex(modelidx) if isinstance(node, DatasetNode): name = node.datasetName() except IndexError: pass return name class DatasetBrowser(qt4.QWidget): """Widget which shows the document's datasets.""" # how datasets can be grouped grpnames = ("none", "filename", "type", "size", "tags") grpentries = { "none": "None", "filename": "Filename", "type": "Type", "size": "Size", "tags": "Tags", } def __init__(self, thedocument, mainwin, parent, readonly=False, filterdims=None, filterdtype=None): """Initialise widget: thedocument: document to show mainwin: main window of application (or None if readonly) parent: parent of widget. readonly: for choosing datasets only filterdims: if set, only show datasets with dimensions given filterdtype: if set, only show datasets with type given """ qt4.QWidget.__init__(self, parent) self.layout = qt4.QVBoxLayout() self.setLayout(self.layout) # options for navigator are in this layout self.optslayout = qt4.QHBoxLayout() # grouping options - use a menu to choose the grouping self.grpbutton = qt4.QPushButton("Group") self.grpmenu = qt4.QMenu() self.grouping = setting.settingdb.get("navtree_grouping", "filename") self.grpact = qt4.QActionGroup(self) self.grpact.setExclusive(True) for name in self.grpnames: a = self.grpmenu.addAction(self.grpentries[name]) a.grpname = name a.setCheckable(True) if name == self.grouping: a.setChecked(True) self.grpact.addAction(a) self.connect(self.grpact, qt4.SIGNAL("triggered(QAction*)"), self.slotGrpChanged) self.grpbutton.setMenu(self.grpmenu) self.grpbutton.setToolTip("Group datasets with property given") self.optslayout.addWidget(self.grpbutton) # filtering by entering text self.optslayout.addWidget(qt4.QLabel("Filter")) self.filteredit = LineEditWithClear() self.filteredit.setToolTip("Enter text here to filter datasets") self.connect(self.filteredit, qt4.SIGNAL("textChanged(const QString&)"), self.slotFilterChanged) self.optslayout.addWidget(self.filteredit) self.layout.addLayout(self.optslayout) # the actual widget tree self.navtree = DatasetsNavigatorTree( thedocument, mainwin, self.grouping, None, readonly=readonly, filterdims=filterdims, filterdtype=filterdtype) self.layout.addWidget(self.navtree) def slotGrpChanged(self, action): """Grouping changed by user.""" self.navtree.changeGrouping(action.grpname) setting.settingdb["navtree_grouping"] = action.grpname def slotFilterChanged(self, filtertext): """Filtering changed by user.""" self.navtree.changeFilter(unicode(filtertext)) def selectDataset(self, dsname): """Find, and if possible select dataset name.""" self.navtree.selectDataset(dsname) class DatasetBrowserPopup(DatasetBrowser): """Popup window for dataset browser for selecting datasets. This is used by setting.controls.Dataset """ def __init__(self, document, dsname, parent, filterdims=None, filterdtype=None): """Open popup window for document dsname: dataset name parent: window parent filterdims: if set, only show datasets with dimensions given filterdtype: if set, only show datasets with type given """ DatasetBrowser.__init__(self, document, None, parent, readonly=True, filterdims=filterdims, filterdtype=filterdtype) self.setWindowFlags(qt4.Qt.Popup) self.setAttribute(qt4.Qt.WA_DeleteOnClose) self.spacing = self.fontMetrics().height() utils.positionFloatingPopup(self, parent) self.selectDataset(dsname) self.installEventFilter(self) self.navtree.setFocus() self.connect(self.navtree, qt4.SIGNAL("updateitem"), self.slotUpdateItem) def eventFilter(self, node, event): """Grab clicks outside this window to close it.""" if ( isinstance(event, qt4.QMouseEvent) and event.buttons() != qt4.Qt.NoButton ): frame = qt4.QRect(0, 0, self.width(), self.height()) if not frame.contains(event.pos()): self.close() return True return qt4.QTextEdit.eventFilter(self, node, event) def sizeHint(self): """A reasonable size for the text editor.""" return qt4.QSize(self.spacing*30, self.spacing*20) def closeEvent(self, event): """Tell the calling widget that we are closing.""" self.emit(qt4.SIGNAL("closing")) event.accept() def slotUpdateItem(self): """Emit new dataset signal.""" selected = self.navtree.selectionModel().currentIndex() if selected.isValid(): n = self.navtree.model.objFromIndex(selected) if isinstance(n, DatasetNode): self.emit(qt4.SIGNAL("newdataset"), n.data[0]) self.close() veusz-1.15/qtwidgets/historyspinbox.py0000644002344000001440000000427211734662204020275 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting class HistorySpinBox(qt4.QSpinBox): """A SpinBox which remembers its setting between calls.""" def __init__(self, *args): qt4.QSpinBox.__init__(self, *args) self.default = 0 def getSettingName(self): """Get name for saving in settings.""" # get dialog for widget dialog = self.parent() while not isinstance(dialog, qt4.QDialog): dialog = dialog.parent() # combine dialog and object names to make setting return "%s_%s_HistorySpinBox" % ( dialog.objectName(), self.objectName() ) def loadHistory(self): """Load contents of HistorySpinBox from settings.""" num = setting.settingdb.get(self.getSettingName(), self.default) self.setValue(num) def saveHistory(self): """Save contents of HistorySpinBox to settings.""" setting.settingdb[self.getSettingName()] = self.value() def showEvent(self, event): """Show HistorySpinBox and load history.""" qt4.QSpinBox.showEvent(self, event) self.loadHistory() def hideEvent(self, event): """Save history as widget is hidden.""" qt4.QSpinBox.hideEvent(self, event) self.saveHistory() veusz-1.15/qtwidgets/lineeditwithclear.py0000644002344000001440000000546311734662204020674 0ustar jssusers00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################### import veusz.qtall as qt4 import veusz.utils as utils class LineEditWithClear(qt4.QLineEdit): """This is a line edit widget which supplies a clear button to delete the text if it is clicked. Adapted from: http://labs.qt.nokia.com/2007/06/06/lineedit-with-a-clear-button/ """ def __init__(self, *args): """Initialise the line edit.""" qt4.QLineEdit.__init__(self, *args) # the clear button itself, with no padding self.clearbutton = cb = qt4.QToolButton(self) cb.setIcon( utils.getIcon('kde-edit-delete') ) cb.setCursor(qt4.Qt.ArrowCursor) cb.setStyleSheet('QToolButton { border: none; padding: 0px; }') cb.setToolTip("Clear text") cb.hide() # make clicking on the button clear the text self.connect(cb, qt4.SIGNAL('clicked()'), self, qt4.SLOT("clear()")) # button should appear if there is text self.connect(self, qt4.SIGNAL('textChanged(const QString&)'), self.updateCloseButton) # positioning of the button fw = self.style().pixelMetric(qt4.QStyle.PM_DefaultFrameWidth) self.setStyleSheet("QLineEdit { padding-right: %ipx; } " % (cb.sizeHint().width() + fw + 1)) msz = self.minimumSizeHint() mx = cb.sizeHint().height()+ fw*2 + 2 self.setMinimumSize( max(msz.width(), mx), max(msz.height(), mx) ) def resizeEvent(self, evt): """Move button if widget resized.""" sz = self.clearbutton.sizeHint() fw = self.style().pixelMetric(qt4.QStyle.PM_DefaultFrameWidth) r = self.rect() self.clearbutton.move( r.right() - fw - sz.width(), (r.bottom() + 1 - sz.height())/2 ) def updateCloseButton(self, text): """Button should only appear if there is text.""" self.clearbutton.setVisible( not text.isEmpty() ) veusz-1.15/qtwidgets/recentfilesbutton.py0000644002344000001440000000536311734662204020732 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import os.path import veusz.qtall as qt4 import veusz.setting as setting def removeBadRecents(itemlist): """Remove duplicates from list and bad entries.""" previous = set() i = 0 while i < len(itemlist): if itemlist[i] in previous: del itemlist[i] elif not os.path.exists(itemlist[i]): del itemlist[i] else: previous.add(itemlist[i]) i += 1 # trim list del itemlist[10:] class RecentFilesButton(qt4.QPushButton): """A button for remembering recent files. emits filechosen(filename) if a file is chosen """ def __init__(self, *args): qt4.QPushButton.__init__(self, *args) self.menu = qt4.QMenu() self.setMenu(self.menu) self.settingname = None def setSetting(self, name): """Specify settings to use when loading menu. Should be called before use.""" self.settingname = name self.fillMenu() def fillMenu(self): """Add filenames to menu.""" self.menu.clear() recent = setting.settingdb.get(self.settingname, []) removeBadRecents(recent) setting.settingdb[self.settingname] = recent for filename in recent: if os.path.exists(filename): act = self.menu.addAction( os.path.basename(filename) ) def loadRecentFile(filename=filename): self.emit(qt4.SIGNAL('filechosen'), filename) self.connect( act, qt4.SIGNAL('triggered()'), loadRecentFile ) def addFile(self, filename): """Add filename to list of recent files.""" recent = setting.settingdb.get(self.settingname, []) recent.insert(0, os.path.abspath(filename)) setting.settingdb[self.settingname] = recent self.fillMenu() veusz-1.15/qtwidgets/historycheck.py0000644002344000001440000000461211734662204017666 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting class HistoryCheck(qt4.QCheckBox): """Checkbox remembers its setting between calls """ def __init__(self, *args): qt4.QCheckBox.__init__(self, *args) self.default = False def getSettingName(self): """Get name for saving in settings.""" # get dialog for widget dialog = self.parent() while not isinstance(dialog, qt4.QDialog): dialog = dialog.parent() # combine dialog and object names to make setting return '%s_%s_HistoryCheck' % ( dialog.objectName(), self.objectName() ) def loadHistory(self): """Load contents of HistoryCheck from settings.""" checked = setting.settingdb.get(self.getSettingName(), self.default) # this is to ensure toggled() signals get sent self.setChecked(not checked) self.setChecked(checked) def saveHistory(self): """Save contents of HistoryCheck to settings.""" setting.settingdb[self.getSettingName()] = self.isChecked() def showEvent(self, event): """Show HistoryCheck and load history.""" qt4.QCheckBox.showEvent(self, event) # we do this now rather than in __init__ because the widget # has no name set at __init__ self.loadHistory() def hideEvent(self, event): """Save history as widget is hidden.""" qt4.QCheckBox.hideEvent(self, event) self.saveHistory() veusz-1.15/qtwidgets/historygroupbox.py0000644002344000001440000000552511734662204020462 0ustar jssusers00000000000000# Copyright (C) 2010 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## import veusz.qtall as qt4 import veusz.setting as setting class HistoryGroupBox(qt4.QGroupBox): """Group box remembers settings of radio buttons inside it. emits radioClicked(radiowidget) when clicked """ def getSettingName(self): """Get name for saving in settings.""" # get dialog for widget dialog = self.parent() while not isinstance(dialog, qt4.QDialog): dialog = dialog.parent() # combine dialog and object names to make setting return '%s_%s_HistoryGroup' % ( dialog.objectName(), self.objectName() ) def loadHistory(self): """Load from settings.""" # connect up radio buttons to emit clicked signal for w in self.children(): if isinstance(w, qt4.QRadioButton): def doemit(w=w): self.emit(qt4.SIGNAL("radioClicked"), w) self.connect( w, qt4.SIGNAL('clicked()'), doemit) # set item to be checked checked = setting.settingdb.get(self.getSettingName(), "") for w in self.children(): if isinstance(w, qt4.QRadioButton) and ( w.objectName() == checked or checked == ""): w.click() return def getRadioChecked(self): """Get name of radio button checked.""" for w in self.children(): if isinstance(w, qt4.QRadioButton) and w.isChecked(): return w return None def saveHistory(self): """Save to settings.""" name = unicode(self.getRadioChecked().objectName()) setting.settingdb[self.getSettingName()] = name def showEvent(self, event): """Show and load history.""" qt4.QGroupBox.showEvent(self, event) self.loadHistory() def hideEvent(self, event): """Save history as widget is hidden.""" qt4.QGroupBox.hideEvent(self, event) self.saveHistory() veusz-1.15/qtwidgets/__init__.py0000644002344000001440000000301611734662204016723 0ustar jssusers00000000000000# Copyright (C) 2011 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """Veusz qtwidgets module.""" # insert history combo into the list of modules so that it can be found # by loadUi - yuck import sys import historycombo import historycheck import historyvaluecombo import historygroupbox import historyspinbox import recentfilesbutton import lineeditwithclear sys.modules['historycombo'] = historycombo sys.modules['historycheck'] = historycheck sys.modules['historyvaluecombo'] = historyvaluecombo sys.modules['historygroupbox'] = historygroupbox sys.modules['historyspinbox'] = historyspinbox sys.modules['recentfilesbutton'] = recentfilesbutton sys.modules['lineeditwithclear'] = lineeditwithclear veusz-1.15/qtwidgets/historycombo.py0000644002344000001440000001077211734662204017714 0ustar jssusers00000000000000# Copyright (C) 2009 Jeremy S. Sanders # Email: Jeremy Sanders # # 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 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ############################################################################## """A combobox which remembers its history. The history is stored in the Veusz settings database. """ import veusz.qtall as qt4 import veusz.setting as setting class HistoryCombo(qt4.QComboBox): """This combobox records what items have been entered into it so the user can choose them again. Duplicates and blanks are ignored. """ def __init__(self, *args): qt4.QComboBox.__init__(self, *args) # sane defaults self.setEditable(True) self.setAutoCompletion(True) self.setMaxCount(50) self.setInsertPolicy(qt4.QComboBox.InsertAtTop) self.setDuplicatesEnabled(False) self.setSizePolicy( qt4.QSizePolicy(qt4.QSizePolicy.MinimumExpanding, qt4.QSizePolicy.Fixed) ) # stops combobox readjusting in size to fit contents self.setSizeAdjustPolicy( qt4.QComboBox.AdjustToMinimumContentsLengthWithIcon) self.default = [] self.hasshown = False def text(self): """Get text in combobox - this gives it the same interface as QLineEdit.""" return self.currentText() def setText(self, text): """Set text in combobox - gives same interface as QLineEdit.""" self.lineEdit().setText(text) def hasAcceptableInput(self): """Input valid? - gives same interface as QLineEdit.""" return self.lineEdit().hasAcceptableInput() def replaceAndAddHistory(self, item): """Replace the text and place item at top of history.""" self.lineEdit().setText(item) index = self.findText(item) # lookup for existing item (if any) if index != -1: # remove any old items matching this self.removeItem(index) # put new item in self.insertItem(0, item) # set selected item in drop down list match current item self.setCurrentIndex(0) def getSettingName(self): """Get name for saving in settings.""" # get dialog for widget dialog = self.parent() while not isinstance(dialog, qt4.QDialog): dialog = dialog.parent() # combine dialog and object names to make setting return '%s_%s_HistoryCombo' % ( dialog.objectName(), self.objectName() ) def loadHistory(self): """Load contents of history combo from settings.""" self.clear() history = setting.settingdb.get(self.getSettingName(), self.default) self.insertItems(0, history) self.hasshown = True def saveHistory(self): """Save contents of history combo to settings.""" # only save history if it has been loaded if not self.hasshown: return # collect current items history = [ unicode(self.itemText(i)) for i in xrange(self.count()) ] history.insert(0, unicode(self.currentText())) # remove dups histout = [] histset = set() for item in history: if item not in histset: histout.append(item) histset.add(item) # save the history setting.settingdb[self.getSettingName()] = histout def showEvent(self, event): """Show HistoryCombo and load history.""" qt4.QComboBox.showEvent(self, event) # we do this now rather than in __init__ because the widget # has no name set at __init__ self.loadHistory() def hideEvent(self, event): """Save history as widget is hidden.""" qt4.QComboBox.hideEvent(self, event) self.saveHistory()